feat: A lot of changes

This commit is contained in:
nm17 2024-02-24 19:46:54 +04:00
parent 824e382e89
commit b0f3343a27
Signed by: nm17
GPG key ID: 3303B70C59145CD4
20 changed files with 1256 additions and 532 deletions

1067
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,10 +7,11 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.71" anyhow = "1.0.71"
axum = { version = "0.6.18", features = ["http2", "headers", "macros"] } bstr = { version = "1.9.0", features = ["serde"] }
bytes = { version = "1.4.0", features = ["serde"] } bytes = { version = "1.4.0", features = ["serde"] }
chrono = { version = "0.4.26", features = ["serde"] } chrono = { version = "0.4.26", features = ["serde"] }
clap = { version = "4.3.8", features = ["derive", "env"] } clap = { version = "4.3.8", features = ["derive", "env"] }
derive_more = "0.99.17"
dotenvy = "0.15.7" dotenvy = "0.15.7"
fred = { version = "6.3.0", features = ["nom"] } fred = { version = "6.3.0", features = ["nom"] }
heapless = { version = "0.7.16", features = ["ufmt-impl"] } heapless = { version = "0.7.16", features = ["ufmt-impl"] }
@ -18,11 +19,14 @@ hex = { version = "0.4.3", default-features = false }
hifitime = "3.8.2" hifitime = "3.8.2"
lazy_static = "1.4.0" lazy_static = "1.4.0"
nom = { version = "7.1.3", default-features = false, features = ["std", "alloc"] } nom = { version = "7.1.3", default-features = false, features = ["std", "alloc"] }
ntex = { version = "1.1.0", features = ["tokio", "cookie", "url"] }
phf = { version = "0.11.2", features = ["serde", "macros"] } phf = { version = "0.11.2", features = ["serde", "macros"] }
regex = "1.8.4" regex = "1.8.4"
rust_decimal = { version = "1.30.0", features = ["rkyv", "rkyv-safe"] } rust_decimal = { version = "1.30.0", features = ["rkyv", "rkyv-safe"] }
serde = { version = "1.0.164", features = ["derive", "alloc"] } serde = { version = "1.0.164", features = ["derive", "alloc"] }
serde_json = "1.0.99" serde_json = "1.0.99"
serde_qs = "0.12.0"
serde_with = { version = "3.6.1", features = ["hex"] }
smallstr = { version = "0.3.0", features = ["std", "union"] } smallstr = { version = "0.3.0", features = ["std", "union"] }
thiserror = "1.0.40" thiserror = "1.0.40"
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }

View file

@ -1,15 +1,15 @@
use std::borrow::Cow;
use phf::phf_map; use phf::phf_map;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Hash, PartialEq, Eq)]
#[repr(u64)] #[repr(u64)]
pub enum SupportedUnit { pub enum SupportedUnit {
Celsius, // Needs verification > 273.15 Celsius, // Needs verification > 273.15
Percentage, // Needs verification >= 100 && <= 0 Percentage, // Needs verification >= 100 && <= 0
MillimeterHg, // Needs verification MillimeterHg, // Needs verification
UVIndex, // Needs verification UVIndex, // Needs verification
Boolean, // Needs verification Boolean, // Needs verification and possible parsing
Kbps, Kbps,
Volume, Volume,
KWh, KWh,
@ -17,12 +17,13 @@ pub enum SupportedUnit {
Volts, Volts,
Watts, Watts,
Seconds, Seconds,
Hertz,
} }
impl Serialize for SupportedUnit { impl Serialize for SupportedUnit {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
serializer.serialize_str(match self { serializer.serialize_str(match self {
SupportedUnit::Celsius => "C", SupportedUnit::Celsius => "C",
@ -36,15 +37,16 @@ impl Serialize for SupportedUnit {
SupportedUnit::Volts => "V", SupportedUnit::Volts => "V",
SupportedUnit::Watts => "W", SupportedUnit::Watts => "W",
SupportedUnit::Seconds => "s", SupportedUnit::Seconds => "s",
SupportedUnit::KWh => "KWh" SupportedUnit::KWh => "KWh",
SupportedUnit::Hertz => "Hz",
}) })
} }
} }
impl<'de> Deserialize<'de> for SupportedUnit { impl<'de> Deserialize<'de> for SupportedUnit {
fn deserialize<'a, D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<'a, D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let color_str = Cow::<'a, str>::deserialize(deserializer)?; let color_str = Cow::<'a, str>::deserialize(deserializer)?;
match STR_TO_UNITS.get(color_str.as_ref()) { match STR_TO_UNITS.get(color_str.as_ref()) {
@ -54,7 +56,6 @@ impl<'de> Deserialize<'de> for SupportedUnit {
} }
} }
static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! { static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! {
"C" => SupportedUnit::Celsius, "C" => SupportedUnit::Celsius,
"%" => SupportedUnit::Percentage, "%" => SupportedUnit::Percentage,

View file

@ -1,19 +1,20 @@
use std::fmt::{Display, Formatter}; use bstr::BStr;
use std::num::ParseFloatError;
use nom::error::VerboseError; use nom::error::VerboseError;
use std::fmt::{Debug, Display};
use std::num::ParseFloatError;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
#[derive(Debug, ThisError)] #[derive(Debug, ThisError)]
pub enum Error<I: std::fmt::Debug> { pub enum Error<I: Debug> {
#[error("Oops it blew up")] #[error("Nom error: {0}")]
NomError(#[from] nom::Err<VerboseError<I>>), NomError(#[from] nom::Err<VerboseError<I>>),
#[error("Oops it blew up")] #[error("Failed to parse a timestamp")]
TimestampParseError(ParseFloatError), TimestampParseError(ParseFloatError),
#[error("Oops it blew up")] #[error("Unknown unit")]
UnknownUnit(I), UnknownUnit(I),
#[error("Oops it blew up")] #[error("Failed to parse a number")]
DecimalParseError(#[from] rust_decimal::Error) DecimalParseError(#[from] rust_decimal::Error),
} }

View file

@ -1,5 +1,5 @@
mod packet_types;
pub mod error; pub mod error;
mod packet_types;
pub mod parser; pub mod parser;
mod server; mod server;
@ -7,4 +7,3 @@ mod server;
mod tests; mod tests;
pub use packet_types::*; pub use packet_types::*;

View file

@ -1,42 +1,76 @@
use crate::hashes::SupportedUnit;
use crate::ingest_protocol::error::Error;
use crate::ingest_protocol::parser::parse_mac_address;
use bstr::BStr;
use hifitime::Epoch;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_with::formats::Separator;
use serde_with::serde_as;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use hifitime::Epoch;
use rust_decimal::Decimal;
use crate::hashes::SupportedUnit;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SensorValue<'a> { pub struct SensorValue {
pub mac: Cow<'a, str>, pub mac: String,
pub value: Decimal, pub value: Decimal,
pub time: Option<Epoch>, pub time: Option<Epoch>,
pub unit: Option<SupportedUnit>, pub unit: Option<SupportedUnit>,
pub name: Option<Cow<'a, str>>, pub name: Option<String>,
} }
impl<'a> Hash for SensorValue<'a> { impl Hash for SensorValue {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.mac.hash(state); self.mac.hash(state)
} }
} }
impl<'a> PartialEq for SensorValue<'a> { pub struct DashSeparator {}
fn eq(&self, other: &Self) -> bool {
self.mac == other.mac impl Separator for DashSeparator {
fn separator() -> &'static str {
"-"
} }
} }
impl<'a> Eq for SensorValue<'a> { fn mac_as_array(value: &str) -> Result<[u8; 6], Error<&str>> {
Ok(parse_mac_address(value)?.1)
} }
#[derive(Debug, Clone, Default)] serde_with::serde_conv!(
pub struct NarodMonPacket<'a> { MacAsArray,
[u8; 6],
|rgb: &[u8; 6]| rgb.to_owned(),
mac_as_array
);
#[serde_as]
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
pub struct NMDeviceDataPacket {
#[serde(alias = "ID")]
#[serde_as(as = "MacAsArray")]
pub mac: [u8; 6], pub mac: [u8; 6],
pub name: Option<Cow<'a, str>>,
pub values: HashSet<SensorValue<'a>>, pub name: Option<String>,
pub owner: Option<Cow<'a, str>>,
pub values: HashSet<SensorValue>,
pub owner: Option<String>,
pub lat: Option<Decimal>, pub lat: Option<Decimal>,
pub lon: Option<Decimal>, pub lon: Option<Decimal>,
pub alt: Option<Decimal> pub alt: Option<Decimal>,
}
// HTTP GET/POST url-encode specific parameters
/// TODO: Желательное поведение в будущем:
/// - Если в values есть хотябы один times и этот time не None => Игнорируем этот time
/// - Если time нет у values => используем этот time (если он None, то соотв. считаем что информации о времени нет)
/// TODO: В базе всё должно храниться как секунды по TAI спустя J1900 для возможности сортировки по времени позже.
pub time: Option<Epoch>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
pub struct NMJsonPacket {
pub devices: Vec<NMDeviceDataPacket>,
}

View file

@ -1,56 +1,52 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::str::FromStr;
use hifitime::Epoch; use hifitime::Epoch;
use nom::{InputTake, Needed, Parser};
use nom::branch::alt;
use nom::bytes::complete::{take_until, take_until1, take_while_m_n};
use nom::bytes::complete::{take_till1, take, take_while, take_while1};
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::streaming::take_till; use nom::bytes::complete::take_until1;
use nom::character::streaming::{anychar, char, hex_digit0, newline}; use nom::bytes::complete::{take, take_while, take_while1};
use nom::character::complete::hex_digit1; use nom::character::complete::hex_digit1;
use nom::character::is_digit; use nom::{InputTake, Parser};
use std::str::FromStr;
use nom::combinator::{map, map_opt, map_parser, opt, recognize, rest};
use nom::Err as NomErr;
use nom::error::{context, ContextError, ErrorKind as NomErrorKind, ParseError as NomParseError, Error as NomError, VerboseError};
use nom::multi::{count, separated_list0, separated_list1};
use nom::sequence::{delimited, preceded, separated_pair};
use rust_decimal::Decimal;
use crate::ingest_protocol::error::Error; use crate::ingest_protocol::error::Error;
use crate::ingest_protocol::{NarodMonPacket, SensorValue};
use crate::ingest_protocol::error::Error::TimestampParseError; use crate::ingest_protocol::error::Error::TimestampParseError;
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
use nom::combinator::{map_parser, opt};
use nom::error::context;
use nom::multi::{count, separated_list0};
use nom::sequence::{delimited, preceded};
use rust_decimal::Decimal;
type MyIError<I, O> = Result<(I, O), Error<I>>; type MyIError<I, O> = Result<(I, O), Error<I>>;
pub fn parse_mac_address(input: &str) -> MyIError<&str, [u8; 6]> { pub fn parse_mac_address(input: &str) -> MyIError<&str, [u8; 6]> {
let mut mac = [0u8; 6]; let mut mac = [0u8; 6];
let mut counter = 0; let mut counter = 0;
let (leftovers, i) = context("17 символов для MAC адреса", take(17usize))(input)?; let (leftovers, i) =
context("17 символов для MAC адреса", take(17usize))(input)?;
let (_, out) = count(|inp| { let (_, out) = count(
let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?; |inp| {
if counter != 5 { let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
(i, _) = tag("-")(i)?; if counter != 5 {
} (i, _) = tag("-")(i)?;
counter += 1; }
Ok((i, o)) counter += 1;
}, 6)(i)?; Ok((i, o))
},
6,
)(i)?;
hex::decode_to_slice(out.join(""), &mut mac).unwrap(); hex::decode_to_slice(out.join(""), &mut mac).unwrap();
Ok((leftovers, mac)) Ok((leftovers, mac))
} }
fn handle_special_sensor_macs<I: std::fmt::Debug> ( fn handle_special_sensor_macs<'a>(
mac: &str, mac: &'a str,
sensor_value: &str, sensor_value: &str,
packet: &mut NarodMonPacket, packet: &mut NMDeviceDataPacket,
) -> Result<bool, Error<I>> { ) -> Result<bool, Error<&'a str>> {
match mac.to_uppercase().as_str() { match mac.to_uppercase().as_str() {
"LAT" => packet.lat = Some(Decimal::from_str(sensor_value)?), "LAT" => packet.lat = Some(Decimal::from_str(sensor_value)?),
"LON" => packet.lon = Some(Decimal::from_str(sensor_value)?), "LON" => packet.lon = Some(Decimal::from_str(sensor_value)?),
@ -62,7 +58,10 @@ fn handle_special_sensor_macs<I: std::fmt::Debug> (
return Ok(true); return Ok(true);
} }
pub fn parse_packet_body<'a>(line: &'a str, packet: &mut NarodMonPacket<'a>) -> MyIError<&'a str, ()> { pub fn parse_packet_body<'a>(
line: &'a str,
packet: &mut NMDeviceDataPacket,
) -> MyIError<&'a str, ()> {
let (line, _) = tag("#")(line)?; let (line, _) = tag("#")(line)?;
let (line, sensor_mac) = take_while1(|c| c != '\n' && c != '#')(line)?; let (line, sensor_mac) = take_while1(|c| c != '\n' && c != '#')(line)?;
@ -79,17 +78,17 @@ pub fn parse_packet_body<'a>(line: &'a str, packet: &mut NarodMonPacket<'a>) ->
} }
_ => { _ => {
let (line, sensor_value) = take_while1(|c| c != '\n' && c != '#')(line)?; let (line, sensor_value) = take_while1(|c| c != '\n' && c != '#')(line)?;
let (line, sensor_time) = opt(preceded(tag("#"), take_while1(|c| c != '\n' && c != '#' && "1234567890.".contains(c))))(line)?; let (line, sensor_time) = opt(preceded(
tag("#"),
take_while1(|c| c != '\n' && c != '#' && "1234567890.".contains(c)),
))(line)?;
let (line, sensor_name) = opt(preceded(tag("#"), take_while1(|c| c != '\n')))(line)?; let (line, sensor_name) = opt(preceded(tag("#"), take_while1(|c| c != '\n')))(line)?;
let sensor_time = match sensor_time { let sensor_time = match sensor_time {
Some(v) => Some( Some(v) => Some(Epoch::from_unix_seconds(
Epoch::from_unix_seconds( v.parse().map_err(|e| TimestampParseError(e))?,
v.parse().map_err(|e| TimestampParseError(e))? )),
) None => None,
),
None => None
}; };
if !handle_special_sensor_macs(sensor_mac, sensor_value, packet)? { if !handle_special_sensor_macs(sensor_mac, sensor_value, packet)? {
@ -98,31 +97,31 @@ pub fn parse_packet_body<'a>(line: &'a str, packet: &mut NarodMonPacket<'a>) ->
value: Decimal::from_str(sensor_value)?, value: Decimal::from_str(sensor_value)?,
time: sensor_time, // TODO time: sensor_time, // TODO
unit: None, unit: None,
name: sensor_name.map(|v| Cow::from(v)), name: sensor_name.map(|v| v.to_string()),
}); });
} }
} }
} }
return Ok((line, ())) return Ok((line, ()));
} }
pub fn parse_packet(input: &str) -> MyIError<&str, NarodMonPacket> { pub fn parse_packet(input: &str) -> MyIError<&str, NMDeviceDataPacket> {
let (input, _) = tag("#")(input)?; let (input, _) = tag("#")(input)?;
let (input, device_mac) = parse_mac_address(input)?; let (input, device_mac) = parse_mac_address(input)?;
let (input, opt_name) = opt(delimited(tag("#"), take_while(|c| c != '\n'), tag("\n")))(input)?; let (input, opt_name) = opt(delimited(tag("#"), take_while(|c| c != '\n'), tag("\n")))(input)?;
let mut packet = NarodMonPacket::default(); let mut packet = NMDeviceDataPacket::default();
packet.mac = device_mac; packet.mac = device_mac;
let (input, lines) = context( let (input, lines) = context(
"Получение значений до тега терминатора", "Получение значений до тега терминатора",
map_parser( map_parser(
take_until1("##"), take_until1("##"),
separated_list0(tag("\n"), take_while1(|c| c != '\n')) separated_list0(tag("\n"), take_while1(|c| c != '\n')),
) ),
)(input)?; )(input)?;
for line in lines { for line in lines {
@ -131,7 +130,7 @@ pub fn parse_packet(input: &str) -> MyIError<&str, NarodMonPacket> {
let (input, _) = tag("##")(input)?; let (input, _) = tag("##")(input)?;
packet.name = opt_name.map(|v| Cow::from(v)); packet.name = opt_name.map(|v| v.to_string());
Ok((input, packet)) Ok((input, packet))
} }

View file

@ -1,3 +1 @@
async fn main() { async fn main() {}
}

View file

@ -12,7 +12,8 @@ fn test_asd() {
#LON#37.6068 #LON#37.6068
#ALT#38 #ALT#38
## ##
"#.trim(); "#
.trim();
dbg!(parse_packet(asd)); dbg!(parse_packet(asd));
} }
@ -32,6 +33,5 @@ fn test_packet() {
#T2#1.2#3400005345 #T2#1.2#3400005345
##"#; ##"#;
println!("{:#?}", parse_packet(inp)); println!("{:#?}", parse_packet(inp));
} }

View file

@ -37,25 +37,23 @@ C названием и координатами:
extern crate core; extern crate core;
mod hashes; mod hashes;
mod web_server;
mod ingest_protocol; mod ingest_protocol;
mod web_server;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{BTreeSet, HashMap, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
use std::mem::{size_of, size_of_val}; use std::mem::{size_of, size_of_val};
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::str::FromStr; use std::str::FromStr;
use axum::Router;
use axum::routing::post;
use hifitime::Epoch; use hifitime::Epoch;
use rust_decimal::Decimal;
use crate::ingest_protocol::{NarodMonPacket, SensorValue};
use crate::ingest_protocol::error::Error; use crate::ingest_protocol::error::Error;
use crate::ingest_protocol::error::Error::TimestampParseError; use crate::ingest_protocol::error::Error::TimestampParseError;
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
use crate::web_server::old_app_api::old_api_handler; use crate::web_server::old_app_api::old_api_handler;
use crate::web_server::server_main; use crate::web_server::server_main;
use rust_decimal::Decimal;
/*fn parse_sensor_value(input: Vec<&str>) -> MyIError<Vec<&str>, NarodMonValues> { /*fn parse_sensor_value(input: Vec<&str>) -> MyIError<Vec<&str>, NarodMonValues> {
Ok( Ok(
@ -68,20 +66,14 @@ use crate::web_server::server_main;
) )
}*/ }*/
struct Params {}
struct Params { #[ntex::main]
}
#[tokio::main]
async fn main() { async fn main() {
//dotenvy::dotenv().unwrap(); //dotenvy::dotenv().unwrap();
//
let web_server_hndl = tokio::spawn(server_main()); // let web_server_hndl = tokio::spawn(server_main());
//
web_server_hndl.await.unwrap(); // web_server_hndl.await.unwrap();
server_main().await;
} }

View file

@ -1,99 +1,135 @@
use std::borrow::Cow; use derive_more::Display;
use axum::headers::HeaderValue;
use axum::http::StatusCode;
use axum::Json;
use axum::response::{IntoResponse, Response};
use fred::prelude::*; use fred::prelude::*;
use ntex::http::header::{HeaderName, HeaderValue};
use ntex::http::{HeaderMap, Response, StatusCode};
use ntex::web;
use ntex::web::types::Json;
use ntex::web::{App, HttpRequest, HttpResponse};
use std::borrow::Cow;
use std::fmt::format;
use std::str::FromStr;
use thiserror::Error;
use crate::ingest_protocol::error::Error;
use crate::insert_header;
use crate::web_server::old_devices_api::QSParserError;
use rust_decimal::Decimal; use rust_decimal::Decimal;
use serde_json::json; use serde_json::json;
use thiserror::Error;
use ufmt::derive::uDebug; use ufmt::derive::uDebug;
#[derive(Debug, Error)] #[derive(Debug, Error, Display)]
pub enum AppError { pub enum AppError {
#[error("IDK")] #[display(fmt = "IDK")]
JsonError(#[from] serde_json::Error), JsonError(#[from] serde_json::Error),
#[error("IDK")] #[display(fmt = "IDK")]
QSError(QSParserError),
#[display(fmt = "IDK")]
ServerRedisError(#[from] RedisError), ServerRedisError(#[from] RedisError),
#[error("Fuck")] #[display(fmt = "IDK")]
UnknownMethod(String), UnknownMethod(String),
#[error("Fuck")] #[display(fmt = "IDK")]
RequestTooLarge, RequestTooLarge,
#[error("Api")] #[display(fmt = "IDK")]
ApiKeyInvalid { ApiKeyInvalid { reason: &'static str },
reason: &'static str
},
#[error("Fuck")] #[display(fmt = "IDK")]
UnitValidationFailed { UnitValidationFailed {
max: Option<Decimal>, max: Option<Decimal>,
min: Option<Decimal> min: Option<Decimal>,
} },
#[display(fmt = "IDK")]
UnknownBody {
json_err: Option<serde_json::Error>,
query_error: Option<serde_qs::Error>,
},
} }
impl IntoResponse for AppError { impl web::error::WebResponseError for AppError {
fn into_response(self) -> Response { fn status_code(&self) -> StatusCode {
match self {
AppError::JsonError(_) => StatusCode::BAD_REQUEST,
AppError::UnknownMethod(_) => StatusCode::BAD_REQUEST,
AppError::UnitValidationFailed { .. } => StatusCode::BAD_REQUEST,
AppError::RequestTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
AppError::ServerRedisError(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST,
AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST,
AppError::QSError(..) => StatusCode::BAD_REQUEST,
}
}
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
let (status, error_message) = match self { let error_message = match self {
AppError::JsonError(_) => { AppError::JsonError(_) => "Invalid JSON",
(StatusCode::BAD_REQUEST, "Invalid JSON") AppError::UnknownMethod(_) => "Unknown command",
}, AppError::UnitValidationFailed { .. } => "Unknown command",
AppError::UnknownMethod(_) => { AppError::RequestTooLarge => "Request is too large",
(StatusCode::BAD_REQUEST, "Unknown command") AppError::ServerRedisError(_) => "Internal server error",
}, AppError::ApiKeyInvalid { .. } => "API Key invalid",
AppError::UnitValidationFailed { .. } => { AppError::UnknownBody { .. } => {
(StatusCode::BAD_REQUEST, "Unknown command") "Can't figure out where and in what encoding the main data is"
},
AppError::RequestTooLarge => {
(StatusCode::PAYLOAD_TOO_LARGE, "Request is too large")
},
AppError::ServerRedisError(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
},
AppError::ApiKeyInvalid { .. } => {
(StatusCode::BAD_REQUEST, "API Key invalid")
} }
AppError::QSError(..) => "UrlEncoded body or query params are incorrect",
}; };
let body = Json(json!({ let status_code = self.status_code();
"errno": status.as_u16(),
"error": error_message,
}));
let mut resp = (status, body).into_response(); let body = json!({
"errno": status_code.as_u16(),
"error": error_message,
});
let mut resp = HttpResponse::build(status_code).json(&body);
let headers = resp.headers_mut(); let headers = resp.headers_mut();
let error_as_string= format!("{:?}", &self); let error_as_string = format!("{:?}", &self);
match self { match self {
AppError::JsonError(json_err) => { AppError::JsonError(json_err) => {
headers.insert("X-Error-Line", HeaderValue::from(json_err.line())); insert_header!(headers, "X-Error-Line", json_err.line());
headers.insert("X-Error-Column", HeaderValue::from(json_err.column())); insert_header!(headers, "X-Error-Column", json_err.column());
headers.insert("X-Error-Description", HeaderValue::try_from(json_err.to_string().escape_default().collect::<String>()).unwrap()); insert_header!(
}, headers,
"X-Error-Description",
json_err.to_string().escape_default().collect::<String>()
);
}
AppError::UnknownMethod(method) => { AppError::UnknownMethod(method) => {
headers.insert("X-Unknown-Cmd", HeaderValue::try_from(method.escape_default().collect::<String>()).unwrap()); insert_header!(
}, headers,
"X-Unknown-Cmd",
method.escape_default().collect::<String>()
);
}
AppError::RequestTooLarge => { AppError::RequestTooLarge => {
headers.insert("X-Max-Request-Size", HeaderValue::try_from("10 KiB = 10240 bytes").unwrap()); insert_header!(headers, "X-Max-Request-Size", "10 KiB = 10240 bytes");
}, }
AppError::ApiKeyInvalid { reason } => { AppError::ApiKeyInvalid { reason } => {
headers.insert("X-Error-Description", HeaderValue::try_from(reason).unwrap()); insert_header!(headers, "X-Error-Description", *reason);
}
AppError::QSError(err) => match err {
QSParserError::ParsingError(desc) => {
insert_header!(
headers,
"X-Error-Description",
desc.escape_default().to_string()
);
}
_ => {}
}, },
_ => {} _ => {}
}; };
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
headers.insert("X-Full-Error", HeaderValue::try_from(error_as_string).unwrap()); insert_header!(headers, "X-Full-Error", error_as_string);
} }
resp resp
} }
} }

View file

@ -1,26 +1,22 @@
use std::time::Duration; use crate::web_server::old_app_api::old_api_handler;
use tokio;
use axum::{
routing::get,
Router,
};
use axum::error_handling::{HandleError, HandleErrorLayer};
use axum::http::StatusCode;
use axum::routing::post;
use fred::bytes_utils::Str; use fred::bytes_utils::Str;
use fred::prelude::*; use fred::prelude::*;
use crate::web_server::old_app_api::old_api_handler; use std::time::Duration;
use tokio;
pub mod old_app_api;
mod utils;
pub(crate) mod app_error; pub(crate) mod app_error;
pub mod old_app_api;
mod old_devices_api;
pub mod utils;
#[derive(Clone)] #[derive(Clone)]
pub struct NMAppState { pub struct NMAppState {
pub redis_client: RedisClient pub redis_client: RedisClient,
} }
use crate::web_server::old_devices_api::device_handler;
use heapless::String as HeaplessString; use heapless::String as HeaplessString;
use ntex::web;
pub async fn server_main() { pub async fn server_main() {
let config = RedisConfig::default(); let config = RedisConfig::default();
@ -37,16 +33,19 @@ pub async fn server_main() {
println!("Ping result: {}", asd); println!("Ping result: {}", asd);
let state = NMAppState { let state = NMAppState {
redis_client: client redis_client: client,
}; };
let app = Router::new() web::HttpServer::new(move || {
.route("/api", post(old_api_handler)) web::App::new()
.with_state(state); .state(state.clone())
.route("/api", web::post().to(old_api_handler))
.route("/get", web::route().to(device_handler))
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .route("/post", web::route().to(device_handler))
.serve(app.into_make_service()) })
.await .bind(("127.0.0.1", 8080))
.unwrap(); .unwrap()
.run()
.await
.unwrap(); // TODO figure out how to handle what
} }

View file

@ -1,41 +1,44 @@
use std::borrow::Cow; use std::borrow::Cow;
use axum::body::{Body, Bytes, HttpBody};
use axum::extract::State;
use axum::http::Request;
use axum::Json;
use axum::response::{IntoResponse, Response};
use nom::AsBytes;
use serde_json::Value;
use crate::web_server::app_error::AppError; use crate::web_server::app_error::AppError;
use crate::web_server::NMAppState; use crate::web_server::old_app_api::handlers::{app_init, version};
use crate::web_server::old_app_api::handlers::app_init;
use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams}; use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams};
use crate::web_server::utils::redis::is_api_key_valid; use crate::web_server::utils::redis::is_api_key_valid;
use crate::web_server::NMAppState;
use nom::AsBytes;
use ntex::util::Bytes;
use ntex::web;
use ntex::web::types::State;
use serde_json::Value;
pub async fn old_api_handler( pub async fn old_api_handler(
app_state: State<NMAppState>, app_state: State<NMAppState>,
body_bytes: Bytes, body_bytes: Bytes,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl web::Responder, AppError> {
if body_bytes.len() > 10 * 1024 { // 10 KiB if body_bytes.len() > 10 * 1024 {
return Err(AppError::RequestTooLarge) // 10 KiB
return Err(AppError::RequestTooLarge);
} }
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes.as_bytes())?; // TODO: Simd-JSON let body_bytes = body_bytes.as_bytes();
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON
match mandatory_params.cmd.as_ref() {
"version" => return Ok(version((), &app_state).await?),
_ => {}
}
is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?; is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?;
return match mandatory_params.cmd.as_ref() { match mandatory_params.cmd.as_ref() {
"appInit" => { "appInit" => {
let body: AppInitRequest = serde_json::from_slice(body_bytes.as_bytes())?; let body: AppInitRequest = serde_json::from_slice(body_bytes)?;
Ok(app_init(body, app_state).await) return Ok(app_init(body, &app_state).await?);
}
_ => {
Err(AppError::UnknownMethod(mandatory_params.cmd.to_string()))
} }
_ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())),
} }
//Ok("fuck") //Ok("fuck")
} }

View file

@ -1,20 +1,36 @@
use axum::body::Body;
use axum::extract::State;
use axum::http::{Request, StatusCode};
use axum::Json;
use axum::response::IntoResponse;
use serde_json::Value as JsonValue;
use crate::web_server::old_app_api::types::AppInitRequest;
use heapless::String as HeaplessString;
use ufmt::uwrite;
use crate::web_server::NMAppState;
use crate::web_server::app_error::AppError; use crate::web_server::app_error::AppError;
use crate::web_server::old_app_api::types::AppInitRequest;
use crate::web_server::NMAppState;
use heapless::String as HeaplessString;
use serde_json::{json, Value as JsonValue};
use ufmt::uwrite;
use crate::insert_header;
use fred::interfaces::KeysInterface; use fred::interfaces::KeysInterface;
use ntex::http::StatusCode;
use ntex::web;
use ntex::web::types::State;
use ntex::web::Responder;
pub async fn app_init(body: AppInitRequest<'_>, State(appState): State<NMAppState>) -> Result<impl IntoResponse, AppError> { pub async fn app_init(
let _: () = appState.redis_client.set("test", 123, None, None, true).await?; body: AppInitRequest<'_>,
app_state: &NMAppState,
) -> Result<web::HttpResponse, AppError> {
let _: () = app_state
.redis_client
.set("test", 123, None, None, true)
.await?;
Ok((StatusCode::OK, "Hello, World!").into_response()) Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!"))
} }
pub async fn version(body: (), app_state: &NMAppState) -> Result<web::HttpResponse, AppError> {
let mut resp = web::HttpResponse::build(StatusCode::OK).json(&json!({
"version": "indev",
"iotishnik": true
}));
insert_header!(resp.headers_mut(), "Cache-Control", "no-cache");
Ok(resp)
}

View file

@ -1,10 +1,7 @@
mod methods; mod methods;
use std::collections::HashMap;
use axum::Json;
use axum::response::IntoResponse;
use phf::phf_map;
use serde::{Deserialize, Serialize};
use crate::hashes::SupportedUnit; use crate::hashes::SupportedUnit;
pub use methods::*; pub use methods::*;
use phf::phf_map;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

View file

@ -1,6 +1,5 @@
mod types;
mod handlers;
mod config_app; mod config_app;
mod handlers;
mod types;
pub use config_app::old_api_handler; pub use config_app::old_api_handler;

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use serde::{Deserialize, Deserializer, Serialize};
use crate::hashes::SupportedUnit; use crate::hashes::SupportedUnit;
use serde::{Deserialize, Deserializer, Serialize};
use std::borrow::Cow;
// fn<'de, D>(D) -> Result<T, D::Error> where D: Deserializer<'de> // fn<'de, D>(D) -> Result<T, D::Error> where D: Deserializer<'de>
@ -15,7 +15,7 @@ pub struct AppInitRequest<'a> {
#[serde(borrow)] #[serde(borrow)]
pub model: Cow<'a, str>, pub model: Cow<'a, str>,
pub width: u64 pub width: u64,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -36,5 +36,5 @@ pub struct MandatoryParams<'a> {
pub uuid: Cow<'a, str>, pub uuid: Cow<'a, str>,
#[serde(borrow)] #[serde(borrow)]
pub api_key: Cow<'a, str> pub api_key: Cow<'a, str>,
} }

View file

@ -0,0 +1,109 @@
use crate::ingest_protocol::error::Error;
use crate::ingest_protocol::parser::parse_mac_address;
use crate::ingest_protocol::{NMDeviceDataPacket, NMJsonPacket};
use crate::web_server::app_error::AppError;
use bstr::BStr;
use ntex::http::error::DecodeError::Method;
use ntex::http::{HttpMessage, Payload, StatusCode};
use ntex::util::{Bytes, HashMap};
use ntex::{http, web};
use std::sync::Arc;
use thiserror::Error;
/// В иделае было бы хорошо сделать всё как у [serde_json::Error], но это слишком большая морока
#[derive(Error, Clone, Debug)]
pub enum QSParserError {
#[error("asd")]
SerdeQSError(#[from] Arc<serde_qs::Error>),
#[error("asd")]
ParsingError(String),
#[error("asd")]
NoMAC,
}
impl From<Error<&str>> for QSParserError {
fn from(value: Error<&str>) -> Self {
QSParserError::ParsingError(format!("{:?}", value))
}
}
impl From<serde_qs::Error> for QSParserError {
fn from(value: serde_qs::Error) -> Self {
QSParserError::SerdeQSError(Arc::new(value))
}
}
pub async fn parse_nm_qs_format<'a>(input: &'a str) -> Result<NMDeviceDataPacket, QSParserError> {
let parsed: HashMap<&str, &str> = serde_qs::from_str(input)?;
let (_, device_mac) = if let Some(id) = parsed.get("ID") {
parse_mac_address(*id)?
} else {
return Err(QSParserError::NoMAC);
};
let device_data = NMDeviceDataPacket {
mac: [0u8; 6],
name: None,
values: Default::default(),
owner: None,
lat: None,
lon: None,
alt: None,
time: None,
};
return Ok(device_data);
}
pub async fn device_handler<'a>(
request: web::HttpRequest,
body: Bytes,
) -> Result<web::HttpResponse, AppError> {
let mut real_body: Option<NMJsonPacket> = None;
let mut json_error = None;
let mut query_error = None;
dbg!(&request.content_type());
dbg!(&request.query_string());
if request.method() == http::Method::POST {
match request.content_type() {
"application/json" => match serde_json::from_slice::<NMJsonPacket>(body.as_ref()) {
Ok(json_body) => real_body = Some(json_body),
Err(error) => json_error = Some(error),
},
"application/x-www-form-urlencoded" => {
match serde_qs::from_bytes::<NMDeviceDataPacket>(body.as_ref()) {
Ok(qs_body) => {
real_body = Some(NMJsonPacket {
devices: Vec::from([qs_body]),
})
}
Err(error) => query_error = Some(error),
}
}
_ => {}
}
} else if request.method() == http::Method::GET {
match serde_qs::from_str::<NMDeviceDataPacket>(request.query_string()) {
Ok(qs_body) => {
real_body = Some(NMJsonPacket {
devices: Vec::from([qs_body]),
})
}
Err(error) => query_error = Some(error),
}
}
if let Some(body) = real_body {
// TODO
} else {
return Err(AppError::UnknownBody {
json_err: json_error,
query_error: query_error,
});
}
Ok(web::HttpResponse::build(StatusCode::OK).finish())
}

View file

@ -1 +1,8 @@
pub mod redis; pub mod redis;
#[macro_export]
macro_rules! insert_header {
($headers: expr, $name: expr, $value: expr) => {
$headers.insert($name.try_into().unwrap(), $value.try_into().unwrap());
};
}

View file

@ -1,31 +1,36 @@
use crate::web_server::app_error::AppError;
use fred::prelude::*; use fred::prelude::*;
use heapless::String as HeaplessString; use heapless::String as HeaplessString;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use ufmt::uwrite; use ufmt::uwrite;
use crate::web_server::app_error::AppError;
lazy_static! { lazy_static! {
static ref ALLOWED_API_KEY_CHARACTERS: Regex = Regex::new("[a-zA-Z0-9]{13}").unwrap(); static ref ALLOWED_API_KEY_CHARACTERS: Regex = Regex::new("[a-zA-Z0-9]{13}").unwrap();
} }
pub struct ApiKeyDescription { pub struct ApiKeyDescription {
apikey_owner: i64 apikey_owner: i64,
} }
pub async fn is_api_key_valid(client: &RedisClient, api_key: &str) -> Result<ApiKeyDescription, AppError> { pub async fn is_api_key_valid(
client: &RedisClient,
api_key: &str,
) -> Result<ApiKeyDescription, AppError> {
if !ALLOWED_API_KEY_CHARACTERS.is_match(api_key) { if !ALLOWED_API_KEY_CHARACTERS.is_match(api_key) {
return Err(AppError::ApiKeyInvalid { reason: "Invalid characters present in the API key." }) return Err(AppError::ApiKeyInvalid {
reason: "Invalid characters present in the API key.",
});
} }
let mut key_buffer = HeaplessString::<{7 + 13}>::new(); let mut key_buffer = HeaplessString::<{ 7 + 13 }>::new();
uwrite!(key_buffer, "apikey_{}", api_key); uwrite!(key_buffer, "apikey_{}", api_key).expect("TODO"); // TODO: Error handling
let valid: Option<i64> = client.hget(key_buffer.as_str(), "owner").await?; let valid: Option<i64> = client.hget(key_buffer.as_str(), "owner").await?;
valid.map(|uid| { valid
ApiKeyDescription { .map(|uid| ApiKeyDescription { apikey_owner: uid })
apikey_owner: uid .ok_or(AppError::ApiKeyInvalid {
} reason: "Unknown API key",
}).ok_or(AppError::ApiKeyInvalid { reason: "Unknown API key" }) })
} }