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