Skip to main content

osdp/reply/
pdid.rs

1//! `osdp_PDID` (`0x45`) — peripheral device identification.
2//!
3//! # Spec: §7.4
4//!
5//! Body is exactly 12 bytes:
6//!
7//! ```text
8//! +-----+-----+-----+-------+--------+----------+----------+----------+----------+----------+----------+
9//! |VC1  |VC2  |VC3  |Model# |Version |Serial#0  |Serial#1  |Serial#2  |Serial#3  |FW major  |FW minor  |FW build|
10//! +-----+-----+-----+-------+--------+----------+----------+----------+----------+----------+----------+
11//!  byte0 byte1 byte2 byte3   byte4    byte5..8 (LE u32)                          byte9..11
12//! ```
13
14use crate::error::Error;
15use alloc::vec::Vec;
16
17/// `osdp_PDID` body.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct PdId {
20    /// IEEE-assigned OUI.
21    pub vendor_oui: [u8; 3],
22    /// Model number.
23    pub model: u8,
24    /// Hardware/firmware family version.
25    pub version: u8,
26    /// 32-bit serial number.
27    pub serial: u32,
28    /// Firmware revision: `[major, minor, build]`.
29    pub firmware: [u8; 3],
30}
31
32impl PdId {
33    /// Wire size.
34    pub const WIRE_LEN: usize = 12;
35
36    /// Encode.
37    pub fn encode(&self) -> Result<Vec<u8>, Error> {
38        let mut out = Vec::with_capacity(Self::WIRE_LEN);
39        out.extend_from_slice(&self.vendor_oui);
40        out.push(self.model);
41        out.push(self.version);
42        out.extend_from_slice(&self.serial.to_le_bytes());
43        out.extend_from_slice(&self.firmware);
44        Ok(out)
45    }
46
47    /// Decode.
48    pub fn decode(data: &[u8]) -> Result<Self, Error> {
49        if data.len() != Self::WIRE_LEN {
50            return Err(Error::MalformedPayload {
51                code: 0x45,
52                reason: "PDID requires 12 bytes",
53            });
54        }
55        let mut vendor_oui = [0u8; 3];
56        vendor_oui.copy_from_slice(&data[..3]);
57        let mut firmware = [0u8; 3];
58        firmware.copy_from_slice(&data[9..12]);
59        Ok(Self {
60            vendor_oui,
61            model: data[3],
62            version: data[4],
63            serial: u32::from_le_bytes([data[5], data[6], data[7], data[8]]),
64            firmware,
65        })
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn roundtrip() {
75        let id = PdId {
76            vendor_oui: [0x00, 0x06, 0x8E], // HID's OUI
77            model: 0x12,
78            version: 0x34,
79            serial: 0xDEAD_BEEF,
80            firmware: [1, 2, 3],
81        };
82        let bytes = id.encode().unwrap();
83        assert_eq!(bytes.len(), 12);
84        let parsed = PdId::decode(&bytes).unwrap();
85        assert_eq!(parsed, id);
86    }
87
88    #[test]
89    fn rejects_truncated() {
90        assert!(PdId::decode(&[0; 11]).is_err());
91        assert!(PdId::decode(&[0; 13]).is_err());
92    }
93}