From 239501440074ec26c9a7a922e00be41c4aed7fd3 Mon Sep 17 00:00:00 2001 From: Paul Zinselmeyer Date: Fri, 3 May 2024 16:30:31 +0200 Subject: [PATCH] add sml + powermeter --- .gitignore | 2 +- Cargo.lock | 62 ++ Cargo.toml | 7 +- canome/src/lib.rs | 1 + canome/src/power.rs | 41 ++ canome_derive/Cargo.toml | 11 + canome_derive/src/lib.rs | 62 ++ sml_parser/Cargo.toml | 10 + sml_parser/sml_parser_derive/Cargo.toml | 11 + sml_parser/sml_parser_derive/src/lib.rs | 52 ++ sml_parser/src/lib.rs | 805 ++++++++++++++++++++++++ sml_parser/src/primitives.rs | 247 ++++++++ 12 files changed, 1307 insertions(+), 4 deletions(-) create mode 100644 canome/src/power.rs create mode 100644 canome_derive/Cargo.toml create mode 100644 canome_derive/src/lib.rs create mode 100644 sml_parser/Cargo.toml create mode 100644 sml_parser/sml_parser_derive/Cargo.toml create mode 100644 sml_parser/sml_parser_derive/src/lib.rs create mode 100644 sml_parser/src/lib.rs create mode 100644 sml_parser/src/primitives.rs diff --git a/.gitignore b/.gitignore index c1ee528..d6cc307 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -Cargo.toml +Cargo.lock target .env result diff --git a/Cargo.lock b/Cargo.lock index 9ea2946..3a69e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,14 @@ dependencies = [ "stm32f1xx-hal", ] +[[package]] +name = "canome_derive" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.60", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -108,6 +116,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "critical-section" version = "1.1.2" @@ -236,6 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", + "serde", "stable_deref_trait", ] @@ -365,6 +389,44 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.200" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.200" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "sml_parser" +version = "0.1.0" +dependencies = [ + "crc", + "defmt", + "heapless", + "sml_parser_derive", +] + +[[package]] +name = "sml_parser_derive" +version = "0.1.0" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 93acceb..af61efd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] resolver = "2" members = [ - "canome" + "canome", + "canome_derive", + "sml_parser", + "sml_parser/sml_parser_derive", ] - - diff --git a/canome/src/lib.rs b/canome/src/lib.rs index b7f5156..5ee2431 100644 --- a/canome/src/lib.rs +++ b/canome/src/lib.rs @@ -8,6 +8,7 @@ pub mod can; pub mod contact; pub mod cover; pub mod light; +pub mod power; pub mod state; #[cfg(feature = "stm32")] pub mod stm32; diff --git a/canome/src/power.rs b/canome/src/power.rs new file mode 100644 index 0000000..0c67dd5 --- /dev/null +++ b/canome/src/power.rs @@ -0,0 +1,41 @@ +use defmt::Format; + +#[derive(Default, Debug, Format, Clone, Copy, PartialEq, Eq)] +pub struct PowermeterState { + pub total: u32, + pub current: u32, +} +#[cfg(feature = "can")] +mod can { + use crate::{can::CanDataFormat, Decode, Encode}; + + use super::PowermeterState; + + impl Encode for PowermeterState { + fn encode(self) -> CanDataFormat { + let total = self.total.to_be_bytes(); + let current = self.current.to_be_bytes(); + CanDataFormat::new(&[ + total[0], total[1], total[2], total[3], current[0], current[1], current[2], + current[3], + ]) + } + } + + impl Decode for PowermeterState { + fn decode(value: CanDataFormat) -> Result + where + Self: Sized, + { + let data = value.data(); + if data.len() != 8 { + return Err(value); + } + + let total = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); + let current = u32::from_be_bytes([data[4], data[4], data[5], data[6]]); + + Ok(Self { total, current }) + } + } +} diff --git a/canome_derive/Cargo.toml b/canome_derive/Cargo.toml new file mode 100644 index 0000000..4e4f7a1 --- /dev/null +++ b/canome_derive/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "canome_derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0" +quote = "1.0" diff --git a/canome_derive/src/lib.rs b/canome_derive/src/lib.rs new file mode 100644 index 0000000..9667e28 --- /dev/null +++ b/canome_derive/src/lib.rs @@ -0,0 +1,62 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parenthesized, Data, Expr, Fields, GenericParam, Path}; + +#[proc_macro_derive(Channel)] +pub fn derive_channel(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_channel(&ast) +} +fn impl_channel(input: &syn::DeriveInput) -> TokenStream { + let name = &input.ident; + + let canome_attr = input + .attrs + .iter() + .find(|x| x.path().is_ident("canome")) + .unwrap(); + + let mut group: Expr = syn::parse_quote!(()); + canome_attr + .parse_nested_meta(|meta| { + if meta.path.is_ident("group") { + let content; + parenthesized!(content in meta.input ); + group = content.parse()?; + + return Ok(()); + } + Err(meta.error("unrecognized canome attr")) + }) + .unwrap(); + + let mut generics = input.generics.clone(); + let tx_cap_param = syn::parse_quote!(const TX_CAP: usize); + generics.params.push(GenericParam::Const(tx_cap_param)); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + match &input.data { + Data::Struct(str) => match &str.fields { + Fields::Unnamed(fields) => { + if fields.unnamed.len() != 1 { + unimplemented!(); + } + let field = fields.unnamed.first().unwrap(); + + let field_type = &field.ty; + + let group = ""; + + quote! { + impl #impl_generics Channel> for #name #ty_generics #where_clause{ + const ID: BxCanId = #group; + type State = #field_type; + } + } + .into() + } + _ => unimplemented!(), + }, + _ => unimplemented!(), + } +} diff --git a/sml_parser/Cargo.toml b/sml_parser/Cargo.toml new file mode 100644 index 0000000..069ff23 --- /dev/null +++ b/sml_parser/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sml_parser" +version = "0.1.0" +edition = "2021" + +[dependencies] +heapless = { version = "0.8.0", features = ["serde"]} +sml_parser_derive = {path = "sml_parser_derive"} +crc = "3.2" +defmt = "0.3.6" diff --git a/sml_parser/sml_parser_derive/Cargo.toml b/sml_parser/sml_parser_derive/Cargo.toml new file mode 100644 index 0000000..87f84a7 --- /dev/null +++ b/sml_parser/sml_parser_derive/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sml_parser_derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "1.0" +quote = "1.0" diff --git a/sml_parser/sml_parser_derive/src/lib.rs b/sml_parser/sml_parser_derive/src/lib.rs new file mode 100644 index 0000000..8a119ac --- /dev/null +++ b/sml_parser/sml_parser_derive/src/lib.rs @@ -0,0 +1,52 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::Data; + +#[proc_macro_derive(SmlType)] +pub fn derive_sml_type(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_syl_type(&ast) +} + +fn impl_syl_type(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + match &ast.data { + Data::Struct(s) => { + let fields = &s + .fields + .iter() + .map(|x| { + let name = &x.ident.as_ref().unwrap(); + quote! { + #name: buf.parse()? + + } + }) + .collect::>(); + let num_fields = s.fields.len(); + + let lifetimes = ast.generics.lifetimes().collect::>(); + + quote! { + impl<'a> SmlType<'a> for #name<#(#lifetimes),*> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != #num_fields { + return Err(Error::ExpectedStruct(core::any::type_name::())); + } + Ok(Self { + #(#fields),* + }) + } + } + } + .into() + } + _ => unimplemented!(), + } +} diff --git a/sml_parser/src/lib.rs b/sml_parser/src/lib.rs new file mode 100644 index 0000000..ab62fed --- /dev/null +++ b/sml_parser/src/lib.rs @@ -0,0 +1,805 @@ +#![no_std] +use core::fmt::Debug; +use core::slice::SliceIndex; +use defmt::Format; +use primitives::{FieldType, ListIter, ListOf, OctetString, TypeLengthField}; +use sml_parser_derive::SmlType; + +pub mod primitives; + +const CRC16: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); + +#[derive(Debug, Format)] +pub enum Error { + ExpectedSigned, + ExpectedUnsigned, + ExpectedOctetString, + ExpectedBool, + ExpectedList, + ExpectedSmlMessage, + ExpectedEndOfSmlMsg, + ExpectedSmlPublicOpenReq, + ExpectedSmlTimestampLocal, + ExpectedSmlPublicCloseReq, + ExpectedSmlPublicCloseRes, + + UnsupportedTLF, + + ExpectedStruct(&'static str), + ExpectedEnum(&'static str), + ExpectedOption, + InvalidCRC, + + ExpectedEscapeSeq, + InvalidPadding, + + ExpectedAbortOnError, + + Custom, +} + +pub trait SmlType<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized; +} + +#[derive(Clone)] +pub struct ParserContext<'a>(pub &'a [u8]); + +impl<'a> ParserContext<'a> { + pub fn parse>(&mut self) -> Result { + T::parse(self) + } + pub fn peek>(&self, range: I) -> Result<&'a I::Output, Error> { + Ok(&self.0[range]) + } + + pub fn peek_until(&self, other: &ParserContext<'a>) -> Result<&'a [u8], Error> { + if let Some(offset) = other.index_within(self) { + self.peek(..offset) + } else { + self.peek(..) + } + } + + pub fn read(&mut self, length: usize) -> Result<&'a [u8], Error> { + let val = &self.0[..length]; + self.0 = &self.0[length..]; + Ok(val) + } + pub fn move_fwd(&mut self, dist: usize) -> Result<(), Error> { + self.0 = &self.0[dist..]; + Ok(()) + } + fn index_within(&self, outer: &Self) -> Option { + let outer_beg = outer.0.as_ptr() as usize; + let outer_end = outer_beg + outer.0.len(); + let inner_beg = self.0.as_ptr() as usize; + let inner_end = inner_beg + self.0.len(); + if inner_beg >= outer_beg && inner_end <= outer_end { + Some(inner_beg - outer_beg) + } else { + None + } + } +} + +/// representation of an SML File that holds multiple [`SmlMessage`]. +pub struct SmlFile<'a> { + data: &'a [u8], + len: usize, +} + +impl<'a> SmlType<'a> for SmlFile<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let crc_buf = buf.clone(); + if buf.peek(..8)? != [0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01] { + return Err(Error::ExpectedEscapeSeq); + } + buf.move_fwd(8)?; + let prev_buf = buf.clone(); + let mut len = 0; + while ![0x00, 0x1b].contains(buf.peek(0)?) { + SmlMessage::parse(buf)?; + len += 1; + } + let data = prev_buf.peek_until(buf)?; + + let mut fill = 0; + while *buf.peek(0)? == 0 { + fill += 1; + buf.move_fwd(1)?; + } + if buf.peek(..5)? != [0x1b, 0x1b, 0x1b, 0x1b, 0x1a] { + return Err(Error::ExpectedEscapeSeq); + } + buf.move_fwd(5)?; + let should_fill = *buf.peek(0)?; + buf.move_fwd(1)?; + if fill != should_fill { + return Err(Error::InvalidPadding); + } + + let crc_data = crc_buf.peek_until(buf)?; + + let mut should_crc = [0; 2]; + should_crc.copy_from_slice(buf.peek(..2)?); + let should_crc = u16::from_be_bytes(should_crc); + + let crc = CRC16.checksum(crc_data).swap_bytes(); + if crc != should_crc { + return Err(Error::InvalidCRC); + } + + Ok(Self { data, len }) + } +} + +impl<'a> SmlFile<'a> { + pub fn iter(&self) -> ListIter<'a, SmlMessage<'a>> { + ListIter::new(self.data, self.len) + } + pub fn len(&self) -> usize { + self.len + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a> Debug for SmlFile<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SmlFile") + .field("len", &self.len) + .field("messages", &self.iter()) + .finish() + } +} + +/// representation of the SML_Message Sequence +#[derive(Debug)] +pub struct SmlMessage<'a> { + pub transaction_id: OctetString<'a>, + pub group_no: u8, + pub abort_on_error: AbortOnError, + pub message_body: SmlMessageBody<'a>, + pub crc16: u16, + pub end_of_sml_msg: EndOfSmlMsg, +} + +impl<'a> SmlType<'a> for SmlMessage<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let pre_buf = buf.clone(); + + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != 6usize { + return Err(Error::ExpectedStruct(core::any::type_name::())); + } + + let transaction_id = buf.parse()?; + let group_no = buf.parse()?; + let abort_on_error = buf.parse()?; + let message_body = buf.parse()?; + + let data_for_crc = pre_buf.peek_until(buf)?; + + let crc16 = buf.parse()?; + + if CRC16.checksum(data_for_crc).swap_bytes() != crc16 { + return Err(Error::InvalidCRC); + } + + Ok(Self { + transaction_id, + group_no, + abort_on_error, + message_body, + crc16, + end_of_sml_msg: buf.parse()?, + }) + } +} + +#[derive(Debug)] +pub enum SmlMessageBody<'a> { + OpenRequest(SmlPublicOpenReq<'a>), + OpenResponse(SmlPublicOpenRes<'a>), + CloseRequest(SmlPublicCloseReq<'a>), + CloseResponse(SmlPublicCloseRes<'a>), + GetProfilePackRequest(SmlGetProfilePackReq<'a>), + GetProfilePackResponse(SmlGetProfilePackRes<'a>), + GetProfileListRequest(SmlGetProfileListReq<'a>), + GetProfileListResponse(SmlGetProfileListRes<'a>), + GetProcParameterRequest(SmlGetProcParameterReq<'a>), + GetProcParameterResponse(SmlGetProcParameterRes<'a>), + SetProcParameterRequest(SmlSetProcParameterReq<'a>), + GetListRequest(SmlGetListReq<'a>), + GetListResponse(SmlGetListRes<'a>), + GetCosemRequest(SmlGetCosemReq<'a>), + GetCosemResponse(SmlGetCosemRes<'a>), + SetCosemRequest(SmlSetCosemReq<'a>), + SetCosemResponse(SmlSetCosemRes<'a>), + ActionCosemRequest(SmlActionCosemReq<'a>), + ActionCosemResponse(SmlActionCosemRes<'a>), + AttentionResponse(SmlAttentionRes<'a>), +} + +impl<'a> SmlType<'a> for SmlMessageBody<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + use SmlMessageBody as S; + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != 2 { + return Err(Error::ExpectedEnum(core::any::type_name::())); + } + let disc: u32 = buf.parse()?; + + match disc { + 0x00000100 => buf.parse().map(S::OpenRequest), + 0x00000101 => buf.parse().map(S::OpenResponse), + 0x00000200 => buf.parse().map(S::CloseRequest), + 0x00000201 => buf.parse().map(S::CloseResponse), + 0x00000300 => buf.parse().map(S::GetProfilePackRequest), + 0x00000301 => buf.parse().map(S::GetProfilePackResponse), + 0x00000400 => buf.parse().map(S::GetProfileListRequest), + 0x00000401 => buf.parse().map(S::GetProfileListResponse), + 0x00000500 => buf.parse().map(S::GetProcParameterRequest), + 0x00000501 => buf.parse().map(S::GetProcParameterResponse), + 0x00000600 => buf.parse().map(S::SetProcParameterRequest), + 0x00000700 => buf.parse().map(S::GetListRequest), + 0x00000701 => buf.parse().map(S::GetListResponse), + 0x00000800 => buf.parse().map(S::GetCosemRequest), + 0x00000801 => buf.parse().map(S::GetCosemResponse), + 0x00000900 => buf.parse().map(S::SetCosemRequest), + 0x00000901 => buf.parse().map(S::SetCosemResponse), + 0x00000a00 => buf.parse().map(S::ActionCosemRequest), + 0x00000a01 => buf.parse().map(S::ActionCosemResponse), + 0x0000ff01 => buf.parse().map(S::AttentionResponse), + _ => Err(Error::ExpectedEnum(core::any::type_name::())), + } + } +} + +#[derive(Debug, SmlType)] +pub struct SmlPublicOpenReq<'a> { + pub codepage: Option>, + pub client_id: OctetString<'a>, + pub req_file_id: OctetString<'a>, + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub sml_version: Option, +} + +#[derive(Debug, SmlType)] +pub struct SmlPublicOpenRes<'a> { + pub codepage: Option>, + pub client_id: Option>, + pub req_file_id: OctetString<'a>, + pub server_id: OctetString<'a>, + pub ref_time: Option, + pub sml_version: Option, +} + +#[derive(Debug, SmlType)] +pub struct SmlPublicCloseReq<'a> { + pub global_signature: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlPublicCloseRes<'a> { + pub global_signature: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlGetProfilePackReq<'a> { + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub with_rawdata: Option, + pub begin_time: Option, + pub end_time: Option, + pub paramter_tree_path: SmlTreePath<'a>, + pub object_list: Option>>, + pub das_details: Option>, +} +pub type SmlObjReqEntry<'a> = OctetString<'a>; + +#[derive(Debug, SmlType)] +pub struct SmlGetProfilePackRes<'a> { + pub server_id: OctetString<'a>, + pub act_time: SmlTime, + pub reg_period: u32, + pub paramter_tree_path: SmlTreePath<'a>, + pub header_list: ListOf<'a, SmlProfObjHeaderEntry<'a>>, + pub period_list: ListOf<'a, SmlProfObjPeriodEntry<'a>>, + pub rawdata: Option>, + pub profile_signature: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlProfObjHeaderEntry<'a> { + pub obj_name: OctetString<'a>, + pub unit: SmlUnit, + pub scaler: u8, +} +#[derive(Debug, SmlType)] +pub struct SmlProfObjPeriodEntry<'a> { + pub val_time: SmlTime, + pub status: u64, + pub value_list: ListOf<'a, SmlValueEntry<'a>>, + pub period_signature: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlValueEntry<'a> { + pub value: SmlValue<'a>, + pub value_signature: Option>, +} + +pub type SmlUnit = u8; + +#[derive(Debug, SmlType)] +pub struct SmlGetProfileListReq<'a> { + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub with_rawdata: Option, + pub begin_time: Option, + pub end_time: Option, + pub paramter_tree_path: SmlTreePath<'a>, + pub object_list: Option>>, + pub das_details: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlGetProfileListRes<'a> { + pub server_id: OctetString<'a>, + pub act_time: SmlTime, + pub reg_period: u32, + pub paramter_tree_path: SmlTreePath<'a>, + pub val_time: SmlTime, + pub status: u64, + pub period_list: ListOf<'a, SmlPeriodEntry<'a>>, + pub rawdata: Option>, + pub period_signature: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlPeriodEntry<'a> { + pub obj_name: OctetString<'a>, + pub unit: SmlUnit, + pub scaler: i8, + pub value: SmlValue<'a>, + pub value_signature: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlGetProcParameterReq<'a> { + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub paramter_tree_path: SmlTreePath<'a>, + pub attribute: Option>, +} + +pub type SmlTreePath<'a> = ListOf<'a, OctetString<'a>>; + +#[derive(Debug, SmlType)] +pub struct SmlGetProcParameterRes<'a> { + pub server_id: OctetString<'a>, + pub paramter_tree_path: SmlTreePath<'a>, + pub paramter_tree: SmlTree<'a>, +} + +#[derive(Debug, SmlType)] +pub struct SmlTree<'a> { + pub parameter_name: OctetString<'a>, + pub parameter_value: Option>, + pub child_list: Option>>, +} + +#[derive(Debug, SmlType)] +pub struct SmlSetProcParameterReq<'a> { + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub paramter_tree_path: SmlTreePath<'a>, + pub paramter_tree: SmlTree<'a>, +} + +#[derive(Debug, SmlType)] +pub struct SmlAttentionRes<'a> { + pub server_id: OctetString<'a>, + pub attention_no: OctetString<'a>, + pub attention_msg: Option>, + pub attention_details: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlGetListReq<'a> { + pub client_id: OctetString<'a>, + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub list_name: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlGetListRes<'a> { + pub client_id: Option>, + pub server_id: OctetString<'a>, + pub list_name: OctetString<'a>, + pub act_sensor_time: Option, + pub val_list: ListOf<'a, SmlListEntry<'a>>, + pub list_signature: Option>, + pub act_gateway_time: Option, +} + +#[derive(Debug, SmlType)] +pub struct SmlListEntry<'a> { + pub obj_name: OctetString<'a>, + pub status: Option, + pub val_time: Option, + pub unit: Option, + pub scaler: Option, + pub value: SmlValue<'a>, + pub value_signature: Option>, +} + +pub type SmlStatus = u64; + +#[derive(Debug, SmlType)] +pub struct SmlGetCosemReq<'a> { + pub client_id: OctetString<'a>, + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub obj_name: OctetString<'a>, + pub class_id: i16, + pub class_version: i16, + pub attribute_index_list: Option>>, +} + +#[derive(Debug, SmlType)] +pub struct SmlCosemAttributeDesc<'a> { + pub attribute_index: i16, + pub selective_access_descriptor: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlCosemSelAccessDesc<'a> { + pub access_selector: u8, + pub access_parameters: SmlCosemValue<'a>, +} + +#[derive(Debug, SmlType)] +pub struct SmlGetCosemRes<'a> { + pub client_id: Option>, + pub server_id: OctetString<'a>, + pub obj_name: OctetString<'a>, + pub class_id: i16, + pub class_version: i16, + pub attribute_list: ListOf<'a, SmlCosemAttribute<'a>>, +} + +#[derive(Debug, SmlType)] +pub struct SmlCosemAttribute<'a> { + pub attribute_description: SmlCosemAttributeDesc<'a>, + pub attribute_content: SmlCosemAttributeContent<'a>, +} + +#[derive(Debug, SmlType)] +pub struct SmlSetCosemReq<'a> { + pub client_id: OctetString<'a>, + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub obj_name: OctetString<'a>, + pub class_id: i16, + pub class_version: i16, + pub attribute_list: ListOf<'a, SmlCosemAttribute<'a>>, +} + +#[derive(Debug, SmlType)] +pub struct SmlSetCosemRes<'a> { + pub client_id: Option>, + pub server_id: OctetString<'a>, + pub obj_name: OctetString<'a>, + pub class_id: i16, + pub class_version: i16, + pub attribute_list: Option>>, +} + +#[derive(Debug, SmlType)] +pub struct SmlActionCosemReq<'a> { + pub client_id: OctetString<'a>, + pub server_id: Option>, + pub username: Option>, + pub password: Option>, + pub obj_name: OctetString<'a>, + pub class_id: i16, + pub class_version: i16, + pub service_index: u8, + pub service_parameter: Option>, +} + +#[derive(Debug, SmlType)] +pub struct SmlActionCosemRes<'a> { + pub client_id: Option>, + pub server_id: OctetString<'a>, + pub obj_name: OctetString<'a>, + pub class_id: i16, + pub class_version: i16, + pub attribute_list: Option>>, +} + +#[derive(Debug)] +pub struct EndOfSmlMsg; +impl<'a> SmlType<'a> for EndOfSmlMsg { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + if *buf.peek(0)? == 0 { + buf.move_fwd(1)?; + Ok(Self) + } else { + Err(Error::ExpectedEndOfSmlMsg) + } + } +} + +#[derive(Debug)] +pub enum AbortOnError { + Continue, + ContinueWithNextGroup, + AbortAfterGroup, + Abort, +} +impl<'a> SmlType<'a> for AbortOnError { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let disc: u8 = buf.read(1)?[0]; + Ok(match disc { + 0x00 => Self::Continue, + 0x01 => Self::ContinueWithNextGroup, + 0x02 => Self::AbortAfterGroup, + 0x03 => Self::Abort, + _ => return Err(Error::ExpectedAbortOnError), + }) + } +} + +#[derive(Debug, Format)] +pub enum SmlValue<'a> { + Boolean(bool), + OctetString(OctetString<'a>), + Integer8(i8), + Integer16(i16), + Integer32(i32), + Integer64(i64), + Unsigned8(u8), + Unsigned16(u16), + Unsigned32(u32), + Unsigned64(u64), + List(SmlListType), +} +impl<'a> SmlType<'a> for SmlValue<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::peek(buf)?; + match (tlf.kind(), tlf.data_len()) { + (FieldType::Boolean, _) => buf.parse().map(Self::Boolean), + (FieldType::OctetString, _) => buf.parse().map(Self::OctetString), + (FieldType::Integer, 1) => buf.parse().map(Self::Integer8), + (FieldType::Integer, 2) => buf.parse().map(Self::Integer16), + (FieldType::Integer, x) if x <= 4 => buf.parse().map(Self::Integer32), + (FieldType::Integer, x) if x <= 8 => buf.parse().map(Self::Integer64), + (FieldType::Unsigned, 1) => buf.parse().map(Self::Unsigned8), + (FieldType::Unsigned, 2) => buf.parse().map(Self::Unsigned16), + (FieldType::Unsigned, x) if x <= 4 => buf.parse().map(Self::Unsigned32), + (FieldType::Unsigned, x) if x <= 8 => buf.parse().map(Self::Unsigned64), + (FieldType::List, _) => buf.parse().map(Self::List), + _ => unimplemented!(), + } + } +} + +#[derive(Debug, Format)] +pub enum SmlListType { + Time(SmlTime), +} + +impl<'a> SmlType<'a> for SmlListType { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + use SmlListType as S; + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != 2 { + return Err(Error::ExpectedEnum(core::any::type_name::())); + } + let disc: u8 = buf.parse()?; + + match disc { + 0x01 => buf.parse().map(S::Time), + _ => Err(Error::ExpectedEnum(core::any::type_name::())), + } + } +} + +#[derive(Debug)] +pub enum SmlProcParValue<'a> { + Value(SmlValue<'a>), + PeriodEntry(SmlPeriodEntry<'a>), + TupelEntry(SmlTupelEntry<'a>), + Time(SmlTime), + ListEntry(SmlListEntry<'a>), +} +impl<'a> SmlType<'a> for SmlProcParValue<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + use SmlProcParValue as S; + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != 2 { + return Err(Error::ExpectedEnum(core::any::type_name::())); + } + let disc: u8 = buf.parse()?; + + match disc { + 0x01 => buf.parse().map(S::Value), + 0x02 => buf.parse().map(S::PeriodEntry), + 0x03 => buf.parse().map(S::TupelEntry), + 0x04 => buf.parse().map(S::Time), + 0x05 => buf.parse().map(S::ListEntry), + _ => Err(Error::ExpectedEnum(core::any::type_name::())), + } + } +} + +#[derive(Debug, SmlType)] +pub struct SmlTupelEntry<'a> { + pub server_id: OctetString<'a>, + pub sec_index: SmlTime, + pub status: u64, + pub unit_pa: SmlUnit, + pub scaler_pa: i8, + pub value_pa: i64, + pub unit_r1: SmlUnit, + pub scaler_r1: i8, + pub value_r1: i64, + pub unit_r4: SmlUnit, + pub scaler_r4: i8, + pub value_r4: i64, + pub signature_pa_r1_r4: OctetString<'a>, + pub unit_ma: SmlUnit, + pub scaler_ma: i8, + pub value_ma: i64, + pub unit_r2: SmlUnit, + pub scaler_r2: i8, + pub value_r2: i64, + pub unit_r3: SmlUnit, + pub scaler_r3: i8, + pub value_r3: i64, + pub signature_ma_r2_r3: OctetString<'a>, +} + +#[derive(Debug, Format)] +pub enum SmlTime { + SecIndex(u32), + Timestamp(SmlTimestamp), + TimestampLocal(SmlTimestampLocal), +} +impl<'a> SmlType<'a> for SmlTime { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + use SmlTime as S; + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != 2 { + return Err(Error::ExpectedEnum(core::any::type_name::())); + } + let disc: u8 = buf.parse()?; + + match disc { + 0x01 => buf.parse().map(S::SecIndex), + 0x02 => buf.parse().map(S::Timestamp), + 0x03 => buf.parse().map(S::TimestampLocal), + _ => Err(Error::ExpectedEnum(core::any::type_name::())), + } + } +} + +pub type SmlTimestamp = u32; + +#[derive(Debug, SmlType, Format)] +pub struct SmlTimestampLocal { + pub timestamp: SmlTimestamp, + pub local_offset: u16, + pub season_time_offset: u16, +} + +#[derive(Debug)] +pub enum SmlCosemValue<'a> { + NullData, + Boolean(bool), + BitString, + OctetString(OctetString<'a>), + Integer8(i8), + Integer16(i16), + Integer32(i32), + Integer64(i64), + Unsigned8(u8), + Unsigned16(u16), + Unsigned32(u32), + Unsigned64(u64), + Struct(ListOf<'a, SmlCosemValue<'a>>), + Array(ListOf<'a, SmlCosemValue<'a>>), +} +impl<'a> SmlType<'a> for SmlCosemValue<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::peek(buf)?; + match (tlf.kind(), tlf.data_len()) { + (FieldType::Boolean, _) => buf.parse().map(Self::Boolean), + (FieldType::OctetString, _) => buf.parse().map(Self::OctetString), + (FieldType::Integer, 1) => buf.parse().map(Self::Integer8), + (FieldType::Integer, 2) => buf.parse().map(Self::Integer16), + (FieldType::Integer, 4) => buf.parse().map(Self::Integer32), + (FieldType::Integer, 8) => buf.parse().map(Self::Integer64), + (FieldType::Unsigned, 1) => buf.parse().map(Self::Unsigned8), + (FieldType::Unsigned, 2) => buf.parse().map(Self::Unsigned16), + (FieldType::Unsigned, 4) => buf.parse().map(Self::Unsigned32), + (FieldType::Unsigned, 8) => buf.parse().map(Self::Unsigned64), + (FieldType::List, _) => buf.parse().map(Self::Struct), + _ => unimplemented!(), + } + } +} + +#[derive(Debug)] +pub enum SmlCosemAttributeContent<'a> { + Data(SmlCosemValue<'a>), + DataAccessResult(u8), +} +impl<'a> SmlType<'a> for SmlCosemAttributeContent<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + use SmlCosemAttributeContent as S; + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind() != FieldType::List || tlf.len() != 2 { + return Err(Error::ExpectedEnum(core::any::type_name::())); + } + let disc: u8 = buf.parse()?; + + match disc { + 0x01 => buf.parse().map(S::Data), + 0x02 => buf.parse().map(S::DataAccessResult), + _ => Err(Error::ExpectedEnum(core::any::type_name::())), + } + } +} + +pub type SmlSignature<'a> = OctetString<'a>; diff --git a/sml_parser/src/primitives.rs b/sml_parser/src/primitives.rs new file mode 100644 index 0000000..135acc2 --- /dev/null +++ b/sml_parser/src/primitives.rs @@ -0,0 +1,247 @@ +use core::{fmt::Debug, marker::PhantomData}; + +use defmt::Format; + +use crate::{Error, ParserContext, SmlType}; + +#[derive(Debug)] +pub struct TypeLengthField { + header_len: usize, + len: usize, + kind: FieldType, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum FieldType { + OctetString, + Boolean, + Integer, + Unsigned, + List, + Reserved, +} + +impl<'a> SmlType<'a> for TypeLengthField { + fn parse(buf: &mut ParserContext<'a>) -> Result { + let val = Self::peek(buf)?; + buf.move_fwd(val.header_len)?; + Ok(val) + } +} +impl TypeLengthField { + pub fn peek(buf: &ParserContext) -> Result { + let mut len: usize = 0; + let mut header_len = 0; + for b in buf.peek(0..)? { + let additional = (*b >> 7) & 1 == 1; + header_len += 1; + if additional { + len += (*b & 0b1111) as usize; + len <<= 4; + } else { + len += (*b & 0b1111) as usize; + break; + } + } + + let kind = (*buf.peek(0)? >> 4) & 0b111; + let kind = match kind { + 0b000 => FieldType::OctetString, + 0b100 => FieldType::Boolean, + 0b101 => FieldType::Integer, + 0b110 => FieldType::Unsigned, + 0b111 => FieldType::List, + 0b001 => FieldType::Reserved, + 0b010 => FieldType::Reserved, + 0b011 => FieldType::Reserved, + _ => return Err(Error::UnsupportedTLF), + }; + Ok(Self { + header_len, + len, + kind, + }) + } + pub fn data_len(&self) -> usize { + self.len - self.header_len + } + pub fn header_len(&self) -> usize { + self.header_len + } + pub fn len(&self) -> usize { + self.len + } + pub fn kind(&self) -> FieldType { + self.kind + } +} + +#[derive(Debug, Format)] +pub struct OctetString<'a>(pub &'a [u8]); +impl<'a> SmlType<'a> for OctetString<'a> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind != FieldType::OctetString { + return Err(Error::ExpectedOctetString); + } + Ok(Self(buf.read(tlf.data_len())?)) + } +} + +pub struct ListOf<'a, T: SmlType<'a>> { + data: &'a [u8], + len: usize, + _t: PhantomData, +} + +impl<'a, T: SmlType<'a>> SmlType<'a> for ListOf<'a, T> { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind != FieldType::List { + return Err(Error::ExpectedList); + } + + let pre_buf = buf.clone(); + + for res in (0..tlf.len).map(|_| T::parse(buf)) { + res?; + } + + Ok(Self { + data: pre_buf.peek_until(buf)?, + len: tlf.len, + _t: PhantomData, + }) + } +} +impl<'a, T: SmlType<'a> + Debug> Debug for ListOf<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ListOf") + .field("len", &self.len) + .field("items", &self.iter()) + .finish() + } +} + +impl<'a, T: SmlType<'a>> ListOf<'a, T> { + pub fn iter(&self) -> ListIter<'a, T> { + ListIter::new(self.data, self.len) + } +} + +pub struct ListIter<'a, T: SmlType<'a>> { + len: usize, + cursor: ParserContext<'a>, + index: usize, + _t: PhantomData, +} + +impl<'a, T: SmlType<'a>> Clone for ListIter<'a, T> { + fn clone(&self) -> Self { + Self { + len: self.len, + cursor: self.cursor.clone(), + index: self.index, + _t: PhantomData, + } + } +} + +impl<'a, T: SmlType<'a> + Debug> Debug for ListIter<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut list = f.debug_list(); + for i in self.clone().filter_map(|x| x.ok()) { + list.entry(&i); + } + list.finish() + } +} + +impl<'a, T: SmlType<'a>> ListIter<'a, T> { + pub const fn new(data: &'a [u8], len: usize) -> Self { + Self { + len, + cursor: ParserContext(data), + index: 0, + _t: PhantomData, + } + } +} +impl<'a: 'b, 'b, T: SmlType<'a>> Iterator for ListIter<'a, T> { + type Item = Result; + + fn next(&mut self) -> Option { + self.index += 1; + if self.index < self.len { + Some(T::parse(&mut self.cursor).map_err(|x| { + self.index = self.len; + x + })) + } else { + None + } + } +} + +impl<'a> SmlType<'a> for bool { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind != FieldType::Boolean { + return Err(Error::ExpectedBool); + } + let val = buf.read(tlf.data_len())?; + Ok(val.iter().any(|x| *x > 0)) + } +} + +macro_rules! impl_sml_type { + ($ident:ident, $type:expr, $len:expr) => { + impl<'a> SmlType<'a> for $ident { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + let tlf = TypeLengthField::parse(buf)?; + if tlf.kind != $type && tlf.data_len() <= $len { + return Err(Error::ExpectedUnsigned); + } + const SIZE: usize = $len; + let mut val = [0; SIZE]; + val[SIZE - tlf.data_len()..].copy_from_slice(buf.read(tlf.data_len())?); + Ok(Self::from_be_bytes(val)) + } + } + }; +} +impl_sml_type!(u8, FieldType::Unsigned, 1); +impl_sml_type!(u16, FieldType::Unsigned, 2); +impl_sml_type!(u32, FieldType::Unsigned, 4); +impl_sml_type!(u64, FieldType::Unsigned, 8); + +impl_sml_type!(i8, FieldType::Integer, 1); +impl_sml_type!(i16, FieldType::Integer, 2); +impl_sml_type!(i32, FieldType::Integer, 4); +impl_sml_type!(i64, FieldType::Integer, 8); + +impl<'a, T: SmlType<'a>> SmlType<'a> for Option { + fn parse(buf: &mut ParserContext<'a>) -> Result + where + Self: Sized, + { + if *buf.peek(0)? == 1 { + buf.move_fwd(1)?; + Ok(None) + } else { + T::parse(buf).map(Some) + } + } +}