use bitvec::prelude::*;
use crate::constants::{
CREATOR_SIZE, HEADER_SIGNATURE, HEADER_SIZE, HEADER1_OFFSET, HEADER2_OFFSET,
MAX_REGION_ENTRIES, REGION_ENTRY_SIZE, REGION_SIGNATURE, REGION_TABLE_SIZE,
REGION_TABLE1_OFFSET, REGION_TABLE2_OFFSET, RT_HEADER_SIZE,
};
use crate::error::{Error, Result, SignaturePosition};
use crate::types::{Crc32c, Guid};
#[derive(Clone, Copy)]
pub struct Header<'a> {
data: &'a [u8],
}
impl<'a> Header<'a> {
pub(crate) fn new(data: &'a [u8]) -> Result<Self> {
let min_size = (REGION_TABLE2_OFFSET + REGION_TABLE_SIZE) as usize;
if data.len() < min_size {
return Err(Error::InvalidFile(format!(
"header section too small: {} bytes, need at least {min_size}",
data.len()
)));
}
Ok(Self { data })
}
#[must_use]
pub fn file_type(&self) -> FileTypeIdentifier<'a> {
FileTypeIdentifier { data: self.data }
}
pub fn header(&self, index: usize) -> Result<HeaderStructure<'a>> {
match index {
0 => self.current_header(),
1 => self.validate_header_at(HEADER1_OFFSET as usize),
2 => self.validate_header_at(HEADER2_OFFSET as usize),
_ => Err(Error::InvalidParameter(format!(
"header index must be 0, 1, or 2, got {index}"
))),
}
}
pub fn region_table(&self, index: usize) -> Result<RegionTable<'a>> {
match index {
0 => self.current_region_table(),
1 => self.validate_region_table_at(REGION_TABLE1_OFFSET as usize),
2 => self.validate_region_table_at(REGION_TABLE2_OFFSET as usize),
_ => Err(Error::InvalidParameter(format!(
"region table index must be 0, 1, or 2, got {index}"
))),
}
}
fn validate_header_at(&self, offset: usize) -> Result<HeaderStructure<'a>> {
let slice = &self.data[offset..][..HEADER_SIZE as usize];
if slice[..4].view_bits::<Lsb0>() != *HEADER_SIGNATURE {
let mut found: [u8; 8] = [0; 8];
found[..4].copy_from_slice(&slice[..4]);
let mut expected: [u8; 8] = [0; 8];
expected[..4].copy_from_slice(&HEADER_SIGNATURE.into_inner().to_le_bytes());
return Err(Error::InvalidSignature {
position: SignaturePosition::Header,
expected,
found,
});
}
let stored_crc = u32::from_le_bytes(slice[4..8].try_into().unwrap());
let computed_crc = crate::common::crc32c_zeroed_checksum(slice);
if computed_crc != stored_crc {
return Err(Error::InvalidChecksum {
expected: stored_crc,
actual: computed_crc,
});
}
Ok(HeaderStructure { data: slice })
}
fn current_header(&self) -> Result<HeaderStructure<'a>> {
let h1 = self.validate_header_at(HEADER1_OFFSET as usize);
let h2 = self.validate_header_at(HEADER2_OFFSET as usize);
match (h1, h2) {
(Ok(h1), Ok(h2)) => {
if h1.sequence_number() == h2.sequence_number() {
return Err(Error::HeaderSequenceNumberInvalid {
sequence_number_1: h1.sequence_number(),
sequence_number_2: h2.sequence_number(),
});
}
if h1.sequence_number() > h2.sequence_number() {
Ok(h1)
} else {
Ok(h2)
}
}
(Ok(h1), Err(_)) => Ok(h1),
(Err(_), Ok(h2)) => Ok(h2),
(Err(e1), Err(_)) => Err(Error::CorruptedHeader(format!(
"both headers are invalid: {e1}"
))),
}
}
fn current_header_index(&self) -> Result<usize> {
let h1 = self.validate_header_at(HEADER1_OFFSET as usize);
let h2 = self.validate_header_at(HEADER2_OFFSET as usize);
match (h1, h2) {
(Ok(h1), Ok(h2)) => {
if h1.sequence_number() == h2.sequence_number() {
return Err(Error::HeaderSequenceNumberInvalid {
sequence_number_1: h1.sequence_number(),
sequence_number_2: h2.sequence_number(),
});
}
if h1.sequence_number() > h2.sequence_number() {
Ok(1)
} else {
Ok(2)
}
}
(Ok(_), Err(_)) => Ok(1),
(Err(_), Ok(_)) => Ok(2),
(Err(e1), Err(_)) => Err(Error::CorruptedHeader(format!(
"both headers are invalid: {e1}"
))),
}
}
fn validate_region_table_at(&self, offset: usize) -> Result<RegionTable<'a>> {
let slice = &self.data[offset..][..REGION_TABLE_SIZE as usize];
if slice[..4].view_bits::<Lsb0>() != *REGION_SIGNATURE {
let mut found: [u8; 8] = [0; 8];
found[..4].copy_from_slice(&slice[..4]);
let mut expected: [u8; 8] = [0; 8];
expected[..4].copy_from_slice(®ION_SIGNATURE.into_inner().to_le_bytes());
return Err(Error::InvalidSignature {
position: SignaturePosition::RegionTable,
expected,
found,
});
}
let stored_crc = u32::from_le_bytes(slice[4..8].try_into().unwrap());
let computed_crc = crate::common::crc32c_zeroed_checksum(slice);
if computed_crc != stored_crc {
return Err(Error::InvalidChecksum {
expected: stored_crc,
actual: computed_crc,
});
}
let entry_count = u32::from_le_bytes(slice[8..12].try_into().unwrap());
if entry_count > u32::from(MAX_REGION_ENTRIES) {
return Err(Error::InvalidRegionTable(format!(
"REGION_ENTRY_COUNT_EXCEEDS_MAXIMUM: entry count {entry_count} exceeds maximum of {MAX_REGION_ENTRIES}"
)));
}
let entries_start = 16; let entries_end = entries_start + entry_count as usize * REGION_ENTRY_SIZE as usize;
if entries_end > REGION_TABLE_SIZE as usize {
return Err(Error::InvalidRegionTable(format!(
"entry count {entry_count} overflows region table"
)));
}
Ok(RegionTable { data: slice })
}
fn current_region_table(&self) -> Result<RegionTable<'a>> {
let idx = self.current_header_index()?;
let offset = match idx {
1 => REGION_TABLE1_OFFSET as usize,
2 => REGION_TABLE2_OFFSET as usize,
_ => unreachable!(),
};
self.validate_region_table_at(offset)
}
}
pub struct FileTypeIdentifier<'a> {
data: &'a [u8],
}
impl<'a> FileTypeIdentifier<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 8] {
self.data[..8].try_into().unwrap()
}
#[must_use]
pub fn creator(&self) -> &'a [u8; 512] {
self.data[8..8 + CREATOR_SIZE as usize].try_into().unwrap()
}
}
pub struct HeaderStructure<'a> {
data: &'a [u8],
}
impl<'a> HeaderStructure<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 4] {
self.data[..4].try_into().unwrap()
}
#[must_use]
pub fn checksum(&self) -> Crc32c {
Crc32c::from_raw(u32::from_le_bytes(self.data[4..8].try_into().unwrap()))
}
#[must_use]
pub fn sequence_number(&self) -> u64 {
u64::from_le_bytes(self.data[8..16].try_into().unwrap())
}
#[must_use]
pub fn file_write_guid(&self) -> Guid {
Guid::from_bytes(self.data[16..32].try_into().unwrap())
}
#[must_use]
pub fn data_write_guid(&self) -> Guid {
Guid::from_bytes(self.data[32..48].try_into().unwrap())
}
#[must_use]
pub fn log_guid(&self) -> Guid {
Guid::from_bytes(self.data[48..64].try_into().unwrap())
}
#[must_use]
pub fn log_version(&self) -> u16 {
u16::from_le_bytes(self.data[64..66].try_into().unwrap())
}
#[must_use]
pub fn version(&self) -> u16 {
u16::from_le_bytes(self.data[66..68].try_into().unwrap())
}
#[must_use]
pub fn log_length(&self) -> u32 {
u32::from_le_bytes(self.data[68..72].try_into().unwrap())
}
#[must_use]
pub fn log_offset(&self) -> u64 {
u64::from_le_bytes(self.data[72..80].try_into().unwrap())
}
}
pub struct RegionTable<'a> {
data: &'a [u8],
}
impl<'a> RegionTable<'a> {
#[must_use]
pub fn header(&self) -> RegionTableHeader<'a> {
RegionTableHeader {
data: &self.data[..RT_HEADER_SIZE as usize],
}
}
pub fn entries(&self) -> impl Iterator<Item = RegionTableEntry<'a>> + '_ {
let count = self.header().entry_count() as usize;
(0..count).map(move |i| {
let offset = RT_HEADER_SIZE as usize + i * REGION_ENTRY_SIZE as usize;
RegionTableEntry {
data: &self.data[offset..][..REGION_ENTRY_SIZE as usize],
}
})
}
}
pub struct RegionTableHeader<'a> {
data: &'a [u8],
}
impl<'a> RegionTableHeader<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 4] {
self.data[..4].try_into().unwrap()
}
#[must_use]
pub fn checksum(&self) -> Crc32c {
Crc32c::from_raw(u32::from_le_bytes(self.data[4..8].try_into().unwrap()))
}
#[must_use]
pub fn entry_count(&self) -> u32 {
u32::from_le_bytes(self.data[8..12].try_into().unwrap())
}
#[must_use]
pub fn reserved(&self) -> u32 {
u32::from_le_bytes(self.data[12..16].try_into().unwrap())
}
}
pub struct RegionTableEntry<'a> {
data: &'a [u8],
}
impl RegionTableEntry<'_> {
#[must_use]
pub fn guid(&self) -> Guid {
Guid::from_bytes(self.data[..16].try_into().unwrap())
}
#[must_use]
pub fn file_offset(&self) -> u64 {
u64::from_le_bytes(self.data[16..24].try_into().unwrap())
}
#[must_use]
pub fn length(&self) -> u32 {
u32::from_le_bytes(self.data[24..28].try_into().unwrap())
}
#[must_use]
pub fn required(&self) -> bool {
self.data[28..32].view_bits::<Lsb0>()[0]
}
}