Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for getting device information from opened device on Linux #102

Merged
merged 11 commits into from
Jan 20, 2025
4 changes: 4 additions & 0 deletions examples/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ fn inspect_device(dev: DeviceInfo) {
}
};

println!("{:#?}", dev.device_descriptor());

println!("Speed: {:?}", dev.speed());

match dev.active_configuration() {
Ok(config) => println!("Active configuration is {}", config.configuration_value()),
Err(e) => println!("Unknown active configuration: {e}"),
Expand Down
18 changes: 4 additions & 14 deletions examples/string_descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,7 @@ fn inspect_device(dev: DeviceInfo) {

let timeout = Duration::from_millis(100);

let dev_descriptor = dev.get_descriptor(0x01, 0, 0, timeout).unwrap();
if dev_descriptor.len() < 18
|| dev_descriptor[0] as usize > dev_descriptor.len()
|| dev_descriptor[1] != 0x01
{
println!(" Invalid device descriptor: {dev_descriptor:?}");
return;
}
let dev_descriptor = dev.device_descriptor();

let languages: Vec<u16> = dev
.get_string_descriptor_supported_languages(timeout)
Expand All @@ -46,20 +39,17 @@ fn inspect_device(dev: DeviceInfo) {

let language = languages.first().copied().unwrap_or(US_ENGLISH);

let i_manufacturer = dev_descriptor[14];
if i_manufacturer != 0 {
if let Some(i_manufacturer) = dev_descriptor.manufacturer_string_index() {
let s = dev.get_string_descriptor(i_manufacturer, language, timeout);
println!(" Manufacturer({i_manufacturer}): {s:?}");
}

let i_product = dev_descriptor[15];
if i_product != 0 {
if let Some(i_product) = dev_descriptor.product_string_index() {
let s = dev.get_string_descriptor(i_product, language, timeout);
println!(" Product({i_product}): {s:?}");
}

let i_serial = dev_descriptor[16];
if i_serial != 0 {
if let Some(i_serial) = dev_descriptor.serial_number_string_index() {
let s = dev.get_string_descriptor(i_serial, language, timeout);
println!(" Serial({i_serial}): {s:?}");
}
Expand Down
206 changes: 204 additions & 2 deletions src/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
Error,
};

pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01;
pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18;

pub(crate) const DESCRIPTOR_TYPE_CONFIGURATION: u8 = 0x02;
Expand Down Expand Up @@ -164,13 +165,13 @@ impl<'a> Iterator for Descriptors<'a> {
}

macro_rules! descriptor_fields {
(impl<'a> $tname:ident<'a> {
(impl $(<$( $i_lt:lifetime ),+>)? $tname:ident $(<$( $t_lt:lifetime ),+>)? {
$(
$(#[$attr:meta])*
$vis:vis fn $name:ident at $pos:literal -> $ty:ty;
)*
}) => {
impl<'a> $tname<'a> {
impl $(<$( $i_lt ),+>)? $tname $(<$( $t_lt ),+>)? {
$(
$(#[$attr])*
#[inline]
Expand All @@ -180,6 +181,189 @@ macro_rules! descriptor_fields {
}
}

/// Check whether the buffer contains a valid device descriptor.
/// On success, it will return length of the descriptor, or returns `None`.
#[allow(unused)]
pub(crate) fn validate_device_descriptor(buf: &[u8]) -> Option<usize> {
if buf.len() < DESCRIPTOR_LEN_DEVICE as usize {
if buf.len() != 0 {
warn!(
"device descriptor buffer is {} bytes, need {}",
buf.len(),
DESCRIPTOR_LEN_DEVICE
);
}
return None;
}

if buf[0] < DESCRIPTOR_LEN_DEVICE {
warn!("invalid device descriptor bLength");
return None;
}

if buf[1] != DESCRIPTOR_TYPE_DEVICE {
warn!(
"device bDescriptorType is {}, not a device descriptor",
buf[1]
);
return None;
}

return Some(buf[0] as usize);
}

/// Information about a USB device.
#[derive(Clone)]
pub struct DeviceDescriptor([u8; DESCRIPTOR_LEN_DEVICE as usize]);

impl DeviceDescriptor {
/// Create a `DeviceDescriptor` from a buffer beginning with a device descriptor.
///
/// You normally obtain a `DeviceDescriptor` from a [`Device`][crate::Device], but this allows creating
/// one from your own descriptor bytes for tests.
///
/// ### Panics
/// * when the buffer is too short for a device descriptor
/// * when the first descriptor is not a device descriptor
pub fn new(buf: &[u8]) -> Self {
assert!(buf.len() >= DESCRIPTOR_LEN_DEVICE as usize);
assert!(buf[0] as usize >= DESCRIPTOR_LEN_DEVICE as usize);
assert!(buf[1] == DESCRIPTOR_TYPE_DEVICE);
Self(buf[0..DESCRIPTOR_LEN_DEVICE as usize].try_into().unwrap())
}

/// Get the bytes of the descriptor.
pub fn as_bytes(&self) -> &[u8] {
&self.0
}

#[allow(unused)]
pub(crate) fn from_fields(
usb_version: u16,
class: u8,
subclass: u8,
protocol: u8,
max_packet_size_0: u8,
vendor_id: u16,
product_id: u16,
device_version: u16,
manufacturer_string_index: u8,
product_string_index: u8,
serial_number_string_index: u8,
num_configurations: u8,
) -> DeviceDescriptor {
DeviceDescriptor([
DESCRIPTOR_LEN_DEVICE,
DESCRIPTOR_TYPE_DEVICE,
usb_version.to_le_bytes()[0],
usb_version.to_le_bytes()[1],
class,
subclass,
protocol,
max_packet_size_0,
vendor_id.to_le_bytes()[0],
vendor_id.to_le_bytes()[1],
product_id.to_le_bytes()[0],
product_id.to_le_bytes()[1],
device_version.to_le_bytes()[0],
device_version.to_le_bytes()[1],
manufacturer_string_index,
product_string_index,
serial_number_string_index,
num_configurations,
])
}
}

descriptor_fields! {
impl DeviceDescriptor {
/// `bcdUSB` descriptor field: USB Specification Number.
#[doc(alias = "bcdUSB")]
pub fn usb_version at 2 -> u16;

/// `bDeviceClass` descriptor field: Class code, assigned by USB-IF.
#[doc(alias = "bDeviceClass")]
pub fn class at 4 -> u8;

/// `bDeviceSubClass` descriptor field: Subclass code, assigned by USB-IF.
#[doc(alias = "bDeviceSubClass")]
pub fn subclass at 5 -> u8;

/// `bDeviceProtocol` descriptor field: Protocol code, assigned by USB-IF.
#[doc(alias = "bDeviceProtocol")]
pub fn protocol at 6 -> u8;

/// `bMaxPacketSize0` descriptor field: Maximum packet size for 0 Endpoint.
#[doc(alias = "bMaxPacketSize0")]
pub fn max_packet_size_0 at 7 -> u8;

/// `idVendor` descriptor field: Vendor ID, assigned by USB-IF.
#[doc(alias = "idVendor")]
pub fn vendor_id at 8 -> u16;

/// `idProduct` descriptor field: Product ID, assigned by the manufacturer.
#[doc(alias = "idProduct")]
pub fn product_id at 10 -> u16;

/// `bcdDevice` descriptor field: Device release number.
#[doc(alias = "bcdDevice")]
pub fn device_version at 12 -> u16;

fn manufacturer_string_index_raw at 14 -> u8;
fn product_string_index_raw at 15 -> u8;
fn serial_number_string_index_raw at 16 -> u8;

/// `bNumConfigurations` descriptor field: Number of configurations
#[doc(alias = "bNumConfigurations")]
pub fn num_configurations at 17 -> u8;
}
}

impl DeviceDescriptor {
/// `iManufacturer` descriptor field: Index for manufacturer description string.
pub fn manufacturer_string_index(&self) -> Option<u8> {
Some(self.manufacturer_string_index_raw()).filter(|&i| i != 0)
}

/// `iProduct` descriptor field: Index for product description string.
pub fn product_string_index(&self) -> Option<u8> {
Some(self.product_string_index_raw()).filter(|&i| i != 0)
}

/// `iSerialNumber` descriptor field: Index for serial number string.
pub fn serial_number_string_index(&self) -> Option<u8> {
Some(self.serial_number_string_index_raw()).filter(|&i| i != 0)
}
}
impl Debug for DeviceDescriptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeviceDescriptor")
.field("usb_version", &format_args!("0x{:04X}", self.usb_version()))
.field("class", &format_args!("0x{:02X}", self.class()))
.field("subclass", &format_args!("0x{:02X}", self.subclass()))
.field("protocol", &format_args!("0x{:02X}", self.protocol()))
.field("max_packet_size_0", &self.max_packet_size_0())
.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id()))
.field("product_id", &format_args!("0x{:04X}", self.product_id()))
.field(
"device_version",
&format_args!("0x{:04X}", self.device_version()),
)
.field(
"manufacturer_string_index",
&self.manufacturer_string_index(),
)
.field("product_string_index", &self.product_string_index())
.field(
"serial_number_string_index",
&self.serial_number_string_index(),
)
.field("num_configurations", &self.num_configurations())
.finish()
}
}

#[allow(unused)]
pub(crate) fn validate_config_descriptor(buf: &[u8]) -> Option<usize> {
if buf.len() < DESCRIPTOR_LEN_CONFIGURATION as usize {
if buf.len() != 0 {
Expand Down Expand Up @@ -549,6 +733,7 @@ impl From<ActiveConfigurationError> for Error {
}

/// Split a chain of concatenated configuration descriptors by `wTotalLength`
#[allow(unused)]
pub(crate) fn parse_concatenated_config_descriptors(mut buf: &[u8]) -> impl Iterator<Item = &[u8]> {
iter::from_fn(move || {
let total_len = validate_config_descriptor(buf)?;
Expand Down Expand Up @@ -659,6 +844,23 @@ fn test_malformed() {
#[test]
#[rustfmt::skip]
fn test_linux_root_hub() {
let dev = DeviceDescriptor::new(&[
0x12, 0x01, 0x00, 0x02, 0x09, 0x00, 0x01, 0x40, 0x6b,
0x1d, 0x02, 0x00, 0x10, 0x05, 0x03, 0x02, 0x01, 0x01
]);
assert_eq!(dev.usb_version(), 0x0200);
assert_eq!(dev.class(), 0x09);
assert_eq!(dev.subclass(), 0x00);
assert_eq!(dev.protocol(), 0x01);
assert_eq!(dev.max_packet_size_0(), 64);
assert_eq!(dev.vendor_id(), 0x1d6b);
assert_eq!(dev.product_id(), 0x0002);
assert_eq!(dev.device_version(), 0x0510);
assert_eq!(dev.manufacturer_string_index(), Some(3));
assert_eq!(dev.product_string_index(), Some(2));
assert_eq!(dev.serial_number_string_index(), Some(1));
assert_eq!(dev.num_configurations(), 1);

let c = Configuration(&[
0x09, 0x02, 0x19, 0x00, 0x01, 0x01, 0x00, 0xe0, 0x00,
0x09, 0x04, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00,
Expand Down
16 changes: 14 additions & 2 deletions src/device.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::{
descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError,
Configuration, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
Configuration, DeviceDescriptor, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
},
platform,
transfer::{
Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError,
TransferFuture,
},
DeviceInfo, Error,
DeviceInfo, Error, Speed,
};
use log::error;
use std::{io::ErrorKind, sync::Arc, time::Duration};
Expand Down Expand Up @@ -94,6 +94,18 @@ impl Device {
Ok(())
}

/// Get the device descriptor.
///
/// This returns cached data and does not perform IO.
pub fn device_descriptor(&self) -> DeviceDescriptor {
self.backend.device_descriptor()
}

/// Get device speed.
pub fn speed(&self) -> Option<Speed> {
self.backend.speed()
}

/// Get information about the active configuration.
///
/// This returns cached data and does not perform IO. However, it can fail if the
Expand Down
30 changes: 28 additions & 2 deletions src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ use super::{
usbfs::{self, Urb},
SysfsPath,
};
use crate::descriptors::Configuration;
use crate::descriptors::{validate_device_descriptor, Configuration, DeviceDescriptor};
use crate::platform::linux_usbfs::events::Watch;
use crate::transfer::{ControlType, Recipient};
use crate::{
descriptors::{parse_concatenated_config_descriptors, DESCRIPTOR_LEN_DEVICE},
transfer::{
notify_completion, Control, Direction, EndpointType, TransferError, TransferHandle,
},
DeviceInfo, Error,
DeviceInfo, Error, Speed,
};

pub(crate) struct LinuxDevice {
Expand Down Expand Up @@ -84,6 +84,13 @@ impl LinuxDevice {
buf
};

let Some(_) = validate_device_descriptor(&descriptors) else {
return Err(Error::new(
ErrorKind::InvalidData,
"invalid device descriptor",
));
};

let active_config = if let Some(active_config) = active_config {
active_config
} else {
Expand Down Expand Up @@ -152,6 +159,10 @@ impl LinuxDevice {
}
}

pub(crate) fn device_descriptor(&self) -> DeviceDescriptor {
DeviceDescriptor::new(&self.descriptors)
}

pub(crate) fn configuration_descriptors(&self) -> impl Iterator<Item = &[u8]> {
parse_concatenated_config_descriptors(&self.descriptors[DESCRIPTOR_LEN_DEVICE as usize..])
}
Expand Down Expand Up @@ -405,6 +416,21 @@ impl LinuxDevice {
);
return Err(ErrorKind::Other.into());
}

pub(crate) fn speed(&self) -> Option<Speed> {
usbfs::get_speed(&self.fd)
.inspect_err(|e| log::error!("USBDEVFS_GET_SPEED failed: {e}"))
.ok()
.and_then(|raw_speed| match raw_speed {
1 => Some(Speed::Low),
2 => Some(Speed::Full),
3 => Some(Speed::High),
// 4 is wireless USB, but we don't support it
5 => Some(Speed::Super),
6 => Some(Speed::SuperPlus),
_ => None,
})
}
}

impl Drop for LinuxDevice {
Expand Down
Loading
Loading