osdp/error.rs
1//! Crate-wide error type. `core::error::Error`-compatible.
2
3use core::fmt;
4
5/// Convenience [`Result`] alias.
6pub type Result<T> = core::result::Result<T, Error>;
7
8/// Crate-wide error.
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Error {
12 /// Buffer too small to hold a syntactically valid packet header.
13 Truncated {
14 /// Bytes available.
15 have: usize,
16 /// Bytes required.
17 need: usize,
18 },
19 /// SOM mismatch — packet did not start with `0x53`.
20 BadSom(u8),
21 /// `LEN` field disagrees with the actual buffer length.
22 BadLength {
23 /// Length declared in the LEN field.
24 declared: usize,
25 /// Length actually delivered.
26 actual: usize,
27 },
28 /// Reserved bits in the [`crate::ControlByte`] are non-zero, or the byte is
29 /// otherwise malformed.
30 BadControlByte(u8),
31 /// CRC verification failed.
32 BadCrc {
33 /// CRC carried on the wire.
34 got: u16,
35 /// CRC we computed from the header + payload.
36 want: u16,
37 },
38 /// Checksum verification failed.
39 BadChecksum {
40 /// Trailer byte carried on the wire.
41 got: u8,
42 /// Checksum we computed.
43 want: u8,
44 },
45 /// Address field is reserved or invalid.
46 BadAddress(u8),
47 /// Sequence number is not in `0..=3`.
48 BadSqn(u8),
49 /// Security block type is unknown.
50 BadSecurityBlock(u8),
51 /// Length of the security block is wrong for its type.
52 BadSecurityBlockLength(u8),
53 /// MAC verification failed (constant-time compare).
54 BadMac,
55 /// MAC is too short (must be at least 4 bytes on the wire).
56 ShortMac,
57 /// Command code is not recognized.
58 UnknownCommand(u8),
59 /// Reply code is not recognized.
60 UnknownReply(u8),
61 /// Payload bytes do not match the expected layout for this command/reply.
62 MalformedPayload {
63 /// Command or reply code.
64 code: u8,
65 /// One-line explanation.
66 reason: &'static str,
67 },
68 /// Multi-part receiver detected an invariant violation.
69 Multipart(MultipartError),
70 /// Secure-channel state machine rejected an event.
71 SecureSession(SecureSessionError),
72 /// Transport-level I/O.
73 Io(&'static str),
74 /// Reply did not arrive within the configured budget.
75 Timeout,
76 /// PD declared off-line.
77 Offline,
78 /// Address mismatch between expected PD and reply ADDR.
79 AddrMismatch {
80 /// Address we sent to.
81 sent: u8,
82 /// Address echoed back in the reply.
83 got: u8,
84 },
85 /// PD answered with [`crate::reply::Nak`].
86 Nak {
87 /// Error code byte from the reply.
88 code: u8,
89 },
90 /// Output buffer was too small.
91 BufferOverflow {
92 /// Minimum required capacity.
93 need: usize,
94 /// Capacity available.
95 have: usize,
96 },
97}
98
99/// Subcategory of multi-part assembler errors.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[non_exhaustive]
102pub enum MultipartError {
103 /// First fragment did not arrive at offset 0.
104 UnexpectedFirstOffset(u16),
105 /// Out-of-order or overlapping fragment.
106 OutOfOrderOffset {
107 /// Expected next offset.
108 expected: u16,
109 /// Offset declared by the fragment we received.
110 got: u16,
111 },
112 /// `MpSizeTotal` differs from the figure in a previous fragment.
113 InconsistentTotal {
114 /// First fragment's stated total.
115 first: u16,
116 /// New fragment's stated total.
117 now: u16,
118 },
119 /// Final fragment overruns the declared total.
120 OverflowsTotal,
121 /// `MpFragmentSize` does not match the actual payload.
122 BadFragmentSize,
123 /// Counterparty aborted the transfer.
124 Aborted,
125}
126
127/// Subcategory of secure-channel state errors.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[non_exhaustive]
130pub enum SecureSessionError {
131 /// Cryptogram comparison failed.
132 BadCryptogram,
133 /// Caller attempted to wrap a frame before SCS was fully established.
134 NotSecure,
135 /// SCS event arrived in a state that does not accept it.
136 BadTransition,
137 /// Encrypted DATA must be padded to a 16-byte multiple.
138 BadPadding,
139}
140
141impl fmt::Display for Error {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Error::Truncated { have, need } => {
145 write!(f, "truncated: have {have} bytes, need {need}")
146 }
147 Error::BadSom(b) => write!(f, "bad SOM byte: {b:#04x}"),
148 Error::BadLength { declared, actual } => {
149 write!(f, "length mismatch: declared {declared}, actual {actual}")
150 }
151 Error::BadControlByte(b) => write!(f, "bad control byte: {b:#04x}"),
152 Error::BadCrc { got, want } => write!(f, "bad CRC: got {got:#06x}, want {want:#06x}"),
153 Error::BadChecksum { got, want } => {
154 write!(f, "bad checksum: got {got:#04x}, want {want:#04x}")
155 }
156 Error::BadAddress(b) => write!(f, "bad address: {b:#04x}"),
157 Error::BadSqn(b) => write!(f, "bad sequence number: {b}"),
158 Error::BadSecurityBlock(b) => write!(f, "unknown SCB type: {b:#04x}"),
159 Error::BadSecurityBlockLength(b) => write!(f, "bad SCB length for type {b:#04x}"),
160 Error::BadMac => f.write_str("bad MAC"),
161 Error::ShortMac => f.write_str("MAC too short"),
162 Error::UnknownCommand(c) => write!(f, "unknown command code: {c:#04x}"),
163 Error::UnknownReply(c) => write!(f, "unknown reply code: {c:#04x}"),
164 Error::MalformedPayload { code, reason } => {
165 write!(f, "malformed payload for {code:#04x}: {reason}")
166 }
167 Error::Multipart(e) => write!(f, "multipart: {e:?}"),
168 Error::SecureSession(e) => write!(f, "secure session: {e:?}"),
169 Error::Io(s) => write!(f, "io: {s}"),
170 Error::Timeout => f.write_str("reply timed out"),
171 Error::Offline => f.write_str("PD declared off-line"),
172 Error::AddrMismatch { sent, got } => {
173 write!(f, "address mismatch: sent {sent:#04x}, got {got:#04x}")
174 }
175 Error::Nak { code } => write!(f, "PD replied NAK ({code:#04x})"),
176 Error::BufferOverflow { need, have } => {
177 write!(f, "buffer overflow: need {need}, have {have}")
178 }
179 }
180 }
181}
182
183impl core::error::Error for Error {}
184
185impl From<MultipartError> for Error {
186 fn from(e: MultipartError) -> Self {
187 Error::Multipart(e)
188 }
189}
190
191impl From<SecureSessionError> for Error {
192 fn from(e: SecureSessionError) -> Self {
193 Error::SecureSession(e)
194 }
195}