Skip to main content

nusb/
enumeration.rs

1#[cfg(any(docsrs, target_os = "windows"))]
2use std::ffi::{OsStr, OsString};
3use std::fmt::Debug;
4
5#[cfg(target_os = "linux")]
6use crate::platform::SysfsPath;
7
8#[cfg(target_os = "windows")]
9use crate::platform::DevInst;
10
11#[cfg(all(docsrs, not(target_os = "windows")))]
12struct DevInst();
13
14use crate::{Device, Error, MaybeFuture};
15
16/// Opaque device identifier
17#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
18pub struct DeviceId(pub(crate) crate::platform::DeviceId);
19
20/// Information about a device that can be obtained without opening it.
21///
22/// `DeviceInfo` is returned by [`list_devices`][crate::list_devices].
23///
24/// ### Platform-specific notes
25///
26/// * Some fields are platform-specific
27///     * Linux: `sysfs_path`, `busnum`
28///     * Windows: `instance_id`, `parent_instance_id`, `port_number`, `driver`
29///     * macOS: `registry_id`, `location_id`
30#[derive(Clone)]
31pub struct DeviceInfo {
32    #[cfg(target_os = "linux")]
33    pub(crate) path: SysfsPath,
34
35    #[cfg(any(target_os = "linux", target_os = "android"))]
36    pub(crate) busnum: u8,
37
38    #[cfg(target_os = "windows")]
39    pub(crate) instance_id: OsString,
40
41    #[cfg(target_os = "windows")]
42    pub(crate) location_paths: Vec<OsString>,
43
44    #[cfg(target_os = "windows")]
45    pub(crate) parent_instance_id: OsString,
46
47    #[cfg(target_os = "windows")]
48    pub(crate) port_number: u32,
49
50    #[cfg(target_os = "windows")]
51    pub(crate) devinst: DevInst,
52
53    #[cfg(target_os = "windows")]
54    pub(crate) driver: Option<String>,
55
56    #[cfg(target_os = "macos")]
57    pub(crate) registry_id: u64,
58
59    #[cfg(target_os = "macos")]
60    pub(crate) location_id: u32,
61
62    #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows",))]
63    pub(crate) bus_id: String,
64
65    #[cfg(any(
66        target_os = "linux",
67        target_os = "macos",
68        target_os = "windows",
69        target_os = "android",
70    ))]
71    pub(crate) device_address: u8,
72
73    #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
74    pub(crate) port_chain: Vec<u8>,
75
76    pub(crate) vendor_id: u16,
77    pub(crate) product_id: u16,
78
79    #[cfg(any(
80        target_os = "linux",
81        target_os = "macos",
82        target_os = "windows",
83        target_arch = "wasm32"
84    ))]
85    pub(crate) device_version: u16,
86
87    pub(crate) usb_version: u16,
88    pub(crate) class: u8,
89    pub(crate) subclass: u8,
90    pub(crate) protocol: u8,
91
92    #[cfg(any(
93        target_os = "linux",
94        target_os = "macos",
95        target_os = "windows",
96        target_os = "android",
97    ))]
98    pub(crate) speed: Option<Speed>,
99
100    pub(crate) manufacturer_string: Option<String>,
101    pub(crate) product_string: Option<String>,
102    pub(crate) serial_number: Option<String>,
103
104    pub(crate) interfaces: Vec<InterfaceInfo>,
105
106    #[cfg(target_arch = "wasm32")]
107    pub(crate) device: crate::platform::UsbDevice,
108}
109
110impl DeviceInfo {
111    /// Opaque identifier for the device.
112    pub fn id(&self) -> DeviceId {
113        #[cfg(target_os = "windows")]
114        {
115            DeviceId(self.devinst)
116        }
117
118        #[cfg(any(target_os = "linux", target_os = "android"))]
119        {
120            DeviceId(crate::platform::DeviceId {
121                bus: self.busnum,
122                addr: self.device_address,
123            })
124        }
125
126        #[cfg(target_os = "macos")]
127        {
128            DeviceId(self.registry_id)
129        }
130
131        #[cfg(target_arch = "wasm32")]
132        {
133            DeviceId(crate::platform::DeviceId::from_device(&self.device))
134        }
135    }
136
137    /// *(Linux-only)* Sysfs path for the device.
138    #[cfg(any(docsrs, target_os = "linux"))]
139    pub fn sysfs_path(&self) -> &std::path::Path {
140        &self.path.0
141    }
142
143    /// *(Linux-only)* Bus number.
144    ///
145    /// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
146    #[cfg(any(docsrs, target_os = "linux"))]
147    pub fn busnum(&self) -> u8 {
148        self.busnum
149    }
150
151    /// *(Windows-only)* Instance ID path of this device
152    #[cfg(any(docsrs, target_os = "windows"))]
153    pub fn instance_id(&self) -> &OsStr {
154        &self.instance_id
155    }
156
157    /// *(Windows-only)* Location paths property
158    #[cfg(any(docsrs, target_os = "windows"))]
159    pub fn location_paths(&self) -> &[OsString] {
160        &self.location_paths
161    }
162
163    /// *(Windows-only)* Instance ID path of the parent hub
164    #[cfg(any(docsrs, target_os = "windows"))]
165    pub fn parent_instance_id(&self) -> &OsStr {
166        &self.parent_instance_id
167    }
168
169    /// *(Windows-only)* Port number
170    #[cfg(any(docsrs, target_os = "windows"))]
171    pub fn port_number(&self) -> u32 {
172        self.port_number
173    }
174
175    /// Path of port numbers identifying the port where the device is connected.
176    ///
177    /// Together with the bus ID, it identifies a physical port. The path is
178    /// expected to remain stable across device insertions or reboots.
179    ///
180    /// Since USB SuperSpeed is a separate topology from USB 2.0 speeds, a
181    /// physical port may be identified differently depending on speed.
182    #[cfg(any(
183        docsrs,
184        target_os = "linux",
185        target_os = "macos",
186        target_os = "windows"
187    ))]
188    pub fn port_chain(&self) -> &[u8] {
189        &self.port_chain
190    }
191
192    /// *(Windows-only)* Driver associated with the device as a whole
193    #[cfg(any(docsrs, target_os = "windows"))]
194    pub fn driver(&self) -> Option<&str> {
195        self.driver.as_deref()
196    }
197
198    /// *(macOS-only)* IOKit Location ID
199    #[cfg(any(docsrs, target_os = "macos"))]
200    pub fn location_id(&self) -> u32 {
201        self.location_id
202    }
203
204    /// *(macOS-only)* IOKit [Registry Entry ID](https://developer.apple.com/documentation/iokit/1514719-ioregistryentrygetregistryentryi?language=objc)
205    #[cfg(any(docsrs, target_os = "macos"))]
206    pub fn registry_entry_id(&self) -> u64 {
207        self.registry_id
208    }
209
210    /// Identifier for the bus / host controller where the device is connected.
211    #[cfg(any(
212        docsrs,
213        target_os = "linux",
214        target_os = "macos",
215        target_os = "windows"
216    ))]
217    pub fn bus_id(&self) -> &str {
218        &self.bus_id
219    }
220
221    /// Number identifying the device within the bus.
222    #[cfg(any(
223        docsrs,
224        target_os = "linux",
225        target_os = "macos",
226        target_os = "windows"
227    ))]
228    pub fn device_address(&self) -> u8 {
229        self.device_address
230    }
231
232    /// The 16-bit number identifying the device's vendor, from the `idVendor` device descriptor field.
233    #[doc(alias = "idVendor")]
234    pub fn vendor_id(&self) -> u16 {
235        self.vendor_id
236    }
237
238    /// The 16-bit number identifying the product, from the `idProduct` device descriptor field.
239    #[doc(alias = "idProduct")]
240    pub fn product_id(&self) -> u16 {
241        self.product_id
242    }
243
244    /// The device version, normally encoded as BCD, from the `bcdDevice` device descriptor field.
245    #[doc(alias = "bcdDevice")]
246    #[cfg(any(
247        docsrs,
248        target_os = "linux",
249        target_os = "macos",
250        target_os = "windows",
251        target_arch = "wasm32"
252    ))]
253    pub fn device_version(&self) -> u16 {
254        self.device_version
255    }
256
257    /// Encoded version of the USB specification, from the `bcdUSB` device descriptor field.
258    #[doc(alias = "bcdUSB")]
259    pub fn usb_version(&self) -> u16 {
260        self.usb_version
261    }
262
263    /// Code identifying the [standard device
264    /// class](https://www.usb.org/defined-class-codes), from the `bDeviceClass`
265    /// device descriptor field.
266    #[doc(alias = "bDeviceClass")]
267    pub fn class(&self) -> u8 {
268        self.class
269    }
270
271    /// Standard subclass, from the `bDeviceSubClass` device descriptor field.
272    #[doc(alias = "bDeviceSubClass")]
273    pub fn subclass(&self) -> u8 {
274        self.subclass
275    }
276
277    /// Standard protocol, from the `bDeviceProtocol` device descriptor field.
278    #[doc(alias = "bDeviceProtocol")]
279    pub fn protocol(&self) -> u8 {
280        self.protocol
281    }
282
283    /// Connection speed
284    #[cfg(any(
285        docsrs,
286        target_os = "linux",
287        target_os = "macos",
288        target_os = "windows"
289    ))]
290    pub fn speed(&self) -> Option<Speed> {
291        self.speed
292    }
293
294    /// Manufacturer string, if available without device IO.
295    ///
296    /// ### Platform-specific notes
297    ///  * Windows: Windows does not cache the manufacturer string, and
298    ///    this will return `None` regardless of whether a descriptor exists.
299    #[doc(alias = "iManufacturer")]
300    pub fn manufacturer_string(&self) -> Option<&str> {
301        self.manufacturer_string.as_deref()
302    }
303
304    /// Product string, if available without device IO.
305    #[doc(alias = "iProduct")]
306    pub fn product_string(&self) -> Option<&str> {
307        self.product_string.as_deref()
308    }
309
310    /// Serial number string, if available without device IO.
311    #[doc(alias = "iSerial")]
312    pub fn serial_number(&self) -> Option<&str> {
313        self.serial_number.as_deref()
314    }
315
316    /// Iterator over the device's interfaces.
317    ///
318    /// This returns summary information about the interfaces in the device's
319    /// active configuration for the purposes of matching devices prior to
320    /// opening them.
321    ///
322    /// Additional information about interfaces can be found in the
323    /// configuration descriptor after opening the device by calling
324    /// [`Device::active_configuration`].
325    ///
326    /// ### Platform-specific notes:
327    ///   * Windows: this is only available for composite devices bound to the
328    ///     `usbccgp` driver, and will be empty if the entire device is bound to
329    ///     a specific driver.
330    ///   * Windows: When interfaces are grouped by an interface
331    ///     association descriptor, this returns details from the interface
332    ///     association descriptor and does not include each of the associated
333    ///     interfaces.
334    pub fn interfaces(&self) -> impl Iterator<Item = &InterfaceInfo> {
335        self.interfaces.iter()
336    }
337
338    /// Check whether this device matches a [`DeviceSelector`].
339    pub fn matches(&self, s: &DeviceSelector) -> bool {
340        s.vendor_id.is_none_or(|id| self.vendor_id == id)
341            && s.product_id.is_none_or(|id| self.product_id == id)
342            && s.serial_number
343                .as_ref()
344                .is_none_or(|s| self.serial_number.as_deref() == Some(s))
345            && ((s.class.is_none_or(|c| self.class == c)
346                && s.subclass.is_none_or(|s| self.subclass == s)
347                && s.protocol.is_none_or(|p| self.protocol == p))
348                || self.interfaces().any(|i| {
349                    s.class.is_none_or(|c| i.class == c)
350                        && s.subclass.is_none_or(|s| i.subclass == s)
351                        && s.protocol.is_none_or(|p| i.protocol == p)
352                }))
353    }
354
355    /// Open the device
356    pub fn open(&self) -> impl MaybeFuture<Output = Result<Device, Error>> {
357        Device::open(self)
358    }
359}
360
361// Not derived so that we can format some fields in hex
362impl std::fmt::Debug for DeviceInfo {
363    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364        let mut s = f.debug_struct("DeviceInfo");
365
366        #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
367        s.field("bus_id", &self.bus_id)
368            .field("device_address", &self.device_address)
369            .field("port_chain", &format_args!("{:?}", self.port_chain))
370            .field("speed", &self.speed);
371
372        s.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id))
373            .field("product_id", &format_args!("0x{:04X}", self.product_id));
374
375        #[cfg(any(
376            target_os = "linux",
377            target_os = "macos",
378            target_os = "windows",
379            target_arch = "wasm32"
380        ))]
381        s.field(
382            "device_version",
383            &format_args!("0x{:04X}", self.device_version),
384        );
385
386        s.field("usb_version", &format_args!("0x{:04X}", self.usb_version))
387            .field("class", &format_args!("0x{:02X}", self.class))
388            .field("subclass", &format_args!("0x{:02X}", self.subclass))
389            .field("protocol", &format_args!("0x{:02X}", self.protocol))
390            .field("manufacturer_string", &self.manufacturer_string)
391            .field("product_string", &self.product_string)
392            .field("serial_number", &self.serial_number);
393
394        #[cfg(target_os = "linux")]
395        {
396            s.field("sysfs_path", &self.path);
397        }
398
399        #[cfg(target_os = "windows")]
400        {
401            s.field("instance_id", &self.instance_id);
402            s.field("parent_instance_id", &self.parent_instance_id);
403            s.field("location_paths", &self.location_paths);
404            s.field("port_number", &self.port_number);
405            s.field("driver", &self.driver);
406        }
407
408        #[cfg(target_os = "macos")]
409        {
410            s.field("location_id", &format_args!("0x{:08X}", self.location_id));
411            s.field(
412                "registry_entry_id",
413                &format_args!("0x{:08X}", self.registry_id),
414            );
415        }
416
417        s.field("interfaces", &self.interfaces);
418
419        s.finish()
420    }
421}
422
423/// USB connection speed
424#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
425#[non_exhaustive]
426pub enum Speed {
427    /// Low speed (1.5 Mbit)
428    Low,
429
430    /// Full speed (12 Mbit)
431    Full,
432
433    /// High speed (480 Mbit)
434    High,
435
436    /// Super speed (5000 Mbit)
437    Super,
438
439    /// Super speed (10000 Mbit)
440    SuperPlus,
441}
442
443impl Speed {
444    #[allow(dead_code)] // not used on all platforms
445    pub(crate) fn from_str(s: &str) -> Option<Self> {
446        match s {
447            "low" | "1.5" => Some(Speed::Low),
448            "full" | "12" => Some(Speed::Full),
449            "high" | "480" => Some(Speed::High),
450            "super" | "5000" => Some(Speed::Super),
451            "super+" | "10000" => Some(Speed::SuperPlus),
452            _ => None,
453        }
454    }
455}
456
457/// Summary information about a device's interface, available before opening a device.
458#[derive(Clone)]
459pub struct InterfaceInfo {
460    pub(crate) interface_number: u8,
461    pub(crate) class: u8,
462    pub(crate) subclass: u8,
463    pub(crate) protocol: u8,
464    pub(crate) interface_string: Option<String>,
465}
466
467impl InterfaceInfo {
468    /// Identifier for the interface from the `bInterfaceNumber` descriptor field.
469    pub fn interface_number(&self) -> u8 {
470        self.interface_number
471    }
472
473    /// Code identifying the standard interface class, from the `bInterfaceClass` interface descriptor field.
474    pub fn class(&self) -> u8 {
475        self.class
476    }
477
478    /// Standard subclass, from the `bInterfaceSubClass` interface descriptor field.
479    pub fn subclass(&self) -> u8 {
480        self.subclass
481    }
482
483    /// Standard protocol, from the `bInterfaceProtocol` interface descriptor field.
484    pub fn protocol(&self) -> u8 {
485        self.protocol
486    }
487
488    /// Interface string descriptor value as cached by the OS.
489    pub fn interface_string(&self) -> Option<&str> {
490        self.interface_string.as_deref()
491    }
492}
493
494// Not derived so that we can format some fields in hex
495impl std::fmt::Debug for InterfaceInfo {
496    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497        f.debug_struct("InterfaceInfo")
498            .field("interface_number", &self.interface_number)
499            .field("class", &format_args!("0x{:02X}", self.class))
500            .field("subclass", &format_args!("0x{:02X}", self.subclass))
501            .field("protocol", &format_args!("0x{:02X}", self.protocol))
502            .field("interface_string", &self.interface_string)
503            .finish()
504    }
505}
506
507/// USB host controller type
508#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
509#[non_exhaustive]
510pub enum UsbControllerType {
511    /// xHCI controller (USB 3.0+)
512    XHCI,
513
514    /// EHCI controller (USB 2.0)
515    EHCI,
516
517    /// OHCI controller (USB 1.1)
518    OHCI,
519
520    /// UHCI controller (USB 1.x) (proprietary interface created by Intel)
521    UHCI,
522
523    /// VHCI controller (virtual internal USB)
524    VHCI,
525}
526
527impl UsbControllerType {
528    #[allow(dead_code)] // not used on all platforms
529    pub(crate) fn from_str(s: &str) -> Option<Self> {
530        let lower_s = s.to_owned().to_ascii_lowercase();
531        match lower_s
532            .find("hci")
533            .filter(|i| *i > 0)
534            .and_then(|i| lower_s.as_bytes().get(i - 1))
535        {
536            Some(b'x') => Some(UsbControllerType::XHCI),
537            Some(b'e') => Some(UsbControllerType::EHCI),
538            Some(b'o') => Some(UsbControllerType::OHCI),
539            Some(b'v') => Some(UsbControllerType::VHCI),
540            Some(b'u') => Some(UsbControllerType::UHCI),
541            _ => None,
542        }
543    }
544}
545
546/// Information about a system USB bus.
547///
548/// Platform-specific fields:
549/// * Linux: `path`, `busnum`, `root_hub`
550/// * Windows: `instance_id`, `parent_instance_id`, `location_paths`, `devinst`, `root_hub_description`
551/// * macOS: `registry_id`, `location_id`, `name`, `provider_class_name`, `class_name`
552#[cfg(any(
553    docsrs,
554    target_os = "linux",
555    target_os = "macos",
556    target_os = "windows"
557))]
558pub struct BusInfo {
559    #[cfg(any(target_os = "linux"))]
560    pub(crate) path: SysfsPath,
561
562    /// The phony root hub device
563    #[cfg(any(target_os = "linux"))]
564    pub(crate) root_hub: DeviceInfo,
565
566    #[cfg(any(target_os = "linux"))]
567    pub(crate) busnum: u8,
568
569    #[cfg(target_os = "windows")]
570    pub(crate) instance_id: OsString,
571
572    #[cfg(target_os = "windows")]
573    pub(crate) location_paths: Vec<OsString>,
574
575    #[cfg(target_os = "windows")]
576    pub(crate) devinst: DevInst,
577
578    #[cfg(target_os = "windows")]
579    pub(crate) root_hub_description: String,
580
581    #[cfg(target_os = "windows")]
582    pub(crate) parent_instance_id: OsString,
583
584    #[cfg(target_os = "macos")]
585    pub(crate) registry_id: u64,
586
587    #[cfg(target_os = "macos")]
588    pub(crate) location_id: u32,
589
590    #[cfg(target_os = "macos")]
591    pub(crate) provider_class_name: String,
592
593    #[cfg(target_os = "macos")]
594    pub(crate) class_name: String,
595
596    #[cfg(target_os = "macos")]
597    pub(crate) name: Option<String>,
598
599    pub(crate) driver: Option<String>,
600
601    /// System ID for the bus
602    pub(crate) bus_id: String,
603
604    /// Detected USB controller type
605    pub(crate) controller_type: Option<UsbControllerType>,
606}
607
608#[cfg(any(
609    docsrs,
610    target_os = "linux",
611    target_os = "macos",
612    target_os = "windows"
613))]
614impl BusInfo {
615    /// *(Linux-only)* Sysfs path for the bus.
616    #[cfg(any(docsrs, target_os = "linux"))]
617    pub fn sysfs_path(&self) -> &std::path::Path {
618        &self.path.0
619    }
620
621    /// *(Linux-only)* Bus number.
622    ///
623    /// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
624    #[cfg(any(docsrs, target_os = "linux"))]
625    pub fn busnum(&self) -> u8 {
626        self.busnum
627    }
628
629    /// *(Linux-only)* The root hub [`DeviceInfo`] representing the bus.
630    #[cfg(any(docsrs, target_os = "linux"))]
631    pub fn root_hub(&self) -> &DeviceInfo {
632        &self.root_hub
633    }
634
635    /// *(Windows-only)* Instance ID path of this device
636    #[cfg(any(docsrs, target_os = "windows"))]
637    pub fn instance_id(&self) -> &OsStr {
638        &self.instance_id
639    }
640
641    /// *(Windows-only)* Instance ID path of the parent device
642    #[cfg(any(docsrs, target_os = "windows"))]
643    pub fn parent_instance_id(&self) -> &OsStr {
644        &self.parent_instance_id
645    }
646
647    /// *(Windows-only)* Location paths property
648    #[cfg(any(docsrs, target_os = "windows"))]
649    pub fn location_paths(&self) -> &[OsString] {
650        &self.location_paths
651    }
652
653    /// *(Windows-only)* Device Instance ID
654    #[cfg(any(docsrs, target_os = "windows"))]
655    pub fn devinst(&self) -> DevInst {
656        self.devinst
657    }
658
659    /// *(macOS-only)* IOKit Location ID
660    #[cfg(any(docsrs, target_os = "macos"))]
661    pub fn location_id(&self) -> u32 {
662        self.location_id
663    }
664
665    /// *(macOS-only)* IOKit [Registry Entry ID](https://developer.apple.com/documentation/iokit/1514719-ioregistryentrygetregistryentryi?language=objc)
666    #[cfg(any(docsrs, target_os = "macos"))]
667    pub fn registry_entry_id(&self) -> u64 {
668        self.registry_id
669    }
670
671    /// *(macOS-only)* IOKit provider class name
672    #[cfg(any(docsrs, target_os = "macos"))]
673    pub fn provider_class_name(&self) -> &str {
674        &self.provider_class_name
675    }
676
677    /// *(macOS-only)* IOKit class name
678    #[cfg(any(docsrs, target_os = "macos"))]
679    pub fn class_name(&self) -> &str {
680        &self.class_name
681    }
682
683    /// *(macOS-only)* Name of the bus
684    #[cfg(any(docsrs, target_os = "macos"))]
685    pub fn name(&self) -> Option<&str> {
686        self.name.as_deref()
687    }
688
689    /// Driver associated with the bus
690    pub fn driver(&self) -> Option<&str> {
691        self.driver.as_deref()
692    }
693
694    /// Identifier for the bus
695    pub fn bus_id(&self) -> &str {
696        &self.bus_id
697    }
698
699    /// Detected USB controller type
700    ///
701    /// None means the controller type could not be determined.
702    pub fn controller_type(&self) -> Option<UsbControllerType> {
703        self.controller_type
704    }
705
706    /// System name of the bus
707    ///
708    /// ### Platform-specific notes
709    ///
710    /// * Linux: The root hub product string.
711    /// * macOS: The [IONameMatched](https://developer.apple.com/documentation/bundleresources/information_property_list/ionamematch) key of the IOService entry.
712    /// * Windows: Description field of the root hub device. How the bus will appear in Device Manager.
713    pub fn system_name(&self) -> Option<&str> {
714        #[cfg(any(target_os = "linux"))]
715        {
716            self.root_hub.product_string()
717        }
718
719        #[cfg(target_os = "windows")]
720        {
721            Some(&self.root_hub_description)
722        }
723
724        #[cfg(target_os = "macos")]
725        {
726            self.name.as_deref()
727        }
728
729        #[cfg(target_arch = "wasm32")]
730        {
731            None
732        }
733    }
734}
735
736#[cfg(any(
737    docsrs,
738    target_os = "linux",
739    target_os = "macos",
740    target_os = "windows"
741))]
742impl std::fmt::Debug for BusInfo {
743    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744        let mut s = f.debug_struct("BusInfo");
745
746        #[cfg(any(target_os = "linux"))]
747        {
748            s.field("sysfs_path", &self.path);
749            s.field("busnum", &self.busnum);
750        }
751
752        #[cfg(target_os = "windows")]
753        {
754            s.field("instance_id", &self.instance_id);
755            s.field("parent_instance_id", &self.parent_instance_id);
756            s.field("location_paths", &self.location_paths);
757        }
758
759        #[cfg(target_os = "macos")]
760        {
761            s.field("location_id", &format_args!("0x{:08X}", self.location_id));
762            s.field(
763                "registry_entry_id",
764                &format_args!("0x{:08X}", self.registry_id),
765            );
766            s.field("class_name", &self.class_name);
767            s.field("provider_class_name", &self.provider_class_name);
768        }
769
770        s.field("bus_id", &self.bus_id)
771            .field("system_name", &self.system_name())
772            .field("controller_type", &self.controller_type)
773            .field("driver", &self.driver);
774
775        s.finish()
776    }
777}
778
779/// A filter for matching devices in [`request_device`][crate::request_device].
780///
781/// ## Example
782///
783/// ```no_run
784/// use nusb::{DeviceSelector, MaybeFuture};
785///
786/// let devices = nusb::request_device(&[
787///     DeviceSelector::all().with_vid_pid(0x1234, 0x5678),
788///     DeviceSelector::all().with_vid_pid(0x1111, 0x2222),
789/// ]).wait().unwrap();
790/// ```
791#[derive(Default, Clone)]
792pub struct DeviceSelector {
793    pub(crate) vendor_id: Option<u16>,
794    pub(crate) product_id: Option<u16>,
795    pub(crate) class: Option<u8>,
796    pub(crate) subclass: Option<u8>,
797    pub(crate) protocol: Option<u8>,
798    pub(crate) serial_number: Option<String>,
799}
800
801impl DeviceSelector {
802    /// A selector that matches all devices.
803    pub const fn all() -> DeviceSelector {
804        DeviceSelector {
805            vendor_id: None,
806            product_id: None,
807            class: None,
808            subclass: None,
809            protocol: None,
810            serial_number: None,
811        }
812    }
813
814    /// Narrow the selector to only match devices with the given vendor ID.
815    pub const fn with_vid(mut self, vendor_id: u16) -> DeviceSelector {
816        self.vendor_id = Some(vendor_id);
817        self
818    }
819
820    /// Narrow the selector to only match devices with the given vendor ID and product ID.
821    pub const fn with_vid_pid(mut self, vendor_id: u16, product_id: u16) -> DeviceSelector {
822        self.vendor_id = Some(vendor_id);
823        self.product_id = Some(product_id);
824        self
825    }
826
827    /// Narrow the selector to only match devices with the given class code.
828    pub const fn with_class(mut self, class_code: u8) -> DeviceSelector {
829        self.class = Some(class_code);
830        self
831    }
832
833    /// Narrow the selector to only match devices with the given class and subclass.
834    pub const fn with_class_subclass(
835        mut self,
836        class_code: u8,
837        subclass_code: u8,
838    ) -> DeviceSelector {
839        self.class = Some(class_code);
840        self.subclass = Some(subclass_code);
841        self
842    }
843
844    /// Narrow the selector to only match devices with the given class, subclass, and protocol.
845    pub const fn with_class_subclass_protocol(
846        mut self,
847        class_code: u8,
848        subclass_code: u8,
849        protocol_code: u8,
850    ) -> DeviceSelector {
851        self.class = Some(class_code);
852        self.subclass = Some(subclass_code);
853        self.protocol = Some(protocol_code);
854        self
855    }
856
857    /// Narrow the selector to only match devices with the given serial number.
858    pub fn with_serial_number(mut self, serial_number: String) -> DeviceSelector {
859        self.serial_number = Some(serial_number);
860        self
861    }
862
863    /// Vendor ID, or `None` if not filtered by vendor ID.
864    pub fn vendor_id(&self) -> Option<u16> {
865        self.vendor_id
866    }
867
868    /// Product ID, or `None` if not filtered by product ID.
869    pub fn product_id(&self) -> Option<u16> {
870        self.product_id
871    }
872
873    /// Class code, or `None` if not filtered by class.
874    pub fn class(&self) -> Option<u8> {
875        self.class
876    }
877
878    /// Subclass code, or `None` if not filtered by subclass.
879    pub fn subclass(&self) -> Option<u8> {
880        self.subclass
881    }
882
883    /// Protocol code, or `None` if not filtered by protocol.
884    pub fn protocol(&self) -> Option<u8> {
885        self.protocol
886    }
887
888    /// Serial number, or `None` if not filtered by serial number.
889    pub fn serial_number(&self) -> Option<&str> {
890        self.serial_number.as_deref()
891    }
892}
893
894impl Debug for DeviceSelector {
895    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
896        let DeviceSelector {
897            vendor_id,
898            product_id,
899            class,
900            subclass,
901            protocol,
902            ref serial_number,
903        } = *self;
904
905        let mut s = f.debug_struct("DeviceSelector");
906        if let Some(vendor_id) = vendor_id {
907            s.field("vendor_id", &format_args!("0x{vendor_id:04X}"));
908        }
909        if let Some(product_id) = product_id {
910            s.field("product_id", &format_args!("0x{product_id:04X}"));
911        }
912        if let Some(class) = class {
913            s.field("class", &format_args!("0x{class:02X}"));
914        }
915        if let Some(subclass) = subclass {
916            s.field("subclass", &format_args!("0x{subclass:02X}"));
917        }
918        if let Some(protocol) = protocol {
919            s.field("protocol", &format_args!("0x{protocol:02X}"));
920        }
921        if let Some(ref serial_number) = serial_number {
922            s.field("serial_number", serial_number);
923        }
924        s.finish()
925    }
926}