mbusparse/
telegram.rs

1use core::convert::TryFrom;
2
3use nom::{
4  IResult,
5  branch::alt,
6  number::streaming::u8,
7  bytes::streaming::{tag, take},
8  combinator::cut,
9  sequence::tuple,
10};
11
12use super::{Error, Control, Address};
13
14/// An M-Bus telegram.
15#[derive(Debug, Clone, PartialEq)]
16pub enum Telegram<'ud> {
17  SingleCharacter,
18  ShortFrame {
19    control: Control,
20    address: Address,
21  },
22  ControlFrame {
23    control: Control,
24    address: Address,
25    control_information: u8,
26  },
27  LongFrame {
28    control: Control,
29    address: Address,
30    control_information: u8,
31    user_data: &'ud [u8],
32  },
33}
34
35impl<'ud> Telegram<'ud> {
36  const SINGLE_CHAR: u8 = 0xe5;
37  const SHORT_START_CHAR: u8 = 0x10;
38  const LONG_START_CHAR: u8 = 0x68;
39  const STOP_CHAR: u8 = 0x16;
40
41  fn map_error(nom_error: nom::Err<nom::error::Error<&[u8]>>, error: Error) -> nom::Err<Error> {
42    nom_error.map(|_| error)
43  }
44
45  fn parse_single(input: &'ud [u8]) -> IResult<&'ud [u8], Self, Error> {
46    let (input, _) = tag([Telegram::SINGLE_CHAR])(input)
47      .map_err(|err| Self::map_error(err, Error::InvalidStartCharacter))?;
48    Ok((input, Self::SingleCharacter))
49  }
50
51  fn parse_short(input: &'ud [u8]) -> IResult<&'ud [u8], Self, Error> {
52    let (input, _) = tag([Self::SHORT_START_CHAR])(input)
53      .map_err(|err| Self::map_error(err, Error::InvalidStartCharacter))?;
54    Self::parse_payload(input, 2)
55  }
56
57  fn parse_long(input: &'ud [u8]) -> IResult<&'ud [u8], Self, Error> {
58    let start_char = tag([Self::LONG_START_CHAR]);
59    let (input, (_, payload_len, payload_len_check, _)) =
60      tuple((&start_char, u8, u8, &start_char))(input)
61        .map_err(|err| Self::map_error(err, Error::InvalidStartCharacter))?;
62
63    if payload_len != payload_len_check {
64      return Err(nom::Err::Error(Error::InvalidStartCharacter))
65    }
66
67    Self::parse_payload(input, payload_len.into())
68  }
69
70  fn parse_payload(input: &'ud [u8], mut payload_len: usize) -> IResult<&'ud [u8], Self, Error> {
71    let mut calculated_checksum = 0u8;
72
73    let mut checksummed_u8 = |input| {
74      let (input, value) = cut(u8)(input)?;
75      calculated_checksum = calculated_checksum.wrapping_add(value);
76      Ok((input, value))
77    };
78
79    let (input, control) = checksummed_u8(input)?;
80    let control = Control::try_from(control)
81      .map_err(|_| nom::Err::Failure(Error::InvalidFormat))?;
82    payload_len -= 1;
83
84    let (input, address) = checksummed_u8(input)?;
85    let address = Address::from(address);
86    payload_len -= 1;
87
88    let (input, control_information) = if payload_len > 0 {
89      let (input, control_information) = checksummed_u8(input)?;
90      payload_len -= 1;
91      (input, Some(control_information))
92    } else {
93      (input, None)
94    };
95
96    let (input, payload) = take(payload_len)(input)?;
97    for &value in payload {
98      calculated_checksum = calculated_checksum.wrapping_add(value);
99    }
100
101    let (input, checksum) = cut(u8)(input)?;
102
103    let (input, _stop_char) = cut(tag([Self::STOP_CHAR]))(input)?;
104
105    if calculated_checksum != checksum {
106      return Err(nom::Err::Failure(Error::ChecksumMismatch))
107    }
108
109    if let Some(control_information) = control_information {
110      let payload_len = payload.len();
111      if payload_len > 0 {
112        Ok((input, Self::LongFrame { control, address, control_information, user_data: payload }))
113      } else {
114        Ok((input, Self::ControlFrame { control, address, control_information }))
115      }
116    } else {
117      Ok((input, Self::ShortFrame { control, address }))
118    }
119  }
120
121  pub fn parse(input: &'ud [u8]) -> Result<(&'ud [u8], Telegram<'ud>), Error> {
122    use nom::Finish;
123
124    alt((Self::parse_single, Self::parse_short, Self::parse_long))(input)
125      .map_err(|err| match err {
126        nom::Err::Incomplete(needed) => nom::Err::Failure(Error::Incomplete(match needed {
127          nom::Needed::Unknown => None,
128          nom::Needed::Size(size) => Some(size),
129        })),
130        err => err,
131      })
132      .finish()
133  }
134}
135
136#[cfg(test)]
137mod test {
138  use super::*;
139
140  const TELEGRAMS: [u8; 376] = [
141    0x68, // Start Character
142    0xfa, // Payload Length
143    0xfa, // Payload Length
144    0x68, // Start Character
145    0x53, // Control Field
146    0xff, // Address Field
147    0x00, // Control Information Field
148    0x01, 0x67, 0xdb, 0x08, 0x4b, 0x46, 0x4d, 0x10, 0x20, 0x01, 0x12, 0xa9, 0x82, 0x01, 0x55, 0x21,
149    0x00, 0x02, 0xbc, 0x66, 0x2f, 0xba, 0x85, 0x66, 0x9e, 0x76, 0xef, 0x03, 0x47, 0x06, 0xcc, 0xfc,
150    0x93, 0x70, 0xf9, 0xb7, 0xab, 0x49, 0xd0, 0x35, 0xdd, 0x0e, 0xe7, 0x5a, 0x95, 0x36, 0xfc, 0x7a,
151    0x19, 0x48, 0xf7, 0x9c, 0x69, 0x8f, 0xac, 0xc2, 0xfc, 0x5f, 0x7b, 0xe5, 0xf1, 0x47, 0xf3, 0xee,
152    0x87, 0x40, 0xbe, 0xe9, 0x11, 0x8c, 0x8f, 0x7b, 0xc5, 0xb2, 0xc5, 0x12, 0x53, 0x29, 0xce, 0xb4,
153    0xbe, 0xad, 0xe1, 0x16, 0xd1, 0x61, 0x2c, 0x7f, 0x82, 0xa0, 0x4f, 0xaa, 0x70, 0xc3, 0x5d, 0x06,
154    0x67, 0xd9, 0xee, 0xec, 0xf6, 0x86, 0xaf, 0xb6, 0x43, 0x19, 0xdc, 0x13, 0xec, 0x3c, 0x9a, 0x39,
155    0x10, 0x5b, 0x46, 0x37, 0x2a, 0xca, 0x24, 0xf2, 0xa1, 0x73, 0x39, 0x32, 0x5f, 0x27, 0x0a, 0xb9,
156    0x8c, 0x40, 0xe7, 0xaa, 0x8a, 0x61, 0xec, 0xf5, 0x88, 0x4c, 0x4e, 0x3b, 0x7e, 0xb4, 0xef, 0x5b,
157    0xb2, 0x92, 0x68, 0x4a, 0x6b, 0xba, 0x91, 0xb3, 0x49, 0xbb, 0x70, 0x41, 0x62, 0xa2, 0x10, 0xc3,
158    0x6d, 0xc1, 0xf2, 0x52, 0x88, 0x42, 0x71, 0x7b, 0xfe, 0x64, 0xf8, 0x05, 0xce, 0xab, 0x98, 0x0e,
159    0x14, 0xc1, 0xe2, 0x9e, 0x10, 0x19, 0x5e, 0x1b, 0xa2, 0xef, 0x24, 0xe8, 0xf9, 0xcb, 0x0f, 0xe6,
160    0x09, 0xd3, 0x2b, 0xb8, 0xc3, 0x6e, 0x23, 0xb8, 0x47, 0x7b, 0x14, 0xda, 0xc2, 0x37, 0x63, 0xa2,
161    0x5b, 0xee, 0x27, 0xa8, 0x1f, 0x20, 0xa7, 0x6c, 0x2f, 0x8e, 0x28, 0xc9, 0x2b, 0x3e, 0xbe, 0x04,
162    0x48, 0x6d, 0xc2, 0xdc, 0x07, 0x41, 0x63, 0xbe, 0x49, 0xdf, 0x25, 0x96, 0x30, 0x9c, 0x86, 0x39,
163    0x53, 0x31, 0x65, 0x35, 0xd1, 0xf0, 0xdf,
164    0x8a, // Checksum
165    0x16, // Stop Character
166
167    0x68, // Start Character
168    0x72, // Payload Length
169    0x72, // Payload Length
170    0x68, // Start Character
171    0x53, // Control Field
172    0xff, // Address Field
173    0x11, // Control Information Field
174    0x01, 0x67, 0x5b, 0x5f, 0x0f, 0xb3, 0x7e, 0xde, 0xda, 0xb5, 0xaf, 0xed, 0x57, 0xbd, 0xa7, 0x5a,
175    0x2e, 0x17, 0xcf, 0x11, 0x79, 0xc8, 0x1d, 0xbe, 0xb4, 0xac, 0xc8, 0x80, 0x2c, 0xb1, 0xdb, 0xf8,
176    0x74, 0xe6, 0x76, 0xa3, 0x42, 0xf6, 0xe5, 0xde, 0x97, 0x29, 0x86, 0x1f, 0x07, 0x67, 0xac, 0xf9,
177    0x04, 0xf8, 0x0a, 0x44, 0xa0, 0xdd, 0x16, 0x46, 0xf2, 0x08, 0x83, 0x44, 0x5e, 0x11, 0x91, 0xe3,
178    0x52, 0x49, 0x58, 0x0e, 0xaa, 0x4b, 0xec, 0x58, 0xaa, 0xee, 0x1a, 0xdf, 0xda, 0x60, 0x14, 0x5f,
179    0x51, 0xb8, 0xbe, 0xd4, 0x36, 0x10, 0xdf, 0xee, 0x5b, 0x2c, 0xe3, 0x38, 0x0d, 0xe7, 0xf3, 0x4d,
180    0x9f, 0xca, 0x2a, 0x15, 0x6f, 0x68, 0x79, 0xf4, 0x1e, 0xec, 0x8d, 0x20, 0xef, 0xa2, 0xdf,
181    0x38, // Checksum
182    0x16, // Stop Character
183  ];
184
185  #[test]
186  fn test_parse() {
187    let input = &TELEGRAMS[..];
188
189    let (input, telegram) = Telegram::parse(input).unwrap();
190    assert_eq!(telegram, Telegram::LongFrame {
191      control: Control::SndUd { fcb: false },
192      address: Address::Broadcast,
193      control_information: 0x00,
194      user_data: &TELEGRAMS[7..254],
195    });
196
197    let (input, telegram) = Telegram::parse(input).unwrap();
198    assert_eq!(telegram, Telegram::LongFrame {
199      control: Control::SndUd { fcb: false },
200      address: Address::Broadcast,
201      control_information: 0x11,
202      user_data: &TELEGRAMS[263..374],
203    });
204
205    assert!(input.is_empty());
206  }
207}