Skip to main content

rtl_sdr_rs/
error.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::{error, fmt, io, result};
6
7/// A result returned by this crate.
8pub type Result<T> = result::Result<T, RtlsdrError>;
9
10/// Stable categories for USB failures.
11#[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/// A USB failure whose public representation is independent of the USB backend.
34#[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}