use std::num::NonZeroUsize;
use std::sync::Mutex;
use lru::LruCache;
use crate::error::Result;
use crate::sections::{Chunk, TableEntry, SECTION_DESCRIPTOR_SIZE};
use crate::segment_source::SegmentSource;
pub(crate) const DEFAULT_SECTION_CACHE: usize = 8;
pub(crate) fn parse_table_section(
entries: &[u8],
entry_count: usize,
base_offset: u64,
seg_idx: usize,
chunk_size: u64,
sectors_data_end: Option<u64>,
) -> Result<Vec<Chunk>> {
let mut chunks: Vec<Chunk> = Vec::with_capacity(entry_count);
let mut prev_offset: Option<u64> = None;
for i in 0..entry_count {
let entry = TableEntry::parse(&entries[i * 4..(i + 1) * 4])?;
let abs_offset = u64::from(entry.chunk_offset) + base_offset;
if let Some(po) = prev_offset {
if let Some(prev_chunk) = chunks.last_mut() {
if prev_chunk.compressed() {
let sz = abs_offset.saturating_sub(po);
if sz > 0 {
prev_chunk.set_size(sz);
}
}
}
}
chunks.push(Chunk::new(
seg_idx,
entry.compressed,
abs_offset,
chunk_size,
));
prev_offset = Some(abs_offset);
}
if let Some(end) = sectors_data_end {
if let Some(last) = chunks.last_mut() {
if last.compressed() && last.size() == chunk_size {
let actual = end.saturating_sub(last.offset());
if actual > 0 && actual < chunk_size {
last.set_size(actual);
}
}
}
}
Ok(chunks)
}
#[derive(Debug, Clone)]
pub(crate) struct SectionMeta {
pub(crate) first_chunk_id: usize,
pub(crate) entry_count: usize,
pub(crate) entries_file_offset: u64,
pub(crate) base_offset: u64,
pub(crate) segment_idx: usize,
pub(crate) sectors_data_end: Option<u64>,
}
pub(crate) struct LazyChunkTable {
index: Vec<SectionMeta>,
len: usize,
chunk_size: u64,
cache: Mutex<LruCache<usize, Vec<Chunk>>>,
}
impl LazyChunkTable {
pub(crate) fn new(index: Vec<SectionMeta>, chunk_size: u64, section_cache_cap: usize) -> Self {
let len = index.last().map_or(0, |m| m.first_chunk_id + m.entry_count);
let cap = NonZeroUsize::new(section_cache_cap.max(1)).unwrap_or(NonZeroUsize::MIN);
Self {
index,
len,
chunk_size,
cache: Mutex::new(LruCache::new(cap)),
}
}
pub(crate) fn len(&self) -> usize {
self.len
}
fn section_for(&self, chunk_id: usize) -> Option<usize> {
let after = self.index.partition_point(|m| m.first_chunk_id <= chunk_id);
if after == 0 {
return None;
}
let pos = after - 1;
let meta = &self.index[pos];
if chunk_id < meta.first_chunk_id + meta.entry_count {
Some(pos)
} else {
None
}
}
pub(crate) fn get(&self, chunk_id: usize, segments: &[SegmentSource]) -> Result<Chunk> {
let pos = self.section_for(chunk_id).ok_or_else(|| {
crate::error::EwfError::Parse(format!(
"chunk id {chunk_id} out of range (len {})",
self.len
))
})?;
let meta = &self.index[pos];
let local = chunk_id - meta.first_chunk_id;
{
let mut cache = self
.cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(section) = cache.get(&pos) {
return Ok(section[local].clone());
}
}
let src = segments.get(meta.segment_idx).ok_or_else(|| {
crate::error::EwfError::Parse(format!(
"lazy table section references missing segment {}",
meta.segment_idx
))
})?;
let mut entries_buf = vec![0u8; meta.entry_count * 4];
let n = src.read_at(&mut entries_buf, meta.entries_file_offset)?;
if n < entries_buf.len() {
return Err(crate::error::EwfError::Parse(format!(
"short read for lazy table section at {:#x}: got {n} of {} bytes",
meta.entries_file_offset,
entries_buf.len()
)));
}
let section = parse_table_section(
&entries_buf,
meta.entry_count,
meta.base_offset,
meta.segment_idx,
self.chunk_size,
meta.sectors_data_end,
)?;
let chunk = section[local].clone();
let mut cache = self
.cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
cache.put(pos, section);
Ok(chunk)
}
pub(crate) fn resident_table_bytes(&self) -> usize {
let index_bytes = self.index.len() * std::mem::size_of::<SectionMeta>();
let cache = self
.cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let cached_chunks: usize = cache.iter().map(|(_, v)| v.len()).sum();
index_bytes + cached_chunks * std::mem::size_of::<Chunk>()
}
}
pub(crate) enum ChunkTable {
Eager(Vec<Chunk>),
Lazy(LazyChunkTable),
}
impl ChunkTable {
pub(crate) fn len(&self) -> usize {
match self {
ChunkTable::Eager(v) => v.len(),
ChunkTable::Lazy(l) => l.len(),
}
}
pub(crate) fn get(&self, chunk_id: usize, segments: &[SegmentSource]) -> Result<Chunk> {
match self {
ChunkTable::Eager(v) => v.get(chunk_id).cloned().ok_or_else(|| {
crate::error::EwfError::Parse(format!(
"chunk id {chunk_id} out of range (len {})",
v.len()
))
}),
ChunkTable::Lazy(l) => l.get(chunk_id, segments),
}
}
pub(crate) fn resident_table_bytes(&self) -> usize {
match self {
ChunkTable::Eager(v) => v.len() * std::mem::size_of::<Chunk>(),
ChunkTable::Lazy(l) => l.resident_table_bytes(),
}
}
}
pub(crate) struct TableSectionRef {
pub(crate) desc_offset: u64,
pub(crate) sectors_data_end: Option<u64>,
}
impl TableSectionRef {
pub(crate) fn read_header(
&self,
src: &SegmentSource,
seg_idx: usize,
first_chunk_id: usize,
max_table_entries: usize,
) -> Result<SectionMeta> {
let hdr_offset = self.desc_offset + SECTION_DESCRIPTOR_SIZE as u64;
let mut tbl_hdr = [0u8; 24];
let n = src.read_at(&mut tbl_hdr, hdr_offset)?;
if n < 24 {
return Err(crate::error::EwfError::Parse(format!(
"short read for lazy table header at {hdr_offset:#x}: got {n} of 24 bytes"
)));
}
let entry_count =
u32::from_le_bytes([tbl_hdr[0], tbl_hdr[1], tbl_hdr[2], tbl_hdr[3]]) as usize;
if entry_count > max_table_entries {
return Err(crate::error::EwfError::Parse(format!(
"table entry count {entry_count} exceeds maximum {max_table_entries}"
)));
}
let base_offset = u64::from_le_bytes(tbl_hdr[8..16].try_into().unwrap_or([0u8; 8]));
Ok(SectionMeta {
first_chunk_id,
entry_count,
entries_file_offset: hdr_offset + 24,
base_offset,
segment_idx: seg_idx,
sectors_data_end: self.sectors_data_end,
})
}
}