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]
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"] }
chrono = { version = "0.4.26", features = ["serde"] }
clap = { version = "4.3.8", features = ["derive", "env"] }
derive_more = "0.99.17"
dotenvy = "0.15.7"
fred = { version = "6.3.0", features = ["nom"] }
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
@ -18,11 +19,14 @@ hex = { version = "0.4.3", default-features = false }
hifitime = "3.8.2"
lazy_static = "1.4.0"
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"] }
regex = "1.8.4"
rust_decimal = { version = "1.30.0", features = ["rkyv", "rkyv-safe"] }
serde = { version = "1.0.164", features = ["derive", "alloc"] }
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"] }
thiserror = "1.0.40"
tokio = { version = "1.28.2", features = ["full"] }

View file

@ -1,15 +1,15 @@
use std::borrow::Cow;
use phf::phf_map;
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)]
pub enum SupportedUnit {
Celsius, // Needs verification > 273.15
Percentage, // Needs verification >= 100 && <= 0
Celsius, // Needs verification > 273.15
Percentage, // Needs verification >= 100 && <= 0
MillimeterHg, // Needs verification
UVIndex, // Needs verification
Boolean, // Needs verification
UVIndex, // Needs verification
Boolean, // Needs verification and possible parsing
Kbps,
Volume,
KWh,
@ -17,12 +17,13 @@ pub enum SupportedUnit {
Volts,
Watts,
Seconds,
Hertz,
}
impl Serialize for SupportedUnit {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
serializer.serialize_str(match self {
SupportedUnit::Celsius => "C",
@ -36,15 +37,16 @@ impl Serialize for SupportedUnit {
SupportedUnit::Volts => "V",
SupportedUnit::Watts => "W",
SupportedUnit::Seconds => "s",
SupportedUnit::KWh => "KWh"
SupportedUnit::KWh => "KWh",
SupportedUnit::Hertz => "Hz",
})
}
}
impl<'de> Deserialize<'de> for SupportedUnit {
fn deserialize<'a, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
where
D: Deserializer<'de>,
{
let color_str = Cow::<'a, str>::deserialize(deserializer)?;
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! {
"C" => SupportedUnit::Celsius,
"%" => SupportedUnit::Percentage,

View file

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

View file

@ -1,5 +1,5 @@
mod packet_types;
pub mod error;
mod packet_types;
pub mod parser;
mod server;
@ -7,4 +7,3 @@ mod server;
mod tests;
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::collections::HashSet;
use std::hash::{Hash, Hasher};
use hifitime::Epoch;
use rust_decimal::Decimal;
use crate::hashes::SupportedUnit;
#[derive(Debug, Clone)]
pub struct SensorValue<'a> {
pub mac: Cow<'a, str>,
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SensorValue {
pub mac: String,
pub value: Decimal,
pub time: Option<Epoch>,
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) {
self.mac.hash(state);
self.mac.hash(state)
}
}
impl<'a> PartialEq for SensorValue<'a> {
fn eq(&self, other: &Self) -> bool {
self.mac == other.mac
pub struct DashSeparator {}
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)]
pub struct NarodMonPacket<'a> {
serde_with::serde_conv!(
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 name: Option<Cow<'a, str>>,
pub values: HashSet<SensorValue<'a>>,
pub owner: Option<Cow<'a, str>>,
pub name: Option<String>,
pub values: HashSet<SensorValue>,
pub owner: Option<String>,
pub lat: 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 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::streaming::take_till;
use nom::character::streaming::{anychar, char, hex_digit0, newline};
use nom::bytes::complete::take_until1;
use nom::bytes::complete::{take, take_while, take_while1};
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::{NarodMonPacket, SensorValue};
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>>;
pub fn parse_mac_address(input: &str) -> MyIError<&str, [u8; 6]> {
let mut mac = [0u8; 6];
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 (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
if counter != 5 {
(i, _) = tag("-")(i)?;
}
counter += 1;
Ok((i, o))
}, 6)(i)?;
let (_, out) = count(
|inp| {
let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
if counter != 5 {
(i, _) = tag("-")(i)?;
}
counter += 1;
Ok((i, o))
},
6,
)(i)?;
hex::decode_to_slice(out.join(""), &mut mac).unwrap();
Ok((leftovers, mac))
}
fn handle_special_sensor_macs<I: std::fmt::Debug> (
mac: &str,
fn handle_special_sensor_macs<'a>(
mac: &'a str,
sensor_value: &str,
packet: &mut NarodMonPacket,
) -> Result<bool, Error<I>> {
packet: &mut NMDeviceDataPacket,
) -> Result<bool, Error<&'a str>> {
match mac.to_uppercase().as_str() {
"LAT" => packet.lat = 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);
}
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, 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_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 sensor_time = match sensor_time {
Some(v) => Some(
Epoch::from_unix_seconds(
v.parse().map_err(|e| TimestampParseError(e))?
)
),
None => None
Some(v) => Some(Epoch::from_unix_seconds(
v.parse().map_err(|e| TimestampParseError(e))?,
)),
None => None,
};
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)?,
time: sensor_time, // TODO
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, device_mac) = parse_mac_address(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;
let (input, lines) = context(
"Получение значений до тега терминатора",
map_parser(
take_until1("##"),
separated_list0(tag("\n"), take_while1(|c| c != '\n'))
)
separated_list0(tag("\n"), take_while1(|c| c != '\n')),
),
)(input)?;
for line in lines {
@ -131,7 +130,7 @@ pub fn parse_packet(input: &str) -> MyIError<&str, NarodMonPacket> {
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))
}

View file

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

View file

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

View file

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

View file

@ -1,97 +1,133 @@
use std::borrow::Cow;
use axum::headers::HeaderValue;
use axum::http::StatusCode;
use axum::Json;
use axum::response::{IntoResponse, Response};
use derive_more::Display;
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 serde_json::json;
use thiserror::Error;
use ufmt::derive::uDebug;
#[derive(Debug, Error)]
#[derive(Debug, Error, Display)]
pub enum AppError {
#[error("IDK")]
#[display(fmt = "IDK")]
JsonError(#[from] serde_json::Error),
#[error("IDK")]
#[display(fmt = "IDK")]
QSError(QSParserError),
#[display(fmt = "IDK")]
ServerRedisError(#[from] RedisError),
#[error("Fuck")]
#[display(fmt = "IDK")]
UnknownMethod(String),
#[error("Fuck")]
#[display(fmt = "IDK")]
RequestTooLarge,
#[error("Api")]
ApiKeyInvalid {
reason: &'static str
},
#[display(fmt = "IDK")]
ApiKeyInvalid { reason: &'static str },
#[error("Fuck")]
#[display(fmt = "IDK")]
UnitValidationFailed {
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 {
fn into_response(self) -> Response {
impl web::error::WebResponseError for AppError {
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,
}
}
let (status, error_message) = match self {
AppError::JsonError(_) => {
(StatusCode::BAD_REQUEST, "Invalid JSON")
},
AppError::UnknownMethod(_) => {
(StatusCode::BAD_REQUEST, "Unknown command")
},
AppError::UnitValidationFailed { .. } => {
(StatusCode::BAD_REQUEST, "Unknown command")
},
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")
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
let error_message = match self {
AppError::JsonError(_) => "Invalid JSON",
AppError::UnknownMethod(_) => "Unknown command",
AppError::UnitValidationFailed { .. } => "Unknown command",
AppError::RequestTooLarge => "Request is too large",
AppError::ServerRedisError(_) => "Internal server error",
AppError::ApiKeyInvalid { .. } => "API Key invalid",
AppError::UnknownBody { .. } => {
"Can't figure out where and in what encoding the main data is"
}
AppError::QSError(..) => "UrlEncoded body or query params are incorrect",
};
let body = Json(json!({
"errno": status.as_u16(),
"error": error_message,
}));
let status_code = self.status_code();
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 error_as_string= format!("{:?}", &self);
let error_as_string = format!("{:?}", &self);
match self {
AppError::JsonError(json_err) => {
headers.insert("X-Error-Line", HeaderValue::from(json_err.line()));
headers.insert("X-Error-Column", HeaderValue::from(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-Line", json_err.line());
insert_header!(headers, "X-Error-Column", json_err.column());
insert_header!(
headers,
"X-Error-Description",
json_err.to_string().escape_default().collect::<String>()
);
}
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 => {
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 } => {
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) {
headers.insert("X-Full-Error", HeaderValue::try_from(error_as_string).unwrap());
insert_header!(headers, "X-Full-Error", error_as_string);
}
resp

View file

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

View file

@ -1,41 +1,44 @@
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::NMAppState;
use crate::web_server::old_app_api::handlers::app_init;
use crate::web_server::old_app_api::handlers::{app_init, version};
use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams};
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(
app_state: State<NMAppState>,
body_bytes: Bytes,
) -> Result<impl IntoResponse, AppError> {
if body_bytes.len() > 10 * 1024 { // 10 KiB
return Err(AppError::RequestTooLarge)
) -> Result<impl web::Responder, AppError> {
if body_bytes.len() > 10 * 1024 {
// 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?;
return match mandatory_params.cmd.as_ref() {
match mandatory_params.cmd.as_ref() {
"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)
}
_ => {
Err(AppError::UnknownMethod(mandatory_params.cmd.to_string()))
return Ok(app_init(body, &app_state).await?);
}
_ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())),
}
//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::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 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> {
let _: () = appState.redis_client.set("test", 123, None, None, true).await?;
pub async fn app_init(
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;
use std::collections::HashMap;
use axum::Json;
use axum::response::IntoResponse;
use phf::phf_map;
use serde::{Deserialize, Serialize};
use crate::hashes::SupportedUnit;
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 handlers;
mod types;
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 serde::{Deserialize, Deserializer, Serialize};
use std::borrow::Cow;
// fn<'de, D>(D) -> Result<T, D::Error> where D: Deserializer<'de>
@ -15,7 +15,7 @@ pub struct AppInitRequest<'a> {
#[serde(borrow)]
pub model: Cow<'a, str>,
pub width: u64
pub width: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -36,5 +36,5 @@ pub struct MandatoryParams<'a> {
pub uuid: Cow<'a, str>,
#[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;
#[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 heapless::String as HeaplessString;
use lazy_static::lazy_static;
use regex::Regex;
use ufmt::uwrite;
use crate::web_server::app_error::AppError;
lazy_static! {
static ref ALLOWED_API_KEY_CHARACTERS: Regex = Regex::new("[a-zA-Z0-9]{13}").unwrap();
}
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) {
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();
uwrite!(key_buffer, "apikey_{}", api_key);
let mut key_buffer = HeaplessString::<{ 7 + 13 }>::new();
uwrite!(key_buffer, "apikey_{}", api_key).expect("TODO"); // TODO: Error handling
let valid: Option<i64> = client.hget(key_buffer.as_str(), "owner").await?;
valid.map(|uid| {
ApiKeyDescription {
apikey_owner: uid
}
}).ok_or(AppError::ApiKeyInvalid { reason: "Unknown API key" })
valid
.map(|uid| ApiKeyDescription { apikey_owner: uid })
.ok_or(AppError::ApiKeyInvalid {
reason: "Unknown API key",
})
}