use crate::AnomalyKind;
#[must_use]
pub fn audit(inode: &apfs_core::inode::Inode) -> Vec<AnomalyKind> {
timestamp_anomalies(
inode.oid,
inode.create_time,
inode.mod_time,
inode.change_time,
inode.access_time,
)
}
fn timestamp_anomalies(
oid: u64,
create: u64,
modify: u64,
change: u64,
access: u64,
) -> Vec<AnomalyKind> {
let mut out = Vec::new();
let ts = [create, modify, change, access];
if ts.iter().any(|&t| t != 0) && ts.contains(&0) {
out.push(AnomalyKind::TimestampZeroed { inode: oid });
}
let order_broken = (change != 0 && create != 0 && change < create)
|| (access != 0 && create != 0 && access < create);
if order_broken {
out.push(AnomalyKind::TimestampOrder { inode: oid });
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn codes(v: &[AnomalyKind]) -> Vec<&'static str> {
v.iter().map(AnomalyKind::code).collect()
}
#[test]
fn all_set_and_ordered_is_clean() {
assert!(timestamp_anomalies(7, 100, 200, 300, 400).is_empty());
}
#[test]
fn zeroed_among_siblings_is_flagged() {
let v = timestamp_anomalies(7, 0, 200, 300, 400);
assert_eq!(codes(&v), vec!["APFS-TIMESTAMP-ZEROED"]);
}
#[test]
fn all_zero_is_not_flagged() {
assert!(timestamp_anomalies(7, 0, 0, 0, 0).is_empty());
}
#[test]
fn change_before_create_is_order_lead() {
let v = timestamp_anomalies(7, 300, 300, 100, 300);
assert!(codes(&v).contains(&"APFS-TIMESTAMP-ORDER"));
}
}