1use thiserror::Error as ThisError;
2
3use crate::wire::{Location, ObjectClass};
4
5pub type Result<T> = std::result::Result<T, Error>;
6
7#[derive(ThisError, Debug)]
8#[non_exhaustive]
9pub enum Error {
10 #[error("message truncated: got {got} bytes, need at least {need}")]
11 Truncated { got: usize, need: usize },
12
13 #[error("length field says {declared} bytes but the message is {actual}")]
14 LengthMismatch { declared: usize, actual: usize },
15
16 #[error("crc mismatch: message carries {expected:#06x}, computed {actual:#06x}")]
17 BadCrc { expected: u16, actual: u16 },
18
19 #[error("device reported status {0:#x}")]
20 DeviceStatus(u32),
21
22 #[error("the device refused a session for {class:?} with status {status:#x}")]
25 ClassRefused { class: ObjectClass, status: u32 },
26
27 #[error("expected a response to command {expected:#x}, got {got:#x}")]
28 UnexpectedResponse { expected: u32, got: u32 },
29
30 #[error("device reported location {reported:?} for the requested location {requested:?}")]
31 UnexpectedLocation {
32 requested: Location,
33 reported: Location,
34 },
35
36 #[error("device reported partition {reported} for the requested partition {requested}")]
37 UnexpectedPartition { requested: u32, reported: u32 },
38
39 #[error(
41 "walking bank {bank}, which declares {slots} slots, became inconsistent at \
42 {answered:?}"
43 )]
44 Enumeration {
45 bank: u32,
46 answered: Location,
47 slots: u32,
48 },
49
50 #[error("bank {bank} cannot be scanned completely within {limit} slots")]
51 ScanLimit { bank: u32, limit: u32 },
52
53 #[error("transport: {0}")]
56 Transport(String),
57
58 #[error("envelope: {0}")]
61 Envelope(String),
62
63 #[error("replay: {0}")]
66 Replay(String),
67
68 #[error("invalid argument: {0}")]
69 InvalidArgument(String),
70
71 #[error(transparent)]
72 Io(#[from] std::io::Error),
73}
74
75impl Error {
76 pub fn expect_kind(&self) -> ErrKind {
87 match self {
88 Error::DeviceStatus(code) => ErrKind::DeviceStatus(*code),
89 Error::ClassRefused { status, .. } => ErrKind::ClassRefused(*status),
90 Error::UnexpectedResponse { .. } => ErrKind::UnexpectedResponse,
91 Error::UnexpectedLocation { .. } => ErrKind::UnexpectedLocation,
92 Error::UnexpectedPartition { .. } => ErrKind::UnexpectedPartition,
93 Error::Enumeration { .. } | Error::ScanLimit { .. } => ErrKind::Enumeration,
94 Error::Replay(_) => ErrKind::Replay,
95 Error::Truncated { .. }
96 | Error::LengthMismatch { .. }
97 | Error::BadCrc { .. }
98 | Error::Transport(_)
99 | Error::Envelope(_)
100 | Error::InvalidArgument(_)
101 | Error::Io(_) => ErrKind::Transport,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum ErrKind {
118 DeviceStatus(u32),
119 ClassRefused(u32),
120 UnexpectedResponse,
121 UnexpectedLocation,
122 UnexpectedPartition,
123 Enumeration,
124 Transport,
125 Replay,
126}
127
128impl ErrKind {
129 pub fn matches(&self, e: &Error) -> bool {
131 *self == e.expect_kind()
132 }
133
134 #[cfg(feature = "replay")]
136 pub(crate) fn parse(value: &str) -> std::result::Result<Self, String> {
137 let (kind, arg) = match value.split_once(char::is_whitespace) {
138 Some((kind, arg)) => (kind, arg.trim()),
139 None => (value, ""),
140 };
141 match (kind, arg) {
142 ("device-status", "") => Err("device-status needs its code, e.g. \
143 'err device-status 0x15'"
144 .into()),
145 ("device-status", code) => parse_u32(code)
146 .map(ErrKind::DeviceStatus)
147 .ok_or_else(|| format!("bad device status {code:?}")),
148 ("class-refused", "") => Err("class-refused needs its code, e.g. \
149 'err class-refused 0x5'"
150 .into()),
151 ("class-refused", code) => parse_u32(code)
152 .map(ErrKind::ClassRefused)
153 .ok_or_else(|| format!("bad class refusal status {code:?}")),
154 ("unexpected-response", "") => Ok(ErrKind::UnexpectedResponse),
155 ("unexpected-location", "") => Ok(ErrKind::UnexpectedLocation),
156 ("unexpected-partition", "") => Ok(ErrKind::UnexpectedPartition),
157 ("enumeration", "") => Ok(ErrKind::Enumeration),
158 ("transport", "") => Ok(ErrKind::Transport),
159 ("replay", "") => Ok(ErrKind::Replay),
160 (kind, _) => Err(format!(
161 "unknown failure {kind:?}; the vocabulary is device-status <code>, \
162 class-refused <code>, unexpected-response, unexpected-location, \
163 unexpected-partition, enumeration, \
164 transport, replay"
165 )),
166 }
167 }
168}
169
170impl std::fmt::Display for ErrKind {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 ErrKind::DeviceStatus(code) => write!(f, "device-status {code:#x}"),
174 ErrKind::ClassRefused(code) => write!(f, "class-refused {code:#x}"),
175 ErrKind::UnexpectedResponse => f.write_str("unexpected-response"),
176 ErrKind::UnexpectedLocation => f.write_str("unexpected-location"),
177 ErrKind::UnexpectedPartition => f.write_str("unexpected-partition"),
178 ErrKind::Enumeration => f.write_str("enumeration"),
179 ErrKind::Transport => f.write_str("transport"),
180 ErrKind::Replay => f.write_str("replay"),
181 }
182 }
183}
184
185#[cfg(feature = "replay")]
187fn parse_u32(s: &str) -> Option<u32> {
188 match s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
189 Some(hex) => u32::from_str_radix(hex, 16).ok(),
190 None => s.parse().ok(),
191 }
192}