Skip to main content

osdp/reply/
nak.rs

1//! `osdp_NAK` (`0x41`).
2//!
3//! # Spec: ยง7.3, Table 47
4//!
5//! Body is `error_code (1 byte)` followed by an *optional* per-record
6//! completion-code array (only when error code is `0x09`).
7
8use crate::error::Error;
9use alloc::vec::Vec;
10
11/// NAK error code (Table 47).
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(u8)]
14#[allow(missing_docs)]
15pub enum NakErrorCode {
16    NoError = 0x00,
17    BadCrcOrChecksum = 0x01,
18    CommandLengthError = 0x02,
19    UnknownCommandCode = 0x03,
20    UnexpectedSequenceNumber = 0x04,
21    SecurityBlockTypeNotSupported = 0x05,
22    EncryptedCommunicationRequired = 0x06,
23    BioTypeNotSupported = 0x07,
24    BioFormatNotSupported = 0x08,
25    UnableToProcessCommandRecord = 0x09,
26}
27
28impl NakErrorCode {
29    /// Parse from byte.
30    pub const fn from_byte(b: u8) -> Result<Self, Error> {
31        Ok(match b {
32            0x00 => Self::NoError,
33            0x01 => Self::BadCrcOrChecksum,
34            0x02 => Self::CommandLengthError,
35            0x03 => Self::UnknownCommandCode,
36            0x04 => Self::UnexpectedSequenceNumber,
37            0x05 => Self::SecurityBlockTypeNotSupported,
38            0x06 => Self::EncryptedCommunicationRequired,
39            0x07 => Self::BioTypeNotSupported,
40            0x08 => Self::BioFormatNotSupported,
41            0x09 => Self::UnableToProcessCommandRecord,
42            _ => {
43                return Err(Error::MalformedPayload {
44                    code: 0x41,
45                    reason: "unknown NAK error code",
46                });
47            }
48        })
49    }
50
51    /// Raw byte.
52    pub const fn as_byte(self) -> u8 {
53        self as u8
54    }
55}
56
57/// `osdp_NAK` body.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct Nak {
60    /// Top-level error code.
61    pub error: NakErrorCode,
62    /// Per-record completion codes (only when `error == UnableToProcessCommandRecord`).
63    pub completion_codes: Vec<u8>,
64}
65
66impl Nak {
67    /// Build a simple NAK with no completion array.
68    pub fn simple(err: NakErrorCode) -> Self {
69        Self {
70            error: err,
71            completion_codes: Vec::new(),
72        }
73    }
74
75    /// Encode.
76    pub fn encode(&self) -> Result<Vec<u8>, Error> {
77        let mut out = Vec::with_capacity(1 + self.completion_codes.len());
78        out.push(self.error.as_byte());
79        out.extend_from_slice(&self.completion_codes);
80        Ok(out)
81    }
82
83    /// Decode. Treats unknown error codes as a parse failure rather than
84    /// silently dropping the byte (NAK arriving with an unknown code is
85    /// usually a sign of bus chaos worth surfacing).
86    pub fn decode(data: &[u8]) -> Result<Self, Error> {
87        if data.is_empty() {
88            return Err(Error::MalformedPayload {
89                code: 0x41,
90                reason: "NAK requires at least 1 byte",
91            });
92        }
93        let error = NakErrorCode::from_byte(data[0])?;
94        let completion_codes = if error == NakErrorCode::UnableToProcessCommandRecord {
95            data[1..].to_vec()
96        } else {
97            Vec::new()
98        };
99        Ok(Self {
100            error,
101            completion_codes,
102        })
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn simple_roundtrip() {
112        let n = Nak::simple(NakErrorCode::BadCrcOrChecksum);
113        let bytes = n.encode().unwrap();
114        let parsed = Nak::decode(&bytes).unwrap();
115        assert_eq!(parsed, n);
116    }
117
118    #[test]
119    fn record_completion_codes() {
120        let n = Nak {
121            error: NakErrorCode::UnableToProcessCommandRecord,
122            completion_codes: alloc::vec![0xAA, 0xBB, 0xCC],
123        };
124        let bytes = n.encode().unwrap();
125        let parsed = Nak::decode(&bytes).unwrap();
126        assert_eq!(parsed, n);
127    }
128
129    #[test]
130    fn rejects_unknown_code() {
131        assert!(Nak::decode(&[0xFE]).is_err());
132    }
133}