Skip to main content

osdp/reply/
mfg.rs

1//! Manufacturer-specific replies.
2//!
3//! # Spec: §7.23–§7.25
4//!
5//! - `osdp_MFGSTATR` (`0x83`) — vendor status (OUI + payload).
6//! - `osdp_MFGERRR` (`0x84`) — vendor error (OUI + error code + payload).
7//! - `osdp_MFGREP` (`0x90`) — vendor reply (OUI + payload).
8
9use crate::error::Error;
10use crate::payload_util::require_at_least;
11use alloc::vec::Vec;
12
13macro_rules! oui_plus_payload {
14    ($Ty:ident, $code:expr, $doc:literal) => {
15        #[doc = $doc]
16        #[derive(Debug, Clone, PartialEq, Eq)]
17        pub struct $Ty {
18            /// IEEE-assigned OUI.
19            pub oui: [u8; 3],
20            /// Vendor payload.
21            pub payload: Vec<u8>,
22        }
23
24        impl $Ty {
25            /// Encode.
26            pub fn encode(&self) -> Result<Vec<u8>, Error> {
27                let mut out = Vec::with_capacity(3 + self.payload.len());
28                out.extend_from_slice(&self.oui);
29                out.extend_from_slice(&self.payload);
30                Ok(out)
31            }
32
33            /// Decode.
34            pub fn decode(data: &[u8]) -> Result<Self, Error> {
35                require_at_least(data, 3, $code)?;
36                let mut oui = [0u8; 3];
37                oui.copy_from_slice(&data[..3]);
38                Ok(Self {
39                    oui,
40                    payload: data[3..].to_vec(),
41                })
42            }
43        }
44    };
45}
46
47oui_plus_payload!(
48    MfgStatR,
49    0x83,
50    "`osdp_MFGSTATR` body — manufacturer status reply."
51);
52oui_plus_payload!(
53    MfgErrR,
54    0x84,
55    "`osdp_MFGERRR` body — manufacturer error reply."
56);
57oui_plus_payload!(MfgRep, 0x90, "`osdp_MFGREP` body — manufacturer reply.");
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn mfgstatr_roundtrip() {
65        let body = MfgStatR {
66            oui: [0x00, 0x06, 0x8E],
67            payload: alloc::vec![0x01, 0x02],
68        };
69        let bytes = body.encode().unwrap();
70        assert_eq!(bytes, [0x00, 0x06, 0x8E, 0x01, 0x02]);
71        assert_eq!(MfgStatR::decode(&bytes).unwrap(), body);
72    }
73
74    #[test]
75    fn mfgstatr_rejects_short_oui() {
76        assert!(matches!(
77            MfgStatR::decode(&[0x00, 0x06]),
78            Err(Error::PayloadTooShort { code: 0x83, .. })
79        ));
80    }
81
82    #[test]
83    fn mfgerrr_roundtrip() {
84        let body = MfgErrR {
85            oui: [0xAA, 0xBB, 0xCC],
86            payload: alloc::vec![0xEE],
87        };
88        let bytes = body.encode().unwrap();
89        assert_eq!(bytes, [0xAA, 0xBB, 0xCC, 0xEE]);
90        assert_eq!(MfgErrR::decode(&bytes).unwrap(), body);
91    }
92
93    #[test]
94    fn mfgerrr_rejects_short_oui() {
95        assert!(matches!(
96            MfgErrR::decode(&[0xAA]),
97            Err(Error::PayloadTooShort { code: 0x84, .. })
98        ));
99    }
100
101    #[test]
102    fn mfgrep_roundtrip_empty_payload() {
103        let body = MfgRep {
104            oui: [0x11, 0x22, 0x33],
105            payload: Vec::new(),
106        };
107        let bytes = body.encode().unwrap();
108        assert_eq!(bytes, [0x11, 0x22, 0x33]);
109        assert_eq!(MfgRep::decode(&bytes).unwrap(), body);
110    }
111
112    #[test]
113    fn mfgrep_rejects_short_oui() {
114        assert!(matches!(
115            MfgRep::decode(&[]),
116            Err(Error::PayloadTooShort { code: 0x90, .. })
117        ));
118    }
119}