Skip to main content

hw_wallet/
error.rs

1use ble_transport::BleError;
2use thiserror::Error;
3use trezor_connect::thp::{BackendError, ThpWorkflowError};
4
5#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6pub enum WalletErrorKind {
7    Ble,
8    Workflow,
9    Device,
10    Validation,
11    Timeout,
12}
13
14#[derive(Debug, Error)]
15pub enum WalletError {
16    #[error("invalid BIP32 path: {0}")]
17    InvalidBip32Path(String),
18    #[error(
19        "BLE peer removed pairing information: remove device from OS Bluetooth settings and pair again"
20    )]
21    PeerRemovedPairingInfo,
22    #[error("BLE error: {0}")]
23    Ble(#[from] BleError),
24    #[error("workflow error: {0}")]
25    Workflow(#[from] ThpWorkflowError),
26    #[error("signing error: {0}")]
27    Signing(String),
28    #[error(
29        "solana serialized tx is too short ({len} bytes, minimum {min}); provide full serialized transaction bytes"
30    )]
31    SolanaTxTooShort { len: usize, min: usize },
32    #[error("Solana transaction version {0} is not supported by firmware")]
33    UnsupportedSolanaTxVersion(u8),
34}
35
36pub type WalletResult<T> = std::result::Result<T, WalletError>;
37
38impl WalletError {
39    pub fn kind(&self) -> WalletErrorKind {
40        match self {
41            Self::InvalidBip32Path(_)
42            | Self::Signing(_)
43            | Self::SolanaTxTooShort { .. }
44            | Self::UnsupportedSolanaTxVersion(_) => WalletErrorKind::Validation,
45            Self::PeerRemovedPairingInfo => WalletErrorKind::Device,
46            Self::Ble(error) => error.into(),
47            Self::Workflow(error) => error.into(),
48        }
49    }
50}
51
52impl From<&BleError> for WalletErrorKind {
53    fn from(error: &BleError) -> Self {
54        match error {
55            BleError::Timeout(_) => Self::Timeout,
56            BleError::Btleplug(_)
57            | BleError::AdapterUnavailable
58            | BleError::NotificationStreamClosed
59            | BleError::MissingCharacteristic { .. } => Self::Ble,
60        }
61    }
62}
63
64impl From<&ThpWorkflowError> for WalletErrorKind {
65    fn from(error: &ThpWorkflowError) -> Self {
66        match error {
67            ThpWorkflowError::Backend(error) => error.into(),
68            ThpWorkflowError::InvalidPhase
69            | ThpWorkflowError::MissingHandshake
70            | ThpWorkflowError::MissingHandshakeCredentials
71            | ThpWorkflowError::AlreadyPaired
72            | ThpWorkflowError::NoCommonPairingMethod
73            | ThpWorkflowError::PairingAborted
74            | ThpWorkflowError::PairingInteractionRequired
75            | ThpWorkflowError::PairingController(_)
76            | ThpWorkflowError::Storage(_) => Self::Workflow,
77        }
78    }
79}
80
81impl From<&BackendError> for WalletErrorKind {
82    fn from(error: &BackendError) -> Self {
83        match error {
84            BackendError::TransportTimeout => Self::Timeout,
85            BackendError::Device(_)
86            | BackendError::DeviceBusy
87            | BackendError::DeviceLocked
88            | BackendError::PinExpected
89            | BackendError::DeviceFirmwareError
90            | BackendError::SessionConfirmationRequired
91            | BackendError::DeviceError { .. } => Self::Device,
92            BackendError::UnsupportedPairingMethod => Self::Validation,
93            BackendError::Transport(_) | BackendError::TransportBusy => Self::Workflow,
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use std::time::Duration;
102
103    #[test]
104    fn ble_timeout_is_classified_as_timeout() {
105        let error = WalletError::Ble(BleError::Timeout(Duration::from_secs(1)));
106        assert_eq!(error.kind(), WalletErrorKind::Timeout);
107        assert_eq!(
108            WalletError::Ble(BleError::AdapterUnavailable).kind(),
109            WalletErrorKind::Ble
110        );
111    }
112}