use adler2::adler32_slice;
use super::{
constants::{E01_VOLUME_DATA_SIZE, S01_VOLUME_DATA_SIZE},
types::EwfMediaType,
};
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EwfVolumeInfo {
pub media_type: EwfMediaType,
pub chunk_count: u32,
pub sectors_per_chunk: u32,
pub bytes_per_sector: u32,
pub sector_count: u64,
pub media_flags: u8,
pub compression_level: u8,
pub error_granularity: u32,
pub set_identifier: [u8; 16],
}
impl EwfVolumeInfo {
pub fn parse_e01(data: &[u8]) -> Result<Self> {
if data.len() < E01_VOLUME_DATA_SIZE {
return Err(Error::invalid_format(format!(
"ewf volume section must be at least {E01_VOLUME_DATA_SIZE} bytes, got {}",
data.len()
)));
}
let data = &data[..E01_VOLUME_DATA_SIZE];
let stored_checksum = u32::from_le_bytes([data[1048], data[1049], data[1050], data[1051]]);
let calculated_checksum = adler32_slice(&data[..1048]);
if stored_checksum != 0 && stored_checksum != calculated_checksum {
return Err(Error::invalid_format(format!(
"ewf volume checksum mismatch: stored 0x{stored_checksum:08x}, calculated 0x{calculated_checksum:08x}"
)));
}
Ok(Self {
media_type: EwfMediaType::from_byte(data[0]),
chunk_count: read_u32_le(data, 4),
sectors_per_chunk: read_u32_le(data, 8),
bytes_per_sector: read_u32_le(data, 12),
sector_count: read_u64_le(data, 16),
media_flags: data[36],
compression_level: data[52],
error_granularity: read_u32_le(data, 56),
set_identifier: copy_array::<16>(&data[64..80])?,
})
}
pub fn parse_s01(data: &[u8]) -> Result<Self> {
if data.len() < S01_VOLUME_DATA_SIZE {
return Err(Error::invalid_format(format!(
"ewf s01 volume section must be at least {S01_VOLUME_DATA_SIZE} bytes, got {}",
data.len()
)));
}
let data = &data[..S01_VOLUME_DATA_SIZE];
let stored_checksum = u32::from_le_bytes([data[90], data[91], data[92], data[93]]);
let calculated_checksum = adler32_slice(&data[..90]);
if stored_checksum != calculated_checksum {
return Err(Error::invalid_format(format!(
"ewf s01 volume checksum mismatch: stored 0x{stored_checksum:08x}, calculated 0x{calculated_checksum:08x}"
)));
}
Ok(Self {
media_type: EwfMediaType::Unknown(0),
chunk_count: read_u32_le(data, 4),
sectors_per_chunk: read_u32_le(data, 8),
bytes_per_sector: read_u32_le(data, 12),
sector_count: u64::from(read_u32_le(data, 16)),
media_flags: 0,
compression_level: 0,
error_granularity: 0,
set_identifier: [0; 16],
})
}
pub fn chunk_size(&self) -> Result<u32> {
self
.sectors_per_chunk
.checked_mul(self.bytes_per_sector)
.ok_or_else(|| Error::invalid_range("ewf chunk size overflow"))
}
pub fn media_size(&self) -> Result<u64> {
self
.sector_count
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| Error::invalid_range("ewf media size overflow"))
}
}
fn copy_array<const N: usize>(data: &[u8]) -> Result<[u8; N]> {
data.try_into().map_err(|_| {
Error::invalid_format(format!(
"ewf fixed-size array conversion failed: expected {N} bytes, got {}",
data.len()
))
})
}
fn read_u32_le(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_u64_le(data: &[u8], offset: usize) -> u64 {
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
#[cfg(test)]
mod tests {
use adler2::adler32_slice;
use super::*;
#[test]
fn parses_e01_volume_fields() {
let mut data = [0u8; E01_VOLUME_DATA_SIZE];
data[0] = 1;
data[4..8].copy_from_slice(&128u32.to_le_bytes());
data[8..12].copy_from_slice(&64u32.to_le_bytes());
data[12..16].copy_from_slice(&512u32.to_le_bytes());
data[16..24].copy_from_slice(&8192u64.to_le_bytes());
data[36] = 1;
data[52] = 2;
data[56..60].copy_from_slice(&64u32.to_le_bytes());
data[64..80].copy_from_slice(&[1; 16]);
let checksum = adler32_slice(&data[..1048]);
data[1048..1052].copy_from_slice(&checksum.to_le_bytes());
let volume = EwfVolumeInfo::parse_e01(&data).unwrap();
assert_eq!(volume.media_type, EwfMediaType::Fixed);
assert_eq!(volume.chunk_count, 128);
assert_eq!(volume.chunk_size().unwrap(), 32768);
assert_eq!(volume.media_size().unwrap(), 4_194_304);
}
#[test]
fn parses_s01_volume_fields() {
let mut data = [0u8; S01_VOLUME_DATA_SIZE];
data[0..4].copy_from_slice(&1u32.to_le_bytes());
data[4..8].copy_from_slice(&45u32.to_le_bytes());
data[8..12].copy_from_slice(&64u32.to_le_bytes());
data[12..16].copy_from_slice(&512u32.to_le_bytes());
data[16..20].copy_from_slice(&2880u32.to_le_bytes());
data[85..90].copy_from_slice(b"SMART");
let checksum = adler32_slice(&data[..90]);
data[90..94].copy_from_slice(&checksum.to_le_bytes());
let volume = EwfVolumeInfo::parse_s01(&data).unwrap();
assert_eq!(volume.chunk_count, 45);
assert_eq!(volume.chunk_size().unwrap(), 32768);
assert_eq!(volume.media_size().unwrap(), 1_474_560);
}
#[test]
fn parses_e01_volume_prefix_with_trailing_bytes() {
let mut data = vec![0u8; E01_VOLUME_DATA_SIZE + 32];
data[0] = 1;
data[4..8].copy_from_slice(&128u32.to_le_bytes());
data[8..12].copy_from_slice(&64u32.to_le_bytes());
data[12..16].copy_from_slice(&512u32.to_le_bytes());
data[16..24].copy_from_slice(&8192u64.to_le_bytes());
let checksum = adler32_slice(&data[..1048]);
data[1048..1052].copy_from_slice(&checksum.to_le_bytes());
let volume = EwfVolumeInfo::parse_e01(&data).unwrap();
assert_eq!(volume.chunk_count, 128);
assert_eq!(volume.media_size().unwrap(), 4_194_304);
}
}