use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
#[cfg(target_os = "macos")]
use crate::sources::helpers::{extract_timing_entropy, mach_time};
static USB_ENUMERATION_INFO: SourceInfo = SourceInfo {
name: "usb_enumeration",
description: "IOKit USB device enumeration timing — CV=116%, USB controller state",
physics: "Times IOKit USB device enumeration (IOServiceMatching(kIOUSBDeviceClassName) + \
device tree walk). USB enumeration latency varies with: USB xHCI controller \
port state, USB bus traffic from active devices, IOKit registry lock contention \
from hot-plug events, controller power state wake-up latency. \
Measured: mean=13046 ticks (~544 µs), CV=116.2%, range=[9791,169338]. \
Cross-process sensitivity: any process using USB devices or hot-plugging \
changes enumeration timing.",
category: SourceCategory::IO,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 1.5,
composite: false,
is_fast: false,
};
pub struct USBEnumerationSource;
#[cfg(target_os = "macos")]
mod usb_imp {
use std::ffi::c_void;
pub type IOReturn = i32;
pub type MachPort = u32;
#[link(name = "IOKit", kind = "framework")]
unsafe extern "C" {
pub fn IOServiceMatching(name: *const i8) -> *mut c_void;
pub fn IOServiceGetMatchingServices(
main_port: MachPort,
matching: *const c_void,
iter: *mut u32,
) -> IOReturn;
pub fn IOIteratorNext(iterator: u32) -> u32;
pub fn IOObjectRelease(obj: u32) -> IOReturn;
}
pub const K_IO_MAIN_PORT_DEFAULT: MachPort = 0;
}
#[cfg(target_os = "macos")]
impl EntropySource for USBEnumerationSource {
fn info(&self) -> &SourceInfo {
&USB_ENUMERATION_INFO
}
fn is_available(&self) -> bool {
true
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
use usb_imp::*;
let raw = n_samples * 2 + 32;
let mut timings = Vec::with_capacity(raw);
for _ in 0..4 {
let matching = unsafe { IOServiceMatching(c"IOUSBDevice".as_ptr()) };
if !matching.is_null() {
let mut iter: u32 = 0;
unsafe {
IOServiceGetMatchingServices(K_IO_MAIN_PORT_DEFAULT, matching, &mut iter);
if iter != 0 {
let mut obj = IOIteratorNext(iter);
while obj != 0 {
IOObjectRelease(obj);
obj = IOIteratorNext(iter);
}
IOObjectRelease(iter);
}
}
}
}
for _ in 0..raw {
let matching = unsafe { IOServiceMatching(c"IOUSBDevice".as_ptr()) };
if matching.is_null() {
continue;
}
let t0 = mach_time();
let mut iter: u32 = 0;
let kr = unsafe {
IOServiceGetMatchingServices(K_IO_MAIN_PORT_DEFAULT, matching, &mut iter)
};
if kr == 0 && iter != 0 {
let mut count = 0;
let mut obj = unsafe { IOIteratorNext(iter) };
while obj != 0 && count < 50 {
unsafe { IOObjectRelease(obj) };
obj = unsafe { IOIteratorNext(iter) };
count += 1;
}
unsafe { IOObjectRelease(iter) };
}
let elapsed = mach_time().wrapping_sub(t0);
if elapsed < 2_400_000 {
timings.push(elapsed);
}
}
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(not(target_os = "macos"))]
impl EntropySource for USBEnumerationSource {
fn info(&self) -> &SourceInfo {
&USB_ENUMERATION_INFO
}
fn is_available(&self) -> bool {
false
}
fn collect(&self, _: usize) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = USBEnumerationSource;
assert_eq!(src.info().name, "usb_enumeration");
assert!(matches!(src.info().category, SourceCategory::IO));
assert_eq!(src.info().platform, Platform::MacOS);
}
#[test]
#[cfg(target_os = "macos")]
fn is_available() {
assert!(USBEnumerationSource.is_available());
}
#[test]
#[ignore]
fn collects_usb_controller_state() {
let data = USBEnumerationSource.collect(32);
assert!(!data.is_empty());
}
}