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