Skip to main content

osdp/command/
biometric.rs

1//! `osdp_BIOREAD` (`0x73`) and `osdp_BIOMATCH` (`0x74`).
2//!
3//! # Spec: §6.14, §6.15, Tables 24–25
4
5use crate::error::Error;
6use alloc::vec::Vec;
7
8/// Biometric type code (Table 24).
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(u8)]
11#[allow(missing_docs)]
12pub enum BioType {
13    NotSpecified = 0x00,
14    RightThumb = 0x01,
15    RightIndex = 0x02,
16    RightMiddle = 0x03,
17    RightRing = 0x04,
18    RightLittle = 0x05,
19    LeftThumb = 0x06,
20    LeftIndex = 0x07,
21    LeftMiddle = 0x08,
22    LeftRing = 0x09,
23    LeftLittle = 0x0A,
24    RightIris = 0x0B,
25    RightRetina = 0x0C,
26    LeftIris = 0x0D,
27    LeftRetina = 0x0E,
28    Face = 0x0F,
29    RightHandGeometry = 0x10,
30    LeftHandGeometry = 0x11,
31}
32
33impl BioType {
34    /// Parse from byte; preserves "NotSpecified" for unknowns to avoid losing
35    /// data that the receiver ought to NAK with `0x07`.
36    pub const fn from_byte(b: u8) -> Self {
37        match b {
38            0x00 => Self::NotSpecified,
39            0x01 => Self::RightThumb,
40            0x02 => Self::RightIndex,
41            0x03 => Self::RightMiddle,
42            0x04 => Self::RightRing,
43            0x05 => Self::RightLittle,
44            0x06 => Self::LeftThumb,
45            0x07 => Self::LeftIndex,
46            0x08 => Self::LeftMiddle,
47            0x09 => Self::LeftRing,
48            0x0A => Self::LeftLittle,
49            0x0B => Self::RightIris,
50            0x0C => Self::RightRetina,
51            0x0D => Self::LeftIris,
52            0x0E => Self::LeftRetina,
53            0x0F => Self::Face,
54            0x10 => Self::RightHandGeometry,
55            0x11 => Self::LeftHandGeometry,
56            _ => Self::NotSpecified,
57        }
58    }
59
60    /// Raw byte.
61    pub const fn as_byte(self) -> u8 {
62        self as u8
63    }
64}
65
66/// Biometric data format (Table 25).
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68#[repr(u8)]
69#[allow(missing_docs)]
70pub enum BioFormat {
71    NotSpecified = 0x00,
72    FingerprintRawPgm = 0x01,
73    FingerprintAnsi378 = 0x02,
74}
75
76impl BioFormat {
77    /// Parse.
78    pub const fn from_byte(b: u8) -> Self {
79        match b {
80            0x01 => Self::FingerprintRawPgm,
81            0x02 => Self::FingerprintAnsi378,
82            _ => Self::NotSpecified,
83        }
84    }
85
86    /// Raw byte.
87    pub const fn as_byte(self) -> u8 {
88        self as u8
89    }
90}
91
92/// `osdp_BIOREAD` body.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct BioRead {
95    /// Reader number.
96    pub reader: u8,
97    /// Biometric type to capture.
98    pub bio_type: BioType,
99    /// Format the PD should respond with.
100    pub bio_format: BioFormat,
101    /// Capture quality (0..=100).
102    pub quality: u8,
103}
104
105impl BioRead {
106    /// Encode.
107    pub fn encode(&self) -> Result<Vec<u8>, Error> {
108        Ok(alloc::vec![
109            self.reader,
110            self.bio_type.as_byte(),
111            self.bio_format.as_byte(),
112            self.quality,
113        ])
114    }
115
116    /// Decode.
117    pub fn decode(data: &[u8]) -> Result<Self, Error> {
118        if data.len() != 4 {
119            return Err(Error::MalformedPayload {
120                code: 0x73,
121                reason: "BIOREAD requires 4 bytes",
122            });
123        }
124        Ok(Self {
125            reader: data[0],
126            bio_type: BioType::from_byte(data[1]),
127            bio_format: BioFormat::from_byte(data[2]),
128            quality: data[3],
129        })
130    }
131}
132
133/// `osdp_BIOMATCH` body. Carries a template to match against.
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct BioMatch {
136    /// Reader number.
137    pub reader: u8,
138    /// Biometric type to capture.
139    pub bio_type: BioType,
140    /// Format of the template.
141    pub bio_format: BioFormat,
142    /// Quality threshold for the match.
143    pub quality: u8,
144    /// Template payload.
145    pub template: Vec<u8>,
146}
147
148impl BioMatch {
149    /// Encode.
150    pub fn encode(&self) -> Result<Vec<u8>, Error> {
151        if self.template.len() > u16::MAX as usize {
152            return Err(Error::MalformedPayload {
153                code: 0x74,
154                reason: "BIOMATCH template > 65535 bytes",
155            });
156        }
157        let mut out = Vec::with_capacity(6 + self.template.len());
158        out.push(self.reader);
159        out.push(self.bio_type.as_byte());
160        out.push(self.bio_format.as_byte());
161        out.push(self.quality);
162        out.extend_from_slice(&(self.template.len() as u16).to_le_bytes());
163        out.extend_from_slice(&self.template);
164        Ok(out)
165    }
166
167    /// Decode.
168    pub fn decode(data: &[u8]) -> Result<Self, Error> {
169        if data.len() < 6 {
170            return Err(Error::MalformedPayload {
171                code: 0x74,
172                reason: "BIOMATCH requires at least 6 bytes",
173            });
174        }
175        let length = u16::from_le_bytes([data[4], data[5]]) as usize;
176        if data.len() != 6 + length {
177            return Err(Error::MalformedPayload {
178                code: 0x74,
179                reason: "BIOMATCH length disagrees with payload",
180            });
181        }
182        Ok(Self {
183            reader: data[0],
184            bio_type: BioType::from_byte(data[1]),
185            bio_format: BioFormat::from_byte(data[2]),
186            quality: data[3],
187            template: data[6..6 + length].to_vec(),
188        })
189    }
190}