Forgot to add

This commit is contained in:
nm17 2023-06-30 18:25:56 +04:00
parent 83d712b930
commit 427a3095f8
15 changed files with 1992 additions and 124 deletions

1640
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,24 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.71"
axum = { version = "0.6.18", features = ["http2", "headers", "macros"] }
bytes = { version = "1.4.0", features = ["serde"] }
chrono = { version = "0.4.26", features = ["serde"] }
clap = { version = "4.3.8", features = ["derive", "env"] }
dotenvy = "0.15.7"
fred = { version = "6.3.0", features = ["nom"] }
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
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"] }
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"
smallstr = { version = "0.3.0", features = ["std", "union"] }
thiserror = "1.0.40"
tokio = { version = "1.28.2", features = ["full"] }
ufmt = "0.2.0"

View file

@ -0,0 +1,71 @@
use std::borrow::Cow;
use phf::phf_map;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[repr(u64)]
pub enum SupportedUnit {
Celsius, // Needs verification > 273.15
Percentage, // Needs verification >= 100 && <= 0
MillimeterHg, // Needs verification
UVIndex, // Needs verification
Boolean, // Needs verification
Kbps,
Volume,
KWh,
Amps,
Volts,
Watts,
Seconds,
}
impl Serialize for SupportedUnit {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(match self {
SupportedUnit::Celsius => "C",
SupportedUnit::Percentage => "%",
SupportedUnit::MillimeterHg => "mmHg",
SupportedUnit::UVIndex => "UV",
SupportedUnit::Boolean => "bool",
SupportedUnit::Kbps => "Kbps",
SupportedUnit::Volume => "m3",
SupportedUnit::Amps => "A",
SupportedUnit::Volts => "V",
SupportedUnit::Watts => "W",
SupportedUnit::Seconds => "s",
SupportedUnit::KWh => "KWh"
})
}
}
impl<'de> Deserialize<'de> for SupportedUnit {
fn deserialize<'a, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let color_str = Cow::<'a, str>::deserialize(deserializer)?;
match STR_TO_UNITS.get(color_str.as_ref()) {
Some(v) => Ok(v.clone()),
None => Err(serde::de::Error::custom("Invalid unit")),
}
}
}
static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! {
"C" => SupportedUnit::Celsius,
"%" => SupportedUnit::Percentage,
"mmHg" => SupportedUnit::MillimeterHg,
"UV" => SupportedUnit::UVIndex,
"bool" => SupportedUnit::Boolean,
"Kbps" => SupportedUnit::Kbps,
"m3" => SupportedUnit::Volume,
"A" => SupportedUnit::Amps,
"V" => SupportedUnit::Volts,
"W" => SupportedUnit::Watts,
"s" => SupportedUnit::Seconds,
"KWh" => SupportedUnit::Seconds,
};

View file

@ -1,3 +1,4 @@
#![feature(impl_trait_in_fn_trait_return)]
/*
Три датчика реалтайм:
@ -33,98 +34,30 @@ C названием и координатами:
##
*/
extern crate core;
mod hashes;
mod web_server;
mod protocol;
use std::borrow::Cow;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::mem::{size_of, size_of_val};
use std::num::NonZeroUsize;
use nom::{InputTake, IResult, Needed, Parser};
use nom::branch::alt;
use nom::bytes::complete::{take_until, take_while_m_n};
use nom::bytes::streaming::{take_till1, take};
use nom::bytes::complete::tag;
use nom::bytes::streaming::take_till;
use nom::character::streaming::{anychar, char, hex_digit0, newline};
use nom::character::complete::hex_digit1;
use std::str::FromStr;
use axum::Router;
use axum::routing::post;
use hifitime::Epoch;
use nom::combinator::{map, map_opt, map_parser, opt, recognize, rest};
use nom::Err as NomErr;
use nom::error::{Error, ErrorKind as NomErrorKind};
use nom::multi::{count, separated_list1};
use nom::sequence::{delimited, preceded, separated_pair};
use rust_decimal::Decimal;
use thiserror::Error;
struct NarodMonPacketSerializer {
}
#[derive(Debug, Clone, Default)]
struct NarodMonValues<'a> {
mac: Cow<'a, str>,
value: Decimal,
time: Option<u64>,
name: Option<u64>
}
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
enum ValueMac<'a> {
Temperature(Cow<'a, str>, Option<u128>)
}
#[derive(Debug, Clone)]
struct NarodMonPacket<'a> {
mac: [u8; 6],
name: Option<Cow<'a, str>>,
values: HashSet<NarodMonValues<'a>>
}
fn parse_mac_address(input: &str) -> IResult<&str, [u8; 6]> {
let mut mac = [0u8; 6];
let mut counter = 0;
let (leftovers, i) = take(17usize)(input)?;
let (_, out) = count(|inp| {
//dbg!(inp);
let (mut i, o) = map_parser(take(2usize), hex_digit1)(inp)?;
if counter != 5 {
(i, _) = tag("-")(i)?;
}
counter+=1;
Ok((i, o))
}, 6)(i)?;
//dbg!(&out);
hex::decode_to_slice(out.join(""), &mut mac).unwrap();
Ok((leftovers, mac))
}
fn parse_packet(input: &str) -> IResult<&str, NarodMonPacket> {
let (input, _) = tag("#")(input)?;
let (input, mac) = parse_mac_address(input)?;
dbg!(input);
let (input, opt_name) = opt(delimited(tag("#"), take_till1(|c| c == '\n'), tag("\n")))(input)?;
use crate::protocol::{NarodMonPacket, NarodMonValues};
use crate::protocol::error::Error;
use crate::protocol::error::Error::TimestampParseError;
use crate::web_server::old_app_api::old_api_handler;
use crate::web_server::server_main;
let (input, values) = parse_sensor_values(input)?;
Ok((input,
NarodMonPacket {
mac,
name: opt_name,
values,
})
)
}
fn parse_sensor_value(input: Vec<&str>) -> IResult<&str, NarodMonValues> {
/*fn parse_sensor_value(input: Vec<&str>) -> MyIError<Vec<&str>, NarodMonValues> {
Ok(
(input, NarodMonValues {
mac: Default::default(),
@ -133,44 +66,46 @@ fn parse_sensor_value(input: Vec<&str>) -> IResult<&str, NarodMonValues> {
name: None,
})
)
}*/
struct Params {
}
fn parse_sensor_values(input: &str) -> IResult<&str, HashSet<NarodMonValues>> {
let (input, asd) = map_parser(take_until("##"),separated_list1(tag("\n"), rest))(input)?;
for line in asd {
let one = NarodMonValues::default();
let (line, _) = tag("#")(line)?;
let (line, sensor_mac) = take_till1(|c| c == '\n' || c == "#")(line);
let (line, _) = preceded(tag("#"), take_till1(|c| c == '\n' || c == "#")(line);
#[tokio::main]
async fn main() {
//dotenvy::dotenv().unwrap();
let web_server_hndl = tokio::spawn(server_main());
}
Ok((input,
Default::default()
))
}
fn main() {
println!("Hello, world!");
web_server_hndl.await.unwrap();
}
#[cfg(test)]
mod tests {
use crate::protocol::parser::{parse_mac_address, parse_packet};
use super::*;
#[test]
fn test_mac() {
assert_eq!(parse_mac_address("12-34-AA-12-55-AA"), Ok(("", [18, 52, 170, 18, 85, 170])))
//assert_eq!(parse_mac_address("12-34-AA-12-55-AA"), Ok(("", [18, 52, 170, 18, 85, 170])) );
println!("{:?}", parse_mac_address("12-34-AA-12-55-AA"));
}
#[test]
fn test_packet() {
let inp = "#26-94-1D-75-C2-F8#Метео\n";
let inp = r#"#26-94-1D-75-C2-F8#Метео
#OWNER#nm17
#T1#1.12
#T2#10000#1231410321#sensorName
#T2#1.2#3400005345
##"#;
println!("{:?}", parse_packet(inp));
println!("{:#?}", parse_packet(inp));
}
}

View file

@ -0,0 +1,7 @@
mod packet_types;
pub mod error;
pub mod parser;
mod server;
pub use packet_types::*;

View file

@ -0,0 +1,117 @@
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::character::complete::hex_digit1;
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::protocol::error::Error;
use crate::protocol::{NarodMonPacket, NarodMonValues};
use crate::protocol::error::Error::TimestampParseError;
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 (_, out) = count(|inp| {
//dbg!(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)?;
//dbg!(&out);
hex::decode_to_slice(out.join(""), &mut mac).unwrap();
Ok((leftovers, mac))
}
pub fn parse_packet(input: &str) -> MyIError<&str, NarodMonPacket> {
let (input, _) = tag("#")(input)?;
let (input, 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();
packet.mac = mac;
let mut hs = HashSet::new();
let (input, asd) = context(
"Получение значений до тега терминатора",
map_parser(
take_until1("##"),
separated_list0(tag("\n"), take_while1(|c| c != '\n'))
)
)(input)?;
println!("ASDASD: {:?}", asd);
for line in asd {
let (line, _) = tag("#")(line)?;
let (line, sensor_mac) = take_while1(|c| c != '\n' && c != '#')(line)?;
let (line, _) = tag("#")(line)?;
match sensor_mac {
"OWNER" => {
let (line, owner_value) = take_while1(|c| c != '\n')(line)?;
packet.owner = Some(owner_value.into())
//hs.insert(NarodMonValues::Owner(owner_value.into()));
//let (line, _) = tag("\n")(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 != '#')))(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
};
hs.insert(NarodMonValues {
mac: sensor_mac.into(),
value: Decimal::from_str(sensor_value).unwrap(),
time: sensor_time, // TODO
unit: None,
name: sensor_name.map(|v| Cow::from(v)),
});
}
}
}
let (input, _) = tag("##")(input)?;
packet.name = opt_name.map(|v| Cow::from(v));
packet.values = hs;
Ok((input, packet))
}

View file

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

View file

@ -25,6 +25,11 @@ pub enum AppError {
#[error("Fuck")]
RequestTooLarge,
#[error("Api")]
ApiKeyInvalid {
reason: &'static str
},
#[error("Fuck")]
UnitValidationFailed {
max: Option<Decimal>,
@ -51,6 +56,9 @@ impl IntoResponse for AppError {
},
AppError::ServerRedisError(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
},
AppError::ApiKeyInvalid { .. } => {
(StatusCode::BAD_REQUEST, "API Key invalid")
}
};
@ -76,6 +84,9 @@ impl IntoResponse for AppError {
AppError::RequestTooLarge => {
headers.insert("X-Max-Request-Size", HeaderValue::try_from("10 KiB = 10240 bytes").unwrap());
},
AppError::ApiKeyInvalid { reason } => {
headers.insert("X-Error-Description", HeaderValue::try_from(reason).unwrap());
},
_ => {}
};

View file

@ -6,13 +6,47 @@ use axum::{
};
use axum::error_handling::{HandleError, HandleErrorLayer};
use axum::http::StatusCode;
use crate::server::old_app_api::old_api_handler;
use axum::routing::post;
use fred::bytes_utils::Str;
use fred::prelude::*;
use crate::web_server::old_app_api::old_api_handler;
pub mod old_app_api;
mod utils;
pub(crate) mod app_error;
#[derive(Clone)]
struct NMAppState {
redis_client: RedisClient
pub struct NMAppState {
pub redis_client: RedisClient
}
async fn main()
use heapless::String as HeaplessString;
pub async fn server_main() {
let config = RedisConfig::default();
let perf = PerformanceConfig::default();
let policy = ReconnectPolicy::default();
let client = RedisClient::new(config, Some(perf), Some(policy));
// connect to the server, returning a handle to the task that drives the connection
let _ = client.connect();
let _ = client.wait_for_connect().await.unwrap();
let asd: Str = client.ping().await.unwrap();
println!("Ping result: {}", asd);
let state = NMAppState {
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();
}

View file

@ -1,29 +1,35 @@
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::server::old_app_api::app_error::AppError;
use crate::server::old_app_api::handlers::app_init;
use crate::server::old_app_api::types::{AppInitRequest, MandatoryParams};
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::types::{AppInitRequest, MandatoryParams};
use crate::web_server::utils::redis::is_api_key_valid;
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)
}
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes.as_bytes())?;
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes.as_bytes())?; // TODO: Simd-JSON
is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?;
return match mandatory_params.cmd.as_ref() {
"appInit" => {
let body: AppInitRequest = serde_json::from_slice(body_bytes.as_bytes())?;
Ok(app_init(body).await)
Ok(app_init(body, app_state).await)
}
_ => {
Err(AppError::UnknownMethod(mandatory_params.cmd.to_string()))

View file

@ -1,17 +1,20 @@
use axum::body::Body;
use axum::extract::State;
use axum::http::Request;
use axum::http::{Request, StatusCode};
use axum::Json;
use axum::response::IntoResponse;
use serde_json::Value as JsonValue;
use crate::server::old_app_api::types::AppInitRequest;
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 fred::interfaces::KeysInterface;
pub async fn app_init(body: AppInitRequest<'_>, State(appState): State<>) -> impl IntoResponse {
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?;
"Hello, World!"
Ok((StatusCode::OK, "Hello, World!").into_response())
}

View file

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

View file

@ -15,8 +15,7 @@ pub struct AppInitRequest<'a> {
#[serde(borrow)]
pub model: Cow<'a, str>,
#[serde(borrow)]
pub width: Cow<'a, str>,
pub width: u64
}
#[derive(Clone, Debug, Serialize, Deserialize)]

View file

@ -0,0 +1 @@
pub mod redis;

View file

@ -0,0 +1,31 @@
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
}
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." })
}
let mut key_buffer = HeaplessString::<{7 + 13}>::new();
uwrite!(key_buffer, "apikey_{}", api_key);
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" })
}