add sml + powermeter
This commit is contained in:
parent
4e48060bfa
commit
2395014400
12 changed files with 1307 additions and 4 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
Cargo.toml
|
Cargo.lock
|
||||||
target
|
target
|
||||||
.env
|
.env
|
||||||
result
|
result
|
||||||
|
|
62
Cargo.lock
generated
62
Cargo.lock
generated
|
@ -70,6 +70,14 @@ dependencies = [
|
||||||
"stm32f1xx-hal",
|
"stm32f1xx-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "canome_derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.60",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -108,6 +116,21 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "critical-section"
|
name = "critical-section"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
@ -236,6 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hash32",
|
"hash32",
|
||||||
|
"serde",
|
||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -365,6 +389,44 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
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]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"canome"
|
"canome",
|
||||||
|
"canome_derive",
|
||||||
|
"sml_parser",
|
||||||
|
"sml_parser/sml_parser_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub mod can;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
pub mod cover;
|
pub mod cover;
|
||||||
pub mod light;
|
pub mod light;
|
||||||
|
pub mod power;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
#[cfg(feature = "stm32")]
|
#[cfg(feature = "stm32")]
|
||||||
pub mod stm32;
|
pub mod stm32;
|
||||||
|
|
41
canome/src/power.rs
Normal file
41
canome/src/power.rs
Normal file
|
@ -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<CanDataFormat> 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<CanDataFormat> for PowermeterState {
|
||||||
|
fn decode(value: CanDataFormat) -> Result<Self, CanDataFormat>
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
canome_derive/Cargo.toml
Normal file
11
canome_derive/Cargo.toml
Normal file
|
@ -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"
|
62
canome_derive/src/lib.rs
Normal file
62
canome_derive/src/lib.rs
Normal file
|
@ -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<BxCanBus<TX_CAP>> for #name #ty_generics #where_clause{
|
||||||
|
const ID: BxCanId = #group;
|
||||||
|
type State = #field_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
},
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
10
sml_parser/Cargo.toml
Normal file
10
sml_parser/Cargo.toml
Normal file
|
@ -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"
|
11
sml_parser/sml_parser_derive/Cargo.toml
Normal file
11
sml_parser/sml_parser_derive/Cargo.toml
Normal file
|
@ -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"
|
52
sml_parser/sml_parser_derive/src/lib.rs
Normal file
52
sml_parser/sml_parser_derive/src/lib.rs
Normal file
|
@ -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::<Vec<_>>();
|
||||||
|
let num_fields = s.fields.len();
|
||||||
|
|
||||||
|
let lifetimes = ast.generics.lifetimes().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl<'a> SmlType<'a> for #name<#(#lifetimes),*> {
|
||||||
|
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
#(#fields),*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
805
sml_parser/src/lib.rs
Normal file
805
sml_parser/src/lib.rs
Normal file
|
@ -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<u16> = crc::Crc::<u16>::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<Self, Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ParserContext<'a>(pub &'a [u8]);
|
||||||
|
|
||||||
|
impl<'a> ParserContext<'a> {
|
||||||
|
pub fn parse<T: SmlType<'a>>(&mut self) -> Result<T, Error> {
|
||||||
|
T::parse(self)
|
||||||
|
}
|
||||||
|
pub fn peek<I: SliceIndex<[u8]>>(&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<usize> {
|
||||||
|
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<Self, Error>
|
||||||
|
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<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
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::<Self>())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlPublicOpenReq<'a> {
|
||||||
|
pub codepage: Option<OctetString<'a>>,
|
||||||
|
pub client_id: OctetString<'a>,
|
||||||
|
pub req_file_id: OctetString<'a>,
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub sml_version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlPublicOpenRes<'a> {
|
||||||
|
pub codepage: Option<OctetString<'a>>,
|
||||||
|
pub client_id: Option<OctetString<'a>>,
|
||||||
|
pub req_file_id: OctetString<'a>,
|
||||||
|
pub server_id: OctetString<'a>,
|
||||||
|
pub ref_time: Option<SmlTime>,
|
||||||
|
pub sml_version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlPublicCloseReq<'a> {
|
||||||
|
pub global_signature: Option<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlPublicCloseRes<'a> {
|
||||||
|
pub global_signature: Option<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlGetProfilePackReq<'a> {
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub with_rawdata: Option<bool>,
|
||||||
|
pub begin_time: Option<SmlTime>,
|
||||||
|
pub end_time: Option<SmlTime>,
|
||||||
|
pub paramter_tree_path: SmlTreePath<'a>,
|
||||||
|
pub object_list: Option<ListOf<'a, SmlObjReqEntry<'a>>>,
|
||||||
|
pub das_details: Option<SmlTree<'a>>,
|
||||||
|
}
|
||||||
|
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<OctetString<'a>>,
|
||||||
|
pub profile_signature: Option<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlValueEntry<'a> {
|
||||||
|
pub value: SmlValue<'a>,
|
||||||
|
pub value_signature: Option<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SmlUnit = u8;
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlGetProfileListReq<'a> {
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub with_rawdata: Option<bool>,
|
||||||
|
pub begin_time: Option<SmlTime>,
|
||||||
|
pub end_time: Option<SmlTime>,
|
||||||
|
pub paramter_tree_path: SmlTreePath<'a>,
|
||||||
|
pub object_list: Option<ListOf<'a, SmlObjReqEntry<'a>>>,
|
||||||
|
pub das_details: Option<SmlTree<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<OctetString<'a>>,
|
||||||
|
pub period_signature: Option<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlGetProcParameterReq<'a> {
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub paramter_tree_path: SmlTreePath<'a>,
|
||||||
|
pub attribute: Option<OctetString<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<SmlProcParValue<'a>>,
|
||||||
|
pub child_list: Option<ListOf<'a, SmlTree<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlSetProcParameterReq<'a> {
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
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<OctetString<'a>>,
|
||||||
|
pub attention_details: Option<SmlTree<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlGetListReq<'a> {
|
||||||
|
pub client_id: OctetString<'a>,
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub list_name: Option<OctetString<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlGetListRes<'a> {
|
||||||
|
pub client_id: Option<OctetString<'a>>,
|
||||||
|
pub server_id: OctetString<'a>,
|
||||||
|
pub list_name: OctetString<'a>,
|
||||||
|
pub act_sensor_time: Option<SmlTime>,
|
||||||
|
pub val_list: ListOf<'a, SmlListEntry<'a>>,
|
||||||
|
pub list_signature: Option<SmlSignature<'a>>,
|
||||||
|
pub act_gateway_time: Option<SmlTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlListEntry<'a> {
|
||||||
|
pub obj_name: OctetString<'a>,
|
||||||
|
pub status: Option<SmlStatus>,
|
||||||
|
pub val_time: Option<SmlTime>,
|
||||||
|
pub unit: Option<SmlUnit>,
|
||||||
|
pub scaler: Option<i8>,
|
||||||
|
pub value: SmlValue<'a>,
|
||||||
|
pub value_signature: Option<SmlSignature<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SmlStatus = u64;
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlGetCosemReq<'a> {
|
||||||
|
pub client_id: OctetString<'a>,
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub obj_name: OctetString<'a>,
|
||||||
|
pub class_id: i16,
|
||||||
|
pub class_version: i16,
|
||||||
|
pub attribute_index_list: Option<ListOf<'a, SmlCosemAttributeDesc<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlCosemAttributeDesc<'a> {
|
||||||
|
pub attribute_index: i16,
|
||||||
|
pub selective_access_descriptor: Option<SmlCosemSelAccessDesc<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<OctetString<'a>>,
|
||||||
|
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<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<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 SmlSetCosemRes<'a> {
|
||||||
|
pub client_id: Option<OctetString<'a>>,
|
||||||
|
pub server_id: OctetString<'a>,
|
||||||
|
pub obj_name: OctetString<'a>,
|
||||||
|
pub class_id: i16,
|
||||||
|
pub class_version: i16,
|
||||||
|
pub attribute_list: Option<ListOf<'a, SmlCosemAttribute<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlActionCosemReq<'a> {
|
||||||
|
pub client_id: OctetString<'a>,
|
||||||
|
pub server_id: Option<OctetString<'a>>,
|
||||||
|
pub username: Option<OctetString<'a>>,
|
||||||
|
pub password: Option<OctetString<'a>>,
|
||||||
|
pub obj_name: OctetString<'a>,
|
||||||
|
pub class_id: i16,
|
||||||
|
pub class_version: i16,
|
||||||
|
pub service_index: u8,
|
||||||
|
pub service_parameter: Option<SmlCosemValue<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, SmlType)]
|
||||||
|
pub struct SmlActionCosemRes<'a> {
|
||||||
|
pub client_id: Option<OctetString<'a>>,
|
||||||
|
pub server_id: OctetString<'a>,
|
||||||
|
pub obj_name: OctetString<'a>,
|
||||||
|
pub class_id: i16,
|
||||||
|
pub class_version: i16,
|
||||||
|
pub attribute_list: Option<ListOf<'a, SmlCosemAttribute<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EndOfSmlMsg;
|
||||||
|
impl<'a> SmlType<'a> for EndOfSmlMsg {
|
||||||
|
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
|
||||||
|
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<Self, Error>
|
||||||
|
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<Self, Error>
|
||||||
|
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<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
let disc: u8 = buf.parse()?;
|
||||||
|
|
||||||
|
match disc {
|
||||||
|
0x01 => buf.parse().map(S::Time),
|
||||||
|
_ => Err(Error::ExpectedEnum(core::any::type_name::<Self>())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
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::<Self>())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
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::<Self>())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Self, Error>
|
||||||
|
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<Self, Error>
|
||||||
|
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::<Self>()));
|
||||||
|
}
|
||||||
|
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::<Self>())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SmlSignature<'a> = OctetString<'a>;
|
247
sml_parser/src/primitives.rs
Normal file
247
sml_parser/src/primitives.rs
Normal file
|
@ -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<Self, Error> {
|
||||||
|
let val = Self::peek(buf)?;
|
||||||
|
buf.move_fwd(val.header_len)?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TypeLengthField {
|
||||||
|
pub fn peek(buf: &ParserContext) -> Result<Self, Error> {
|
||||||
|
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<Self, Error>
|
||||||
|
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<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: SmlType<'a>> SmlType<'a> for ListOf<'a, T> {
|
||||||
|
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
|
||||||
|
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<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T, Error>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
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<Self, Error>
|
||||||
|
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<Self, Error>
|
||||||
|
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<T> {
|
||||||
|
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
if *buf.peek(0)? == 1 {
|
||||||
|
buf.move_fwd(1)?;
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
T::parse(buf).map(Some)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue