Skip to main content

m_bus_core/
lib.rs

1pub mod decryption;
2
3#[cfg(feature = "std")]
4use std::fmt::{self, Display};
5
6#[cfg(not(feature = "std"))]
7use core::fmt;
8
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Debug, Clone, Copy, PartialEq)]
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12pub struct ManufacturerCode {
13    pub code: [char; 3],
14}
15
16impl ManufacturerCode {
17    pub const fn from_id(id: u16) -> Result<Self, ApplicationLayerError> {
18        let first_letter = ((id / (32 * 32)) + 64) as u8 as char;
19        let second_letter = (((id % (32 * 32)) / 32) + 64) as u8 as char;
20        let third_letter = ((id % 32) + 64) as u8 as char;
21
22        if first_letter.is_ascii_uppercase()
23            && second_letter.is_ascii_uppercase()
24            && third_letter.is_ascii_uppercase()
25        {
26            Ok(Self {
27                code: [first_letter, second_letter, third_letter],
28            })
29        } else {
30            Err(ApplicationLayerError::InvalidManufacturerCode { code: id })
31        }
32    }
33
34    #[must_use]
35    pub const fn to_id(&self) -> u16 {
36        (self.code[0] as u16 - 64) * 32 * 32
37            + (self.code[1] as u16 - 64) * 32
38            + (self.code[2] as u16 - 64)
39    }
40}
41
42#[cfg(feature = "std")]
43impl fmt::Display for ManufacturerCode {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}{}{}", self.code[0], self.code[1], self.code[2])
46    }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(feature = "defmt", derive(defmt::Format))]
52#[non_exhaustive]
53pub enum ApplicationLayerError {
54    MissingControlInformation,
55    InvalidControlInformation { byte: u8 },
56    IdentificationNumberError { digits: [u8; 4], number: u32 },
57    InvalidManufacturerCode { code: u16 },
58    InsufficientData,
59    Unimplemented { feature: &'static str },
60}
61
62#[cfg(feature = "std")]
63impl fmt::Display for ApplicationLayerError {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            ApplicationLayerError::MissingControlInformation => {
67                write!(f, "Missing control information")
68            }
69            ApplicationLayerError::InvalidControlInformation { byte } => {
70                write!(f, "Invalid control information: {}", byte)
71            }
72            ApplicationLayerError::InvalidManufacturerCode { code } => {
73                write!(f, "Invalid manufacturer code: {}", code)
74            }
75            ApplicationLayerError::IdentificationNumberError { digits, number } => {
76                write!(
77                    f,
78                    "Invalid identification number: {:?}, number: {}",
79                    digits, number
80                )
81            }
82            ApplicationLayerError::InsufficientData => {
83                write!(f, "Insufficient data")
84            }
85            ApplicationLayerError::Unimplemented { feature } => {
86                write!(f, "Unimplemented feature: {}", feature)
87            }
88        }
89    }
90}
91
92#[cfg(feature = "std")]
93impl std::error::Error for ApplicationLayerError {}
94
95pub fn bcd_hex_digits_to_u32(digits: [u8; 4]) -> Result<u32, ApplicationLayerError> {
96    let mut number = 0u32;
97
98    for &digit in digits.iter().rev() {
99        let lower = digit & 0x0F;
100        let upper = digit >> 4;
101        if lower > 9 || upper > 9 {
102            return Err(ApplicationLayerError::IdentificationNumberError { digits, number });
103        }
104        number = number * 100 + (u32::from(upper) * 10) + u32::from(lower);
105    }
106
107    Ok(number)
108}
109
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111#[derive(Debug, Clone, Copy, PartialEq)]
112#[cfg_attr(feature = "defmt", derive(defmt::Format))]
113pub struct IdentificationNumber {
114    pub number: u32,
115}
116
117impl core::fmt::Display for IdentificationNumber {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "{:08}", self.number)
120    }
121}
122
123/// This used to be called "Medium"
124/// Defined in EN 13757-7
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126#[cfg_attr(feature = "defmt", derive(defmt::Format))]
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum DeviceType {
129    Other,
130    OilMeter,
131    ElectricityMeter,
132    GasMeter,
133    HeatMeterReturn,
134    SteamMeter,
135    WarmWaterMeter,
136    WaterMeter,
137    HeatCostAllocator,
138    CompressedAir,
139    CoolingMeterReturn,
140    CoolingMeterFlow,
141    HeatMeterFlow,
142    CombinedHeatCoolingMeter,
143    BusSystemComponent,
144    UnknownDevice,
145    IrrigationWaterMeter,
146    WaterDataLogger,
147    GasDataLogger,
148    GasConverter,
149    CalorificValue,
150    HotWaterMeter,
151    ColdWaterMeter,
152    DualRegisterWaterMeter,
153    PressureMeter,
154    AdConverter,
155    SmokeDetector,
156    RoomSensor,
157    GasDetector,
158    ReservedSensor(u8),
159    ElectricityBreaker,
160    Valve,
161    ReservedSwitch(u8),
162    CustomerUnit,
163    ReservedCustomer(u8),
164    WasteWaterMeter,
165    Garbage,
166    ReservedCO2,
167    ReservedEnvironmental(u8),
168    ServiceTool,
169    CommunicationController,
170    UnidirectionalRepeater,
171    BidirectionalRepeater,
172    ReservedSystem(u8),
173    RadioConverterSystemSide,
174    RadioConverterMeterSide,
175    BusConverterMeterSide,
176    Reserved(u8),
177    Wildcard,
178}
179
180#[cfg(feature = "std")]
181impl Display for DeviceType {
182    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
183        match self {
184            DeviceType::Other => write!(f, "Other"),
185            DeviceType::OilMeter => write!(f, "Oil Meter"),
186            DeviceType::ElectricityMeter => write!(f, "Electricity Meter"),
187            DeviceType::GasMeter => write!(f, "Gas Meter"),
188            DeviceType::HeatMeterReturn => write!(f, "Heat Meter (Return)"),
189            DeviceType::SteamMeter => write!(f, "Steam Meter"),
190            DeviceType::WarmWaterMeter => write!(f, "Warm Water Meter (30-90°C)"),
191            DeviceType::WaterMeter => write!(f, "Water Meter"),
192            DeviceType::HeatCostAllocator => write!(f, "Heat Cost Allocator"),
193            DeviceType::CompressedAir => write!(f, "Compressed Air"),
194            DeviceType::CoolingMeterReturn => write!(f, "Cooling Meter (Return)"),
195            DeviceType::CoolingMeterFlow => write!(f, "Cooling Meter (Flow)"),
196            DeviceType::HeatMeterFlow => write!(f, "Heat Meter (Flow)"),
197            DeviceType::CombinedHeatCoolingMeter => write!(f, "Combined Heat/Cooling Meter"),
198            DeviceType::BusSystemComponent => write!(f, "Bus/System Component"),
199            DeviceType::UnknownDevice => write!(f, "Unknown Device"),
200            DeviceType::IrrigationWaterMeter => write!(f, "Irrigation Water Meter"),
201            DeviceType::WaterDataLogger => write!(f, "Water Data Logger"),
202            DeviceType::GasDataLogger => write!(f, "Gas Data Logger"),
203            DeviceType::GasConverter => write!(f, "Gas Converter"),
204            DeviceType::CalorificValue => write!(f, "Calorific Value"),
205            DeviceType::HotWaterMeter => write!(f, "Hot Water Meter (≥90°C)"),
206            DeviceType::ColdWaterMeter => write!(f, "Cold Water Meter"),
207            DeviceType::DualRegisterWaterMeter => write!(f, "Dual Register Water Meter"),
208            DeviceType::PressureMeter => write!(f, "Pressure Meter"),
209            DeviceType::AdConverter => write!(f, "A/D Converter"),
210            DeviceType::SmokeDetector => write!(f, "Smoke Detector"),
211            DeviceType::RoomSensor => write!(f, "Room Sensor"),
212            DeviceType::GasDetector => write!(f, "Gas Detector"),
213            DeviceType::ReservedSensor(code) => write!(f, "Reserved Sensor (0x{:02X})", code),
214            DeviceType::ElectricityBreaker => write!(f, "Breaker (Electricity)"),
215            DeviceType::Valve => write!(f, "Valve (Gas/Water)"),
216            DeviceType::ReservedSwitch(code) => write!(f, "Reserved Switch (0x{:02X})", code),
217            DeviceType::CustomerUnit => write!(f, "Customer Unit (Display)"),
218            DeviceType::ReservedCustomer(code) => {
219                write!(f, "Reserved Customer Unit (0x{:02X})", code)
220            }
221            DeviceType::WasteWaterMeter => write!(f, "Waste Water Meter"),
222            DeviceType::Garbage => write!(f, "Garbage"),
223            DeviceType::ReservedCO2 => write!(f, "Reserved (CO₂)"),
224            DeviceType::ReservedEnvironmental(code) => {
225                write!(f, "Reserved Environmental (0x{:02X})", code)
226            }
227            DeviceType::ServiceTool => write!(f, "Service Tool"),
228            DeviceType::CommunicationController => write!(f, "Communication Controller (Gateway)"),
229            DeviceType::UnidirectionalRepeater => write!(f, "Unidirectional Repeater"),
230            DeviceType::BidirectionalRepeater => write!(f, "Bidirectional Repeater"),
231            DeviceType::ReservedSystem(code) => write!(f, "Reserved System (0x{:02X})", code),
232            DeviceType::RadioConverterSystemSide => write!(f, "Radio Converter (System Side)"),
233            DeviceType::RadioConverterMeterSide => write!(f, "Radio Converter (Meter Side)"),
234            DeviceType::BusConverterMeterSide => write!(f, "Bus Converter (Meter Side)"),
235            DeviceType::Reserved(code) => write!(f, "Reserved (0x{:02X})", code),
236            DeviceType::Wildcard => write!(f, "Wildcard"),
237        }
238    }
239}
240
241impl From<DeviceType> for u8 {
242    fn from(value: DeviceType) -> Self {
243        match value {
244            DeviceType::Other => 0x00,
245            DeviceType::OilMeter => 0x01,
246            DeviceType::ElectricityMeter => 0x02,
247            DeviceType::GasMeter => 0x03,
248            DeviceType::HeatMeterReturn => 0x04,
249            DeviceType::SteamMeter => 0x05,
250            DeviceType::WarmWaterMeter => 0x06,
251            DeviceType::WaterMeter => 0x07,
252            DeviceType::HeatCostAllocator => 0x08,
253            DeviceType::CompressedAir => 0x09,
254            DeviceType::CoolingMeterReturn => 0x0A,
255            DeviceType::CoolingMeterFlow => 0x0B,
256            DeviceType::HeatMeterFlow => 0x0C,
257            DeviceType::CombinedHeatCoolingMeter => 0x0D,
258            DeviceType::BusSystemComponent => 0x0E,
259            DeviceType::UnknownDevice => 0x0F,
260            DeviceType::IrrigationWaterMeter => 0x10,
261            DeviceType::WaterDataLogger => 0x11,
262            DeviceType::GasDataLogger => 0x12,
263            DeviceType::GasConverter => 0x13,
264            DeviceType::CalorificValue => 0x14,
265            DeviceType::HotWaterMeter => 0x15,
266            DeviceType::ColdWaterMeter => 0x16,
267            DeviceType::DualRegisterWaterMeter => 0x17,
268            DeviceType::PressureMeter => 0x18,
269            DeviceType::AdConverter => 0x19,
270            DeviceType::SmokeDetector => 0x1A,
271            DeviceType::RoomSensor => 0x1B,
272            DeviceType::GasDetector => 0x1C,
273            DeviceType::ReservedSensor(code) => code,
274            DeviceType::ElectricityBreaker => 0x20,
275            DeviceType::Valve => 0x21,
276            DeviceType::ReservedSwitch(code) => code,
277            DeviceType::CustomerUnit => 0x25,
278            DeviceType::ReservedCustomer(code) => code,
279            DeviceType::WasteWaterMeter => 0x28,
280            DeviceType::Garbage => 0x29,
281            DeviceType::ReservedCO2 => 0x2A,
282            DeviceType::ReservedEnvironmental(code) => code,
283            DeviceType::ServiceTool => 0x30,
284            DeviceType::CommunicationController => 0x31,
285            DeviceType::UnidirectionalRepeater => 0x32,
286            DeviceType::BidirectionalRepeater => 0x33,
287            DeviceType::ReservedSystem(code) => code,
288            DeviceType::RadioConverterSystemSide => 0x36,
289            DeviceType::RadioConverterMeterSide => 0x37,
290            DeviceType::BusConverterMeterSide => 0x38,
291            DeviceType::Reserved(code) => code,
292            DeviceType::Wildcard => 0xFF,
293        }
294    }
295}
296impl From<u8> for DeviceType {
297    fn from(value: u8) -> Self {
298        match value {
299            0x00 => DeviceType::Other,
300            0x01 => DeviceType::OilMeter,
301            0x02 => DeviceType::ElectricityMeter,
302            0x03 => DeviceType::GasMeter,
303            0x04 => DeviceType::HeatMeterReturn,
304            0x05 => DeviceType::SteamMeter,
305            0x06 => DeviceType::WarmWaterMeter,
306            0x07 => DeviceType::WaterMeter,
307            0x08 => DeviceType::HeatCostAllocator,
308            0x09 => DeviceType::CompressedAir,
309            0x0A => DeviceType::CoolingMeterReturn,
310            0x0B => DeviceType::CoolingMeterFlow,
311            0x0C => DeviceType::HeatMeterFlow,
312            0x0D => DeviceType::CombinedHeatCoolingMeter,
313            0x0E => DeviceType::BusSystemComponent,
314            0x0F => DeviceType::UnknownDevice,
315            0x10 => DeviceType::IrrigationWaterMeter,
316            0x11 => DeviceType::WaterDataLogger,
317            0x12 => DeviceType::GasDataLogger,
318            0x13 => DeviceType::GasConverter,
319            0x14 => DeviceType::CalorificValue,
320            0x15 => DeviceType::HotWaterMeter,
321            0x16 => DeviceType::ColdWaterMeter,
322            0x17 => DeviceType::DualRegisterWaterMeter,
323            0x18 => DeviceType::PressureMeter,
324            0x19 => DeviceType::AdConverter,
325            0x1A => DeviceType::SmokeDetector,
326            0x1B => DeviceType::RoomSensor,
327            0x1C => DeviceType::GasDetector,
328            0x1D..=0x1F => DeviceType::ReservedSensor(value),
329            0x20 => DeviceType::ElectricityBreaker,
330            0x21 => DeviceType::Valve,
331            0x22..=0x24 => DeviceType::ReservedSwitch(value),
332            0x25 => DeviceType::CustomerUnit,
333            0x26..=0x27 => DeviceType::ReservedCustomer(value),
334            0x28 => DeviceType::WasteWaterMeter,
335            0x29 => DeviceType::Garbage,
336            0x2A => DeviceType::ReservedCO2,
337            0x2B..=0x2F => DeviceType::ReservedEnvironmental(value),
338            0x30 => DeviceType::ServiceTool,
339            0x31 => DeviceType::CommunicationController,
340            0x32 => DeviceType::UnidirectionalRepeater,
341            0x33 => DeviceType::BidirectionalRepeater,
342            0x34..=0x35 => DeviceType::ReservedSystem(value),
343            0x36 => DeviceType::RadioConverterSystemSide,
344            0x37 => DeviceType::RadioConverterMeterSide,
345            0x38 => DeviceType::BusConverterMeterSide,
346            0x39..=0x3F => DeviceType::ReservedSystem(value),
347            0x40..=0xFE => DeviceType::Reserved(value),
348            0xFF => DeviceType::Wildcard,
349        }
350    }
351}
352impl From<IdentificationNumber> for u32 {
353    fn from(id: IdentificationNumber) -> Self {
354        id.number
355    }
356}
357
358impl IdentificationNumber {
359    pub fn from_bcd_hex_digits(digits: [u8; 4]) -> Result<Self, ApplicationLayerError> {
360        let number = bcd_hex_digits_to_u32(digits)?;
361        Ok(Self { number })
362    }
363}
364
365#[cfg(test)]
366mod test {
367    use super::*;
368
369    #[test]
370    fn test_manufacturer_code() -> Result<(), ApplicationLayerError> {
371        let code = ManufacturerCode::from_id(0x1ee6)?;
372        assert_eq!(
373            code,
374            ManufacturerCode {
375                code: ['G', 'W', 'F']
376            }
377        );
378        Ok(())
379    }
380
381    #[test]
382    fn test_manufacturer_code_roundtrip() -> Result<(), ApplicationLayerError> {
383        // Test that to_id is the inverse of from_id
384        let original_id = 0x1ee6;
385        let code = ManufacturerCode::from_id(original_id)?;
386        let converted_id = code.to_id();
387        assert_eq!(original_id, converted_id);
388
389        // Test a few more cases
390        for id in [0x0000, 0x0421, 0x1234, 0x7FFF] {
391            if let Ok(code) = ManufacturerCode::from_id(id) {
392                assert_eq!(id, code.to_id());
393            }
394        }
395        Ok(())
396    }
397
398    #[test]
399    fn test_identification_number() -> Result<(), ApplicationLayerError> {
400        let data = [0x78, 0x56, 0x34, 0x12];
401        let result = IdentificationNumber::from_bcd_hex_digits(data)?;
402        assert_eq!(result, IdentificationNumber { number: 12345678 });
403        Ok(())
404    }
405
406    #[test]
407    fn test_configuration_field_debug() {
408        // Test raw value 1360 (0x0550) - corresponds to mode 5 (AES-CBC-128; IV ≠ 0)
409        let cf = ConfigurationField::from(1360);
410        let debug_output = format!("{:?}", cf);
411        assert_eq!(
412            debug_output,
413            "ConfigurationField { mode: AesCbc128IvNonZero }"
414        );
415
416        // Test mode 0 (No encryption)
417        let cf_no_enc = ConfigurationField::from(0);
418        let debug_output = format!("{:?}", cf_no_enc);
419        assert_eq!(debug_output, "ConfigurationField { mode: NoEncryption }");
420    }
421}
422
423#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
424#[derive(Debug, Clone, Copy, PartialEq)]
425#[cfg_attr(feature = "defmt", derive(defmt::Format))]
426#[non_exhaustive]
427pub enum Function {
428    SndNk { prm: bool },
429    SndUd { fcb: bool },
430    SndUd2,
431    SndUd3,
432    SndNr,
433    SendIr,
434    AccNr,
435    AccDmd,
436    ReqUd1 { fcb: bool },
437    ReqUd2 { fcb: bool },
438    RspUd { acd: bool, dfc: bool },
439    Ack,
440    Nack,
441    CnfIr,
442}
443
444#[cfg(feature = "std")]
445impl std::fmt::Display for Function {
446    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447        match self {
448            Function::SndNk { prm: _prm } => write!(f, "SndNk"),
449            Function::SndUd { fcb } => write!(f, "SndUd (FCB: {fcb})"),
450            Function::ReqUd2 { fcb } => write!(f, "ReqUd2 (FCB: {fcb})"),
451            Function::ReqUd1 { fcb } => write!(f, "ReqUd1 (FCB: {fcb})"),
452            Function::RspUd { acd, dfc } => write!(f, "RspUd (ACD: {acd}, DFC: {dfc})"),
453            _ => write!(f, "{:?}", self),
454        }
455    }
456}
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
460#[cfg_attr(feature = "defmt", derive(defmt::Format))]
461pub enum FrameError {
462    EmptyData,
463    InvalidStartByte,
464    InvalidStopByte,
465    WrongLengthIndication,
466    LengthShort,
467    LengthShorterThanSix { length: usize },
468    WrongLength { expected: usize, actual: usize },
469    WrongCrc { expected: u16, actual: u16 },
470    WrongChecksum { expected: u8, actual: u8 },
471    InvalidControlInformation { byte: u8 },
472    InvalidFunction { byte: u8 },
473}
474
475#[cfg(feature = "std")]
476impl std::error::Error for FrameError {}
477
478#[cfg(feature = "std")]
479impl std::fmt::Display for FrameError {
480    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        match self {
482            FrameError::EmptyData => write!(f, "Data is empty"),
483            FrameError::InvalidStartByte => write!(f, "Invalid start byte"),
484            FrameError::InvalidStopByte => write!(f, "Invalid stop byte"),
485            FrameError::LengthShort => write!(f, "Length mismatch"),
486            FrameError::LengthShorterThanSix { length } => {
487                write!(f, "Length is shorter than six: {}", length)
488            }
489            FrameError::WrongChecksum { expected, actual } => write!(
490                f,
491                "Wrong checksum, expected: {}, actual: {}",
492                expected, actual
493            ),
494            FrameError::InvalidControlInformation { byte } => {
495                write!(f, "Invalid control information: {}", byte)
496            }
497            FrameError::InvalidFunction { byte } => write!(f, "Invalid function: {}", byte),
498            FrameError::WrongLengthIndication => write!(f, "Wrong length indication"),
499            FrameError::WrongLength { expected, actual } => write!(
500                f,
501                "Wrong length, expected: {}, actual: {}",
502                expected, actual
503            ),
504            FrameError::WrongCrc { expected, actual } => {
505                write!(f, "Wrong CRC, expected: {}, actual: {}", expected, actual)
506            }
507        }
508    }
509}
510impl TryFrom<u8> for Function {
511    type Error = FrameError;
512
513    fn try_from(byte: u8) -> Result<Self, Self::Error> {
514        match byte {
515            0x40 => Ok(Self::SndNk { prm: false }),
516            0x44 => Ok(Self::SndNr),
517            0x53 => Ok(Self::SndUd { fcb: false }),
518            0x73 => Ok(Self::SndUd { fcb: true }),
519            0x5B => Ok(Self::ReqUd2 { fcb: false }),
520            0x7B => Ok(Self::ReqUd2 { fcb: true }),
521            0x5A => Ok(Self::ReqUd1 { fcb: false }),
522            0x7A => Ok(Self::ReqUd1 { fcb: true }),
523            0x08 => Ok(Self::RspUd {
524                acd: false,
525                dfc: false,
526            }),
527            0x18 => Ok(Self::RspUd {
528                acd: false,
529                dfc: true,
530            }),
531            0x28 => Ok(Self::RspUd {
532                acd: true,
533                dfc: false,
534            }),
535            0x38 => Ok(Self::RspUd {
536                acd: true,
537                dfc: true,
538            }),
539            _ => Err(FrameError::InvalidFunction { byte }),
540        }
541    }
542}
543
544/// Security Mode as defined in EN 13757-7:2018 Table 19
545///
546/// The Security mode defines the applied set of security mechanisms
547/// and is encoded in bits 12-8 (5 bits) of the Configuration Field.
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
549#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
550#[cfg_attr(feature = "defmt", derive(defmt::Format))]
551#[non_exhaustive]
552pub enum SecurityMode {
553    NoEncryption,
554    ManufacturerSpecific,
555    DesIvZero,
556    DesIvNonZero,
557    SpecificUsage4,
558    AesCbc128IvNonZero,
559    Reserved6,
560    AesCbc128IvZero,
561    AesCtr128Cmac,
562    AesGcm128,
563    AesCcm128,
564    Reserved11,
565    Reserved12,
566    SpecificUsage13,
567    Reserved14,
568    SpecificUsage15,
569    ReservedHigher(u8),
570}
571
572impl SecurityMode {
573    /// Create SecurityMode from 5-bit value (bits 12-8)
574    pub const fn from_bits(mode: u8) -> Self {
575        match mode & 0b0001_1111 {
576            0 => Self::NoEncryption,
577            1 => Self::ManufacturerSpecific,
578            2 => Self::DesIvZero,
579            3 => Self::DesIvNonZero,
580            4 => Self::SpecificUsage4,
581            5 => Self::AesCbc128IvNonZero,
582            6 => Self::Reserved6,
583            7 => Self::AesCbc128IvZero,
584            8 => Self::AesCtr128Cmac,
585            9 => Self::AesGcm128,
586            10 => Self::AesCcm128,
587            11 => Self::Reserved11,
588            12 => Self::Reserved12,
589            13 => Self::SpecificUsage13,
590            14 => Self::Reserved14,
591            15 => Self::SpecificUsage15,
592            other => Self::ReservedHigher(other),
593        }
594    }
595
596    /// Get the 5-bit mode value
597    pub const fn to_bits(&self) -> u8 {
598        match self {
599            Self::NoEncryption => 0,
600            Self::ManufacturerSpecific => 1,
601            Self::DesIvZero => 2,
602            Self::DesIvNonZero => 3,
603            Self::SpecificUsage4 => 4,
604            Self::AesCbc128IvNonZero => 5,
605            Self::Reserved6 => 6,
606            Self::AesCbc128IvZero => 7,
607            Self::AesCtr128Cmac => 8,
608            Self::AesGcm128 => 9,
609            Self::AesCcm128 => 10,
610            Self::Reserved11 => 11,
611            Self::Reserved12 => 12,
612            Self::SpecificUsage13 => 13,
613            Self::Reserved14 => 14,
614            Self::SpecificUsage15 => 15,
615            Self::ReservedHigher(mode) => *mode & 0b0001_1111,
616        }
617    }
618}
619
620#[cfg(feature = "std")]
621impl fmt::Display for SecurityMode {
622    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
623        match self {
624            Self::NoEncryption => write!(f, "No encryption used"),
625            Self::ManufacturerSpecific => write!(f, "Manufacturer specific usage"),
626            Self::DesIvZero => write!(f, "DES; IV = 0 (deprecated)"),
627            Self::DesIvNonZero => write!(f, "DES; IV ≠ 0 (deprecated)"),
628            Self::SpecificUsage4 => write!(f, "Specific usage (Bibliographical Entry [6])"),
629            Self::AesCbc128IvNonZero => write!(f, "AES-CBC-128; IV ≠ 0"),
630            Self::Reserved6 => write!(f, "Reserved for future use"),
631            Self::AesCbc128IvZero => write!(f, "AES-CBC-128; IV = 0"),
632            Self::AesCtr128Cmac => write!(f, "AES-CTR-128; CMAC"),
633            Self::AesGcm128 => write!(f, "AES-GCM-128"),
634            Self::AesCcm128 => write!(f, "AES-CCM-128"),
635            Self::Reserved11 => write!(f, "Reserved for future use"),
636            Self::Reserved12 => write!(f, "Reserved for future use"),
637            Self::SpecificUsage13 => write!(f, "Specific usage (Bibliographical Entry [8])"),
638            Self::Reserved14 => write!(f, "Reserved for future use"),
639            Self::SpecificUsage15 => write!(f, "Specific usage (Bibliographical Entry [7])"),
640            Self::ReservedHigher(mode) => write!(f, "Reserved for future use (mode {})", mode),
641        }
642    }
643}
644
645/// Configuration Field (CF) - EN 13757-7:2018 Clause 7.5.8, Table 18
646///
647/// The configuration field consists of two bytes containing information about the applied
648/// Security mode. The Security mode defines:
649/// - applied set of security mechanisms
650/// - content of other bits in the configuration field
651/// - presence, length and content of configuration field extension (CFE)
652/// - number, length and content of optional TPL-header/trailer fields
653///
654/// # Bit Layout (Table 18)
655/// - Bits 15-13: Security mode specific (X)
656/// - Bits 12-8: Security mode bits (M) - 5 bits defining the security mode
657/// - Bits 7-0: Security mode specific (X)
658///
659/// The decoding of bits marked "X" depends on the selected Security mode.
660#[derive(Clone, Copy, PartialEq, Eq, Hash)]
661#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
662#[cfg_attr(feature = "defmt", derive(defmt::Format))]
663pub struct ConfigurationField {
664    raw: u16,
665}
666
667impl ConfigurationField {
668    /// Create a Configuration Field from two bytes
669    ///
670    /// # Arguments
671    /// * `lsb` - Lower byte (bits 7-0)
672    /// * `msb` - Upper byte (bits 15-8)
673    pub const fn from_bytes(lsb: u8, msb: u8) -> Self {
674        Self {
675            raw: u16::from_le_bytes([lsb, msb]),
676        }
677    }
678
679    /// Get the raw 16-bit value
680    pub const fn raw(&self) -> u16 {
681        self.raw
682    }
683
684    /// Get the Security mode (bits 12-8, 5 bits)
685    ///
686    /// The Security mode defines the applied set of security mechanisms.
687    /// See EN 13757-7:2018 Table 19 for Security mode definitions.
688    pub const fn security_mode(&self) -> SecurityMode {
689        let mode_bits = ((self.raw >> 8) & 0b0001_1111) as u8;
690        SecurityMode::from_bits(mode_bits)
691    }
692
693    /// Get the lower mode-specific byte (bits 7-0)
694    ///
695    /// The meaning of these bits depends on the selected Security mode.
696    pub const fn mode_specific_lower(&self) -> u8 {
697        (self.raw & 0xFF) as u8
698    }
699
700    /// Get the upper mode-specific bits (bits 15-13)
701    ///
702    /// The meaning of these bits depends on the selected Security mode.
703    pub const fn mode_specific_upper(&self) -> u8 {
704        ((self.raw >> 13) & 0b0000_0111) as u8
705    }
706}
707
708impl From<u16> for ConfigurationField {
709    fn from(value: u16) -> Self {
710        Self { raw: value }
711    }
712}
713
714impl From<ConfigurationField> for u16 {
715    fn from(cf: ConfigurationField) -> Self {
716        cf.raw
717    }
718}
719
720#[cfg(feature = "std")]
721impl fmt::Display for ConfigurationField {
722    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
723        write!(
724            f,
725            "Configuration Field: 0x{:04X} (Security mode: {})",
726            self.raw,
727            self.security_mode()
728        )
729    }
730}
731
732impl fmt::Debug for ConfigurationField {
733    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734        f.debug_struct("ConfigurationField")
735            .field("mode", &self.security_mode())
736            .finish()
737    }
738}