From e4287bb2ac66f9677180ab6f3f3bd92b1efa7f5c Mon Sep 17 00:00:00 2001 From: nm17 Date: Fri, 8 Mar 2024 23:07:10 +0400 Subject: [PATCH] docs: documented most of the code --- Cargo.lock | 62 +++++++++---------- Cargo.toml | 2 +- README.md | 22 +++++++ src/ingest_protocol/mod.rs | 2 + src/ingest_protocol/packet_types.rs | 32 +++++++--- src/main.rs | 60 +----------------- src/{hashes.rs => utils.rs} | 12 +++- src/web_server/app_error.rs | 5 +- src/web_server/mod.rs | 13 +++- src/web_server/old_app_api/config_app.rs | 45 -------------- .../old_app_api/handlers/methods.rs | 36 ----------- src/web_server/old_app_api/handlers/mod.rs | 33 +++++++++- src/web_server/old_app_api/mod.rs | 52 +++++++++++++++- src/web_server/old_app_api/types/mod.rs | 11 +++- src/web_server/old_device_sensor_api/mod.rs | 13 +++- .../old_device_sensor_api/qs_parser.rs | 5 ++ src/web_server/utils/mod.rs | 3 + src/web_server/utils/redis.rs | 6 ++ 18 files changed, 223 insertions(+), 191 deletions(-) create mode 100644 README.md rename src/{hashes.rs => utils.rs} (78%) delete mode 100644 src/web_server/old_app_api/config_app.rs delete mode 100644 src/web_server/old_app_api/handlers/methods.rs diff --git a/Cargo.lock b/Cargo.lock index 36d94d0..3e852f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,6 +984,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "iotishnik-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "bstr", + "bytes", + "chrono", + "clap", + "derive_more", + "dotenvy", + "fred", + "heapless", + "hex", + "hifitime", + "lazy_static", + "nom", + "ntex", + "phf", + "regex", + "rust_decimal", + "serde", + "serde_json", + "serde_qs", + "serde_with", + "smallstr", + "thiserror", + "tokio", + "ufmt", +] + [[package]] name = "is-terminal" version = "0.4.7" @@ -1156,37 +1187,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -[[package]] -name = "narodmon-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "bstr", - "bytes", - "chrono", - "clap", - "derive_more", - "dotenvy", - "fred", - "heapless", - "hex", - "hifitime", - "lazy_static", - "nom", - "ntex", - "phf", - "regex", - "rust_decimal", - "serde", - "serde_json", - "serde_qs", - "serde_with", - "smallstr", - "thiserror", - "tokio", - "ufmt", -] - [[package]] name = "nom" version = "7.1.3" diff --git a/Cargo.toml b/Cargo.toml index a909712..00757bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "narodmon-server" +name = "iotishnik-server" version = "0.1.0" edition = "2021" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0e2290 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# IoTishnik +Платформа для обработки данных окружающей среды с IoT устройств. + +## Описание идеи +> ### Что есть сейчас: +> +> Есть куча девайсов, которые сейчас со своих сенсоров, для публичного и в некоторых случаях частного использования выкладывают на сервер narodmon.ru данные. +> +> ### В чём проблема narodmon.ru: +> +>1. Там всё такоееее легаси, что ты не представляешь. Речь идёт о cgi скриптах написанных на bash. +>2. Разрабы ищут помощь в виде лиц, в которых можно плевать без последствий сколько угодно. +>3. Отсутствие тех поддержки для обычных пользователей от слову совсем. +>4. Ужасное API которое ужасное не только из-за своей кривизны. В нём нужно отправлять телеметрию устройств, админ панель разработчика считай отсутствует. О OAuth2 вообще говорить не стоит. И если ты отправишь неправильные запросы с клиента своего, твой "api ключ" приложения заблокируют, и пофиг на то, что его можно спиздить с чужих приложений. +>5. Интерфейс просто ужас. Можешь сам зайти и посмотреть, даже писать про это не буду. Даже сам UX в изоляции от UI ужасный. +>6. Документация API не соблюдается. Даже когда мы с Андреем написали, что, мол, у вас поля изменились, им было плевать. +>7. Работа с приватными датчиками ужастная и требует денег для того, чтобы оно вообще работало. +>8. Некоторые очень важные API для устройств (такие как MQTT, на которых работают большое количество готовых продуктов не заточенные под narodmon.ru) доступны тоже только по подписке разрабам. +> +> ### Наше решение: +> +> Сделать с нуля своё решение, которое будет горизонтально масштабируемое, с поддержкой старых API для поддержки устройств сделанных под narodmon.ru . Выдать разрабам которые хотят новое и мощное API это API вместе с SDK в виде либ под ардуинку и т.п. Сделать поддержку OAuth для входа используя чужие сервисы и не только. И многое другое что я мб забыл упомянуть \ No newline at end of file diff --git a/src/ingest_protocol/mod.rs b/src/ingest_protocol/mod.rs index 7ea31e5..627fa9f 100644 --- a/src/ingest_protocol/mod.rs +++ b/src/ingest_protocol/mod.rs @@ -1,3 +1,5 @@ +//! Модуль для парсинга всего, что связанно с данными от устройств. + pub mod error; mod packet_types; pub mod parser; diff --git a/src/ingest_protocol/packet_types.rs b/src/ingest_protocol/packet_types.rs index 902964b..a8538cd 100644 --- a/src/ingest_protocol/packet_types.rs +++ b/src/ingest_protocol/packet_types.rs @@ -1,4 +1,12 @@ -use crate::hashes::SupportedUnit; +//! Сборник типов для внутренней обработки данных с датчиков. +//! +//! Предполагается, что struct-ы будут совместимы с JSON API для передачи данных датчиков. + +// Не забывайте про: +// #[serde(rename = "...")] +// #[serde(alias = "...")] + +use crate::utils::SupportedUnit; use crate::ingest_protocol::error::Error; use crate::ingest_protocol::parser::parse_mac_address; @@ -11,6 +19,17 @@ use serde_with::serde_as; use std::collections::HashSet; use std::hash::{Hash, Hasher}; +/// Данные с одного датчика. +/// +/// Основной идентификатор - поле `mac`. В случае с [crate::web_server::old_device_sensor_api], +/// `mac` может быть не полем, а названием ключа в параметрах. Из-за двусмысленности документации +/// NarodMon, `mac` может означать EUI-48 совместимый MAC адрес, или же просто +/// уникальный идентификатор. +/// +/// Парсинг этих данных отличается в разных транспортных протоколах. +/// Для HTTP /post или /get: см. [crate::web_server::old_device_sensor_api] +/// Для TCP/UDP: TODO +/// Для MQTT: TODO #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct SensorValue { pub mac: String, @@ -26,14 +45,9 @@ impl Hash for SensorValue { } } -pub struct DashSeparator {} - -impl Separator for DashSeparator { - fn separator() -> &'static str { - "-" - } -} - +/// Функция-помощник для [MacAsArray], предназначенный для использования с [serde_with]. +/// +/// Преобразует MAC-адрес. fn mac_as_array(value: &str) -> Result<[u8; 6], Error<&str>> { Ok(parse_mac_address(value)?.1) } diff --git a/src/main.rs b/src/main.rs index 12c99ed..5539a55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,69 +1,13 @@ -/* -Три датчика реалтайм: -#26-94-1D-75-C2-F8 -#T1#6.93 -#H1#21 -#P1#700.91 -## +#![doc = include_str!("../README.md")] -Формат пакета данных: -#MAC[#NAME]\n -#mac1#value1[#time1][#name1]\n -... -#macN#valueN[#timeN][#nameN]\n -## - -Загрузка истории показаний: -#26-94-1D-75-C2-F8 -#T1#6.93#1687006667 -#T1#10.17#1687006067 -#T1#27.26#1687005467 -## - -C названием и координатами: -#26-94-1D-75-C2-F8#Метео -#OWNER#nm17 -#T1#6.93#Улица -#T2#27.26#Дом -#P1#700.91#Барометр -#LAT#54.308997 -#LON#48.395861 -#ALT#233 -## - */ #![feature(try_blocks)] extern crate core; -mod hashes; +mod utils; mod ingest_protocol; mod web_server; - - - - - - - - - - - - - use crate::web_server::server_main; - -/*fn parse_sensor_value(input: Vec<&str>) -> MyIError, NarodMonValues> { - Ok( - (input, NarodMonValues { - mac: Default::default(), - value: Default::default(), - time: None, - name: None, - }) - ) -}*/ - struct Params {} #[ntex::main] diff --git a/src/hashes.rs b/src/utils.rs similarity index 78% rename from src/hashes.rs rename to src/utils.rs index 77fdb8c..0f4e19a 100644 --- a/src/hashes.rs +++ b/src/utils.rs @@ -1,11 +1,20 @@ +//! Глобальный модуль для вспомогательных типов и утилит. +//! + + use phf::phf_map; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Cow; + +/// Поддерживаемые типы. +/// +/// TODO: Решить необходимо ли к данным прикреплять единицы измерения. +/// TODO: Сейчас вообще сомнительно оставлять ли это или нет. #[derive(Debug, Clone, Hash, PartialEq, Eq)] #[repr(u64)] pub enum SupportedUnit { - Celsius, // Needs verification > 273.15 + Celsius, // Needs verification > -273.15 Percentage, // Needs verification >= 100 && <= 0 MillimeterHg, // Needs verification UVIndex, // Needs verification @@ -56,6 +65,7 @@ impl<'de> Deserialize<'de> for SupportedUnit { } } +/// Таблица преобразования текстового представления единиц в значения [SupportedUnit]. static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! { "C" => SupportedUnit::Celsius, "%" => SupportedUnit::Percentage, diff --git a/src/web_server/app_error.rs b/src/web_server/app_error.rs index 5195244..aa9ea10 100644 --- a/src/web_server/app_error.rs +++ b/src/web_server/app_error.rs @@ -12,11 +12,14 @@ use thiserror::Error; use crate::insert_header; -use crate::web_server::old_device_sensor_api::QSParserError; +use crate::web_server::old_device_sensor_api::qs_parser::QSParserError; use rust_decimal::Decimal; use serde_json::json; +/// Главный объект ошибки [std::error::Error] для всего Web API. +/// +/// В целом, все Result у Web сервера должны использовать этот Error. #[derive(Debug, Error, Display)] pub enum AppError { #[display(fmt = "IDK")] diff --git a/src/web_server/mod.rs b/src/web_server/mod.rs index bcc3d93..8e591d0 100644 --- a/src/web_server/mod.rs +++ b/src/web_server/mod.rs @@ -1,12 +1,19 @@ -use crate::web_server::old_app_api::old_api_handler; +//! Модуль веб сервера. +//! +//! Все модули отвечают только за Web сторону. Такие вещи как +//! [crate::web_server::old_device_sensor_api] отвечают только за веб версию. +//! +//! TODO: Начать работу над TCP/UDP и MQTT сервером + +use old_app_api::old_api_handler; use fred::bytes_utils::Str; use fred::prelude::*; -pub(crate) mod app_error; +pub mod app_error; pub mod old_app_api; -mod old_device_sensor_api; +pub mod old_device_sensor_api; pub mod utils; #[derive(Clone)] diff --git a/src/web_server/old_app_api/config_app.rs b/src/web_server/old_app_api/config_app.rs deleted file mode 100644 index 961c9b9..0000000 --- a/src/web_server/old_app_api/config_app.rs +++ /dev/null @@ -1,45 +0,0 @@ - - -use crate::web_server::app_error::AppError; -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; - - -pub async fn old_api_handler( - app_state: State, - body_bytes: Bytes, -) -> Result { - if body_bytes.len() > 10 * 1024 { - // 10 KiB - return Err(AppError::RequestTooLarge); - } - - let body_bytes = body_bytes.as_bytes(); - - let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON - - // Ignore clippy singlematch - match mandatory_params.cmd.as_ref() { - "version" => return version((), &app_state).await, - _ => {} - } - - is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?; - - match mandatory_params.cmd.as_ref() { - "appInit" => { - let body: AppInitRequest = serde_json::from_slice(body_bytes)?; - - app_init(body, &app_state).await - } - _ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())), - } - - //Ok("fuck") -} diff --git a/src/web_server/old_app_api/handlers/methods.rs b/src/web_server/old_app_api/handlers/methods.rs deleted file mode 100644 index e1a9893..0000000 --- a/src/web_server/old_app_api/handlers/methods.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::web_server::app_error::AppError; -use crate::web_server::old_app_api::types::AppInitRequest; -use crate::web_server::NMAppState; - -use serde_json::{json}; - - -use crate::insert_header; -use fred::interfaces::KeysInterface; -use ntex::http::StatusCode; -use ntex::web; - - - -pub async fn app_init( - _body: AppInitRequest<'_>, - app_state: &NMAppState, -) -> Result { - let _: () = app_state - .redis_client - .set("test", 123, None, None, true) - .await?; - - Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!")) -} - -pub async fn version(_body: (), _app_state: &NMAppState) -> Result { - 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) -} diff --git a/src/web_server/old_app_api/handlers/mod.rs b/src/web_server/old_app_api/handlers/mod.rs index 9cb9400..e1a9893 100644 --- a/src/web_server/old_app_api/handlers/mod.rs +++ b/src/web_server/old_app_api/handlers/mod.rs @@ -1,7 +1,36 @@ -mod methods; +use crate::web_server::app_error::AppError; +use crate::web_server::old_app_api::types::AppInitRequest; +use crate::web_server::NMAppState; + +use serde_json::{json}; -pub use methods::*; +use crate::insert_header; +use fred::interfaces::KeysInterface; +use ntex::http::StatusCode; +use ntex::web; +pub async fn app_init( + _body: AppInitRequest<'_>, + app_state: &NMAppState, +) -> Result { + let _: () = app_state + .redis_client + .set("test", 123, None, None, true) + .await?; + + Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!")) +} + +pub async fn version(_body: (), _app_state: &NMAppState) -> Result { + 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) +} diff --git a/src/web_server/old_app_api/mod.rs b/src/web_server/old_app_api/mod.rs index c1572dd..6abc3f3 100644 --- a/src/web_server/old_app_api/mod.rs +++ b/src/web_server/old_app_api/mod.rs @@ -1,5 +1,53 @@ -mod config_app; +//! Модуль обработки данных для приложений конечных пользователей, которые используют старый API. + mod handlers; mod types; -pub use config_app::old_api_handler; +use ntex::web::types::State; +use ntex::util::Bytes; +use ntex::web; +use nom::AsBytes; +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::types::{AppInitRequest, MandatoryParams}; +use crate::web_server::utils::redis::is_api_key_valid; + + +/// Обработчик запросов от приложений. +/// +/// Отвечает за разделение на функции по `cmd`. +/// +/// Вызывается напрямую из ntex приложения. +pub async fn old_api_handler( + app_state: State, + body_bytes: Bytes, +) -> Result { + if body_bytes.len() > 10 * 1024 { + // 10 KiB + return Err(AppError::RequestTooLarge); + } + + let body_bytes = body_bytes.as_bytes(); + + let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON + + // Ignore clippy singlematch + match mandatory_params.cmd.as_ref() { + "version" => return version((), &app_state).await, + _ => {} + } + + is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?; + + match mandatory_params.cmd.as_ref() { + "appInit" => { + let body: AppInitRequest = serde_json::from_slice(body_bytes)?; + + app_init(body, &app_state).await + } + _ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())), + } + + //Ok("fuck") +} diff --git a/src/web_server/old_app_api/types/mod.rs b/src/web_server/old_app_api/types/mod.rs index 31d83e3..de9b9bb 100644 --- a/src/web_server/old_app_api/types/mod.rs +++ b/src/web_server/old_app_api/types/mod.rs @@ -1,4 +1,3 @@ - use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -24,6 +23,11 @@ pub struct AddLikeRequest { pub version: u64, } +/// Обязательные параметры у JSON app API. +/// +/// При обработке входящих данных производиться два запроса [serde_json::from_str]. Один вызов пытается +/// получить [MandatoryParams], другой в зависимости от `cmd`. Это позволяет не добвалять поля из +/// этой структуры в каждом специфичном типе. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MandatoryParams<'a> { #[serde(borrow)] @@ -32,6 +36,11 @@ pub struct MandatoryParams<'a> { #[serde(borrow)] pub lang: Cow<'a, str>, + /// Уникальный ID клиента. + /// + /// Используется на подобии как куки PHPSESSID в php. + /// + /// См. также: #[serde(borrow)] pub uuid: Cow<'a, str>, diff --git a/src/web_server/old_device_sensor_api/mod.rs b/src/web_server/old_device_sensor_api/mod.rs index e0bb540..fe109eb 100644 --- a/src/web_server/old_device_sensor_api/mod.rs +++ b/src/web_server/old_device_sensor_api/mod.rs @@ -1,4 +1,6 @@ -mod qs_parser; +//! Модуль обработки данных с устройств, которые используют старый API. + +pub mod qs_parser; use std::str::FromStr; use crate::ingest_protocol::{NMDeviceDataPacket, NMJsonPacket}; @@ -13,6 +15,7 @@ use hifitime::Epoch; use thiserror::Error; use ufmt::uwrite; use crate::web_server::NMAppState; +use crate::web_server::old_device_sensor_api::qs_parser::QSParserError; #[derive(Error, Debug)] pub enum Error { @@ -20,9 +23,17 @@ pub enum Error { DeviceNotFound(String), #[error("Time sent with the device is way to behind now")] TimeIsLongBehindNow, + #[error("{0}")] + QSParserError(#[from] QSParserError) } +/// Обработчик данных датчиков с устройств. +/// +/// Слушает /post и /get. +/// Для того чтобы пользователям было легче, на оба пути можно отправлять и POST и GET. +/// +/// На POST можно отправлять JSON или url-encoded тело, на GET - только через Query String. pub async fn device_handler<'a>( request: web::HttpRequest, body: Bytes, diff --git a/src/web_server/old_device_sensor_api/qs_parser.rs b/src/web_server/old_device_sensor_api/qs_parser.rs index d517356..1bf1e3e 100644 --- a/src/web_server/old_device_sensor_api/qs_parser.rs +++ b/src/web_server/old_device_sensor_api/qs_parser.rs @@ -40,6 +40,11 @@ impl From for QSParserError { } } +/// Преобразование оставшихся параметров в urlencoded теле или query string в данные с датчиков +/// [SensorValue]. +/// +/// Формат: `=`. +/// Других данных на подобии названия и времени нет. pub fn qs_rest_to_values(parsed: HashMap) -> Result, QSParserError> { let mut hashset = HashSet::new(); diff --git a/src/web_server/utils/mod.rs b/src/web_server/utils/mod.rs index 5fed630..672afe1 100644 --- a/src/web_server/utils/mod.rs +++ b/src/web_server/utils/mod.rs @@ -1,3 +1,6 @@ +//! Сборник полезных функций которые используются в многих местах одновременно или слишком +//! неспециализированные. + pub mod redis; #[macro_export] diff --git a/src/web_server/utils/redis.rs b/src/web_server/utils/redis.rs index fe6a9da..cbb6a46 100644 --- a/src/web_server/utils/redis.rs +++ b/src/web_server/utils/redis.rs @@ -1,3 +1,5 @@ +//! Сборник утилит для работы с Redis. + use crate::web_server::app_error::AppError; use fred::prelude::*; use heapless::String as HeaplessString; @@ -6,13 +8,17 @@ use regex::Regex; use ufmt::uwrite; lazy_static! { + /// Разрешённые знаки для API ключа. static ref ALLOWED_API_KEY_CHARACTERS: Regex = Regex::new("[a-zA-Z0-9]{13}").unwrap(); } +/// Описание полей в KV DB у `apikey_{}`. pub struct ApiKeyDescription { + /// ID владельца API ключа. apikey_owner: i64, } +/// Проверка API ключа на валидность. pub async fn is_api_key_valid( client: &RedisClient, api_key: &str,