feat: A lot of changes
This commit is contained in:
parent
824e382e89
commit
b0f3343a27
20 changed files with 1256 additions and 532 deletions
1067
Cargo.lock
generated
1067
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"] }
|
||||||
|
|
|
@ -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,6 +17,7 @@ pub enum SupportedUnit {
|
||||||
Volts,
|
Volts,
|
||||||
Watts,
|
Watts,
|
||||||
Seconds,
|
Seconds,
|
||||||
|
Hertz,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for SupportedUnit {
|
impl Serialize for SupportedUnit {
|
||||||
|
@ -36,7 +37,8 @@ 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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
|
@ -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::*;
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
|inp| {
|
||||||
let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
|
let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
|
||||||
if counter != 5 {
|
if counter != 5 {
|
||||||
(i, _) = tag("-")(i)?;
|
(i, _) = tag("-")(i)?;
|
||||||
}
|
}
|
||||||
counter += 1;
|
counter += 1;
|
||||||
Ok((i, o))
|
Ok((i, o))
|
||||||
}, 6)(i)?;
|
},
|
||||||
|
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))
|
||||||
}
|
}
|
|
@ -1,3 +1 @@
|
||||||
async fn main() {
|
async fn main() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
30
src/main.rs
30
src/main.rs
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,97 +1,133 @@
|
||||||
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,
|
||||||
let (status, error_message) = match self {
|
AppError::UnknownMethod(_) => StatusCode::BAD_REQUEST,
|
||||||
AppError::JsonError(_) => {
|
AppError::UnitValidationFailed { .. } => StatusCode::BAD_REQUEST,
|
||||||
(StatusCode::BAD_REQUEST, "Invalid JSON")
|
AppError::RequestTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
|
||||||
},
|
AppError::ServerRedisError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
AppError::UnknownMethod(_) => {
|
AppError::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST,
|
||||||
(StatusCode::BAD_REQUEST, "Unknown command")
|
AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST,
|
||||||
},
|
AppError::QSError(..) => StatusCode::BAD_REQUEST,
|
||||||
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!({
|
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
|
||||||
|
|
|
@ -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())
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))
|
||||||
|
.unwrap()
|
||||||
|
.run()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap(); // TODO figure out how to handle what
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
109
src/web_server/old_devices_api/mod.rs
Normal file
109
src/web_server/old_devices_api/mod.rs
Normal 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())
|
||||||
|
}
|
|
@ -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());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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" })
|
})
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue