use std::collections::BTreeMap;
use std::path::PathBuf;
use serde::Serialize;
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct UsbInterface {
pub number: u32,
pub class_code: u8,
pub sub_class: u8,
pub protocol: u8,
pub driver: String,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct UsbDevice {
pub sysfs_path: PathBuf,
pub bus_port: String,
pub vendor_id: u16,
pub product_id: u16,
pub manufacturer: String,
pub product: String,
pub serial: String,
pub version: String,
pub speed: u32,
pub max_power_ma: u32,
pub device_class: u8,
pub device_sub_class: u8,
pub device_protocol: u8,
pub bus_num: u32,
pub dev_num: u32,
pub rx_lanes: u32,
pub tx_lanes: u32,
pub removable: String,
pub num_interfaces: u32,
pub num_configurations: u32,
pub interfaces: Vec<UsbInterface>,
pub children: Vec<UsbDevice>,
pub is_hub: bool,
pub is_root_hub: bool,
pub raw_attributes: BTreeMap<String, String>,
}
impl UsbDevice {
pub fn display_name(&self) -> String {
if !self.product.is_empty() {
self.product.clone()
} else {
format!("{:04x}:{:04x}", self.vendor_id, self.product_id)
}
}
pub fn speed_label(&self) -> &'static str {
speed_label(self.speed)
}
pub fn power_label(&self) -> Option<String> {
if self.max_power_ma == 0 {
return None;
}
Some(if self.max_power_ma >= 1000 {
format!("{:.1} W", self.max_power_ma as f64 / 1000.0)
} else {
format!("{} mA", self.max_power_ma)
})
}
}
pub fn speed_label(speed: u32) -> &'static str {
match speed {
s if s >= 20000 => "USB4 20 Gbps",
s if s >= 10000 => "SuperSpeed+ 10 Gbps",
s if s >= 5000 => "SuperSpeed 5 Gbps",
s if s >= 480 => "High Speed 480 Mbps",
s if s >= 12 => "Full Speed 12 Mbps",
s if s >= 2 => "Low Speed 1.5 Mbps",
_ => "Unknown speed",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn speed_label_thresholds() {
assert_eq!(speed_label(0), "Unknown speed");
assert_eq!(speed_label(2), "Low Speed 1.5 Mbps");
assert_eq!(speed_label(12), "Full Speed 12 Mbps");
assert_eq!(speed_label(480), "High Speed 480 Mbps");
assert_eq!(speed_label(5000), "SuperSpeed 5 Gbps");
assert_eq!(speed_label(10000), "SuperSpeed+ 10 Gbps");
assert_eq!(speed_label(20000), "USB4 20 Gbps");
}
#[test]
fn power_label_formats() {
let d = UsbDevice {
max_power_ma: 0,
..Default::default()
};
assert!(d.power_label().is_none());
let d = UsbDevice {
max_power_ma: 100,
..Default::default()
};
assert_eq!(d.power_label().as_deref(), Some("100 mA"));
let d = UsbDevice {
max_power_ma: 1500,
..Default::default()
};
assert_eq!(d.power_label().as_deref(), Some("1.5 W"));
}
#[test]
fn display_name_falls_back_to_vidpid() {
let mut d = UsbDevice {
vendor_id: 0x05AC,
product_id: 0x12A8,
..Default::default()
};
assert_eq!(d.display_name(), "05ac:12a8");
d.product = "iPhone".into();
assert_eq!(d.display_name(), "iPhone");
}
}