1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2
3use m_bus_core::{DeviceType, Function, IdentificationNumber, ManufacturerCode};
4
5fn crc16_en13757(data: &[u8]) -> u16 {
8 let mut crc: u16 = 0x0000;
9 for &byte in data {
10 crc ^= (byte as u16) << 8;
11 for _ in 0..8 {
12 if crc & 0x8000 != 0 {
13 crc = (crc << 1) ^ 0x3D65;
14 } else {
15 crc <<= 1;
16 }
17 }
18 }
19 crc ^ 0xFFFF
20}
21
22pub fn trailing_frame_crc_start(data: &[u8]) -> Option<usize> {
25 let crc_start = data.len().checked_sub(2)?;
26 if crc_start < 10 {
27 return None;
28 }
29
30 let expected = u16::from_be_bytes([data[crc_start], data[crc_start + 1]]);
31 (crc16_en13757(&data[..crc_start]) == expected).then_some(crc_start)
32}
33
34fn validate_format_a_header(data: &[u8]) -> Option<()> {
35 let header = data.get(..10)?;
36 let crc = data.get(10..12)?;
37 (crc16_en13757(header) == u16::from_be_bytes([crc[0], crc[1]])).then_some(())
38}
39
40#[derive(Clone, Debug)]
59pub struct FormatAFrame<'a> {
60 data: &'a [u8],
61 length: usize,
62}
63
64impl<'a> FormatAFrame<'a> {
65 #[must_use]
66 pub fn new(data: &'a [u8]) -> Option<Self> {
67 validate_format_a_header(data)?;
68 let length = FormatAChunks {
69 remaining: &data[12..],
70 }
71 .fold(10, |length, chunk| length + chunk.len());
72 Some(Self { data, length })
73 }
74
75 #[must_use]
77 pub const fn len(&self) -> usize {
78 self.length
79 }
80
81 #[must_use]
83 pub const fn is_empty(&self) -> bool {
84 false
85 }
86
87 #[must_use]
89 pub fn bytes(&self) -> FormatABytes<'a> {
90 FormatABytes {
91 length_byte: Some((self.length - 1) as u8),
92 current: self.data[1..10].iter(),
93 chunks: FormatAChunks {
94 remaining: &self.data[12..],
95 },
96 remaining: self.length,
97 }
98 }
99}
100
101#[derive(Clone, Debug)]
102struct FormatAChunks<'a> {
103 remaining: &'a [u8],
104}
105
106impl<'a> Iterator for FormatAChunks<'a> {
107 type Item = &'a [u8];
108
109 fn next(&mut self) -> Option<Self::Item> {
110 let data = self.remaining;
111 if data.is_empty() {
112 return None;
113 }
114 if data.len() >= 3 {
115 for length in (1..=16.min(data.len() - 2)).rev() {
116 let crc = u16::from_be_bytes([data[length], data[length + 1]]);
117 if crc16_en13757(&data[..length]) == crc {
118 self.remaining = &data[length + 2..];
119 return Some(&data[..length]);
120 }
121 }
122 }
123 self.remaining = &[];
124 Some(data)
125 }
126}
127
128#[derive(Clone, Debug)]
133pub struct FormatABytes<'a> {
134 length_byte: Option<u8>,
135 current: core::slice::Iter<'a, u8>,
136 chunks: FormatAChunks<'a>,
137 remaining: usize,
138}
139
140impl Iterator for FormatABytes<'_> {
141 type Item = u8;
142
143 fn next(&mut self) -> Option<Self::Item> {
144 let byte = if let Some(length) = self.length_byte.take() {
145 length
146 } else {
147 loop {
148 if let Some(byte) = self.current.next() {
149 break *byte;
150 }
151 self.current = self.chunks.next()?.iter();
152 }
153 };
154 self.remaining -= 1;
155 Some(byte)
156 }
157
158 fn size_hint(&self) -> (usize, Option<usize>) {
159 (self.remaining, Some(self.remaining))
160 }
161}
162
163impl ExactSizeIterator for FormatABytes<'_> {}
164impl core::iter::FusedIterator for FormatABytes<'_> {}
165
166pub fn strip_format_a_crcs<'a>(data: &[u8], output: &'a mut [u8]) -> Option<&'a [u8]> {
170 if output.len() < data.len() {
172 return None;
173 }
174 validate_format_a_header(data)?;
175 output[..10].copy_from_slice(&data[..10]);
176 let mut length = 10;
177 for chunk in (FormatAChunks {
178 remaining: &data[12..],
179 }) {
180 output[length..length + chunk.len()].copy_from_slice(chunk);
181 length += chunk.len();
182 }
183 output[0] = (length - 1) as u8;
184 Some(&output[..length])
185}
186
187#[derive(Debug, Clone, Copy, PartialEq)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
189pub struct WirelessFrame<'a> {
190 pub control_field: u8,
192 pub function: Option<Function>,
196 pub manufacturer_id: ManufacturerId,
197 #[cfg_attr(
198 feature = "serde",
199 serde(serialize_with = "m_bus_core::serde_hex::serialize")
200 )]
201 pub data: &'a [u8],
202}
203
204#[derive(Debug, Clone, Copy, PartialEq)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
206pub struct ManufacturerId {
207 pub manufacturer_code: ManufacturerCode,
208 pub identification_number: IdentificationNumber,
209 pub device_type: DeviceType,
210 pub version: u8,
211 pub is_unique_globally: bool,
214}
215
216impl TryFrom<&[u8]> for ManufacturerId {
217 type Error = FrameError;
218 fn try_from(data: &[u8]) -> Result<Self, FrameError> {
219 let mut iter = data.iter();
220 let manufacturer_field = u16::from_le_bytes([
221 *iter.next().ok_or(FrameError::TooShort)?,
222 *iter.next().ok_or(FrameError::TooShort)?,
223 ]);
224 Ok(ManufacturerId {
225 manufacturer_code: ManufacturerCode::from_id(manufacturer_field).map_err(|_| {
226 FrameError::InvalidManufacturerCode {
227 code: manufacturer_field,
228 }
229 })?,
230 identification_number: IdentificationNumber::from_bcd_hex_digits([
231 *iter.next().ok_or(FrameError::TooShort)?,
232 *iter.next().ok_or(FrameError::TooShort)?,
233 *iter.next().ok_or(FrameError::TooShort)?,
234 *iter.next().ok_or(FrameError::TooShort)?,
235 ])
236 .map_err(|_| FrameError::TooShort)?,
237 version: *iter.next().ok_or(FrameError::TooShort)?,
238 device_type: {
243 let device_byte = *iter.next().ok_or(FrameError::TooShort)?;
244 let ci_byte = *data.get(8).ok_or(FrameError::TooShort)?;
246 let device_type_code = if (0xA0..=0xAF).contains(&ci_byte) {
247 (device_byte >> 4) & 0x0F
249 } else {
250 device_byte
252 };
253 DeviceType::from(device_type_code)
254 },
255 is_unique_globally: (manufacturer_field & !ManufacturerCode::CODE_MASK) == 0,
256 })
257 }
258}
259
260#[derive(Debug, Copy, Clone, PartialEq)]
261#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
262pub enum FrameError {
263 EmptyData,
264 TooShort,
265 InvalidManufacturerCode {
267 code: u16,
268 },
269 WrongLength {
270 expected: usize,
271 actual: usize,
272 },
273}
274
275impl<'a> TryFrom<&'a [u8]> for WirelessFrame<'a> {
276 type Error = FrameError;
277
278 fn try_from(data: &'a [u8]) -> Result<Self, FrameError> {
279 let length = data.len();
280 let length_byte = *data.first().ok_or(FrameError::EmptyData)? as usize;
281 let control_field = *data.get(1).ok_or(FrameError::TooShort)?;
282 let manufacturer_id = ManufacturerId::try_from(&data[2..])?;
283
284 if length_byte + 1 == length {
286 let data_end = trailing_frame_crc_start(data).unwrap_or(length);
287 return Ok(WirelessFrame {
288 control_field,
289 function: Function::try_from(control_field).ok(),
290 manufacturer_id,
291 data: &data[10..data_end],
292 });
293 }
294
295 Err(FrameError::WrongLength {
296 expected: length_byte + 1,
297 actual: data.len(),
298 })
299 }
300}
301
302#[cfg(test)]
303mod test {
304 use super::*;
305
306 #[test]
307 fn test_dummy() {
308 let _id = 33225544;
309 let _medium = 7; let _man = "SEN";
311 let _version = 104;
312 let frame: &[u8] = &[
313 0x18, 0x44, 0xAE, 0x4C, 0x44, 0x55, 0x22, 0x33, 0x68, 0x07, 0x7A, 0x55, 0x00, 0x00,
314 0x00, 0x00, 0x04, 0x13, 0x89, 0xE2, 0x01, 0x00, 0x02, 0x3B, 0x00, 0x00,
315 ];
316 let parsed = WirelessFrame::try_from(frame);
317 println!("{:#?}", parsed);
318 }
319
320 #[test]
321 fn test_trailing_frame_crc_is_not_payload() {
322 let frame = [
323 0x14, 0x44, 0xAE, 0x0C, 0x78, 0x56, 0x34, 0x12, 0x01, 0x07, 0x8C, 0x20, 0x27, 0x78,
324 0x0B, 0x13, 0x43, 0x65, 0x87, 0x7A, 0xC5,
325 ];
326
327 assert_eq!(trailing_frame_crc_start(&frame), Some(19));
328
329 let parsed = WirelessFrame::try_from(frame.as_slice()).expect("valid wireless frame");
330 assert_eq!(
331 parsed.data,
332 &[0x8C, 0x20, 0x27, 0x78, 0x0B, 0x13, 0x43, 0x65, 0x87]
333 );
334 }
335
336 #[test]
337 fn c_field_is_decoded_and_preserved() {
338 let frame = [
339 0x18, 0x44, 0xAE, 0x4C, 0x44, 0x55, 0x22, 0x33, 0x68, 0x07, 0x7A, 0x55, 0x00, 0x00,
340 0x00, 0x00, 0x04, 0x13, 0x89, 0xE2, 0x01, 0x00, 0x02, 0x3B, 0x00,
341 ];
342 let parsed = WirelessFrame::try_from(frame.as_slice()).expect("valid wireless frame");
343 assert_eq!(parsed.control_field, 0x44);
344 assert_eq!(parsed.function, Some(Function::SndNr));
345 }
346
347 #[test]
348 fn manufacturer_field_with_top_bit_set_is_decoded() {
349 let frame = [
350 0x18, 0x44, 0x97, 0xA6, 0x44, 0x55, 0x22, 0x33, 0x68, 0x07, 0x7A, 0x55, 0x00, 0x00,
351 0x00, 0x00, 0x04, 0x13, 0x89, 0xE2, 0x01, 0x00, 0x02, 0x3B, 0x00,
352 ];
353 let parsed = WirelessFrame::try_from(frame.as_slice()).expect("valid wireless frame");
354 assert_eq!(
355 parsed.manufacturer_id.manufacturer_code.code,
356 ['I', 'T', 'W']
357 );
358 assert!(!parsed.manufacturer_id.is_unique_globally);
359
360 let mut frame = frame;
362 frame[3] = 0x26;
363 let parsed = WirelessFrame::try_from(frame.as_slice()).expect("valid wireless frame");
364 assert_eq!(
365 parsed.manufacturer_id.manufacturer_code.code,
366 ['I', 'T', 'W']
367 );
368 assert!(parsed.manufacturer_id.is_unique_globally);
369 }
370
371 #[test]
372 fn undecodable_manufacturer_field_is_not_reported_as_too_short() {
373 let frame = [
374 0x18, 0x44, 0x00, 0x00, 0x44, 0x55, 0x22, 0x33, 0x68, 0x07, 0x7A, 0x55, 0x00, 0x00,
375 0x00, 0x00, 0x04, 0x13, 0x89, 0xE2, 0x01, 0x00, 0x02, 0x3B, 0x00,
376 ];
377 assert_eq!(
378 WirelessFrame::try_from(frame.as_slice()),
379 Err(FrameError::InvalidManufacturerCode { code: 0x0000 })
380 );
381 }
382
383 #[test]
384 fn unknown_c_field_does_not_invalidate_frame() {
385 let frame = [
386 0x18, 0x45, 0xAE, 0x4C, 0x44, 0x55, 0x22, 0x33, 0x68, 0x07, 0x7A, 0x55, 0x00, 0x00,
387 0x00, 0x00, 0x04, 0x13, 0x89, 0xE2, 0x01, 0x00, 0x02, 0x3B, 0x00,
388 ];
389 let parsed = WirelessFrame::try_from(frame.as_slice()).expect("valid wireless frame");
390 assert_eq!(parsed.control_field, 0x45);
391 assert_eq!(parsed.function, None);
392 }
393}
394
395#[cfg(test)]
396mod format_a_tests {
397 use super::*;
398
399 fn encode(payload: &[u8]) -> Vec<u8> {
400 let header = [0, 0x44, 0x49, 0x6A, 0x31, 0, 1, 0x55, 0x14, 0x37];
401 let mut frame = header.to_vec();
402 frame.extend(crc16_en13757(&header).to_be_bytes());
403 for chunk in payload.chunks(16) {
404 frame.extend(chunk);
405 frame.extend(crc16_en13757(chunk).to_be_bytes());
406 }
407 frame
408 }
409
410 #[test]
411 fn normalized_bytes_skip_crcs_across_block_boundaries() {
412 for length in [0, 1, 15, 16, 17, 31, 32, 33, 80, 240] {
413 let payload: Vec<u8> = (0..length).map(|n| (n * 17) as u8).collect();
414 let encoded = encode(&payload);
415 let original = encoded.clone();
416 let view = FormatAFrame::new(&encoded).unwrap();
417 let mut expected = vec![(length + 9) as u8];
418 expected.extend(&encoded[1..10]);
419 expected.extend(&payload);
420 assert_eq!(view.len(), expected.len());
421 assert!(!view.is_empty());
422 assert!(view.bytes().eq(expected.iter().copied()));
423 let mut bytes = view.bytes();
424 for (index, want) in expected.iter().enumerate() {
425 assert_eq!(bytes.len(), view.len() - index);
426 assert_eq!(bytes.size_hint(), (bytes.len(), Some(bytes.len())));
427 assert_eq!(bytes.next(), Some(*want));
428 assert!(bytes.clone().eq(expected.iter().skip(index + 1).copied()));
429 }
430 assert_eq!(bytes.next(), None);
431 assert_eq!(bytes.next(), None);
432 assert_eq!(bytes.len(), 0);
433 assert_eq!(encoded, original);
434 let mut output = vec![0; encoded.len()];
435 assert_eq!(
436 strip_format_a_crcs(&encoded, &mut output),
437 Some(expected.as_slice())
438 );
439 }
440 }
441
442 #[test]
443 fn tail_and_validation_match_the_buffer_api() {
444 for tail in [
445 &[][..],
446 &[0x33][..],
447 &[0x33, 0x44][..],
448 &[1, 2, 3, 4, 5][..],
449 ] {
450 let mut encoded = encode(&[]);
451 encoded.extend(tail);
452 let frame = FormatAFrame::new(&encoded).unwrap();
453 assert!(frame.bytes().skip(10).eq(tail.iter().copied()));
454 let mut too_small = vec![0xAA; encoded.len() - 1];
455 assert!(strip_format_a_crcs(&encoded, &mut too_small).is_none());
456 assert!(too_small.iter().all(|byte| *byte == 0xAA));
457 }
458 let encoded = encode(&[]);
459 for length in 0..12 {
460 assert!(FormatAFrame::new(&encoded[..length]).is_none());
461 }
462 let mut corrupt = encoded;
463 corrupt[10] ^= 1;
464 assert!(FormatAFrame::new(&corrupt).is_none());
465 }
466
467 #[test]
468 fn iterator_borrows_source_not_temporary_view() {
469 let encoded = encode(&[1, 2, 3]);
470 let bytes = {
471 let view = FormatAFrame::new(&encoded).unwrap();
472 view.bytes()
473 };
474 assert_eq!(bytes.skip(10).collect::<Vec<_>>(), [1, 2, 3]);
475 }
476}