1use crate::error::Error;
6use crate::payload_util::{require_at_least, require_exact_len};
7use alloc::vec::Vec;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[repr(u8)]
12#[allow(missing_docs)]
13pub enum BioType {
14 NotSpecified = 0x00,
15 RightThumb = 0x01,
16 RightIndex = 0x02,
17 RightMiddle = 0x03,
18 RightRing = 0x04,
19 RightLittle = 0x05,
20 LeftThumb = 0x06,
21 LeftIndex = 0x07,
22 LeftMiddle = 0x08,
23 LeftRing = 0x09,
24 LeftLittle = 0x0A,
25 RightIris = 0x0B,
26 RightRetina = 0x0C,
27 LeftIris = 0x0D,
28 LeftRetina = 0x0E,
29 Face = 0x0F,
30 RightHandGeometry = 0x10,
31 LeftHandGeometry = 0x11,
32}
33
34impl BioType {
35 pub const fn from_byte(b: u8) -> Self {
38 match b {
39 0x00 => Self::NotSpecified,
40 0x01 => Self::RightThumb,
41 0x02 => Self::RightIndex,
42 0x03 => Self::RightMiddle,
43 0x04 => Self::RightRing,
44 0x05 => Self::RightLittle,
45 0x06 => Self::LeftThumb,
46 0x07 => Self::LeftIndex,
47 0x08 => Self::LeftMiddle,
48 0x09 => Self::LeftRing,
49 0x0A => Self::LeftLittle,
50 0x0B => Self::RightIris,
51 0x0C => Self::RightRetina,
52 0x0D => Self::LeftIris,
53 0x0E => Self::LeftRetina,
54 0x0F => Self::Face,
55 0x10 => Self::RightHandGeometry,
56 0x11 => Self::LeftHandGeometry,
57 _ => Self::NotSpecified,
58 }
59 }
60
61 pub const fn as_byte(self) -> u8 {
63 self as u8
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69#[repr(u8)]
70#[allow(missing_docs)]
71pub enum BioFormat {
72 NotSpecified = 0x00,
73 FingerprintRawPgm = 0x01,
74 FingerprintAnsi378 = 0x02,
75}
76
77impl BioFormat {
78 pub const fn from_byte(b: u8) -> Self {
80 match b {
81 0x01 => Self::FingerprintRawPgm,
82 0x02 => Self::FingerprintAnsi378,
83 _ => Self::NotSpecified,
84 }
85 }
86
87 pub const fn as_byte(self) -> u8 {
89 self as u8
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct BioRead {
96 pub reader: u8,
98 pub bio_type: BioType,
100 pub bio_format: BioFormat,
102 pub quality: u8,
104}
105
106impl BioRead {
107 pub fn encode(&self) -> Result<Vec<u8>, Error> {
109 Ok(alloc::vec![
110 self.reader,
111 self.bio_type.as_byte(),
112 self.bio_format.as_byte(),
113 self.quality,
114 ])
115 }
116
117 pub fn decode(data: &[u8]) -> Result<Self, Error> {
119 require_exact_len(data, 4, 0x73)?;
120 Ok(Self {
121 reader: data[0],
122 bio_type: BioType::from_byte(data[1]),
123 bio_format: BioFormat::from_byte(data[2]),
124 quality: data[3],
125 })
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct BioMatch {
132 pub reader: u8,
134 pub bio_type: BioType,
136 pub bio_format: BioFormat,
138 pub quality: u8,
140 pub template: Vec<u8>,
142}
143
144impl BioMatch {
145 pub fn encode(&self) -> Result<Vec<u8>, Error> {
147 if self.template.len() > u16::MAX as usize {
148 return Err(Error::MalformedPayload {
149 code: 0x74,
150 reason: "BIOMATCH template > 65535 bytes",
151 });
152 }
153 let mut out = Vec::with_capacity(6 + self.template.len());
154 out.push(self.reader);
155 out.push(self.bio_type.as_byte());
156 out.push(self.bio_format.as_byte());
157 out.push(self.quality);
158 out.extend_from_slice(&(self.template.len() as u16).to_le_bytes());
159 out.extend_from_slice(&self.template);
160 Ok(out)
161 }
162
163 pub fn decode(data: &[u8]) -> Result<Self, Error> {
165 require_at_least(data, 6, 0x74)?;
166 let length = u16::from_le_bytes([data[4], data[5]]) as usize;
167 if data.len() != 6 + length {
168 return Err(Error::MalformedPayload {
169 code: 0x74,
170 reason: "BIOMATCH length disagrees with payload",
171 });
172 }
173 Ok(Self {
174 reader: data[0],
175 bio_type: BioType::from_byte(data[1]),
176 bio_format: BioFormat::from_byte(data[2]),
177 quality: data[3],
178 template: data[6..6 + length].to_vec(),
179 })
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn bioread_roundtrip() {
189 let body = BioRead {
190 reader: 0x02,
191 bio_type: BioType::LeftThumb,
192 bio_format: BioFormat::FingerprintAnsi378,
193 quality: 80,
194 };
195 let bytes = body.encode().unwrap();
196 assert_eq!(bytes, [0x02, 0x06, 0x02, 80]);
197 assert_eq!(BioRead::decode(&bytes).unwrap(), body);
198 }
199
200 #[test]
201 fn bioread_rejects_wrong_length() {
202 assert!(matches!(
203 BioRead::decode(&[0; 5]),
204 Err(Error::PayloadLength { code: 0x73, .. })
205 ));
206 }
207
208 #[test]
209 fn biotype_unknown_decodes_to_not_specified() {
210 assert_eq!(BioType::from_byte(0xFF), BioType::NotSpecified);
211 }
212
213 #[test]
214 fn biomatch_roundtrip() {
215 let body = BioMatch {
216 reader: 0x01,
217 bio_type: BioType::RightIndex,
218 bio_format: BioFormat::FingerprintRawPgm,
219 quality: 75,
220 template: alloc::vec![0xDE, 0xAD, 0xBE, 0xEF],
221 };
222 let bytes = body.encode().unwrap();
223 assert_eq!(
225 bytes,
226 [0x01, 0x02, 0x01, 75, 0x04, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]
227 );
228 assert_eq!(BioMatch::decode(&bytes).unwrap(), body);
229 }
230
231 #[test]
232 fn biomatch_rejects_length_disagreement() {
233 let bad = [0x01, 0x02, 0x01, 75, 0x05, 0x00, 0xAA, 0xBB, 0xCC];
235 assert!(matches!(
236 BioMatch::decode(&bad),
237 Err(Error::MalformedPayload { code: 0x74, .. })
238 ));
239 }
240}