use crate::format::bytes::read_le_uint as read_uint;
use crate::format::checksum::checksum_metadata;
use crate::format::{FormatContext, FormatError, FormatResult};
pub const FRHP_SIGNATURE: [u8; 4] = *b"FRHP";
pub const FHIB_SIGNATURE: [u8; 4] = *b"FHIB";
pub const FHDB_SIGNATURE: [u8; 4] = *b"FHDB";
const HDR_FLAG_CHECKSUM_DBLOCKS: u8 = 0x02;
const MAX_BLOCKS: usize = 65_536;
#[derive(Debug, Clone)]
pub struct FractalHeapHeader {
pub id_len: u16,
pub filter_len: u16,
pub checksum_dblocks: bool,
pub man_nobjs: u64,
pub table_width: u16,
pub start_block_size: u64,
pub max_direct_size: u64,
pub max_heap_size_bits: u16,
pub table_addr: u64,
pub curr_root_rows: u16,
pub heap_off_size: u8,
pub max_direct_rows: u32,
pub row_block_size: Vec<u64>,
}
fn log2_of2(n: u64) -> u32 {
if n == 0 || (n & (n - 1)) != 0 {
return 0;
}
n.trailing_zeros()
}
fn size_of_offset_bits(bits: u16) -> u8 {
bits.div_ceil(8) as u8
}
fn need(buf: &[u8], pos: usize, n: usize) -> FormatResult<()> {
if buf.len() < pos + n {
Err(FormatError::BufferTooShort {
needed: pos + n,
available: buf.len(),
})
} else {
Ok(())
}
}
impl FractalHeapHeader {
fn base_size(ctx: &FormatContext) -> usize {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
4 + 1 + 2 + 2 + 1 + 4 + ss + sa + ss + sa + 8 * ss + 2 + ss + ss + 2 + 2 + sa + 2 + 4
}
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
let base = Self::base_size(ctx);
need(buf, 0, base)?;
if buf[0..4] != FRHP_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
let version = buf[4];
if version != 0 {
return Err(FormatError::InvalidVersion(version));
}
let mut pos = 5;
let id_len = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let filter_len = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let heap_flags = buf[pos];
pos += 1;
let checksum_dblocks = heap_flags & HDR_FLAG_CHECKSUM_DBLOCKS != 0;
pos += 4; pos += ss; pos += sa;
pos += ss; pos += sa;
pos += ss; pos += ss; pos += ss; let man_nobjs = read_uint(&buf[pos..], ss);
pos += ss;
pos += ss; pos += ss; pos += ss; pos += ss;
let table_width = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let start_block_size = read_uint(&buf[pos..], ss);
pos += ss;
let max_direct_size = read_uint(&buf[pos..], ss);
pos += ss;
let max_heap_size_bits = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
pos += 2; let table_addr = read_uint(&buf[pos..], sa);
pos += sa;
let curr_root_rows = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
debug_assert_eq!(pos, base - 4);
let stored = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
let computed = checksum_metadata(&buf[..pos]);
if stored != computed {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
if table_width == 0 || start_block_size == 0 {
return Err(FormatError::InvalidData(
"fractal heap doubling-table has zero width or block size".into(),
));
}
let start_bits = log2_of2(start_block_size);
let first_row_bits = start_bits + log2_of2(table_width as u64);
let max_root_rows = (max_heap_size_bits as u32)
.saturating_sub(first_row_bits)
.saturating_add(1);
let max_direct_bits = log2_of2(max_direct_size);
let max_direct_rows = max_direct_bits.saturating_sub(start_bits).saturating_add(2);
let heap_off_size = size_of_offset_bits(max_heap_size_bits);
let mut row_block_size = Vec::with_capacity(max_root_rows as usize);
if max_root_rows > 0 {
row_block_size.push(start_block_size);
let mut tmp = start_block_size;
for _ in 1..max_root_rows {
row_block_size.push(tmp);
tmp = tmp.saturating_mul(2);
}
}
Ok(Self {
id_len,
filter_len,
checksum_dblocks,
man_nobjs,
table_width,
start_block_size,
max_direct_size,
max_heap_size_bits,
table_addr,
curr_root_rows,
heap_off_size,
max_direct_rows,
row_block_size,
})
}
}
pub trait BlockReader {
fn read_block(&mut self, offset: u64, len: usize) -> FormatResult<Vec<u8>>;
}
pub fn collect_managed_objects<R: BlockReader>(
header: &FractalHeapHeader,
ctx: &FormatContext,
reader: &mut R,
) -> FormatResult<Vec<Vec<u8>>> {
let mut objects = Vec::new();
if header.table_addr == u64::MAX || header.man_nobjs == 0 {
return Ok(objects);
}
let mut block_budget = MAX_BLOCKS;
if header.curr_root_rows == 0 {
read_direct_block(
header,
ctx,
reader,
header.table_addr,
header.start_block_size as usize,
&mut objects,
&mut block_budget,
)?;
} else {
walk_indirect_block(
header,
ctx,
reader,
header.table_addr,
header.curr_root_rows as u32,
&mut objects,
&mut block_budget,
0,
)?;
}
Ok(objects)
}
#[allow(clippy::too_many_arguments)]
fn walk_indirect_block<R: BlockReader>(
header: &FractalHeapHeader,
ctx: &FormatContext,
reader: &mut R,
addr: u64,
nrows: u32,
objects: &mut Vec<Vec<u8>>,
budget: &mut usize,
depth: usize,
) -> FormatResult<()> {
const MAX_INDIRECT_DEPTH: usize = 256;
if depth > MAX_INDIRECT_DEPTH {
return Err(FormatError::InvalidData(
"fractal heap indirect-block nesting exceeds maximum depth".into(),
));
}
if addr == u64::MAX || nrows == 0 {
return Ok(());
}
if *budget == 0 {
return Err(FormatError::InvalidData(
"fractal heap block budget exhausted".into(),
));
}
*budget -= 1;
let sa = ctx.sizeof_addr as usize;
let width = header.table_width as usize;
let n_entries = nrows as usize * width;
let dir_rows = nrows.min(header.max_direct_rows) as usize;
let dir_entries = dir_rows * width;
let per_dir_entry = if header.filter_len > 0 {
sa + ctx.sizeof_size as usize + 4
} else {
sa
};
let indir_entries = n_entries - dir_entries;
let block_len = 4
+ 1
+ sa
+ header.heap_off_size as usize
+ dir_entries * per_dir_entry
+ indir_entries * sa
+ 4;
let buf = reader.read_block(addr, block_len)?;
need(&buf, 0, block_len)?;
if buf[0..4] != FHIB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
if buf[4] != 0 {
return Err(FormatError::InvalidVersion(buf[4]));
}
let csum_off = block_len - 4;
let stored = u32::from_le_bytes([
buf[csum_off],
buf[csum_off + 1],
buf[csum_off + 2],
buf[csum_off + 3],
]);
let computed = checksum_metadata(&buf[..csum_off]);
if stored != computed {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
let mut pos = 4 + 1 + sa + header.heap_off_size as usize;
for entry in 0..n_entries {
let row = entry / width;
let child_addr = read_uint(&buf[pos..], sa);
pos += sa;
if header.filter_len > 0 && row < header.max_direct_rows as usize {
pos += ctx.sizeof_size as usize + 4;
}
if child_addr == u64::MAX || child_addr == 0 {
continue;
}
if row < header.max_direct_rows as usize {
let size = header
.row_block_size
.get(row)
.copied()
.unwrap_or(header.start_block_size) as usize;
read_direct_block(header, ctx, reader, child_addr, size, objects, budget)?;
} else {
let block_size = header
.row_block_size
.get(row)
.copied()
.unwrap_or(header.start_block_size);
let child_nrows = indirect_nrows(header, block_size);
walk_indirect_block(
header,
ctx,
reader,
child_addr,
child_nrows,
objects,
budget,
depth + 1,
)?;
}
}
Ok(())
}
fn indirect_nrows(header: &FractalHeapHeader, block_size: u64) -> u32 {
let start_bits = log2_of2(header.start_block_size);
let first_row_bits = start_bits + log2_of2(header.table_width as u64);
let size_log2 = log2_of2(block_size);
size_log2.saturating_sub(first_row_bits).saturating_add(1)
}
fn read_direct_block<R: BlockReader>(
header: &FractalHeapHeader,
ctx: &FormatContext,
reader: &mut R,
addr: u64,
size: usize,
objects: &mut Vec<Vec<u8>>,
budget: &mut usize,
) -> FormatResult<()> {
if addr == u64::MAX || size == 0 {
return Ok(());
}
if *budget == 0 {
return Err(FormatError::InvalidData(
"fractal heap block budget exhausted".into(),
));
}
*budget -= 1;
let sa = ctx.sizeof_addr as usize;
let buf = reader.read_block(addr, size)?;
let prefix_min = 4 + 1 + sa + header.heap_off_size as usize;
if buf.len() < prefix_min {
return Ok(());
}
if buf[0..4] != FHDB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
if buf[4] != 0 {
return Err(FormatError::InvalidVersion(buf[4]));
}
let mut payload_start = prefix_min;
if header.checksum_dblocks {
let chk_off = prefix_min;
if header.filter_len == 0 && buf.len() >= chk_off + 4 {
let stored = u32::from_le_bytes([
buf[chk_off],
buf[chk_off + 1],
buf[chk_off + 2],
buf[chk_off + 3],
]);
let mut image = buf.clone();
image[chk_off..chk_off + 4].fill(0);
let computed = checksum_metadata(&image);
if stored != computed {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
}
payload_start += 4;
}
if payload_start >= buf.len() {
return Ok(());
}
objects.push(buf[payload_start..].to_vec());
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log2_of2_basic() {
assert_eq!(log2_of2(1), 0);
assert_eq!(log2_of2(2), 1);
assert_eq!(log2_of2(512), 9);
assert_eq!(log2_of2(4096), 12);
assert_eq!(log2_of2(3), 0);
assert_eq!(log2_of2(0), 0);
}
#[test]
fn size_of_offset_bits_basic() {
assert_eq!(size_of_offset_bits(0), 0);
assert_eq!(size_of_offset_bits(8), 1);
assert_eq!(size_of_offset_bits(9), 2);
assert_eq!(size_of_offset_bits(16), 2);
assert_eq!(size_of_offset_bits(17), 3);
}
#[test]
fn bad_signature_rejected() {
let ctx = FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
};
let buf = vec![0u8; FractalHeapHeader::base_size(&ctx)];
let err = FractalHeapHeader::decode(&buf, &ctx).unwrap_err();
assert!(matches!(err, FormatError::InvalidSignature));
}
#[test]
fn too_short_rejected() {
let ctx = FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
};
let buf = vec![0u8; 8];
let err = FractalHeapHeader::decode(&buf, &ctx).unwrap_err();
assert!(matches!(err, FormatError::BufferTooShort { .. }));
}
}