Skip to main content

hackrf_nusb/
errors.rs

1//! Errors returned by the HackRF driver.
2
3use core::fmt;
4
5/// Error returned by a HackRF operation.
6#[derive(Debug)]
7#[non_exhaustive]
8pub enum Error {
9    /// A receiver configuration value failed validation.
10    InvalidConfig {
11        /// Name of the invalid field.
12        field: &'static str,
13        /// Reason the value is invalid.
14        reason: &'static str,
15    },
16    /// No matching HackRF device was found.
17    DeviceNotFound,
18    /// The logical device session has been shut down.
19    DeviceClosed,
20    /// The device or USB resource is already in use.
21    Busy,
22    /// The requested operation is unsupported.
23    Unsupported,
24    /// The receive stream is stopped or unusable.
25    StreamClosed {
26        /// Reason the stream is unavailable.
27        reason: &'static str,
28    },
29    /// The USB backend returned an error outside an individual transfer.
30    Usb(nusb::Error),
31    /// An individual USB transfer failed.
32    Transfer(nusb::transfer::TransferError),
33    /// A driver operation failed with a more specific source error.
34    Operation {
35        /// Operation being performed.
36        operation: &'static str,
37        /// Underlying error.
38        source: Box<Error>,
39    },
40    /// The device violated the expected HackRF USB protocol.
41    Protocol {
42        /// Operation that failed.
43        operation: &'static str,
44        /// Unexpected response or state.
45        reason: &'static str,
46    },
47}
48
49/// Stable high-level error category.
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51#[non_exhaustive]
52pub enum ErrorKind {
53    /// Invalid configuration.
54    InvalidConfig,
55    /// No matching device.
56    NotFound,
57    /// The logical device is closed.
58    DeviceClosed,
59    /// The physical USB device disconnected.
60    DeviceDisconnected,
61    /// Device or resource busy.
62    Busy,
63    /// Unsupported operation.
64    Unsupported,
65    /// USB or transfer failure.
66    Usb,
67    /// Stream stopped or closed.
68    StreamClosed,
69    /// Protocol or other driver failure.
70    Other,
71}
72
73impl Error {
74    pub(crate) const fn invalid_config(field: &'static str, reason: &'static str) -> Self {
75        Self::InvalidConfig { field, reason }
76    }
77
78    pub(crate) const fn stream_closed(reason: &'static str) -> Self {
79        Self::StreamClosed { reason }
80    }
81
82    pub(crate) const fn protocol(operation: &'static str, reason: &'static str) -> Self {
83        Self::Protocol { operation, reason }
84    }
85
86    pub(crate) fn at(self, operation: &'static str) -> Self {
87        Self::Operation {
88            operation,
89            source: Box::new(self),
90        }
91    }
92
93    /// Return a backend-independent error category.
94    pub fn kind(&self) -> ErrorKind {
95        match self {
96            Self::InvalidConfig { .. } => ErrorKind::InvalidConfig,
97            Self::DeviceNotFound => ErrorKind::NotFound,
98            Self::DeviceClosed => ErrorKind::DeviceClosed,
99            Self::Busy => ErrorKind::Busy,
100            Self::Unsupported => ErrorKind::Unsupported,
101            Self::StreamClosed { .. } => ErrorKind::StreamClosed,
102            Self::Usb(error) => match error.kind() {
103                nusb::ErrorKind::Disconnected => ErrorKind::DeviceDisconnected,
104                nusb::ErrorKind::Busy => ErrorKind::Busy,
105                nusb::ErrorKind::NotFound => ErrorKind::NotFound,
106                nusb::ErrorKind::Unsupported => ErrorKind::Unsupported,
107                _ => ErrorKind::Usb,
108            },
109            Self::Transfer(nusb::transfer::TransferError::Disconnected) => {
110                ErrorKind::DeviceDisconnected
111            }
112            Self::Transfer(_) => ErrorKind::Usb,
113            Self::Operation { source, .. } => source.kind(),
114            Self::Protocol { .. } => ErrorKind::Other,
115        }
116    }
117}
118
119impl fmt::Display for Error {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            Self::InvalidConfig { field, reason } => {
123                write!(f, "invalid configuration for {field}: {reason}")
124            }
125            Self::DeviceNotFound => f.write_str("no matching HackRF device found"),
126            Self::DeviceClosed => f.write_str("HackRF device is closed"),
127            Self::Busy => f.write_str("HackRF device or USB resource is busy"),
128            Self::Unsupported => f.write_str("operation is unsupported"),
129            Self::StreamClosed { reason } => write!(f, "stream closed: {reason}"),
130            Self::Usb(error) => write!(f, "USB error: {error}"),
131            Self::Transfer(error) => write!(f, "USB transfer error: {error}"),
132            Self::Operation { operation, source } => write!(f, "{operation}: {source}"),
133            Self::Protocol { operation, reason } => write!(f, "{operation}: {reason}"),
134        }
135    }
136}
137
138impl std::error::Error for Error {
139    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
140        match self {
141            Self::Usb(error) => Some(error),
142            Self::Transfer(error) => Some(error),
143            Self::Operation { source, .. } => Some(source),
144            _ => None,
145        }
146    }
147}
148
149impl From<nusb::Error> for Error {
150    fn from(value: nusb::Error) -> Self {
151        Self::Usb(value)
152    }
153}
154
155impl From<nusb::transfer::TransferError> for Error {
156    fn from(value: nusb::transfer::TransferError) -> Self {
157        Self::Transfer(value)
158    }
159}
160
161/// Crate result alias using [`Error`].
162pub type Result<T> = core::result::Result<T, Error>;
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn operation_context_preserves_error_kind() {
170        let error = Error::from(nusb::transfer::TransferError::Fault).at("reading samples");
171        assert_eq!(error.kind(), ErrorKind::Usb);
172        assert!(error.to_string().starts_with("reading samples:"));
173    }
174}