use colored::*;
use serde::de::{self, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use serde_with::{skip_serializing_none, DeserializeFromStr, SerializeDisplay};
use std::cmp::Ordering;
use std::fmt;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use super::*;
use crate::error::{Error, ErrorKind};
use crate::types::{NumericalUnit, VidPid};
use crate::usb::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct SystemProfile {
#[serde(rename(deserialize = "SPUSBDataType"), alias = "buses")]
pub buses: Vec<Bus>,
}
impl SystemProfile {
pub fn len(&self) -> usize {
self.buses.iter().map(|b| b.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.buses.iter().all(|b| b.is_empty())
}
pub fn into_flattened(&mut self) {
for bus in &mut self.buses {
bus.into_flattened_devices();
}
}
pub fn flattened_devices(&self) -> Vec<&Device> {
let mut ret = Vec::with_capacity(self.len());
for bus in &self.buses {
ret.extend(bus.flattened_devices());
}
ret
}
pub fn get_bus(&self, number: u8) -> Option<&Bus> {
self.buses.iter().find(|b| b.usb_bus_number == Some(number))
}
pub fn get_bus_mut(&mut self, number: u8) -> Option<&mut Bus> {
self.buses
.iter_mut()
.find(|b| b.usb_bus_number == Some(number))
}
pub fn get_node(&self, port_path: &PortPath) -> Option<&Device> {
let bus_no = port_path.bus();
if let Some(bus) = self.get_bus(bus_no) {
if let Some(node) = bus.get_node(port_path) {
return Some(node);
}
} else {
for bus in self.buses.iter() {
if let Some(node) = bus.get_node(port_path) {
return Some(node);
}
}
}
None
}
pub fn get_node_mut(&mut self, port_path: &PortPath) -> Option<&mut Device> {
let bus_no = port_path.bus();
if self.buses.iter().any(|b| b.usb_bus_number == Some(bus_no)) {
if let Some(bus) = self.get_bus_mut(bus_no) {
if let Some(node) = bus.get_node_mut(port_path) {
return Some(node);
}
}
} else {
for bus in self.buses.iter_mut() {
if let Some(node) = bus.get_node_mut(port_path) {
return Some(node);
}
}
}
None
}
pub fn get_node_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Device> {
UsbPath::from(path.as_ref())
.port_path()
.and_then(|pp| self.get_node(&pp))
}
pub fn get_node_path_by_path_mut<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut Device> {
UsbPath::from(path.as_ref())
.port_path()
.and_then(|pp| self.get_node_mut(&pp))
}
pub fn get_node_by_str(&self, path: &str) -> Option<&Device> {
PortPath::try_from(path)
.ok()
.and_then(|pp| self.get_node(&pp))
}
pub fn get_node_str_mut(&mut self, path: &str) -> Option<&mut Device> {
PortPath::try_from(path)
.ok()
.and_then(|pp| self.get_node_mut(&pp))
}
pub fn get_config(&self, port_path: &PortPath, config: u8) -> Option<&Configuration> {
self.get_node(port_path).and_then(|d| d.get_config(config))
}
pub fn get_config_mut(
&mut self,
port_path: &PortPath,
config: u8,
) -> Option<&mut Configuration> {
self.get_node_mut(port_path)
.and_then(|d| d.get_config_mut(config))
}
pub fn get_interface(&self, device_path: &DevicePath) -> Option<&Interface> {
if let (Some(config), Some(interface)) =
(device_path.configuration(), device_path.interface())
{
self.get_node(device_path.port_path())
.and_then(|d| d.get_interface(config, interface, device_path.alt_setting()))
} else {
None
}
}
pub fn get_interface_mut(&mut self, device_path: &DevicePath) -> Option<&mut Interface> {
if let (Some(config), Some(interface)) =
(device_path.configuration(), device_path.interface())
{
self.get_node_mut(device_path.port_path())
.and_then(|d| d.get_interface_mut(config, interface, device_path.alt_setting()))
} else {
None
}
}
pub fn get_endpoint(&self, endpoint_path: &EndpointPath) -> Option<&Endpoint> {
if let (Some(config), Some(interface), endpoint) = (
endpoint_path.device_path().configuration(),
endpoint_path.device_path().interface(),
endpoint_path.endpoint(),
) {
self.get_node(endpoint_path.device_path().port_path())
.and_then(|d| {
d.get_endpoint(
config,
interface,
endpoint_path.device_path().alt_setting(),
endpoint,
)
})
} else {
None
}
}
pub fn get_endpoint_mut(&mut self, endpoint_path: &EndpointPath) -> Option<&mut Endpoint> {
if let (Some(config), Some(interface), endpoint) = (
endpoint_path.device_path().configuration(),
endpoint_path.device_path().interface(),
endpoint_path.endpoint(),
) {
self.get_node_mut(endpoint_path.device_path().port_path())
.and_then(|d| {
d.get_endpoint_mut(
config,
interface,
endpoint_path.device_path().alt_setting(),
endpoint,
)
})
} else {
None
}
}
#[cfg(feature = "nusb")]
pub fn get_id(&self, id: &::nusb::DeviceId) -> Option<&Device> {
for bus in self.buses.iter() {
if let Some(node) = bus.get_id(id) {
return Some(node);
}
}
None
}
#[cfg(feature = "nusb")]
pub fn get_id_mut(&mut self, id: &::nusb::DeviceId) -> Option<&mut Device> {
for bus in self.buses.iter_mut() {
if let Some(node) = bus.get_id_mut(id) {
return Some(node);
}
}
None
}
pub fn replace(&mut self, mut new: Device) -> Result<Device> {
if let Some(existing) = self.get_node_mut(&new.port_path()) {
let devices = std::mem::take(&mut existing.devices);
new.devices = devices;
new.internal = existing.internal.clone();
if new.is_expanded() {
new.set_all_expanded(true);
}
let ret = std::mem::replace(existing, new);
Ok(ret)
} else {
Err(Error::new(
ErrorKind::NotFound,
"Device not found to replace",
))
}
}
pub fn insert(&mut self, new: Device) -> Option<Device> {
if self.get_node_mut(&new.port_path()).is_some() {
return self.replace(new).ok();
} else if new.is_trunk_device() {
let bus = self.get_bus_mut(new.location_id.bus).unwrap();
if let Some(bd) = bus.devices.as_mut() {
bd.push(new);
} else {
bus.devices = Some(vec![new]);
}
} else if let Some(parent_path) = new.parent_port_path() {
if let Some(parent) = self.get_node_mut(&parent_path) {
if let Some(bd) = parent.devices.as_mut() {
bd.push(new);
} else {
parent.devices = Some(vec![new]);
}
}
}
None
}
}
impl<'a> IntoIterator for &'a SystemProfile {
type Item = &'a Device;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> std::vec::IntoIter<Self::Item> {
self.flattened_devices().into_iter()
}
}
impl fmt::Display for SystemProfile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for v in &self.buses {
if f.alternate() {
if f.sign_plus() {
writeln!(f, "{v:+#}")?;
} else {
writeln!(f, "{v:#}")?;
}
} else if f.sign_plus() {
write!(f, "{v:+}")?;
} else {
write!(f, "{v:}")?;
}
}
Ok(())
}
}
#[deprecated(since = "2.0.0", note = "Use SystemProfile instead")]
pub type SPUSBDataType = SystemProfile;
#[derive(Debug, Clone)]
pub(crate) struct PciInfo {
pub vendor_id: u16,
pub product_id: u16,
pub revision: u16,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Bus {
#[serde(rename(deserialize = "_name"), alias = "name")]
pub name: String,
pub host_controller: String,
pub host_controller_vendor: Option<String>,
pub host_controller_device: Option<String>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub pci_vendor: Option<u16>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub pci_device: Option<u16>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub pci_revision: Option<u16>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub usb_bus_number: Option<u8>,
#[serde(rename(deserialize = "_items"), alias = "devices")]
pub devices: Option<Vec<Device>>,
#[cfg(all(target_os = "windows", feature = "nusb"))]
#[serde(skip)]
pub id: String,
#[serde(skip)]
pub(crate) internal: InternalData,
}
#[deprecated(since = "2.0.0", note = "Use Bus instead")]
pub type USBBus = Bus;
impl TryFrom<Device> for Bus {
type Error = Error;
fn try_from(device: Device) -> Result<Self> {
if !device.is_root_hub() {
return Err(Error::new(
ErrorKind::InvalidArg,
"Device is not a root hub",
));
}
let (pci_vendor, pci_device, pci_revision) = match platform::pci_info_from_device(&device) {
Some(v) => (Some(v.vendor_id), Some(v.product_id), Some(v.revision)),
None => (None, None, None),
};
let (host_controller_vendor, host_controller_device) =
if let (Some(v), Some(p)) = (pci_vendor, pci_device) {
log::debug!("looking up PCI IDs: {v:04x}:{p:04x}");
match pci_ids::Device::from_vid_pid(v, p) {
Some(d) => (
Some(d.vendor().name().to_string()),
Some(d.name().to_string()),
),
None => (None, None),
}
} else {
(None, None)
};
Ok(Bus {
name: device.name,
host_controller: device.manufacturer.unwrap_or_default(),
host_controller_vendor,
host_controller_device,
pci_device: pci_device.filter(|v| *v != 0xffff && *v != 0),
pci_vendor: pci_vendor.filter(|v| *v != 0xffff && *v != 0),
pci_revision: pci_revision.filter(|v| *v != 0xffff && *v != 0),
usb_bus_number: Some(device.location_id.bus),
devices: device.devices,
..Default::default()
})
}
}
impl<'a> IntoIterator for &'a Bus {
type Item = &'a Device;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> std::vec::IntoIter<Self::Item> {
self.flattened_devices().into_iter()
}
}
impl From<u8> for Bus {
fn from(bus: u8) -> Self {
Bus {
name: format!("USB Bus {bus:03}"),
host_controller: String::from("USB Host Controller"),
usb_bus_number: Some(bus),
..Default::default()
}
}
}
impl Bus {
pub fn len(&self) -> usize {
self.devices
.as_ref()
.map_or(0, |d| d.iter().map(|dd| dd.len()).sum())
}
pub fn into_flattened_devices(&mut self) {
if let Some(mut devices) = self.devices.take() {
let mut new_devices: Vec<Device> = Vec::new();
while let Some(device) = devices.pop() {
new_devices.extend(device.into_flattened())
}
self.devices = Some(new_devices)
}
}
pub fn flattened_devices(&self) -> Vec<&Device> {
if let Some(devices) = &self.devices {
devices.iter().flat_map(|d| d.flatten()).collect()
} else {
Vec::new()
}
}
pub fn is_empty(&self) -> bool {
match &self.devices {
Some(d) => d.is_empty() || d.iter().all(|dd| dd.internal.hidden),
None => true,
}
}
pub fn has_empty_hubs(&self) -> bool {
match &self.devices {
Some(d) => d.iter().all(|dd| dd.is_hub() && !dd.has_devices()),
None => false,
}
}
pub fn get_bus_number(&self) -> Option<u8> {
self.usb_bus_number.or_else(|| {
self.devices
.as_ref()
.and_then(|d| d.first().map(|dd| dd.location_id.bus))
})
}
pub fn path(&self) -> Option<PathBuf> {
self.get_bus_number().map(|n| get_trunk_path(n, &[]).into())
}
pub fn interface(&self) -> Option<DevicePath> {
self.get_bus_number()
.map(|n| DevicePath::new(n, vec![0], Some(1), Some(0), None))
}
pub fn remove_root_hub_device(&mut self) {
self.devices
.iter_mut()
.for_each(|devs| devs.retain(|d| !d.is_root_hub()));
}
pub fn get_root_hub_device(&self) -> Option<&Device> {
self.interface().and_then(|i| self.get_node(i.port_path()))
}
pub fn get_root_hub_device_mut(&mut self) -> Option<&mut Device> {
self.interface()
.and_then(|i| self.get_node_mut(i.port_path()))
}
pub fn get_node(&self, port_path: &PortPath) -> Option<&Device> {
if let Some(devices) = self.devices.as_ref() {
for dev in devices {
if let Some(node) = dev.get_node(port_path) {
log::debug!("Found {node}");
return Some(node);
}
}
}
None
}
pub fn get_node_mut(&mut self, port_path: &PortPath) -> Option<&mut Device> {
if let Some(devices) = self.devices.as_mut() {
for dev in devices {
if let Some(node) = dev.get_node_mut(port_path) {
log::debug!("Found {node}");
return Some(node);
}
}
}
None
}
pub fn get_node_by_path<P: AsRef<Path>>(&self, port_path: P) -> Option<&Device> {
UsbPath::from(port_path.as_ref())
.port_path()
.and_then(|pp| self.get_node(&pp))
}
pub fn get_node_path_by_path_mut<P: AsRef<Path>>(
&mut self,
port_path: P,
) -> Option<&mut Device> {
UsbPath::from(port_path.as_ref())
.port_path()
.and_then(|pp| self.get_node_mut(&pp))
}
pub fn get_node_by_str(&self, path: &str) -> Option<&Device> {
PortPath::try_from(path)
.ok()
.and_then(|pp| self.get_node(&pp))
}
pub fn get_node_str_mut(&mut self, path: &str) -> Option<&mut Device> {
PortPath::try_from(path)
.ok()
.and_then(|pp| self.get_node_mut(&pp))
}
pub fn get_config(&self, port_path: &PortPath, config: u8) -> Option<&Configuration> {
self.get_node(port_path).and_then(|d| d.get_config(config))
}
pub fn get_config_mut(
&mut self,
port_path: &PortPath,
config: u8,
) -> Option<&mut Configuration> {
self.get_node_mut(port_path)
.and_then(|d| d.get_config_mut(config))
}
pub fn get_interface(&self, device_path: &DevicePath) -> Option<&Interface> {
if let (Some(config), Some(interface)) =
(device_path.configuration(), device_path.interface())
{
self.get_node(device_path.port_path())
.and_then(|d| d.get_interface(config, interface, device_path.alt_setting()))
} else {
None
}
}
pub fn get_interface_mut(&mut self, device_path: &DevicePath) -> Option<&mut Interface> {
if let (Some(config), Some(interface)) =
(device_path.configuration(), device_path.interface())
{
self.get_node_mut(device_path.port_path())
.and_then(|d| d.get_interface_mut(config, interface, device_path.alt_setting()))
} else {
None
}
}
pub fn get_endpoint(&self, endpoint_path: &EndpointPath) -> Option<&Endpoint> {
if let (Some(config), Some(interface), endpoint) = (
endpoint_path.device_path().configuration(),
endpoint_path.device_path().interface(),
endpoint_path.endpoint(),
) {
self.get_node(endpoint_path.device_path().port_path())
.and_then(|d| {
d.get_endpoint(
config,
interface,
endpoint_path.device_path().alt_setting(),
endpoint,
)
})
} else {
None
}
}
pub fn get_endpoint_mut(&mut self, endpoint_path: &EndpointPath) -> Option<&mut Endpoint> {
if let (Some(config), Some(interface), endpoint) = (
endpoint_path.device_path().configuration(),
endpoint_path.device_path().interface(),
endpoint_path.endpoint(),
) {
self.get_node_mut(endpoint_path.device_path().port_path())
.and_then(|d| {
d.get_endpoint_mut(
config,
interface,
endpoint_path.device_path().alt_setting(),
endpoint,
)
})
} else {
None
}
}
#[cfg(feature = "nusb")]
pub fn get_id(&self, id: &::nusb::DeviceId) -> Option<&Device> {
if let Some(devices) = self.devices.as_ref() {
for dev in devices {
if let Some(node) = dev.get_id(id) {
return Some(node);
}
}
}
None
}
#[cfg(feature = "nusb")]
pub fn get_id_mut(&mut self, id: &::nusb::DeviceId) -> Option<&mut Device> {
if let Some(devices) = self.devices.as_mut() {
for dev in devices {
if let Some(node) = dev.get_id_mut(id) {
return Some(node);
}
}
}
None
}
pub fn to_lsusb_string(&self) -> String {
format!(
"Bus {:03} Device 000: ID {:04x}:{:04x} {} {}",
self.get_bus_number().unwrap_or(0xff),
self.pci_vendor.unwrap_or(0xffff),
self.pci_device.unwrap_or(0xffff),
self.name,
self.host_controller,
)
}
pub fn to_lsusb_tree_string(&self, verbosity: u8) -> Vec<String> {
let mut ret: Vec<String> = Vec::with_capacity(verbosity as usize);
if let Some(root_device) = self.get_root_hub_device() {
let speed = match &root_device.device_speed {
Some(v) => match v {
DeviceSpeed::SpeedValue(v) => v.to_lsusb_speed(),
DeviceSpeed::Description(_) => String::new(),
},
None => String::from(""),
};
let (driver, vendor, product, ports) = match &root_device.extra {
Some(v) => (
v.driver.to_owned().unwrap_or(String::from("[none]")),
v.vendor.to_owned().unwrap_or(String::from("[unknown]")),
v.product_name
.to_owned()
.unwrap_or(String::from("[unknown]")),
v.hub.to_owned().map(|h| h.num_ports),
),
None => (
String::from("[none]"),
String::from("[unknown]"),
String::from("[unknown]"),
None,
),
};
let driver_string = if let Some(ports) = ports {
format!("{driver}/{ports}p")
} else {
driver
};
ret.push(format!(
"Bus {:03}.Port 001: Dev 001, Class=root_hub, Driver={}, {}",
self.get_bus_number().unwrap_or(0xff),
driver_string,
speed
));
if verbosity > 0 {
ret.push(format!(
"ID {:04x}:{:04x} {} {}",
root_device.vendor_id.unwrap_or(0xFFFF),
root_device.product_id.unwrap_or(0xFFFF),
vendor,
product
));
}
if verbosity > 1 {
ret.push(format!(
"/sys/bus/usb/devices/usb{} {}",
self.get_bus_number().unwrap_or(0xff),
get_dev_path(self.get_bus_number().unwrap_or(0xff), None).display()
));
}
} else {
log::warn!("Failed to get root_device in bus");
ret.push(format!(
"Bus {:03}.Port 001: Dev 001, Class=root_hub, Driver=[none],",
self.get_bus_number().unwrap_or(0xff),
));
if verbosity > 0 {
ret.push(format!(
"ID {:04x}:{:04x} {} {}",
self.pci_vendor.unwrap_or(0xFFFF),
self.pci_device.unwrap_or(0xFFFF),
self.host_controller,
self.name,
));
}
if verbosity > 1 {
ret.push(format!(
"/sys/bus/usb/devices/usb{} {}",
self.get_bus_number().unwrap_or(0xff),
get_dev_path(self.get_bus_number().unwrap_or(0xff), None).display()
));
}
}
ret
}
pub(crate) fn fill_host_controller_from_ids(&mut self) {
if let (Some(v), Some(p)) = (self.pci_vendor, self.pci_device) {
if let Some(d) = pci_ids::Device::from_vid_pid(v, p) {
self.host_controller_vendor = Some(d.vendor().name().to_string());
self.host_controller_device = Some(d.name().to_string());
}
}
}
pub fn is_hidden(&self) -> bool {
self.internal.hidden
}
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;
if let Some(devices) = self.devices.as_mut() {
for dev in devices {
dev.set_all_expanded(expanded);
}
}
}
}
pub fn write_devices_recursive(f: &mut fmt::Formatter, devices: &Vec<Device>) -> fmt::Result {
for device in devices {
if f.sign_plus() && device.is_root_hub() {
continue;
}
if f.alternate() {
if f.sign_plus() {
writeln!(f, "{device:+#}")?;
} else {
writeln!(f, "{device:#}")?;
}
} else if f.sign_plus() {
writeln!(f, "{device:+}")?;
} else {
writeln!(f, "{device}")?;
}
device
.devices
.as_ref()
.map_or(Ok(()), |d| write_devices_recursive(f, d))?
}
Ok(())
}
impl fmt::Display for Bus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let tree: &str = if !f.sign_plus() {
""
} else if f.alternate() {
"\u{25CF} "
} else {
"/: "
};
if f.alternate() {
writeln!(
f,
"{:}{:} {:} {:}:{:} Revision: 0x{:04x}",
tree.bright_black().bold(),
self.name.blue(),
self.host_controller.green(),
format!("0x{:04x}", self.pci_vendor.unwrap_or(0xffff))
.yellow()
.bold(),
format!("0x{:04x}", self.pci_device.unwrap_or(0xffff)).yellow(),
self.pci_revision.unwrap_or(0xffff),
)?;
} else if f.sign_plus() {
writeln!(
f,
"{}{}",
tree,
self.to_lsusb_tree_string(0).first().unwrap()
)?;
} else {
writeln!(f, "{}", self.to_lsusb_string())?
}
self.devices
.as_ref()
.map_or(Ok(()), |d| write_devices_recursive(f, d))
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Eq)]
pub struct DeviceLocation {
pub bus: u8,
pub tree_positions: Vec<u8>,
pub number: u8,
}
impl FromStr for DeviceLocation {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let location_split: Vec<&str> = s.split('/').collect();
match (location_split.first(), location_split.get(1)) {
(Some(reg), Some(dev)) => {
let reg = reg.trim().trim_start_matches("0x");
let tree_positions: Vec<u8> = reg
.get(2..)
.unwrap_or("0")
.trim_end_matches('0')
.chars()
.map(|v| v.to_digit(10).unwrap_or(0) as u8)
.collect();
let bus = (u32::from_str_radix(reg, 16)
.map_err(|v| Error::new(ErrorKind::Parsing, &v.to_string()))?
>> 24) as u8;
let number = dev.trim().parse::<u8>().map_err(|v| {
Error::new(
ErrorKind::Parsing,
&format!("Invalid device number: {dev} {v}"),
)
})?;
Ok(DeviceLocation {
bus,
tree_positions,
number,
})
}
(Some(reg), None) => {
let reg = reg.trim().trim_start_matches("0x");
let tree_positions: Vec<u8> = reg
.get(2..)
.unwrap_or("0")
.trim_end_matches('0')
.chars()
.map(|v| v.to_digit(10).unwrap_or(0) as u8)
.collect();
let bus = (u32::from_str_radix(reg, 16)
.map_err(|v| Error::new(ErrorKind::Parsing, &v.to_string()))?
>> 24) as u8;
let number = tree_positions.iter().sum();
Ok(DeviceLocation {
bus,
tree_positions,
number,
})
}
_ => Err(Error::new(ErrorKind::Parsing, "Invalid location_id")),
}
}
}
impl From<DeviceLocation> for PortPath {
fn from(location: DeviceLocation) -> Self {
PortPath::new(location.bus, location.tree_positions)
}
}
impl<'de> Deserialize<'de> for DeviceLocation {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Bus,
Number,
TreePositions,
}
struct DeviceLocationVisitor;
impl<'de> Visitor<'de> for DeviceLocationVisitor {
type Value = DeviceLocation;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string with 0xLOCATION_REG/DEVICE_NO")
}
fn visit_seq<V>(self, mut seq: V) -> core::result::Result<DeviceLocation, V::Error>
where
V: SeqAccess<'de>,
{
let bus = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let tree_positions = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let number = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
Ok(DeviceLocation {
bus,
number,
tree_positions,
})
}
fn visit_map<V>(self, mut map: V) -> core::result::Result<DeviceLocation, V::Error>
where
V: MapAccess<'de>,
{
let mut bus = None;
let mut number = None;
let mut tree_positions = None;
while let Some(key) = map.next_key()? {
match key {
Field::Bus => {
if bus.is_some() {
return Err(de::Error::duplicate_field("bus"));
}
bus = Some(map.next_value()?);
}
Field::Number => {
if number.is_some() {
return Err(de::Error::duplicate_field("number"));
}
number = Some(map.next_value()?);
}
Field::TreePositions => {
if tree_positions.is_some() {
return Err(de::Error::duplicate_field("tree_positions"));
}
tree_positions = Some(map.next_value()?);
}
}
}
let bus = bus.ok_or_else(|| de::Error::missing_field("bus"))?;
let number = number.ok_or_else(|| de::Error::missing_field("number"))?;
let tree_positions =
tree_positions.ok_or_else(|| de::Error::missing_field("tree_positions"))?;
Ok(DeviceLocation {
bus,
number,
tree_positions,
})
}
fn visit_string<E>(self, value: String) -> core::result::Result<Self::Value, E>
where
E: de::Error,
{
DeviceLocation::from_str(value.as_str()).map_err(serde::de::Error::custom)
}
fn visit_str<E>(self, value: &str) -> core::result::Result<Self::Value, E>
where
E: de::Error,
{
DeviceLocation::from_str(value).map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_any(DeviceLocationVisitor)
}
}
#[derive(Debug, Clone, PartialEq, DeserializeFromStr, SerializeDisplay)]
pub enum DeviceSpeed {
SpeedValue(Speed),
Description(String),
}
impl fmt::Display for DeviceSpeed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DeviceSpeed::SpeedValue(v) => {
if f.sign_plus() {
return write!(f, "{}", v.original_label());
} else if f.sign_minus() {
return write!(f, "{}", v.marketing_label());
}
let dv = NumericalUnit::<f32>::from(v);
#[allow(clippy::unnecessary_unwrap)]
if f.alternate() && dv.description.is_some() {
f.write_fmt(core::format_args!("{}", dv.description.unwrap()))
} else {
write!(f, "{dv:.1}")
}
}
DeviceSpeed::Description(v) => {
if f.alternate() {
write!(f, "{v}")
} else {
write!(f, "{:5} {:4}", "-", "-")
}
}
}
}
}
impl DeviceSpeed {
pub fn operation_mode(&self) -> Option<crate::usb::OperationMode> {
match self {
DeviceSpeed::SpeedValue(s) => s.operation_mode(),
DeviceSpeed::Description(_) => None,
}
}
pub fn original_label(&self) -> Option<String> {
match self {
DeviceSpeed::SpeedValue(s) => Some(s.original_label()),
DeviceSpeed::Description(_) => None,
}
}
pub fn marketing_label(&self) -> Option<String> {
match self {
DeviceSpeed::SpeedValue(s) => Some(s.marketing_label()),
DeviceSpeed::Description(_) => None,
}
}
}
impl FromStr for DeviceSpeed {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match Speed::from_str(s) {
Ok(v) => Ok(DeviceSpeed::SpeedValue(v)),
Err(_) => Ok(DeviceSpeed::Description(s.to_owned())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeviceEvent {
Profiled(chrono::DateTime<chrono::Local>),
Connected(chrono::DateTime<chrono::Local>),
Disconnected(chrono::DateTime<chrono::Local>),
}
impl fmt::Display for DeviceEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DeviceEvent::Profiled(t) => write!(f, "P: {}", t.format("%y-%m-%d %H:%M:%S")),
DeviceEvent::Connected(t) => write!(f, "C: {}", t.format("%y-%m-%d %H:%M:%S")),
DeviceEvent::Disconnected(t) => {
write!(f, "D: {}", t.format("%y-%m-%d %H:%M:%S"))
}
}
}
}
impl Default for DeviceEvent {
fn default() -> Self {
DeviceEvent::Profiled(chrono::Local::now())
}
}
impl DeviceEvent {
pub fn time(&self) -> chrono::DateTime<chrono::Local> {
match self {
DeviceEvent::Profiled(t) => *t,
DeviceEvent::Connected(t) => *t,
DeviceEvent::Disconnected(t) => *t,
}
}
pub fn format(&self, fmt: &str) -> String {
self.time().format(fmt).to_string()
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct InternalData {
pub(crate) expanded: bool,
pub(crate) hidden: bool,
}
#[skip_serializing_none]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Device {
#[serde(rename(deserialize = "_name"), alias = "name")]
pub name: String,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub vendor_id: Option<u16>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub product_id: Option<u16>,
pub location_id: DeviceLocation,
pub serial_num: Option<String>,
pub manufacturer: Option<String>,
#[serde(
default,
serialize_with = "version_serializer",
deserialize_with = "deserialize_option_version_from_string"
)]
pub bcd_device: Option<Version>,
#[serde(
default,
serialize_with = "version_serializer",
deserialize_with = "deserialize_option_version_from_string"
)]
pub bcd_usb: Option<Version>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub bus_power: Option<u16>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub bus_power_used: Option<u16>,
pub device_speed: Option<DeviceSpeed>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub extra_current_used: Option<u16>,
#[serde(rename(deserialize = "_items"), alias = "devices")]
pub devices: Option<Vec<Device>>,
pub class: Option<BaseClass>,
pub sub_class: Option<u8>,
pub protocol: Option<u8>,
#[serde(default)]
pub extra: Option<DeviceExtra>,
#[serde(skip)]
pub profiler_error: Option<String>,
#[serde(skip)]
#[cfg(feature = "nusb")]
pub id: Option<::nusb::DeviceId>,
pub last_event: Option<DeviceEvent>,
#[serde(skip)]
pub internal: InternalData,
}
#[deprecated(since = "2.0.0", note = "Use Device instead")]
pub type USBDevice = Device;
#[cfg(feature = "nusb")]
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
(self.id == other.id) && self.id.is_some()
}
}
impl Device {
pub fn has_devices(&self) -> bool {
match &self.devices {
Some(d) => !d.is_empty() && !d.iter().all(|dd| dd.internal.hidden),
None => false,
}
}
fn len(&self) -> usize {
1 + self
.devices
.as_ref()
.map_or(0, |d| d.iter().map(|dd| dd.len()).sum())
}
pub fn has_interface_class(&self, c: &BaseClass) -> bool {
if let Some(extra) = self.extra.as_ref() {
extra
.configurations
.iter()
.any(|conf| conf.interfaces.iter().any(|i| i.class == *c))
} else {
false
}
}
pub fn get_root_hub(&self) -> Option<&Device> {
if self.is_root_hub() {
Some(self)
} else {
None
}
}
pub fn get_root_hub_mut(&mut self) -> Option<&mut Device> {
if self.is_root_hub() {
Some(self)
} else {
None
}
}
pub fn get_node(&self, port_path: &PortPath) -> Option<&Device> {
if port_path.is_root_hub() {
return self.get_root_hub();
}
let search_depth = port_path.depth();
let current_depth = self.get_depth();
let self_port_path = self.port_path();
log::debug!(
"Get node @ {port_path}: exploring {self_port_path} ({self}); depth {current_depth}/{search_depth}"
);
match current_depth.cmp(&search_depth) {
Ordering::Greater => return None,
Ordering::Equal => {
if self_port_path == *port_path {
return Some(self);
} else {
return None;
}
}
Ordering::Less => {}
}
if let Some(devices) = self.devices.as_ref() {
for dev in devices {
if let Some(node) = dev.get_node(port_path) {
return Some(node);
}
}
}
None
}
pub fn get_node_mut(&mut self, port_path: &PortPath) -> Option<&mut Device> {
if port_path.is_root_hub() {
return self.get_root_hub_mut();
}
let search_depth = port_path.depth();
let current_depth = self.get_depth();
let self_port_path = self.port_path();
log::debug!(
"Get node @ {port_path}: exploring {self_port_path} ({self}); depth {current_depth}/{search_depth}"
);
match current_depth.cmp(&search_depth) {
Ordering::Greater => return None,
Ordering::Equal => {
if self_port_path == *port_path {
return Some(self);
} else {
return None;
}
}
Ordering::Less => {}
}
if let Some(devices) = self.devices.as_mut() {
for dev in devices {
if let Some(node) = dev.get_node_mut(port_path) {
return Some(node);
}
}
}
None
}
pub fn get_node_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Device> {
UsbPath::from(path.as_ref())
.port_path()
.and_then(|pp| self.get_node(&pp))
}
pub fn get_node_mut_by_path<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut Device> {
UsbPath::from(path.as_ref())
.port_path()
.and_then(|pp| self.get_node_mut(&pp))
}
pub fn get_node_by_str(&self, path: &str) -> Option<&Device> {
PortPath::try_from(path)
.ok()
.and_then(|pp| self.get_node(&pp))
}
pub fn get_node_str_mut(&mut self, path: &str) -> Option<&mut Device> {
PortPath::try_from(path)
.ok()
.and_then(|pp| self.get_node_mut(&pp))
}
pub fn get_config(&self, config: u8) -> Option<&Configuration> {
self.extra
.as_ref()
.and_then(|e| e.configurations.iter().find(|c| c.number == config))
}
pub fn get_config_mut(&mut self, config: u8) -> Option<&mut Configuration> {
self.extra
.as_mut()
.and_then(|e| e.configurations.iter_mut().find(|c| c.number == config))
}
pub fn get_interface(&self, config: u8, interface: u8, alt_setting: u8) -> Option<&Interface> {
self.get_config(config).and_then(|c| {
c.interfaces
.iter()
.find(|i| i.number == interface && i.alt_setting == alt_setting)
})
}
pub fn get_interface_mut(
&mut self,
config: u8,
interface: u8,
alt_setting: u8,
) -> Option<&mut Interface> {
self.get_config_mut(config).and_then(|c| {
c.interfaces
.iter_mut()
.find(|i| i.number == interface && i.alt_setting == alt_setting)
})
}
pub fn get_endpoint(
&self,
config: u8,
interface: u8,
alt_setting: u8,
endpoint_address: u8,
) -> Option<&Endpoint> {
self.get_interface(config, interface, alt_setting)
.and_then(|i| {
i.endpoints
.iter()
.find(|e| e.address.address == endpoint_address)
})
}
pub fn get_endpoint_mut(
&mut self,
config: u8,
interface: u8,
alt_setting: u8,
endpoint_address: u8,
) -> Option<&mut Endpoint> {
self.get_interface_mut(config, interface, alt_setting)
.and_then(|i| {
i.endpoints
.iter_mut()
.find(|e| e.address.address == endpoint_address)
})
}
pub fn get_branch_position(&self) -> u8 {
self.location_id.tree_positions.last().copied().unwrap_or(0)
}
pub fn get_depth(&self) -> usize {
self.location_id.tree_positions.len()
}
pub fn is_hub(&self) -> bool {
self.name.to_lowercase().contains("hub")
|| self.class.as_ref().is_some_and(|c| *c == BaseClass::Hub)
}
pub fn port_path(&self) -> PortPath {
self.location_id.clone().into()
}
pub fn sysfs_path(&self) -> PathBuf {
UsbPath::from(self.port_path()).to_sysfs_path()
}
pub fn parent_port_path(&self) -> Option<PortPath> {
self.port_path().parent()
}
pub fn trunk_port_path(&self) -> PortPath {
self.port_path().trunk()
}
pub fn dev_path(&self) -> PathBuf {
get_dev_path(self.location_id.bus, Some(self.location_id.number))
}
pub fn sysfs_name(&self) -> String {
get_sysfs_name(self.location_id.bus, &self.location_id.tree_positions)
}
pub fn is_trunk_device(&self) -> bool {
self.location_id.tree_positions.len() == 1
}
pub fn is_root_hub(&self) -> bool {
self.location_id.tree_positions.is_empty() || self.location_id.tree_positions == [0]
}
pub fn get_vendor_product_with_fallback(&self) -> (String, String) {
match &self.extra {
Some(v) => (
v.vendor
.to_owned()
.unwrap_or(self.manufacturer.to_owned().unwrap_or_default()),
v.product_name
.to_owned()
.unwrap_or(self.name.trim().to_owned()),
),
None => (
self.manufacturer.to_owned().unwrap_or_default(),
self.name.trim().to_owned(),
),
}
}
pub fn to_lsusb_string(&self) -> String {
let (vendor, product) = self.get_vendor_product_with_fallback();
format!(
"Bus {:03} Device {:03}: ID {:04x}:{:04x} {} {}",
self.location_id.bus,
self.location_id.number,
self.vendor_id.unwrap_or(0xffff),
self.product_id.unwrap_or(0xffff),
vendor,
product,
)
}
pub fn to_lsusb_tree_strings(&self, verbosity: u8) -> Vec<Vec<String>> {
let mut ret = Vec::new();
let speed = match &self.device_speed {
Some(v) => match v {
DeviceSpeed::SpeedValue(v) => v.to_lsusb_speed(),
DeviceSpeed::Description(_) => String::new(),
},
None => String::from(""),
};
let (driver, vendor, product) = match &self.extra {
Some(v) => (
v.driver.to_owned().unwrap_or(String::from("[none]")),
v.vendor.to_owned().unwrap_or_default(),
v.product_name.to_owned().unwrap_or_default(),
),
None => (String::from("[none]"), String::new(), String::new()),
};
let get_istrings = || match (
&self.name,
self.manufacturer.as_ref(),
self.serial_num.as_ref(),
) {
(n, Some(m), Some(s)) => {
format!("Manufacturer={n} Product={m} Serial={s}")
}
(n, Some(m), None) => {
format!("Manufacturer={n} Product={m}")
}
(n, None, Some(s)) => {
format!("Product={n} Serial={s}")
}
(n, None, None) => {
format!("Product={n}")
}
};
if let Some(extra) = self.extra.as_ref() {
let ports = extra.hub.as_ref().map(|hub| hub.num_ports);
let active_config = extra
.configurations
.iter()
.find(|c| c.active)
.or_else(|| extra.configurations.first());
if let Some(config) = active_config {
let mut interfaces_to_show = Vec::new();
for (_if_num, group) in &config.interfaces.iter().chunk_by(|i| i.number) {
let group: Vec<_> = group.collect();
let active_if = group.iter().find(|i| i.active).or_else(|| group.first());
if let Some(interface) = active_if {
interfaces_to_show.push(*interface);
}
}
for interface in interfaces_to_show {
let mut device_strings = Vec::with_capacity(verbosity as usize);
let interface_driver = interface
.driver
.as_ref()
.map_or(String::from("[none]"), |d| d.to_string());
let driver_string = if let Some(p) = ports {
format!("{interface_driver}/{p}p")
} else {
interface_driver
};
device_strings.push(format!(
"Port {:03}: Dev {:03}, If {}, Class={}, Driver={}, {}",
self.get_branch_position(),
self.location_id.number,
interface.number,
interface.class.to_lsusb_string(),
driver_string,
speed
));
if verbosity > 0 {
device_strings.push(format!(
"ID {:04x}:{:04x} {} {}",
self.vendor_id.unwrap_or(0xFFFF),
self.product_id.unwrap_or(0xFFFF),
vendor,
product,
));
}
if verbosity > 1 {
device_strings.push(format!(
"{}/{} {}",
"/sys/bus/usb/devices",
self.port_path(),
self.dev_path().display(),
));
}
if verbosity > 2 {
device_strings.push(get_istrings());
}
ret.push(device_strings);
}
}
} else {
log::warn!("Rendering {self} lsusb tree without extra data because it is missing. No configurations or interfaces will be shown");
let mut device_strings = Vec::with_capacity(verbosity as usize);
device_strings.push(format!(
"Port {:03}: Dev {:03}, If {}, Class={}, Driver={}, {}",
self.get_branch_position(),
self.location_id.number,
0,
self.class
.as_ref()
.map_or(String::from("[unknown]"), |c| c.to_lsusb_string()),
driver,
speed
));
if verbosity > 0 {
device_strings.push(format!(
"ID {:04x}:{:04x} {} {}",
self.vendor_id.unwrap_or(0xFFFF),
self.product_id.unwrap_or(0xFFFF),
self.manufacturer
.as_ref()
.unwrap_or(&String::from("[unknown]")),
self.name,
));
}
if verbosity > 1 {
device_strings.push(format!(
"{}/{} {}",
"/sys/bus/usb/devices",
self.port_path(),
self.dev_path().display(),
));
}
if verbosity > 2 {
device_strings.push(get_istrings());
}
ret.push(device_strings);
}
ret
}
pub fn base_class_code(&self) -> Option<u8> {
self.class.as_ref().map(|c| u8::from(*c))
}
pub fn class_name(&self) -> Option<&str> {
match self.base_class_code() {
Some(cid) => usb_ids::Classes::iter()
.find(|c| c.id() == cid)
.map(|c| c.name()),
None => None,
}
}
pub fn sub_class_name(&self) -> Option<&str> {
match (self.base_class_code(), self.sub_class) {
(Some(cid), Some(sid)) => {
usb_ids::SubClass::from_cid_scid(cid, sid).map(|sc| sc.name())
}
_ => None,
}
}
pub fn protocol_name(&self) -> Option<&str> {
match (self.base_class_code(), self.sub_class, self.protocol) {
(Some(cid), Some(sid), Some(pid)) => {
usb_ids::Protocol::from_cid_scid_pid(cid, sid, pid).map(|p| p.name())
}
_ => None,
}
}
pub fn fully_defined_class(&self) -> Option<ClassCode> {
self.class
.map(|c| (c, self.sub_class.unwrap_or(0), self.protocol.unwrap_or(0)).into())
}
pub fn flatten(&self) -> Vec<&Device> {
let mut ret: Vec<&Device> = Vec::with_capacity(self.len());
ret.push(self);
if let Some(d) = self.devices.as_ref() {
for child in d {
ret.extend(child.flatten());
}
}
ret
}
pub fn flatten_mut(&mut self) -> Vec<&mut Device> {
if let Some(d) = self.devices.as_mut() {
d.iter_mut()
.flat_map(|d| d.flatten_mut())
.collect::<Vec<&mut Device>>()
} else {
Vec::new()
}
}
pub fn into_flattened(mut self) -> Vec<Device> {
let mut ret: Vec<Device> = Vec::with_capacity(self.len());
if let Some(mut d) = self.devices.take() {
while let Some(child) = d.pop() {
ret.extend(child.into_flattened());
}
}
ret.insert(0, self);
ret
}
#[cfg(feature = "nusb")]
pub fn get_id(&self, id: &::nusb::DeviceId) -> Option<&Self> {
if self.id == Some(*id) {
return Some(self);
}
if let Some(devices) = self.devices.as_ref() {
for dev in devices {
if let Some(node) = dev.get_id(id) {
return Some(node);
}
}
}
None
}
#[cfg(feature = "nusb")]
pub fn get_id_mut(&mut self, id: &::nusb::DeviceId) -> Option<&mut Self> {
if self.id == Some(*id) {
return Some(self);
}
if let Some(devices) = self.devices.as_mut() {
for dev in devices {
if let Some(node) = dev.get_id_mut(id) {
return Some(node);
}
}
}
None
}
pub fn last_event(&self) -> Option<DeviceEvent> {
self.last_event
}
pub fn is_disconnected(&self) -> bool {
matches!(self.last_event, Some(DeviceEvent::Disconnected(_)))
}
pub fn is_hidden(&self) -> bool {
self.internal.hidden
}
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;
if let Some(extra) = self.extra.as_mut() {
for config in &mut extra.configurations {
config.set_all_expanded(expanded);
}
}
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut spaces = if f.sign_plus() {
self.location_id.tree_positions.len() * 4
} else {
0
};
let speed = match &self.device_speed {
Some(v) => v.to_string(),
None => String::from(""),
};
let tree: &str = if !f.sign_plus() {
""
} else if f.alternate() {
"\u{2514}\u{2500}\u{2500} "
} else {
"|__ "
};
if f.alternate() {
write!(
f,
"{:>spaces$}{}/{} {}:{} {} {} {}",
tree.bright_black(),
format!("{:03}", self.location_id.bus).cyan(),
format!("{:03}", self.location_id.number).magenta(),
format!("0x{:04x}", self.vendor_id.unwrap_or(0))
.yellow()
.bold(),
format!("0x{:04x}", self.product_id.unwrap_or(0)).yellow(),
self.name.trim().bold().blue(),
self.serial_num
.as_ref()
.unwrap_or(&String::from("None"))
.trim()
.green(),
speed.purple()
)
} else {
if f.sign_plus() {
if spaces > 0 {
spaces += 3;
}
let interface_strs: Vec<String> = self
.to_lsusb_tree_strings(0)
.iter()
.map(|s| format!("{:>spaces$}{}", tree, s[0]))
.collect();
write!(f, "{}", interface_strs.join("\n\r"))
} else {
write!(f, "{}", self.to_lsusb_string())
}
}
}
}
impl<'a> IntoIterator for &'a Device {
type Item = &'a Device;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> std::vec::IntoIter<Self::Item> {
if let Some(d) = self.devices.as_ref() {
d.iter()
.flat_map(|d| d.flatten())
.collect::<Vec<&Device>>()
.into_iter()
} else {
Vec::new().into_iter()
}
}
}
impl<'a> IntoIterator for &'a mut Device {
type Item = &'a mut Device;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> std::vec::IntoIter<Self::Item> {
self.flatten_mut().into_iter()
}
}
#[skip_serializing_none]
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilterEntry {
pub vidpid: Option<VidPid>,
pub name: Option<String>,
pub serial: Option<String>,
pub class: Option<BaseClass>,
pub bus: Option<u8>,
pub number: Option<u8>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub case_sensitive: bool,
}
impl From<FilterEntry> for Filter {
fn from(e: FilterEntry) -> Self {
Filter {
vid: e.vidpid.and_then(|v| v.0),
pid: e.vidpid.and_then(|v| v.1),
name: e.name,
serial: e.serial,
class: e.class,
bus: e.bus,
number: e.number,
case_sensitive: e.case_sensitive,
}
}
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(default)]
pub struct Filter {
pub vid: Option<u16>,
pub pid: Option<u16>,
pub bus: Option<u8>,
pub number: Option<u8>,
pub name: Option<String>,
pub serial: Option<String>,
pub class: Option<BaseClass>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub case_sensitive: bool,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ProfileDepth {
#[default]
Identity,
Standard,
Full,
}
impl ProfileDepth {
pub fn includes_extra(&self) -> bool {
matches!(self, Self::Standard | Self::Full)
}
pub fn includes_more_extra(&self) -> bool {
matches!(self, Self::Full)
}
pub fn is_verbose(&self) -> bool {
matches!(self, Self::Full)
}
pub fn set_verbose(&mut self, verbose: bool) {
*self = if verbose { Self::Full } else { Self::Identity };
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ProfilerOptions {
pub filter: Option<DeviceFilter>,
pub depth: ProfileDepth,
pub tree: bool,
}
impl ProfilerOptions {
pub fn new() -> Self {
Default::default()
}
pub fn set_verbose(&mut self, verbose: bool) {
self.depth.set_verbose(verbose);
}
pub fn is_verbose(&self) -> bool {
self.depth.is_verbose()
}
}
#[deprecated(since = "2.0.0", note = "Use Filter instead")]
pub type USBFilter = Filter;
impl Filter {
pub fn new() -> Self {
Default::default()
}
pub fn new_with_name(name: String, case_sensitive: bool) -> Self {
Self {
name: Some(name),
case_sensitive,
..Default::default()
}
}
pub fn new_with_vid_pid(vid: u16, pid: u16) -> Self {
Self {
vid: Some(vid),
pid: Some(pid),
..Default::default()
}
}
pub fn new_with_bus_number(bus: u8, number: u8) -> Self {
Self {
bus: Some(bus),
number: Some(number),
..Default::default()
}
}
pub fn new_with_class(class: BaseClass) -> Self {
Self {
class: Some(class),
..Default::default()
}
}
pub fn new_with_serial(serial: String, case_sensitive: bool) -> Self {
Self {
serial: Some(serial),
case_sensitive,
..Default::default()
}
}
fn string_match(&self, pattern: &Option<String>, query: Option<&String>) -> bool {
if let (Some(p), Some(q)) = (pattern, query) {
let case_sensitive = if !self.case_sensitive {
p.chars().any(|c| c.is_uppercase())
} else {
self.case_sensitive
};
if !case_sensitive {
q.to_lowercase().contains(p.to_lowercase().as_str())
} else {
q.contains(p.as_str())
}
} else {
true
}
}
pub fn is_match(&self, device: &Device) -> bool {
self.is_identity_match(device)
}
pub fn is_identity_match(&self, device: &Device) -> bool {
(Some(device.location_id.bus) == self.bus || self.bus.is_none())
&& (Some(device.location_id.number) == self.number || self.number.is_none())
&& (device.vendor_id == self.vid || self.vid.is_none())
&& (device.product_id == self.pid || self.pid.is_none())
&& (self.string_match(&self.name, Some(&device.name)))
&& (self.string_match(&self.serial, device.serial_num.as_ref()))
&& self.class.as_ref().is_none_or(|fc| {
device.class.as_ref() == Some(fc) || device.has_interface_class(fc)
})
}
pub fn is_potential_match(&self, device: &Device) -> bool {
(Some(device.location_id.bus) == self.bus || self.bus.is_none())
&& (Some(device.location_id.number) == self.number || self.number.is_none())
&& (device.vendor_id == self.vid || self.vid.is_none())
&& (device.product_id == self.pid || self.pid.is_none())
&& (self.string_match(&self.name, Some(&device.name)))
&& (self.string_match(&self.serial, device.serial_num.as_ref()))
&& self.class.as_ref().is_none_or(|fc| {
device.class.as_ref() == Some(fc) || device.extra.is_none()
})
}
pub fn exists_in_tree(&self, device: &Device) -> bool {
if self.is_match(device) {
return true;
}
match &device.devices {
Some(devs) => devs.iter().any(|d| self.exists_in_tree(d)),
None => false,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case", default)]
pub struct DeviceFilter {
pub filters: Vec<Filter>,
pub exclude_filters: Vec<Filter>,
pub exclude_empty_bus: bool,
pub exclude_empty_hub: bool,
pub include_root_hubs: bool,
}
impl DeviceFilter {
pub fn new() -> Self {
Default::default()
}
pub fn is_match(&self, device: &Device) -> bool {
if self.exclude_empty_hub && device.is_hub() && !device.has_devices() {
return false;
}
if device.is_root_hub() && !self.include_root_hubs {
return false;
}
let included =
self.filters.is_empty() || self.filters.iter().any(|f| f.is_identity_match(device));
if !included {
return false;
}
!self
.exclude_filters
.iter()
.any(|f| f.is_identity_match(device))
}
pub fn is_potential_match(&self, device: &Device) -> bool {
if device.is_root_hub() && !self.include_root_hubs {
return false;
}
let potentially_included =
self.filters.is_empty() || self.filters.iter().any(|f| f.is_potential_match(device));
if !potentially_included {
return false;
}
!self
.exclude_filters
.iter()
.any(|f| f.class.is_none() && f.is_identity_match(device))
}
pub fn is_bus_match(&self, bus: &Bus) -> bool {
if self.exclude_empty_bus && bus.is_empty() {
return false;
}
if self.filters.is_empty() {
return true;
}
let bus_no = bus.usb_bus_number;
self.filters
.iter()
.any(|f| f.bus.is_none() || bus_no == f.bus || bus_no.is_none())
}
pub fn exists_in_tree(&self, device: &Device) -> bool {
if self.is_match(device) {
return true;
}
match &device.devices {
Some(devs) => devs.iter().any(|d| self.exists_in_tree(d)),
None => false,
}
}
pub fn retain_buses(&self, buses: &mut Vec<Bus>) {
buses.retain(|b| self.is_bus_match(b));
for bus in buses.iter_mut() {
bus.devices.iter_mut().for_each(|d| self.retain_devices(d));
}
buses.retain(|b| self.is_bus_match(b));
}
pub fn hide_buses(&self, buses: &mut [Bus]) {
buses
.iter_mut()
.for_each(|b| b.internal.hidden = !self.is_bus_match(b));
for bus in buses.iter_mut() {
bus.devices.iter_mut().for_each(|d| self.hide_devices(d));
}
buses
.iter_mut()
.for_each(|b| b.internal.hidden = !self.is_bus_match(b));
}
pub fn retain_devices(&self, devices: &mut Vec<Device>) {
devices.retain(|d| self.exists_in_tree(d));
for d in devices {
d.devices.iter_mut().for_each(|d| self.retain_devices(d));
}
}
pub fn hide_devices(&self, devices: &mut [Device]) {
devices
.iter_mut()
.for_each(|d| d.internal.hidden = !self.exists_in_tree(d));
for d in devices {
d.devices.iter_mut().for_each(|d| self.hide_devices(d));
}
}
pub fn retain_flattened_devices_ref(&self, devices: &mut Vec<&Device>) {
devices.retain(|d| self.is_match(d))
}
}
impl From<Filter> for DeviceFilter {
fn from(filter: Filter) -> Self {
DeviceFilter {
filters: vec![filter],
..Default::default()
}
}
}
impl From<Vec<Filter>> for DeviceFilter {
fn from(filters: Vec<Filter>) -> Self {
DeviceFilter {
filters,
..Default::default()
}
}
}
pub fn read_json_dump<P: AsRef<Path>>(file_path: P) -> Result<SystemProfile> {
let mut file = fs::File::options().read(true).open(file_path.as_ref())?;
let mut data = String::new();
file.read_to_string(&mut data)?;
let json_dump: SystemProfile = serde_json::from_str(&data).map_err(|e| {
Error::new(
ErrorKind::Parsing,
&format!(
"Failed to parse dump at {:?}; Error({})",
file_path.as_ref().display(),
e
),
)
})?;
Ok(json_dump)
}
pub fn read_flat_json_dump<P: AsRef<Path>>(file_path: P) -> Result<Vec<Device>> {
let mut file = fs::File::options().read(true).open(file_path.as_ref())?;
let mut data = String::new();
file.read_to_string(&mut data)?;
let json_dump: Vec<Device> = serde_json::from_str(&data).map_err(|e| {
Error::new(
ErrorKind::Parsing,
&format!(
"Failed to parse dump at {:?}; Error({})",
file_path.as_ref().display(),
e
),
)
})?;
Ok(json_dump)
}
pub fn read_flat_json_to_phony_bus<P: AsRef<Path>>(file_path: P) -> Result<SystemProfile> {
let devices = read_flat_json_dump(file_path)?;
let bus = Bus {
name: String::from("Phony Flat JSON Import Bus"),
host_controller: String::from("Phony Host Controller"),
host_controller_vendor: None,
host_controller_device: None,
pci_device: None,
pci_vendor: None,
pci_revision: None,
usb_bus_number: None,
devices: Some(devices),
..Default::default()
};
Ok(SystemProfile { buses: vec![bus] })
}
fn deserialize_option_number_from_string<'de, T, D>(
deserializer: D,
) -> core::result::Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr + serde::Deserialize<'de>,
<T as FromStr>::Err: fmt::Display,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum NumericOrNull<'a, T> {
Str(&'a str),
FromStr(T),
Null,
}
match NumericOrNull::<T>::deserialize(deserializer)? {
NumericOrNull::Str(mut s) => match s {
"" => Ok(None),
_ => {
if s.contains("apple_vendor_id") {
s = "0x05ac";
}
let vendor_vec: Vec<&str> = s.split(' ').collect();
if s.contains("0x") {
let removed_0x = vendor_vec[0].trim_start_matches("0x");
let base16_num = u64::from_str_radix(removed_0x.trim(), 16);
let result = match base16_num {
Ok(num) => T::from_str(num.to_string().as_str()),
Err(e) => return Err(serde::de::Error::custom(e)),
};
result.map(Some).map_err(serde::de::Error::custom)
} else {
T::from_str(s.trim())
.map(Some)
.map_err(serde::de::Error::custom)
}
}
},
NumericOrNull::FromStr(i) => Ok(Some(i)),
NumericOrNull::Null => Ok(None),
}
}
fn deserialize_option_version_from_string<'de, D>(
deserializer: D,
) -> core::result::Result<Option<Version>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum VersionOrNull {
#[serde(deserialize_with = "deserialize_version")]
From(Version),
Null,
}
match VersionOrNull::deserialize(deserializer)? {
VersionOrNull::From(i) => Ok(Some(i)),
VersionOrNull::Null => Ok(None),
}
}
fn deserialize_version<'de, D>(deserializer: D) -> core::result::Result<Version, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct VersionVisitor;
impl serde::de::Visitor<'_> for VersionVisitor {
type Value = Version;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("BCD version base16 encoding [MM.mP] where MM is Major, m is Minor and P is sub-minor")
}
fn visit_f32<E>(self, value: f32) -> core::result::Result<Version, E>
where
E: serde::de::Error,
{
Version::try_from(value)
.map_err(|_| E::invalid_value(serde::de::Unexpected::Float(value.into()), &self))
}
fn visit_str<E>(self, value: &str) -> core::result::Result<Version, E>
where
E: serde::de::Error,
{
Version::from_str(value)
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))
}
}
deserializer.deserialize_any(VersionVisitor)
}
fn version_serializer<S>(version: &Option<Version>, s: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match version {
Some(v) => s.serialize_str(&v.to_string()),
None => s.serialize_none(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_device() {
let device_json = "{
\"_name\" : \"Arduino Zero\",
\"bcd_device\" : \"1.00\",
\"bus_power\" : \"500\",
\"bus_power_used\" : \"500\",
\"device_speed\" : \"full_speed\",
\"extra_current_used\" : \"0\",
\"location_id\" : \"0x02110000 / 3\",
\"manufacturer\" : \"Arduino LLC\",
\"product_id\" : \"0x804d\",
\"serial_num\" : \"6DC00ADC5053574C342E3120FF122422\",
\"vendor_id\" : \"0x2341\"
}";
let device: Device = serde_json::from_str(device_json).unwrap();
assert_eq!(device.name, "Arduino Zero");
assert_eq!(device.bcd_device, Some(Version(1, 0, 0)));
assert_eq!(device.bus_power, Some(500));
assert_eq!(device.bus_power_used, Some(500));
assert_eq!(
device.device_speed,
Some(DeviceSpeed::SpeedValue(Speed::FullSpeed))
);
assert_eq!(device.extra_current_used, Some(0));
assert_eq!(
device.location_id,
DeviceLocation {
bus: 2,
tree_positions: vec![1, 1],
number: 3,
}
);
assert_eq!(device.manufacturer, Some("Arduino LLC".to_string()));
assert_eq!(device.product_id, Some(0x804d));
assert_eq!(device.vendor_id, Some(0x2341));
}
#[test]
fn test_deserialize_bus() {
let device_json = "{
\"_name\" : \"USB31Bus\",
\"host_controller\" : \"AppleUSBXHCITR\",
\"pci_device\" : \"0x15f0 \",
\"pci_revision\" : \"0x0006 \",
\"pci_vendor\" : \"0x8086 \",
\"usb_bus_number\" : \"0x00 \"
}";
let device: Bus = serde_json::from_str(device_json).unwrap();
assert_eq!(device.name, "USB31Bus");
assert_eq!(device.host_controller, "AppleUSBXHCITR");
assert_eq!(device.pci_device, Some(0x15f0));
assert_eq!(device.pci_revision, Some(0x0006));
assert_eq!(device.pci_vendor, Some(0x8086));
assert_eq!(device.usb_bus_number, Some(0x00));
}
#[test]
fn test_json_dump_read_not_panic() {
read_json_dump("./tests/data/system_profiler_dump.json").unwrap();
}
}