use std::fmt::{self, Display};
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::path::PathBuf;
use color_eyre::eyre::Result;
use nusb::DeviceInfo;
use thiserror::Error;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Vid(pub u16);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Pid(pub u16);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct InterfaceClass(pub u8);
impl InterfaceClass
{
pub const APPLICATION_SPECIFIC: Self = Self(0xfe);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct InterfaceSubClass(pub u8);
impl InterfaceSubClass
{
pub const DFU: Self = Self(0x01);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct InterfaceProtocol(pub u8);
impl InterfaceProtocol
{
#[allow(dead_code)] pub const DFU_DFU_MODE: Self = Self(0x02);
#[allow(dead_code)] pub const DFU_RUNTIME_MODE: Self = Self(0x01);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[allow(dead_code)]
pub enum DfuRequest
{
Detach = 0,
Dnload = 1,
Upload = 2,
GetStatus = 3,
ClrStatus = 4,
GetState = 5,
Abort = 6,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum DfuOperatingMode
{
Runtime,
FirmwareUpgrade,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GenericDescriptorRef<'a>
{
pub raw: &'a [u8],
}
impl<'a> GenericDescriptorRef<'a>
{
pub fn single_from_bytes(bytes: &'a [u8]) -> Self
{
let length = bytes[0] as usize;
Self {
raw: &bytes[0..length],
}
}
pub fn multiple_from_bytes(bytes: &'a [u8]) -> Vec<Self>
{
let mut v: Vec<Self> = Vec::new();
let mut current_bytes = &bytes[0..];
loop {
let descriptor = Self::single_from_bytes(current_bytes);
let parsed_count = descriptor.length_usize();
let remaining = current_bytes.len() - parsed_count;
v.push(descriptor);
if remaining == 0 {
break;
} else if remaining > 2 {
current_bytes = ¤t_bytes[parsed_count..];
} else {
panic!("Descriptor seems to have an invalid size of {}!", remaining);
}
}
v
}
#[allow(dead_code)] pub fn length(&self) -> u8
{
self.raw[0]
}
pub fn length_usize(&self) -> usize
{
self.raw[0] as usize
}
pub fn descriptor_type(&self) -> u8
{
self.raw[1]
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
pub enum DescriptorConvertError
{
#[error(
"bLength field ({provided_length}) in provided data does not match the correct value({correct_length}) for \
this descriptor type"
)]
LengthFieldMismatch
{
provided_length: u8, correct_length: u8
},
#[error(
"bDescriptorType field ({provided_type}) in provided data does not match the correctvalue ({correct_type}) \
for this descriptor type"
)]
DescriptorTypeMismatch
{
provided_type: u8, correct_type: u8
},
}
#[allow(non_snake_case)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct DfuFunctionalDescriptor
{
pub bLength: u8, pub bDescriptorType: u8, pub bmAttributes: u8,
pub wDetachTimeOut: u16,
pub wTransferSize: u16,
pub bcdDFUVersion: u16,
}
impl DfuFunctionalDescriptor
{
pub const LENGTH: u8 = 0x09;
pub const TYPE: u8 = 0x21;
pub fn copy_from_bytes(bytes: &[u8; 0x09]) -> Result<Self, DescriptorConvertError>
{
if bytes[0] != Self::LENGTH {
return Err(DescriptorConvertError::LengthFieldMismatch {
provided_length: bytes[0],
correct_length: Self::LENGTH,
});
}
if bytes[1] != Self::TYPE {
return Err(DescriptorConvertError::DescriptorTypeMismatch {
provided_type: bytes[0],
correct_type: Self::TYPE,
});
}
Ok(Self {
bLength: bytes[0],
bDescriptorType: bytes[1],
bmAttributes: bytes[2],
wDetachTimeOut: u16::from_le_bytes(bytes[3..=4].try_into().unwrap()),
wTransferSize: u16::from_le_bytes(bytes[5..=6].try_into().unwrap()),
bcdDFUVersion: u16::from_le_bytes(bytes[7..=8].try_into().unwrap()),
})
}
}
#[derive(Debug, Eq, Clone)]
pub struct PortId
{
bus_number: u8,
#[cfg(any(target_os = "linux", target_os = "android"))]
path: PathBuf,
#[cfg(target_os = "windows")]
port_number: u32,
#[cfg(target_os = "macos")]
location: u32,
}
impl PortId
{
pub fn new(device: &DeviceInfo) -> Self
{
Self {
bus_number: device.bus_number(),
#[cfg(any(target_os = "linux", target_os = "android"))]
path: device.sysfs_path().to_path_buf(),
#[cfg(target_os = "windows")]
port_number: device.port_number(),
#[cfg(target_os = "macos")]
location: device.location_id(),
}
}
}
impl PartialEq for PortId
{
#[cfg(any(target_os = "linux", target_os = "android"))]
fn eq(&self, other: &Self) -> bool
{
self.bus_number == other.bus_number && self.path == other.path
}
#[cfg(target_os = "windows")]
fn eq(&self, other: &Self) -> bool
{
self.bus_number == other.bus_number && self.port_number == other.port_number
}
#[cfg(target_os = "macos")]
fn eq(&self, other: &Self) -> bool
{
self.bus_number == other.bus_number && self.location == other.location
}
}
impl Display for PortId
{
#[cfg(any(target_os = "linux", target_os = "android"))]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
{
let port = self.path.file_name().map_or_else(
|| Ok("Invalid PortId (bad path)".into()),
|name| name.to_os_string().into_string(),
);
match port {
Ok(port) => write!(f, "{}", port),
Err(_) => Err(fmt::Error),
}
}
#[cfg(target_os = "windows")]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
{
write!(f, "{}-{}", self.bus_number, self.port_number)
}
#[cfg(target_os = "macos")]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
{
write!(f, "{}-{}", self.bus_number, self.location)
}
}