1use std::{error, fmt, io, result};
6
7pub type Result<T> = result::Result<T, RtlsdrError>;
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum UsbErrorKind {
14 Disconnected,
15 Busy,
16 PermissionDenied,
17 NotFound,
18 Unsupported,
19 Cancelled,
20 Timeout,
21 Stall,
22 Fault,
23 InvalidArgument,
24 Other,
25}
26
27#[derive(Debug)]
28enum UsbErrorSource {
29 Device(nusb::Error),
30 Transfer(nusb::transfer::TransferError),
31}
32
33#[derive(Debug)]
35pub struct UsbError {
36 kind: UsbErrorKind,
37 source: UsbErrorSource,
38}
39
40impl UsbError {
41 pub fn kind(&self) -> UsbErrorKind {
42 self.kind
43 }
44
45 fn from_device(source: nusb::Error) -> Self {
46 let kind = match source.kind() {
47 nusb::ErrorKind::Disconnected => UsbErrorKind::Disconnected,
48 nusb::ErrorKind::Busy => UsbErrorKind::Busy,
49 nusb::ErrorKind::PermissionDenied => UsbErrorKind::PermissionDenied,
50 nusb::ErrorKind::NotFound => UsbErrorKind::NotFound,
51 nusb::ErrorKind::Unsupported => UsbErrorKind::Unsupported,
52 nusb::ErrorKind::Other => UsbErrorKind::Other,
53 _ => UsbErrorKind::Other,
54 };
55 Self {
56 kind,
57 source: UsbErrorSource::Device(source),
58 }
59 }
60
61 fn from_transfer(source: nusb::transfer::TransferError) -> Self {
62 use nusb::transfer::TransferError;
63
64 let kind = match source {
65 TransferError::Cancelled => UsbErrorKind::Cancelled,
66 TransferError::Stall => UsbErrorKind::Stall,
67 TransferError::Disconnected => UsbErrorKind::Disconnected,
68 TransferError::Fault => UsbErrorKind::Fault,
69 TransferError::InvalidArgument => UsbErrorKind::InvalidArgument,
70 TransferError::Unknown(_) => UsbErrorKind::Other,
71 };
72 Self {
73 kind,
74 source: UsbErrorSource::Transfer(source),
75 }
76 }
77
78 fn from_timed_transfer(source: nusb::transfer::TransferError) -> Self {
79 let mut error = Self::from_transfer(source);
80 if error.kind == UsbErrorKind::Cancelled {
81 error.kind = UsbErrorKind::Timeout;
82 }
83 error
84 }
85}
86
87impl fmt::Display for UsbError {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 if self.kind == UsbErrorKind::Timeout {
90 return f.write_str("USB transfer timed out");
91 }
92
93 match &self.source {
94 UsbErrorSource::Device(source) => source.fmt(f),
95 UsbErrorSource::Transfer(source) => source.fmt(f),
96 }
97 }
98}
99
100impl error::Error for UsbError {
101 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
102 match &self.source {
103 UsbErrorSource::Device(source) => Some(source),
104 UsbErrorSource::Transfer(source) => Some(source),
105 }
106 }
107}
108
109#[derive(Debug)]
110#[non_exhaustive]
111pub enum RtlsdrError {
112 Usb(UsbError),
113 Io(io::Error),
114 RtlsdrErr(String),
115}
116
117impl RtlsdrError {
118 pub(crate) fn from_usb(source: nusb::Error) -> Self {
119 Self::Usb(UsbError::from_device(source))
120 }
121
122 pub(crate) fn from_usb_transfer(source: nusb::transfer::TransferError) -> Self {
123 Self::Usb(UsbError::from_transfer(source))
124 }
125
126 pub(crate) fn from_timed_usb_transfer(source: nusb::transfer::TransferError) -> Self {
127 Self::Usb(UsbError::from_timed_transfer(source))
128 }
129}
130
131impl From<UsbError> for RtlsdrError {
132 fn from(error: UsbError) -> Self {
133 Self::Usb(error)
134 }
135}
136
137impl From<io::Error> for RtlsdrError {
138 fn from(error: io::Error) -> Self {
139 Self::Io(error)
140 }
141}
142
143impl From<String> for RtlsdrError {
144 fn from(error: String) -> Self {
145 Self::RtlsdrErr(error)
146 }
147}
148
149impl fmt::Display for RtlsdrError {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 match self {
152 Self::Usb(error) => error.fmt(f),
153 Self::Io(error) => error.fmt(f),
154 Self::RtlsdrErr(error) => error.fmt(f),
155 }
156 }
157}
158
159impl error::Error for RtlsdrError {
160 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
161 match self {
162 Self::Usb(error) => Some(error),
163 Self::Io(error) => Some(error),
164 Self::RtlsdrErr(_) => None,
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::{RtlsdrError, UsbErrorKind};
172 use nusb::transfer::TransferError;
173
174 #[test]
175 fn transfer_errors_map_to_stable_usb_kinds() {
176 let cases = [
177 (TransferError::Cancelled, UsbErrorKind::Cancelled),
178 (TransferError::Stall, UsbErrorKind::Stall),
179 (TransferError::Disconnected, UsbErrorKind::Disconnected),
180 (TransferError::Fault, UsbErrorKind::Fault),
181 (
182 TransferError::InvalidArgument,
183 UsbErrorKind::InvalidArgument,
184 ),
185 (TransferError::Unknown(1), UsbErrorKind::Other),
186 ];
187
188 for (source, expected) in cases {
189 let error = RtlsdrError::from_usb_transfer(source);
190 let RtlsdrError::Usb(error) = error else {
191 panic!("expected USB error");
192 };
193 assert_eq!(error.kind(), expected);
194 assert!(std::error::Error::source(&error).is_some());
195 }
196 }
197
198 #[test]
199 fn cancelled_timed_transfer_maps_to_timeout() {
200 let error = RtlsdrError::from_timed_usb_transfer(TransferError::Cancelled);
201 let RtlsdrError::Usb(error) = error else {
202 panic!("expected USB error");
203 };
204
205 assert_eq!(error.kind(), UsbErrorKind::Timeout);
206 assert_eq!(error.to_string(), "USB transfer timed out");
207 assert!(std::error::Error::source(&error).is_some());
208 }
209}