Skip to main content

osdp/
caps.rs

1//! PD capability function codes.
2//!
3//! # Spec: Annex B
4//!
5//! Each entry in an `osdp_PDCAP` reply is a 3-byte tuple
6//! `(function_code, compliance, number_of)`. The `function_code` is one of
7//! the values in [`FunctionCode`] and the meaning of `compliance` /
8//! `number_of` is function-specific.
9
10use crate::error::Error;
11
12/// Capability function code.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14#[repr(u8)]
15pub enum FunctionCode {
16    /// Contact-status monitoring.
17    ContactStatus = 1,
18    /// Output control.
19    OutputControl = 2,
20    /// Card-data format.
21    CardDataFormat = 3,
22    /// Reader LED control.
23    LedControl = 4,
24    /// Reader audible (buzzer) output.
25    AudibleOutput = 5,
26    /// Reader text output.
27    TextOutput = 6,
28    /// Time keeping (deprecated).
29    TimeKeeping = 7,
30    /// Check-character support: `compliance` 0 = checksum only, 1 = CRC.
31    CheckCharacter = 8,
32    /// Communication security: `compliance` bit 0 = AES-128.
33    CommunicationSecurity = 9,
34    /// Receive buffer size: 16-bit value with `compliance` = LSB,
35    /// `number_of` = MSB.
36    ReceiveBufferSize = 10,
37    /// Largest combined message size: same little-endian split.
38    LargestCombinedSize = 11,
39    /// Smart-card support: `compliance` bit 0 = transparent, bit 1 = extended.
40    SmartCardSupport = 12,
41    /// Number of downstream readers.
42    Readers = 13,
43    /// Biometrics: `compliance` 1 = fp T1, 2 = fp T2, 3 = iris T1.
44    Biometrics = 14,
45    /// Secure PIN entry.
46    SecurePinEntry = 15,
47    /// OSDP version: 0 pre-IEC, 1 IEC 60839-11-5, 2 SIA OSDP 2.2.
48    OsdpVersion = 16,
49}
50
51impl FunctionCode {
52    /// Parse from byte.
53    pub const fn from_byte(b: u8) -> Result<Self, Error> {
54        Ok(match b {
55            1 => Self::ContactStatus,
56            2 => Self::OutputControl,
57            3 => Self::CardDataFormat,
58            4 => Self::LedControl,
59            5 => Self::AudibleOutput,
60            6 => Self::TextOutput,
61            7 => Self::TimeKeeping,
62            8 => Self::CheckCharacter,
63            9 => Self::CommunicationSecurity,
64            10 => Self::ReceiveBufferSize,
65            11 => Self::LargestCombinedSize,
66            12 => Self::SmartCardSupport,
67            13 => Self::Readers,
68            14 => Self::Biometrics,
69            15 => Self::SecurePinEntry,
70            16 => Self::OsdpVersion,
71            other => {
72                return Err(Error::MalformedPayload {
73                    code: other,
74                    reason: "unknown PDCAP function code",
75                });
76            }
77        })
78    }
79
80    /// Raw byte value.
81    pub const fn as_byte(self) -> u8 {
82        self as u8
83    }
84}
85
86/// One capability entry from `osdp_PDCAP`.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub struct Capability {
89    /// Function code byte (may be unknown to us — keep raw).
90    pub code: u8,
91    /// Compliance / level. Meaning is per-function.
92    pub compliance: u8,
93    /// Quantity / index. Meaning is per-function.
94    pub number_of: u8,
95}
96
97impl Capability {
98    /// Wire size of one capability triplet.
99    pub const WIRE_LEN: usize = 3;
100
101    /// Encode to 3 bytes.
102    pub const fn encode(self) -> [u8; 3] {
103        [self.code, self.compliance, self.number_of]
104    }
105
106    /// Decode from 3 bytes.
107    pub const fn decode(bytes: [u8; 3]) -> Self {
108        Self {
109            code: bytes[0],
110            compliance: bytes[1],
111            number_of: bytes[2],
112        }
113    }
114
115    /// Recognized [`FunctionCode`], if any.
116    pub fn function(self) -> Option<FunctionCode> {
117        FunctionCode::from_byte(self.code).ok()
118    }
119
120    /// 16-bit value formed by `compliance | (number_of << 8)`.
121    pub const fn u16_value(self) -> u16 {
122        u16::from_le_bytes([self.compliance, self.number_of])
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn rx_size_combined() {
132        let c = Capability {
133            code: FunctionCode::ReceiveBufferSize.as_byte(),
134            compliance: 0x80, // 128
135            number_of: 0x00,
136        };
137        assert_eq!(c.u16_value(), 128);
138    }
139
140    #[test]
141    fn function_roundtrip() {
142        for raw in 1u8..=16 {
143            let f = FunctionCode::from_byte(raw).unwrap();
144            assert_eq!(f.as_byte(), raw);
145        }
146    }
147}