API to make a GET_DESCRIPTOR request
This commit is contained in:
parent
e153fd0fc0
commit
bf0db93029
5 changed files with 227 additions and 9 deletions
68
examples/string_descriptors.rs
Normal file
68
examples/string_descriptors.rs
Normal 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!("");
|
||||
}
|
||||
|
|
@ -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]`,
|
||||
|
|
|
|||
115
src/device.rs
115
src/device.rs
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue