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