peripheral_core/
emdmgmt.rs1use crate::Provenance;
12use std::io::Cursor;
13use winreg_core::hive::Hive;
14
15const EMD_PATH: &str = "Microsoft\\Windows NT\\CurrentVersion\\EMDMgmt";
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct EmdVolume {
21 pub volume_label: String,
23 pub volume_serial: u32,
25 pub source: Provenance,
27}
28
29#[must_use]
33pub fn parse_emdmgmt(hive: &Hive<Cursor<Vec<u8>>>, file: &str) -> Vec<EmdVolume> {
34 let Ok(Some(emd)) = hive.open_key(EMD_PATH) else {
35 return Vec::new();
36 };
37 let Ok(subkeys) = emd.subkeys() else {
38 return Vec::new(); };
40 let mut out = Vec::new();
41 for sub in subkeys {
42 let name = sub.name();
43 let Some((label, serial)) = parse_emd_name(&name) else {
44 continue;
45 };
46 out.push(EmdVolume {
47 volume_label: label,
48 volume_serial: serial,
49 source: Provenance {
50 file: file.to_string(),
51 line: 0,
52 key_path: Some(format!("{EMD_PATH}\\{name}")),
53 },
54 });
55 }
56 out
57}
58
59fn parse_emd_name(name: &str) -> Option<(String, u32)> {
66 let (rest, serial_str) = name.rsplit_once('_')?;
67 let serial: u32 = serial_str.parse().ok()?;
68 let label = match rest.rfind("__") {
69 Some(i) => &rest[i + 2..],
70 None => rest,
71 };
72 Some((label.to_string(), serial))
73}
74
75#[cfg(test)]
76#[allow(clippy::unwrap_used, clippy::expect_used)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn parse_emd_name_splits_prefix_label_and_serial() {
82 assert_eq!(
84 parse_emd_name("JOJ___Authorized USB_1551191358"),
85 Some(("Authorized USB".to_string(), 1_551_191_358))
86 );
87 assert_eq!(
88 parse_emd_name("TO_B__IAMAN $_@_2657770370"),
89 Some(("IAMAN $_@".to_string(), 2_657_770_370))
90 );
91 assert_eq!(
93 parse_emd_name("ED_Q___3389815368"),
94 Some((String::new(), 3_389_815_368))
95 );
96 assert_eq!(
98 parse_emd_name("PFX__My__Disk_42"),
99 Some(("Disk".to_string(), 42))
100 );
101 assert_eq!(parse_emd_name("Label_42"), Some(("Label".to_string(), 42)));
103 assert_eq!(parse_emd_name("NoSerialHere"), None);
105 assert_eq!(parse_emd_name("Prefix__Label_notanumber"), None);
106 }
107
108 #[test]
109 fn synthetic_emdmgmt_hive_decodes_the_volume() {
110 const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_emdmgmt.hive");
111 let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
112 let vols = parse_emdmgmt(&hive, "SOFTWARE");
113 let v = vols
114 .iter()
115 .find(|v| v.volume_label == "TESTUSB")
116 .expect("labelled volume present");
117 assert_eq!(v.volume_serial, 1_234_567_890);
118 assert!(v.source.key_path.is_some());
119 }
120
121 #[test]
122 fn a_hive_without_emdmgmt_yields_nothing() {
123 const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_usb_system.hive");
124 let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
125 assert!(parse_emdmgmt(&hive, "SYSTEM").is_empty());
126 }
127}