use md5::{Digest, Md5};
use std::io::{Read, Seek, SeekFrom};
const DATA_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data");
fn full_media_md5(reader: &mut ewf::EwfReader) -> String {
let mut hasher = Md5::new();
let mut buf = vec![0u8; 1024 * 1024]; reader.seek(SeekFrom::Start(0)).unwrap();
loop {
let n = reader.read(&mut buf).unwrap();
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
format!("{:x}", hasher.finalize())
}
#[test]
fn exfat1_media_size() {
let path = format!("{DATA_DIR}/exfat1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
assert_eq!(reader.total_size(), 100_020_736);
}
#[test]
fn exfat1_full_media_md5() {
let path = format!("{DATA_DIR}/exfat1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
assert_eq!(
full_media_md5(&mut reader),
"0777ee90c27ed5ff5868af2015bed635",
"Full-media MD5 mismatch vs libewf/Sleuth Kit"
);
}
#[test]
fn exfat1_seek_and_read_consistency() {
let path = format!("{DATA_DIR}/exfat1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let mut sequential = [0u8; 512];
reader.seek(SeekFrom::Start(0)).unwrap();
reader.read_exact(&mut sequential).unwrap();
let mut seeked = [0u8; 512];
reader.seek(SeekFrom::Start(0)).unwrap();
reader.read_exact(&mut seeked).unwrap();
assert_eq!(sequential, seeked);
let mut at_boundary = [0u8; 512];
reader.seek(SeekFrom::Start(32768)).unwrap();
reader.read_exact(&mut at_boundary).unwrap();
let mut at_boundary2 = [0u8; 512];
reader.seek(SeekFrom::Start(32768)).unwrap();
reader.read_exact(&mut at_boundary2).unwrap();
assert_eq!(at_boundary, at_boundary2);
}
#[test]
fn mmls1_media_size() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
assert_eq!(reader.total_size(), 62_915_072);
}
#[test]
fn mmls1_full_media_md5() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
assert_eq!(
full_media_md5(&mut reader),
"8ec671e301095c258224aad701740503",
"Full-media MD5 mismatch vs libewf/Sleuth Kit"
);
}
#[test]
fn mmls1_mbr_signature() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let mut mbr = [0u8; 512];
reader.read_exact(&mut mbr).unwrap();
assert_eq!(mbr[510], 0x55);
assert_eq!(mbr[511], 0xAA);
}
#[test]
fn mmls1_seek_from_end() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
reader.seek(SeekFrom::End(-512)).unwrap();
let mut last_sector = [0u8; 512];
reader.read_exact(&mut last_sector).unwrap();
let pos = reader.stream_position().unwrap();
assert_eq!(pos, 62_915_072);
}
#[test]
fn emails_media_size() {
let path = format!("{DATA_DIR}/nps-2010-emails.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
assert_eq!(reader.total_size(), 10_485_760);
}
#[test]
fn emails_full_media_md5() {
let path = format!("{DATA_DIR}/nps-2010-emails.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
assert_eq!(
full_media_md5(&mut reader),
"7dae50cec8163697415e69fd72387c01",
"Full-media MD5 mismatch vs libewf/Sleuth Kit"
);
}
#[test]
fn emails_sequential_equals_random_access() {
let path = format!("{DATA_DIR}/nps-2010-emails.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let mut sequential = vec![0u8; 32768 * 4];
reader.seek(SeekFrom::Start(0)).unwrap();
reader.read_exact(&mut sequential).unwrap();
let mut random_access = vec![0u8; 32768 * 4];
for i in (0..4).rev() {
let offset = i * 32768;
reader.seek(SeekFrom::Start(offset as u64)).unwrap();
reader
.read_exact(&mut random_access[offset..offset + 32768])
.unwrap();
}
assert_eq!(sequential, random_access);
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
#[test]
fn exfat1_stored_md5_matches_media_hash() {
let path = format!("{DATA_DIR}/exfat1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let hashes = reader.stored_hashes();
let md5 = hashes.md5.expect("EnCase 6 image should contain stored MD5");
assert_eq!(
hex(&md5),
"0777ee90c27ed5ff5868af2015bed635",
"Stored MD5 should match full-media MD5"
);
}
#[test]
fn emails_stored_md5_matches_media_hash() {
let path = format!("{DATA_DIR}/nps-2010-emails.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let hashes = reader.stored_hashes();
let md5 = hashes.md5.expect("EnCase 6 image should contain stored MD5");
assert_eq!(
hex(&md5),
"7dae50cec8163697415e69fd72387c01",
"Stored MD5 should match full-media MD5"
);
}
#[test]
fn stored_hashes_returns_none_when_absent() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let _hashes = reader.stored_hashes(); }
#[test]
fn exfat1_verify_passes() {
let path = format!("{DATA_DIR}/exfat1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let result = reader.verify().unwrap();
assert!(result.md5_match.unwrap(), "MD5 verification should pass for intact image");
}
#[test]
fn emails_verify_passes() {
let path = format!("{DATA_DIR}/nps-2010-emails.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let result = reader.verify().unwrap();
assert!(result.md5_match.unwrap(), "MD5 verification should pass for intact image");
}
#[test]
fn verify_returns_none_when_no_stored_hashes() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let result = reader.verify().unwrap();
if reader.stored_hashes().md5.is_none() {
assert!(result.md5_match.is_none(), "No stored MD5 means md5_match should be None");
}
}
#[test]
fn verify_computed_hashes_match_manual_stream() {
let path = format!("{DATA_DIR}/exfat1.E01");
let mut reader = ewf::EwfReader::open(&path).unwrap();
let result = reader.verify().unwrap();
assert_eq!(
hex(&result.computed_md5),
"0777ee90c27ed5ff5868af2015bed635",
"verify() computed MD5 should match independent full-media hash"
);
}
#[test]
fn mmls1_metadata_has_case_info() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let meta = reader.metadata();
assert_eq!(meta.case_number.as_deref(), Some("1"));
assert_eq!(meta.evidence_number.as_deref(), Some("1"));
assert_eq!(meta.description.as_deref(), Some("Test E01 for sleuthkit"));
assert_eq!(meta.examiner.as_deref(), Some("Rishwanth"));
assert_eq!(meta.notes.as_deref(), Some("Used to test sleuthkit libraries"));
}
#[test]
fn mmls1_metadata_has_tool_info() {
let path = format!("{DATA_DIR}/imageformat_mmls_1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let meta = reader.metadata();
assert_eq!(meta.acquiry_software.as_deref(), Some("ADI2.9.0.13"));
assert_eq!(meta.os_version.as_deref(), Some("Windows 200x"));
}
#[test]
fn exfat1_metadata_has_os_and_dates() {
let path = format!("{DATA_DIR}/exfat1.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let meta = reader.metadata();
assert_eq!(meta.os_version.as_deref(), Some("Darwin"));
assert!(meta.acquiry_date.is_some(), "Should have acquisition date");
assert!(meta.system_date.is_some(), "Should have system date");
}
#[test]
fn emails_metadata_parses_without_panic() {
let path = format!("{DATA_DIR}/nps-2010-emails.E01");
let reader = ewf::EwfReader::open(&path).unwrap();
let meta = reader.metadata();
assert_eq!(meta.os_version.as_deref(), Some("Darwin"));
}
#[test]
fn clean_image_has_no_acquisition_errors() {
for name in ["exfat1.E01", "imageformat_mmls_1.E01", "nps-2010-emails.E01"] {
let path = format!("{DATA_DIR}/{name}");
let reader = ewf::EwfReader::open(&path).unwrap();
assert!(
reader.acquisition_errors().is_empty(),
"{name} should have no acquisition errors"
);
}
}
#[test]
fn parse_error2_data_extracts_entries() {
let mut data = Vec::new();
data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&[0u8; 4]); data.extend_from_slice(&100u32.to_le_bytes()); data.extend_from_slice(&5u32.to_le_bytes()); data.extend_from_slice(&5000u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&[0u8; 4]);
let errors = ewf::parse_error2_data(&data);
assert_eq!(errors.len(), 2);
assert_eq!(errors[0].first_sector, 100);
assert_eq!(errors[0].sector_count, 5);
assert_eq!(errors[1].first_sector, 5000);
assert_eq!(errors[1].sector_count, 1);
}
#[test]
fn parse_error2_data_handles_empty() {
let mut data = Vec::new();
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&[0u8; 4]);
data.extend_from_slice(&[0u8; 4]);
let errors = ewf::parse_error2_data(&data);
assert!(errors.is_empty());
}
#[test]
fn l01_opens_when_extension_is_l01() {
let src = format!("{DATA_DIR}/nps-2010-emails.E01");
let tmp = tempfile::tempdir().unwrap();
let l01_path = tmp.path().join("evidence.L01");
std::fs::copy(&src, &l01_path).unwrap();
let result = ewf::EwfReader::open(&l01_path);
assert!(result.is_ok(), "EwfReader::open should succeed for .L01 files, got: {:?}", result.err());
assert_eq!(result.unwrap().total_size(), 10_485_760);
}
#[test]
fn l01_opens_lowercase_extension() {
let src = format!("{DATA_DIR}/nps-2010-emails.E01");
let tmp = tempfile::tempdir().unwrap();
let l01_path = tmp.path().join("evidence.l01");
std::fs::copy(&src, &l01_path).unwrap();
let result = ewf::EwfReader::open(&l01_path);
assert!(result.is_ok(), "EwfReader::open should succeed for .l01 files, got: {:?}", result.err());
assert_eq!(result.unwrap().total_size(), 10_485_760);
}
#[test]
fn l01_full_media_md5_matches() {
let src = format!("{DATA_DIR}/nps-2010-emails.E01");
let tmp = tempfile::tempdir().unwrap();
let l01_path = tmp.path().join("evidence.L01");
std::fs::copy(&src, &l01_path).unwrap();
let result = ewf::EwfReader::open(&l01_path);
assert!(result.is_ok(), "L01 should open");
let mut reader = result.unwrap();
assert_eq!(
full_media_md5(&mut reader),
"7dae50cec8163697415e69fd72387c01",
"L01 full-media MD5 should match source E01"
);
}