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