#![allow(clippy::unwrap_used, clippy::expect_used)]
mod builder;
use builder::E01Builder;
use ewf_forensic::{ComputedHashes, EwfIntegrity, EwfIntegrityPath};
use std::io::Write as _;
use tempfile::NamedTempFile;
use blazehash::algorithm::{hash_bytes, Algorithm};
fn sector_data_for_single_chunk() -> Vec<u8> {
vec![0u8; 512 * 64]
}
fn write_temp(data: &[u8], suffix: &str) -> NamedTempFile {
let mut f = tempfile::Builder::new().suffix(suffix).tempfile().unwrap();
f.write_all(data).unwrap();
f.flush().unwrap();
f
}
fn hex_to_16(s: &str) -> [u8; 16] {
let b = hex::decode(s).expect("oracle hex");
b.try_into().unwrap()
}
fn hex_to_20(s: &str) -> [u8; 20] {
let b = hex::decode(s).expect("oracle hex");
b.try_into().unwrap()
}
fn hex_to_32(s: &str) -> [u8; 32] {
let b = hex::decode(s).expect("oracle hex");
b.try_into().unwrap()
}
#[test]
fn compute_hashes_returns_some_for_valid_image() {
let image = E01Builder::new(512 * 64).build();
let result = EwfIntegrity::new(&image).compute_hashes();
assert!(
result.is_some(),
"compute_hashes must return Some for a valid image"
);
}
#[test]
fn compute_hashes_md5_matches_blazehash_oracle() {
let sector_data = sector_data_for_single_chunk();
let expected_md5_hex = hash_bytes(Algorithm::Md5, §or_data);
let expected: [u8; 16] = hex_to_16(&expected_md5_hex);
let image = E01Builder::new(512 * 64).build();
let hashes = EwfIntegrity::new(&image)
.compute_hashes()
.expect("must produce hashes");
assert_eq!(
hashes.md5,
expected,
"MD5 must match blazehash oracle.\n ewf-forensic: {}\n blazehash: {}",
hex_string(&hashes.md5),
expected_md5_hex,
);
}
#[test]
fn compute_hashes_sha1_matches_blazehash_oracle() {
let sector_data = sector_data_for_single_chunk();
let expected_sha1_hex = hash_bytes(Algorithm::Sha1, §or_data);
let expected: [u8; 20] = hex_to_20(&expected_sha1_hex);
let image = E01Builder::new(512 * 64).build();
let hashes = EwfIntegrity::new(&image)
.compute_hashes()
.expect("must produce hashes");
assert_eq!(
hashes.sha1,
expected,
"SHA-1 must match blazehash oracle.\n ewf-forensic: {}\n blazehash: {}",
hex_string(&hashes.sha1),
expected_sha1_hex,
);
}
#[test]
fn compute_hashes_sha256_matches_blazehash_oracle() {
let sector_data = sector_data_for_single_chunk();
let expected_sha256_hex = hash_bytes(Algorithm::Sha256, §or_data);
let expected: [u8; 32] = hex_to_32(&expected_sha256_hex);
let image = E01Builder::new(512 * 64).build();
let hashes = EwfIntegrity::new(&image)
.compute_hashes()
.expect("must produce hashes");
assert_eq!(
hashes.sha256,
expected,
"SHA-256 must match blazehash oracle.\n ewf-forensic: {}\n blazehash: {}",
hex_string(&hashes.sha256),
expected_sha256_hex,
);
}
#[test]
fn compute_hashes_md5_consistent_with_stored_hash() {
let image = E01Builder::new(512 * 64).build();
let findings = EwfIntegrity::new(&image).analyse();
assert!(
findings
.iter()
.all(|a| !matches!(a, ewf_forensic::EwfIntegrityAnomaly::HashMismatch { .. })),
"builder must produce clean image; got: {findings:#?}"
);
let hashes = EwfIntegrity::new(&image)
.compute_hashes()
.expect("must produce hashes");
let findings2 = EwfIntegrity::new(&image)
.with_expected_md5(hashes.md5)
.analyse();
assert!(
findings2.iter().all(|a| !matches!(
a,
ewf_forensic::EwfIntegrityAnomaly::ExternalMd5Mismatch { .. }
)),
"compute_hashes().md5 must agree with verification path; got: {findings2:#?}"
);
}
#[test]
fn compute_hashes_returns_none_for_invalid_image() {
let bad = vec![0u8; 4];
let result = EwfIntegrity::new(&bad).compute_hashes();
assert!(
result.is_none(),
"compute_hashes on unparseable image should return None"
);
}
#[test]
fn ewf_integrity_path_compute_hashes_matches_ewf_integrity() {
let image = E01Builder::new(512 * 64).build();
let f = write_temp(&image, ".E01");
let via_path = EwfIntegrityPath::from_path(f.path())
.compute_hashes()
.expect("analyse must not error")
.expect("must produce hashes for valid image");
let via_slice = EwfIntegrity::new(&image)
.compute_hashes()
.expect("must produce hashes");
assert_eq!(via_path.md5, via_slice.md5, "MD5 must agree");
assert_eq!(via_path.sha1, via_slice.sha1, "SHA-1 must agree");
assert_eq!(via_path.sha256, via_slice.sha256, "SHA-256 must agree");
}
#[test]
fn computed_hashes_type_is_public() {
let sector_data = sector_data_for_single_chunk();
let expected_md5 = hex_to_16(&hash_bytes(Algorithm::Md5, §or_data));
let expected_sha1 = hex_to_20(&hash_bytes(Algorithm::Sha1, §or_data));
let expected_sha256 = hex_to_32(&hash_bytes(Algorithm::Sha256, §or_data));
let image = E01Builder::new(512 * 64).build();
let hashes: ComputedHashes = EwfIntegrity::new(&image)
.compute_hashes()
.expect("must produce hashes");
let md5: [u8; 16] = hashes.md5;
let sha1: [u8; 20] = hashes.sha1;
let sha256: [u8; 32] = hashes.sha256;
assert_eq!(md5, expected_md5);
assert_eq!(sha1, expected_sha1);
assert_eq!(sha256, expected_sha256);
}
fn hex_string(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}