Skip to main content

m_bus_application_layer/
lib.rs

1//! Parser for the M-Bus application layer defined by EN 13757-3.
2//!
3//! Use [`parse_application_layer`] when the input starts with a control
4//! information (CI) field. Use [`parse_data_records`] when the transport
5//! header has already been removed and the input starts with a DIF/VIF data
6//! record.
7//!
8//! # Parse data records directly
9//!
10//! ```rust
11//! use m_bus_application_layer::{
12//!     data_information::DataType, parse_data_records, DataRecordError,
13//! };
14//!
15//! fn main() -> Result<(), DataRecordError> {
16//!     // 24-bit integer, volume in m³ with a 10^-3 scale.
17//!     let data = [0x03, 0x13, 0x15, 0x31, 0x00];
18//!     let mut records = parse_data_records(&data);
19//!     let record = records.next().expect("one data record")?;
20//!
21//!     assert_eq!(record.value(), Some(&DataType::Number(12_565.0)));
22//!     assert_eq!(record.raw_bytes(), &data);
23//!     Ok(())
24//! }
25//! ```
26#![cfg_attr(not(feature = "std"), no_std)]
27
28#[cfg(feature = "std")]
29use std::fmt;
30
31pub use m_bus_core::decryption;
32
33use m_bus_core::{
34    bcd_hex_digits_to_u32, ConfigurationField, DeviceType, IdentificationNumber, ManufacturerCode,
35};
36pub use variable_user_data::DataRecordError;
37
38pub use self::data_record::DataRecord;
39#[cfg(feature = "decryption")]
40use m_bus_core::decryption::DecryptionError::{NotEncrypted, UnknownEncryptionState};
41pub use m_bus_core::ApplicationLayerError;
42
43pub mod data_information;
44pub mod data_record;
45pub mod extended_link_layer;
46pub mod value_information;
47pub mod variable_user_data;
48
49use extended_link_layer::ExtendedLinkLayer;
50
51/// Parses an application-layer user data block beginning with a CI field.
52pub fn parse_application_layer(data: &[u8]) -> Result<UserDataBlock<'_>, ApplicationLayerError> {
53    UserDataBlock::try_from(data)
54}
55
56/// Parses DIF/VIF data records after any CI and transport headers were removed.
57///
58/// The returned iterator yields one result per record and does not allocate.
59#[must_use]
60pub const fn parse_data_records(data: &[u8]) -> DataRecords<'_> {
61    DataRecords::new(data, None)
62}
63
64/// Parses DIF/VIF data records using context from a long transport header.
65///
66/// Header context is needed for data encodings whose interpretation depends on
67/// transport-layer metadata, such as two-digit years.
68#[must_use]
69pub const fn parse_data_records_with_header<'a>(
70    data: &'a [u8],
71    header: &'a LongTplHeader,
72) -> DataRecords<'a> {
73    DataRecords::new(data, Some(header))
74}
75
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77#[cfg_attr(feature = "serde", serde(into = "Vec<DataRecord>"))]
78#[cfg_attr(feature = "defmt", derive(defmt::Format))]
79#[derive(Clone, Debug, PartialEq)]
80pub struct DataRecords<'a> {
81    offset: usize,
82    data: &'a [u8],
83    long_tpl_header: Option<&'a LongTplHeader>,
84}
85
86#[cfg(feature = "std")]
87impl<'a> From<DataRecords<'a>> for Vec<DataRecord<'a>> {
88    fn from(value: DataRecords<'a>) -> Self {
89        let value: Result<Vec<_>, _> = value.collect();
90        value.unwrap_or_default()
91    }
92}
93
94impl<'a> Iterator for DataRecords<'a> {
95    type Item = Result<DataRecord<'a>, DataRecordError>;
96
97    // Inlined, and every record is returned exactly as `DataRecord::parse`
98    // produced it, never read in between: that lets the compiler build it
99    // directly in the caller's slot instead of a local copy.
100    #[inline]
101    fn next(&mut self) -> Option<Self::Item> {
102        while self.offset < self.data.len() {
103            let dif = data_information::DataInformationField::from(*self.data.get(self.offset)?);
104
105            if dif.is_special_function() {
106                match dif.special_function() {
107                    data_information::SpecialFunctions::IdleFiller => {
108                        self.offset += 1;
109                    }
110                    data_information::SpecialFunctions::ManufacturerSpecific
111                    | data_information::SpecialFunctions::MoreRecordsFollow => {
112                        let remaining = self.data.get(self.offset..)?;
113                        self.offset = self.data.len();
114                        return Some(DataRecord::parse(remaining, self.long_tpl_header, &mut 0));
115                    }
116                    data_information::SpecialFunctions::GlobalReadoutRequest => {
117                        let remaining = self.data.get(self.offset..)?;
118                        self.offset += 1;
119                        return Some(DataRecord::parse(remaining, self.long_tpl_header, &mut 0));
120                    }
121                    data_information::SpecialFunctions::Reserved => {
122                        self.offset += 1;
123                    }
124                }
125            } else {
126                // After a record that fails to decode, `parse` still reports
127                // its declared length so the records behind it are read; if
128                // even that is unknown the stream cannot be resynchronised,
129                // and the rest is skipped.
130                let mut consumed = 0;
131                let record = DataRecord::parse(
132                    self.data.get(self.offset..)?,
133                    self.long_tpl_header,
134                    &mut consumed,
135                );
136                self.offset += consumed;
137                return Some(record);
138            }
139        }
140        None
141    }
142}
143
144impl<'a> DataRecords<'a> {
145    #[must_use]
146    pub const fn new(data: &'a [u8], long_tpl_header: Option<&'a LongTplHeader>) -> Self {
147        DataRecords {
148            offset: 0,
149            data,
150            long_tpl_header,
151        }
152    }
153}
154
155bitflags::bitflags! {
156    #[repr(transparent)]
157    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
158    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159    pub struct StatusField: u8 {
160        const COUNTER_BINARY_SIGNED     = 0b0000_0001;
161        const COUNTER_FIXED_DATE        = 0b0000_0010;
162        const POWER_LOW                 = 0b0000_0100;
163        const PERMANENT_ERROR           = 0b0000_1000;
164        const TEMPORARY_ERROR           = 0b0001_0000;
165        const MANUFACTURER_SPECIFIC_1   = 0b0010_0000;
166        const MANUFACTURER_SPECIFIC_2   = 0b0100_0000;
167        const MANUFACTURER_SPECIFIC_3   = 0b1000_0000;
168    }
169}
170
171#[cfg(feature = "defmt")]
172impl defmt::Format for StatusField {
173    fn format(&self, f: defmt::Formatter) {
174        defmt::write!(f, "{:?}", self);
175    }
176}
177
178#[cfg(feature = "std")]
179impl fmt::Display for StatusField {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        let mut status = String::new();
182        if self.contains(StatusField::COUNTER_BINARY_SIGNED) {
183            status.push_str("Counter binary signed, ");
184        }
185        if self.contains(StatusField::COUNTER_FIXED_DATE) {
186            status.push_str("Counter fixed date, ");
187        }
188        if self.contains(StatusField::POWER_LOW) {
189            status.push_str("Power low, ");
190        }
191        if self.contains(StatusField::PERMANENT_ERROR) {
192            status.push_str("Permanent error, ");
193        }
194        if self.contains(StatusField::TEMPORARY_ERROR) {
195            status.push_str("Temporary error, ");
196        }
197        if self.contains(StatusField::MANUFACTURER_SPECIFIC_1) {
198            status.push_str("Manufacturer specific 1, ");
199        }
200        if self.contains(StatusField::MANUFACTURER_SPECIFIC_2) {
201            status.push_str("Manufacturer specific 2, ");
202        }
203        if self.contains(StatusField::MANUFACTURER_SPECIFIC_3) {
204            status.push_str("Manufacturer specific 3, ");
205        }
206        if status.is_empty() {
207            status.push_str("No Error(s)");
208        }
209        write!(f, "{}", status.trim_end_matches(", "))
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq)]
214#[cfg_attr(feature = "defmt", derive(defmt::Format))]
215#[non_exhaustive]
216pub enum Direction {
217    SlaveToMaster,
218    MasterToSlave,
219}
220
221// implement from trait for direction
222impl From<ControlInformation> for Direction {
223    fn from(single_byte: ControlInformation) -> Self {
224        match single_byte {
225            ControlInformation::ResetAtApplicationLevel => Self::MasterToSlave,
226            ControlInformation::SendData => Self::MasterToSlave,
227            ControlInformation::SelectSlave => Self::MasterToSlave,
228            ControlInformation::SynchronizeSlave => Self::MasterToSlave,
229            ControlInformation::SetBaudRate300 => Self::MasterToSlave,
230            ControlInformation::SetBaudRate600 => Self::MasterToSlave,
231            ControlInformation::SetBaudRate1200 => Self::MasterToSlave,
232            ControlInformation::SetBaudRate2400 => Self::MasterToSlave,
233            ControlInformation::SetBaudRate4800 => Self::MasterToSlave,
234            ControlInformation::SetBaudRate9600 => Self::MasterToSlave,
235            ControlInformation::SetBaudRate19200 => Self::MasterToSlave,
236            ControlInformation::SetBaudRate38400 => Self::MasterToSlave,
237            ControlInformation::OutputRAMContent => Self::MasterToSlave,
238            ControlInformation::WriteRAMContent => Self::MasterToSlave,
239            ControlInformation::StartCalibrationTestMode => Self::MasterToSlave,
240            ControlInformation::ReadEEPROM => Self::MasterToSlave,
241            ControlInformation::StartSoftwareTest => Self::MasterToSlave,
242            ControlInformation::HashProcedure(_) => Self::MasterToSlave,
243            ControlInformation::SendErrorStatus => Self::SlaveToMaster,
244            ControlInformation::SendAlarmStatus => Self::SlaveToMaster,
245            ControlInformation::ResponseWithVariableDataStructure { lsb_order: _ } => {
246                Self::SlaveToMaster
247            }
248            ControlInformation::ResponseWithFixedDataStructure => Self::SlaveToMaster,
249            ControlInformation::DataSentWithShortTransportLayer => Self::MasterToSlave,
250            ControlInformation::DataSentWithLongTransportLayer => Self::MasterToSlave,
251            ControlInformation::CosemDataWithLongTransportLayer => Self::MasterToSlave,
252            ControlInformation::CosemDataWithShortTransportLayer => Self::MasterToSlave,
253            ControlInformation::ObisDataReservedLongTransportLayer => Self::MasterToSlave,
254            ControlInformation::ObisDataReservedShortTransportLayer => Self::MasterToSlave,
255            ControlInformation::ApplicationLayerFormatFrameNoTransport => Self::MasterToSlave,
256            ControlInformation::ApplicationLayerFormatFrameShortTransport => Self::MasterToSlave,
257            ControlInformation::ApplicationLayerFormatFrameLongTransport => Self::MasterToSlave,
258            ControlInformation::ClockSyncAbsolute => Self::MasterToSlave,
259            ControlInformation::ClockSyncRelative => Self::MasterToSlave,
260            ControlInformation::ApplicationErrorShortTransport => Self::SlaveToMaster,
261            ControlInformation::ApplicationErrorLongTransport => Self::SlaveToMaster,
262            ControlInformation::AlarmShortTransport => Self::SlaveToMaster,
263            ControlInformation::AlarmLongTransport => Self::SlaveToMaster,
264            ControlInformation::ApplicationLayerNoTransport => Self::SlaveToMaster,
265            ControlInformation::ApplicationLayerCompactFrameNoTransport => Self::SlaveToMaster,
266            ControlInformation::ApplicationLayerShortTransport => Self::SlaveToMaster,
267            ControlInformation::ApplicationLayerCompactFrameShortTransport => Self::SlaveToMaster,
268            ControlInformation::CosemApplicationLayerLongTransport => Self::SlaveToMaster,
269            ControlInformation::CosemApplicationLayerShortTransport => Self::SlaveToMaster,
270            ControlInformation::ObisApplicationLayerReservedLongTransport => Self::SlaveToMaster,
271            ControlInformation::ObisApplicationLayerReservedShortTransport => Self::SlaveToMaster,
272            ControlInformation::TransportLayerLongReadoutToMeter => Self::MasterToSlave,
273            ControlInformation::NetworkLayerData => Self::MasterToSlave,
274            ControlInformation::FutureUse => Self::MasterToSlave,
275            ControlInformation::NetworkManagementApplication => Self::MasterToSlave,
276            ControlInformation::TransportLayerCompactFrame => Self::MasterToSlave,
277            ControlInformation::TransportLayerFormatFrame => Self::MasterToSlave,
278            ControlInformation::NetworkManagementDataReserved => Self::MasterToSlave,
279            ControlInformation::TransportLayerShortMeterToReadout => Self::SlaveToMaster,
280            ControlInformation::TransportLayerLongMeterToReadout => Self::SlaveToMaster,
281            ControlInformation::ExtendedLinkLayerI => Self::SlaveToMaster,
282            ControlInformation::ExtendedLinkLayerII => Self::SlaveToMaster,
283            ControlInformation::ExtendedLinkLayerIII => Self::SlaveToMaster,
284        }
285    }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq)]
289#[cfg_attr(feature = "defmt", derive(defmt::Format))]
290#[non_exhaustive]
291pub enum ControlInformation {
292    SendData,
293    SelectSlave,
294    ResetAtApplicationLevel,
295    SynchronizeSlave,
296    SetBaudRate300,
297    SetBaudRate600,
298    SetBaudRate1200,
299    SetBaudRate2400,
300    SetBaudRate4800,
301    SetBaudRate9600,
302    SetBaudRate19200,
303    SetBaudRate38400,
304    OutputRAMContent,
305    WriteRAMContent,
306    StartCalibrationTestMode,
307    ReadEEPROM,
308    StartSoftwareTest,
309    HashProcedure(u8),
310    SendErrorStatus,
311    SendAlarmStatus,
312    ResponseWithVariableDataStructure { lsb_order: bool },
313    ResponseWithFixedDataStructure,
314    // Wireless M-Bus CI values
315    DataSentWithShortTransportLayer,
316    DataSentWithLongTransportLayer,
317    CosemDataWithLongTransportLayer,
318    CosemDataWithShortTransportLayer,
319    ObisDataReservedLongTransportLayer,
320    ObisDataReservedShortTransportLayer,
321    ApplicationLayerFormatFrameNoTransport,
322    ApplicationLayerFormatFrameShortTransport,
323    ApplicationLayerFormatFrameLongTransport,
324    ClockSyncAbsolute,
325    ClockSyncRelative,
326    ApplicationErrorShortTransport,
327    ApplicationErrorLongTransport,
328    AlarmShortTransport,
329    AlarmLongTransport,
330    ApplicationLayerNoTransport,
331    ApplicationLayerCompactFrameNoTransport,
332    ApplicationLayerShortTransport,
333    ApplicationLayerCompactFrameShortTransport,
334    CosemApplicationLayerLongTransport,
335    CosemApplicationLayerShortTransport,
336    ObisApplicationLayerReservedLongTransport,
337    ObisApplicationLayerReservedShortTransport,
338    TransportLayerLongReadoutToMeter,
339    NetworkLayerData,
340    FutureUse,
341    NetworkManagementApplication,
342    TransportLayerCompactFrame,
343    TransportLayerFormatFrame,
344    NetworkManagementDataReserved,
345    TransportLayerShortMeterToReadout,
346    TransportLayerLongMeterToReadout,
347    ExtendedLinkLayerI,
348    ExtendedLinkLayerII,
349    ExtendedLinkLayerIII,
350}
351
352impl ControlInformation {
353    const fn from(byte: u8) -> Result<Self, ApplicationLayerError> {
354        match byte {
355            0x50 => Ok(Self::ResetAtApplicationLevel),
356            0x51 => Ok(Self::SendData),
357            0x52 => Ok(Self::SelectSlave),
358            0x54 => Ok(Self::SynchronizeSlave),
359            0x5A => Ok(Self::DataSentWithShortTransportLayer),
360            0x5B => Ok(Self::DataSentWithLongTransportLayer),
361            0x60 => Ok(Self::CosemDataWithLongTransportLayer),
362            0x61 => Ok(Self::CosemDataWithShortTransportLayer),
363            0x64 => Ok(Self::ObisDataReservedLongTransportLayer),
364            0x65 => Ok(Self::ObisDataReservedShortTransportLayer),
365            0x69 => Ok(Self::ApplicationLayerFormatFrameNoTransport),
366            0x6A => Ok(Self::ApplicationLayerFormatFrameShortTransport),
367            0x6B => Ok(Self::ApplicationLayerFormatFrameLongTransport),
368            0x6C => Ok(Self::ClockSyncAbsolute),
369            0x6D => Ok(Self::ClockSyncRelative),
370            0x6E => Ok(Self::ApplicationErrorShortTransport),
371            0x6F => Ok(Self::ApplicationErrorLongTransport),
372            0x70 => Ok(Self::SendErrorStatus),
373            0x71 => Ok(Self::SendAlarmStatus),
374            0x72 | 0x76 => Ok(Self::ResponseWithVariableDataStructure {
375                lsb_order: byte & 0x04 != 0,
376            }),
377            0x73 | 0x77 => Ok(Self::ResponseWithFixedDataStructure),
378            0x74 => Ok(Self::AlarmShortTransport),
379            0x75 => Ok(Self::AlarmLongTransport),
380            0x78 => Ok(Self::ApplicationLayerNoTransport),
381            0x79 => Ok(Self::ApplicationLayerCompactFrameNoTransport),
382            0x7A => Ok(Self::ApplicationLayerShortTransport),
383            0x7B => Ok(Self::ApplicationLayerCompactFrameShortTransport),
384            0x7C => Ok(Self::CosemApplicationLayerLongTransport),
385            0x7D => Ok(Self::CosemApplicationLayerShortTransport),
386            0x7E => Ok(Self::ObisApplicationLayerReservedLongTransport),
387            0x7F => Ok(Self::ObisApplicationLayerReservedShortTransport),
388            0x80 => Ok(Self::TransportLayerLongReadoutToMeter),
389            0x81 => Ok(Self::NetworkLayerData),
390            0x82 => Ok(Self::FutureUse),
391            0x83 => Ok(Self::NetworkManagementApplication),
392            0x84 => Ok(Self::TransportLayerCompactFrame),
393            0x85 => Ok(Self::TransportLayerFormatFrame),
394            0x89 => Ok(Self::NetworkManagementDataReserved),
395            0x8A => Ok(Self::TransportLayerShortMeterToReadout),
396            0x8B => Ok(Self::TransportLayerLongMeterToReadout),
397            0x8C => Ok(Self::ExtendedLinkLayerI),
398            0x8D => Ok(Self::ExtendedLinkLayerII),
399            0x8E => Ok(Self::ExtendedLinkLayerIII),
400            0x90..=0x97 => Ok(Self::HashProcedure(byte - 0x90)),
401            // Encrypted CI codes (0xA0-0xAF) - these are encrypted variants of 0x7A (ApplicationLayerShortTransport)
402            // When used with NOKEY, they should be parsed as unencrypted ApplicationLayerShortTransport
403            // The lower 4 bits indicate the encryption mode:
404            //   0xA0 = Mode 5 (AES-CBC-IV zero) or NOKEY
405            //   0xA2 = Mode 7 (AES-CBC-IV non-zero) or NOKEY
406            //   0xA4, 0xA6, etc. = other modes
407            0xA0..=0xAF => Ok(Self::ApplicationLayerShortTransport),
408            0xB1 => Ok(Self::OutputRAMContent),
409            0xB2 => Ok(Self::WriteRAMContent),
410            0xB3 => Ok(Self::StartCalibrationTestMode),
411            0xB4 => Ok(Self::ReadEEPROM),
412            0xB6 => Ok(Self::StartSoftwareTest),
413            0xB8 => Ok(Self::SetBaudRate300),
414            0xB9 => Ok(Self::SetBaudRate600),
415            0xBA => Ok(Self::SetBaudRate1200),
416            0xBB => Ok(Self::SetBaudRate2400),
417            0xBC => Ok(Self::SetBaudRate4800),
418            0xBD => Ok(Self::SetBaudRate9600),
419            0xBE => Ok(Self::SetBaudRate19200),
420            0xBF => Ok(Self::SetBaudRate38400),
421            _ => Err(ApplicationLayerError::InvalidControlInformation { byte }),
422        }
423    }
424}
425
426#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
427#[derive(Debug, Clone, Copy, PartialEq)]
428#[cfg_attr(feature = "defmt", derive(defmt::Format))]
429#[non_exhaustive]
430pub enum ApplicationResetSubcode {
431    All(u8),
432    UserData(u8),
433    SimpleBilling(u8),
434    EnhancedBilling(u8),
435    MultiTariffBilling(u8),
436    InstantaneousValues(u8),
437    LoadManagementValues(u8),
438    Reserved1(u8),
439    InstallationStartup(u8),
440    Testing(u8),
441    Calibration(u8),
442    ConfigurationUpdates(u8),
443    Manufacturing(u8),
444    Development(u8),
445    Selftest(u8),
446    Reserved2(u8),
447}
448
449#[cfg(feature = "std")]
450impl fmt::Display for ApplicationResetSubcode {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        let subcode = match self {
453            Self::All(_) => "All",
454            Self::UserData(_) => "User data",
455            Self::SimpleBilling(_) => "Simple billing",
456            Self::EnhancedBilling(_) => "Enhanced billing",
457            Self::MultiTariffBilling(_) => "Multi-tariff billing",
458            Self::InstantaneousValues(_) => "Instantaneous values",
459            Self::LoadManagementValues(_) => "Load management values",
460            Self::Reserved1(_) => "Reserved",
461            Self::InstallationStartup(_) => "Installation startup",
462            Self::Testing(_) => "Testing",
463            Self::Calibration(_) => "Calibration",
464            Self::ConfigurationUpdates(_) => "Configuration updates",
465            Self::Manufacturing(_) => "Manufacturing",
466            Self::Development(_) => "Development",
467            Self::Selftest(_) => "Self-test",
468            Self::Reserved2(_) => "Reserved",
469        };
470        write!(f, "{}", subcode)
471    }
472}
473
474impl ApplicationResetSubcode {
475    #[must_use]
476    pub const fn from(value: u8) -> Self {
477        match value & 0b1111 {
478            // Extracting the lower 4 bits
479            0b0000 => Self::All(value),
480            0b0001 => Self::UserData(value),
481            0b0010 => Self::SimpleBilling(value),
482            0b0011 => Self::EnhancedBilling(value),
483            0b0100 => Self::MultiTariffBilling(value),
484            0b0101 => Self::InstantaneousValues(value),
485            0b0110 => Self::LoadManagementValues(value),
486            0b0111 => Self::Reserved1(value),
487            0b1000 => Self::InstallationStartup(value),
488            0b1001 => Self::Testing(value),
489            0b1010 => Self::Calibration(value),
490            0b1011 => Self::ConfigurationUpdates(value),
491            0b1100 => Self::Manufacturing(value),
492            0b1101 => Self::Development(value),
493            0b1110 => Self::Selftest(value),
494            _ => Self::Reserved2(value),
495        }
496    }
497}
498
499#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
500#[derive(Debug, PartialEq)]
501#[cfg_attr(feature = "defmt", derive(defmt::Format))]
502pub struct Counter {
503    count: u32,
504}
505
506#[cfg(feature = "std")]
507impl fmt::Display for Counter {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        write!(f, "{:08}", self.count)
510    }
511}
512
513impl Counter {
514    pub fn from_bcd_hex_digits(digits: [u8; 4]) -> Result<Self, ApplicationLayerError> {
515        let count = bcd_hex_digits_to_u32(digits)?;
516        Ok(Self { count })
517    }
518}
519
520#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
521#[allow(clippy::large_enum_variant)]
522#[derive(Debug, PartialEq)]
523#[cfg_attr(feature = "defmt", derive(defmt::Format))]
524#[non_exhaustive]
525pub enum UserDataBlock<'a> {
526    ResetAtApplicationLevel {
527        subcode: ApplicationResetSubcode,
528    },
529    FixedDataStructure {
530        identification_number: IdentificationNumber,
531        access_number: u8,
532        status: StatusField,
533        device_type_and_unit: u16,
534        counter1: Counter,
535        counter2: Counter,
536    },
537    VariableDataStructureWithLongTplHeader {
538        extended_link_layer: Option<ExtendedLinkLayer>,
539        long_tpl_header: LongTplHeader,
540        #[cfg_attr(feature = "serde", serde(skip_serializing))]
541        variable_data_block: &'a [u8],
542    },
543
544    VariableDataStructureWithShortTplHeader {
545        extended_link_layer: Option<ExtendedLinkLayer>,
546        short_tpl_header: ShortTplHeader,
547        #[cfg_attr(feature = "serde", serde(skip_serializing))]
548        variable_data_block: &'a [u8],
549    },
550
551    VariableDataStructureWithoutTplHeader {
552        extended_link_layer: Option<ExtendedLinkLayer>,
553        #[cfg_attr(feature = "serde", serde(skip_serializing))]
554        variable_data_block: &'a [u8],
555    },
556}
557
558impl<'a> UserDataBlock<'a> {
559    /// Returns an iterator over the variable data records in this block.
560    ///
561    /// Fixed data structures, control messages, and encrypted payloads do not
562    /// expose records. Decrypt an encrypted payload before parsing its records.
563    #[must_use]
564    pub fn data_records(&self) -> Option<DataRecords<'_>> {
565        match self {
566            Self::VariableDataStructureWithLongTplHeader {
567                long_tpl_header,
568                variable_data_block,
569                ..
570            } if !long_tpl_header.is_encrypted() => Some(parse_data_records_with_header(
571                variable_data_block,
572                long_tpl_header,
573            )),
574            Self::VariableDataStructureWithShortTplHeader {
575                short_tpl_header,
576                variable_data_block,
577                ..
578            } if !short_tpl_header.is_encrypted() => Some(parse_data_records(variable_data_block)),
579            Self::VariableDataStructureWithoutTplHeader {
580                variable_data_block,
581                ..
582            } => Some(parse_data_records(variable_data_block)),
583            _ => None,
584        }
585    }
586
587    #[must_use]
588    pub fn is_encrypted(&self) -> Option<bool> {
589        match self {
590            Self::VariableDataStructureWithLongTplHeader {
591                long_tpl_header, ..
592            } => Some(long_tpl_header.is_encrypted()),
593            _ => None,
594        }
595    }
596
597    /// Returns the length of the variable data block (encrypted payload size)
598    #[must_use]
599    pub fn variable_data_len(&self) -> usize {
600        match self {
601            Self::VariableDataStructureWithLongTplHeader {
602                variable_data_block,
603                ..
604            } => variable_data_block.len(),
605            Self::VariableDataStructureWithShortTplHeader {
606                variable_data_block,
607                ..
608            } => variable_data_block.len(),
609            Self::VariableDataStructureWithoutTplHeader {
610                variable_data_block,
611                ..
612            } => variable_data_block.len(),
613            _ => 0,
614        }
615    }
616
617    #[cfg(feature = "decryption")]
618    pub fn decrypt_variable_data<K: crate::decryption::KeyProvider>(
619        &self,
620        provider: &K,
621        output: &mut [u8],
622    ) -> Result<usize, crate::decryption::DecryptionError> {
623        use crate::decryption::{DecryptionError, EncryptedPayload, KeyContext};
624
625        match self {
626            Self::VariableDataStructureWithLongTplHeader {
627                long_tpl_header,
628                variable_data_block,
629                ..
630            } => {
631                if !long_tpl_header.is_encrypted() {
632                    return Err(NotEncrypted);
633                }
634
635                let security_mode = long_tpl_header
636                    .short_tpl_header
637                    .configuration_field
638                    .security_mode();
639
640                let manufacturer = long_tpl_header
641                    .manufacturer
642                    .map_err(|_| DecryptionError::DecryptionFailed)?;
643
644                let context = KeyContext {
645                    manufacturer,
646                    identification_number: long_tpl_header.identification_number.number,
647                    version: long_tpl_header.version,
648                    device_type: long_tpl_header.device_type,
649                    security_mode,
650                    access_number: long_tpl_header.short_tpl_header.access_number,
651                };
652
653                let payload = EncryptedPayload::new(variable_data_block, context);
654                payload.decrypt_into(provider, output)
655            }
656            Self::VariableDataStructureWithShortTplHeader {
657                short_tpl_header, ..
658            } => {
659                if !short_tpl_header.is_encrypted() {
660                    Err(NotEncrypted)
661                } else {
662                    // Short TPL header doesn't contain manufacturer info,
663                    // use decrypt_variable_data_with_context() instead
664                    Err(UnknownEncryptionState)
665                }
666            }
667            _ => Err(DecryptionError::UnknownEncryptionState),
668        }
669    }
670
671    /// Decrypt variable data when manufacturer info is not available in the TPL header.
672    /// Use this for frames with Short TPL header where manufacturer info comes from the link layer.
673    #[cfg(feature = "decryption")]
674    pub fn decrypt_variable_data_with_context<K: crate::decryption::KeyProvider>(
675        &self,
676        provider: &K,
677        manufacturer: ManufacturerCode,
678        identification_number: u32,
679        version: u8,
680        device_type: DeviceType,
681        output: &mut [u8],
682    ) -> Result<usize, crate::decryption::DecryptionError> {
683        use crate::decryption::{DecryptionError, EncryptedPayload, KeyContext};
684
685        match self {
686            Self::VariableDataStructureWithShortTplHeader {
687                short_tpl_header,
688                variable_data_block,
689                ..
690            } => {
691                if !short_tpl_header.is_encrypted() {
692                    return Err(NotEncrypted);
693                }
694
695                let security_mode = short_tpl_header.configuration_field.security_mode();
696
697                let context = KeyContext {
698                    manufacturer,
699                    identification_number,
700                    version,
701                    device_type,
702                    security_mode,
703                    access_number: short_tpl_header.access_number,
704                };
705
706                let payload = EncryptedPayload::new(variable_data_block, context);
707                payload.decrypt_into(provider, output)
708            }
709            Self::VariableDataStructureWithLongTplHeader { .. } => {
710                // Long TPL header has its own manufacturer info, use decrypt_variable_data() instead
711                self.decrypt_variable_data(provider, output)
712            }
713            _ => Err(DecryptionError::UnknownEncryptionState),
714        }
715    }
716}
717
718#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
719#[derive(Debug, PartialEq)]
720#[cfg_attr(feature = "defmt", derive(defmt::Format))]
721pub struct LongTplHeader {
722    pub identification_number: IdentificationNumber,
723    #[cfg_attr(
724        feature = "serde",
725        serde(skip_deserializing, default = "default_manufacturer_result")
726    )]
727    pub manufacturer: Result<ManufacturerCode, ApplicationLayerError>,
728    pub version: u8,
729    pub device_type: DeviceType,
730    pub short_tpl_header: ShortTplHeader,
731    pub lsb_order: bool,
732}
733
734#[cfg(feature = "serde")]
735fn default_manufacturer_result() -> Result<ManufacturerCode, ApplicationLayerError> {
736    Err(ApplicationLayerError::InsufficientData)
737}
738
739#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
740#[derive(Debug, PartialEq)]
741#[cfg_attr(feature = "defmt", derive(defmt::Format))]
742pub struct ShortTplHeader {
743    pub access_number: u8,
744    pub status: StatusField,
745    pub configuration_field: ConfigurationField,
746}
747
748impl LongTplHeader {
749    #[must_use]
750    pub fn is_encrypted(&self) -> bool {
751        use m_bus_core::SecurityMode;
752        !matches!(
753            self.short_tpl_header.configuration_field.security_mode(),
754            SecurityMode::NoEncryption
755        )
756    }
757}
758
759impl ShortTplHeader {
760    #[must_use]
761    pub fn is_encrypted(&self) -> bool {
762        use m_bus_core::SecurityMode;
763        !matches!(
764            self.configuration_field.security_mode(),
765            SecurityMode::NoEncryption
766        )
767    }
768}
769
770impl<'a> TryFrom<&'a [u8]> for UserDataBlock<'a> {
771    type Error = ApplicationLayerError;
772
773    fn try_from(data: &'a [u8]) -> Result<Self, ApplicationLayerError> {
774        if data.is_empty() {
775            return Err(ApplicationLayerError::MissingControlInformation);
776        }
777        let control_information = ControlInformation::from(
778            *data
779                .first()
780                .ok_or(ApplicationLayerError::InsufficientData)?,
781        )?;
782
783        match control_information {
784            ControlInformation::ResetAtApplicationLevel => {
785                let subcode = ApplicationResetSubcode::from(
786                    *data.get(1).ok_or(ApplicationLayerError::InsufficientData)?,
787                );
788                Ok(UserDataBlock::ResetAtApplicationLevel { subcode })
789            }
790            ControlInformation::SendData => Err(ApplicationLayerError::Unimplemented {
791                feature: "SendData control information",
792            }),
793            ControlInformation::SelectSlave => Err(ApplicationLayerError::Unimplemented {
794                feature: "SelectSlave control information",
795            }),
796            ControlInformation::SynchronizeSlave => Err(ApplicationLayerError::Unimplemented {
797                feature: "SynchronizeSlave control information",
798            }),
799            ControlInformation::SetBaudRate300 => Err(ApplicationLayerError::Unimplemented {
800                feature: "SetBaudRate300 control information",
801            }),
802            ControlInformation::SetBaudRate600 => Err(ApplicationLayerError::Unimplemented {
803                feature: "SetBaudRate600 control information",
804            }),
805            ControlInformation::SetBaudRate1200 => Err(ApplicationLayerError::Unimplemented {
806                feature: "SetBaudRate1200 control information",
807            }),
808            ControlInformation::SetBaudRate2400 => Err(ApplicationLayerError::Unimplemented {
809                feature: "SetBaudRate2400 control information",
810            }),
811            ControlInformation::SetBaudRate4800 => Err(ApplicationLayerError::Unimplemented {
812                feature: "SetBaudRate4800 control information",
813            }),
814            ControlInformation::SetBaudRate9600 => Err(ApplicationLayerError::Unimplemented {
815                feature: "SetBaudRate9600 control information",
816            }),
817            ControlInformation::SetBaudRate19200 => Err(ApplicationLayerError::Unimplemented {
818                feature: "SetBaudRate19200 control information",
819            }),
820            ControlInformation::SetBaudRate38400 => Err(ApplicationLayerError::Unimplemented {
821                feature: "SetBaudRate38400 control information",
822            }),
823            ControlInformation::OutputRAMContent => Err(ApplicationLayerError::Unimplemented {
824                feature: "OutputRAMContent control information",
825            }),
826            ControlInformation::WriteRAMContent => Err(ApplicationLayerError::Unimplemented {
827                feature: "WriteRAMContent control information",
828            }),
829            ControlInformation::StartCalibrationTestMode => {
830                Err(ApplicationLayerError::Unimplemented {
831                    feature: "StartCalibrationTestMode control information",
832                })
833            }
834            ControlInformation::ReadEEPROM => Err(ApplicationLayerError::Unimplemented {
835                feature: "ReadEEPROM control information",
836            }),
837            ControlInformation::StartSoftwareTest => Err(ApplicationLayerError::Unimplemented {
838                feature: "StartSoftwareTest control information",
839            }),
840            ControlInformation::HashProcedure(_) => Err(ApplicationLayerError::Unimplemented {
841                feature: "HashProcedure control information",
842            }),
843            ControlInformation::SendErrorStatus => Err(ApplicationLayerError::Unimplemented {
844                feature: "SendErrorStatus control information",
845            }),
846            ControlInformation::SendAlarmStatus => Err(ApplicationLayerError::Unimplemented {
847                feature: "SendAlarmStatus control information",
848            }),
849            ControlInformation::ResponseWithVariableDataStructure { lsb_order } => {
850                let mut iter = data.iter().skip(1);
851                let mut identification_number_bytes = [
852                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
853                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
854                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
855                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
856                ];
857                if lsb_order {
858                    identification_number_bytes.reverse();
859                }
860
861                Ok(UserDataBlock::VariableDataStructureWithLongTplHeader {
862                    long_tpl_header: LongTplHeader {
863                        identification_number: IdentificationNumber::from_bcd_hex_digits(
864                            identification_number_bytes,
865                        )?,
866                        manufacturer: ManufacturerCode::from_id(u16::from_le_bytes([
867                            *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
868                            *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
869                        ])),
870                        version: *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
871                        device_type: DeviceType::from(
872                            *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
873                        ),
874                        short_tpl_header: ShortTplHeader {
875                            access_number: *iter
876                                .next()
877                                .ok_or(ApplicationLayerError::InsufficientData)?,
878                            status: {
879                                StatusField::from_bits_truncate(
880                                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
881                                )
882                            },
883                            configuration_field: {
884                                ConfigurationField::from_bytes(
885                                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
886                                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
887                                )
888                            },
889                        },
890                        lsb_order,
891                    },
892                    variable_data_block: data
893                        .get(13..data.len())
894                        .ok_or(ApplicationLayerError::InsufficientData)?,
895                    extended_link_layer: None,
896                })
897            }
898            ControlInformation::ResponseWithFixedDataStructure => {
899                let mut iter = data.iter().skip(1);
900                let identification_number = IdentificationNumber::from_bcd_hex_digits([
901                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
902                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
903                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
904                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
905                ])?;
906
907                let access_number = *iter.next().ok_or(ApplicationLayerError::InsufficientData)?;
908
909                let status = StatusField::from_bits_truncate(
910                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
911                );
912                let device_type_and_unit = u16::from_be_bytes([
913                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
914                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
915                ]);
916                let counter1 = Counter::from_bcd_hex_digits([
917                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
918                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
919                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
920                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
921                ])?;
922                let counter2 = Counter::from_bcd_hex_digits([
923                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
924                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
925                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
926                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
927                ])?;
928                Ok(UserDataBlock::FixedDataStructure {
929                    identification_number,
930                    access_number,
931                    status,
932                    device_type_and_unit,
933                    counter1,
934                    counter2,
935                })
936            }
937            ControlInformation::DataSentWithShortTransportLayer => {
938                Err(ApplicationLayerError::Unimplemented {
939                    feature: "DataSentWithShortTransportLayer control information",
940                })
941            }
942            ControlInformation::DataSentWithLongTransportLayer => {
943                Err(ApplicationLayerError::Unimplemented {
944                    feature: "DataSentWithLongTransportLayer control information",
945                })
946            }
947            ControlInformation::CosemDataWithLongTransportLayer => {
948                Err(ApplicationLayerError::Unimplemented {
949                    feature: "CosemDataWithLongTransportLayer control information",
950                })
951            }
952            ControlInformation::CosemDataWithShortTransportLayer => {
953                Err(ApplicationLayerError::Unimplemented {
954                    feature: "CosemDataWithShortTransportLayer control information",
955                })
956            }
957            ControlInformation::ObisDataReservedLongTransportLayer => {
958                Err(ApplicationLayerError::Unimplemented {
959                    feature: "ObisDataReservedLongTransportLayer control information",
960                })
961            }
962            ControlInformation::ObisDataReservedShortTransportLayer => {
963                Err(ApplicationLayerError::Unimplemented {
964                    feature: "ObisDataReservedShortTransportLayer control information",
965                })
966            }
967            ControlInformation::ApplicationLayerFormatFrameNoTransport => {
968                Err(ApplicationLayerError::Unimplemented {
969                    feature: "ApplicationLayerFormatFrameNoTransport control information",
970                })
971            }
972            ControlInformation::ApplicationLayerFormatFrameShortTransport => {
973                Err(ApplicationLayerError::Unimplemented {
974                    feature: "ApplicationLayerFormatFrameShortTransport control information",
975                })
976            }
977            ControlInformation::ApplicationLayerFormatFrameLongTransport => {
978                Err(ApplicationLayerError::Unimplemented {
979                    feature: "ApplicationLayerFormatFrameLongTransport control information",
980                })
981            }
982            ControlInformation::ClockSyncAbsolute => Err(ApplicationLayerError::Unimplemented {
983                feature: "ClockSyncAbsolute control information",
984            }),
985            ControlInformation::ClockSyncRelative => Err(ApplicationLayerError::Unimplemented {
986                feature: "ClockSyncRelative control information",
987            }),
988            ControlInformation::ApplicationErrorShortTransport => {
989                Err(ApplicationLayerError::Unimplemented {
990                    feature: "ApplicationErrorShortTransport control information",
991                })
992            }
993            ControlInformation::ApplicationErrorLongTransport => {
994                Err(ApplicationLayerError::Unimplemented {
995                    feature: "ApplicationErrorLongTransport control information",
996                })
997            }
998            ControlInformation::AlarmShortTransport => Err(ApplicationLayerError::Unimplemented {
999                feature: "AlarmShortTransport control information",
1000            }),
1001            ControlInformation::AlarmLongTransport => Err(ApplicationLayerError::Unimplemented {
1002                feature: "AlarmLongTransport control information",
1003            }),
1004            ControlInformation::ApplicationLayerNoTransport => {
1005                Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1006                    extended_link_layer: None,
1007                    variable_data_block: data
1008                        .get(1..data.len())
1009                        .ok_or(ApplicationLayerError::InsufficientData)?,
1010                })
1011            }
1012            ControlInformation::ApplicationLayerCompactFrameNoTransport => {
1013                Err(ApplicationLayerError::Unimplemented {
1014                    feature: "ApplicationLayerCompactFrameNoTransport control information",
1015                })
1016            }
1017            ControlInformation::ApplicationLayerShortTransport => {
1018                // CI=0xA0 (encryption mode 5) has an additional encryption configuration byte after CI
1019                // Other encrypted CI codes (0xA2, 0xA4, etc.) do not have this byte
1020                let has_encryption_config_byte = data[0] == 0xA0;
1021                let skip_count = if has_encryption_config_byte { 2 } else { 1 };
1022                let data_block_offset = if has_encryption_config_byte { 6 } else { 5 };
1023
1024                let mut iter = data.iter().skip(skip_count);
1025
1026                Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1027                    short_tpl_header: ShortTplHeader {
1028                        access_number: *iter
1029                            .next()
1030                            .ok_or(ApplicationLayerError::InsufficientData)?,
1031                        status: {
1032                            StatusField::from_bits_truncate(
1033                                *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
1034                            )
1035                        },
1036                        configuration_field: {
1037                            ConfigurationField::from_bytes(
1038                                *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
1039                                *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
1040                            )
1041                        },
1042                    },
1043                    variable_data_block: data
1044                        .get(data_block_offset..data.len())
1045                        .ok_or(ApplicationLayerError::InsufficientData)?,
1046                    extended_link_layer: None,
1047                })
1048            }
1049            ControlInformation::ApplicationLayerCompactFrameShortTransport => {
1050                Err(ApplicationLayerError::Unimplemented {
1051                    feature: "ApplicationLayerCompactFrameShortTransport control information",
1052                })
1053            }
1054            ControlInformation::CosemApplicationLayerLongTransport => {
1055                Err(ApplicationLayerError::Unimplemented {
1056                    feature: "CosemApplicationLayerLongTransport control information",
1057                })
1058            }
1059            ControlInformation::CosemApplicationLayerShortTransport => {
1060                Err(ApplicationLayerError::Unimplemented {
1061                    feature: "CosemApplicationLayerShortTransport control information",
1062                })
1063            }
1064            ControlInformation::ObisApplicationLayerReservedLongTransport => {
1065                Err(ApplicationLayerError::Unimplemented {
1066                    feature: "ObisApplicationLayerReservedLongTransport control information",
1067                })
1068            }
1069            ControlInformation::ObisApplicationLayerReservedShortTransport => {
1070                Err(ApplicationLayerError::Unimplemented {
1071                    feature: "ObisApplicationLayerReservedShortTransport control information",
1072                })
1073            }
1074            ControlInformation::TransportLayerLongReadoutToMeter => {
1075                Err(ApplicationLayerError::Unimplemented {
1076                    feature: "TransportLayerLongReadoutToMeter control information",
1077                })
1078            }
1079            ControlInformation::NetworkLayerData => Err(ApplicationLayerError::Unimplemented {
1080                feature: "NetworkLayerData control information",
1081            }),
1082            ControlInformation::FutureUse => Err(ApplicationLayerError::Unimplemented {
1083                feature: "FutureUse control information",
1084            }),
1085            ControlInformation::NetworkManagementApplication => {
1086                Err(ApplicationLayerError::Unimplemented {
1087                    feature: "NetworkManagementApplication control information",
1088                })
1089            }
1090            ControlInformation::TransportLayerCompactFrame => {
1091                Err(ApplicationLayerError::Unimplemented {
1092                    feature: "TransportLayerCompactFrame control information",
1093                })
1094            }
1095            ControlInformation::TransportLayerFormatFrame => {
1096                Err(ApplicationLayerError::Unimplemented {
1097                    feature: "TransportLayerFormatFrame control information",
1098                })
1099            }
1100            ControlInformation::NetworkManagementDataReserved => {
1101                Err(ApplicationLayerError::Unimplemented {
1102                    feature: "NetworkManagementDataReserved control information",
1103                })
1104            }
1105            ControlInformation::TransportLayerShortMeterToReadout => {
1106                Err(ApplicationLayerError::Unimplemented {
1107                    feature: "TransportLayerShortMeterToReadout control information",
1108                })
1109            }
1110            ControlInformation::TransportLayerLongMeterToReadout => {
1111                Err(ApplicationLayerError::Unimplemented {
1112                    feature: "TransportLayerLongMeterToReadout control information",
1113                })
1114            }
1115            ControlInformation::ExtendedLinkLayerI => {
1116                let mut iter = data.iter();
1117                iter.next();
1118                let extended_link_layer = Some(ExtendedLinkLayer {
1119                    communication_control: *iter
1120                        .next()
1121                        .ok_or(ApplicationLayerError::InsufficientData)?,
1122                    access_number: *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
1123                    receiver_address: None,
1124                    encryption: None,
1125                });
1126                match UserDataBlock::try_from(iter.as_slice()) {
1127                    Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1128                        short_tpl_header,
1129                        variable_data_block,
1130                        ..
1131                    }) => Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1132                        extended_link_layer,
1133                        short_tpl_header,
1134                        variable_data_block,
1135                    }),
1136                    Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1137                        variable_data_block,
1138                        ..
1139                    }) => Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1140                        extended_link_layer,
1141                        variable_data_block,
1142                    }),
1143                    _ => Err(ApplicationLayerError::MissingControlInformation),
1144                }
1145            }
1146            ControlInformation::ExtendedLinkLayerII => {
1147                // CI byte + ELL II (8 bytes) = 9 bytes total before application data
1148                let (ell, ell_size) = ExtendedLinkLayer::parse(
1149                    data.get(1..)
1150                        .ok_or(ApplicationLayerError::InsufficientData)?,
1151                    extended_link_layer::EllFormat::FormatII,
1152                )?;
1153                let app_data_offset = 1 + ell_size;
1154
1155                // Create a ShortTplHeader from ELL fields
1156                // For encrypted ELL frames, the ELL fields become the "header"
1157                let short_tpl_header = ShortTplHeader {
1158                    access_number: ell.access_number,
1159                    status: StatusField::from_bits_truncate(ell.communication_control),
1160                    configuration_field: ConfigurationField::from_bytes(0x00, 0x00),
1161                };
1162
1163                Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1164                    extended_link_layer: Some(ell),
1165                    short_tpl_header,
1166                    variable_data_block: data
1167                        .get(app_data_offset..)
1168                        .ok_or(ApplicationLayerError::InsufficientData)?,
1169                })
1170            }
1171            ControlInformation::ExtendedLinkLayerIII => {
1172                // CI byte + ELL III (16 bytes) = 17 bytes total before application data
1173                let (ell, ell_size) = ExtendedLinkLayer::parse(
1174                    data.get(1..)
1175                        .ok_or(ApplicationLayerError::InsufficientData)?,
1176                    extended_link_layer::EllFormat::FormatIII,
1177                )?;
1178                let app_data_offset = 1 + ell_size;
1179
1180                // Create a ShortTplHeader from ELL fields
1181                // For encrypted ELL frames, the ELL fields become the "header"
1182                let short_tpl_header = ShortTplHeader {
1183                    access_number: ell.access_number,
1184                    status: StatusField::from_bits_truncate(ell.communication_control),
1185                    configuration_field: ConfigurationField::from_bytes(0x00, 0x00),
1186                };
1187
1188                Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1189                    extended_link_layer: Some(ell),
1190                    short_tpl_header,
1191                    variable_data_block: data
1192                        .get(app_data_offset..)
1193                        .ok_or(ApplicationLayerError::InsufficientData)?,
1194                })
1195            }
1196        }
1197    }
1198}
1199
1200#[allow(clippy::unwrap_used, clippy::panic)]
1201#[cfg(all(test, feature = "std"))]
1202mod tests {
1203
1204    use super::*;
1205
1206    #[test]
1207    fn undecodable_record_does_not_discard_the_rest_of_the_frame() {
1208        // Record 1 is `3C 2A DD B4 EB DD` from abb_f95: an 8-digit BCD field
1209        // holding a manufacturer error marker, which is not valid BCD. The
1210        // records on either side of it must still be yielded.
1211        let data = [
1212            0x0C, 0x12, 0x42, 0x07, 0x00, 0x00, // volume, 4 bytes BCD
1213            0x3C, 0x2A, 0xDD, 0xB4, 0xEB, 0xDD, // power, undecodable
1214            0x0A, 0x5A, 0x04, 0x02, // flow temperature, 2 bytes BCD
1215        ];
1216
1217        let results: Vec<_> = parse_data_records(&data).collect();
1218        assert_eq!(results.len(), 3);
1219        assert!(results[0].is_ok());
1220        assert!(results[1].is_err());
1221        assert!(results[2].is_ok());
1222
1223        let flow_temperature = results[2].as_ref().expect("third record parses");
1224        assert_eq!(
1225            flow_temperature.value(),
1226            Some(&data_information::DataType::Number(204.0))
1227        );
1228    }
1229
1230    #[test]
1231    fn unresynchronisable_record_stops_the_stream() {
1232        // A truncated variable-length field: the LVAR byte claims more data
1233        // than the frame holds, so there is no length to step over.
1234        let data = [0x0D, 0x2A, 0x40, 0x01, 0x02];
1235
1236        let results: Vec<_> = parse_data_records(&data).collect();
1237        assert_eq!(results.len(), 1);
1238        assert!(results[0].is_err());
1239    }
1240
1241    #[test]
1242    fn test_control_information() {
1243        assert_eq!(
1244            ControlInformation::from(0x50),
1245            Ok(ControlInformation::ResetAtApplicationLevel)
1246        );
1247        assert_eq!(
1248            ControlInformation::from(0x51),
1249            Ok(ControlInformation::SendData)
1250        );
1251        assert_eq!(
1252            ControlInformation::from(0x52),
1253            Ok(ControlInformation::SelectSlave)
1254        );
1255        assert_eq!(
1256            ControlInformation::from(0x54),
1257            Ok(ControlInformation::SynchronizeSlave)
1258        );
1259        assert_eq!(
1260            ControlInformation::from(0xB8),
1261            Ok(ControlInformation::SetBaudRate300)
1262        );
1263        assert_eq!(
1264            ControlInformation::from(0xB9),
1265            Ok(ControlInformation::SetBaudRate600)
1266        );
1267        assert_eq!(
1268            ControlInformation::from(0xBA),
1269            Ok(ControlInformation::SetBaudRate1200)
1270        );
1271        assert_eq!(
1272            ControlInformation::from(0xBB),
1273            Ok(ControlInformation::SetBaudRate2400)
1274        );
1275        assert_eq!(
1276            ControlInformation::from(0xBC),
1277            Ok(ControlInformation::SetBaudRate4800)
1278        );
1279        assert_eq!(
1280            ControlInformation::from(0xBD),
1281            Ok(ControlInformation::SetBaudRate9600)
1282        );
1283        assert_eq!(
1284            ControlInformation::from(0xBE),
1285            Ok(ControlInformation::SetBaudRate19200)
1286        );
1287        assert_eq!(
1288            ControlInformation::from(0xBF),
1289            Ok(ControlInformation::SetBaudRate38400)
1290        );
1291        assert_eq!(
1292            ControlInformation::from(0xB1),
1293            Ok(ControlInformation::OutputRAMContent)
1294        );
1295        assert_eq!(
1296            ControlInformation::from(0xB2),
1297            Ok(ControlInformation::WriteRAMContent)
1298        );
1299        assert_eq!(
1300            ControlInformation::from(0xB3),
1301            Ok(ControlInformation::StartCalibrationTestMode)
1302        );
1303        assert_eq!(
1304            ControlInformation::from(0xB4),
1305            Ok(ControlInformation::ReadEEPROM)
1306        );
1307        assert_eq!(
1308            ControlInformation::from(0xB6),
1309            Ok(ControlInformation::StartSoftwareTest)
1310        );
1311        assert_eq!(
1312            ControlInformation::from(0x90),
1313            Ok(ControlInformation::HashProcedure(0,))
1314        );
1315        assert_eq!(
1316            ControlInformation::from(0x91),
1317            Ok(ControlInformation::HashProcedure(1,))
1318        );
1319    }
1320
1321    #[test]
1322    fn test_reset_subcode() {
1323        // Application layer of frame | 68 04 04 68 | 53 FE 50 | 10 | B1 16
1324        let data = [0x50, 0x10];
1325        let result = UserDataBlock::try_from(data.as_slice());
1326        assert_eq!(
1327            result,
1328            Ok(UserDataBlock::ResetAtApplicationLevel {
1329                subcode: ApplicationResetSubcode::All(0x10)
1330            })
1331        );
1332    }
1333
1334    #[test]
1335    fn test_application_layer_no_transport() {
1336        let data = [0x78, 0x0B, 0x13, 0x43, 0x65, 0x87];
1337        let result = UserDataBlock::try_from(data.as_slice());
1338
1339        assert_eq!(
1340            result,
1341            Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1342                extended_link_layer: None,
1343                variable_data_block: &data[1..],
1344            })
1345        );
1346    }
1347
1348    #[test]
1349    fn test_ell_i_with_application_layer_no_transport() {
1350        let data = [0x8C, 0x20, 0x27, 0x78, 0x0B, 0x13, 0x43, 0x65, 0x87];
1351        let result = UserDataBlock::try_from(data.as_slice());
1352
1353        match result {
1354            Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1355                extended_link_layer: Some(ell),
1356                variable_data_block,
1357            }) => {
1358                assert_eq!(ell.communication_control, 0x20);
1359                assert_eq!(ell.access_number, 0x27);
1360                assert_eq!(variable_data_block, &data[4..]);
1361            }
1362            other => panic!("expected no-TPL user data after ELL I, got {other:?}"),
1363        }
1364    }
1365
1366    #[test]
1367    fn test_device_type_roundtrip() {
1368        // Test that to_byte is the inverse of from_byte for specific values
1369        let test_cases = [
1370            (0x00, DeviceType::Other),
1371            (0x01, DeviceType::OilMeter),
1372            (0x02, DeviceType::ElectricityMeter),
1373            (0x03, DeviceType::GasMeter),
1374            (0x04, DeviceType::HeatMeterReturn),
1375            (0x05, DeviceType::SteamMeter),
1376            (0x06, DeviceType::WarmWaterMeter),
1377            (0x07, DeviceType::WaterMeter),
1378            (0x08, DeviceType::HeatCostAllocator),
1379            (0x09, DeviceType::CompressedAir),
1380            (0x0A, DeviceType::CoolingMeterReturn),
1381            (0x0B, DeviceType::CoolingMeterFlow),
1382            (0x0C, DeviceType::HeatMeterFlow),
1383            (0x0D, DeviceType::CombinedHeatCoolingMeter),
1384            (0x0E, DeviceType::BusSystemComponent),
1385            (0x0F, DeviceType::UnknownDevice),
1386            (0x10, DeviceType::IrrigationWaterMeter),
1387            (0x11, DeviceType::WaterDataLogger),
1388            (0x12, DeviceType::GasDataLogger),
1389            (0x13, DeviceType::GasConverter),
1390            (0x14, DeviceType::CalorificValue),
1391            (0x15, DeviceType::HotWaterMeter),
1392            (0x16, DeviceType::ColdWaterMeter),
1393            (0x17, DeviceType::DualRegisterWaterMeter),
1394            (0x18, DeviceType::PressureMeter),
1395            (0x19, DeviceType::AdConverter),
1396            (0x1A, DeviceType::SmokeDetector),
1397            (0x1B, DeviceType::RoomSensor),
1398            (0x1C, DeviceType::GasDetector),
1399            (0x20, DeviceType::ElectricityBreaker),
1400            (0x21, DeviceType::Valve),
1401            (0x25, DeviceType::CustomerUnit),
1402            (0x28, DeviceType::WasteWaterMeter),
1403            (0x29, DeviceType::Garbage),
1404            (0x30, DeviceType::ServiceTool),
1405            (0x31, DeviceType::CommunicationController),
1406            (0x32, DeviceType::UnidirectionalRepeater),
1407            (0x33, DeviceType::BidirectionalRepeater),
1408            (0x36, DeviceType::RadioConverterSystemSide),
1409            (0x37, DeviceType::RadioConverterMeterSide),
1410            (0x38, DeviceType::BusConverterMeterSide),
1411            (0xFF, DeviceType::Wildcard),
1412            // Reserved ranges with specific variants
1413            (0x1D, DeviceType::ReservedSensor(0x1D)), // Reserved for sensors
1414            (0x22, DeviceType::ReservedSwitch(0x22)), // Reserved for switching devices
1415            (0x40, DeviceType::Reserved(0x40)),       // Reserved
1416        ];
1417
1418        for (byte, expected_device_type) in test_cases {
1419            let device_type = DeviceType::from(byte);
1420            assert_eq!(device_type, expected_device_type);
1421            assert_eq!(u8::from(device_type), byte);
1422        }
1423
1424        // Test that Reserved variants map back to their byte values
1425        assert_eq!(u8::from(DeviceType::Reserved(0x40)), 0x40);
1426        assert_eq!(u8::from(DeviceType::ReservedSensor(0x1D)), 0x1D);
1427        assert_eq!(u8::from(DeviceType::ReservedSwitch(0x22)), 0x22);
1428
1429        // Test that Unknown maps to canonical value 0x0F
1430        assert_eq!(u8::from(DeviceType::UnknownDevice), 0x0F);
1431    }
1432
1433    #[test]
1434    fn test_identification_number() -> Result<(), ApplicationLayerError> {
1435        let data = [0x78, 0x56, 0x34, 0x12];
1436        let result = IdentificationNumber::from_bcd_hex_digits(data)?;
1437        assert_eq!(result, IdentificationNumber { number: 12345678 });
1438        Ok(())
1439    }
1440
1441    #[test]
1442    fn test_fixed_data_structure() {
1443        let data = [
1444            0x73, 0x78, 0x56, 0x34, 0x12, 0x0A, 0x00, 0xE9, 0x7E, 0x01, 0x00, 0x00, 0x00, 0x35,
1445            0x01, 0x00, 0x00,
1446        ];
1447
1448        let result = UserDataBlock::try_from(data.as_slice());
1449
1450        assert_eq!(
1451            result,
1452            Ok(UserDataBlock::FixedDataStructure {
1453                identification_number: IdentificationNumber { number: 12345678 },
1454                access_number: 0x0A,
1455                status: StatusField::from_bits_truncate(0x00),
1456                device_type_and_unit: 0xE97E,
1457                counter1: Counter { count: 1 },
1458                counter2: Counter { count: 135 },
1459            })
1460        );
1461    }
1462
1463    #[test]
1464    fn test_manufacturer_code() -> Result<(), ApplicationLayerError> {
1465        let code = ManufacturerCode::from_id(0x1ee6)?;
1466        assert_eq!(
1467            code,
1468            ManufacturerCode {
1469                code: ['G', 'W', 'F']
1470            }
1471        );
1472        Ok(())
1473    }
1474
1475    #[test]
1476    fn global_readout_request_does_not_consume_following_records() {
1477        // 0x7F followed by a valid 8-bit integer record: DIF=0x01, VIF=0x13, data=0x05
1478        let data: &[u8] = &[0x7F, 0x01, 0x13, 0x05];
1479        let records: Vec<_> = DataRecords::new(data, None).flatten().collect();
1480        assert_eq!(records.len(), 2);
1481    }
1482
1483    #[test]
1484    fn parse_data_records_api_returns_record_values() {
1485        use crate::data_information::DataType;
1486
1487        let data = [0x03, 0x13, 0x15, 0x31, 0x00];
1488        let mut records = parse_data_records(&data);
1489        let record = records.next().unwrap().unwrap();
1490
1491        assert_eq!(record.value(), Some(&DataType::Number(12_565.0)));
1492        assert_eq!(record.raw_bytes(), &data);
1493        assert!(record.data_information().is_some());
1494        assert!(record.value_information().is_some());
1495        assert!(records.next().is_none());
1496    }
1497
1498    #[test]
1499    fn parse_application_layer_api_exposes_records() {
1500        let data = [0x78, 0x03, 0x13, 0x15, 0x31, 0x00];
1501        let application_layer = parse_application_layer(&data).unwrap();
1502        let records: Result<Vec<_>, _> = application_layer.data_records().unwrap().collect();
1503
1504        assert_eq!(records.unwrap().len(), 1);
1505    }
1506
1507    #[test]
1508    fn data_record_iterator_reports_parse_errors() {
1509        let mut records = parse_data_records(&[0x04]);
1510
1511        assert!(records.next().unwrap().is_err());
1512        assert!(records.next().is_none());
1513    }
1514}