1use thiserror::Error;
6
7pub type Result<T> = std::result::Result<T, TrezorError>;
9
10#[derive(Debug, Error)]
12#[non_exhaustive]
13pub enum TrezorError {
14 #[error("Transport error: {0}")]
16 Transport(#[from] TransportError),
17
18 #[error("Protocol error: {0}")]
20 Protocol(#[from] ProtocolError),
21
22 #[error("Device error: {0}")]
24 Device(#[from] DeviceError),
25
26 #[error("THP error: {0}")]
28 Thp(#[from] ThpError),
29
30 #[error("Session error: {0}")]
32 Session(#[from] SessionError),
33
34 #[error("Bitcoin error: {0}")]
36 Bitcoin(#[from] BitcoinError),
37
38 #[error("Operation cancelled")]
40 Cancelled,
41
42 #[error("Operation timed out")]
44 Timeout,
45
46 #[error("I/O error: {0}")]
48 IoError(String),
49}
50
51#[derive(Debug, Error)]
53#[non_exhaustive]
54pub enum TransportError {
55 #[error("No Trezor device found")]
57 DeviceNotFound,
58
59 #[error("Device disconnected during operation")]
61 DeviceDisconnected,
62
63 #[error("Unable to open device: {0}")]
65 UnableToOpen(String),
66
67 #[error("Unable to close device: {0}")]
69 UnableToClose(String),
70
71 #[error("Data transfer error: {0}")]
73 DataTransfer(String),
74
75 #[cfg(feature = "usb")]
77 #[error("USB error: {0}")]
78 Usb(String),
79
80 #[cfg(feature = "bluetooth")]
82 #[error("Bluetooth error: {0}")]
83 Bluetooth(String),
84
85 #[error("Permission denied: {0}")]
87 PermissionDenied(String),
88
89 #[error("Device is busy")]
91 DeviceBusy,
92}
93
94#[derive(Debug, Error)]
96#[non_exhaustive]
97pub enum ProtocolError {
98 #[error("Malformed message: {0}")]
100 Malformed(String),
101
102 #[error("Invalid message type: {0}")]
104 InvalidMessageType(u16),
105
106 #[error("Message too short: expected {expected}, got {actual}")]
108 MessageTooShort { expected: usize, actual: usize },
109
110 #[error("Invalid header")]
112 InvalidHeader,
113
114 #[error("Chunk header mismatch")]
116 ChunkHeaderMismatch,
117
118 #[error("Protobuf encode error: {0}")]
120 ProtobufEncode(String),
121
122 #[error("Protobuf decode error: {0}")]
124 ProtobufDecode(String),
125
126 #[error("Unexpected response type: expected {expected}, got {actual}")]
128 UnexpectedResponse { expected: String, actual: String },
129}
130
131#[derive(Debug, Error)]
133#[non_exhaustive]
134pub enum DeviceError {
135 #[error("Device not connected or session not acquired")]
137 NotConnected,
138
139 #[error("Action cancelled by user")]
141 ActionCancelled,
142
143 #[error("PIN is required")]
145 PinRequired,
146
147 #[error("Invalid PIN")]
149 InvalidPin,
150
151 #[error("PIN entry cancelled")]
153 PinCancelled,
154
155 #[error("Passphrase is required")]
157 PassphraseRequired,
158
159 #[error("Passphrase entry cancelled")]
161 PassphraseCancelled,
162
163 #[error("Passphrase is incorrect (device state mismatch)")]
167 InvalidState,
168
169 #[error("Device is not initialized")]
171 NotInitialized,
172
173 #[error("Device needs firmware update")]
175 FirmwareUpdateRequired,
176
177 #[error("Seed is not backed up")]
179 SeedNotBackedUp,
180
181 #[error("Feature not supported: {0}")]
183 NotSupported(String),
184
185 #[error("Device failure: {code:?} - {message}")]
187 Failure { code: Option<i32>, message: String },
188
189 #[error("Device error: code {code} - {message}")]
191 DeviceError { code: i32, message: String },
192
193 #[error("Button request: {0}")]
195 ButtonRequest(String),
196
197 #[error("Protobuf decode error: {0}")]
199 ProtobufDecode(String),
200
201 #[error("Invalid input: {0}")]
203 InvalidInput(String),
204}
205
206impl DeviceError {
207 pub fn from_failure(code: Option<i32>, message: String) -> Self {
215 match code {
216 Some(7) => DeviceError::InvalidPin, Some(6) => DeviceError::PinCancelled, Some(5) => DeviceError::PinRequired, Some(4) => DeviceError::ActionCancelled, other => DeviceError::DeviceError {
221 code: other.unwrap_or(0),
222 message,
223 },
224 }
225 }
226}
227
228#[derive(Debug, Error)]
230#[non_exhaustive]
231pub enum ThpError {
232 #[error("Channel allocation failed")]
234 ChannelAllocationFailed,
235
236 #[error("Handshake failed: {0}")]
238 HandshakeFailed(String),
239
240 #[error("Pairing required")]
242 PairingRequired,
243
244 #[error("Pairing failed: {0}")]
246 PairingFailed(String),
247
248 #[error("Invalid credentials")]
250 InvalidCredentials,
251
252 #[error("Encryption error: {0}")]
254 EncryptionError(String),
255
256 #[error("Decryption error: {0}")]
258 DecryptionError(String),
259
260 #[error("ACK not received")]
262 AckNotReceived,
263
264 #[error("Invalid sync bit")]
266 InvalidSyncBit,
267
268 #[error("THP state missing")]
270 StateMissing,
271
272 #[error("Session error: {0}")]
274 SessionError(String),
275}
276
277#[derive(Debug, Error)]
279#[non_exhaustive]
280pub enum SessionError {
281 #[error("Session not found")]
283 NotFound,
284
285 #[error("Wrong previous session")]
287 WrongPrevious,
288
289 #[error("Session already acquired by another client")]
291 AlreadyAcquired,
292
293 #[error("Session expired")]
295 Expired,
296}
297
298#[derive(Debug, Error)]
300#[non_exhaustive]
301pub enum BitcoinError {
302 #[error("Invalid derivation path: {0}")]
304 InvalidPath(String),
305
306 #[error("Invalid address: {0}")]
308 InvalidAddress(String),
309
310 #[error("Invalid transaction: {0}")]
312 InvalidTransaction(String),
313
314 #[error("Insufficient funds")]
316 InsufficientFunds,
317
318 #[error("Invalid signature")]
320 InvalidSignature,
321
322 #[error("Network mismatch: expected {expected}, got {actual}")]
324 NetworkMismatch { expected: String, actual: String },
325}
326
327impl From<std::io::Error> for TrezorError {
330 fn from(err: std::io::Error) -> Self {
331 TrezorError::Transport(TransportError::DataTransfer(err.to_string()))
332 }
333}
334
335impl From<prost::DecodeError> for ProtocolError {
336 fn from(err: prost::DecodeError) -> Self {
337 ProtocolError::ProtobufDecode(err.to_string())
338 }
339}
340
341impl From<prost::EncodeError> for ProtocolError {
342 fn from(err: prost::EncodeError) -> Self {
343 ProtocolError::ProtobufEncode(err.to_string())
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn from_failure_maps_pin_invalid() {
353 assert!(matches!(
355 DeviceError::from_failure(Some(7), "invalid pin".to_string()),
356 DeviceError::InvalidPin
357 ));
358 }
359
360 #[test]
361 fn from_failure_maps_pin_cancelled() {
362 assert!(matches!(
364 DeviceError::from_failure(Some(6), "cancelled".to_string()),
365 DeviceError::PinCancelled
366 ));
367 }
368
369 #[test]
370 fn from_failure_maps_pin_expected() {
371 assert!(matches!(
373 DeviceError::from_failure(Some(5), "pin expected".to_string()),
374 DeviceError::PinRequired
375 ));
376 }
377
378 #[test]
379 fn from_failure_maps_action_cancelled() {
380 assert!(matches!(
382 DeviceError::from_failure(Some(4), "action cancelled".to_string()),
383 DeviceError::ActionCancelled
384 ));
385 }
386
387 #[test]
388 fn from_failure_unknown_code_stays_generic() {
389 match DeviceError::from_failure(Some(99), "boom".to_string()) {
392 DeviceError::DeviceError { code, message } => {
393 assert_eq!(code, 99);
394 assert_eq!(message, "boom");
395 }
396 other => panic!("expected generic DeviceError, got {other:?}"),
397 }
398 }
399
400 #[test]
401 fn from_failure_missing_code_stays_generic() {
402 match DeviceError::from_failure(None, "no code".to_string()) {
403 DeviceError::DeviceError { code, message } => {
404 assert_eq!(code, 0);
405 assert_eq!(message, "no code");
406 }
407 other => panic!("expected generic DeviceError, got {other:?}"),
408 }
409 }
410}