Skip to main content

m_bus_application_layer/
lib.rs

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