Skip to content

Commit b2e6a95

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 b2e6a95

File tree

3 files changed

+354
-12
lines changed

3 files changed

+354
-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: 109 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,37 @@ 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+
{
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+
183+
let iproduct =
184+
IoControl::get_usb_string_descriptor(&hdevice, port_number, &IoDescriptor::Product);
185+
186+
usb_port_info.manufacturer = imanufacturer;
187+
usb_port_info.product = iproduct;
188+
}
189+
190+
Some(usb_port_info)
133191
}
134192

135193
struct PortDevices {
@@ -320,11 +378,32 @@ impl PortDevice {
320378
// Determines the port_type for this device, and if it's a USB port populate the various fields.
321379
pub fn port_type(&mut self) -> SerialPortType {
322380
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()))
381+
.map(|s| {
382+
(
383+
s,
384+
self.parent_instance_id(),
385+
#[cfg(feature = "iocontrol")]
386+
self.property(SPDRP_LOCATION_INFORMATION),
387+
)
388+
}) // Get parent instance id if it exists.
389+
.and_then(
390+
|#[cfg(not(feature = "iocontrol"))] (d, p),
391+
#[cfg(feature = "iocontrol")] (d, p, l)| {
392+
parse_usb_port_info(
393+
&d,
394+
p.as_deref(),
395+
#[cfg(feature = "iocontrol")]
396+
l.as_deref(),
397+
)
398+
},
399+
)
325400
.map(|mut info: UsbPortInfo| {
326-
info.manufacturer = self.property(SPDRP_MFG);
327-
info.product = self.property(SPDRP_FRIENDLYNAME);
401+
if info.manufacturer.is_none() {
402+
info.manufacturer = self.property(SPDRP_MFG)
403+
};
404+
if info.product.is_none() {
405+
info.product = self.property(SPDRP_FRIENDLYNAME)
406+
};
328407
SerialPortType::UsbPort(info)
329408
})
330409
.unwrap_or(SerialPortType::Unknown)
@@ -399,7 +478,13 @@ pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
399478
fn test_parsing_usb_port_information() {
400479
let bm_uart_hwid = r"USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0000";
401480
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();
481+
let info = parse_usb_port_info(
482+
bm_uart_hwid,
483+
Some(bm_parent_hwid),
484+
#[cfg(feature = "iocontrol")]
485+
None,
486+
)
487+
.unwrap();
403488

404489
assert_eq!(info.vid, 0x1D50);
405490
assert_eq!(info.pid, 0x6018);
@@ -408,7 +493,13 @@ fn test_parsing_usb_port_information() {
408493
assert_eq!(info.interface, Some(2));
409494

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

413504
assert_eq!(info.vid, 0x0403);
414505
assert_eq!(info.pid, 0x6001);
@@ -417,7 +508,13 @@ fn test_parsing_usb_port_information() {
417508
assert_eq!(info.interface, None);
418509

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

422519
assert_eq!(info.vid, 0xF055);
423520
assert_eq!(info.pid, 0x9802);

0 commit comments

Comments
 (0)