1 use std::collections::HashSet;
2 use std::{mem, ptr};
3
4 use winapi::ctypes::c_void;
5 use winapi::shared::guiddef::*;
6 use winapi::shared::minwindef::*;
7 use winapi::shared::winerror::*;
8 use winapi::um::cfgmgr32::*;
9 use winapi::um::cguid::GUID_NULL;
10 use winapi::um::setupapi::*;
11 use winapi::um::winnt::{KEY_READ, REG_SZ};
12 use winapi::um::winreg::*;
13
14 use crate::{Error, ErrorKind, Result, SerialPortInfo, SerialPortType, UsbPortInfo};
15
16 /// takes normal Rust `str` and outputs a null terminated UTF-16 encoded string
as_utf16(utf8: &str) -> Vec<u16>17 fn as_utf16(utf8: &str) -> Vec<u16> {
18 utf8.encode_utf16().chain(Some(0)).collect()
19 }
20
21 /// takes a UTF-16 encoded slice (null termination not required)
22 /// and converts to a UTF8 Rust string. Trailing null chars are removed
from_utf16_lossy_trimmed(utf16: &[u16]) -> String23 fn from_utf16_lossy_trimmed(utf16: &[u16]) -> String {
24 String::from_utf16_lossy(utf16)
25 .trim_end_matches(0 as char)
26 .to_string()
27 }
28
29 /// According to the MSDN docs, we should use SetupDiGetClassDevs, SetupDiEnumDeviceInfo
30 /// and SetupDiGetDeviceInstanceId in order to enumerate devices.
31 /// https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/enumerating-installed-devices
get_ports_guids() -> Result<Vec<GUID>>32 fn get_ports_guids() -> Result<Vec<GUID>> {
33 // SetupDiGetClassDevs returns the devices associated with a particular class of devices.
34 // We want the list of devices which are listed as COM ports (generally those that show up in the
35 // Device Manager as "Ports (COM & LPT)" which is otherwise known as the "Ports" class).
36 //
37 // The list of system defined classes can be found here:
38 // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors
39 let class_names = ["Ports", "Modem"];
40 let mut guids: Vec<GUID> = Vec::new();
41 for class_name in class_names {
42 let class_name_w = as_utf16(class_name);
43 let mut num_guids: DWORD = 1; // Initially assume that there is only 1 guid per name.
44 let class_start_idx = guids.len(); // start idx for this name (for potential resize with multiple guids)
45
46 // first attempt with size == 1, second with the size returned from the first try
47 for _ in 0..2 {
48 guids.resize(class_start_idx + num_guids as usize, GUID_NULL);
49 let guid_buffer = &mut guids[class_start_idx..];
50 // Find out how many GUIDs are associated with this class name. num_guids will tell us how many there actually are.
51 let res = unsafe {
52 SetupDiClassGuidsFromNameW(
53 class_name_w.as_ptr(),
54 guid_buffer.as_mut_ptr(),
55 guid_buffer.len() as DWORD,
56 &mut num_guids,
57 )
58 };
59 if res == FALSE {
60 return Err(Error::new(
61 ErrorKind::Unknown,
62 "Unable to determine number of Ports GUIDs",
63 ));
64 }
65 let len_cmp = guid_buffer.len().cmp(&(num_guids as usize));
66 // under allocated
67 if len_cmp == std::cmp::Ordering::Less {
68 continue; // retry
69 }
70 // allocation > required len
71 else if len_cmp == std::cmp::Ordering::Greater {
72 guids.truncate(class_start_idx + num_guids as usize);
73 }
74 break; // next name
75 }
76 }
77 Ok(guids)
78 }
79
80 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
81 struct HwidMatches<'hwid> {
82 vid: &'hwid str,
83 pid: &'hwid str,
84 serial: Option<&'hwid str>,
85 interface: Option<&'hwid str>,
86 }
87
88 impl<'hwid> HwidMatches<'hwid> {
new(hwid: &'hwid str) -> Option<Self>89 fn new(hwid: &'hwid str) -> Option<Self> {
90 // When we match something, update this so that we are always looking forward
91 let mut hwid_tail = hwid;
92
93 // VID_(?P<vid>[[:xdigit:]]{4})
94 let vid_start = hwid.find("VID_")?;
95
96 // We won't match for hex characters here. That can be done when parsed.
97 let vid = hwid_tail.get(vid_start + 4..vid_start + 8)?;
98 hwid_tail = hwid_tail.get(vid_start + 8..)?;
99
100 // [&+]PID_(?P<pid>[[:xdigit:]]{4})
101 let pid = if hwid_tail.starts_with("&PID_") || hwid_tail.starts_with("+PID_") {
102 // We will let the hex parser fail if there are not hex digits.
103 hwid_tail.get(5..9)?
104 } else {
105 return None;
106 };
107 hwid_tail = hwid_tail.get(9..)?;
108
109 // (?:[&+]MI_(?P<iid>[[:xdigit:]]{2})){0,1}
110 let iid = if hwid_tail.starts_with("&MI_") || hwid_tail.starts_with("+MI_") {
111 // We will let the hex parser fail if there are not hex digits.
112 let iid = hwid_tail.get(4..6);
113 hwid_tail = hwid_tail.get(6..).unwrap_or(hwid_tail);
114
115 iid
116 } else {
117 None
118 };
119
120 // ([\\+](?P<serial>\w+))? with slightly modified check for alphanumeric characters instead
121 // of regex word character
122 //
123 // TODO: Fix returning no serial number at all for devices without one. The previous regex
124 // and the code below return the first thing from the intance ID. See issue #203.
125 let serial = if hwid_tail.starts_with('\\') || hwid_tail.starts_with('+') {
126 hwid_tail.get(1..).and_then(|tail| {
127 let index = tail
128 .char_indices()
129 .find(|&(_, char)| !char.is_alphanumeric())
130 .map(|(index, _)| index)
131 .unwrap_or(tail.len());
132 tail.get(..index)
133 })
134 } else {
135 None
136 };
137
138 Some(Self {
139 vid,
140 pid,
141 serial,
142 interface: iid,
143 })
144 }
145 }
146
147 /// Windows usb port information can be determined by the port's HWID string.
148 ///
149 /// This function parses the HWID string using regex, and returns the USB port
150 /// information if the hardware ID can be parsed correctly. The manufacturer
151 /// and product names cannot be determined from the HWID string, so those are
152 /// set as None.
153 ///
154 /// For composite USB devices, the HWID string will be for the interface. In
155 /// this case, the parent HWID string must be provided so that the correct
156 /// serial number can be determined.
157 ///
158 /// Some HWID examples are:
159 /// - MicroPython pyboard: USB\VID_F055&PID_9802\385435603432
160 /// - BlackMagic GDB Server: USB\VID_1D50&PID_6018&MI_00\6&A694CA9&0&0000
161 /// - BlackMagic UART port: USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0002
162 /// - FTDI Serial Adapter: FTDIBUS\VID_0403+PID_6001+A702TB52A\0000
parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> Option<UsbPortInfo>163 fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> Option<UsbPortInfo> {
164 let mut caps = HwidMatches::new(hardware_id)?;
165
166 let interface = caps.interface.and_then(|m| u8::from_str_radix(m, 16).ok());
167
168 if interface.is_some() {
169 // If this is a composite device, we need to parse the parent's HWID to get the correct information.
170 caps = HwidMatches::new(parent_hardware_id?)?;
171 }
172
173 Some(UsbPortInfo {
174 vid: u16::from_str_radix(caps.vid, 16).ok()?,
175 pid: u16::from_str_radix(caps.pid, 16).ok()?,
176 serial_number: caps.serial.map(str::to_string),
177 manufacturer: None,
178 product: None,
179
180 #[cfg(feature = "usbportinfo-interface")]
181 interface,
182 })
183 }
184
185 struct PortDevices {
186 /// Handle to a device information set.
187 hdi: HDEVINFO,
188
189 /// Index used by iterator.
190 dev_idx: DWORD,
191 }
192
193 impl PortDevices {
194 // Creates PortDevices object which represents the set of devices associated with a particular
195 // Ports class (given by `guid`).
new(guid: &GUID) -> Self196 pub fn new(guid: &GUID) -> Self {
197 PortDevices {
198 hdi: unsafe { SetupDiGetClassDevsW(guid, ptr::null(), ptr::null_mut(), DIGCF_PRESENT) },
199 dev_idx: 0,
200 }
201 }
202 }
203
204 impl Iterator for PortDevices {
205 type Item = PortDevice;
206
207 /// Iterator which returns a PortDevice from the set of PortDevices associated with a
208 /// particular PortDevices class (guid).
next(&mut self) -> Option<PortDevice>209 fn next(&mut self) -> Option<PortDevice> {
210 let mut port_dev = PortDevice {
211 hdi: self.hdi,
212 devinfo_data: SP_DEVINFO_DATA {
213 cbSize: mem::size_of::<SP_DEVINFO_DATA>() as DWORD,
214 ClassGuid: GUID_NULL,
215 DevInst: 0,
216 Reserved: 0,
217 },
218 };
219 let res =
220 unsafe { SetupDiEnumDeviceInfo(self.hdi, self.dev_idx, &mut port_dev.devinfo_data) };
221 if res == FALSE {
222 None
223 } else {
224 self.dev_idx += 1;
225 Some(port_dev)
226 }
227 }
228 }
229
230 impl Drop for PortDevices {
drop(&mut self)231 fn drop(&mut self) {
232 // Release the PortDevices object allocated in the constructor.
233 unsafe {
234 SetupDiDestroyDeviceInfoList(self.hdi);
235 }
236 }
237 }
238
239 struct PortDevice {
240 /// Handle to a device information set.
241 hdi: HDEVINFO,
242
243 /// Information associated with this device.
244 pub devinfo_data: SP_DEVINFO_DATA,
245 }
246
247 impl PortDevice {
248 /// Retrieves the device instance id string associated with this device's parent.
249 /// This is useful for determining the serial number of a composite USB device.
parent_instance_id(&mut self) -> Option<String>250 fn parent_instance_id(&mut self) -> Option<String> {
251 let mut result_buf = [0u16; MAX_PATH];
252 let mut parent_device_instance_id = 0;
253
254 let res =
255 unsafe { CM_Get_Parent(&mut parent_device_instance_id, self.devinfo_data.DevInst, 0) };
256 if res == CR_SUCCESS {
257 let buffer_len = result_buf.len() - 1;
258 let res = unsafe {
259 CM_Get_Device_IDW(
260 parent_device_instance_id,
261 result_buf.as_mut_ptr(),
262 buffer_len as ULONG,
263 0,
264 )
265 };
266
267 if res == CR_SUCCESS {
268 Some(from_utf16_lossy_trimmed(&result_buf))
269 } else {
270 None
271 }
272 } else {
273 None
274 }
275 }
276
277 /// Retrieves the device instance id string associated with this device. Some examples of
278 /// instance id strings are:
279 /// * MicroPython Board: USB\VID_F055&PID_9802\385435603432
280 /// * FTDI USB Adapter: FTDIBUS\VID_0403+PID_6001+A702TB52A\0000
281 /// * Black Magic Probe (Composite device with 2 UARTS):
282 /// * GDB Port: USB\VID_1D50&PID_6018&MI_00\6&A694CA9&0&0000
283 /// * UART Port: USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0002
284 ///
285 /// Reference: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/device-instance-ids
instance_id(&mut self) -> Option<String>286 fn instance_id(&mut self) -> Option<String> {
287 let mut result_buf = [0u16; MAX_DEVICE_ID_LEN];
288 let working_buffer_len = result_buf.len() - 1; // always null terminated
289 let mut desired_result_len = 0; // possibly larger than the buffer
290 let res = unsafe {
291 SetupDiGetDeviceInstanceIdW(
292 self.hdi,
293 &mut self.devinfo_data,
294 result_buf.as_mut_ptr(),
295 working_buffer_len as DWORD,
296 &mut desired_result_len,
297 )
298 };
299 if res == FALSE {
300 // Try to retrieve hardware id property.
301 self.property(SPDRP_HARDWAREID)
302 } else {
303 let actual_result_len = working_buffer_len.min(desired_result_len as usize);
304 Some(from_utf16_lossy_trimmed(&result_buf[..actual_result_len]))
305 }
306 }
307
308 /// Retrieves the problem status of this device. For example, `CM_PROB_DISABLED` indicates
309 /// the device has been disabled in Device Manager.
problem(&mut self) -> Option<ULONG>310 fn problem(&mut self) -> Option<ULONG> {
311 let mut status = 0;
312 let mut problem_number = 0;
313 let res = unsafe {
314 CM_Get_DevNode_Status(
315 &mut status,
316 &mut problem_number,
317 self.devinfo_data.DevInst,
318 0,
319 )
320 };
321 if res == CR_SUCCESS {
322 Some(problem_number)
323 } else {
324 None
325 }
326 }
327
328 // Retrieves the port name (i.e. COM6) associated with this device.
name(&mut self) -> String329 pub fn name(&mut self) -> String {
330 // https://learn.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdiopendevregkey
331 let hkey = unsafe {
332 SetupDiOpenDevRegKey(
333 self.hdi,
334 &mut self.devinfo_data,
335 DICS_FLAG_GLOBAL,
336 0,
337 DIREG_DEV,
338 KEY_READ,
339 )
340 };
341
342 if hkey as *mut c_void == winapi::um::handleapi::INVALID_HANDLE_VALUE {
343 // failed to open registry key. Return empty string as the failure case
344 return String::new();
345 }
346
347 // https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw
348 let mut port_name_buffer = [0u16; MAX_PATH];
349 let buffer_byte_len = 2 * port_name_buffer.len() as DWORD;
350 let mut byte_len = buffer_byte_len;
351 let mut value_type = 0;
352
353 let value_name = as_utf16("PortName");
354 let err = unsafe {
355 RegQueryValueExW(
356 hkey,
357 value_name.as_ptr(),
358 ptr::null_mut(),
359 &mut value_type,
360 port_name_buffer.as_mut_ptr() as *mut u8,
361 &mut byte_len,
362 )
363 };
364 unsafe { RegCloseKey(hkey) };
365 if FAILED(err) {
366 // failed to query registry for some reason. Return empty string as the failure case
367 return String::new();
368 }
369 // https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types
370 if value_type != REG_SZ || byte_len % 2 != 0 || byte_len > buffer_byte_len {
371 // read something but it wasn't the expected registry type
372 return String::new();
373 }
374 // len of u16 chars, not bytes
375 let len = buffer_byte_len as usize / 2;
376 let port_name = &port_name_buffer[0..len];
377
378 from_utf16_lossy_trimmed(port_name)
379 }
380
381 // Determines the port_type for this device, and if it's a USB port populate the various fields.
port_type(&mut self) -> SerialPortType382 pub fn port_type(&mut self) -> SerialPortType {
383 self.instance_id()
384 .map(|s| (s, self.parent_instance_id())) // Get parent instance id if it exists.
385 .and_then(|(d, p)| parse_usb_port_info(&d, p.as_deref()))
386 .map(|mut info: UsbPortInfo| {
387 info.manufacturer = self.property(SPDRP_MFG);
388 info.product = self.property(SPDRP_FRIENDLYNAME);
389 SerialPortType::UsbPort(info)
390 })
391 .unwrap_or(SerialPortType::Unknown)
392 }
393
394 // Retrieves a device property and returns it, if it exists. Returns None if the property
395 // doesn't exist.
property(&mut self, property_id: DWORD) -> Option<String>396 fn property(&mut self, property_id: DWORD) -> Option<String> {
397 let mut value_type = 0;
398 let mut property_buf = [0u16; MAX_PATH];
399
400 let res = unsafe {
401 SetupDiGetDeviceRegistryPropertyW(
402 self.hdi,
403 &mut self.devinfo_data,
404 property_id,
405 &mut value_type,
406 property_buf.as_mut_ptr() as PBYTE,
407 property_buf.len() as DWORD,
408 ptr::null_mut(),
409 )
410 };
411
412 if res == FALSE || value_type != REG_SZ {
413 return None;
414 }
415
416 // Using the unicode version of 'SetupDiGetDeviceRegistryProperty' seems to report the
417 // entire mfg registry string. This typically includes some driver information that we should discard.
418 // Example string: 'FTDI5.inf,%ftdi%;FTDI'
419 from_utf16_lossy_trimmed(&property_buf)
420 .split(';')
421 .last()
422 .map(str::to_string)
423 }
424 }
425
426 /// Not all COM ports are listed under the "Ports" device class
427 /// The full list of COM ports is available from the registry at
428 /// HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
429 ///
430 /// port of https://learn.microsoft.com/en-us/windows/win32/sysinfo/enumerating-registry-subkeys
get_registry_com_ports() -> HashSet<String>431 fn get_registry_com_ports() -> HashSet<String> {
432 let mut ports_list = HashSet::new();
433
434 let reg_key = as_utf16("HARDWARE\\DEVICEMAP\\SERIALCOMM");
435 let key_ptr = reg_key.as_ptr();
436 let mut ports_key = std::ptr::null_mut();
437
438 // SAFETY: ffi, all inputs are correct
439 let open_res =
440 unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_ptr, 0, KEY_READ, &mut ports_key) };
441 if SUCCEEDED(open_res) {
442 let mut class_name_buff = [0u16; MAX_PATH];
443 let mut class_name_size = MAX_PATH as u32;
444 let mut sub_key_count = 0;
445 let mut largest_sub_key = 0;
446 let mut largest_class_string = 0;
447 let mut num_key_values = 0;
448 let mut longest_value_name = 0;
449 let mut longest_value_data = 0;
450 let mut size_security_desc = 0;
451 let mut last_write_time = FILETIME {
452 dwLowDateTime: 0,
453 dwHighDateTime: 0,
454 };
455 // SAFETY: ffi, all inputs are correct
456 let query_res = unsafe {
457 RegQueryInfoKeyW(
458 ports_key,
459 class_name_buff.as_mut_ptr(),
460 &mut class_name_size,
461 std::ptr::null_mut(),
462 &mut sub_key_count,
463 &mut largest_sub_key,
464 &mut largest_class_string,
465 &mut num_key_values,
466 &mut longest_value_name,
467 &mut longest_value_data,
468 &mut size_security_desc,
469 &mut last_write_time,
470 )
471 };
472 if SUCCEEDED(query_res) {
473 for idx in 0..num_key_values {
474 let mut val_name_buff = [0u16; MAX_PATH];
475 let mut val_name_size = MAX_PATH as u32;
476 let mut value_type = 0;
477 let mut val_data = [0u16; MAX_PATH];
478 let buffer_byte_len = 2 * val_data.len() as DWORD; // len doubled
479 let mut byte_len = buffer_byte_len;
480
481 // SAFETY: ffi, all inputs are correct
482 let res = unsafe {
483 RegEnumValueW(
484 ports_key,
485 idx,
486 val_name_buff.as_mut_ptr(),
487 &mut val_name_size,
488 std::ptr::null_mut(),
489 &mut value_type,
490 val_data.as_mut_ptr() as *mut u8,
491 &mut byte_len,
492 )
493 };
494 if FAILED(res)
495 || value_type != REG_SZ // only valid for text values
496 || byte_len % 2 != 0 // out byte len should be a multiple of u16 size
497 || byte_len > buffer_byte_len
498 {
499 break;
500 }
501 // key data is returned as u16
502 // SAFETY: data_size is checked and pointer is valid
503 let val_data = from_utf16_lossy_trimmed(unsafe {
504 let utf16_len = byte_len / 2; // utf16 len
505 std::slice::from_raw_parts(val_data.as_ptr(), utf16_len as usize)
506 });
507 ports_list.insert(val_data);
508 }
509 }
510 // SAFETY: ffi, all inputs are correct
511 unsafe { RegCloseKey(ports_key) };
512 }
513 ports_list
514 }
515
516 /// List available serial ports on the system.
available_ports() -> Result<Vec<SerialPortInfo>>517 pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
518 let mut ports = Vec::new();
519 for guid in get_ports_guids()? {
520 let port_devices = PortDevices::new(&guid);
521 for mut port_device in port_devices {
522 // Ignore nonfunctional devices
523 if port_device.problem() != Some(0) {
524 continue;
525 }
526
527 let port_name = port_device.name();
528
529 debug_assert!(
530 port_name.as_bytes().last().map_or(true, |c| *c != b'\0'),
531 "port_name has a trailing nul: {:?}",
532 port_name
533 );
534
535 // This technique also returns parallel ports, so we filter these out.
536 if port_name.starts_with("LPT") {
537 continue;
538 }
539
540 ports.push(SerialPortInfo {
541 port_name,
542 port_type: port_device.port_type(),
543 });
544 }
545 }
546 // ports identified through the registry have no additional information
547 let mut raw_ports_set = get_registry_com_ports();
548 if raw_ports_set.len() > ports.len() {
549 // remove any duplicates. HashSet makes this relatively cheap
550 for port in ports.iter() {
551 raw_ports_set.remove(&port.port_name);
552 }
553 // add remaining ports as "unknown" type
554 for raw_port in raw_ports_set {
555 ports.push(SerialPortInfo {
556 port_name: raw_port,
557 port_type: SerialPortType::Unknown,
558 })
559 }
560 }
561 Ok(ports)
562 }
563
564 #[cfg(test)]
565 mod tests {
566 use super::*;
567
568 use quickcheck_macros::quickcheck;
569
570 #[test]
from_utf16_lossy_trimmed_trimming_empty()571 fn from_utf16_lossy_trimmed_trimming_empty() {
572 assert_eq!("", from_utf16_lossy_trimmed(&[]));
573 assert_eq!("", from_utf16_lossy_trimmed(&[0]));
574 }
575
576 #[test]
from_utf16_lossy_trimmed_trimming()577 fn from_utf16_lossy_trimmed_trimming() {
578 let test_str = "Testing";
579 let wtest_str: Vec<u16> = as_utf16(test_str);
580 let wtest_str_trailing = wtest_str
581 .iter()
582 .copied()
583 .chain([0, 0, 0, 0]) // add some null chars
584 .collect::<Vec<_>>();
585 let and_back = from_utf16_lossy_trimmed(&wtest_str_trailing);
586
587 assert_eq!(test_str, and_back);
588 }
589
590 // Check that passing some random data to HwidMatches::new() does not cause a panic.
591 #[quickcheck]
quickcheck_hwidmatches_new_does_not_panic_from_random_input(hwid: String) -> bool592 fn quickcheck_hwidmatches_new_does_not_panic_from_random_input(hwid: String) -> bool {
593 let _ = HwidMatches::new(&hwid);
594 true
595 }
596
597 // Corner cases which might not always represent what we want to/should parse. But they at
598 // least illustrate how we are parsing device identification strings today.
599 #[test]
test_hwidmatches_new_corner_cases()600 fn test_hwidmatches_new_corner_cases() {
601 assert!(HwidMatches::new("").is_none());
602 assert!(HwidMatches::new("ROOT").is_none());
603 assert!(HwidMatches::new("ROOT\\").is_none());
604 assert!(HwidMatches::new("USB\\").is_none());
605 assert!(HwidMatches::new("USB\\VID_1234").is_none());
606 assert!(HwidMatches::new("USB\\PID_1234").is_none());
607 assert!(HwidMatches::new("USB\\MI_12").is_none());
608
609 assert_eq!(
610 HwidMatches::new("VID_1234&PID_5678").unwrap(),
611 HwidMatches {
612 vid: "1234",
613 pid: "5678",
614 serial: None,
615 interface: None,
616 }
617 );
618
619 assert_eq!(
620 HwidMatches::new("ABC\\VID_1234&PID_5678&MI_90").unwrap(),
621 HwidMatches {
622 vid: "1234",
623 pid: "5678",
624 serial: None,
625 interface: Some("90"),
626 }
627 );
628
629 assert_eq!(
630 HwidMatches::new("FTDIBUS\\VID_1234&PID_5678&MI_90").unwrap(),
631 HwidMatches {
632 vid: "1234",
633 pid: "5678",
634 serial: None,
635 interface: Some("90"),
636 }
637 );
638
639 assert_eq!(
640 HwidMatches::new("USB\\VID_1234+PID_5678+MI_90").unwrap(),
641 HwidMatches {
642 vid: "1234",
643 pid: "5678",
644 serial: None,
645 interface: Some("90"),
646 }
647 );
648
649 assert_eq!(
650 HwidMatches::new("FTDIBUS\\VID_1234+PID_5678\\0000").unwrap(),
651 HwidMatches {
652 vid: "1234",
653 pid: "5678",
654 serial: Some("0000"),
655 interface: None,
656 }
657 );
658 }
659
660 #[test]
661 fn test_hwidmatches_new_standard_cases_ftdi() {
662 assert_eq!(
663 HwidMatches::new("FTDIBUS\\VID_1234+PID_5678+SERIAL123\\0000").unwrap(),
664 HwidMatches {
665 vid: "1234",
666 pid: "5678",
667 serial: Some("SERIAL123"),
668 interface: None,
669 }
670 );
671 }
672
673 #[test]
674 fn test_hwidmatches_new_standard_cases_usb() {
675 assert_eq!(
676 HwidMatches::new("USB\\VID_1234&PID_5678").unwrap(),
677 HwidMatches {
678 vid: "1234",
679 pid: "5678",
680 serial: None,
681 interface: None,
682 }
683 );
684
685 assert_eq!(
686 HwidMatches::new("USB\\VID_1234&PID_5678&MI_90").unwrap(),
687 HwidMatches {
688 vid: "1234",
689 pid: "5678",
690 serial: None,
691 interface: Some("90"),
692 }
693 );
694
695 assert_eq!(
696 HwidMatches::new("USB\\VID_1234&PID_5678\\SERIAL123").unwrap(),
697 HwidMatches {
698 vid: "1234",
699 pid: "5678",
700 serial: Some("SERIAL123"),
701 interface: None,
702 }
703 );
704
705 assert_eq!(
706 HwidMatches::new("USB\\VID_1234&PID_5678&MI_90\\SERIAL123").unwrap(),
707 HwidMatches {
708 vid: "1234",
709 pid: "5678",
710 serial: Some("SERIAL123"),
711 interface: Some("90"),
712 }
713 );
714 }
715
716 #[test]
717 fn test_parsing_usb_port_information() {
718 let madeup_hwid = r"USB\VID_1D50&PID_6018+6&A694CA9&0&0000";
719 let info = parse_usb_port_info(madeup_hwid, None).unwrap();
720 // TODO: Fix returning no serial at all for devices without one. See issue #203.
721 assert_eq!(info.serial_number, Some("6".to_string()));
722
723 let bm_uart_hwid = r"USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0000";
724 let bm_parent_hwid = r"USB\VID_1D50&PID_6018\85A12F01";
725 let info = parse_usb_port_info(bm_uart_hwid, Some(bm_parent_hwid)).unwrap();
726
727 assert_eq!(info.vid, 0x1D50);
728 assert_eq!(info.pid, 0x6018);
729 assert_eq!(info.serial_number, Some("85A12F01".to_string()));
730 #[cfg(feature = "usbportinfo-interface")]
731 assert_eq!(info.interface, Some(2));
732
733 let ftdi_serial_hwid = r"FTDIBUS\VID_0403+PID_6001+A702TB52A\0000";
734 let info = parse_usb_port_info(ftdi_serial_hwid, None).unwrap();
735
736 assert_eq!(info.vid, 0x0403);
737 assert_eq!(info.pid, 0x6001);
738 assert_eq!(info.serial_number, Some("A702TB52A".to_string()));
739 #[cfg(feature = "usbportinfo-interface")]
740 assert_eq!(info.interface, None);
741
742 let pyboard_hwid = r"USB\VID_F055&PID_9802\385435603432";
743 let info = parse_usb_port_info(pyboard_hwid, None).unwrap();
744
745 assert_eq!(info.vid, 0xF055);
746 assert_eq!(info.pid, 0x9802);
747 assert_eq!(info.serial_number, Some("385435603432".to_string()));
748 #[cfg(feature = "usbportinfo-interface")]
749 assert_eq!(info.interface, None);
750
751 let unicode_serial = r"USB\VID_F055&PID_9802\3854356β03432&test";
752 let info = parse_usb_port_info(unicode_serial, None).unwrap();
753 assert_eq!(info.serial_number.as_deref(), Some("3854356β03432"));
754
755 let unicode_serial = r"USB\VID_F055&PID_9802\3854356β03432";
756 let info = parse_usb_port_info(unicode_serial, None).unwrap();
757 assert_eq!(info.serial_number.as_deref(), Some("3854356β03432"));
758
759 let unicode_serial = r"USB\VID_F055&PID_9802\3854356β";
760 let info = parse_usb_port_info(unicode_serial, None).unwrap();
761 assert_eq!(info.serial_number.as_deref(), Some("3854356β"));
762 }
763 }
764