Skip to content

Commit 45097fa

Browse files
committed
Add iocontrol feature
This feature only affects the Windows systems. Enabling this feature causes the `manufacturer` and `product` fields of the UsbPortInfo structure to provide the same values provided on Unix systems.
1 parent 819b640 commit 45097fa

File tree

3 files changed

+364
-12
lines changed

3 files changed

+364
-12
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ default = ["libudev"]
5151
# TODO: Make the feature unconditionally available with the next major release
5252
# (5.0) and remove this feature gate.
5353
usbportinfo-interface = []
54+
iocontrol = ["winapi/ioapiset", "winapi/usbioctl", "winapi/winnls"]

src/windows/enumerate.rs

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ use winapi::um::winreg::*;
1414

1515
use crate::{Error, ErrorKind, Result, SerialPortInfo, SerialPortType, UsbPortInfo};
1616

17+
#[cfg(feature = "iocontrol")]
18+
mod iocontrol;
19+
20+
#[cfg(feature = "iocontrol")]
21+
use iocontrol::{IoControl, IoDescriptor};
22+
1723
// According to the MSDN docs, we should use SetupDiGetClassDevs, SetupDiEnumDeviceInfo
1824
// and SetupDiGetDeviceInstanceId in order to enumerate devices.
1925
// https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/enumerating-installed-devices
@@ -94,7 +100,11 @@ fn get_ports_guids() -> Result<Vec<GUID>> {
94100
/// - BlackMagic GDB Server: USB\VID_1D50&PID_6018&MI_00\6&A694CA9&0&0000
95101
/// - BlackMagic UART port: USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0002
96102
/// - FTDI Serial Adapter: FTDIBUS\VID_0403+PID_6001+A702TB52A\0000
97-
fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> Option<UsbPortInfo> {
103+
fn parse_usb_port_info(
104+
hardware_id: &str,
105+
parent_hardware_id: Option<&str>,
106+
#[cfg(feature = "iocontrol")] device_location_info: Option<&str>,
107+
) -> Option<UsbPortInfo> {
98108
let re = Regex::new(concat!(
99109
r"VID_(?P<vid>[[:xdigit:]]{4})",
100110
r"[&+]PID_(?P<pid>[[:xdigit:]]{4})",
@@ -109,12 +119,31 @@ fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> O
109119
.name("iid")
110120
.and_then(|m| u8::from_str_radix(m.as_str(), 16).ok());
111121

112-
if let Some(_) = interface {
122+
if interface.is_some() {
113123
// If this is a composite device, we need to parse the parent's HWID to get the correct information.
114124
caps = re.captures(parent_hardware_id?)?;
115125
}
116126

117-
Some(UsbPortInfo {
127+
#[cfg(not(feature = "iocontrol"))]
128+
let usb_port_info = UsbPortInfo {
129+
vid: u16::from_str_radix(&caps[1], 16).ok()?,
130+
pid: u16::from_str_radix(&caps[2], 16).ok()?,
131+
serial_number: caps.name("serial").map(|m| {
132+
let m = m.as_str();
133+
if m.contains('&') {
134+
m.split('&').nth(1).unwrap().to_string()
135+
} else {
136+
m.to_string()
137+
}
138+
}),
139+
manufacturer: None,
140+
product: None,
141+
#[cfg(feature = "usbportinfo-interface")]
142+
interface,
143+
};
144+
145+
#[cfg(feature = "iocontrol")]
146+
let mut usb_port_info = UsbPortInfo {
118147
vid: u16::from_str_radix(&caps[1], 16).ok()?,
119148
pid: u16::from_str_radix(&caps[2], 16).ok()?,
120149
serial_number: caps.name("serial").map(|m| {
@@ -128,8 +157,39 @@ fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> O
128157
manufacturer: None,
129158
product: None,
130159
#[cfg(feature = "usbportinfo-interface")]
131-
interface: interface,
132-
})
160+
interface,
161+
};
162+
163+
#[cfg(feature = "iocontrol")]
164+
if parent_hardware_id.is_some() && device_location_info.is_some() {
165+
let re = Regex::new(concat!(r"Port_#(?P<hub_device_location>[[:xdigit:]]{4})",)).unwrap();
166+
167+
caps = re.captures(device_location_info?)?;
168+
let port_number = u8::from_str_radix(&caps[1], 8).ok()?;
169+
170+
let hub_name = format!(
171+
"{}#{{f18a0e88-c30c-11d0-8815-00a0c906bed8}}",
172+
parent_hardware_id?.replace('\\', "#"),
173+
);
174+
175+
let hdevice = IoControl::get_handle(&mut hub_name.clone()).ok()?;
176+
177+
let imanufacturer = IoControl::get_usb_string_descriptor(
178+
&hdevice,
179+
port_number,
180+
&IoDescriptor::Manufacturer,
181+
)
182+
.ok()?;
183+
184+
let iproduct =
185+
IoControl::get_usb_string_descriptor(&hdevice, port_number, &IoDescriptor::Product)
186+
.ok()?;
187+
188+
usb_port_info.manufacturer = Some(imanufacturer);
189+
usb_port_info.product = Some(iproduct);
190+
}
191+
192+
Some(usb_port_info)
133193
}
134194

135195
struct PortDevices {
@@ -320,11 +380,32 @@ impl PortDevice {
320380
// Determines the port_type for this device, and if it's a USB port populate the various fields.
321381
pub fn port_type(&mut self) -> SerialPortType {
322382
self.instance_id()
323-
.map(|s| (s, self.parent_instance_id())) // Get parent instance id if it exists.
324-
.and_then(|(d, p)| parse_usb_port_info(&d, p.as_deref()))
383+
.map(|s| {
384+
(
385+
s,
386+
self.parent_instance_id(),
387+
#[cfg(feature = "iocontrol")]
388+
self.property(SPDRP_LOCATION_INFORMATION),
389+
)
390+
}) // Get parent instance id if it exists.
391+
.and_then(
392+
|#[cfg(not(feature = "iocontrol"))] (d, p),
393+
#[cfg(feature = "iocontrol")] (d, p, l)| {
394+
parse_usb_port_info(
395+
&d,
396+
p.as_deref(),
397+
#[cfg(feature = "iocontrol")]
398+
l.as_deref(),
399+
)
400+
},
401+
)
325402
.map(|mut info: UsbPortInfo| {
326-
info.manufacturer = self.property(SPDRP_MFG);
327-
info.product = self.property(SPDRP_FRIENDLYNAME);
403+
if info.manufacturer.is_none() {
404+
info.manufacturer = self.property(SPDRP_MFG)
405+
};
406+
if info.product.is_none() {
407+
info.product = self.property(SPDRP_FRIENDLYNAME)
408+
};
328409
SerialPortType::UsbPort(info)
329410
})
330411
.unwrap_or(SerialPortType::Unknown)
@@ -399,7 +480,13 @@ pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
399480
fn test_parsing_usb_port_information() {
400481
let bm_uart_hwid = r"USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0000";
401482
let bm_parent_hwid = r"USB\VID_1D50&PID_6018\85A12F01";
402-
let info = parse_usb_port_info(bm_uart_hwid, Some(bm_parent_hwid)).unwrap();
483+
let info = parse_usb_port_info(
484+
bm_uart_hwid,
485+
Some(bm_parent_hwid),
486+
#[cfg(feature = "iocontrol")]
487+
None,
488+
)
489+
.unwrap();
403490

404491
assert_eq!(info.vid, 0x1D50);
405492
assert_eq!(info.pid, 0x6018);
@@ -408,7 +495,13 @@ fn test_parsing_usb_port_information() {
408495
assert_eq!(info.interface, Some(2));
409496

410497
let ftdi_serial_hwid = r"FTDIBUS\VID_0403+PID_6001+A702TB52A\0000";
411-
let info = parse_usb_port_info(ftdi_serial_hwid, None).unwrap();
498+
let info = parse_usb_port_info(
499+
ftdi_serial_hwid,
500+
None,
501+
#[cfg(feature = "iocontrol")]
502+
None,
503+
)
504+
.unwrap();
412505

413506
assert_eq!(info.vid, 0x0403);
414507
assert_eq!(info.pid, 0x6001);
@@ -417,7 +510,13 @@ fn test_parsing_usb_port_information() {
417510
assert_eq!(info.interface, None);
418511

419512
let pyboard_hwid = r"USB\VID_F055&PID_9802\385435603432";
420-
let info = parse_usb_port_info(pyboard_hwid, None).unwrap();
513+
let info = parse_usb_port_info(
514+
pyboard_hwid,
515+
None,
516+
#[cfg(feature = "iocontrol")]
517+
None,
518+
)
519+
.unwrap();
421520

422521
assert_eq!(info.vid, 0xF055);
423522
assert_eq!(info.pid, 0x9802);

0 commit comments

Comments
 (0)