use crate::constants::{XUM1541_PID, XUM1541_VID};
use libc::{EACCES, EINVAL, EIO, ENODEV, ENOENT, ETIMEDOUT};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Serialize, Deserialize)]
pub enum Error {
#[error("USB error while attempting to communicate with the XUM1541: {0}")]
Usb(SerializableUsbError),
#[error("XUM1541 device initialization failed: {message}")]
Init { message: String },
#[error("XUM1541 device communication error: {kind}")]
Communication { kind: Communication },
#[error("XUM1541 operation timed out after {dur:?}")]
Timeout { dur: std::time::Duration },
#[error("{kind}")]
DeviceAccess { kind: DeviceAccess },
#[error("xum1541 library called with invalid arguments: {message}")]
Args { message: String },
}
#[derive(Debug, Error, Clone, PartialEq, Serialize, Deserialize)]
pub enum DeviceAccess {
#[error("The library is not connected to an XUM1541")]
NoDevice,
#[error("XUM1541 device {vid:04x}/{pid:04x} not found - is it connected and do you have permissions to access it?")]
NotFound { vid: u16, pid: u16 },
#[error("XUM1541 device found, but non-matching serial numbers. Found {actual:?}, was looking for {expected:?}")]
SerialMismatch {
vid: u16,
pid: u16,
actual: Vec<u8>,
expected: Option<u8>,
},
#[error(
"XUM1541 device has unsupported firmware version {actual}, expected minimum {expected}"
)]
FirmwareVersion { actual: u8, expected: u8 },
#[error("Hit USB permissions error while attempting to access XUM1541 device. Are you sure you have suitable permissions? You may need to reconfigure udev rules in /etc/udev/rules.d/.")]
Permission,
#[error("Failed to resolve network address: {message}")]
AddressResolution { message: String, errno: i32 },
#[error("Failed to connect to remote device: {message}")]
NetworkConnection { message: String, errno: i32 },
}
#[derive(Debug, Error, Clone, PartialEq, Serialize, Deserialize)]
pub enum Communication {
#[error("Failed to issue ioctl to device")]
IoctlFailed,
#[error("Device returned an invalid status response format")]
StatusFormat,
#[error("Device returned an IO error status response")]
StatusIo,
#[error("Device returned an invalid status response value 0x{value:02x}")]
StatusResponse { value: u8 },
#[error("Device returned an error status value 0x{value:04x}")]
StatusValue { value: u16 },
#[error("Timed out waiting for a status response from the device")]
StatusTimeout { dur: std::time::Duration },
#[error("Remote disconnected: {message} {errno}")]
RemoteDisconnected { message: String, errno: i32 },
#[error("Hit an error communicating with a remote device: {message}, {errno}")]
Remote { message: String, errno: i32 },
#[error("Hit an unknown error during communication: {message}, {errno}")]
Unknown { message: String, errno: i32 },
}
#[derive(Debug, Error, Clone, PartialEq, Serialize, Deserialize)]
pub enum SerializableUsbError {
#[error("{message}")]
UsbError { message: String },
}
impl Error {
#[must_use]
pub fn to_errno(&self) -> i32 {
#[allow(clippy::match_same_arms)]
match self {
Error::Usb { .. } | Error::Init { .. } => EIO,
Error::Communication { kind } => match kind {
Communication::RemoteDisconnected { errno, .. } => *errno,
Communication::Remote { errno, .. } | Communication::Unknown { errno, .. } => {
*errno
}
_ => EIO,
},
Error::Timeout { .. } => ETIMEDOUT,
Error::DeviceAccess { kind } => match kind {
DeviceAccess::NoDevice { .. } | DeviceAccess::FirmwareVersion { .. } => ENODEV,
DeviceAccess::NotFound { .. } | DeviceAccess::SerialMismatch { .. } => ENOENT,
DeviceAccess::Permission { .. } => EACCES,
DeviceAccess::AddressResolution { errno, .. }
| DeviceAccess::NetworkConnection { errno, .. } => *errno,
},
Error::Args { .. } => EINVAL,
}
}
}
#[derive(Debug, Error, Serialize, Deserialize)]
pub enum Internal {
#[error("Invalid device information: {message}")]
DeviceInfo { message: String },
#[error("{error}")]
PublicError { error: Error },
}
impl From<Error> for Internal {
fn from(error: Error) -> Self {
Internal::PublicError { error }
}
}
impl From<rusb::Error> for Internal {
fn from(error: rusb::Error) -> Self {
Internal::PublicError {
error: error.into(),
}
}
}
impl From<rusb::Error> for Error {
fn from(err: rusb::Error) -> Self {
match err {
rusb::Error::NoDevice => Error::DeviceAccess {
kind: DeviceAccess::NoDevice,
},
rusb::Error::NotFound => Error::DeviceAccess {
kind: DeviceAccess::NotFound {
vid: XUM1541_VID,
pid: XUM1541_PID,
},
},
rusb::Error::Access => Error::DeviceAccess {
kind: DeviceAccess::Permission,
},
_ => Error::Usb(SerializableUsbError::UsbError {
message: err.to_string(),
}),
}
}
}
impl From<Communication> for Error {
fn from(kind: Communication) -> Self {
Self::Communication { kind }
}
}