use crate::Provenance;
use std::io::Cursor;
use winreg_core::hive::Hive;
const EMD_PATH: &str = "Microsoft\\Windows NT\\CurrentVersion\\EMDMgmt";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EmdVolume {
pub volume_label: String,
pub volume_serial: u32,
pub source: Provenance,
}
#[must_use]
pub fn parse_emdmgmt(hive: &Hive<Cursor<Vec<u8>>>, file: &str) -> Vec<EmdVolume> {
let Ok(Some(emd)) = hive.open_key(EMD_PATH) else {
return Vec::new();
};
let Ok(subkeys) = emd.subkeys() else {
return Vec::new(); };
let mut out = Vec::new();
for sub in subkeys {
let name = sub.name();
let Some((label, serial)) = parse_emd_name(&name) else {
continue;
};
out.push(EmdVolume {
volume_label: label,
volume_serial: serial,
source: Provenance {
file: file.to_string(),
line: 0,
key_path: Some(format!("{EMD_PATH}\\{name}")),
},
});
}
out
}
fn parse_emd_name(name: &str) -> Option<(String, u32)> {
let (rest, serial_str) = name.rsplit_once('_')?;
let serial: u32 = serial_str.parse().ok()?;
let label = match rest.rfind("__") {
Some(i) => &rest[i + 2..],
None => rest,
};
Some((label.to_string(), serial))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn parse_emd_name_splits_prefix_label_and_serial() {
assert_eq!(
parse_emd_name("JOJ___Authorized USB_1551191358"),
Some(("Authorized USB".to_string(), 1_551_191_358))
);
assert_eq!(
parse_emd_name("TO_B__IAMAN $_@_2657770370"),
Some(("IAMAN $_@".to_string(), 2_657_770_370))
);
assert_eq!(
parse_emd_name("ED_Q___3389815368"),
Some((String::new(), 3_389_815_368))
);
assert_eq!(
parse_emd_name("PFX__My__Disk_42"),
Some(("Disk".to_string(), 42))
);
assert_eq!(parse_emd_name("Label_42"), Some(("Label".to_string(), 42)));
assert_eq!(parse_emd_name("NoSerialHere"), None);
assert_eq!(parse_emd_name("Prefix__Label_notanumber"), None);
}
#[test]
fn synthetic_emdmgmt_hive_decodes_the_volume() {
const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_emdmgmt.hive");
let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
let vols = parse_emdmgmt(&hive, "SOFTWARE");
let v = vols
.iter()
.find(|v| v.volume_label == "TESTUSB")
.expect("labelled volume present");
assert_eq!(v.volume_serial, 1_234_567_890);
assert!(v.source.key_path.is_some());
}
#[test]
fn a_hive_without_emdmgmt_yields_nothing() {
const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_usb_system.hive");
let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
assert!(parse_emdmgmt(&hive, "SYSTEM").is_empty());
}
}