use crate::error::Result;
use itertools::Itertools;
use std::collections::HashMap;
use crate::error::{Error, ErrorKind};
#[cfg(all(target_os = "linux", any(feature = "udev", feature = "udevlib")))]
use crate::udev;
use crate::usb;
const REQUEST_GET_DESCRIPTOR: u8 = 0x06;
const REQUEST_GET_STATUS: u8 = 0x00;
const REQUEST_WEBUSB_URL: u8 = 0x02;
pub(crate) const SYSFS_USB_PREFIX: &str = "/sys/bus/usb/devices/";
pub(crate) const SYSFS_PCI_PREFIX: &str = "/sys/bus/pci/devices/";
pub mod types;
pub use types::*;
#[cfg(feature = "libusb")]
pub mod libusb;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(feature = "nusb")]
pub mod nusb;
#[cfg(all(feature = "nusb", feature = "watch"))]
pub mod watch;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum Direction {
Out = 0,
In = 1,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum ControlType {
Standard = 0,
Class = 1,
Vendor = 2,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum Recipient {
Device = 0,
Interface = 1,
Endpoint = 2,
Other = 3,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct ControlRequest {
pub control_type: ControlType,
pub recipient: Recipient,
pub request: u8,
pub value: u16,
pub index: u16,
pub length: usize,
pub claim_interface: bool,
}
pub(crate) trait UsbOperations {
fn get_descriptor_string(&self, string_index: u8) -> Option<String>;
fn get_control_msg(&self, control_request: ControlRequest) -> Result<Vec<u8>>;
}
pub(crate) trait Profiler<T>
where
T: UsbOperations + std::fmt::Debug,
Self: std::fmt::Debug,
{
fn get_report_descriptor(device: &T, index: u16, length: u16) -> Result<Vec<u8>> {
let control_request = ControlRequest {
control_type: ControlType::Standard,
recipient: Recipient::Interface,
request: REQUEST_GET_DESCRIPTOR,
value: (u8::from(usb::DescriptorType::Report) as u16) << 8,
index,
length: length as usize,
claim_interface: cfg!(target_os = "linux") || cfg!(target_os = "android"),
};
device.get_control_msg(control_request)
}
fn get_hub_descriptor(
device: &T,
protocol: u8,
bcd: u16,
has_ssp: bool,
) -> Result<usb::HubDescriptor> {
let is_ext_status = protocol == 3 && bcd >= 0x0310 && has_ssp;
let value = if bcd >= 0x0300 {
(u8::from(usb::DescriptorType::SuperSpeedHub) as u16) << 8
} else {
(u8::from(usb::DescriptorType::Hub) as u16) << 8
};
let control = ControlRequest {
control_type: ControlType::Class,
request: REQUEST_GET_DESCRIPTOR,
value,
index: 0,
recipient: Recipient::Device,
length: 12,
claim_interface: false,
};
let data = match device.get_control_msg(control) {
Ok(data) => data,
Err(_) => {
let control = ControlRequest {
control_type: ControlType::Class,
request: REQUEST_GET_DESCRIPTOR,
value,
index: 0,
recipient: Recipient::Device,
length: 9,
claim_interface: false,
};
device.get_control_msg(control)?
}
};
let mut hub = usb::HubDescriptor::try_from(data.as_slice())?;
let mut port_statues: Vec<[u8; 8]> = Vec::with_capacity(hub.num_ports as usize);
for p in 0..hub.num_ports {
let control = ControlRequest {
control_type: ControlType::Class,
request: REQUEST_GET_STATUS,
value: if is_ext_status { 2 } else { 0 },
index: p as u16 + 1,
recipient: Recipient::Other,
length: if is_ext_status { 8 } else { 4 },
claim_interface: false,
};
match device.get_control_msg(control) {
Ok(mut data) => {
if data.len() < 8 {
let remaining = 8 - data.len();
data.extend(vec![0; remaining]);
}
port_statues.push(data.try_into().unwrap());
}
Err(e) => {
log::warn!(
"Failed to get port {} status for {:?}: {}",
p + 1,
device,
e
);
return Ok(hub);
}
}
}
hub.port_statuses = Some(port_statues);
Ok(hub)
}
fn get_device_status(device: &T) -> Result<u16> {
let control = ControlRequest {
control_type: ControlType::Standard,
request: REQUEST_GET_STATUS,
value: 0,
index: 0,
recipient: Recipient::Device,
length: 2,
claim_interface: false,
};
let data = device.get_control_msg(control)?;
Ok(u16::from_le_bytes([data[0], data[1]]))
}
fn get_debug_descriptor(device: &T) -> Result<usb::DebugDescriptor> {
let control = ControlRequest {
control_type: ControlType::Standard,
request: REQUEST_GET_DESCRIPTOR,
value: (u8::from(usb::DescriptorType::Debug) as u16) << 8,
index: 0,
recipient: Recipient::Device,
length: 2,
claim_interface: cfg!(target_os = "macos"),
};
let data = device.get_control_msg(control)?;
usb::DebugDescriptor::try_from(data.as_slice())
}
fn get_bos_descriptor(
device: &T,
) -> Result<usb::descriptors::bos::BinaryObjectStoreDescriptor> {
let mut control = ControlRequest {
control_type: ControlType::Standard,
request: REQUEST_GET_DESCRIPTOR,
value: (u8::from(usb::DescriptorType::Bos) as u16) << 8,
index: 0,
recipient: Recipient::Device,
length: 5,
claim_interface: false,
};
let data = device.get_control_msg(control)?;
let total_length = u16::from_le_bytes([data[2], data[3]]);
log::debug!("{device:?} Attempt read BOS descriptor total length: {total_length}");
control.length = total_length as usize;
let data = device.get_control_msg(control)?;
log::debug!("{device:?} BOS descriptor data: {data:?}");
let mut bos =
usb::descriptors::bos::BinaryObjectStoreDescriptor::try_from(data.as_slice())?;
for c in bos.capabilities.iter_mut() {
match c {
usb::descriptors::bos::BosCapability::WebUsbPlatform(w) => {
w.url = Self::get_webusb_url(device, w.vendor_code, w.landing_page_index).ok();
log::trace!("{:?} WebUSB URL: {:?}", device, w.url);
}
usb::descriptors::bos::BosCapability::Billboard(ref mut b) => {
b.additional_info_url =
device.get_descriptor_string(b.additional_info_url_index);
for a in b.alternate_modes.iter_mut() {
a.alternate_mode_string =
device.get_descriptor_string(a.alternate_mode_string_index);
}
}
_ => (),
}
}
Ok(bos)
}
fn get_device_qualifier(device: &T) -> Result<usb::DeviceQualifierDescriptor> {
let control = ControlRequest {
control_type: ControlType::Standard,
request: REQUEST_GET_DESCRIPTOR,
value: (u8::from(usb::DescriptorType::DeviceQualifier) as u16) << 8,
index: 0,
recipient: Recipient::Device,
length: 10,
claim_interface: false,
};
let data = device.get_control_msg(control)?;
log::debug!("{device:?} Qualifier descriptor data: {data:?}");
usb::DeviceQualifierDescriptor::try_from(data.as_slice())
}
fn get_webusb_url(device: &T, vendor_request: u8, index: u8) -> Result<String> {
let control = ControlRequest {
control_type: ControlType::Vendor,
request: vendor_request,
value: index as u16,
index: (REQUEST_WEBUSB_URL as u16) << 8,
recipient: Recipient::Device,
length: 3,
claim_interface: false,
};
let data = device.get_control_msg(control)?;
log::trace!("WebUSB URL descriptor data: {data:?}");
let len = data[0] as usize;
if data[1] != u8::from(usb::DescriptorType::String) {
return Err(Error {
kind: ErrorKind::Parsing,
message: "Failed to parse WebUSB URL: Bad URL descriptor type".to_string(),
});
}
if data.len() < len {
return Err(Error {
kind: ErrorKind::Parsing,
message: "Failed to parse WebUSB URL: Data length mismatch".to_string(),
});
}
let url = String::from_utf8(data[3..len].to_vec()).map_err(|e| Error {
kind: ErrorKind::Parsing,
message: format!("Failed to parse WebUSB URL: {e}"),
})?;
match data[2] {
0x00 => Ok(format!("http://{url}")),
0x01 => Ok(format!("https://{url}")),
0xFF => Ok(url),
_ => Err(Error {
kind: ErrorKind::Parsing,
message: "Failed to parse WebUSB URL: Bad URL scheme".to_string(),
}),
}
}
fn build_descriptor_extra<C: Into<usb::BaseClass> + Copy>(
&self,
device: &T,
class_code: Option<usb::ClassCodeTriplet<C>>,
interface_number: Option<u8>,
extra_bytes: &[u8],
) -> Result<usb::Descriptor> {
let mut dt = match usb::Descriptor::try_from(extra_bytes) {
Ok(d) => d,
Err(e) => {
log::debug!("{device:?} Failed to convert extra descriptor bytes: {e}");
return Err(e);
}
};
if let Some(interface_desc) = class_code {
if let Err(e) = dt.update_with_class_context(interface_desc) {
log::debug!("{device:?} Failed to update extra descriptor with class context: {e}");
}
}
match dt {
usb::Descriptor::InterfaceAssociation(ref mut iad) => {
iad.function_string = device.get_descriptor_string(iad.function_string_index);
}
usb::Descriptor::Device(ref mut c)
| usb::Descriptor::Interface(ref mut c)
| usb::Descriptor::Endpoint(ref mut c) => match c {
usb::ClassDescriptor::Printer(ref mut p) => {
for pd in p.descriptors.iter_mut() {
pd.uuid_string = device.get_descriptor_string(pd.uuid_string_index);
}
}
usb::ClassDescriptor::Communication(ref mut cdc) => match cdc.interface {
usb::descriptors::cdc::CdcInterfaceDescriptor::CountrySelection(ref mut d) => {
d.country_code_date =
device.get_descriptor_string(d.country_code_date_index);
}
usb::descriptors::cdc::CdcInterfaceDescriptor::NetworkChannel(ref mut d) => {
d.name = device.get_descriptor_string(d.name_string_index);
}
usb::descriptors::cdc::CdcInterfaceDescriptor::EthernetNetworking(
ref mut d,
) => {
d.mac_address = device.get_descriptor_string(d.mac_address_index);
}
usb::descriptors::cdc::CdcInterfaceDescriptor::CommandSet(ref mut d) => {
d.command_set_string =
device.get_descriptor_string(d.command_set_string_index);
}
_ => (),
},
usb::ClassDescriptor::Hid(ref mut hd) => {
for rd in hd.descriptors.iter_mut() {
if let Some(index) = interface_number {
rd.data =
Self::get_report_descriptor(device, index as u16, rd.length).ok();
}
}
}
usb::ClassDescriptor::Midi(ref mut md, _) => match md.interface {
usb::descriptors::audio::MidiInterfaceDescriptor::InputJack(ref mut mh) => {
mh.jack_string = device.get_descriptor_string(mh.jack_string_index);
}
usb::descriptors::audio::MidiInterfaceDescriptor::OutputJack(ref mut mh) => {
mh.jack_string = device.get_descriptor_string(mh.jack_string_index);
}
usb::descriptors::audio::MidiInterfaceDescriptor::Element(ref mut mh) => {
mh.element_string = device.get_descriptor_string(mh.element_string_index);
}
_ => (),
},
usb::ClassDescriptor::Audio(ref mut ad, _) => match ad.interface {
usb::descriptors::audio::UacInterfaceDescriptor::InputTerminal1(ref mut ah) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
ah.terminal = device.get_descriptor_string(ah.terminal_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::InputTerminal2(ref mut ah) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
ah.terminal = device.get_descriptor_string(ah.terminal_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::OutputTerminal1(
ref mut ah,
) => {
ah.terminal = device.get_descriptor_string(ah.terminal_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::OutputTerminal2(
ref mut ah,
) => {
ah.terminal = device.get_descriptor_string(ah.terminal_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::StreamingInterface2(
ref mut ah,
) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::SelectorUnit1(ref mut ah) => {
ah.selector = device.get_descriptor_string(ah.selector_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::SelectorUnit2(ref mut ah) => {
ah.selector = device.get_descriptor_string(ah.selector_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ProcessingUnit1(
ref mut ah,
) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
ah.processing = device.get_descriptor_string(ah.processing_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ProcessingUnit2(
ref mut ah,
) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
ah.processing = device.get_descriptor_string(ah.processing_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::EffectUnit2(ref mut ah) => {
ah.effect = device.get_descriptor_string(ah.effect_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::FeatureUnit1(ref mut ah) => {
ah.feature = device.get_descriptor_string(ah.feature_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::FeatureUnit2(ref mut ah) => {
ah.feature = device.get_descriptor_string(ah.feature_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ExtensionUnit1(ref mut ah) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
ah.extension = device.get_descriptor_string(ah.extension_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ExtensionUnit2(ref mut ah) => {
ah.channel_names = device.get_descriptor_string(ah.channel_names_index);
ah.extension = device.get_descriptor_string(ah.extension_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ClockSource2(ref mut ah) => {
ah.clock_source = device.get_descriptor_string(ah.clock_source_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ClockSelector2(ref mut ah) => {
ah.clock_selector = device.get_descriptor_string(ah.clock_selector_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::ClockMultiplier2(
ref mut ah,
) => {
ah.clock_multiplier =
device.get_descriptor_string(ah.clock_multiplier_index);
}
usb::descriptors::audio::UacInterfaceDescriptor::SampleRateConverter2(
ref mut ah,
) => {
ah.src = device.get_descriptor_string(ah.src_index);
}
_ => (),
},
usb::ClassDescriptor::Video(ref mut vd, _) => match vd.interface {
usb::descriptors::video::UvcInterfaceDescriptor::InputTerminal(ref mut vh) => {
vh.terminal = device.get_descriptor_string(vh.terminal_index);
}
usb::descriptors::video::UvcInterfaceDescriptor::OutputTerminal(ref mut vh) => {
vh.terminal = device.get_descriptor_string(vh.terminal_index);
}
usb::descriptors::video::UvcInterfaceDescriptor::SelectorUnit(ref mut vh) => {
vh.selector = device.get_descriptor_string(vh.selector_index);
}
usb::descriptors::video::UvcInterfaceDescriptor::ProcessingUnit(ref mut vh) => {
vh.processing = device.get_descriptor_string(vh.processing_index);
}
usb::descriptors::video::UvcInterfaceDescriptor::ExtensionUnit(ref mut vh) => {
vh.extension = device.get_descriptor_string(vh.extension_index);
}
usb::descriptors::video::UvcInterfaceDescriptor::EncodingUnit(ref mut vh) => {
vh.encoding = device.get_descriptor_string(vh.encoding_index);
}
_ => (),
},
_ => (),
},
_ => (),
}
Ok(dt)
}
fn build_config_descriptor_extra(
&self,
device: &T,
mut raw: Vec<u8>,
) -> Result<Vec<usb::Descriptor>> {
let extra_len = raw.len();
let mut taken = 0;
let mut ret = Vec::new();
while taken < extra_len && extra_len >= 2 {
let dt_len = raw[0] as usize;
let dt = self.build_descriptor_extra::<u8>(
device,
None,
None,
&raw.drain(..dt_len).collect::<Vec<u8>>(),
)?;
log::debug!("{device:?} Config descriptor extra: {dt:?}");
ret.push(dt);
taken += dt_len;
}
Ok(ret)
}
fn build_interface_descriptor_extra<C: Into<usb::BaseClass> + Copy>(
&self,
device: &T,
class_code: usb::ClassCodeTriplet<C>,
interface_number: u8,
mut raw: Vec<u8>,
) -> Result<Vec<usb::Descriptor>> {
let extra_len = raw.len();
let mut taken = 0;
let mut ret = Vec::new();
while taken < extra_len && extra_len >= 2 {
let dt_len = raw[0] as usize;
let dt = self.build_descriptor_extra(
device,
Some(class_code),
Some(interface_number),
&raw.drain(..dt_len).collect::<Vec<u8>>(),
)?;
log::debug!("{device:?} Interface descriptor extra: {dt:?}");
ret.push(dt);
taken += dt_len;
}
Ok(ret)
}
fn build_endpoint_descriptor_extra<C: Into<usb::BaseClass> + Copy>(
&self,
device: &T,
class_code: usb::ClassCodeTriplet<C>,
interface_number: u8,
mut raw: Vec<u8>,
) -> Result<Option<Vec<usb::Descriptor>>> {
let extra_len = raw.len();
let mut taken = 0;
let mut ret = Vec::new();
while taken < extra_len && extra_len >= 2 {
let dt_len = raw[0] as usize;
let dt = self.build_descriptor_extra(
device,
Some(class_code),
Some(interface_number),
&raw.drain(..dt_len).collect::<Vec<u8>>(),
)?;
log::debug!("{device:?} Endpoint descriptor extra: {dt:?}");
ret.push(dt);
taken += dt_len;
}
Ok(Some(ret))
}
fn get_devices(&mut self, options: &ProfilerOptions) -> Result<Vec<Device>>;
fn get_root_hubs(&mut self, options: &ProfilerOptions) -> Result<HashMap<u8, Device>>;
fn get_buses(&mut self, options: &ProfilerOptions) -> Result<HashMap<u8, Bus>>;
fn new_sp_bus(&self, bus_number: u8, root_hub: Option<Device>) -> Bus {
root_hub
.map(|rh| {
rh.try_into().unwrap_or_else(|e| {
log::warn!("Failed to convert root hub to Bus: {e:?}");
Bus::from(bus_number)
})
})
.unwrap_or(Bus::from(bus_number))
}
fn get_spusb(&mut self, options: &ProfilerOptions) -> Result<SystemProfile> {
let mut spusb = SystemProfile { buses: Vec::new() };
if options.depth.includes_extra() {
log::info!("Building SystemProfile using {self:?} with extra device data");
} else {
log::info!("Building SystemProfile using {self:?} without extra device data");
}
let cache = self.get_devices(options)?;
log::trace!("Devices {cache:#?}");
let mut buses = self.get_buses(options)?;
log::trace!("Buses {buses:#?}");
for (key, group) in &cache
.into_iter()
.sorted_by_key(|d| d.port_path())
.chunk_by(|d| d.location_id.bus)
{
let mut new_bus = buses.remove(&key).unwrap_or(Bus::from(key));
if !options.tree {
let existing = std::mem::take(&mut new_bus.devices);
let mut all_devices: Vec<Device> = group.collect();
if let Some(mut root_devices) = existing {
if let Some(filter) = &options.filter {
root_devices.retain(|d| !d.is_root_hub() || filter.include_root_hubs);
}
root_devices.append(&mut all_devices);
new_bus.devices = Some(root_devices);
} else {
new_bus.devices = Some(all_devices);
}
} else {
let parent_groups =
group.chunk_by(|d| d.parent_port_path().unwrap_or(d.trunk_port_path()));
for (parent_path, children) in
parent_groups.into_iter().sorted_by_key(|x| x.0.depth())
{
if parent_path.is_root_hub() {
let devices = std::mem::take(&mut new_bus.devices);
if let Some(mut d) = devices {
for new_device in children {
d.push(new_device);
}
new_bus.devices = Some(d);
} else {
new_bus.devices = Some(children.collect());
}
} else {
let parent_node = new_bus
.get_node_mut(&parent_path)
.expect("Parent node does not exist in new bus!");
let devices = std::mem::take(&mut parent_node.devices);
if let Some(mut d) = devices {
for new_device in children {
d.push(new_device);
}
parent_node.devices = Some(d);
} else {
parent_node.devices = Some(children.collect());
}
}
}
}
spusb.buses.push(new_bus);
}
if !buses.is_empty() {
for (_, bus) in buses {
spusb.buses.push(bus);
}
spusb.buses.sort_by_key(|b| b.usb_bus_number);
}
Ok(spusb)
}
fn fill_spusb(&mut self, spusb: &mut SystemProfile, options: &ProfilerOptions) -> Result<()> {
let libusb_spusb = self.get_spusb(options)?;
if !spusb.buses.is_empty() {
for mut bus in libusb_spusb.buses {
if let Some(existing) = spusb
.buses
.iter_mut()
.find(|b| b.get_bus_number() == bus.get_bus_number())
{
existing.devices = std::mem::take(&mut bus.devices);
}
}
}
Ok(())
}
}
#[allow(unused_variables)]
fn get_sysfs_string(sysfs_name: &str, attr: &str) -> Option<String> {
log::trace!("Getting sysfs string at {sysfs_name}/{attr}");
#[cfg(any(target_os = "linux", target_os = "android"))]
return std::fs::read_to_string(format!("{SYSFS_USB_PREFIX}{sysfs_name}/{attr}"))
.ok()
.map(|s| s.trim().to_string());
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return None;
}
#[allow(unused_variables)]
fn get_sysfs_configuration_value(sysfs_name: &str) -> Option<u8> {
#[cfg(target_os = "linux")]
return get_sysfs_string(sysfs_name, "bConfigurationValue").and_then(|s| s.parse::<u8>().ok());
#[cfg(not(target_os = "linux"))]
None
}
#[allow(unused_variables)]
fn get_sysfs_active_alternate_setting(interface_path: &str) -> Option<u8> {
#[cfg(target_os = "linux")]
return get_sysfs_string(interface_path, "bAlternateSetting")
.and_then(|s| s.parse::<u8>().ok());
#[cfg(not(target_os = "linux"))]
None
}
#[allow(unused_variables)]
fn get_sysfs_hub_ports(sysfs_name: &str) -> Option<u8> {
#[cfg(target_os = "linux")]
return get_sysfs_string(sysfs_name, "maxchild").and_then(|s| s.parse::<u8>().ok());
#[cfg(not(target_os = "linux"))]
None
}
#[allow(unused_variables)]
fn get_sysfs_readlink(sysfs_name: &str, attr: &str) -> Option<String> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let path = if sysfs_name.starts_with("usb") && attr == "driver" {
format!("{SYSFS_USB_PREFIX}{sysfs_name}/../{attr}")
} else {
format!("{SYSFS_USB_PREFIX}{sysfs_name}/{attr}")
};
log::trace!("readlink at {path}");
std::fs::read_link(path)
.ok()
.and_then(|s| s.file_name().map(|f| f.to_string_lossy().to_string()))
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return None;
}
#[allow(unused_variables)]
fn get_udev_driver_name(port_path: &str) -> Result<Option<String>> {
#[cfg(all(target_os = "linux", any(feature = "udev", feature = "udevlib")))]
return udev::get_udev_driver_name(port_path);
#[cfg(not(all(target_os = "linux", any(feature = "udev", feature = "udevlib"))))]
return Ok(None);
}
#[allow(unused_variables)]
fn get_udev_syspath(port_path: &str) -> Result<Option<String>> {
#[cfg(all(target_os = "linux", any(feature = "udev", feature = "udevlib")))]
return udev::get_udev_syspath(port_path);
#[cfg(not(all(target_os = "linux", any(feature = "udev", feature = "udevlib"))))]
return Ok(None);
}
#[allow(unused_variables)]
fn get_devlinks(tty_path: &str) -> Result<Option<Vec<String>>> {
#[cfg(all(target_os = "linux", any(feature = "udev", feature = "udevlib")))]
return udev::get_devlinks(tty_path);
#[cfg(not(all(target_os = "linux", any(feature = "udev", feature = "udevlib"))))]
return Ok(None);
}
#[allow(unused_variables)]
fn get_syspath(port_path: &str) -> Option<String> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return Some(format!("{SYSFS_USB_PREFIX}{port_path}"));
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return None;
}
pub fn get_spusb() -> Result<SystemProfile> {
get_spusb_with_options(&ProfilerOptions::default())
}
pub fn get_spusb_with_extra() -> Result<SystemProfile> {
get_spusb_with_options(&ProfilerOptions {
depth: ProfileDepth::Standard,
..Default::default()
})
}
#[allow(unused_variables)]
pub fn get_spusb_with_options(options: &ProfilerOptions) -> Result<SystemProfile> {
#[cfg(all(feature = "libusb", not(feature = "nusb")))]
{
let mut profiler = libusb::LibUsbProfiler;
<libusb::LibUsbProfiler as Profiler<libusb::UsbDevice<rusb::Context>>>::get_spusb(
&mut profiler,
options,
)
}
#[cfg(feature = "nusb")]
{
let mut profiler = nusb::NusbProfiler::new();
profiler.get_spusb(options)
}
#[cfg(all(not(feature = "libusb"), not(feature = "nusb")))]
{
Err(crate::error::Error::new(
crate::error::ErrorKind::Unsupported,
"nusb or libusb feature is required to do this, install with `cargo install --features nusb/libusb`",
))
}
}
#[cfg(target_os = "windows")]
mod platform {
use super::*;
use std::ffi::{OsStr, OsString};
fn parse_host_controller_id(s: &OsStr) -> Option<(u16, u16, u8, u32, Option<String>)> {
let s = s.to_str()?;
let s = s.strip_prefix("PCI\\VEN_")?;
let vid = u16::from_str_radix(s.get(0..4)?, 16).ok()?;
let s = s.get(4..)?.strip_prefix("&DEV_")?;
let pid = u16::from_str_radix(s.get(0..4)?, 16).ok()?;
let s = s.get(4..)?.strip_prefix("&SUBSYS_")?;
let sidcid = u32::from_str_radix(s.get(0..8)?, 16).ok()?;
let s = s.get(8..)?.strip_prefix("&REV_")?;
let rev = u8::from_str_radix(s.get(0..2)?, 16).ok()?;
let id = s.get(2..)?.strip_prefix("\\").map(|s| s.to_owned());
Some((vid, pid, rev, sidcid, id))
}
pub(crate) fn pci_info_from_parent(pci_path: &OsStr) -> Option<PciInfo> {
let pci_id = parse_host_controller_id(pci_path)?;
Some(PciInfo {
vendor_id: pci_id.0,
product_id: pci_id.1,
revision: pci_id.2 as u16,
})
}
pub(crate) fn pci_info_from_device(device: &Device) -> Option<PciInfo> {
device
.serial_num
.as_ref()
.and_then(|s| pci_info_from_parent(&OsString::from(s)))
}
#[cfg(feature = "nusb")]
pub(crate) fn pci_info_from_bus(bus_info: &::nusb::BusInfo) -> Option<PciInfo> {
pci_info_from_parent(bus_info.parent_instance_id())
}
#[cfg(feature = "nusb")]
pub(crate) fn from(bus: &::nusb::BusInfo) -> Bus {
if let Some(pci_info) = platform::pci_info_from_bus(bus) {
let (host_controller_vendor, host_controller_device) =
match pci_ids::Device::from_vid_pid(pci_info.vendor_id, pci_info.product_id) {
Some(d) => (
Some(d.vendor().name().to_string()),
Some(d.name().to_string()),
),
None => (None, None),
};
Bus {
usb_bus_number: None,
name: bus.system_name().map(|s| s.to_string()).unwrap_or_default(),
host_controller: bus.parent_instance_id().to_string_lossy().to_string(),
host_controller_vendor,
host_controller_device,
pci_vendor: Some(pci_info.vendor_id),
pci_device: Some(pci_info.product_id),
pci_revision: Some(pci_info.revision),
id: bus.bus_id().to_string(),
..Default::default()
}
} else {
Bus {
usb_bus_number: None,
name: bus.system_name().map(|s| s.to_string()).unwrap_or_default(),
host_controller: bus.parent_instance_id().to_string_lossy().to_string(),
id: bus.bus_id().to_string(),
..Default::default()
}
}
}
#[test]
fn test_parse_host_controller_id() {
assert_eq!(parse_host_controller_id(OsStr::new("")), None);
assert_eq!(
parse_host_controller_id(OsStr::new(
"PCI\\VEN_8086&DEV_2658&SUBSYS_04001AB8&REV_02\\3&11583659&0&E8"
)),
Some((
0x8086,
0x2658,
2,
0x04001AB8,
Some("3&11583659&0&E8".to_string())
))
);
assert_eq!(
parse_host_controller_id(OsStr::new("PCI\\VEN_8086&DEV_2658")),
None
);
assert_eq!(
parse_host_controller_id(OsStr::new("PCI\\VEN_8086&DEV_2658&SUBSYS_04001AB8&REV_02")),
Some((0x8086, 0x2658, 2, 0x04001AB8, None))
);
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
mod platform {
use super::*;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Debug, Clone)]
struct SysfsPath(pub(crate) PathBuf);
impl SysfsPath {
pub(crate) fn exists(&self) -> bool {
self.0.exists()
}
fn parse_attr<T>(&self, attr: &str, parse: impl FnOnce(&str) -> Result<T>) -> Result<T> {
let attr_path = self.0.join(attr);
fs::read_to_string(&attr_path)
.map_err(|e| Error::new(ErrorKind::Io, &e.to_string()))
.and_then(|v| parse(v.trim()))
}
pub(crate) fn read_attr<T: FromStr>(&self, attr: &str) -> Result<T> {
self.parse_attr(attr, |s| {
s.parse().map_err(|_| {
Error::new(ErrorKind::Parsing, &format!("Failed to parse attr: {attr}"))
})
})
}
fn read_attr_hex<T: FromHexStr>(&self, attr: &str) -> Result<T> {
self.parse_attr(attr, |s| T::from_hex_str(s.strip_prefix("0x").unwrap_or(s)))
}
fn children(&self) -> impl Iterator<Item = SysfsPath> {
fs::read_dir(&self.0)
.ok()
.into_iter()
.flatten()
.filter_map(|f| f.ok())
.filter(|f| f.file_type().ok().is_some_and(|t| t.is_dir()))
.map(|f| SysfsPath(f.path()))
}
}
trait FromHexStr: Sized {
fn from_hex_str(s: &str) -> Result<Self>;
}
impl FromHexStr for u8 {
fn from_hex_str(s: &str) -> Result<Self> {
u8::from_str_radix(s, 16).map_err(|_| Error::new(ErrorKind::Parsing, s))
}
}
impl FromHexStr for u16 {
fn from_hex_str(s: &str) -> Result<Self> {
u16::from_str_radix(s, 16).map_err(|_| Error::new(ErrorKind::Parsing, s))
}
}
impl std::fmt::Display for SysfsPath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.display().fmt(f)
}
}
impl From<&str> for SysfsPath {
fn from(s: &str) -> Self {
SysfsPath(PathBuf::from(s))
}
}
impl From<String> for SysfsPath {
fn from(s: String) -> Self {
SysfsPath(PathBuf::from(s))
}
}
impl From<PathBuf> for SysfsPath {
fn from(p: PathBuf) -> Self {
SysfsPath(p)
}
}
impl std::ops::Deref for SysfsPath {
type Target = PathBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<SysfsPath> for PathBuf {
fn from(s: SysfsPath) -> Self {
s.0
}
}
fn pci_info_from_parent(pci_path: &SysfsPath) -> Option<PciInfo> {
Some(PciInfo {
vendor_id: pci_path.read_attr_hex("vendor").ok()?,
product_id: pci_path.read_attr_hex("device").ok()?,
revision: pci_path.read_attr_hex("revision").ok()?,
})
}
pub(crate) fn pci_info_from_device(device: &Device) -> Option<PciInfo> {
device.serial_num.as_ref().and_then(|s| {
let pci_path = SysfsPath::from(PathBuf::from(SYSFS_PCI_PREFIX).join(s));
log::debug!("Probing device {pci_path:?}");
pci_info_from_parent(&pci_path)
})
}
#[cfg(feature = "nusb")]
pub(crate) fn pci_info_from_bus(bus_info: &::nusb::BusInfo) -> Option<PciInfo> {
let path = bus_info.sysfs_path();
let parent_path = path
.parent()
.and_then(|p| p.to_str())
.map(|s| s.to_string())?;
let pci_path = SysfsPath::from(PathBuf::from(SYSFS_PCI_PREFIX).join(parent_path));
log::debug!("Probing bus parent device {pci_path:?}");
pci_info_from_parent(&pci_path)
}
#[cfg(feature = "nusb")]
pub(crate) fn from(bus: &::nusb::BusInfo) -> Bus {
if let Some(pci_info) = platform::pci_info_from_bus(bus) {
let (host_controller_vendor, host_controller_device) =
match pci_ids::Device::from_vid_pid(pci_info.vendor_id, pci_info.product_id) {
Some(d) => (
Some(d.vendor().name().to_string()),
Some(d.name().to_string()),
),
None => (None, None),
};
Bus {
usb_bus_number: Some(bus.bus_id().parse::<u8>().expect(
"Failed to parse bus_id: Linux bus_id should be a decimal string and not None",
)),
name: bus.system_name().map(|s| s.to_string()).unwrap_or_default(),
host_controller: bus
.root_hub()
.manufacturer_string()
.map(|s| s.to_string())
.unwrap_or_default(),
host_controller_vendor,
host_controller_device,
pci_vendor: Some(pci_info.vendor_id),
pci_device: Some(pci_info.product_id),
pci_revision: Some(pci_info.revision),
..Default::default()
}
} else {
Bus {
usb_bus_number: Some(bus.bus_id().parse::<u8>().expect(
"Failed to parse bus_id: Linux bus_id should be a decimal string and not None",
)),
name: bus.system_name().map(|s| s.to_string()).unwrap_or_default(),
host_controller: bus
.root_hub()
.manufacturer_string()
.map(|s| s.to_string())
.unwrap_or_default(),
..Default::default()
}
}
}
}
#[cfg(target_os = "macos")]
mod platform {
use super::*;
use macos::HostControllerInfo;
impl From<HostControllerInfo> for PciInfo {
fn from(pci_info: HostControllerInfo) -> Self {
PciInfo {
vendor_id: pci_info.vendor_id,
product_id: pci_info.device_id,
revision: pci_info.revision_id,
}
}
}
#[allow(unused_variables)]
pub(crate) fn pci_info_from_device(device: &Device) -> Option<PciInfo> {
None
}
#[cfg(feature = "nusb")]
pub(crate) fn pci_info_from_bus(bus_info: &::nusb::BusInfo) -> Option<PciInfo> {
bus_info
.name()
.and_then(|name| macos::get_controller(name).ok().map(|c| c.into()))
}
#[cfg(feature = "nusb")]
pub(crate) fn from(bus: &::nusb::BusInfo) -> Bus {
if let Some(pci_info) = platform::pci_info_from_bus(bus) {
let (host_controller_vendor, host_controller_device) =
match pci_ids::Device::from_vid_pid(pci_info.vendor_id, pci_info.product_id) {
Some(d) => (
Some(d.vendor().name().to_string()),
Some(d.name().to_string()),
),
None => (None, None),
};
Bus {
usb_bus_number: Some(u8::from_str_radix(bus.bus_id(), 16).expect("Failed to parse bus_id: macOS bus_id should be a hexadecimal string and not None")),
name: bus.class_name().to_string(),
host_controller: bus.provider_class_name().to_string(),
host_controller_vendor,
host_controller_device,
pci_vendor: Some(pci_info.vendor_id),
pci_device: Some(pci_info.product_id),
pci_revision: Some(pci_info.revision),
..Default::default()
}
} else {
Bus {
usb_bus_number: Some(u8::from_str_radix(bus.bus_id(), 16).expect("Failed to parse bus_id: macOS bus_id should be a hexadecimal string and not None")),
name: bus.class_name().to_string(),
host_controller: bus.provider_class_name().to_string(),
..Default::default()
}
}
}
}