use crate::error::{EwfError, Result};
pub const EVF2_SIGNATURE: [u8; 8] = [0x45, 0x56, 0x46, 0x32, 0x0d, 0x0a, 0x81, 0x00];
pub const LEF2_SIGNATURE: [u8; 8] = [0x4c, 0x45, 0x46, 0x32, 0x0d, 0x0a, 0x81, 0x00];
pub const FILE_HEADER_SIZE: usize = 32;
pub const SECTION_DESCRIPTOR_SIZE: usize = 64;
pub const TABLE_ENTRY_SIZE: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionMethod {
None,
Zlib,
Bzip2,
}
impl CompressionMethod {
pub fn from_u16(val: u16) -> Result<Self> {
match val {
0 => Ok(Self::None),
1 => Ok(Self::Zlib),
2 => Ok(Self::Bzip2),
_ => Err(EwfError::Parse(format!("unknown EWF2 compression method: {val}"))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ewf2SectionType {
DeviceInfo,
CaseData,
SectorData,
SectorTable,
ErrorTable,
SessionTable,
IncrementData,
Md5Hash,
Sha1Hash,
RestartData,
EncryptionKeys,
MemoryExtents,
Next,
FinalInfo,
Done,
AnalyticalData,
SingleFilesData,
Unknown(u32),
}
impl Ewf2SectionType {
pub fn from_u32(val: u32) -> Self {
match val {
0x01 => Self::DeviceInfo,
0x02 => Self::CaseData,
0x03 => Self::SectorData,
0x04 => Self::SectorTable,
0x05 => Self::ErrorTable,
0x06 => Self::SessionTable,
0x07 => Self::IncrementData,
0x08 => Self::Md5Hash,
0x09 => Self::Sha1Hash,
0x0A => Self::RestartData,
0x0B => Self::EncryptionKeys,
0x0C => Self::MemoryExtents,
0x0D => Self::Next,
0x0E => Self::FinalInfo,
0x0F => Self::Done,
0x10 => Self::AnalyticalData,
0x20 => Self::SingleFilesData,
other => Self::Unknown(other),
}
}
pub fn name(&self) -> &str {
match self {
Self::DeviceInfo => "device_info",
Self::CaseData => "case_data",
Self::SectorData => "sector_data",
Self::SectorTable => "sector_table",
Self::ErrorTable => "error_table",
Self::SessionTable => "session_table",
Self::IncrementData => "increment_data",
Self::Md5Hash => "md5_hash",
Self::Sha1Hash => "sha1_hash",
Self::RestartData => "restart_data",
Self::EncryptionKeys => "encryption_keys",
Self::MemoryExtents => "memory_extents",
Self::Next => "next",
Self::FinalInfo => "final_info",
Self::Done => "done",
Self::AnalyticalData => "analytical_data",
Self::SingleFilesData => "single_files_data",
Self::Unknown(_) => "unknown",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ewf2FileHeader {
pub is_physical: bool,
pub major_version: u8,
pub minor_version: u8,
pub compression_method: CompressionMethod,
pub segment_number: u32,
pub set_identifier: [u8; 16],
}
impl Ewf2FileHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < FILE_HEADER_SIZE {
return Err(EwfError::BufferTooShort {
expected: FILE_HEADER_SIZE,
got: buf.len(),
});
}
let is_physical = if buf[0..8] == EVF2_SIGNATURE {
true
} else if buf[0..8] == LEF2_SIGNATURE {
false
} else {
return Err(EwfError::InvalidSignature);
};
let major_version = buf[8];
let minor_version = buf[9];
let compression_method =
CompressionMethod::from_u16(u16::from_le_bytes([buf[10], buf[11]]))?;
let segment_number = u32::from_le_bytes(buf[12..16].try_into().unwrap());
let mut set_identifier = [0u8; 16];
set_identifier.copy_from_slice(&buf[16..32]);
Ok(Self {
is_physical,
major_version,
minor_version,
compression_method,
segment_number,
set_identifier,
})
}
}
pub const DATA_FLAG_MD5HASHED: u32 = 0x0000_0001;
pub const DATA_FLAG_ENCRYPTED: u32 = 0x0000_0002;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ewf2SectionDescriptor {
pub section_type: Ewf2SectionType,
pub data_flags: u32,
pub previous_offset: u64,
pub data_size: u64,
pub descriptor_size: u32,
pub padding_size: u32,
pub data_integrity_hash: [u8; 16],
pub offset: u64,
}
impl Ewf2SectionDescriptor {
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 section_type =
Ewf2SectionType::from_u32(u32::from_le_bytes(buf[0..4].try_into().unwrap()));
let data_flags = u32::from_le_bytes(buf[4..8].try_into().unwrap());
let previous_offset = u64::from_le_bytes(buf[8..16].try_into().unwrap());
let data_size = u64::from_le_bytes(buf[16..24].try_into().unwrap());
let descriptor_size = u32::from_le_bytes(buf[24..28].try_into().unwrap());
let padding_size = u32::from_le_bytes(buf[28..32].try_into().unwrap());
let mut data_integrity_hash = [0u8; 16];
data_integrity_hash.copy_from_slice(&buf[32..48]);
Ok(Self {
section_type,
data_flags,
previous_offset,
data_size,
descriptor_size,
padding_size,
data_integrity_hash,
offset,
})
}
pub fn is_md5_hashed(&self) -> bool {
self.data_flags & DATA_FLAG_MD5HASHED != 0
}
pub fn is_encrypted(&self) -> bool {
self.data_flags & DATA_FLAG_ENCRYPTED != 0
}
}
pub const CHUNK_FLAG_COMPRESSED: u32 = 0x0000_0001;
pub const CHUNK_FLAG_CHECKSUMED: u32 = 0x0000_0002;
pub const CHUNK_FLAG_PATTERNFILL: u32 = 0x0000_0004;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ewf2TableEntry {
pub chunk_data_offset: u64,
pub chunk_data_size: u32,
pub flags: u32,
}
impl Ewf2TableEntry {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < TABLE_ENTRY_SIZE {
return Err(EwfError::BufferTooShort {
expected: TABLE_ENTRY_SIZE,
got: buf.len(),
});
}
Ok(Self {
chunk_data_offset: u64::from_le_bytes(buf[0..8].try_into().unwrap()),
chunk_data_size: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
flags: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
})
}
pub fn is_compressed(&self) -> bool {
self.flags & CHUNK_FLAG_COMPRESSED != 0
}
pub fn is_checksumed(&self) -> bool {
self.flags & CHUNK_FLAG_CHECKSUMED != 0
}
pub fn is_pattern_fill(&self) -> bool {
self.flags & (CHUNK_FLAG_COMPRESSED | CHUNK_FLAG_PATTERNFILL)
== (CHUNK_FLAG_COMPRESSED | CHUNK_FLAG_PATTERNFILL)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ewf2TableHeader {
pub first_chunk: u64,
pub entry_count: u32,
}
impl Ewf2TableHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 20 {
return Err(EwfError::BufferTooShort {
expected: 20,
got: buf.len(),
});
}
Ok(Self {
first_chunk: u64::from_le_bytes(buf[0..8].try_into().unwrap()),
entry_count: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
})
}
}