#![allow(clippy::unwrap_used, clippy::expect_used)]
use ewf_forensic::{EwfIntegrityPath, Severity};
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug)]
struct DiffResult {
ewfverify_exit: i32,
ewfverify_output: String,
ewf_anomalies: Vec<String>,
ewf_errors: Vec<String>,
}
impl DiffResult {
fn ewfverify_clean(&self) -> bool {
self.ewfverify_exit == 0
}
fn ewf_clean(&self) -> bool {
self.ewf_errors.is_empty()
}
fn has_anomaly_containing(&self, needle: &str) -> bool {
self.ewf_anomalies.iter().any(|a| a.contains(needle))
}
}
fn run_differential(e01_path: &Path) -> Option<DiffResult> {
let ev = match Command::new("ewfverify").arg("-q").arg(e01_path).output() {
Ok(o) => o,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return None,
Err(e) => panic!("ewfverify failed to launch: {e}"),
};
let exit = ev.status.code().unwrap_or(-1);
let output = format!(
"{}{}",
String::from_utf8_lossy(&ev.stdout),
String::from_utf8_lossy(&ev.stderr)
);
let findings = EwfIntegrityPath::from_path(e01_path)
.analyse()
.expect("ewf-forensic I/O must not fail");
let ewf_errors: Vec<String> = findings
.iter()
.filter(|a| matches!(a.severity(), Severity::High | Severity::Critical))
.map(|a| format!("{a}"))
.collect();
let ewf_anomalies: Vec<String> = findings.iter().map(|a| format!("{a}")).collect();
Some(DiffResult {
ewfverify_exit: exit,
ewfverify_output: output,
ewf_anomalies,
ewf_errors,
})
}
fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/data")
.join(name)
}
fn optional_fixture(name: &str) -> Option<PathBuf> {
let p = fixture(name);
if p.exists() {
Some(p)
} else {
None
}
}
#[test]
fn ctf_file6_both_clean() {
let Some(r) = run_differential(&fixture("ctf_file6.E01")) else {
return;
};
assert!(
r.ewfverify_clean(),
"ewfverify must report SUCCESS for ctf_file6; output={}",
r.ewfverify_output
);
assert!(
r.ewf_clean(),
"ewf-forensic must report no errors for ctf_file6; anomalies={:?}",
r.ewf_errors
);
assert!(
r.ewf_anomalies.is_empty(),
"ewf-forensic must report zero anomalies (all severities) for ctf_file6; got={:?}",
r.ewf_anomalies
);
}
#[test]
#[ignore = "60 MB — download to tests/data/2011-10-19-Sample.E01 before running"]
fn ctf_autopsy_sample_ewfverify_misses_bad_sectors() {
let path = if let Some(p) = optional_fixture("2011-10-19-Sample.E01") {
p
} else {
eprintln!(
"SKIP: tests/data/2011-10-19-Sample.E01 not found. \
Download from: https://raw.githubusercontent.com/oddin-forensic/\
autopsy-sample-case/master/2011-10-19-Sample.E01"
);
return;
};
let Some(r) = run_differential(&path) else {
return;
};
assert!(
r.ewfverify_clean(),
"ewfverify characterisation changed: expected SUCCESS; exit={}; output={}",
r.ewfverify_exit,
r.ewfverify_output
);
let has_bad_sectors = r.has_anomaly_containing("error2")
|| r.has_anomaly_containing("bad sector")
|| r.has_anomaly_containing("unreadable sector")
|| r.has_anomaly_containing("BadSectors");
assert!(
has_bad_sectors,
"ewf-forensic must surface BadSectorsPresent for this image; anomalies={:?}",
r.ewf_anomalies
);
assert!(
r.ewf_clean(),
"ewf-forensic must report no Error/Critical for this image; errors={:?}",
r.ewf_errors
);
}
#[test]
#[ignore = "88 MB — download to tests/data/CNC.E01 before running"]
fn ctf_cnc_ewfverify_false_negative_table_mismatch() {
let path = if let Some(p) = optional_fixture("CNC.E01") {
p
} else {
eprintln!(
"SKIP: tests/data/CNC.E01 not found. \
Download from: https://raw.githubusercontent.com/HaxonicOfficial/\
CTF-Practice/master/CNC.E01"
);
return;
};
let Some(r) = run_differential(&path) else {
return;
};
assert!(
r.ewfverify_clean(),
"ewfverify behaviour changed: expected SUCCESS; exit={}; output={}",
r.ewfverify_exit,
r.ewfverify_output
);
let has_table_mismatch = r.has_anomaly_containing("chunk count mismatch")
|| r.has_anomaly_containing("TableChunkCount")
|| r.has_anomaly_containing("in_volume")
|| r.has_anomaly_containing("in_table");
assert!(
has_table_mismatch,
"ewf-forensic must report TableChunkCountMismatch (volume=61440, table=16375); \
anomalies={:?}",
r.ewf_anomalies
);
let has_hash_error =
r.has_anomaly_containing("mismatch") || r.has_anomaly_containing("HashMismatch");
assert!(
has_hash_error,
"ewf-forensic must report hash mismatch for the partial image; anomalies={:?}",
r.ewf_anomalies
);
assert!(
!r.ewf_clean(),
"ewf-forensic must report Error/Critical for CNC.E01 (ewfverify false negative); \
anomalies={:?}",
r.ewf_anomalies
);
}