#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RtlSdrError {
#[error("USB error: {0}")]
Usb(#[from] rusb::Error),
#[error("device not found at index {index}")]
DeviceNotFound { index: u32 },
#[error("no supported tuner found")]
NoTuner,
#[error("tuner error: {0}")]
Tuner(#[from] TunerError),
#[error("invalid sample rate: {rate_hz} Hz")]
InvalidSampleRate { rate_hz: u32 },
#[error("invalid parameter: {0}")]
InvalidParameter(String),
#[error("device busy")]
DeviceBusy,
#[error("device lost")]
DeviceLost,
#[error("register access failed (block={block:?}, addr=0x{address:04x})")]
RegisterAccess {
block: crate::reg::Block,
address: u16,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum TunerError {
#[error("PLL not locked for {freq_hz} Hz")]
PllNotLocked { freq_hz: u32 },
#[error("PLL reference (xtal) is zero or below the minimum")]
XtalIsZero,
#[error("{backend}: PLL programming failed for {freq_hz} Hz ({reason})")]
PllProgrammingFailed {
backend: &'static str,
freq_hz: u32,
reason: &'static str,
},
#[error("I2C {operation} failed: got {got} bytes, expected {expected}")]
I2cTransferFailed {
operation: &'static str,
got: usize,
expected: usize,
},
#[error("R82xx: no cached value for register 0x{reg:02x}")]
ShadowCacheMiss { reg: u8 },
#[error("FC2580: unsupported filter bandwidth mode {mode}")]
UnsupportedFilterBandwidth { mode: u8 },
#[error("invalid gain ({what}): {detail}")]
InvalidGain { what: &'static str, detail: String },
#[error("{context}: {source}")]
Context {
context: &'static str,
#[source]
source: Box<TunerError>,
},
}
impl RtlSdrError {
#[must_use]
pub fn is_disconnected(&self) -> bool {
matches!(
self,
Self::DeviceLost
| Self::Usb(rusb::Error::NoDevice | rusb::Error::Pipe | rusb::Error::Io)
)
}
#[must_use]
pub fn is_timeout(&self) -> bool {
matches!(self, Self::Usb(rusb::Error::Timeout))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_disconnected_recognises_device_lost_and_no_device() {
assert!(RtlSdrError::DeviceLost.is_disconnected());
assert!(RtlSdrError::Usb(rusb::Error::NoDevice).is_disconnected());
}
#[test]
fn is_disconnected_recognises_linux_hot_unplug_surrogates() {
assert!(RtlSdrError::Usb(rusb::Error::Pipe).is_disconnected());
assert!(RtlSdrError::Usb(rusb::Error::Io).is_disconnected());
}
#[test]
fn is_disconnected_returns_false_for_other_variants() {
assert!(!RtlSdrError::DeviceBusy.is_disconnected());
assert!(!RtlSdrError::Usb(rusb::Error::Timeout).is_disconnected());
assert!(!RtlSdrError::NoTuner.is_disconnected());
assert!(!RtlSdrError::DeviceNotFound { index: 0 }.is_disconnected());
assert!(!RtlSdrError::Tuner(TunerError::XtalIsZero).is_disconnected());
assert!(!RtlSdrError::Usb(rusb::Error::Overflow).is_disconnected());
assert!(!RtlSdrError::Usb(rusb::Error::Access).is_disconnected());
}
#[test]
fn is_timeout_recognises_only_usb_timeout() {
assert!(RtlSdrError::Usb(rusb::Error::Timeout).is_timeout());
}
#[test]
fn is_timeout_returns_false_for_other_variants() {
assert!(!RtlSdrError::DeviceLost.is_timeout());
assert!(!RtlSdrError::DeviceBusy.is_timeout());
assert!(!RtlSdrError::Usb(rusb::Error::NoDevice).is_timeout());
assert!(!RtlSdrError::Usb(rusb::Error::Io).is_timeout());
assert!(!RtlSdrError::Usb(rusb::Error::Pipe).is_timeout());
assert!(
!RtlSdrError::RegisterAccess {
block: crate::reg::Block::Demod,
address: 0
}
.is_timeout()
);
}
}