Skip to main content

m_bus_core/
lib.rs

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