#![forbid(unsafe_code)]
pub mod emdmgmt;
pub mod linux_syslog;
pub mod mounted_volumes;
pub mod mountpoints2;
pub mod registry;
pub mod setupapi;
pub mod shellbag;
pub mod usb_ids;
pub mod volume_info;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Bus {
Usb,
Mtp,
FireWire,
Thunderbolt,
Pcie,
Esata,
SdMmc,
Bluetooth,
ExpressCard,
ScsiSas,
Nvme,
Unknown,
}
impl Bus {
#[must_use]
pub fn from_enumerator(enumerator: &str) -> Self {
let e = enumerator.trim().to_ascii_uppercase();
match e.as_str() {
"USBSTOR" | "USB" => Self::Usb,
"1394" => Self::FireWire,
"THUNDERBOLT" => Self::Thunderbolt,
"PCI" | "PCIE" => Self::Pcie,
"SCSI" | "SAS" => Self::ScsiSas,
"NVME" => Self::Nvme,
"SD" | "MMC" | "SDBUS" => Self::SdMmc,
"ESATA" => Self::Esata,
"BTHENUM" | "BTHLE" | "BLUETOOTH" => Self::Bluetooth,
"EXPRESSCARD" => Self::ExpressCard,
"WPDBUSENUMROOT" | "MTP" => Self::Mtp,
_ => Self::Unknown,
}
}
#[must_use]
pub fn is_dma_capable(self) -> bool {
matches!(
self,
Self::FireWire | Self::Thunderbolt | Self::Pcie | Self::ExpressCard
)
}
#[must_use]
pub fn is_mass_storage(self) -> bool {
matches!(
self,
Self::Usb | Self::Esata | Self::SdMmc | Self::ScsiSas | Self::Nvme
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Confidence {
Authoritative,
Inferred,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Stamp {
pub value: i64,
pub confidence: Confidence,
}
impl Stamp {
#[must_use]
pub fn authoritative(value: i64) -> Self {
Self {
value,
confidence: Confidence::Authoritative,
}
}
#[must_use]
pub fn inferred(value: i64) -> Self {
Self {
value,
confidence: Confidence::Inferred,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MitreRef(pub &'static str);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceConnection {
pub bus: Bus,
pub device_class_guid: Option<String>,
pub vid: Option<u16>,
pub pid: Option<u16>,
pub device_serial: Option<String>,
pub serial_is_os_generated: bool,
pub friendly_name: Option<String>,
pub device_instance_id: String,
pub first_install: Option<Stamp>,
pub last_install: Option<Stamp>,
pub last_arrival: Option<Stamp>,
pub last_removal: Option<Stamp>,
pub parent_id_prefix: Option<String>,
pub volume_guid: Option<String>,
pub drive_letter: Option<char>,
pub volume_serial: Option<u32>,
pub disk_signature: Option<u32>,
pub dma_capable: bool,
pub mitre: Vec<MitreRef>,
pub source: Provenance,
}
impl DeviceConnection {
#[must_use]
pub fn vendor_name<'a>(&self, db: &'a crate::usb_ids::UsbIdDb) -> Option<&'a str> {
self.vid.and_then(|v| db.vendor_name(v))
}
#[must_use]
pub fn product_name<'a>(&self, db: &'a crate::usb_ids::UsbIdDb) -> Option<&'a str> {
match (self.vid, self.pid) {
(Some(v), Some(p)) => db.product_name(v, p),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Provenance {
pub file: String,
pub line: usize,
pub key_path: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn usb_enumerators_classify_as_usb() {
assert_eq!(Bus::from_enumerator("USBSTOR"), Bus::Usb);
assert_eq!(Bus::from_enumerator("USB"), Bus::Usb);
assert_eq!(Bus::from_enumerator("usbstor"), Bus::Usb); }
#[test]
fn bus_specific_enumerators_classify() {
assert_eq!(Bus::from_enumerator("1394"), Bus::FireWire);
assert_eq!(Bus::from_enumerator("SCSI"), Bus::ScsiSas);
assert_eq!(Bus::from_enumerator("PCI"), Bus::Pcie);
assert_eq!(Bus::from_enumerator("SD"), Bus::SdMmc);
assert_eq!(Bus::from_enumerator("WpdBusEnumRoot"), Bus::Mtp);
assert_eq!(Bus::from_enumerator("THUNDERBOLT"), Bus::Thunderbolt);
assert_eq!(Bus::from_enumerator("ESATA"), Bus::Esata);
assert_eq!(Bus::from_enumerator("EXPRESSCARD"), Bus::ExpressCard);
assert_eq!(Bus::from_enumerator("BTHENUM"), Bus::Bluetooth);
assert_eq!(Bus::from_enumerator("NVME"), Bus::Nvme);
}
#[test]
fn unknown_enumerator_is_unknown_never_panics() {
assert_eq!(Bus::from_enumerator("HID"), Bus::Unknown);
assert_eq!(Bus::from_enumerator(""), Bus::Unknown);
assert_eq!(Bus::from_enumerator(" "), Bus::Unknown);
}
#[test]
fn dma_capable_is_exactly_firewire_thunderbolt_pcie_expresscard() {
for b in [Bus::FireWire, Bus::Thunderbolt, Bus::Pcie, Bus::ExpressCard] {
assert!(b.is_dma_capable(), "{b:?} must be DMA-capable");
}
for b in [Bus::Usb, Bus::Esata, Bus::SdMmc, Bus::ScsiSas, Bus::Nvme] {
assert!(!b.is_dma_capable(), "{b:?} must NOT be DMA-capable");
}
for b in [Bus::Bluetooth, Bus::Mtp, Bus::Unknown] {
assert!(!b.is_dma_capable(), "{b:?} must NOT be DMA-capable");
}
}
#[test]
fn mass_storage_classes() {
for b in [Bus::Usb, Bus::Esata, Bus::SdMmc, Bus::ScsiSas, Bus::Nvme] {
assert!(b.is_mass_storage(), "{b:?} should be mass storage");
}
for b in [Bus::FireWire, Bus::Thunderbolt, Bus::Bluetooth, Bus::Mtp] {
assert!(!b.is_mass_storage(), "{b:?} should not be mass storage");
}
}
#[test]
fn stamp_carries_confidence() {
assert_eq!(
Stamp::authoritative(10).confidence,
Confidence::Authoritative
);
assert_eq!(Stamp::inferred(10).confidence, Confidence::Inferred);
}
fn connection(vid: Option<u16>, pid: Option<u16>) -> DeviceConnection {
DeviceConnection {
bus: Bus::Usb,
device_class_guid: None,
vid,
pid,
device_serial: None,
serial_is_os_generated: false,
friendly_name: None,
device_instance_id: String::new(),
first_install: None,
last_install: None,
last_arrival: None,
last_removal: None,
parent_id_prefix: None,
volume_guid: None,
drive_letter: None,
volume_serial: None,
disk_signature: None,
dma_capable: false,
mitre: Vec::new(),
source: Provenance {
file: String::new(),
line: 0,
key_path: None,
},
}
}
const IDS: &str = "0781 SanDisk Corp.\n\t5583 Ultra Fit\n";
#[test]
fn vendor_name_resolves_a_known_vid_and_stays_none_otherwise() {
let db = crate::usb_ids::UsbIdDb::parse(IDS);
assert_eq!(
connection(Some(0x0781), None).vendor_name(&db),
Some("SanDisk Corp.")
);
assert_eq!(connection(Some(0xFFFF), None).vendor_name(&db), None);
assert_eq!(connection(None, None).vendor_name(&db), None);
}
#[test]
fn product_name_needs_both_ids() {
let db = crate::usb_ids::UsbIdDb::parse(IDS);
assert_eq!(
connection(Some(0x0781), Some(0x5583)).product_name(&db),
Some("Ultra Fit")
);
assert_eq!(connection(Some(0x0781), None).product_name(&db), None);
assert_eq!(connection(None, Some(0x5583)).product_name(&db), None);
assert_eq!(connection(None, None).product_name(&db), None);
}
}