Skip to main content

osdp/command/
piv.rs

1//! PIV-related commands: `osdp_PIVDATA` (`0xA3`), `osdp_GENAUTH` (`0xA4`),
2//! `osdp_CRAUTH` (`0xA5`).
3//!
4//! # Spec: §6.23 (PIVDATA), §6.24 (GENAUTH), §6.25 (CRAUTH)
5//!
6//! These commands typically use the multi-part envelope (Annex E examples)
7//! when their payload exceeds a single packet's RX size.
8
9use crate::error::Error;
10use crate::payload_util::require_at_least;
11use alloc::vec::Vec;
12
13/// `osdp_PIVDATA` body.
14///
15/// Format follows Annex F of the spec; we treat the body as a typed selector
16/// plus opaque payload for forward-compatibility with PIV variants.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct PivData {
19    /// PIV object tag (e.g. CHUID, CCC, PHOTO).
20    pub object_id: [u8; 3],
21    /// Element ID within the object.
22    pub element_id: u8,
23    /// Data offset (when fragmented).
24    pub offset: u16,
25    /// Trailing payload (often empty for read requests).
26    pub data: Vec<u8>,
27}
28
29impl PivData {
30    /// Encode.
31    pub fn encode(&self) -> Result<Vec<u8>, Error> {
32        let mut out = Vec::with_capacity(6 + self.data.len());
33        out.extend_from_slice(&self.object_id);
34        out.push(self.element_id);
35        out.extend_from_slice(&self.offset.to_le_bytes());
36        out.extend_from_slice(&self.data);
37        Ok(out)
38    }
39
40    /// Decode.
41    pub fn decode(data: &[u8]) -> Result<Self, Error> {
42        require_at_least(data, 6, 0xA3)?;
43        let mut object_id = [0u8; 3];
44        object_id.copy_from_slice(&data[..3]);
45        Ok(Self {
46            object_id,
47            element_id: data[3],
48            offset: u16::from_le_bytes([data[4], data[5]]),
49            data: data[6..].to_vec(),
50        })
51    }
52}
53
54/// `osdp_GENAUTH` body. Generic authenticate sub-command for PIV.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct GenAuth {
57    /// Algorithm reference (PIV §3.2.4).
58    pub algorithm: u8,
59    /// Key reference.
60    pub key_ref: u8,
61    /// Encoded TLV authentication template.
62    pub auth_template: Vec<u8>,
63}
64
65impl GenAuth {
66    /// Encode.
67    pub fn encode(&self) -> Result<Vec<u8>, Error> {
68        let mut out = Vec::with_capacity(2 + self.auth_template.len());
69        out.push(self.algorithm);
70        out.push(self.key_ref);
71        out.extend_from_slice(&self.auth_template);
72        Ok(out)
73    }
74
75    /// Decode.
76    pub fn decode(data: &[u8]) -> Result<Self, Error> {
77        require_at_least(data, 2, 0xA4)?;
78        Ok(Self {
79            algorithm: data[0],
80            key_ref: data[1],
81            auth_template: data[2..].to_vec(),
82        })
83    }
84}
85
86/// `osdp_CRAUTH` body. Crypto challenge.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct CrAuth {
89    /// Challenge nonce (typically 16 bytes).
90    pub challenge: Vec<u8>,
91}
92
93impl CrAuth {
94    /// Encode.
95    pub fn encode(&self) -> Result<Vec<u8>, Error> {
96        Ok(self.challenge.clone())
97    }
98
99    /// Decode.
100    pub fn decode(data: &[u8]) -> Result<Self, Error> {
101        Ok(Self {
102            challenge: data.to_vec(),
103        })
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn pivdata_roundtrip() {
113        let body = PivData {
114            object_id: [0x5F, 0xC1, 0x02],
115            element_id: 0x01,
116            offset: 0x0010,
117            data: alloc::vec![0xAA, 0xBB],
118        };
119        let bytes = body.encode().unwrap();
120        assert_eq!(bytes, [0x5F, 0xC1, 0x02, 0x01, 0x10, 0x00, 0xAA, 0xBB]);
121        assert_eq!(PivData::decode(&bytes).unwrap(), body);
122    }
123
124    #[test]
125    fn pivdata_rejects_short_header() {
126        assert!(matches!(
127            PivData::decode(&[0; 5]),
128            Err(Error::PayloadTooShort { code: 0xA3, .. })
129        ));
130    }
131
132    #[test]
133    fn genauth_roundtrip() {
134        let body = GenAuth {
135            algorithm: 0x07,
136            key_ref: 0x9A,
137            auth_template: alloc::vec![0x7C, 0x02, 0x80, 0x00],
138        };
139        let bytes = body.encode().unwrap();
140        assert_eq!(bytes, [0x07, 0x9A, 0x7C, 0x02, 0x80, 0x00]);
141        assert_eq!(GenAuth::decode(&bytes).unwrap(), body);
142    }
143
144    #[test]
145    fn genauth_rejects_short() {
146        assert!(matches!(
147            GenAuth::decode(&[0x07]),
148            Err(Error::PayloadTooShort { code: 0xA4, .. })
149        ));
150    }
151
152    #[test]
153    fn crauth_passthrough() {
154        let body = CrAuth {
155            challenge: alloc::vec![0x11; 16],
156        };
157        let bytes = body.encode().unwrap();
158        assert_eq!(bytes.len(), 16);
159        assert_eq!(CrAuth::decode(&bytes).unwrap(), body);
160    }
161
162    #[test]
163    fn crauth_accepts_empty() {
164        assert_eq!(
165            CrAuth::decode(&[]).unwrap(),
166            CrAuth {
167                challenge: Vec::new()
168            }
169        );
170    }
171}