dlms_cosem 0.2.0

A `no_std` library for parsing DLMS/COSEM messages.
Documentation
use core::convert::TryFrom;
use core::fmt;
use alloc::{string::String, vec::Vec};
#[cfg(feature = "serde")]
use alloc::string::ToString;

use nom::{
  IResult,
  combinator::fail,
  number::streaming::{be_f32, be_f64, i8, be_i16, be_i32, be_i64, u8, be_u16, be_u32, be_u64},
  multi::{length_count},
  sequence::{tuple},
};
#[cfg(feature = "serde")]
use serde::{Serialize, Serializer};

#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum DataType {
  Null               =  0,
  Array              =  1,
  Structure          =  2,
  Bool               =  3,
  BitString          =  4,
  DoubleLong         =  5,
  DoubleLongUnsigned =  6,
  OctetString        =  9,
  VisibleString      = 10,
  Utf8String         = 12,
  BinaryCodedDecimal = 13,
  Integer            = 15,
  Long               = 16,
  Unsigned           = 17,
  LongUnsigned       = 18,
  CompactArray       = 19,
  Long64             = 20,
  Long64Unsigned     = 21,
  Enum               = 22,
  Float32            = 23,
  Float64            = 24,
  DateTime           = 25,
  Date               = 26,
  Time               = 27,
}

impl TryFrom<u8> for DataType {
  type Error = u8;

  fn try_from(dt: u8) -> Result<Self, Self::Error> {
    Ok(match dt {
      0x00 => Self::Null,
      0x01 => Self::Array,
      0x02 => Self::Structure,
      0x03 => Self::Bool,
      0x04 => Self::BitString,
      0x05 => Self::DoubleLong,
      0x06 => Self::DoubleLongUnsigned ,
      0x09 => Self::OctetString,
      0x0a => Self::VisibleString,
      0x0c => Self::Utf8String,
      0x0d => Self::BinaryCodedDecimal ,
      0x0f => Self::Integer,
      0x10 => Self::Long,
      0x11 => Self::Unsigned,
      0x12 => Self::LongUnsigned,
      0x13 => Self::CompactArray,
      0x14 => Self::Long64,
      0x15 => Self::Long64Unsigned,
      0x16 => Self::Enum,
      0x17 => Self::Float32,
      0x18 => Self::Float64,
      0x19 => Self::DateTime,
      0x1a => Self::Date,
      0x1b => Self::Time,
      dt => return Err(dt),
    })
  }
}

#[derive(Clone, PartialEq)]
pub struct Date {
  pub(crate) year: u16,
  pub(crate) month: u8,
  pub(crate) day_of_month: u8,
  pub(crate) day_of_week: u8,
}

impl Date {
  fn parse(input: &[u8]) -> IResult<&[u8], Self> {
    let (input, year) = be_u16(input)?;
    let (input, month) = u8(input)?;
    let (input, day_of_month) = u8(input)?;
    let (input, day_of_week) = u8(input)?;

    Ok((input, Self {
      year,
      month,
      day_of_month,
      day_of_week,
    }))
  }
}

impl fmt::Display for Date {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day_of_month)
  }
}

impl fmt::Debug for Date {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Date(\"{}\")", self)
  }
}

#[cfg(feature = "serde")]
impl Serialize for Date {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

#[derive(Clone, PartialEq)]
pub struct Time {
  pub(crate) hour: Option<u8>,
  pub(crate) minute: Option<u8>,
  pub(crate) second: Option<u8>,
  pub(crate) hundredth: Option<u8>,
}

impl Time {
  pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
    let (input, (hour, minute, second, hundredth)) = tuple((u8, u8, u8, u8))(input)?;

    let hour = match hour {
      0xff => None,
      0..=23 => Some(hour),
      _ => return fail(input),
    };
    let minute = match minute {
      0xff => None,
      0..=59 => Some(minute),
      _ => return fail(input),
    };
    let second = match second {
      0xff => None,
      0..=59 => Some(second),
      _ => return fail(input),
    };
    let hundredth = match hundredth {
      0xff => None,
      0..=99 => Some(hundredth),
      _ => return fail(input),
    };

    Ok((input, Self { hour, minute, second, hundredth }))
  }
}

impl fmt::Display for Time {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{:02}:{:02}:{:02}.{:02}",
      self.hour.unwrap_or(0),
      self.minute.unwrap_or(0),
      self.second.unwrap_or(0),
      self.hundredth.unwrap_or(0),
    )
  }
}

impl fmt::Debug for Time {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Time(\"{}\")", self)
  }
}

#[cfg(feature = "serde")]
impl Serialize for Time {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct ClockStatus(pub(crate) u8);

impl ClockStatus {
  const INVALID_VALUE_BIT:   u8 = 0b00000001;
  const DOUBTFUL_VALUE_BIT:  u8 = 0b00000010;
  const DIFFERENT_BASE_BIT:  u8 = 0b00000100;
  const INVALID_STATUS_BIT:  u8 = 0b00001000;
  const DAYLIGHT_SAVING_BIT: u8 = 0b10000000;

  pub fn invalid_value(&self) -> bool {
    (self.0 & Self::INVALID_VALUE_BIT) != 0
  }

  pub fn doubtful_value(&self) -> bool {
    (self.0 & Self::DOUBTFUL_VALUE_BIT) != 0
  }

  pub fn different_base(&self) -> bool {
    (self.0 & Self::DIFFERENT_BASE_BIT) != 0
  }

  pub fn invalid_status(&self) -> bool {
    (self.0 & Self::INVALID_STATUS_BIT) != 0
  }

  pub fn daylight_saving(&self) -> bool {
    (self.0 & Self::DAYLIGHT_SAVING_BIT) != 0
  }
}

#[derive(Clone, PartialEq)]
pub struct DateTime {
  pub(crate) date: Date,
  pub(crate) time: Time,
  pub(crate) offset_minutes: Option<i16>,
  pub(crate) clock_status: Option<ClockStatus>,
}

impl DateTime {
  pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
    let (input, date) = Date::parse(input)?;
    let (input, time) = Time::parse(input)?;
    let (input, offset_minutes) = be_i16(input)?;
    let offset_minutes = Some(offset_minutes).filter(|&b| b != 0x8000u16 as i16);
    let (input, clock_status) = u8(input)?;
    let clock_status = Some(clock_status).filter(|&b| b != 0xff).map(ClockStatus);

    Ok((input, Self { date, time, offset_minutes, clock_status }))
  }
}

impl fmt::Display for DateTime {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{}T{}", self.date, self.time)?;

    if let Some(offset_minutes) = self.offset_minutes {
      if offset_minutes >= 0 {
        '-'.fmt(f)?;
      } else {
        '+'.fmt(f)?;
      };
      let offset_minutes = offset_minutes.abs();
      write!(f, "{:02}:{:02}", offset_minutes / 60, offset_minutes % 60)?;
    }

    Ok(())
  }
}

impl fmt::Debug for DateTime {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "DateTime(\"{}\")", self)
  }
}

#[cfg(feature = "serde")]
impl Serialize for DateTime {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Data {
  Null,
  OctetString(Vec<u8>),
  Utf8String(String),
  Integer(i8),
  Unsigned(u8),
  Long(i16),
  LongUnsigned(u16),
  DoubleLong(i32),
  DoubleLongUnsigned(u32),
  Long64(i64),
  Long64Unsigned(u64),
  Float32(f32),
  Float64(f64),
  DateTime(DateTime),
  Date(Date),
  Time(Time),
  Structure(Vec<Data>),
  Enum(u8),
}

impl Data {
  pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
    let (input, data_type) = u8(input)?;
    let data_type = DataType::try_from(data_type)
      .map_err(|_| nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Fail)))?;
    Ok(match data_type {
      DataType::DateTime => {
        let (input, date_time) = DateTime::parse(input)?;
        (input, Data::DateTime(date_time))
      },
      DataType::Date => {
        let (input, date) = Date::parse(input)?;
        (input, Data::Date(date))
      },
      DataType::Time => {
        let (input, time) = Time::parse(input)?;
        (input, Data::Time(time))
      },
      DataType::Null => (input, Data::Null),
      DataType::Structure => {
        let (input, structure) = length_count(u8, Self::parse)(input)?;
        (input, Data::Structure(structure))
      },
      DataType::OctetString => {
        let (input, bytes) = length_count(u8, u8)(input)?;
        (input, Data::OctetString(bytes))
      },
      DataType::Float32 => {
        let (input, n) = be_f32(input)?;
        (input, Data::Float32(n))
      },
      DataType::Float64 => {
        let (input, n) = be_f64(input)?;
        (input, Data::Float64(n))
      },
      DataType::Integer => {
        let (input, n) = i8(input)?;
        (input, Data::Integer(n))
      },
      DataType::Long => {
        let (input, n) = be_i16(input)?;
        (input, Data::Long(n))
      },
      DataType::DoubleLong => {
        let (input, n) = be_i32(input)?;
        (input, Data::DoubleLong(n))
      },
      DataType::Long64 => {
        let (input, n) = be_i64(input)?;
        (input, Data::Long64(n))
      },
      DataType::Enum => {
        let (input, n) = u8(input)?;
        (input, Data::Enum(n))
      },
      DataType::LongUnsigned => {
        let (input, n) = be_u16(input)?;
        (input, Data::LongUnsigned(n))
      },
      DataType::DoubleLongUnsigned => {
        let (input, n) = be_u32(input)?;
        (input, Data::DoubleLongUnsigned(n))
      },
      DataType::Long64Unsigned => {
        let (input, n) = be_u64(input)?;
        (input, Data::Long64Unsigned(n))
      },
      dt => unimplemented!("decoding data type {:?}", dt),
    })
  }
}