use crate::cursor::Cursor;
use crate::error::{Error, Result};
use crate::source::FileSource;
use crate::types::Header;
#[derive(Debug, Clone)]
pub struct RecordBlockInfo {
pub comp_offset: u64,
pub comp_size: u64,
pub decomp_offset: u64,
pub decomp_size: u64,
}
#[derive(Debug, Clone)]
pub struct RecordIndex {
pub num_entries: u64,
pub total_decompressed_len: u64,
pub blocks: Vec<RecordBlockInfo>,
}
impl RecordIndex {
pub fn find_block(&self, record_offset: u64) -> Option<usize> {
let mut left = 0usize;
let mut right = self.blocks.len();
while left < right {
let mid = (left + right) / 2;
let block = &self.blocks[mid];
let end = block.decomp_offset + block.decomp_size;
if record_offset < block.decomp_offset {
right = mid;
} else if record_offset >= end {
left = mid + 1;
} else {
return Some(mid);
}
}
None
}
}
pub fn parse_record_index(
source: &FileSource,
header: &Header,
record_section_offset: u64,
) -> Result<RecordIndex> {
if !header.is_v2() {
return Err(Error::Unsupported("MDict version 1.x"));
}
let header_bytes = source.read_exact_at(record_section_offset, 32, "record section header")?;
let mut cursor = Cursor::new(&header_bytes);
let num_blocks = cursor.read_u64_be("record num_blocks")?;
let num_entries = cursor.read_u64_be("record num_entries")?;
let index_len = cursor.read_u64_be("record index length")?;
let blocks_len = cursor.read_u64_be("record blocks length")?;
let index_bytes = source.read_exact_at(
record_section_offset + 32,
index_len as usize,
"record block index",
)?;
let mut cursor = Cursor::new(&index_bytes);
let mut blocks = Vec::with_capacity(num_blocks as usize);
let mut comp_offset = record_section_offset + 32 + index_len;
let mut decomp_offset = 0u64;
let mut total_comp = 0u64;
for _ in 0..num_blocks {
let comp_size = cursor.read_u64_be("record block compressed size")?;
let decomp_size = cursor.read_u64_be("record block decompressed size")?;
blocks.push(RecordBlockInfo {
comp_offset,
comp_size,
decomp_offset,
decomp_size,
});
comp_offset = comp_offset
.checked_add(comp_size)
.ok_or(Error::InvalidFormat("record block offset overflow"))?;
decomp_offset = decomp_offset
.checked_add(decomp_size)
.ok_or(Error::InvalidFormat("record block offset overflow"))?;
total_comp = total_comp
.checked_add(comp_size)
.ok_or(Error::InvalidFormat("record block length overflow"))?;
}
if total_comp != blocks_len {
return Err(Error::InvalidData(format!(
"record blocks length mismatch: header={blocks_len}, summed={total_comp}"
)));
}
Ok(RecordIndex {
num_entries,
total_decompressed_len: decomp_offset,
blocks,
})
}