use crate::model::{Attribute, Claim, DeviceKey, Value};
use std::collections::{BTreeMap, BTreeSet};
#[must_use]
pub fn reconcile_volume_serials(claims: &[Claim]) -> Vec<Claim> {
let mut carriers: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for claim in claims {
let (Attribute::VolumeSerial | Attribute::DriveLetter, Value::Text(identity)) =
(claim.attribute, &claim.value)
else {
continue;
};
if claim.device.0 != *identity {
carriers
.entry(identity.clone())
.or_default()
.insert(claim.device.0.clone());
}
}
claims
.iter()
.map(|claim| {
let mut out = claim.clone();
if let Some(devices) = carriers.get(&claim.device.0) {
if let (1, Some(device)) = (devices.len(), devices.iter().next()) {
out.device = DeviceKey(device.clone());
}
}
out
})
.collect()
}
#[must_use]
pub fn canonicalize_mounted_volumes(
claims: &[Claim],
volumes: &[peripheral_core::mounted_volumes::MountedVolume],
) -> Vec<Claim> {
let mut names: BTreeMap<(u32, u64), Vec<String>> = BTreeMap::new();
let mut canonical: BTreeMap<(u32, u64), String> = BTreeMap::new();
for v in volumes {
let key = (v.disk_signature, v.partition_offset);
if let Some(letter) = v.drive_letter {
names.entry(key).or_default().push(format!("{letter}:"));
}
if let Some(guid) = &v.volume_guid {
names.entry(key).or_default().push(guid.clone());
canonical.insert(key, guid.clone());
}
names
.entry(key)
.or_default()
.push(format!("disk-{:08X}", v.disk_signature));
}
let mut remap: BTreeMap<String, String> = BTreeMap::new();
for (key, members) in &names {
if let Some(canon) = canonical.get(key) {
for member in members {
remap.insert(member.clone(), canon.clone());
}
}
}
claims
.iter()
.map(|claim| {
let mut out = claim.clone();
if let Some(canon) = remap.get(&claim.device.0) {
out.device = DeviceKey(canon.clone());
}
out
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Provenance, SourceKind};
use peripheral_core::mounted_volumes::MountedVolume;
use peripheral_core::Provenance as PcProvenance;
fn mounted(letter: Option<char>, guid: Option<&str>, sig: u32, off: u64) -> MountedVolume {
MountedVolume {
drive_letter: letter,
volume_guid: guid.map(str::to_owned),
disk_signature: sig,
partition_offset: off,
source: PcProvenance {
file: "SYSTEM".into(),
line: 0,
key_path: None,
},
}
}
fn text_claim(device: &str, attr: Attribute, val: &str, src: SourceKind) -> Claim {
Claim {
device: DeviceKey(device.into()),
attribute: attr,
value: Value::Text(val.into()),
provenance: Provenance {
source: src,
locator: "x".into(),
},
}
}
#[test]
fn drive_letter_and_volume_guid_facts_collapse_onto_the_volume_guid() {
let volumes = [
mounted(Some('E'), None, 0xAB, 0x1000),
mounted(None, Some("{vol}"), 0xAB, 0x1000),
];
let claims = vec![
text_claim(
"E:",
Attribute::VolumeName,
"IAMAN",
SourceKind::VolumeInfoCache,
),
Claim {
device: DeviceKey("{vol}".into()),
attribute: Attribute::LastConnected,
value: Value::Timestamp(1_427_230_953),
provenance: Provenance {
source: SourceKind::MountPoints2,
locator: "m".into(),
},
},
];
let out = canonicalize_mounted_volumes(&claims, &volumes);
assert!(out.iter().all(|c| c.device == DeviceKey("{vol}".into())));
}
#[test]
fn a_device_image_disk_signature_joins_the_bridged_volume() {
let volumes = [mounted(None, Some("{vol}"), 0xE221_034C, 0x1000)];
let claims = vec![
text_claim(
"disk-E221034C",
Attribute::VolumeSerial,
"B4D8-5399",
SourceKind::DeviceImage,
),
Claim {
device: DeviceKey("{vol}".into()),
attribute: Attribute::LastConnected,
value: Value::Timestamp(1),
provenance: Provenance {
source: SourceKind::MountPoints2,
locator: "m".into(),
},
},
];
let out = canonicalize_mounted_volumes(&claims, &volumes);
assert!(out.iter().all(|c| c.device == DeviceKey("{vol}".into())));
}
#[test]
fn a_drive_letter_without_a_bridged_volume_guid_is_left_untouched() {
let volumes = [mounted(Some('E'), None, 0xAB, 0x1000)];
let claims = vec![text_claim(
"E:",
Attribute::VolumeName,
"IAMAN",
SourceKind::VolumeInfoCache,
)];
let out = canonicalize_mounted_volumes(&claims, &volumes);
assert_eq!(out[0].device, DeviceKey("E:".into()));
}
fn vol_serial(device: &str, serial: &str, src: SourceKind) -> Claim {
Claim {
device: DeviceKey(device.into()),
attribute: Attribute::VolumeSerial,
value: Value::Text(serial.into()),
provenance: Provenance {
source: src,
locator: "x".into(),
},
}
}
fn accessed(device: &str, path: &str) -> Claim {
Claim {
device: DeviceKey(device.into()),
attribute: Attribute::AccessedFile,
value: Value::Text(path.into()),
provenance: Provenance {
source: SourceKind::Lnk,
locator: "y".into(),
},
}
}
fn device_of(out: &[Claim], attr: Attribute, src: SourceKind) -> &DeviceKey {
&out.iter()
.find(|c| c.attribute == attr && c.provenance.source == src)
.expect("claim present")
.device
}
#[test]
fn file_access_is_reattributed_to_the_device_carrying_the_volume() {
let claims = vec![
vol_serial("USBSTOR-DEV-1", "DEAD-BEEF", SourceKind::PartitionDiag),
vol_serial("DEAD-BEEF", "DEAD-BEEF", SourceKind::Lnk),
accessed("DEAD-BEEF", "E:\\secret.docx"),
];
let out = reconcile_volume_serials(&claims);
let dev = DeviceKey("USBSTOR-DEV-1".into());
assert_eq!(
*device_of(&out, Attribute::AccessedFile, SourceKind::Lnk),
dev
);
assert_eq!(
*device_of(&out, Attribute::VolumeSerial, SourceKind::Lnk),
dev
);
assert_eq!(
*device_of(&out, Attribute::VolumeSerial, SourceKind::PartitionDiag),
dev
);
}
#[test]
fn ambiguous_volume_serial_is_left_untouched() {
let claims = vec![
vol_serial("DEV-A", "AAAA-BBBB", SourceKind::PartitionDiag),
vol_serial("DEV-B", "AAAA-BBBB", SourceKind::Usbstor),
accessed("AAAA-BBBB", "E:\\f.txt"),
];
let out = reconcile_volume_serials(&claims);
assert_eq!(
*device_of(&out, Attribute::AccessedFile, SourceKind::Lnk),
DeviceKey("AAAA-BBBB".into())
);
}
#[test]
fn unmatched_volume_serial_is_left_untouched() {
let claims = vec![accessed("NO-MATCH", "E:\\f.txt")];
let out = reconcile_volume_serials(&claims);
assert_eq!(out[0].device, DeviceKey("NO-MATCH".into()));
}
fn drive_letter(device: &str, letter: &str, src: SourceKind) -> Claim {
Claim {
device: DeviceKey(device.into()),
attribute: Attribute::DriveLetter,
value: Value::Text(letter.into()),
provenance: Provenance {
source: src,
locator: "d".into(),
},
}
}
fn volume_name(device: &str, name: &str) -> Claim {
Claim {
device: DeviceKey(device.into()),
attribute: Attribute::VolumeName,
value: Value::Text(name.into()),
provenance: Provenance {
source: SourceKind::VolumeInfoCache,
locator: "v".into(),
},
}
}
#[test]
fn volume_label_is_reattributed_to_the_device_mounted_at_that_drive_letter() {
let claims = vec![
drive_letter("USBSTOR-DEV-1", "E:", SourceKind::Usbstor),
volume_name("E:", "Authorized USB"),
];
let out = reconcile_volume_serials(&claims);
assert_eq!(
*device_of(&out, Attribute::VolumeName, SourceKind::VolumeInfoCache),
DeviceKey("USBSTOR-DEV-1".into())
);
}
#[test]
fn ambiguous_drive_letter_label_is_left_untouched() {
let claims = vec![
drive_letter("DEV-A", "E:", SourceKind::Usbstor),
drive_letter("DEV-B", "E:", SourceKind::PartitionDiag),
volume_name("E:", "Authorized USB"),
];
let out = reconcile_volume_serials(&claims);
assert_eq!(
*device_of(&out, Attribute::VolumeName, SourceKind::VolumeInfoCache),
DeviceKey("E:".into())
);
}
#[test]
fn non_text_volume_serial_cannot_seed_the_map() {
let claims = vec![
Claim {
device: DeviceKey("DEV".into()),
attribute: Attribute::VolumeSerial,
value: Value::Timestamp(5),
provenance: Provenance {
source: SourceKind::Usbstor,
locator: "x".into(),
},
},
accessed("DEV", "E:\\f.txt"),
];
let out = reconcile_volume_serials(&claims);
assert_eq!(out[1].device, DeviceKey("DEV".into()));
}
}