use crate::{EwfError, Result};
pub(crate) const EVF_SIGNATURE: [u8; 8] = [0x45, 0x56, 0x46, 0x09, 0x0d, 0x0a, 0xff, 0x00];
pub(crate) const LVF_SIGNATURE: [u8; 8] = [0x4c, 0x56, 0x46, 0x09, 0x0d, 0x0a, 0xff, 0x00];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FileHeader {
pub(crate) segment_number: u16,
pub(crate) logical: bool,
}
impl FileHeader {
pub(crate) fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < FILE_HEADER_SIZE {
return Err(EwfError::BufferTooShort {
needed: FILE_HEADER_SIZE,
actual: buf.len(),
});
}
let logical = if buf[0..8] == EVF_SIGNATURE {
false
} else if buf[0..8] == LVF_SIGNATURE {
true
} else {
return Err(EwfError::InvalidSignature);
};
Ok(Self {
segment_number: u16::from_le_bytes([buf[9], buf[10]]),
logical,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SectionDescriptor {
pub(crate) section_type: String,
pub(crate) next: u64,
pub(crate) size: u64,
pub(crate) offset: u64,
}
impl SectionDescriptor {
pub(crate) fn parse(buf: &[u8], offset: u64) -> Result<Self> {
if buf.len() < SECTION_DESCRIPTOR_SIZE {
return Err(EwfError::BufferTooShort {
needed: SECTION_DESCRIPTOR_SIZE,
actual: 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().expect("slice length checked"));
let size = u64::from_le_bytes(buf[24..32].try_into().expect("slice length checked"));
Ok(Self {
section_type,
next,
size,
offset,
})
}
pub(crate) fn data_size(&self) -> Result<u64> {
if self.size == 0 && matches!(self.section_type.as_str(), "done" | "next") {
return Ok(0);
}
self.size
.checked_sub(SECTION_DESCRIPTOR_SIZE as u64)
.ok_or_else(|| EwfError::Malformed("section is smaller than descriptor".into()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Volume {
pub(crate) chunk_count: u32,
pub(crate) sectors_per_chunk: u32,
pub(crate) bytes_per_sector: u32,
pub(crate) sector_count: u64,
pub(crate) set_identifier: Option<[u8; 16]>,
pub(crate) media_type: Option<u8>,
pub(crate) media_flags: Option<u8>,
pub(crate) compression_level: Option<u8>,
pub(crate) error_granularity: Option<u32>,
pub(crate) smart: bool,
}
impl Volume {
pub(crate) fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 24 {
return Err(EwfError::BufferTooShort {
needed: 24,
actual: buf.len(),
});
}
let smart_volume = buf.len() == SMART_VOLUME_SIZE;
let sector_count = if smart_volume {
u64::from(u32::from_le_bytes(
buf[16..20].try_into().expect("slice length checked"),
))
} else {
u64::from_le_bytes(buf[16..24].try_into().expect("slice length checked"))
};
let set_identifier = if smart_volume {
None
} else {
buf.get(64..80).and_then(|value| {
let mut identifier = [0; 16];
identifier.copy_from_slice(value);
(identifier != [0; 16]).then_some(identifier)
})
};
let has_full_volume_profile = !smart_volume && buf.len() >= VOLUME_DATA_SIZE;
let error_granularity = has_full_volume_profile
.then(|| u32::from_le_bytes(buf[56..60].try_into().expect("slice length checked")))
.filter(|value| *value != 0);
Ok(Self {
chunk_count: u32::from_le_bytes(buf[4..8].try_into().expect("slice length checked")),
sectors_per_chunk: u32::from_le_bytes(
buf[8..12].try_into().expect("slice length checked"),
),
bytes_per_sector: u32::from_le_bytes(
buf[12..16].try_into().expect("slice length checked"),
),
sector_count,
set_identifier,
media_type: has_full_volume_profile.then_some(buf[0]),
media_flags: has_full_volume_profile.then_some(buf[36]),
compression_level: buf.get(52).copied(),
error_granularity,
smart: smart_volume,
})
}
pub(crate) fn chunk_size(&self) -> Result<u64> {
u64::from(self.sectors_per_chunk)
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| EwfError::Malformed("EWF1 chunk size overflow".into()))
}
pub(crate) fn logical_size(&self) -> Result<u64> {
self.sector_count
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| EwfError::Malformed("EWF1 logical size overflow".into()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct TableEntry {
pub(crate) compressed: bool,
pub(crate) offset: u64,
pub(crate) raw: u32,
}
impl TableEntry {
pub(crate) fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 4 {
return Err(EwfError::BufferTooShort {
needed: 4,
actual: buf.len(),
});
}
let raw = u32::from_le_bytes(buf[..4].try_into().expect("slice length checked"));
Ok(Self {
compressed: raw & 0x8000_0000 != 0,
offset: u64::from(raw & 0x7fff_ffff),
raw,
})
}
}
pub(crate) const FILE_HEADER_SIZE: usize = 13;
pub(crate) const SECTION_DESCRIPTOR_SIZE: usize = 76;
const VOLUME_DATA_SIZE: usize = 1052;
const SMART_VOLUME_SIZE: usize = 94;
#[cfg(test)]
mod tests {
use super::*;
fn file_header(signature: [u8; 8], segment_number: u16) -> [u8; FILE_HEADER_SIZE] {
let mut buf = [0; FILE_HEADER_SIZE];
buf[0..8].copy_from_slice(&signature);
buf[8] = 1;
buf[9..11].copy_from_slice(&segment_number.to_le_bytes());
buf
}
#[test]
fn parses_evf_file_header() {
let header = FileHeader::parse(&file_header(EVF_SIGNATURE, 42)).unwrap();
assert_eq!(header.segment_number, 42);
assert!(!header.logical);
}
#[test]
fn parses_lvf_file_header_as_logical() {
let header = FileHeader::parse(&file_header(LVF_SIGNATURE, 7)).unwrap();
assert_eq!(header.segment_number, 7);
assert!(header.logical);
}
#[test]
fn rejects_invalid_file_header_signature() {
let err = FileHeader::parse(&[0; FILE_HEADER_SIZE]).unwrap_err();
assert!(matches!(err, EwfError::InvalidSignature));
}
#[test]
fn rejects_short_file_header() {
let err = FileHeader::parse(&[0; 4]).unwrap_err();
assert!(matches!(
err,
EwfError::BufferTooShort {
needed: FILE_HEADER_SIZE,
actual: 4
}
));
}
#[test]
fn parses_section_descriptor() {
let mut buf = [0; SECTION_DESCRIPTOR_SIZE];
buf[0..6].copy_from_slice(b"volume");
buf[16..24].copy_from_slice(&1234_u64.to_le_bytes());
buf[24..32].copy_from_slice(&170_u64.to_le_bytes());
let desc = SectionDescriptor::parse(&buf, FILE_HEADER_SIZE as u64).unwrap();
assert_eq!(desc.section_type, "volume");
assert_eq!(desc.next, 1234);
assert_eq!(desc.size, 170);
assert_eq!(desc.offset, FILE_HEADER_SIZE as u64);
assert_eq!(desc.data_size().unwrap(), 94);
}
#[test]
fn zero_sized_terminal_section_has_no_data() {
let mut buf = [0; SECTION_DESCRIPTOR_SIZE];
buf[0..4].copy_from_slice(b"done");
let desc = SectionDescriptor::parse(&buf, FILE_HEADER_SIZE as u64).unwrap();
assert_eq!(desc.data_size().unwrap(), 0);
}
#[test]
fn parses_volume_geometry() {
let mut buf = [0; 94];
buf[4..8].copy_from_slice(&1000_u32.to_le_bytes());
buf[8..12].copy_from_slice(&64_u32.to_le_bytes());
buf[12..16].copy_from_slice(&512_u32.to_le_bytes());
buf[16..24].copy_from_slice(&64_000_u64.to_le_bytes());
let volume = Volume::parse(&buf).unwrap();
assert_eq!(volume.chunk_count, 1000);
assert_eq!(volume.sectors_per_chunk, 64);
assert_eq!(volume.bytes_per_sector, 512);
assert_eq!(volume.sector_count, 64_000);
assert_eq!(volume.set_identifier, None);
assert_eq!(volume.chunk_size().unwrap(), 32_768);
assert_eq!(volume.logical_size().unwrap(), 32_768_000);
}
#[test]
fn parses_smart_volume_sector_count_as_u32() {
let mut buf = [0; 94];
buf[4..8].copy_from_slice(&1000_u32.to_le_bytes());
buf[8..12].copy_from_slice(&64_u32.to_le_bytes());
buf[12..16].copy_from_slice(&512_u32.to_le_bytes());
buf[16..20].copy_from_slice(&64_000_u32.to_le_bytes());
buf[20..24].copy_from_slice(&0xfeed_beef_u32.to_le_bytes());
let volume = Volume::parse(&buf).unwrap();
assert_eq!(volume.chunk_count, 1000);
assert_eq!(volume.sectors_per_chunk, 64);
assert_eq!(volume.bytes_per_sector, 512);
assert_eq!(volume.sector_count, 64_000);
assert_eq!(volume.logical_size().unwrap(), 32_768_000);
}
#[test]
fn parses_volume_set_identifier() {
let mut buf = [0; 1052];
buf[4..8].copy_from_slice(&1_u32.to_le_bytes());
buf[8..12].copy_from_slice(&64_u32.to_le_bytes());
buf[12..16].copy_from_slice(&512_u32.to_le_bytes());
buf[16..24].copy_from_slice(&64_u64.to_le_bytes());
buf[64..80].copy_from_slice(&[0xab; 16]);
let volume = Volume::parse(&buf).unwrap();
assert_eq!(volume.set_identifier, Some([0xab; 16]));
}
#[test]
fn ignores_smart_volume_padding_as_set_identifier() {
let mut buf = [0; SMART_VOLUME_SIZE];
buf[4..8].copy_from_slice(&1_u32.to_le_bytes());
buf[8..12].copy_from_slice(&64_u32.to_le_bytes());
buf[12..16].copy_from_slice(&512_u32.to_le_bytes());
buf[16..20].copy_from_slice(&64_u32.to_le_bytes());
buf[64..80].copy_from_slice(&[0xab; 16]);
let volume = Volume::parse(&buf).unwrap();
assert_eq!(volume.set_identifier, None);
}
#[test]
fn parses_table_entry_compression_bit_and_masked_offset() {
let entry = TableEntry::parse(&0x8000_1234_u32.to_le_bytes()).unwrap();
assert!(entry.compressed);
assert_eq!(entry.offset, 0x1234);
assert_eq!(entry.raw, 0x8000_1234);
}
}