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