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    VariableDataStructureWithoutTplHeader {
508        extended_link_layer: Option<ExtendedLinkLayer>,
509        #[cfg_attr(feature = "serde", serde(skip_serializing))]
510        variable_data_block: &'a [u8],
511    },
512}
513
514impl<'a> UserDataBlock<'a> {
515    #[must_use]
516    pub fn is_encrypted(&self) -> Option<bool> {
517        match self {
518            Self::VariableDataStructureWithLongTplHeader {
519                long_tpl_header, ..
520            } => Some(long_tpl_header.is_encrypted()),
521            _ => None,
522        }
523    }
524
525    /// Returns the length of the variable data block (encrypted payload size)
526    #[must_use]
527    pub fn variable_data_len(&self) -> usize {
528        match self {
529            Self::VariableDataStructureWithLongTplHeader {
530                variable_data_block,
531                ..
532            } => variable_data_block.len(),
533            Self::VariableDataStructureWithShortTplHeader {
534                variable_data_block,
535                ..
536            } => variable_data_block.len(),
537            Self::VariableDataStructureWithoutTplHeader {
538                variable_data_block,
539                ..
540            } => variable_data_block.len(),
541            _ => 0,
542        }
543    }
544
545    #[cfg(feature = "decryption")]
546    pub fn decrypt_variable_data<K: crate::decryption::KeyProvider>(
547        &self,
548        provider: &K,
549        output: &mut [u8],
550    ) -> Result<usize, crate::decryption::DecryptionError> {
551        use crate::decryption::{DecryptionError, EncryptedPayload, KeyContext};
552
553        match self {
554            Self::VariableDataStructureWithLongTplHeader {
555                long_tpl_header,
556                variable_data_block,
557                ..
558            } => {
559                if !long_tpl_header.is_encrypted() {
560                    return Err(NotEncrypted);
561                }
562
563                let security_mode = long_tpl_header
564                    .short_tpl_header
565                    .configuration_field
566                    .security_mode();
567
568                let manufacturer = long_tpl_header
569                    .manufacturer
570                    .map_err(|_| DecryptionError::DecryptionFailed)?;
571
572                let context = KeyContext {
573                    manufacturer,
574                    identification_number: long_tpl_header.identification_number.number,
575                    version: long_tpl_header.version,
576                    device_type: long_tpl_header.device_type,
577                    security_mode,
578                    access_number: long_tpl_header.short_tpl_header.access_number,
579                };
580
581                let payload = EncryptedPayload::new(variable_data_block, context);
582                payload.decrypt_into(provider, output)
583            }
584            Self::VariableDataStructureWithShortTplHeader {
585                short_tpl_header, ..
586            } => {
587                if !short_tpl_header.is_encrypted() {
588                    Err(NotEncrypted)
589                } else {
590                    // Short TPL header doesn't contain manufacturer info,
591                    // use decrypt_variable_data_with_context() instead
592                    Err(UnknownEncryptionState)
593                }
594            }
595            _ => Err(DecryptionError::UnknownEncryptionState),
596        }
597    }
598
599    /// Decrypt variable data when manufacturer info is not available in the TPL header.
600    /// Use this for frames with Short TPL header where manufacturer info comes from the link layer.
601    #[cfg(feature = "decryption")]
602    pub fn decrypt_variable_data_with_context<K: crate::decryption::KeyProvider>(
603        &self,
604        provider: &K,
605        manufacturer: ManufacturerCode,
606        identification_number: u32,
607        version: u8,
608        device_type: DeviceType,
609        output: &mut [u8],
610    ) -> Result<usize, crate::decryption::DecryptionError> {
611        use crate::decryption::{DecryptionError, EncryptedPayload, KeyContext};
612
613        match self {
614            Self::VariableDataStructureWithShortTplHeader {
615                short_tpl_header,
616                variable_data_block,
617                ..
618            } => {
619                if !short_tpl_header.is_encrypted() {
620                    return Err(NotEncrypted);
621                }
622
623                let security_mode = short_tpl_header.configuration_field.security_mode();
624
625                let context = KeyContext {
626                    manufacturer,
627                    identification_number,
628                    version,
629                    device_type,
630                    security_mode,
631                    access_number: short_tpl_header.access_number,
632                };
633
634                let payload = EncryptedPayload::new(variable_data_block, context);
635                payload.decrypt_into(provider, output)
636            }
637            Self::VariableDataStructureWithLongTplHeader { .. } => {
638                // Long TPL header has its own manufacturer info, use decrypt_variable_data() instead
639                self.decrypt_variable_data(provider, output)
640            }
641            _ => Err(DecryptionError::UnknownEncryptionState),
642        }
643    }
644}
645
646#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
647#[derive(Debug, PartialEq)]
648#[cfg_attr(feature = "defmt", derive(defmt::Format))]
649pub struct LongTplHeader {
650    pub identification_number: IdentificationNumber,
651    #[cfg_attr(
652        feature = "serde",
653        serde(skip_deserializing, default = "default_manufacturer_result")
654    )]
655    pub manufacturer: Result<ManufacturerCode, ApplicationLayerError>,
656    pub version: u8,
657    pub device_type: DeviceType,
658    pub short_tpl_header: ShortTplHeader,
659    pub lsb_order: bool,
660}
661
662#[cfg(feature = "serde")]
663fn default_manufacturer_result() -> Result<ManufacturerCode, ApplicationLayerError> {
664    Err(ApplicationLayerError::InsufficientData)
665}
666
667#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
668#[derive(Debug, PartialEq)]
669#[cfg_attr(feature = "defmt", derive(defmt::Format))]
670pub struct ShortTplHeader {
671    pub access_number: u8,
672    pub status: StatusField,
673    pub configuration_field: ConfigurationField,
674}
675
676impl LongTplHeader {
677    #[must_use]
678    pub fn is_encrypted(&self) -> bool {
679        use m_bus_core::SecurityMode;
680        !matches!(
681            self.short_tpl_header.configuration_field.security_mode(),
682            SecurityMode::NoEncryption
683        )
684    }
685}
686
687impl ShortTplHeader {
688    #[must_use]
689    pub fn is_encrypted(&self) -> bool {
690        use m_bus_core::SecurityMode;
691        !matches!(
692            self.configuration_field.security_mode(),
693            SecurityMode::NoEncryption
694        )
695    }
696}
697
698impl<'a> TryFrom<&'a [u8]> for UserDataBlock<'a> {
699    type Error = ApplicationLayerError;
700
701    fn try_from(data: &'a [u8]) -> Result<Self, ApplicationLayerError> {
702        if data.is_empty() {
703            return Err(ApplicationLayerError::MissingControlInformation);
704        }
705        let control_information = ControlInformation::from(
706            *data
707                .first()
708                .ok_or(ApplicationLayerError::InsufficientData)?,
709        )?;
710
711        match control_information {
712            ControlInformation::ResetAtApplicationLevel => {
713                let subcode = ApplicationResetSubcode::from(
714                    *data.get(1).ok_or(ApplicationLayerError::InsufficientData)?,
715                );
716                Ok(UserDataBlock::ResetAtApplicationLevel { subcode })
717            }
718            ControlInformation::SendData => Err(ApplicationLayerError::Unimplemented {
719                feature: "SendData control information",
720            }),
721            ControlInformation::SelectSlave => Err(ApplicationLayerError::Unimplemented {
722                feature: "SelectSlave control information",
723            }),
724            ControlInformation::SynchronizeSlave => Err(ApplicationLayerError::Unimplemented {
725                feature: "SynchronizeSlave control information",
726            }),
727            ControlInformation::SetBaudRate300 => Err(ApplicationLayerError::Unimplemented {
728                feature: "SetBaudRate300 control information",
729            }),
730            ControlInformation::SetBaudRate600 => Err(ApplicationLayerError::Unimplemented {
731                feature: "SetBaudRate600 control information",
732            }),
733            ControlInformation::SetBaudRate1200 => Err(ApplicationLayerError::Unimplemented {
734                feature: "SetBaudRate1200 control information",
735            }),
736            ControlInformation::SetBaudRate2400 => Err(ApplicationLayerError::Unimplemented {
737                feature: "SetBaudRate2400 control information",
738            }),
739            ControlInformation::SetBaudRate4800 => Err(ApplicationLayerError::Unimplemented {
740                feature: "SetBaudRate4800 control information",
741            }),
742            ControlInformation::SetBaudRate9600 => Err(ApplicationLayerError::Unimplemented {
743                feature: "SetBaudRate9600 control information",
744            }),
745            ControlInformation::SetBaudRate19200 => Err(ApplicationLayerError::Unimplemented {
746                feature: "SetBaudRate19200 control information",
747            }),
748            ControlInformation::SetBaudRate38400 => Err(ApplicationLayerError::Unimplemented {
749                feature: "SetBaudRate38400 control information",
750            }),
751            ControlInformation::OutputRAMContent => Err(ApplicationLayerError::Unimplemented {
752                feature: "OutputRAMContent control information",
753            }),
754            ControlInformation::WriteRAMContent => Err(ApplicationLayerError::Unimplemented {
755                feature: "WriteRAMContent control information",
756            }),
757            ControlInformation::StartCalibrationTestMode => {
758                Err(ApplicationLayerError::Unimplemented {
759                    feature: "StartCalibrationTestMode control information",
760                })
761            }
762            ControlInformation::ReadEEPROM => Err(ApplicationLayerError::Unimplemented {
763                feature: "ReadEEPROM control information",
764            }),
765            ControlInformation::StartSoftwareTest => Err(ApplicationLayerError::Unimplemented {
766                feature: "StartSoftwareTest control information",
767            }),
768            ControlInformation::HashProcedure(_) => Err(ApplicationLayerError::Unimplemented {
769                feature: "HashProcedure control information",
770            }),
771            ControlInformation::SendErrorStatus => Err(ApplicationLayerError::Unimplemented {
772                feature: "SendErrorStatus control information",
773            }),
774            ControlInformation::SendAlarmStatus => Err(ApplicationLayerError::Unimplemented {
775                feature: "SendAlarmStatus control information",
776            }),
777            ControlInformation::ResponseWithVariableDataStructure { lsb_order } => {
778                let mut iter = data.iter().skip(1);
779                let mut identification_number_bytes = [
780                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
781                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
782                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
783                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
784                ];
785                if lsb_order {
786                    identification_number_bytes.reverse();
787                }
788
789                Ok(UserDataBlock::VariableDataStructureWithLongTplHeader {
790                    long_tpl_header: LongTplHeader {
791                        identification_number: IdentificationNumber::from_bcd_hex_digits(
792                            identification_number_bytes,
793                        )?,
794                        manufacturer: ManufacturerCode::from_id(u16::from_le_bytes([
795                            *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
796                            *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
797                        ])),
798                        version: *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
799                        device_type: DeviceType::from(
800                            *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
801                        ),
802                        short_tpl_header: ShortTplHeader {
803                            access_number: *iter
804                                .next()
805                                .ok_or(ApplicationLayerError::InsufficientData)?,
806                            status: {
807                                StatusField::from_bits_truncate(
808                                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
809                                )
810                            },
811                            configuration_field: {
812                                ConfigurationField::from_bytes(
813                                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
814                                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
815                                )
816                            },
817                        },
818                        lsb_order,
819                    },
820                    variable_data_block: data
821                        .get(13..data.len())
822                        .ok_or(ApplicationLayerError::InsufficientData)?,
823                    extended_link_layer: None,
824                })
825            }
826            ControlInformation::ResponseWithFixedDataStructure => {
827                let mut iter = data.iter().skip(1);
828                let identification_number = IdentificationNumber::from_bcd_hex_digits([
829                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
830                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
831                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
832                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
833                ])?;
834
835                let access_number = *iter.next().ok_or(ApplicationLayerError::InsufficientData)?;
836
837                let status = StatusField::from_bits_truncate(
838                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
839                );
840                let device_type_and_unit = u16::from_be_bytes([
841                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
842                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
843                ]);
844                let counter1 = Counter::from_bcd_hex_digits([
845                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
846                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
847                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
848                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
849                ])?;
850                let counter2 = Counter::from_bcd_hex_digits([
851                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
852                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
853                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
854                    *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
855                ])?;
856                Ok(UserDataBlock::FixedDataStructure {
857                    identification_number,
858                    access_number,
859                    status,
860                    device_type_and_unit,
861                    counter1,
862                    counter2,
863                })
864            }
865            ControlInformation::DataSentWithShortTransportLayer => {
866                Err(ApplicationLayerError::Unimplemented {
867                    feature: "DataSentWithShortTransportLayer control information",
868                })
869            }
870            ControlInformation::DataSentWithLongTransportLayer => {
871                Err(ApplicationLayerError::Unimplemented {
872                    feature: "DataSentWithLongTransportLayer control information",
873                })
874            }
875            ControlInformation::CosemDataWithLongTransportLayer => {
876                Err(ApplicationLayerError::Unimplemented {
877                    feature: "CosemDataWithLongTransportLayer control information",
878                })
879            }
880            ControlInformation::CosemDataWithShortTransportLayer => {
881                Err(ApplicationLayerError::Unimplemented {
882                    feature: "CosemDataWithShortTransportLayer control information",
883                })
884            }
885            ControlInformation::ObisDataReservedLongTransportLayer => {
886                Err(ApplicationLayerError::Unimplemented {
887                    feature: "ObisDataReservedLongTransportLayer control information",
888                })
889            }
890            ControlInformation::ObisDataReservedShortTransportLayer => {
891                Err(ApplicationLayerError::Unimplemented {
892                    feature: "ObisDataReservedShortTransportLayer control information",
893                })
894            }
895            ControlInformation::ApplicationLayerFormatFrameNoTransport => {
896                Err(ApplicationLayerError::Unimplemented {
897                    feature: "ApplicationLayerFormatFrameNoTransport control information",
898                })
899            }
900            ControlInformation::ApplicationLayerFormatFrameShortTransport => {
901                Err(ApplicationLayerError::Unimplemented {
902                    feature: "ApplicationLayerFormatFrameShortTransport control information",
903                })
904            }
905            ControlInformation::ApplicationLayerFormatFrameLongTransport => {
906                Err(ApplicationLayerError::Unimplemented {
907                    feature: "ApplicationLayerFormatFrameLongTransport control information",
908                })
909            }
910            ControlInformation::ClockSyncAbsolute => Err(ApplicationLayerError::Unimplemented {
911                feature: "ClockSyncAbsolute control information",
912            }),
913            ControlInformation::ClockSyncRelative => Err(ApplicationLayerError::Unimplemented {
914                feature: "ClockSyncRelative control information",
915            }),
916            ControlInformation::ApplicationErrorShortTransport => {
917                Err(ApplicationLayerError::Unimplemented {
918                    feature: "ApplicationErrorShortTransport control information",
919                })
920            }
921            ControlInformation::ApplicationErrorLongTransport => {
922                Err(ApplicationLayerError::Unimplemented {
923                    feature: "ApplicationErrorLongTransport control information",
924                })
925            }
926            ControlInformation::AlarmShortTransport => Err(ApplicationLayerError::Unimplemented {
927                feature: "AlarmShortTransport control information",
928            }),
929            ControlInformation::AlarmLongTransport => Err(ApplicationLayerError::Unimplemented {
930                feature: "AlarmLongTransport control information",
931            }),
932            ControlInformation::ApplicationLayerNoTransport => {
933                Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
934                    extended_link_layer: None,
935                    variable_data_block: data
936                        .get(1..data.len())
937                        .ok_or(ApplicationLayerError::InsufficientData)?,
938                })
939            }
940            ControlInformation::ApplicationLayerCompactFrameNoTransport => {
941                Err(ApplicationLayerError::Unimplemented {
942                    feature: "ApplicationLayerCompactFrameNoTransport control information",
943                })
944            }
945            ControlInformation::ApplicationLayerShortTransport => {
946                // CI=0xA0 (encryption mode 5) has an additional encryption configuration byte after CI
947                // Other encrypted CI codes (0xA2, 0xA4, etc.) do not have this byte
948                let has_encryption_config_byte = data[0] == 0xA0;
949                let skip_count = if has_encryption_config_byte { 2 } else { 1 };
950                let data_block_offset = if has_encryption_config_byte { 6 } else { 5 };
951
952                let mut iter = data.iter().skip(skip_count);
953
954                Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
955                    short_tpl_header: ShortTplHeader {
956                        access_number: *iter
957                            .next()
958                            .ok_or(ApplicationLayerError::InsufficientData)?,
959                        status: {
960                            StatusField::from_bits_truncate(
961                                *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
962                            )
963                        },
964                        configuration_field: {
965                            ConfigurationField::from_bytes(
966                                *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
967                                *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
968                            )
969                        },
970                    },
971                    variable_data_block: data
972                        .get(data_block_offset..data.len())
973                        .ok_or(ApplicationLayerError::InsufficientData)?,
974                    extended_link_layer: None,
975                })
976            }
977            ControlInformation::ApplicationLayerCompactFrameShortTransport => {
978                Err(ApplicationLayerError::Unimplemented {
979                    feature: "ApplicationLayerCompactFrameShortTransport control information",
980                })
981            }
982            ControlInformation::CosemApplicationLayerLongTransport => {
983                Err(ApplicationLayerError::Unimplemented {
984                    feature: "CosemApplicationLayerLongTransport control information",
985                })
986            }
987            ControlInformation::CosemApplicationLayerShortTransport => {
988                Err(ApplicationLayerError::Unimplemented {
989                    feature: "CosemApplicationLayerShortTransport control information",
990                })
991            }
992            ControlInformation::ObisApplicationLayerReservedLongTransport => {
993                Err(ApplicationLayerError::Unimplemented {
994                    feature: "ObisApplicationLayerReservedLongTransport control information",
995                })
996            }
997            ControlInformation::ObisApplicationLayerReservedShortTransport => {
998                Err(ApplicationLayerError::Unimplemented {
999                    feature: "ObisApplicationLayerReservedShortTransport control information",
1000                })
1001            }
1002            ControlInformation::TransportLayerLongReadoutToMeter => {
1003                Err(ApplicationLayerError::Unimplemented {
1004                    feature: "TransportLayerLongReadoutToMeter control information",
1005                })
1006            }
1007            ControlInformation::NetworkLayerData => Err(ApplicationLayerError::Unimplemented {
1008                feature: "NetworkLayerData control information",
1009            }),
1010            ControlInformation::FutureUse => Err(ApplicationLayerError::Unimplemented {
1011                feature: "FutureUse control information",
1012            }),
1013            ControlInformation::NetworkManagementApplication => {
1014                Err(ApplicationLayerError::Unimplemented {
1015                    feature: "NetworkManagementApplication control information",
1016                })
1017            }
1018            ControlInformation::TransportLayerCompactFrame => {
1019                Err(ApplicationLayerError::Unimplemented {
1020                    feature: "TransportLayerCompactFrame control information",
1021                })
1022            }
1023            ControlInformation::TransportLayerFormatFrame => {
1024                Err(ApplicationLayerError::Unimplemented {
1025                    feature: "TransportLayerFormatFrame control information",
1026                })
1027            }
1028            ControlInformation::NetworkManagementDataReserved => {
1029                Err(ApplicationLayerError::Unimplemented {
1030                    feature: "NetworkManagementDataReserved control information",
1031                })
1032            }
1033            ControlInformation::TransportLayerShortMeterToReadout => {
1034                Err(ApplicationLayerError::Unimplemented {
1035                    feature: "TransportLayerShortMeterToReadout control information",
1036                })
1037            }
1038            ControlInformation::TransportLayerLongMeterToReadout => {
1039                Err(ApplicationLayerError::Unimplemented {
1040                    feature: "TransportLayerLongMeterToReadout control information",
1041                })
1042            }
1043            ControlInformation::ExtendedLinkLayerI => {
1044                let mut iter = data.iter();
1045                iter.next();
1046                let extended_link_layer = Some(ExtendedLinkLayer {
1047                    communication_control: *iter
1048                        .next()
1049                        .ok_or(ApplicationLayerError::InsufficientData)?,
1050                    access_number: *iter.next().ok_or(ApplicationLayerError::InsufficientData)?,
1051                    receiver_address: None,
1052                    encryption: None,
1053                });
1054                match UserDataBlock::try_from(iter.as_slice()) {
1055                    Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1056                        short_tpl_header,
1057                        variable_data_block,
1058                        ..
1059                    }) => Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1060                        extended_link_layer,
1061                        short_tpl_header,
1062                        variable_data_block,
1063                    }),
1064                    Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1065                        variable_data_block,
1066                        ..
1067                    }) => Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1068                        extended_link_layer,
1069                        variable_data_block,
1070                    }),
1071                    _ => Err(ApplicationLayerError::MissingControlInformation),
1072                }
1073            }
1074            ControlInformation::ExtendedLinkLayerII => {
1075                // CI byte + ELL II (8 bytes) = 9 bytes total before application data
1076                let (ell, ell_size) = ExtendedLinkLayer::parse(
1077                    data.get(1..)
1078                        .ok_or(ApplicationLayerError::InsufficientData)?,
1079                    extended_link_layer::EllFormat::FormatII,
1080                )?;
1081                let app_data_offset = 1 + ell_size;
1082
1083                // Create a ShortTplHeader from ELL fields
1084                // For encrypted ELL frames, the ELL fields become the "header"
1085                let short_tpl_header = ShortTplHeader {
1086                    access_number: ell.access_number,
1087                    status: StatusField::from_bits_truncate(ell.communication_control),
1088                    configuration_field: ConfigurationField::from_bytes(0x00, 0x00),
1089                };
1090
1091                Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1092                    extended_link_layer: Some(ell),
1093                    short_tpl_header,
1094                    variable_data_block: data
1095                        .get(app_data_offset..)
1096                        .ok_or(ApplicationLayerError::InsufficientData)?,
1097                })
1098            }
1099            ControlInformation::ExtendedLinkLayerIII => {
1100                // CI byte + ELL III (16 bytes) = 17 bytes total before application data
1101                let (ell, ell_size) = ExtendedLinkLayer::parse(
1102                    data.get(1..)
1103                        .ok_or(ApplicationLayerError::InsufficientData)?,
1104                    extended_link_layer::EllFormat::FormatIII,
1105                )?;
1106                let app_data_offset = 1 + ell_size;
1107
1108                // Create a ShortTplHeader from ELL fields
1109                // For encrypted ELL frames, the ELL fields become the "header"
1110                let short_tpl_header = ShortTplHeader {
1111                    access_number: ell.access_number,
1112                    status: StatusField::from_bits_truncate(ell.communication_control),
1113                    configuration_field: ConfigurationField::from_bytes(0x00, 0x00),
1114                };
1115
1116                Ok(UserDataBlock::VariableDataStructureWithShortTplHeader {
1117                    extended_link_layer: Some(ell),
1118                    short_tpl_header,
1119                    variable_data_block: data
1120                        .get(app_data_offset..)
1121                        .ok_or(ApplicationLayerError::InsufficientData)?,
1122                })
1123            }
1124        }
1125    }
1126}
1127
1128#[allow(clippy::unwrap_used, clippy::panic)]
1129#[cfg(all(test, feature = "std"))]
1130mod tests {
1131
1132    use super::*;
1133
1134    #[test]
1135    fn test_control_information() {
1136        assert_eq!(
1137            ControlInformation::from(0x50),
1138            Ok(ControlInformation::ResetAtApplicationLevel)
1139        );
1140        assert_eq!(
1141            ControlInformation::from(0x51),
1142            Ok(ControlInformation::SendData)
1143        );
1144        assert_eq!(
1145            ControlInformation::from(0x52),
1146            Ok(ControlInformation::SelectSlave)
1147        );
1148        assert_eq!(
1149            ControlInformation::from(0x54),
1150            Ok(ControlInformation::SynchronizeSlave)
1151        );
1152        assert_eq!(
1153            ControlInformation::from(0xB8),
1154            Ok(ControlInformation::SetBaudRate300)
1155        );
1156        assert_eq!(
1157            ControlInformation::from(0xB9),
1158            Ok(ControlInformation::SetBaudRate600)
1159        );
1160        assert_eq!(
1161            ControlInformation::from(0xBA),
1162            Ok(ControlInformation::SetBaudRate1200)
1163        );
1164        assert_eq!(
1165            ControlInformation::from(0xBB),
1166            Ok(ControlInformation::SetBaudRate2400)
1167        );
1168        assert_eq!(
1169            ControlInformation::from(0xBC),
1170            Ok(ControlInformation::SetBaudRate4800)
1171        );
1172        assert_eq!(
1173            ControlInformation::from(0xBD),
1174            Ok(ControlInformation::SetBaudRate9600)
1175        );
1176        assert_eq!(
1177            ControlInformation::from(0xBE),
1178            Ok(ControlInformation::SetBaudRate19200)
1179        );
1180        assert_eq!(
1181            ControlInformation::from(0xBF),
1182            Ok(ControlInformation::SetBaudRate38400)
1183        );
1184        assert_eq!(
1185            ControlInformation::from(0xB1),
1186            Ok(ControlInformation::OutputRAMContent)
1187        );
1188        assert_eq!(
1189            ControlInformation::from(0xB2),
1190            Ok(ControlInformation::WriteRAMContent)
1191        );
1192        assert_eq!(
1193            ControlInformation::from(0xB3),
1194            Ok(ControlInformation::StartCalibrationTestMode)
1195        );
1196        assert_eq!(
1197            ControlInformation::from(0xB4),
1198            Ok(ControlInformation::ReadEEPROM)
1199        );
1200        assert_eq!(
1201            ControlInformation::from(0xB6),
1202            Ok(ControlInformation::StartSoftwareTest)
1203        );
1204        assert_eq!(
1205            ControlInformation::from(0x90),
1206            Ok(ControlInformation::HashProcedure(0,))
1207        );
1208        assert_eq!(
1209            ControlInformation::from(0x91),
1210            Ok(ControlInformation::HashProcedure(1,))
1211        );
1212    }
1213
1214    #[test]
1215    fn test_reset_subcode() {
1216        // Application layer of frame | 68 04 04 68 | 53 FE 50 | 10 | B1 16
1217        let data = [0x50, 0x10];
1218        let result = UserDataBlock::try_from(data.as_slice());
1219        assert_eq!(
1220            result,
1221            Ok(UserDataBlock::ResetAtApplicationLevel {
1222                subcode: ApplicationResetSubcode::All(0x10)
1223            })
1224        );
1225    }
1226
1227    #[test]
1228    fn test_application_layer_no_transport() {
1229        let data = [0x78, 0x0B, 0x13, 0x43, 0x65, 0x87];
1230        let result = UserDataBlock::try_from(data.as_slice());
1231
1232        assert_eq!(
1233            result,
1234            Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1235                extended_link_layer: None,
1236                variable_data_block: &data[1..],
1237            })
1238        );
1239    }
1240
1241    #[test]
1242    fn test_ell_i_with_application_layer_no_transport() {
1243        let data = [0x8C, 0x20, 0x27, 0x78, 0x0B, 0x13, 0x43, 0x65, 0x87];
1244        let result = UserDataBlock::try_from(data.as_slice());
1245
1246        match result {
1247            Ok(UserDataBlock::VariableDataStructureWithoutTplHeader {
1248                extended_link_layer: Some(ell),
1249                variable_data_block,
1250            }) => {
1251                assert_eq!(ell.communication_control, 0x20);
1252                assert_eq!(ell.access_number, 0x27);
1253                assert_eq!(variable_data_block, &data[4..]);
1254            }
1255            other => panic!("expected no-TPL user data after ELL I, got {other:?}"),
1256        }
1257    }
1258
1259    #[test]
1260    fn test_device_type_roundtrip() {
1261        // Test that to_byte is the inverse of from_byte for specific values
1262        let test_cases = [
1263            (0x00, DeviceType::Other),
1264            (0x01, DeviceType::OilMeter),
1265            (0x02, DeviceType::ElectricityMeter),
1266            (0x03, DeviceType::GasMeter),
1267            (0x04, DeviceType::HeatMeterReturn),
1268            (0x05, DeviceType::SteamMeter),
1269            (0x06, DeviceType::WarmWaterMeter),
1270            (0x07, DeviceType::WaterMeter),
1271            (0x08, DeviceType::HeatCostAllocator),
1272            (0x09, DeviceType::CompressedAir),
1273            (0x0A, DeviceType::CoolingMeterReturn),
1274            (0x0B, DeviceType::CoolingMeterFlow),
1275            (0x0C, DeviceType::HeatMeterFlow),
1276            (0x0D, DeviceType::CombinedHeatCoolingMeter),
1277            (0x0E, DeviceType::BusSystemComponent),
1278            (0x0F, DeviceType::UnknownDevice),
1279            (0x10, DeviceType::IrrigationWaterMeter),
1280            (0x11, DeviceType::WaterDataLogger),
1281            (0x12, DeviceType::GasDataLogger),
1282            (0x13, DeviceType::GasConverter),
1283            (0x14, DeviceType::CalorificValue),
1284            (0x15, DeviceType::HotWaterMeter),
1285            (0x16, DeviceType::ColdWaterMeter),
1286            (0x17, DeviceType::DualRegisterWaterMeter),
1287            (0x18, DeviceType::PressureMeter),
1288            (0x19, DeviceType::AdConverter),
1289            (0x1A, DeviceType::SmokeDetector),
1290            (0x1B, DeviceType::RoomSensor),
1291            (0x1C, DeviceType::GasDetector),
1292            (0x20, DeviceType::ElectricityBreaker),
1293            (0x21, DeviceType::Valve),
1294            (0x25, DeviceType::CustomerUnit),
1295            (0x28, DeviceType::WasteWaterMeter),
1296            (0x29, DeviceType::Garbage),
1297            (0x30, DeviceType::ServiceTool),
1298            (0x31, DeviceType::CommunicationController),
1299            (0x32, DeviceType::UnidirectionalRepeater),
1300            (0x33, DeviceType::BidirectionalRepeater),
1301            (0x36, DeviceType::RadioConverterSystemSide),
1302            (0x37, DeviceType::RadioConverterMeterSide),
1303            (0x38, DeviceType::BusConverterMeterSide),
1304            (0xFF, DeviceType::Wildcard),
1305            // Reserved ranges with specific variants
1306            (0x1D, DeviceType::ReservedSensor(0x1D)), // Reserved for sensors
1307            (0x22, DeviceType::ReservedSwitch(0x22)), // Reserved for switching devices
1308            (0x40, DeviceType::Reserved(0x40)),       // Reserved
1309        ];
1310
1311        for (byte, expected_device_type) in test_cases {
1312            let device_type = DeviceType::from(byte);
1313            assert_eq!(device_type, expected_device_type);
1314            assert_eq!(u8::from(device_type), byte);
1315        }
1316
1317        // Test that Reserved variants map back to their byte values
1318        assert_eq!(u8::from(DeviceType::Reserved(0x40)), 0x40);
1319        assert_eq!(u8::from(DeviceType::ReservedSensor(0x1D)), 0x1D);
1320        assert_eq!(u8::from(DeviceType::ReservedSwitch(0x22)), 0x22);
1321
1322        // Test that Unknown maps to canonical value 0x0F
1323        assert_eq!(u8::from(DeviceType::UnknownDevice), 0x0F);
1324    }
1325
1326    #[test]
1327    fn test_identification_number() -> Result<(), ApplicationLayerError> {
1328        let data = [0x78, 0x56, 0x34, 0x12];
1329        let result = IdentificationNumber::from_bcd_hex_digits(data)?;
1330        assert_eq!(result, IdentificationNumber { number: 12345678 });
1331        Ok(())
1332    }
1333
1334    #[test]
1335    fn test_fixed_data_structure() {
1336        let data = [
1337            0x73, 0x78, 0x56, 0x34, 0x12, 0x0A, 0x00, 0xE9, 0x7E, 0x01, 0x00, 0x00, 0x00, 0x35,
1338            0x01, 0x00, 0x00,
1339        ];
1340
1341        let result = UserDataBlock::try_from(data.as_slice());
1342
1343        assert_eq!(
1344            result,
1345            Ok(UserDataBlock::FixedDataStructure {
1346                identification_number: IdentificationNumber { number: 12345678 },
1347                access_number: 0x0A,
1348                status: StatusField::from_bits_truncate(0x00),
1349                device_type_and_unit: 0xE97E,
1350                counter1: Counter { count: 1 },
1351                counter2: Counter { count: 135 },
1352            })
1353        );
1354    }
1355
1356    #[test]
1357    fn test_manufacturer_code() -> Result<(), ApplicationLayerError> {
1358        let code = ManufacturerCode::from_id(0x1ee6)?;
1359        assert_eq!(
1360            code,
1361            ManufacturerCode {
1362                code: ['G', 'W', 'F']
1363            }
1364        );
1365        Ok(())
1366    }
1367
1368    #[test]
1369    fn test_lsb_frame() {
1370        use crate::data_information::DataType;
1371        use wired_mbus_link_layer::WiredFrame;
1372
1373        let lsb_frame: &[u8] = &[
1374            0x68, 0x64, 0x64, 0x68, 0x8, 0x7f, 0x76, 0x9, 0x67, 0x1, 0x6, 0x0, 0x0, 0x51, 0x4,
1375            0x50, 0x0, 0x0, 0x0, 0x2, 0x6c, 0x38, 0x1c, 0xc, 0xf, 0x0, 0x80, 0x87, 0x32, 0x8c,
1376            0x20, 0xf, 0x0, 0x0, 0x0, 0x0, 0xc, 0x14, 0x13, 0x32, 0x82, 0x58, 0xbc, 0x10, 0x15,
1377            0x0, 0x25, 0x81, 0x25, 0x8c, 0x20, 0x13, 0x0, 0x0, 0x0, 0x0, 0x8c, 0x30, 0x13, 0x0,
1378            0x0, 0x1, 0x61, 0x8c, 0x40, 0x13, 0x0, 0x0, 0x16, 0x88, 0xa, 0x3c, 0x1, 0x10, 0xa,
1379            0x2d, 0x0, 0x80, 0xa, 0x5a, 0x7, 0x18, 0xa, 0x5e, 0x6, 0x53, 0xc, 0x22, 0x0, 0x16, 0x7,
1380            0x26, 0x3c, 0x22, 0x0, 0x0, 0x33, 0x81, 0x4, 0x7e, 0x0, 0x0, 0x67, 0xc, 0xc, 0x16,
1381        ];
1382        let non_lsb_frame: &[u8] = &[
1383            0x68, 0xc7, 0xc7, 0x68, 0x8, 0x38, 0x72, 0x56, 0x73, 0x23, 0x72, 0x2d, 0x2c, 0x34, 0x4,
1384            0x87, 0x0, 0x0, 0x0, 0x4, 0xf, 0x7f, 0x1c, 0x1, 0x0, 0x4, 0xff, 0x7, 0x8a, 0xad, 0x8,
1385            0x0, 0x4, 0xff, 0x8, 0x6, 0xfe, 0x5, 0x0, 0x4, 0x14, 0x4e, 0x55, 0xb, 0x0, 0x84, 0x40,
1386            0x14, 0x0, 0x0, 0x0, 0x0, 0x84, 0x80, 0x40, 0x14, 0x0, 0x0, 0x0, 0x0, 0x4, 0x22, 0x76,
1387            0x7f, 0x0, 0x0, 0x34, 0x22, 0x8b, 0x2c, 0x0, 0x0, 0x2, 0x59, 0x61, 0x1b, 0x2, 0x5d,
1388            0x5f, 0x10, 0x2, 0x61, 0x2, 0xb, 0x4, 0x2d, 0x55, 0x0, 0x0, 0x0, 0x14, 0x2d, 0x83, 0x0,
1389            0x0, 0x0, 0x4, 0x3b, 0x6, 0x1, 0x0, 0x0, 0x14, 0x3b, 0xaa, 0x1, 0x0, 0x0, 0x4, 0xff,
1390            0x22, 0x0, 0x0, 0x0, 0x0, 0x4, 0x6d, 0x6, 0x2c, 0x1f, 0x3a, 0x44, 0xf, 0xcf, 0x11, 0x1,
1391            0x0, 0x44, 0xff, 0x7, 0xb, 0x69, 0x8, 0x0, 0x44, 0xff, 0x8, 0x54, 0xd3, 0x5, 0x0, 0x44,
1392            0x14, 0x11, 0xf3, 0xa, 0x0, 0xc4, 0x40, 0x14, 0x0, 0x0, 0x0, 0x0, 0xc4, 0x80, 0x40,
1393            0x14, 0x0, 0x0, 0x0, 0x0, 0x54, 0x2d, 0x3a, 0x0, 0x0, 0x0, 0x54, 0x3b, 0x28, 0x1, 0x0,
1394            0x0, 0x42, 0x6c, 0x1, 0x3a, 0x2, 0xff, 0x1a, 0x1, 0x1a, 0xc, 0x78, 0x56, 0x73, 0x23,
1395            0x72, 0x4, 0xff, 0x16, 0xe6, 0x84, 0x1e, 0x0, 0x4, 0xff, 0x17, 0xc1, 0xd5, 0xb4, 0x0,
1396            0x12, 0x16,
1397        ];
1398
1399        let frames = [
1400            (lsb_frame, 9670106, Some(DataType::Number(808732.0))),
1401            (non_lsb_frame, 72237356, Some(DataType::Number(568714.0))),
1402        ];
1403
1404        for (frame, expected_iden_nr, data_record_value) in frames {
1405            let frame = WiredFrame::try_from(frame).unwrap();
1406
1407            if let WiredFrame::LongFrame {
1408                function: _,
1409                address: _,
1410                data,
1411            } = frame
1412            {
1413                let user_data_block = UserDataBlock::try_from(data).unwrap();
1414                if let UserDataBlock::VariableDataStructureWithLongTplHeader {
1415                    long_tpl_header: fixed_data_header,
1416                    variable_data_block,
1417                    ..
1418                } = user_data_block
1419                {
1420                    assert_eq!(
1421                        fixed_data_header.identification_number.number,
1422                        expected_iden_nr
1423                    );
1424
1425                    let mut data_records =
1426                        DataRecords::from((variable_data_block, &fixed_data_header)).flatten();
1427                    data_records.next().unwrap();
1428                    assert_eq!(data_records.next().unwrap().data.value, data_record_value);
1429                } else {
1430                    panic!("UserDataBlock is not a variable data structure");
1431                }
1432            } else {
1433                panic!("Frame is not a long frame");
1434            }
1435        }
1436    }
1437
1438    #[test]
1439    fn test_manufacturer_specific_data() {
1440        use crate::data_information::DataType;
1441        use wired_mbus_link_layer::WiredFrame;
1442
1443        let manufacturer_specific_data_frame: &[u8] = &[
1444            0x68, 0x55, 0x55, 0x68, 0x8, 0x1e, 0x72, 0x34, 0x35, 0x58, 0x12, 0x92, 0x26, 0x18, 0x4,
1445            0x14, 0x0, 0x0, 0x0, 0xc, 0x78, 0x34, 0x35, 0x58, 0x12, 0x4, 0xe, 0x57, 0x64, 0x3, 0x0,
1446            0xc, 0x14, 0x73, 0x58, 0x44, 0x0, 0xb, 0x2d, 0x6, 0x0, 0x0, 0xb, 0x3b, 0x55, 0x0, 0x0,
1447            0xa, 0x5a, 0x87, 0x6, 0xa, 0x5e, 0x77, 0x5, 0xb, 0x61, 0x1, 0x11, 0x0, 0x4, 0x6d, 0x10,
1448            0x2, 0x4, 0x3c, 0x2, 0x27, 0x79, 0x11, 0x9, 0xfd, 0xe, 0x6, 0x9, 0xfd, 0xf, 0x6, 0x8c,
1449            0xc0, 0x0, 0x15, 0x71, 0x25, 0x0, 0x0, 0xf, 0x0, 0x0, 0x86, 0x16,
1450        ];
1451
1452        let frame = WiredFrame::try_from(manufacturer_specific_data_frame).unwrap();
1453
1454        if let WiredFrame::LongFrame {
1455            function: _,
1456            address: _,
1457            data,
1458        } = frame
1459        {
1460            let user_data_block = UserDataBlock::try_from(data).unwrap();
1461            if let UserDataBlock::VariableDataStructureWithLongTplHeader {
1462                long_tpl_header: fixed_data_header,
1463                variable_data_block,
1464                ..
1465            } = user_data_block
1466            {
1467                let mut data_records: Vec<_> =
1468                    DataRecords::from((variable_data_block, &fixed_data_header))
1469                        .flatten()
1470                        .collect();
1471
1472                assert_eq!(data_records.len(), 14);
1473
1474                assert_eq!(
1475                    data_records.pop().unwrap().data.value,
1476                    Some(DataType::ManufacturerSpecific(&[15, 0, 0]))
1477                );
1478                assert_eq!(
1479                    data_records.pop().unwrap().data.value,
1480                    Some(DataType::Number(2571.0))
1481                );
1482            }
1483        }
1484    }
1485
1486    #[test]
1487    fn real32bit() {
1488        use crate::data_information::DataType;
1489        use crate::value_information::ValueLabel;
1490        use wired_mbus_link_layer::WiredFrame;
1491
1492        let real32bit: &[u8] = &[
1493            0x68, 0xa7, 0xa7, 0x68, 0x8, 0x4d, 0x72, 0x82, 0x4, 0x75, 0x30, 0xee, 0x4d, 0x19, 0x4,
1494            0xc2, 0x0, 0x0, 0x0, 0x4, 0xe, 0x1b, 0xe, 0x0, 0x0, 0x84, 0xa, 0xe, 0x4c, 0x6, 0x0,
1495            0x0, 0x4, 0x13, 0x7, 0x81, 0x0, 0x0, 0x84, 0xa, 0x13, 0x9d, 0x37, 0x0, 0x0, 0xb, 0xfd,
1496            0xf, 0x0, 0x7, 0x1, 0xa, 0xfd, 0xd, 0x0, 0x11, 0x8c, 0x40, 0x79, 0x1, 0x0, 0x0, 0x0,
1497            0x84, 0x40, 0x14, 0x31, 0x5, 0x0, 0x0, 0x84, 0x4a, 0x14, 0xfd, 0x4, 0x0, 0x0, 0x8c,
1498            0x80, 0x40, 0x79, 0x2, 0x0, 0x0, 0x0, 0x84, 0x80, 0x40, 0x14, 0x27, 0x50, 0x0, 0x0,
1499            0x84, 0x8a, 0x40, 0x14, 0x8, 0x31, 0x0, 0x0, 0x5, 0xff, 0x1, 0xdf, 0xa3, 0xb1, 0x3e,
1500            0x5, 0xff, 0x2, 0xa8, 0x59, 0x6b, 0x3f, 0xc, 0x78, 0x82, 0x4, 0x75, 0x30, 0x4, 0x6d,
1501            0x5, 0xb, 0x2f, 0x31, 0x82, 0xa, 0x6c, 0xe1, 0xf1, 0x5, 0x5b, 0x40, 0x7a, 0x63, 0x42,
1502            0x5, 0x5f, 0x80, 0xc3, 0x25, 0x42, 0x5, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x5, 0x2b, 0x0, 0x0,
1503            0x0, 0x0, 0x1, 0xff, 0x2b, 0x0, 0x3, 0x22, 0x17, 0x3b, 0x0, 0x2, 0xff, 0x2c, 0x0, 0x0,
1504            0x1f, 0xa4, 0x16,
1505        ];
1506
1507        let frame = WiredFrame::try_from(real32bit).unwrap();
1508
1509        if let WiredFrame::LongFrame {
1510            function: _,
1511            address: _,
1512            data,
1513        } = frame
1514        {
1515            let user_data_block = UserDataBlock::try_from(data).unwrap();
1516            if let UserDataBlock::VariableDataStructureWithLongTplHeader {
1517                long_tpl_header: fixed_data_header,
1518                variable_data_block,
1519                ..
1520            } = user_data_block
1521            {
1522                let data_records: Vec<DataRecord> =
1523                    DataRecords::from((variable_data_block, &fixed_data_header))
1524                        .flatten()
1525                        .collect();
1526
1527                assert_eq!(data_records.len(), 24);
1528
1529                for data_record in data_records {
1530                    let labels = data_record
1531                        .data_record_header
1532                        .processed_data_record_header
1533                        .value_information
1534                        .as_ref()
1535                        .unwrap()
1536                        .labels
1537                        .clone();
1538                    if labels.contains(&ValueLabel::ReturnTemperature) {
1539                        assert_eq!(
1540                            data_record.data.value,
1541                            Some(DataType::Number(41.44091796875))
1542                        );
1543                    }
1544                    if labels.contains(&ValueLabel::FlowTemperature) {
1545                        assert_eq!(
1546                            data_record.data.value,
1547                            Some(DataType::Number(56.869384765625))
1548                        );
1549                    }
1550                }
1551            }
1552        }
1553    }
1554
1555    #[test]
1556    fn global_readout_request_does_not_consume_following_records() {
1557        // 0x7F followed by a valid 8-bit integer record: DIF=0x01, VIF=0x13, data=0x05
1558        let data: &[u8] = &[0x7F, 0x01, 0x13, 0x05];
1559        let records: Vec<_> = DataRecords::new(data, None).flatten().collect();
1560        assert_eq!(records.len(), 2);
1561    }
1562}