use crate::AnomalyKind;
#[must_use]
pub fn audit<R: std::io::Read + std::io::Seek>(
container: &apfs_core::ApfsContainer<R>,
) -> Vec<AnomalyKind> {
let live_xid = container.superblock().xid;
let mappings: Vec<(u64, u64)> = container
.checkpoint_mappings()
.iter()
.map(|m| (m.oid, m.paddr))
.collect();
duplicate_oid_anomalies(&mappings, live_xid)
}
fn duplicate_oid_anomalies(mappings: &[(u64, u64)], live_xid: u64) -> Vec<AnomalyKind> {
let mut out = Vec::new();
for i in 0..mappings.len() {
let (oid, paddr) = mappings[i];
let already = mappings[..i].iter().any(|&(o, _)| o == oid);
let collides = mappings[i + 1..]
.iter()
.any(|&(o, p)| o == oid && p != paddr);
if !already && collides {
out.push(AnomalyKind::XidReuse { oid, xid: live_xid });
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unique_oids_are_clean() {
let maps = [(1u64, 10u64), (2, 11), (3, 12)];
assert!(duplicate_oid_anomalies(&maps, 5).is_empty());
}
#[test]
fn same_oid_two_blocks_is_xid_reuse() {
let maps = [(1u64, 10u64), (2, 11), (1, 99)];
let v = duplicate_oid_anomalies(&maps, 7);
assert_eq!(v.len(), 1);
assert_eq!(v[0].code(), "APFS-XID-REUSE");
}
#[test]
fn same_oid_same_block_is_not_flagged() {
let maps = [(1u64, 10u64), (1, 10)];
assert!(duplicate_oid_anomalies(&maps, 7).is_empty());
}
}