use clap::ValueEnum;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
pub mod descriptors;
pub use descriptors::*;
pub mod path;
pub use path::*;
use crate::error::{self, Error, ErrorKind};
use crate::profiler::InternalData;
use crate::types::NumericalUnit;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Version(pub u8, pub u8, pub u8);
impl Version {
pub fn from_bcd(mut raw: u16) -> Self {
let sub_minor: u8 = (raw & 0x000F) as u8;
raw >>= 4;
let minor: u8 = (raw & 0x000F) as u8;
raw >>= 4;
let mut major: u8 = (raw & 0x000F) as u8;
raw >>= 4;
major += (10 * raw) as u8;
Version(major, minor, sub_minor)
}
pub fn major(self) -> u8 {
let Version(major, _, _) = self;
major
}
pub fn minor(self) -> u8 {
let Version(_, minor, _) = self;
minor
}
pub fn sub_minor(self) -> u8 {
let Version(_, _, sub_minor) = self;
sub_minor
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:x}.{:x}{:x}",
self.major(),
self.minor() & 0x0F,
self.sub_minor() & 0x0F
)
}
}
impl FromStr for Version {
type Err = Error;
fn from_str(s: &str) -> error::Result<Self> {
let (parse_ints, _): (Vec<Result<u8, _>>, Vec<_>) = s
.split('.')
.map(|vs| u8::from_str_radix(vs, 16))
.partition(Result::is_ok);
let numbers: Vec<u8> = parse_ints.into_iter().map(|v| v.unwrap()).collect();
match numbers.get(0..2) {
Some(slice) => Ok(Version(slice[0], (slice[1] & 0xF0) >> 4, slice[1] & 0x0F)),
None => Err(Error::new(
ErrorKind::Decoding,
&format!("No two base16 encoded versions in {s}"),
)),
}
}
}
impl TryFrom<f32> for Version {
type Error = Error;
fn try_from(f: f32) -> error::Result<Self> {
let s = format!("{f:2.2}");
let (parse_ints, _): (Vec<Result<u8, _>>, Vec<_>) = s
.split('.')
.map(|vs| vs.parse::<u8>())
.partition(Result::is_ok);
let numbers: Vec<u8> = parse_ints.into_iter().map(|v| v.unwrap()).collect();
match numbers.get(0..2) {
Some(slice) => Ok(Version(slice[0], (slice[1] & 0xF0) >> 4, slice[1] & 0x0F)),
None => Err(Error::new(
ErrorKind::Decoding,
&format!("Failed to parse float into MM.mP {f}"),
)),
}
}
}
impl From<Version> for u16 {
fn from(v: Version) -> Self {
let Version(major, minor, sub_minor) = v;
((major as u16) << 8) | ((minor as u16) << 4) | (sub_minor as u16)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ConfigAttributes {
BusPowered,
SelfPowered,
RemoteWakeup,
BatteryPowered,
}
impl fmt::Display for ConfigAttributes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl ConfigAttributes {
pub fn attributes_to_string(attributes: &[ConfigAttributes]) -> String {
let vec: Vec<String> = attributes.iter().map(|a| a.to_string()).collect();
vec.join(";")
}
}
#[derive(Debug)]
pub enum DescriptorUsage {
Device,
Interface,
Both,
}
#[derive(Debug, ValueEnum, Default, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
#[repr(u8)]
pub enum BaseClass {
#[default]
UseInterfaceDescriptor = 0x00,
Audio = 0x01,
#[serde(alias = "c-d-c-communications", alias = "CDC Communications")]
CdcCommunications = 0x02,
#[serde(alias = "h-i-d", alias = "HID")]
Hid = 0x03,
Physical = 0x05,
Image = 0x06,
Printer = 0x07,
MassStorage = 0x08,
Hub = 0x09,
#[serde(alias = "c-d-c-data", alias = "CDC Data")]
CdcData = 0x0a,
SmartCard = 0x0b,
ContentSecurity = 0x0d,
Video = 0x0e,
PersonalHealthcare = 0x0f,
AudioVideo = 0x10,
Billboard = 0x11,
#[serde(alias = "u-s-b-type-c-bridge", alias = "USB TypeC Bridge")]
UsbTypeCBridge = 0x12,
#[serde(alias = "b-d-p", alias = "BDP")]
Bdp = 0x13,
#[serde(alias = "m-c-t-p", alias = "MCTP")]
Mctp = 0x14,
#[serde(alias = "i-3-c-device", alias = "I3C Device")]
I3cDevice = 0x3c,
Diagnostic = 0xdc,
WirelessController = 0xe0,
Miscellaneous = 0xef,
ApplicationSpecificInterface = 0xfe,
VendorSpecificClass = 0xff,
}
impl fmt::Display for BaseClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<u8> for BaseClass {
fn from(b: u8) -> BaseClass {
match b {
0x00 => BaseClass::UseInterfaceDescriptor,
0x01 => BaseClass::Audio,
0x02 => BaseClass::CdcCommunications,
0x03 => BaseClass::Hid,
0x05 => BaseClass::Physical,
0x06 => BaseClass::Image,
0x07 => BaseClass::Printer,
0x08 => BaseClass::MassStorage,
0x09 => BaseClass::Hub,
0x0a => BaseClass::CdcData,
0x0b => BaseClass::SmartCard,
0x0d => BaseClass::ContentSecurity,
0x0e => BaseClass::Video,
0x0f => BaseClass::PersonalHealthcare,
0x10 => BaseClass::AudioVideo,
0x11 => BaseClass::Billboard,
0x12 => BaseClass::UsbTypeCBridge,
0x13 => BaseClass::Bdp,
0x14 => BaseClass::Mctp,
0x3c => BaseClass::I3cDevice,
0xdc => BaseClass::Diagnostic,
0xe0 => BaseClass::WirelessController,
0xef => BaseClass::Miscellaneous,
0xfe => BaseClass::ApplicationSpecificInterface,
0xff => BaseClass::VendorSpecificClass,
_ => BaseClass::UseInterfaceDescriptor,
}
}
}
impl From<BaseClass> for u8 {
fn from(val: BaseClass) -> Self {
val as u8
}
}
impl From<ClassCode> for BaseClass {
fn from(c: ClassCode) -> Self {
match c {
ClassCode::Generic(c) => c,
ClassCode::FullSpeedHub => BaseClass::Hub,
ClassCode::HighSpeedHubSingleTt => BaseClass::Hub,
ClassCode::HighSpeedHubMultiTt => BaseClass::Hub,
ClassCode::AudioVideoControlInterface => BaseClass::Audio,
ClassCode::AudioVideoDataVideo => BaseClass::Audio,
ClassCode::AudioVideoDataAudio => BaseClass::Audio,
ClassCode::MctpManagementController => BaseClass::Mctp,
ClassCode::MctpHostInterfaceEndpoint => BaseClass::Mctp,
ClassCode::Usb2ComplianceDevice => BaseClass::Diagnostic,
ClassCode::DebugTargetVendorDefined => BaseClass::Diagnostic,
ClassCode::GnuRemoteDebugCommandSet => BaseClass::Diagnostic,
ClassCode::VendorDefinedTraceDbC => BaseClass::Diagnostic,
ClassCode::VendorDefinedDfxDbC => BaseClass::Diagnostic,
ClassCode::VendorDefinedTraceGPDvC => BaseClass::Diagnostic,
ClassCode::GnuProtocolGpDvC => BaseClass::Diagnostic,
ClassCode::VendorDefinedDfxDvC => BaseClass::Diagnostic,
ClassCode::VendorDefinedTraceDvC => BaseClass::Diagnostic,
ClassCode::BluetoothProgrammingInterface => BaseClass::WirelessController,
ClassCode::UwbRadioControlInterface => BaseClass::WirelessController,
ClassCode::RemoteNdis => BaseClass::WirelessController,
ClassCode::BluetoothAmpController => BaseClass::WirelessController,
ClassCode::HostWireAdaptor => BaseClass::WirelessController,
ClassCode::DeviceWireAdaptor => BaseClass::WirelessController,
ClassCode::DeviceWireAdaptorIsochronous => BaseClass::WirelessController,
ClassCode::ActiveSync => BaseClass::Miscellaneous,
ClassCode::PalmSync => BaseClass::Miscellaneous,
ClassCode::InterfaceAssociationDescriptor => BaseClass::Miscellaneous,
ClassCode::WireAdaptorMultifunctionPeripheral => BaseClass::Miscellaneous,
ClassCode::CableBasedAssociationFramework => BaseClass::Miscellaneous,
ClassCode::RndisOverEthernet => BaseClass::Miscellaneous,
ClassCode::RndisOverWifi => BaseClass::Miscellaneous,
ClassCode::RndisOverWiMax => BaseClass::Miscellaneous,
ClassCode::RndisOverWwan => BaseClass::Miscellaneous,
ClassCode::RndisForRawIpv4 => BaseClass::Miscellaneous,
ClassCode::RndisForRawIpv6 => BaseClass::Miscellaneous,
ClassCode::RndisForGprs => BaseClass::Miscellaneous,
ClassCode::Usb3VisionControlInterface => BaseClass::Miscellaneous,
ClassCode::Usb3VisionEventInterface => BaseClass::Miscellaneous,
ClassCode::Usb3VisionStreamingInterface => BaseClass::Miscellaneous,
ClassCode::StepStreamTransport => BaseClass::Miscellaneous,
ClassCode::StepRawStreamTransport => BaseClass::Miscellaneous,
ClassCode::CommandInterfaceIad => BaseClass::Miscellaneous,
ClassCode::CommandInterfaceId => BaseClass::Miscellaneous,
ClassCode::MediaInterfaceId => BaseClass::Miscellaneous,
ClassCode::DeviceFirmwareUpgrade => BaseClass::ApplicationSpecificInterface,
ClassCode::IrdaBridge => BaseClass::ApplicationSpecificInterface,
ClassCode::UsbTestMeasurement => BaseClass::ApplicationSpecificInterface,
ClassCode::UsbTestMeasurementUsbTmc488 => BaseClass::ApplicationSpecificInterface,
}
}
}
impl BaseClass {
pub fn usage(&self) -> DescriptorUsage {
match self {
BaseClass::UseInterfaceDescriptor | BaseClass::Hub | BaseClass::Billboard => {
DescriptorUsage::Device
}
BaseClass::CdcCommunications
| BaseClass::Diagnostic
| BaseClass::Miscellaneous
| BaseClass::VendorSpecificClass => DescriptorUsage::Both,
_ => DescriptorUsage::Interface,
}
}
pub fn to_lsusb_string(&self) -> String {
match self {
BaseClass::Hid => "Human Interface Device".into(),
BaseClass::CdcCommunications => "Communications".into(),
BaseClass::WirelessController => "Wireless".into(),
_ => self.to_title_case(),
}
}
pub fn to_title_case(&self) -> String {
let title = heck::AsTitleCase(self.to_string()).to_string();
let split: Vec<&str> = title.split(' ').collect();
let first = split.first().unwrap_or(&"");
match first.to_owned() {
"Cdc" | "Usb" | "I3c" | "Hid" | "Bdp" | "Mctp" => {
title.replace(first, &first.to_uppercase())
}
_ => title,
}
}
}
impl From<BaseClass> for DescriptorUsage {
fn from(c: BaseClass) -> DescriptorUsage {
c.usage()
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum ClassCode {
Generic(BaseClass),
FullSpeedHub,
HighSpeedHubSingleTt,
HighSpeedHubMultiTt,
AudioVideoControlInterface,
AudioVideoDataVideo,
AudioVideoDataAudio,
MctpManagementController,
MctpHostInterfaceEndpoint,
Usb2ComplianceDevice,
DebugTargetVendorDefined,
GnuRemoteDebugCommandSet,
VendorDefinedTraceDbC,
VendorDefinedDfxDbC,
VendorDefinedTraceGPDvC,
GnuProtocolGpDvC,
VendorDefinedDfxDvC,
VendorDefinedTraceDvC,
BluetoothProgrammingInterface,
UwbRadioControlInterface,
RemoteNdis,
BluetoothAmpController,
HostWireAdaptor,
DeviceWireAdaptor,
DeviceWireAdaptorIsochronous,
ActiveSync,
PalmSync,
InterfaceAssociationDescriptor,
WireAdaptorMultifunctionPeripheral,
CableBasedAssociationFramework,
RndisOverEthernet,
RndisOverWifi,
RndisOverWiMax,
RndisOverWwan,
RndisForRawIpv4,
RndisForRawIpv6,
RndisForGprs,
Usb3VisionControlInterface,
Usb3VisionEventInterface,
Usb3VisionStreamingInterface,
StepStreamTransport,
StepRawStreamTransport,
CommandInterfaceIad,
CommandInterfaceId,
MediaInterfaceId,
DeviceFirmwareUpgrade,
IrdaBridge,
UsbTestMeasurement,
UsbTestMeasurementUsbTmc488,
}
pub type ClassCodeTriplet<T> = (T, u8, u8);
impl<T> From<ClassCodeTriplet<T>> for ClassCode
where
T: Into<BaseClass>,
{
fn from(triplet: ClassCodeTriplet<T>) -> Self {
match (triplet.0.into(), triplet.1, triplet.2) {
(BaseClass::Hub, 0x00, 0x00) => ClassCode::FullSpeedHub,
(BaseClass::Hub, 0x00, 0x01) => ClassCode::HighSpeedHubSingleTt,
(BaseClass::Hub, 0x00, 0x02) => ClassCode::HighSpeedHubMultiTt,
(BaseClass::Audio, 0x01, 0x00) => ClassCode::AudioVideoControlInterface,
(BaseClass::Audio, 0x02, 0x00) => ClassCode::AudioVideoDataVideo,
(BaseClass::Audio, 0x03, 0x00) => ClassCode::AudioVideoDataAudio,
(BaseClass::Mctp, 0x00, 0x01) => ClassCode::MctpManagementController,
(BaseClass::Mctp, 0x00, 0x02) => ClassCode::MctpHostInterfaceEndpoint,
(BaseClass::Diagnostic, 0x01, 0x01) => ClassCode::Usb2ComplianceDevice,
(BaseClass::Diagnostic, 0x02, 0x00) => ClassCode::DebugTargetVendorDefined,
(BaseClass::Diagnostic, 0x02, 0x01) => ClassCode::GnuRemoteDebugCommandSet,
(BaseClass::Diagnostic, 0x03, 0x01) => ClassCode::VendorDefinedTraceDbC,
(BaseClass::Diagnostic, 0x04, 0x01) => ClassCode::VendorDefinedDfxDbC,
(BaseClass::Diagnostic, 0x05, 0x00) => ClassCode::VendorDefinedTraceGPDvC,
(BaseClass::Diagnostic, 0x05, 0x01) => ClassCode::GnuProtocolGpDvC,
(BaseClass::Diagnostic, 0x06, 0x01) => ClassCode::VendorDefinedDfxDvC,
(BaseClass::Diagnostic, 0x07, 0x01) => ClassCode::VendorDefinedTraceDvC,
(BaseClass::WirelessController, 0x01, 0x01) => ClassCode::BluetoothProgrammingInterface,
(BaseClass::WirelessController, 0x01, 0x02) => ClassCode::UwbRadioControlInterface,
(BaseClass::WirelessController, 0x01, 0x03) => ClassCode::RemoteNdis,
(BaseClass::WirelessController, 0x01, 0x04) => ClassCode::BluetoothAmpController,
(BaseClass::WirelessController, 0x02, 0x01) => ClassCode::HostWireAdaptor,
(BaseClass::WirelessController, 0x02, 0x02) => ClassCode::DeviceWireAdaptor,
(BaseClass::WirelessController, 0x02, 0x03) => ClassCode::DeviceWireAdaptorIsochronous,
(BaseClass::Miscellaneous, 0x01, 0x01) => ClassCode::ActiveSync,
(BaseClass::Miscellaneous, 0x01, 0x02) => ClassCode::PalmSync,
(BaseClass::Miscellaneous, 0x02, 0x01) => ClassCode::InterfaceAssociationDescriptor,
(BaseClass::Miscellaneous, 0x02, 0x02) => ClassCode::WireAdaptorMultifunctionPeripheral,
(BaseClass::Miscellaneous, 0x03, 0x01) => ClassCode::CableBasedAssociationFramework,
(BaseClass::Miscellaneous, 0x04, 0x01) => ClassCode::RndisOverEthernet,
(BaseClass::Miscellaneous, 0x04, 0x02) => ClassCode::RndisOverWifi,
(BaseClass::Miscellaneous, 0x04, 0x03) => ClassCode::RndisOverWiMax,
(BaseClass::Miscellaneous, 0x04, 0x04) => ClassCode::RndisOverWwan,
(BaseClass::Miscellaneous, 0x04, 0x05) => ClassCode::RndisForRawIpv4,
(BaseClass::Miscellaneous, 0x04, 0x06) => ClassCode::RndisForRawIpv6,
(BaseClass::Miscellaneous, 0x04, 0x07) => ClassCode::RndisForGprs,
(BaseClass::Miscellaneous, 0x05, 0x00) => ClassCode::Usb3VisionControlInterface,
(BaseClass::Miscellaneous, 0x05, 0x01) => ClassCode::Usb3VisionEventInterface,
(BaseClass::Miscellaneous, 0x05, 0x02) => ClassCode::Usb3VisionStreamingInterface,
(BaseClass::Miscellaneous, 0x06, 0x01) => ClassCode::StepStreamTransport,
(BaseClass::Miscellaneous, 0x06, 0x02) => ClassCode::StepRawStreamTransport,
(BaseClass::Miscellaneous, 0x07, 0x01) => ClassCode::CommandInterfaceId,
(BaseClass::Miscellaneous, 0x07, 0x02) => ClassCode::MediaInterfaceId,
(BaseClass::ApplicationSpecificInterface, 0x01, 0x01) => {
ClassCode::DeviceFirmwareUpgrade
}
(BaseClass::ApplicationSpecificInterface, 0x02, 0x00) => ClassCode::IrdaBridge,
(BaseClass::ApplicationSpecificInterface, 0x03, 0x00) => ClassCode::UsbTestMeasurement,
(BaseClass::ApplicationSpecificInterface, 0x03, 0x01) => {
ClassCode::UsbTestMeasurementUsbTmc488
}
(c, _, _) => ClassCode::Generic(c),
}
}
}
impl fmt::Display for ClassCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<BaseClass> for ClassCode {
fn from(class: BaseClass) -> Self {
ClassCode::Generic(class)
}
}
impl ClassCode {
fn usage(&self) -> DescriptorUsage {
match self {
ClassCode::Generic(c) => c.usage(),
_ => DescriptorUsage::Interface,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(untagged, rename_all = "snake_case")]
#[allow(missing_docs)]
pub enum Speed {
Unknown,
LowSpeed,
FullSpeed,
HighSpeed,
HighBandwidth,
SuperSpeed,
SuperSpeedPlus,
SuperSpeedPlusX2,
Usb40Gbps,
Usb80Gbps,
}
impl FromStr for Speed {
type Err = Error;
fn from_str(s: &str) -> error::Result<Self> {
Ok(match s {
"80000" | "80.0 Gb/s" | "usb_80_gbps" | "usb80gbps" => Speed::Usb80Gbps,
"40000" | "40.0 Gb/s" | "usb_40_gbps" | "usb40gbps" => Speed::Usb40Gbps,
"20000" | "20.0 Gb/s" | "super_speed_plus_plus" | "super++" => Speed::SuperSpeedPlusX2,
"10000" | "10.0 Gb/s" | "super_speed_plus" | "super+" => Speed::SuperSpeedPlus,
"5000" | "5.0 Gb/s" | "super_speed" | "super" => Speed::SuperSpeed,
"480" | "480.0 Mb/s" | "high_speed" | "high_bandwidth" | "high" => Speed::HighSpeed,
"12" | "12.0 Mb/s" | "full_speed" | "full" => Speed::FullSpeed,
"1.5" | "1.5 Mb/s" | "low_speed" | "low" => Speed::LowSpeed,
_ => Speed::Unknown,
})
}
}
impl From<u8> for Speed {
fn from(b: u8) -> Self {
match b {
6 => Speed::SuperSpeedPlusX2,
5 => Speed::SuperSpeedPlus,
4 => Speed::SuperSpeed,
3 => Speed::HighSpeed,
2 => Speed::FullSpeed,
1 => Speed::LowSpeed,
_ => Speed::Unknown,
}
}
}
impl fmt::Display for Speed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
match self {
Speed::Usb80Gbps => write!(f, "USB80Gbps"),
Speed::Usb40Gbps => write!(f, "USB40Gbps"),
Speed::SuperSpeedPlusX2 => write!(f, "SuperSpeed20Gb/s"),
Speed::SuperSpeedPlus => write!(f, "SuperSpeed+"),
Speed::SuperSpeed => write!(f, "SuperSpeed"),
Speed::HighSpeed | Speed::HighBandwidth => write!(f, "HighSpeed"),
Speed::FullSpeed => write!(f, "FullSpeed"),
Speed::LowSpeed => write!(f, "LowSpeed"),
Speed::Unknown => write!(f, "Unknown"),
}
} else {
write!(
f,
"{}",
match self {
Speed::Usb80Gbps => "usb_80_gbps",
Speed::Usb40Gbps => "usb_40_gbps",
Speed::SuperSpeedPlusX2 => "super_speed_plus_plus",
Speed::SuperSpeedPlus => "super_speed_plus",
Speed::SuperSpeed => "super_speed",
Speed::HighSpeed | Speed::HighBandwidth => "high_speed",
Speed::FullSpeed => "full_speed",
Speed::LowSpeed => "low_speed",
Speed::Unknown => "unknown",
}
)
}
}
}
impl From<&Speed> for NumericalUnit<f32> {
fn from(speed: &Speed) -> NumericalUnit<f32> {
match speed {
Speed::Usb80Gbps => NumericalUnit {
value: 80.0,
unit: String::from("Gb/s"),
description: Some(speed.to_string()),
},
Speed::Usb40Gbps => NumericalUnit {
value: 40.0,
unit: String::from("Gb/s"),
description: Some(speed.to_string()),
},
Speed::SuperSpeedPlusX2 => NumericalUnit {
value: 20.0,
unit: String::from("Gb/s"),
description: Some(speed.to_string()),
},
Speed::SuperSpeedPlus => NumericalUnit {
value: 10.0,
unit: String::from("Gb/s"),
description: Some(speed.to_string()),
},
Speed::SuperSpeed => NumericalUnit {
value: 5.0,
unit: String::from("Gb/s"),
description: Some(speed.to_string()),
},
Speed::HighSpeed | Speed::HighBandwidth => NumericalUnit {
value: 480.0,
unit: String::from("Mb/s"),
description: Some(speed.to_string()),
},
Speed::FullSpeed => NumericalUnit {
value: 12.0,
unit: String::from("Mb/s"),
description: Some(speed.to_string()),
},
Speed::LowSpeed => NumericalUnit {
value: 1.5,
unit: String::from("Mb/s"),
description: Some(speed.to_string()),
},
Speed::Unknown => NumericalUnit {
value: 0.0,
unit: String::from("Mb/s"),
description: Some(speed.to_string()),
},
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct OperationMode {
pub version: Version,
pub generation: Option<u8>,
pub lanes: Option<u8>,
}
impl fmt::Display for OperationMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.version.major() >= 4 {
write!(f, "USB{}", self.version.major())?;
} else {
write!(f, "USB {}.{}", self.version.major(), self.version.minor())?;
}
if let (Some(gen), Some(lanes)) = (self.generation, self.lanes) {
write!(f, " Gen {}x{}", gen, lanes)?;
}
Ok(())
}
}
impl Speed {
pub fn to_lsusb_speed(&self) -> String {
let dv = NumericalUnit::<f32>::from(self);
let prefix = dv.unit.chars().next().unwrap_or('M');
match prefix {
'G' => format!("{:.0}{}", dv.value * 1000.0, 'M'),
_ => format!("{:.0}{}", dv.value, prefix),
}
}
pub fn original_label(&self) -> String {
format!("{:#}", self)
}
pub fn marketing_label(&self) -> String {
match self {
Speed::Usb80Gbps => "USB 80Gbps".into(),
Speed::Usb40Gbps => "USB 40Gbps".into(),
Speed::SuperSpeedPlusX2 => "USB 20Gbps".into(),
Speed::SuperSpeedPlus => "USB 10Gbps".into(),
Speed::SuperSpeed => "USB 5Gbps".into(),
Speed::HighSpeed | Speed::HighBandwidth => "High-Speed".into(),
Speed::FullSpeed | Speed::LowSpeed => "Basic-Speed".into(),
Speed::Unknown => "Unknown".into(),
}
}
pub fn operation_mode(&self) -> Option<OperationMode> {
let mode = |major, minor, gen, lanes| OperationMode {
version: Version(major, minor, 0),
generation: gen,
lanes,
};
match self {
Speed::LowSpeed => Some(mode(1, 0, None, None)),
Speed::FullSpeed => Some(mode(1, 1, None, None)),
Speed::HighSpeed | Speed::HighBandwidth => Some(mode(2, 0, None, None)),
Speed::SuperSpeed => Some(mode(3, 2, Some(1), Some(1))),
Speed::SuperSpeedPlus => Some(mode(3, 2, Some(2), Some(1))),
Speed::SuperSpeedPlusX2 => Some(mode(3, 2, Some(2), Some(2))),
Speed::Usb40Gbps => Some(mode(4, 0, Some(3), Some(2))),
Speed::Usb80Gbps => Some(mode(4, 0, Some(4), Some(2))),
Speed::Unknown => None,
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
Out,
In,
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
match self {
Direction::Out => write!(f, "OUT"),
Direction::In => write!(f, "IN"),
}
} else {
write!(f, "{self:?}")
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[repr(u8)]
pub enum TransferType {
Control,
Isochronous,
Bulk,
Interrupt,
}
impl fmt::Display for TransferType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<u8> for TransferType {
fn from(b: u8) -> Self {
match b & 0x03 {
0 => TransferType::Control,
1 => TransferType::Isochronous,
2 => TransferType::Bulk,
3 => TransferType::Interrupt,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[repr(u8)]
pub enum SyncType {
None,
Asynchronous,
Adaptive,
Synchronous,
}
impl fmt::Display for SyncType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<u8> for SyncType {
fn from(b: u8) -> Self {
match (b & 0x0c) >> 2 {
0 => SyncType::None,
1 => SyncType::Asynchronous,
2 => SyncType::Adaptive,
3 => SyncType::Synchronous,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[repr(u8)]
#[non_exhaustive]
pub enum UsageType {
Data,
Feedback,
FeedbackData,
Reserved,
}
impl fmt::Display for UsageType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<u8> for UsageType {
fn from(b: u8) -> Self {
match (b & 0x30) >> 4 {
0 => UsageType::Data,
1 => UsageType::Feedback,
2 => UsageType::FeedbackData,
3 => UsageType::Reserved,
_ => unreachable!(),
}
}
}
fn default_device_desc_length() -> u8 {
18
}
fn default_configuration_desc_length() -> u8 {
9
}
fn default_interface_desc_length() -> u8 {
9
}
fn default_endpoint_desc_length() -> u8 {
7
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct EndpointAddress {
pub address: u8,
pub number: u8,
pub direction: Direction,
}
impl From<u8> for EndpointAddress {
fn from(b: u8) -> Self {
EndpointAddress {
address: b,
number: b & 0x0f,
direction: if b & 0x80 == 0 {
Direction::Out
} else {
Direction::In
},
}
}
}
impl From<EndpointAddress> for u8 {
fn from(addr: EndpointAddress) -> u8 {
addr.address
}
}
impl From<EndpointPath> for EndpointAddress {
fn from(path: EndpointPath) -> Self {
path.endpoint_address()
}
}
impl fmt::Display for EndpointAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "EP {} {}", self.number, self.direction)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Endpoint {
#[serde(default = "default_endpoint_desc_length")] pub length: u8,
pub address: EndpointAddress,
pub transfer_type: TransferType,
pub sync_type: SyncType,
pub usage_type: UsageType,
pub max_packet_size: u16,
pub interval: u8,
#[serde(default)] pub extra: Option<Vec<Descriptor>>,
#[serde(skip)]
pub(crate) internal: InternalData,
pub(crate) endpoint_path: Option<EndpointPath>,
}
#[deprecated(since = "2.0.0", note = "Use Endpoint instead")]
pub type USBEndpoint = Endpoint;
impl Endpoint {
pub fn max_packet_string(&self) -> String {
format!(
"{}x {}",
self.packets_per_microframe(),
self.max_packet_size & 0x7ff
)
}
pub fn max_packet_size(&self) -> usize {
(self.max_packet_size & ((1 << 11) - 1)) as usize
}
pub fn packets_per_microframe(&self) -> u8 {
((self.max_packet_size >> 11) & 0b11) as u8 + 1
}
pub fn attributes(&self) -> u8 {
self.transfer_type.to_owned() as u8
| ((self.sync_type.to_owned() as u8) << 2)
| ((self.usage_type.to_owned() as u8) << 4)
}
pub fn is_expanded(&self) -> bool {
self.internal.expanded
}
pub fn set_expanded(&mut self, expanded: bool) {
self.internal.expanded = expanded;
}
pub fn toggle_expanded(&mut self) {
self.internal.expanded = !self.internal.expanded;
}
pub fn endpoint_path(&self) -> Option<EndpointPath> {
self.endpoint_path.to_owned()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Interface {
pub name: Option<String>,
#[serde(default)]
pub string_index: u8,
pub number: u8,
pub path: String,
pub class: BaseClass,
pub sub_class: u8,
pub protocol: u8,
pub alt_setting: u8,
#[serde(default)]
pub active: bool,
pub driver: Option<String>,
pub syspath: Option<String>,
pub endpoints: Vec<Endpoint>,
#[serde(default = "default_interface_desc_length")]
pub length: u8,
#[serde(default)] pub extra: Option<Vec<Descriptor>>,
#[serde(skip)]
pub(crate) internal: InternalData,
pub(crate) device_path: Option<DevicePath>,
pub(crate) devpaths: Option<Vec<PathBuf>>,
pub(crate) mount_paths: Option<Vec<(PathBuf, PathBuf)>>,
}
#[deprecated(since = "2.0.0", note = "Use Interface instead")]
pub type USBInterface = Interface;
impl Interface {
pub fn sysfs_name(&self) -> String {
self.path.to_owned()
}
pub fn sysfs_path(&self) -> Option<PathBuf> {
self.syspath.as_ref().map(PathBuf::from)
}
pub fn device_path(&self) -> Option<DevicePath> {
if let Some(ref dp) = self.device_path {
Some(dp.to_owned())
} else {
let mut dp = DevicePath::from_str(&self.path).ok()?;
dp.set_alt_setting(self.alt_setting);
Some(dp)
}
}
pub fn class_name(&self) -> Option<&str> {
usb_ids::Classes::iter()
.find(|c| c.id() == u8::from(self.class))
.map(|c| c.name())
}
pub fn sub_class_name(&self) -> Option<&str> {
usb_ids::SubClass::from_cid_scid(u8::from(self.class), self.sub_class).map(|sc| sc.name())
}
pub fn protocol_name(&self) -> Option<&str> {
usb_ids::Protocol::from_cid_scid_pid(u8::from(self.class), self.sub_class, self.protocol)
.map(|p| p.name())
}
pub fn fully_defined_class(&self) -> ClassCode {
(self.class, self.sub_class, self.protocol).into()
}
pub fn is_expanded(&self) -> bool {
self.internal.expanded
}
pub fn toggle_expanded(&mut self) {
self.internal.expanded = !self.internal.expanded;
}
pub fn set_all_expanded(&mut self, expanded: bool) {
self.internal.expanded = expanded;
for endpoint in self.endpoints.iter_mut() {
endpoint.set_expanded(expanded);
}
}
pub fn dev_paths(&self) -> Option<Vec<PathBuf>> {
if self.devpaths.is_some() {
return self.devpaths.to_owned();
}
match self.class {
BaseClass::CdcData | BaseClass::CdcCommunications => self
.sysfs_path()
.and_then(|p| get_serial_device_path(&p).map(|dp| vec![dp])),
BaseClass::MassStorage => self.sysfs_path().and_then(|p| get_block_device_path(&p)),
_ => None,
}
}
pub fn block_mount_paths(&self) -> Option<Vec<(PathBuf, PathBuf)>> {
if self.mount_paths.is_some() {
return self.mount_paths.to_owned();
}
#[cfg(target_os = "linux")]
{
let dev_paths = self.dev_paths()?;
let mut all_mounts = Vec::new();
for dev_path in dev_paths {
if let Ok(Some(mounts)) = get_usb_mounts(&dev_path) {
all_mounts.extend(mounts);
}
}
if all_mounts.is_empty() {
None
} else {
Some(all_mounts)
}
}
#[cfg(not(target_os = "linux"))]
{
None
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Configuration {
pub name: String,
#[serde(default)]
pub string_index: u8,
pub number: u8,
#[serde(default)]
pub active: bool,
#[serde(default)]
pub num_interfaces: Option<u8>,
pub interfaces: Vec<Interface>,
pub attributes: Vec<ConfigAttributes>,
pub max_power: NumericalUnit<u32>,
#[serde(default = "default_configuration_desc_length")]
pub length: u8,
#[serde(default)]
pub total_length: u16,
#[serde(default)] pub extra: Option<Vec<Descriptor>>,
#[serde(skip)]
pub(crate) internal: InternalData,
}
#[deprecated(since = "2.0.0", note = "Use Configuration instead")]
pub type USBConfiguration = Configuration;
impl Configuration {
pub fn attributes_string(&self) -> String {
ConfigAttributes::attributes_to_string(&self.attributes)
}
pub fn attributes_value(&self) -> u8 {
let mut ret: u8 = 0x80; for attr in self.attributes.iter() {
match attr {
ConfigAttributes::SelfPowered => ret |= 0x40,
ConfigAttributes::RemoteWakeup => ret |= 0x20,
ConfigAttributes::BatteryPowered => ret |= 0x10,
_ => (),
}
}
ret
}
pub fn is_expanded(&self) -> bool {
self.internal.expanded
}
pub fn toggle_expanded(&mut self) {
self.internal.expanded = !self.internal.expanded;
}
pub fn set_all_expanded(&mut self, expanded: bool) {
self.internal.expanded = expanded;
for interface in self.interfaces.iter_mut() {
interface.set_all_expanded(expanded);
}
}
fn device_path(&self) -> Option<DevicePath> {
self.interfaces.first().and_then(|i| i.device_path())
}
pub fn parent_port_path(&self) -> Option<PortPath> {
self.device_path().map(|p| p.port_path().to_owned())
}
pub fn configuration_path(&self) -> Option<ConfigurationPath> {
self.parent_port_path().map(|p| (p, self.number))
}
pub fn number_of_interfaces(&self) -> u8 {
if let Some(num) = self.num_interfaces {
num
} else {
self.interfaces
.iter()
.map(|interface| interface.number)
.collect::<HashSet<_>>()
.len() as u8
}
}
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceExtra {
pub max_packet_size: u8,
pub driver: Option<String>,
pub syspath: Option<String>,
pub vendor: Option<String>,
pub product_name: Option<String>,
#[serde(default)]
pub string_indexes: (u8, u8, u8),
pub configurations: Vec<Configuration>,
pub status: Option<u16>,
pub debug: Option<DebugDescriptor>,
pub binary_object_store: Option<bos::BinaryObjectStoreDescriptor>,
pub qualifier: Option<DeviceQualifierDescriptor>,
pub hub: Option<HubDescriptor>,
pub negotiated_speed: Option<Speed>,
}
#[deprecated(since = "2.0.0", note = "Use DeviceExtra instead")]
pub type USBDeviceExtra = DeviceExtra;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_to_string() {
assert_eq!(Version(155, 0, 0).to_string(), "9b.00");
assert_eq!(Version(10, 4, 15).to_string(), "a.4f");
assert_eq!(Version(2, 0, 1).to_string(), "2.01");
}
#[test]
fn test_version_from_f32() {
assert_eq!(Version::try_from(155.0).unwrap(), Version(155, 0, 0));
assert_eq!(Version::try_from(101.0).unwrap(), Version(101, 0, 0));
assert_eq!(Version::try_from(2.01).unwrap(), Version(2, 0, 1));
assert_eq!(Version::try_from(2.31).unwrap(), Version(2, 1, 15));
}
}