use disk_forensic::container::{open, ContainerFormat, OpenError};
use disk_forensic::logical;
fn build_ad1() -> (
tempfile::TempDir,
std::path::PathBuf,
Vec<ad1::testfix::Expected>,
) {
let built = ad1::testfix::build(ad1::testfix::sample_tree());
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("image.ad1");
std::fs::write(&path, &built.bytes).unwrap();
(dir, path, built.expected)
}
#[test]
fn container_open_refuses_ad1_as_logical_container() {
let (_dir, path, _expected) = build_ad1();
let err = open(&path).unwrap_err();
assert!(
matches!(err, OpenError::LogicalContainer(ContainerFormat::Ad1)),
"got {err:?}"
);
assert!(
err.to_string().contains("logical::open"),
"message should point the caller at logical::open: {err}"
);
}
#[test]
fn logical_open_lists_and_reads_ad1_files() {
let (_dir, path, expected) = build_ad1();
let mut img = logical::open(&path).unwrap();
assert_eq!(img.format(), ContainerFormat::Ad1);
for ex in &expected {
let got = img
.entries()
.iter()
.find(|e| e.path == ex.path)
.unwrap_or_else(|| panic!("entry {:?} missing from the AD1 tree", ex.path));
assert_eq!(got.is_dir, ex.is_dir, "is_dir for {:?}", ex.path);
assert_eq!(got.size, ex.size, "size for {:?}", ex.path);
}
let file = expected
.iter()
.find(|e| !e.is_dir && e.data.as_ref().is_some_and(|d| !d.is_empty()))
.expect("sample tree has at least one non-empty file");
let idx = img
.entries()
.iter()
.position(|e| e.path == file.path)
.unwrap();
let bytes = img.read_file(idx).unwrap();
assert_eq!(
&bytes,
file.data.as_ref().unwrap(),
"content of {:?}",
file.path
);
}
#[test]
fn logical_open_rejects_a_directory_read() {
let (_dir, path, expected) = build_ad1();
let mut img = logical::open(&path).unwrap();
let dir_path = expected
.iter()
.find(|e| e.is_dir)
.expect("sample tree has a directory")
.path
.clone();
let idx = img
.entries()
.iter()
.position(|e| e.path == dir_path)
.unwrap();
let err = img.read_file(idx).unwrap_err();
assert!(
matches!(err, logical::LogicalError::IsDirectory(_)),
"got {err:?}"
);
}
#[test]
fn logical_open_rejects_a_raw_disk_image() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("raw.dd");
let mut disk = vec![0u8; 512];
disk[510] = 0x55;
disk[511] = 0xAA;
std::fs::write(&path, &disk).unwrap();
let err = logical::open(&path).unwrap_err();
assert!(
matches!(
err,
logical::LogicalError::NotLogical(ContainerFormat::Raw, _)
),
"got {err:?}"
);
}
#[test]
fn truncated_ad1_errors_without_panic() {
let built = ad1::testfix::build(ad1::testfix::sample_tree());
let truncated = &built.bytes[..40];
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("truncated.ad1");
std::fs::write(&path, truncated).unwrap();
let err = logical::open(&path).unwrap_err();
assert!(
matches!(
err,
logical::LogicalError::Ad1(_) | logical::LogicalError::Io(_)
),
"got {err:?}"
);
}
#[test]
fn ad1_file_with_truncated_data_reads_without_panic() {
let built = ad1::testfix::build(ad1::testfix::sample_tree());
let truncated = &built.bytes[..built.bytes.len() / 2];
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("data_truncated.ad1");
std::fs::write(&path, truncated).unwrap();
let mut img = logical::open(&path).expect("tree is intact, open succeeds");
let idx = img
.entries()
.iter()
.position(|e| e.path.ends_with("a.bin"))
.expect("a.bin listed");
let declared = img.entries()[idx].size;
match img.read_file(idx) {
Ok(bytes) => assert!(
(bytes.len() as u64) < declared,
"a truncated file must not yield its full declared length"
),
Err(logical::LogicalError::Ad1(_) | logical::LogicalError::Io(_)) => {}
Err(other) => panic!("unexpected error kind: {other:?}"),
}
}
#[test]
fn logical_open_reads_a_logical_aff4() {
let content = b"aff4 logical file body";
let bytes = aff4::testutil::test_aff4_logical("notes.txt", content, &"0".repeat(32));
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("logical.aff4");
std::fs::write(&path, &bytes).unwrap();
let mut img = logical::open(&path).unwrap();
assert_eq!(img.format(), ContainerFormat::Aff4);
let idx = img
.entries()
.iter()
.position(|e| e.path.ends_with("notes.txt"))
.expect("the logical file should be listed");
assert_eq!(img.read_file(idx).unwrap(), content);
}
#[test]
fn logical_open_rejects_a_physical_aff4() {
let bytes = aff4::testutil::test_aff4(b"physical payload");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("physical.aff4");
std::fs::write(&path, &bytes).unwrap();
let err = logical::open(&path).unwrap_err();
assert!(
matches!(
err,
logical::LogicalError::NotLogical(ContainerFormat::Aff4, _)
),
"got {err:?}"
);
}