API to make a GET_DESCRIPTOR request

This commit is contained in:
Kevin Mehall 2023-12-16 13:43:54 -07:00
parent e153fd0fc0
commit bf0db93029
5 changed files with 227 additions and 9 deletions

View file

@ -0,0 +1,68 @@
use std::time::Duration;
use nusb::{descriptors::language_id::US_ENGLISH, DeviceInfo};
fn main() {
env_logger::init();
for dev in nusb::list_devices().unwrap() {
inspect_device(dev);
}
}
fn inspect_device(dev: DeviceInfo) {
println!(
"Device {:03}.{:03} ({:04x}:{:04x}) {} {}",
dev.bus_number(),
dev.device_address(),
dev.vendor_id(),
dev.product_id(),
dev.manufacturer_string().unwrap_or(""),
dev.product_string().unwrap_or("")
);
let dev = match dev.open() {
Ok(dev) => dev,
Err(e) => {
println!("Failed to open device: {}", e);
return;
}
};
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 languages: Vec<u16> = dev
.get_string_descriptor_supported_languages(timeout)
.map(|i| i.collect())
.unwrap_or_default();
println!(" Languages: {languages:02x?}");
let language = languages.first().copied().unwrap_or(US_ENGLISH);
let i_manufacturer = dev_descriptor[14];
if i_manufacturer != 0 {
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 {
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 {
let s = dev.get_string_descriptor(i_serial, language, timeout);
println!(" Serial({i_serial}): {s:?}");
}
println!("");
}

View file

@ -28,6 +28,16 @@ pub(crate) const DESCRIPTOR_LEN_INTERFACE: u8 = 9;
pub(crate) const DESCRIPTOR_TYPE_ENDPOINT: u8 = 0x05;
pub(crate) const DESCRIPTOR_LEN_ENDPOINT: u8 = 7;
/// USB defined language IDs for string descriptors.
///
/// In practice, different language IDs are not used,
/// and device string descriptors are only provided
/// with [`language_id::US_ENGLISH`].
pub mod language_id {
/// US English
pub const US_ENGLISH: u16 = 0x0409;
}
/// A raw USB descriptor.
///
/// Wraps a byte slice to provide access to the bytes of a descriptor by implementing `Deref` to `[u8]`,

View file

@ -1,4 +1,6 @@
use std::{sync::Arc, time::Duration};
use std::{io::ErrorKind, sync::Arc, time::Duration};
use log::error;
use crate::{
descriptors::{ActiveConfigurationError, Configuration},
@ -10,6 +12,9 @@ use crate::{
DeviceInfo, Error,
};
const STANDARD_REQUEST_GET_DESCRIPTOR: u8 = 0x06;
const DESCRIPTOR_TYPE_STRING: u8 = 0x03;
/// An opened USB device.
///
/// Obtain a `Device` by calling [`DeviceInfo::open`]:
@ -77,6 +82,103 @@ impl Device {
self.backend.set_configuration(configuration)
}
/// Request a descriptor from the device.
///
/// The `language_id` should be `0` unless you are requesting a string descriptor.
///
/// ### Platform-specific details
///
/// * On Windows, the timeout argument is ignored, and an OS-defined timeout is used.
/// * On Windows, this does not wake suspended devices. Reading their
/// descriptors will return an error.
pub fn get_descriptor(
&self,
desc_type: u8,
desc_index: u8,
language_id: u16,
timeout: Duration,
) -> Result<Vec<u8>, Error> {
#[cfg(target_os = "windows")]
{
let _ = timeout;
self.backend
.get_descriptor(desc_type, desc_index, language_id)
}
#[cfg(not(target_os = "windows"))]
{
use crate::transfer::{ControlType, Recipient};
let mut buf = vec![0; 4096];
let len = self.control_in_blocking(
Control {
control_type: ControlType::Standard,
recipient: Recipient::Device,
request: STANDARD_REQUEST_GET_DESCRIPTOR,
value: ((desc_type as u16) << 8) | desc_index as u16,
index: language_id,
},
&mut buf,
timeout,
)?;
buf.truncate(len);
Ok(buf)
}
}
/// Request the list of supported languages for string descriptors.
///
/// ### Platform-specific details
///
/// See notes on [`get_descriptor`][`Self::get_descriptor`].
pub fn get_string_descriptor_supported_languages(
&self,
timeout: Duration,
) -> Result<impl Iterator<Item = u16>, Error> {
let data = self.get_descriptor(DESCRIPTOR_TYPE_STRING, 0, 0, timeout)?;
validate_string_descriptor(&data)?;
//TODO: Use array_chunks once stable
let mut iter = data.into_iter().skip(2);
Ok(std::iter::from_fn(move || {
Some(u16::from_le_bytes([iter.next()?, iter.next()?]))
}))
}
/// Request a string descriptor from the device.
///
/// Almost all devices support only the language ID [`US_ENGLISH`][`crate::descriptors::language_id::US_ENGLISH`].
///
/// Unpaired UTF-16 surrogates will be replaced with `<60>`, like [`String::from_utf16_lossy`].
///
/// ### Platform-specific details
///
/// See notes on [`get_descriptor`][`Self::get_descriptor`].
pub fn get_string_descriptor(
&self,
desc_index: u8,
language_id: u16,
timeout: Duration,
) -> Result<String, Error> {
if desc_index == 0 {
return Err(Error::new(
ErrorKind::InvalidInput,
"string descriptor index 0 is reserved for the language table",
));
}
let data = self.get_descriptor(DESCRIPTOR_TYPE_STRING, desc_index, language_id, timeout)?;
validate_string_descriptor(&data)?;
Ok(char::decode_utf16(
data[2..]
.chunks_exact(2)
.map(|c| u16::from_le_bytes(c.try_into().unwrap())),
)
.map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
.collect::<String>())
}
/// Reset the device, forcing it to re-enumerate.
///
/// This `Device` will no longer be usable, and you should drop it and call
@ -191,6 +293,17 @@ impl Device {
}
}
fn validate_string_descriptor(data: &[u8]) -> Result<(), Error> {
if data.len() < 2 || data[0] as usize != data.len() || data[1] != DESCRIPTOR_TYPE_STRING {
error!("String descriptor language list read {data:?}, not a valid string descriptor");
return Err(Error::new(
ErrorKind::InvalidData,
"string descriptor data was invalid",
));
}
Ok(())
}
/// An opened interface of a USB device.
///
/// Obtain an `Interface` with the [`Device::claim_interface`] method.

View file

@ -38,6 +38,8 @@ pub(crate) struct WindowsDevice {
config_descriptors: Vec<Vec<u8>>,
interface_paths: HashMap<u8, OsString>,
active_config: u8,
hub_handle: HubHandle,
hub_port_number: u32,
}
impl WindowsDevice {
@ -83,6 +85,8 @@ impl WindowsDevice {
interface_paths: d.interfaces.clone(),
config_descriptors,
active_config: connection_info.CurrentConfigurationValue,
hub_handle,
hub_port_number,
}))
}
@ -101,6 +105,19 @@ impl WindowsDevice {
))
}
pub(crate) fn get_descriptor(
&self,
desc_type: u8,
desc_index: u8,
language_id: u16,
) -> Result<Vec<u8>, Error> {
//TODO: this has a race condition in that the device is identified only by port number. If the device is
// disconnected and another device connects to the same port while this `Device` exists, it may return
// descriptors from the new device.
self.hub_handle
.get_descriptor(self.hub_port_number, desc_type, desc_index, language_id)
}
pub(crate) fn reset(&self) -> Result<(), Error> {
Err(io::Error::new(
ErrorKind::Unsupported,

View file

@ -1,20 +1,21 @@
use std::{
alloc::{self, Layout},
ffi::{c_void, OsStr},
io, mem,
io::ErrorKind,
mem,
os::windows::prelude::OwnedHandle,
ptr::{addr_of, null_mut},
slice,
};
use log::error;
use log::{error, warn};
use windows_sys::Win32::{
Devices::Usb::{
GUID_DEVINTERFACE_USB_HUB, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, USB_DESCRIPTOR_REQUEST,
USB_DESCRIPTOR_REQUEST_0, USB_NODE_CONNECTION_INFORMATION_EX,
},
Foundation::TRUE,
Foundation::{GetLastError, ERROR_GEN_FAILURE, TRUE},
System::IO::DeviceIoControl,
};
@ -69,7 +70,7 @@ impl HubHandle {
if r == TRUE {
Ok(info)
} else {
let err = io::Error::last_os_error();
let err = Error::last_os_error();
error!("Hub DeviceIoControl failed: {err:?}");
Err(err)
}
@ -83,7 +84,10 @@ impl HubHandle {
descriptor_index: u8,
language_id: u16,
) -> Result<Vec<u8>, Error> {
let length = 4096;
// Experimentally determined on Windows 10 19045.3803 that this fails
// with ERROR_INVALID_PARAMETER for non-cached descriptors when
// requesting length greater than 4095.
let length = 4095;
unsafe {
let layout = Layout::from_size_align(
@ -125,9 +129,15 @@ impl HubHandle {
let vec = slice::from_raw_parts(start, len).to_owned();
Ok(vec)
} else {
let err = io::Error::last_os_error();
error!("Hub get descriptor failed: {err:?}");
Err(err)
let err = GetLastError();
warn!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}");
Err(match err {
ERROR_GEN_FAILURE => Error::new(
ErrorKind::Other,
"Descriptor request failed. Device might be suspended.",
),
_ => Error::from_raw_os_error(err as i32),
})
};
alloc::dealloc(req as *mut _, layout);