Skip to main content

m_bus_core/
lib.rs

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