usb_enumeration 0.2.0

A cross platform library that returns details of currently connected USB devices
Documentation
use crate::common::*;
use core_foundation::{base::*, dictionary::*, number::*, string::*};
use io_kit_sys::{types::*, usb::lib::*, *};
use mach::kern_return::*;
use std::{error::Error, mem::MaybeUninit};

pub fn enumerate_platform(vid: Option<u16>, pid: Option<u16>) -> Vec<UsbDevice> {
    let mut output = Vec::new();

    unsafe {
        let matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
        if matching_dict.as_ref().is_none() {
            panic!("Failed to get IOServiceMatching");
        }

        let mut iter: io_iterator_t = 0;

        let kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matching_dict, &mut iter);
        if kr != KERN_SUCCESS {
            panic!("Failed IOServiceGetMatchingServices");
        }

        #[allow(unused_assignments)]
        let mut device: io_service_t = 0;

        #[allow(clippy::unit_cmp)]
        while (device = IOIteratorNext(iter)) == () && device > 0 {
            #[allow(clippy::uninit_assumed_init)]
            let mut props = MaybeUninit::<CFMutableDictionaryRef>::uninit();

            let _result = IORegistryEntryCreateCFProperties(
                device,
                props.as_mut_ptr(),
                kCFAllocatorDefault,
                0,
            );

            let props = props.assume_init();

            let properties: CFDictionary<CFString, CFType> =
                CFMutableDictionary::wrap_under_get_rule(props).to_immutable();

            let _ = || -> Result<(), Box<dyn Error>> {
                let key = CFString::from_static_string("idVendor");
                let vendor_id = properties
                    .find(&key)
                    .and_then(|value_ref| value_ref.downcast::<CFNumber>())
                    .ok_or(ParseError)?
                    .to_i32()
                    .ok_or(ParseError)? as u16;

                if let Some(vid) = vid {
                    if vid != vendor_id {
                        return Ok(());
                    }
                }

                let key = CFString::from_static_string("idProduct");
                let product_id = properties
                    .find(&key)
                    .and_then(|value_ref| value_ref.downcast::<CFNumber>())
                    .ok_or(ParseError)?
                    .to_i32()
                    .ok_or(ParseError)? as u16;

                if let Some(pid) = pid {
                    if pid != product_id {
                        return Ok(());
                    }
                }

                let key = CFString::from_static_string("sessionID");
                let id = properties
                    .find(&key)
                    .and_then(|value_ref| value_ref.downcast::<CFNumber>())
                    .ok_or(ParseError)?
                    .to_i64()
                    .ok_or(ParseError)?;

                let key = CFString::from_static_string("USB Product Name");
                let description = properties
                    .find(&key)
                    .and_then(|value_ref| value_ref.downcast::<CFString>())
                    .map(|s| s.to_string());

                let key = CFString::from_static_string("USB Serial Number");
                let serial_number = properties
                    .find(&key)
                    .and_then(|value_ref| value_ref.downcast::<CFString>())
                    .map(|s| s.to_string());

                output.push(UsbDevice {
                    id: id.to_string(),
                    vendor_id,
                    product_id,
                    description,
                    serial_number,
                });

                Ok(())
            }();

            IOObjectRelease(device);
        }

        IOObjectRelease(iter);
    }

    output
}