use crate::error::{EwfError, Result};
pub const EVF_SIGNATURE: [u8; 8] = [0x45, 0x56, 0x46, 0x09, 0x0d, 0x0a, 0xff, 0x00];
pub const FILE_HEADER_SIZE: usize = 13;
pub const SECTION_DESCRIPTOR_SIZE: usize = 76;
pub const SECTION_DESCRIPTOR_CRC_OFFSET: usize = 72;
pub const VOLUME_BODY_FULL_SIZE: usize = 1052;
pub const VOLUME_BODY_CRC_OFFSET: usize = 1048;
pub const TABLE_HEADER_SIZE: usize = 24;
pub const TABLE_HEADER_CRC_OFFSET: usize = 16;
pub(crate) const DEFAULT_LRU_SIZE: usize = 100;
#[must_use]
pub fn adler32(data: &[u8]) -> u32 {
adler2::adler32_slice(data)
}
fn le_u32(data: &[u8], off: usize) -> u32 {
let mut b = [0u8; 4];
if let Some(s) = data.get(off..off + 4) {
b.copy_from_slice(s);
}
u32::from_le_bytes(b)
}
fn le_u64(data: &[u8], off: usize) -> u64 {
let mut b = [0u8; 8];
if let Some(s) = data.get(off..off + 8) {
b.copy_from_slice(s);
}
u64::from_le_bytes(b)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EwfFileHeader {
pub segment_number: u16,
}
impl EwfFileHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < FILE_HEADER_SIZE {
return Err(EwfError::BufferTooShort {
expected: FILE_HEADER_SIZE,
got: buf.len(),
});
}
if buf[0..8] != EVF_SIGNATURE {
return Err(EwfError::InvalidSignature);
}
let segment_number = u16::from_le_bytes([buf[9], buf[10]]);
Ok(Self { segment_number })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SectionDescriptor {
pub section_type: String,
pub next: u64,
pub section_size: u64,
pub offset: u64,
pub stored_crc: u32,
}
impl SectionDescriptor {
pub fn parse(buf: &[u8], offset: u64) -> Result<Self> {
if buf.len() < SECTION_DESCRIPTOR_SIZE {
return Err(EwfError::BufferTooShort {
expected: SECTION_DESCRIPTOR_SIZE,
got: buf.len(),
});
}
let type_end = buf[..16].iter().position(|&b| b == 0).unwrap_or(16);
let section_type = String::from_utf8_lossy(&buf[..type_end]).into_owned();
let next = u64::from_le_bytes(buf[16..24].try_into().unwrap());
let section_size = u64::from_le_bytes(buf[24..32].try_into().unwrap());
let stored_crc = le_u32(buf, SECTION_DESCRIPTOR_CRC_OFFSET);
Ok(Self {
section_type,
next,
section_size,
offset,
stored_crc,
})
}
#[must_use]
pub fn crc_covers() -> std::ops::Range<usize> {
0..SECTION_DESCRIPTOR_CRC_OFFSET
}
#[must_use]
pub fn verify_crc(&self, raw: &[u8]) -> bool {
raw.get(Self::crc_covers())
.is_some_and(|covered| adler32(covered) == self.stored_crc)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EwfVolume {
pub chunk_count: u32,
pub sectors_per_chunk: u32,
pub bytes_per_sector: u32,
pub sector_count: u64,
pub media_type: u8,
pub set_identifier: [u8; 16],
pub stored_crc: Option<u32>,
}
impl EwfVolume {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 24 {
return Err(EwfError::BufferTooShort {
expected: 24,
got: buf.len(),
});
}
let media_type = buf[0];
let chunk_count = u32::from_le_bytes(buf[4..8].try_into().unwrap());
let sectors_per_chunk = u32::from_le_bytes(buf[8..12].try_into().unwrap());
let bytes_per_sector = u32::from_le_bytes(buf[12..16].try_into().unwrap());
let sector_count = u64::from_le_bytes(buf[16..24].try_into().unwrap());
let mut set_identifier = [0u8; 16];
if let Some(s) = buf.get(64..80) {
set_identifier.copy_from_slice(s);
}
let stored_crc = if buf.len() >= VOLUME_BODY_FULL_SIZE {
Some(le_u32(buf, VOLUME_BODY_CRC_OFFSET))
} else {
None
};
Ok(Self {
chunk_count,
sectors_per_chunk,
bytes_per_sector,
sector_count,
media_type,
set_identifier,
stored_crc,
})
}
#[must_use]
pub fn chunk_size(&self) -> u64 {
u64::from(self.sectors_per_chunk) * u64::from(self.bytes_per_sector)
}
#[must_use]
pub fn total_size(&self) -> u64 {
u64::from(self.bytes_per_sector) * self.sector_count
}
#[must_use]
pub fn crc_covers() -> std::ops::Range<usize> {
0..VOLUME_BODY_CRC_OFFSET
}
#[must_use]
pub fn verify_crc(&self, raw: &[u8]) -> Option<bool> {
let stored = self.stored_crc?;
Some(
raw.get(Self::crc_covers())
.is_some_and(|covered| adler32(covered) == stored),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TableEntry {
pub compressed: bool,
pub chunk_offset: u32,
}
impl TableEntry {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 4 {
return Err(EwfError::BufferTooShort {
expected: 4,
got: buf.len(),
});
}
let raw = u32::from_le_bytes(buf[..4].try_into().unwrap());
let compressed = (raw >> 31) != 0;
let chunk_offset = raw & 0x7FFF_FFFF;
Ok(Self {
compressed,
chunk_offset,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TableHeader {
pub entry_count: u32,
pub base_offset: u64,
pub stored_crc: u32,
}
impl TableHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < TABLE_HEADER_CRC_OFFSET {
return Err(EwfError::BufferTooShort {
expected: TABLE_HEADER_CRC_OFFSET,
got: buf.len(),
});
}
Ok(Self {
entry_count: le_u32(buf, 0),
base_offset: le_u64(buf, 8),
stored_crc: le_u32(buf, TABLE_HEADER_CRC_OFFSET),
})
}
#[must_use]
pub fn crc_covers() -> std::ops::Range<usize> {
0..TABLE_HEADER_CRC_OFFSET
}
#[must_use]
pub fn verify_crc(&self, raw: &[u8]) -> Option<bool> {
if self.stored_crc == 0 {
return None;
}
Some(
raw.get(Self::crc_covers())
.is_some_and(|covered| adler32(covered) == self.stored_crc),
)
}
}
#[derive(Debug, Clone)]
pub(crate) struct Chunk {
offset_packed: u64,
segment_idx: u32,
size: u32,
}
impl Chunk {
const COMPRESSED_BIT: u64 = 1 << 63;
pub(crate) fn new(segment_idx: usize, compressed: bool, offset: u64, size: u64) -> Self {
debug_assert!(
offset < Self::COMPRESSED_BIT,
"EWF file offset must fit 63 bits"
);
let flag = if compressed { Self::COMPRESSED_BIT } else { 0 };
Self {
offset_packed: (offset & !Self::COMPRESSED_BIT) | flag,
segment_idx: segment_idx as u32,
size: size as u32,
}
}
pub(crate) fn segment_idx(&self) -> usize {
self.segment_idx as usize
}
pub(crate) fn compressed(&self) -> bool {
self.offset_packed & Self::COMPRESSED_BIT != 0
}
pub(crate) fn offset(&self) -> u64 {
self.offset_packed & !Self::COMPRESSED_BIT
}
pub(crate) fn size(&self) -> u64 {
u64::from(self.size)
}
pub(crate) fn set_size(&mut self, size: u64) {
self.size = size as u32;
}
}
#[cfg(test)]
mod crc_tests {
use super::*;
#[test]
fn adler32_matches_published_vectors() {
assert_eq!(adler32(b""), 0x0000_0001);
assert_eq!(adler32(b"abc"), 0x024D_0127);
assert_eq!(adler32(b"Wikipedia"), 0x11E6_0398);
}
fn descriptor(type_name: &[u8]) -> [u8; SECTION_DESCRIPTOR_SIZE] {
let mut d = [0u8; SECTION_DESCRIPTOR_SIZE];
d[..type_name.len()].copy_from_slice(type_name);
d[16..24].copy_from_slice(&1000u64.to_le_bytes()); d[24..32].copy_from_slice(&170u64.to_le_bytes()); let crc = adler32(&d[..SECTION_DESCRIPTOR_CRC_OFFSET]);
d[SECTION_DESCRIPTOR_CRC_OFFSET..].copy_from_slice(&crc.to_le_bytes());
d
}
#[test]
fn descriptor_verify_crc_accepts_correct_and_rejects_tampered() {
let d = descriptor(b"volume");
let desc = SectionDescriptor::parse(&d, 13).unwrap();
assert_eq!(SectionDescriptor::crc_covers(), 0..72);
assert!(desc.verify_crc(&d), "correct CRC must verify");
let mut tampered = d;
tampered[24] ^= 0xFF;
assert!(!desc.verify_crc(&tampered), "tampered body must fail");
}
#[test]
fn descriptor_verify_crc_false_when_raw_too_short() {
let d = descriptor(b"done");
let desc = SectionDescriptor::parse(&d, 0).unwrap();
assert!(!desc.verify_crc(&d[..50]));
}
#[test]
fn volume_crc_present_only_for_full_body() {
let short = [0u8; 94];
let vol = EwfVolume::parse(&short).unwrap();
assert_eq!(vol.stored_crc, None);
assert_eq!(vol.verify_crc(&short), None);
let mut full = vec![0u8; VOLUME_BODY_FULL_SIZE];
full[0] = 0x01; full[4..8].copy_from_slice(&5u32.to_le_bytes()); let crc = adler32(&full[..VOLUME_BODY_CRC_OFFSET]);
full[VOLUME_BODY_CRC_OFFSET..].copy_from_slice(&crc.to_le_bytes());
let vol = EwfVolume::parse(&full).unwrap();
assert_eq!(vol.media_type, 0x01);
assert_eq!(vol.chunk_count, 5);
assert_eq!(vol.stored_crc, Some(crc));
assert_eq!(EwfVolume::crc_covers(), 0..1048);
assert_eq!(vol.verify_crc(&full), Some(true));
full[4] ^= 0xFF; assert_eq!(vol.verify_crc(&full), Some(false));
}
#[test]
fn table_header_crc_none_when_stored_zero() {
let mut hdr = [0u8; TABLE_HEADER_SIZE];
hdr[0..4].copy_from_slice(&3u32.to_le_bytes());
hdr[8..16].copy_from_slice(&200u64.to_le_bytes());
let th = TableHeader::parse(&hdr).unwrap();
assert_eq!(th.entry_count, 3);
assert_eq!(th.base_offset, 200);
assert_eq!(th.stored_crc, 0);
assert_eq!(th.verify_crc(&hdr), None);
}
#[test]
fn table_header_crc_verifies_when_stored() {
let mut hdr = [0u8; TABLE_HEADER_SIZE];
hdr[0..4].copy_from_slice(&7u32.to_le_bytes());
hdr[8..16].copy_from_slice(&4096u64.to_le_bytes());
let crc = adler32(&hdr[..TABLE_HEADER_CRC_OFFSET]);
hdr[TABLE_HEADER_CRC_OFFSET..TABLE_HEADER_CRC_OFFSET + 4]
.copy_from_slice(&crc.to_le_bytes());
let th = TableHeader::parse(&hdr).unwrap();
assert_eq!(EwfVolume::crc_covers().start, 0);
assert_eq!(TableHeader::crc_covers(), 0..16);
assert_eq!(th.verify_crc(&hdr), Some(true));
hdr[0] ^= 0xFF;
assert_eq!(th.verify_crc(&hdr), Some(false));
}
}