Skip to main content

osdp/reply/
raw_card.rs

1//! `osdp_RAW` (`0x50`) and `osdp_FMT` (`0x51`) — card data reports.
2//!
3//! # Spec: §7.10, §7.11
4//!
5//! `RAW` carries a raw bit array: `reader (1)` + `format_code (1)` +
6//! `bit_count (2 LE)` + `bytes`. `bytes` is `ceil(bit_count / 8)` bytes;
7//! trailing bits in the final byte beyond `bit_count` are reserved and must
8//! be ignored.
9
10use crate::error::Error;
11use alloc::vec::Vec;
12
13/// `osdp_RAW` body.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Raw {
16    /// Reader number.
17    pub reader: u8,
18    /// Format code (vendor-specific; see Annex F).
19    pub format_code: u8,
20    /// Number of bits actually present in `bits`.
21    pub bit_count: u16,
22    /// `ceil(bit_count / 8)` bytes; high-order bits first.
23    pub bits: Vec<u8>,
24}
25
26impl Raw {
27    /// Encode.
28    pub fn encode(&self) -> Result<Vec<u8>, Error> {
29        let need = (self.bit_count as usize).div_ceil(8);
30        if self.bits.len() != need {
31            return Err(Error::MalformedPayload {
32                code: 0x50,
33                reason: "RAW bit count does not match byte length",
34            });
35        }
36        let mut out = Vec::with_capacity(4 + self.bits.len());
37        out.push(self.reader);
38        out.push(self.format_code);
39        out.extend_from_slice(&self.bit_count.to_le_bytes());
40        out.extend_from_slice(&self.bits);
41        Ok(out)
42    }
43
44    /// Decode.
45    pub fn decode(data: &[u8]) -> Result<Self, Error> {
46        if data.len() < 4 {
47            return Err(Error::MalformedPayload {
48                code: 0x50,
49                reason: "RAW requires at least 4 bytes",
50            });
51        }
52        let bit_count = u16::from_le_bytes([data[2], data[3]]);
53        let need = (bit_count as usize).div_ceil(8);
54        if data.len() != 4 + need {
55            return Err(Error::MalformedPayload {
56                code: 0x50,
57                reason: "RAW bit count does not match payload length",
58            });
59        }
60        Ok(Self {
61            reader: data[0],
62            format_code: data[1],
63            bit_count,
64            bits: data[4..].to_vec(),
65        })
66    }
67
68    /// Iterate over the active card bits, MSB-first within each byte,
69    /// stopping at `bit_count`.
70    pub fn iter_bits(&self) -> impl Iterator<Item = bool> + '_ {
71        let n = self.bit_count as usize;
72        self.bits
73            .iter()
74            .enumerate()
75            .flat_map(move |(byte_idx, &b)| {
76                (0..8).filter_map(move |bit| {
77                    let pos = byte_idx * 8 + bit;
78                    if pos >= n {
79                        None
80                    } else {
81                        Some(b & (0x80 >> bit) != 0)
82                    }
83                })
84            })
85    }
86}
87
88/// `osdp_FMT` body.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Fmt {
91    /// Reader number.
92    pub reader: u8,
93    /// Read direction (0 = forward, 1 = reverse).
94    pub direction: u8,
95    /// Length of `chars`.
96    pub char_count: u8,
97    /// ASCII chars from the formatted card data.
98    pub chars: Vec<u8>,
99}
100
101impl Fmt {
102    /// Encode.
103    pub fn encode(&self) -> Result<Vec<u8>, Error> {
104        if self.chars.len() != self.char_count as usize {
105            return Err(Error::MalformedPayload {
106                code: 0x51,
107                reason: "FMT char_count disagrees with chars",
108            });
109        }
110        let mut out = Vec::with_capacity(3 + self.chars.len());
111        out.push(self.reader);
112        out.push(self.direction);
113        out.push(self.char_count);
114        out.extend_from_slice(&self.chars);
115        Ok(out)
116    }
117
118    /// Decode.
119    pub fn decode(data: &[u8]) -> Result<Self, Error> {
120        if data.len() < 3 {
121            return Err(Error::MalformedPayload {
122                code: 0x51,
123                reason: "FMT requires at least 3 bytes",
124            });
125        }
126        let char_count = data[2];
127        if data.len() != 3 + char_count as usize {
128            return Err(Error::MalformedPayload {
129                code: 0x51,
130                reason: "FMT char_count disagrees with payload",
131            });
132        }
133        Ok(Self {
134            reader: data[0],
135            direction: data[1],
136            char_count,
137            chars: data[3..].to_vec(),
138        })
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn raw_iter_bits_truncates() {
148        // 26-bit Wiegand card data
149        let raw = Raw {
150            reader: 0,
151            format_code: 0,
152            bit_count: 26,
153            bits: alloc::vec![0xAA, 0xBB, 0xCC, 0x80],
154        };
155        let bits: alloc::vec::Vec<bool> = raw.iter_bits().collect();
156        assert_eq!(bits.len(), 26);
157    }
158
159    #[test]
160    fn raw_roundtrip() {
161        let r = Raw {
162            reader: 1,
163            format_code: 0,
164            bit_count: 26,
165            bits: alloc::vec![0xAA, 0xBB, 0xCC, 0x80],
166        };
167        let bytes = r.encode().unwrap();
168        let parsed = Raw::decode(&bytes).unwrap();
169        assert_eq!(parsed, r);
170    }
171}