Skip to main content

osdp/reply/
mod.rs

1//! Typed OSDP replies (PD → ACU). See sibling [`crate::command`].
2//!
3//! # Spec: §7 / Annex A.2
4
5use crate::error::Error;
6
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9
10/// All `REPLY` byte values from Annex A.2 of the spec.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[repr(u8)]
13#[allow(missing_docs)]
14pub enum ReplyCode {
15    Ack = 0x40,
16    Nak = 0x41,
17    PdId = 0x45,
18    PdCap = 0x46,
19    LStatR = 0x48,
20    IStatR = 0x49,
21    OStatR = 0x4A,
22    RStatR = 0x4B,
23    Raw = 0x50,
24    Fmt = 0x51,
25    Keypad = 0x53,
26    Com = 0x54,
27    BioReadR = 0x57,
28    BioMatchR = 0x58,
29    CCrypt = 0x76,
30    RMacI = 0x78,
31    Busy = 0x79,
32    FtStat = 0x7A,
33    PivDataR = 0x80,
34    GenAuthR = 0x81,
35    CrAuthR = 0x82,
36    MfgStatR = 0x83,
37    MfgErrR = 0x84,
38    MfgRep = 0x90,
39    Xrd = 0xB1,
40}
41
42impl ReplyCode {
43    /// Parse from raw byte.
44    pub const fn from_byte(b: u8) -> Result<Self, Error> {
45        Ok(match b {
46            0x40 => Self::Ack,
47            0x41 => Self::Nak,
48            0x45 => Self::PdId,
49            0x46 => Self::PdCap,
50            0x48 => Self::LStatR,
51            0x49 => Self::IStatR,
52            0x4A => Self::OStatR,
53            0x4B => Self::RStatR,
54            0x50 => Self::Raw,
55            0x51 => Self::Fmt,
56            0x53 => Self::Keypad,
57            0x54 => Self::Com,
58            0x57 => Self::BioReadR,
59            0x58 => Self::BioMatchR,
60            0x76 => Self::CCrypt,
61            0x78 => Self::RMacI,
62            0x79 => Self::Busy,
63            0x7A => Self::FtStat,
64            0x80 => Self::PivDataR,
65            0x81 => Self::GenAuthR,
66            0x82 => Self::CrAuthR,
67            0x83 => Self::MfgStatR,
68            0x84 => Self::MfgErrR,
69            0x90 => Self::MfgRep,
70            0xB1 => Self::Xrd,
71            other => return Err(Error::UnknownReply(other)),
72        })
73    }
74
75    /// Raw byte.
76    pub const fn as_byte(self) -> u8 {
77        self as u8
78    }
79}
80
81pub mod ack;
82pub mod bio;
83pub mod busy;
84pub mod ccrypt;
85pub mod com;
86pub mod crauth;
87pub mod ft_stat;
88pub mod genauth;
89pub mod istat;
90pub mod keypad;
91pub mod lstat;
92pub mod mfg;
93pub mod nak;
94pub mod ostat;
95pub mod pdcap;
96pub mod pdid;
97pub mod piv;
98pub mod raw_card;
99pub mod rmac_i;
100pub mod rstat;
101pub mod xrd;
102
103pub use ack::Ack;
104pub use bio::{BioMatchR, BioReadR};
105pub use busy::Busy;
106pub use ccrypt::CCrypt;
107pub use com::Com;
108pub use crauth::CrAuthR;
109pub use ft_stat::FtStat;
110pub use genauth::GenAuthR;
111pub use istat::IStatR;
112pub use keypad::Keypad;
113pub use lstat::LStatR;
114pub use mfg::{MfgErrR, MfgRep, MfgStatR};
115pub use nak::{Nak, NakErrorCode};
116pub use ostat::OStatR;
117pub use pdcap::PdCap;
118pub use pdid::PdId;
119pub use piv::PivDataR;
120pub use raw_card::{Fmt, Raw};
121pub use rmac_i::RMacI;
122pub use rstat::RStatR;
123pub use xrd::Xrd;
124
125/// Typed dispatch over every reply.
126#[cfg(feature = "alloc")]
127#[derive(Debug, Clone, PartialEq, Eq)]
128#[non_exhaustive]
129#[allow(missing_docs)]
130pub enum Reply {
131    Ack(Ack),
132    Nak(Nak),
133    PdId(PdId),
134    PdCap(PdCap),
135    LStatR(LStatR),
136    IStatR(IStatR),
137    OStatR(OStatR),
138    RStatR(RStatR),
139    Raw(Raw),
140    Fmt(Fmt),
141    Keypad(Keypad),
142    Com(Com),
143    BioReadR(BioReadR),
144    BioMatchR(BioMatchR),
145    CCrypt(CCrypt),
146    RMacI(RMacI),
147    Busy(Busy),
148    FtStat(FtStat),
149    PivDataR(PivDataR),
150    GenAuthR(GenAuthR),
151    CrAuthR(CrAuthR),
152    MfgStatR(MfgStatR),
153    MfgErrR(MfgErrR),
154    MfgRep(MfgRep),
155    Xrd(Xrd),
156}
157
158#[cfg(feature = "alloc")]
159impl Reply {
160    /// Code byte for this reply.
161    pub fn code(&self) -> ReplyCode {
162        match self {
163            Reply::Ack(_) => ReplyCode::Ack,
164            Reply::Nak(_) => ReplyCode::Nak,
165            Reply::PdId(_) => ReplyCode::PdId,
166            Reply::PdCap(_) => ReplyCode::PdCap,
167            Reply::LStatR(_) => ReplyCode::LStatR,
168            Reply::IStatR(_) => ReplyCode::IStatR,
169            Reply::OStatR(_) => ReplyCode::OStatR,
170            Reply::RStatR(_) => ReplyCode::RStatR,
171            Reply::Raw(_) => ReplyCode::Raw,
172            Reply::Fmt(_) => ReplyCode::Fmt,
173            Reply::Keypad(_) => ReplyCode::Keypad,
174            Reply::Com(_) => ReplyCode::Com,
175            Reply::BioReadR(_) => ReplyCode::BioReadR,
176            Reply::BioMatchR(_) => ReplyCode::BioMatchR,
177            Reply::CCrypt(_) => ReplyCode::CCrypt,
178            Reply::RMacI(_) => ReplyCode::RMacI,
179            Reply::Busy(_) => ReplyCode::Busy,
180            Reply::FtStat(_) => ReplyCode::FtStat,
181            Reply::PivDataR(_) => ReplyCode::PivDataR,
182            Reply::GenAuthR(_) => ReplyCode::GenAuthR,
183            Reply::CrAuthR(_) => ReplyCode::CrAuthR,
184            Reply::MfgStatR(_) => ReplyCode::MfgStatR,
185            Reply::MfgErrR(_) => ReplyCode::MfgErrR,
186            Reply::MfgRep(_) => ReplyCode::MfgRep,
187            Reply::Xrd(_) => ReplyCode::Xrd,
188        }
189    }
190
191    /// Encode the DATA payload (does not include the code byte).
192    pub fn encode_data(&self) -> Result<Vec<u8>, Error> {
193        match self {
194            Reply::Ack(r) => r.encode(),
195            Reply::Nak(r) => r.encode(),
196            Reply::PdId(r) => r.encode(),
197            Reply::PdCap(r) => r.encode(),
198            Reply::LStatR(r) => r.encode(),
199            Reply::IStatR(r) => r.encode(),
200            Reply::OStatR(r) => r.encode(),
201            Reply::RStatR(r) => r.encode(),
202            Reply::Raw(r) => r.encode(),
203            Reply::Fmt(r) => r.encode(),
204            Reply::Keypad(r) => r.encode(),
205            Reply::Com(r) => r.encode(),
206            Reply::BioReadR(r) => r.encode(),
207            Reply::BioMatchR(r) => r.encode(),
208            Reply::CCrypt(r) => r.encode(),
209            Reply::RMacI(r) => r.encode(),
210            Reply::Busy(r) => r.encode(),
211            Reply::FtStat(r) => r.encode(),
212            Reply::PivDataR(r) => r.encode(),
213            Reply::GenAuthR(r) => r.encode(),
214            Reply::CrAuthR(r) => r.encode(),
215            Reply::MfgStatR(r) => r.encode(),
216            Reply::MfgErrR(r) => r.encode(),
217            Reply::MfgRep(r) => r.encode(),
218            Reply::Xrd(r) => r.encode(),
219        }
220    }
221
222    /// Decode the typed payload, given the code byte and DATA bytes.
223    pub fn decode(code: ReplyCode, data: &[u8]) -> Result<Self, Error> {
224        Ok(match code {
225            ReplyCode::Ack => Reply::Ack(Ack::decode(data)?),
226            ReplyCode::Nak => Reply::Nak(Nak::decode(data)?),
227            ReplyCode::PdId => Reply::PdId(PdId::decode(data)?),
228            ReplyCode::PdCap => Reply::PdCap(PdCap::decode(data)?),
229            ReplyCode::LStatR => Reply::LStatR(LStatR::decode(data)?),
230            ReplyCode::IStatR => Reply::IStatR(IStatR::decode(data)?),
231            ReplyCode::OStatR => Reply::OStatR(OStatR::decode(data)?),
232            ReplyCode::RStatR => Reply::RStatR(RStatR::decode(data)?),
233            ReplyCode::Raw => Reply::Raw(Raw::decode(data)?),
234            ReplyCode::Fmt => Reply::Fmt(Fmt::decode(data)?),
235            ReplyCode::Keypad => Reply::Keypad(Keypad::decode(data)?),
236            ReplyCode::Com => Reply::Com(Com::decode(data)?),
237            ReplyCode::BioReadR => Reply::BioReadR(BioReadR::decode(data)?),
238            ReplyCode::BioMatchR => Reply::BioMatchR(BioMatchR::decode(data)?),
239            ReplyCode::CCrypt => Reply::CCrypt(CCrypt::decode(data)?),
240            ReplyCode::RMacI => Reply::RMacI(RMacI::decode(data)?),
241            ReplyCode::Busy => Reply::Busy(Busy::decode(data)?),
242            ReplyCode::FtStat => Reply::FtStat(FtStat::decode(data)?),
243            ReplyCode::PivDataR => Reply::PivDataR(PivDataR::decode(data)?),
244            ReplyCode::GenAuthR => Reply::GenAuthR(GenAuthR::decode(data)?),
245            ReplyCode::CrAuthR => Reply::CrAuthR(CrAuthR::decode(data)?),
246            ReplyCode::MfgStatR => Reply::MfgStatR(MfgStatR::decode(data)?),
247            ReplyCode::MfgErrR => Reply::MfgErrR(MfgErrR::decode(data)?),
248            ReplyCode::MfgRep => Reply::MfgRep(MfgRep::decode(data)?),
249            ReplyCode::Xrd => Reply::Xrd(Xrd::decode(data)?),
250        })
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn code_roundtrip() {
260        for byte in [
261            0x40u8, 0x41, 0x45, 0x46, 0x48, 0x49, 0x4A, 0x4B, 0x50, 0x51, 0x53, 0x54, 0x57, 0x58,
262            0x76, 0x78, 0x79, 0x7A, 0x80, 0x81, 0x82, 0x83, 0x84, 0x90, 0xB1,
263        ] {
264            assert_eq!(ReplyCode::from_byte(byte).unwrap().as_byte(), byte);
265        }
266    }
267
268    #[test]
269    fn unknown_byte_errors() {
270        assert!(matches!(
271            ReplyCode::from_byte(0xFF),
272            Err(Error::UnknownReply(0xFF))
273        ));
274    }
275}