use std::sync::Arc;
use crate::persistence::{
CHUNK_ENTRY_LEN, CHUNK_FLAG_CRITICAL, FORMAT_VERSION, LoadError, PYLD_DESC_LEN,
PYLD_DESC_LEN_FIXED, SUPERBLOCK_LEN, TAG_PYLD, TAG_TREE, TREE_DESC_LEN, derive_level_bounds,
expected_tree_shape, parse_pyld_chunk, parse_tree_chunk, read_u32_at, read_u64_at,
read_u64_le_unchecked,
};
use super::StreamError;
use super::directory::{StreamCoreParts, directory_start};
use super::limits::{Budget, COALESCE_GAP_BYTES, StreamLimits, directory_node_budget};
use super::payload::{
PayloadSection, emit_run_payloads, emit_run_payloads_fixed, payload_blob_span, payload_run_end,
payload_run_end_fixed,
};
use super::planner::{apply_gather_run, expand_frontier, plan_gather};
use super::readers::RangeReader;
const MAX_CONTAINER_CHUNKS_WITHOUT_LEN: usize = 1024;
pub(crate) struct StreamCore<R> {
pub(crate) reader: R,
pub(crate) node_size: usize,
pub(crate) num_items: usize,
pub(crate) num_nodes: usize,
pub(crate) level_count: usize,
pub(crate) level_bounds: Vec<usize>,
pub(crate) record: usize,
pub(crate) box_stride: usize,
pub(crate) interleaved: bool,
pub(crate) box0: u64,
pub(crate) idx0: u64,
pub(crate) dir_node_start: usize,
pub(crate) dir_boxes: Arc<[u8]>,
pub(crate) dir_indices: Arc<[u8]>,
pub(crate) payload: Option<PayloadSection>,
pub(crate) limits: StreamLimits,
}
impl<R> StreamCore<R> {
pub(crate) fn has_payload(&self) -> bool {
self.payload.is_some()
}
pub(crate) fn coalesce_gap(&self) -> u64 {
self.limits.coalesce_gap_bytes.unwrap_or(COALESCE_GAP_BYTES)
}
pub(crate) fn into_parts(self) -> (StreamCoreParts, R) {
let parts = StreamCoreParts {
node_size: self.node_size,
num_items: self.num_items,
num_nodes: self.num_nodes,
level_count: self.level_count,
level_bounds: self.level_bounds,
record: self.record,
box_stride: self.box_stride,
interleaved: self.interleaved,
box0: self.box0,
idx0: self.idx0,
dir_node_start: self.dir_node_start,
dir_boxes: self.dir_boxes,
dir_indices: self.dir_indices,
payload: self.payload,
};
(parts, self.reader)
}
pub(crate) fn from_parts(parts: StreamCoreParts, reader: R, limits: StreamLimits) -> Self {
StreamCore {
reader,
node_size: parts.node_size,
num_items: parts.num_items,
num_nodes: parts.num_nodes,
level_count: parts.level_count,
level_bounds: parts.level_bounds,
record: parts.record,
box_stride: parts.box_stride,
interleaved: parts.interleaved,
box0: parts.box0,
idx0: parts.idx0,
dir_node_start: parts.dir_node_start,
dir_boxes: parts.dir_boxes,
dir_indices: parts.dir_indices,
payload: parts.payload,
limits,
}
}
}
pub(super) fn checked_directory_span(
chunk_count: usize,
file_len: Option<u64>,
) -> Result<(usize, u64), LoadError> {
if file_len.is_none() && chunk_count > MAX_CONTAINER_CHUNKS_WITHOUT_LEN {
return Err(LoadError::InvalidTree);
}
let dir_len = chunk_count
.checked_mul(CHUNK_ENTRY_LEN)
.ok_or(LoadError::IntegerOverflow)?;
let dir_end = SUPERBLOCK_LEN
.checked_add(dir_len)
.ok_or(LoadError::IntegerOverflow)?;
if let Some(file_len) = file_len
&& file_len < dir_end as u64
{
return Err(LoadError::Truncated);
}
Ok((dir_len, dir_end as u64))
}
pub(super) fn align8_u64(value: u64) -> Result<u64, LoadError> {
value
.checked_add(7)
.map(|v| v & !7)
.ok_or(LoadError::IntegerOverflow)
}
impl<R: RangeReader> StreamCore<R> {
pub(crate) fn open(
reader: R,
dimensions: usize,
coord_bytes: usize,
limits: StreamLimits,
) -> Result<Self, StreamError> {
let mut head = [0u8; SUPERBLOCK_LEN];
reader.read_exact_at(0, &mut head)?;
if &head[..8] != b"PSINDEX\0" {
return Err(StreamError::Format(LoadError::BadMagic));
}
if u64::from_le_bytes(head[8..16].try_into().unwrap()) != FORMAT_VERSION {
return Err(StreamError::Format(LoadError::UnsupportedVersion));
}
let chunk_count = read_u32_at(&head, 16)? as usize;
let file_len = reader.len();
let (dir_len, dir_end) = checked_directory_span(chunk_count, file_len)?;
let mut dir = vec![0u8; dir_len];
reader.read_exact_at(SUPERBLOCK_LEN as u64, &mut dir)?;
let mut max_end = dir_end;
let mut tree: Option<(u64, u64)> = None;
let mut pyld: Option<(u64, u64)> = None;
for i in 0..chunk_count {
let base = i * CHUNK_ENTRY_LEN;
let mut tag = [0u8; 4];
tag.copy_from_slice(&dir[base..base + 4]);
let flags = read_u32_at(&dir, base + 4)?;
let offset = read_u64_at(&dir, base + 8)?;
let len = read_u64_at(&dir, base + 16)?;
let end = offset.checked_add(len).ok_or(LoadError::IntegerOverflow)?;
if file_len.is_some_and(|fl| end > fl) {
return Err(StreamError::Format(LoadError::InvalidTree));
}
max_end = max_end.max(end);
if tag == TAG_TREE {
tree = Some((offset, len));
} else if tag == TAG_PYLD {
pyld = Some((offset, len));
} else if flags & CHUNK_FLAG_CRITICAL != 0 {
return Err(StreamError::Format(LoadError::UnsupportedVersion));
}
}
let aligned_end = align8_u64(max_end)?;
if let Some(fl) = file_len
&& fl > aligned_end
{
return Err(StreamError::Format(LoadError::LengthMismatch {
expected: max_end as usize,
actual: fl as usize,
}));
}
let (toff, tlen) = tree.ok_or(LoadError::InvalidTree)?;
if tlen < TREE_DESC_LEN as u64 {
return Err(StreamError::Format(LoadError::Truncated));
}
let mut desc = [0u8; TREE_DESC_LEN];
reader.read_exact_at(toff, &mut desc)?;
let (td, _) = parse_tree_chunk(&desc)?;
if td.dimensions != dimensions || td.coord_bytes != coord_bytes {
return Err(StreamError::Format(LoadError::UnsupportedVersion));
}
let (num_nodes, level_count) = expected_tree_shape(td.num_items, td.node_size)?;
let record = dimensions
.checked_mul(2 * coord_bytes)
.ok_or(LoadError::IntegerOverflow)?;
let box_stride = if td.interleaved { record + 8 } else { record };
let box0 = toff + td.desc_len as u64;
let node_len = num_nodes
.checked_mul(box_stride + if td.interleaved { 0 } else { 8 })
.ok_or(LoadError::IntegerOverflow)?;
if tlen != td.desc_len as u64 + node_len as u64 {
return Err(StreamError::Format(LoadError::InvalidTree));
}
let idx0 = if td.interleaved {
box0
} else {
box0 + (num_nodes * record) as u64
};
let level_bounds = derive_level_bounds(td.num_items, td.node_size, level_count);
let payload = match pyld {
Some((poff, plen)) => {
if plen < PYLD_DESC_LEN as u64 {
return Err(StreamError::Format(LoadError::Truncated));
}
let dn = (PYLD_DESC_LEN_FIXED as u64).min(plen) as usize;
let mut pd = [0u8; PYLD_DESC_LEN_FIXED];
reader.read_exact_at(poff, &mut pd[..dn])?;
let (pdesc, _) = parse_pyld_chunk(&pd[..dn])?;
let body0 = poff + pdesc.desc_len as u64;
if pdesc.record_stride != 0 {
let stride = pdesc.record_stride as u64;
let blob_total = (td.num_items as u64)
.checked_mul(stride)
.ok_or(StreamError::Format(LoadError::IntegerOverflow))?;
let need = pdesc.desc_len as u64 + blob_total;
if plen != need {
return Err(StreamError::Format(LoadError::InvalidTree));
}
Some(PayloadSection {
offsets_start: 0,
blobs_start: body0,
blob_total,
stride,
})
} else {
let offsets_start = body0;
let last_at = offsets_start + (td.num_items as u64) * 8;
let mut last = [0u8; 8];
reader.read_exact_at(last_at, &mut last)?;
let blob_total = u64::from_le_bytes(last);
let blobs_start = offsets_start + (td.num_items as u64 + 1) * 8;
let need = pdesc.desc_len as u64 + (td.num_items as u64 + 1) * 8 + blob_total;
if plen != need {
return Err(StreamError::Format(LoadError::InvalidTree));
}
Some(PayloadSection {
offsets_start,
blobs_start,
blob_total,
stride: 0,
})
}
}
None => None,
};
let budget = directory_node_budget(&limits, box_stride, td.interleaved);
let dir_node_start = directory_start(&level_bounds, level_count, budget);
let cached_nodes = num_nodes - dir_node_start;
let mut dir_boxes = vec![0u8; cached_nodes * box_stride];
if !dir_boxes.is_empty() {
let offset = box0 + (dir_node_start * box_stride) as u64;
reader.read_exact_at(offset, &mut dir_boxes)?;
}
let mut dir_indices = if td.interleaved {
Vec::new()
} else {
vec![0u8; cached_nodes * 8]
};
if !dir_indices.is_empty() {
let offset = idx0 + (dir_node_start * 8) as u64;
reader.read_exact_at(offset, &mut dir_indices)?;
}
let dir_boxes: Arc<[u8]> = dir_boxes.into();
let dir_indices: Arc<[u8]> = dir_indices.into();
Ok(StreamCore {
reader,
node_size: td.node_size,
num_items: td.num_items,
num_nodes,
level_count,
level_bounds,
record,
box_stride,
interleaved: td.interleaved,
box0,
idx0,
dir_node_start,
dir_boxes,
dir_indices,
payload,
limits,
})
}
pub(crate) fn cached_box_bytes(&self, position: usize) -> Option<&[u8]> {
if position < self.dir_node_start || position >= self.num_nodes {
return None;
}
let start = (position - self.dir_node_start) * self.box_stride;
self.dir_boxes.get(start..start + self.record)
}
#[allow(clippy::too_many_arguments)]
fn gather(
&self,
positions: &[usize],
section0: u64,
stride: usize,
cache: &[u8],
out: &mut Vec<u8>,
scratch: &mut Vec<u8>,
budget: &mut Budget,
) -> Result<(), StreamError> {
let runs = plan_gather(
positions,
section0,
stride,
self.dir_node_start,
cache,
out,
self.coalesce_gap(),
);
for run in &runs {
budget.charge_read(run.len)?;
scratch.clear();
scratch.resize(run.len, 0);
self.reader.read_exact_at(run.offset, scratch)?;
apply_gather_run(out, run, scratch, stride);
}
Ok(())
}
fn traverse<O, L>(&self, overlaps: O, mut leaf: L) -> Result<(), StreamError>
where
O: Fn(&[u8]) -> bool,
L: FnMut(&[usize], &[u8], &mut Budget) -> Result<(), StreamError>,
{
if self.num_items == 0 {
return Ok(());
}
let mut budget = Budget::new(self.limits);
let mut frontier = vec![self.num_nodes - 1];
let mut level = self.level_count - 1;
let mut boxes = Vec::new();
let mut indices = Vec::new();
let mut scratch = Vec::new();
let mut survivors: Vec<usize> = Vec::new();
loop {
self.gather(
&frontier,
self.box0,
self.box_stride,
&self.dir_boxes,
&mut boxes,
&mut scratch,
&mut budget,
)?;
survivors.clear();
indices.clear();
for (i, &pos) in frontier.iter().enumerate() {
let slot = i * self.box_stride;
if overlaps(&boxes[slot..slot + self.record]) {
survivors.push(pos);
if self.interleaved {
indices
.extend_from_slice(&boxes[slot + self.record..slot + self.record + 8]);
}
}
}
if survivors.is_empty() {
return Ok(());
}
if !self.interleaved {
self.gather(
&survivors,
self.idx0,
8,
&self.dir_indices,
&mut indices,
&mut scratch,
&mut budget,
)?;
}
if level == 0 {
return leaf(&survivors, &indices, &mut budget);
}
frontier = expand_frontier(
&self.level_bounds,
self.node_size,
level,
survivors.len(),
&indices,
)?;
level -= 1;
}
}
pub(crate) fn visit_ids<O, F>(&self, overlaps: O, mut visit: F) -> Result<(), StreamError>
where
O: Fn(&[u8]) -> bool,
F: FnMut(usize),
{
self.traverse(overlaps, |survivors, indices, budget| {
for i in 0..survivors.len() {
let id = read_index(indices, i)?;
if id >= self.num_items {
return Err(StreamError::Format(LoadError::InvalidTree));
}
budget.charge_item()?;
visit(id);
}
Ok(())
})
}
pub(crate) fn visit_payloads<O, F>(&self, overlaps: O, mut emit: F) -> Result<(), StreamError>
where
O: Fn(&[u8]) -> bool,
F: FnMut(usize, &[u8]),
{
let section = self.payload.as_ref().ok_or(StreamError::NoPayload)?;
let mut off_buf = Vec::new();
let mut blob_buf = Vec::new();
self.traverse(overlaps, |survivors, indices, budget| {
if section.stride != 0 {
self.gather_payloads_fixed(
section,
survivors,
indices,
&mut blob_buf,
budget,
&mut emit,
)
} else {
self.gather_payloads(
section,
survivors,
indices,
&mut off_buf,
&mut blob_buf,
budget,
&mut emit,
)
}
})
}
#[allow(clippy::too_many_arguments)]
fn gather_payloads<F>(
&self,
section: &PayloadSection,
leaf_positions: &[usize],
indices: &[u8],
off_buf: &mut Vec<u8>,
blob_buf: &mut Vec<u8>,
budget: &mut Budget,
emit: &mut F,
) -> Result<(), StreamError>
where
F: FnMut(usize, &[u8]),
{
let mut j = 0;
while j < leaf_positions.len() {
let k = payload_run_end(leaf_positions, j, self.coalesce_gap());
let lo = leaf_positions[j];
let hi = leaf_positions[k];
off_buf.clear();
off_buf.resize((hi + 2 - lo) * 8, 0);
budget.charge_read(off_buf.len())?;
self.reader
.read_exact_at(section.offsets_start + (lo * 8) as u64, off_buf)?;
let (blob_lo, blob_hi) = payload_blob_span(off_buf, lo, hi, section.blob_total)?;
blob_buf.clear();
blob_buf.resize((blob_hi - blob_lo) as usize, 0);
if !blob_buf.is_empty() {
budget.charge_read(blob_buf.len())?;
self.reader
.read_exact_at(section.blobs_start + blob_lo, blob_buf)?;
}
emit_run_payloads(
leaf_positions,
indices,
j,
k,
lo,
off_buf,
blob_lo,
blob_hi,
blob_buf,
self.num_items,
budget,
emit,
)?;
j = k + 1;
}
Ok(())
}
fn gather_payloads_fixed<F>(
&self,
section: &PayloadSection,
leaf_positions: &[usize],
indices: &[u8],
blob_buf: &mut Vec<u8>,
budget: &mut Budget,
emit: &mut F,
) -> Result<(), StreamError>
where
F: FnMut(usize, &[u8]),
{
let stride = section.stride as usize;
let mut j = 0;
while j < leaf_positions.len() {
let k = payload_run_end_fixed(leaf_positions, j, stride, self.coalesce_gap());
let lo = leaf_positions[j];
let hi = leaf_positions[k];
let span = (hi + 1 - lo) * stride;
blob_buf.clear();
blob_buf.resize(span, 0);
budget.charge_read(span)?;
self.reader
.read_exact_at(section.blobs_start + (lo * stride) as u64, blob_buf)?;
emit_run_payloads_fixed(
leaf_positions,
indices,
j,
k,
lo,
stride,
blob_buf,
self.num_items,
budget,
emit,
)?;
j = k + 1;
}
Ok(())
}
}
pub(crate) fn read_index(bytes: &[u8], i: usize) -> Result<usize, StreamError> {
let value = read_u64_le_unchecked(bytes, i * 8);
usize::try_from(value).map_err(|_| StreamError::Format(LoadError::IntegerOverflow))
}