use ados_protocol::hwcaps::{Probed, UsbId};
#[cfg(target_os = "linux")]
const USB_DEVICES_DIR: &str = "/sys/bus/usb/devices";
#[cfg(target_os = "linux")]
pub fn probe_usb_ids() -> Probed<Vec<UsbId>> {
use ados_protocol::hwcaps::{AbsenceReason, Evidence};
let Ok(rd) = std::fs::read_dir(USB_DEVICES_DIR) else {
return Probed::absent(AbsenceReason::NodeMissing);
};
let mut ids = Vec::new();
for entry in rd.flatten() {
let p = entry.path();
let vid = std::fs::read_to_string(p.join("idVendor")).ok();
let pid = std::fs::read_to_string(p.join("idProduct")).ok();
if let (Some(vid), Some(pid)) = (vid, pid) {
if let (Some(vid), Some(pid)) = (parse_hex16(&vid), parse_hex16(&pid)) {
ids.push(UsbId { vid, pid });
}
}
}
Probed::present(ids, Evidence::SysfsPath(USB_DEVICES_DIR.to_string()))
}
#[cfg(not(target_os = "linux"))]
pub fn probe_usb_ids() -> Probed<Vec<UsbId>> {
Probed::NotProbed
}
#[allow(dead_code)]
fn parse_hex16(s: &str) -> Option<u16> {
u16::from_str_radix(s.trim(), 16).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_typical_sysfs_ids() {
assert_eq!(parse_hex16("0bda"), Some(0x0BDA));
assert_eq!(parse_hex16("a81a"), Some(0xA81A));
assert_eq!(parse_hex16("8812"), Some(0x8812));
}
#[test]
fn tolerates_trailing_newline_and_whitespace() {
assert_eq!(parse_hex16("0bda\n"), Some(0x0BDA));
assert_eq!(parse_hex16(" 1d6b "), Some(0x1D6B));
assert_eq!(parse_hex16("\t0002\n"), Some(0x0002));
}
#[test]
fn parses_full_range_bounds() {
assert_eq!(parse_hex16("0000"), Some(0));
assert_eq!(parse_hex16("ffff"), Some(0xFFFF));
assert_eq!(parse_hex16("FFFF"), Some(0xFFFF));
}
#[test]
fn rejects_non_hex_and_overflow() {
assert_eq!(parse_hex16(""), None);
assert_eq!(parse_hex16("zzzz"), None);
assert_eq!(parse_hex16("0x0bda"), None);
assert_eq!(parse_hex16("10000"), None);
}
}