batpak 0.8.2

Event sourcing with causal graphs and caller-defined gates. Sync API, no async runtime.
Documentation
use super::{IndexScanEvent, Reader, FRAME_HEADER_BYTES, MAX_BATCH_RECOVERY_ITEMS};
use crate::event::{EventKind, HashChain};
use crate::store::segment;
use crate::store::StoreError;

impl Reader {
    pub(super) fn checked_frame_len(segment_id: u64, length: u32) -> Result<usize, StoreError> {
        let frame_len = usize::try_from(length).map_err(|_| {
            StoreError::corrupt_segment_with_detail(
                segment_id,
                "stored frame length does not fit in usize",
            )
        })?;
        let max_frame_len = FRAME_HEADER_BYTES + segment::MAX_FRAME_PAYLOAD;
        if frame_len < FRAME_HEADER_BYTES || frame_len > max_frame_len {
            return Err(StoreError::corrupt_segment_with_detail(
                segment_id,
                format!(
                    "stored frame length {frame_len} is outside valid range [{FRAME_HEADER_BYTES}, {max_frame_len}]"
                ),
            ));
        }
        Ok(frame_len)
    }

    pub(super) fn checked_frame_range(
        segment_id: u64,
        offset: u64,
        length: u32,
        available_len: usize,
    ) -> Result<std::ops::Range<usize>, StoreError> {
        let start = usize::try_from(offset).map_err(|_| StoreError::corrupt_eof(segment_id))?;
        let frame_len = Self::checked_frame_len(segment_id, length)?;
        let end = start.checked_add(frame_len).ok_or_else(|| {
            StoreError::corrupt_segment_with_detail(segment_id, "frame offset overflow")
        })?;
        if end > available_len {
            return Err(StoreError::corrupt_eof(segment_id));
        }
        Ok(start..end)
    }

    pub(super) fn payload_len_exceeds_max(payload_len: usize) -> bool {
        payload_len > segment::MAX_FRAME_PAYLOAD
    }

    pub(super) fn checked_batch_count(
        segment_id: u64,
        offset: u64,
        batch_count: u32,
    ) -> Result<u32, StoreError> {
        if batch_count == 0 || batch_count > MAX_BATCH_RECOVERY_ITEMS {
            return Err(StoreError::corrupt_segment_with_detail(
                segment_id,
                format!(
                    "invalid batch marker count {batch_count} at offset {offset}; expected 1..={MAX_BATCH_RECOVERY_ITEMS}"
                ),
            ));
        }
        Ok(batch_count)
    }

    pub(super) fn required_index_hash_chain(
        event: &IndexScanEvent,
        segment_id: u64,
        offset: u64,
    ) -> Result<HashChain, StoreError> {
        match &event.hash_chain {
            Some(chain) => Ok(chain.clone()),
            None if matches!(
                event.header.event_kind,
                EventKind::SYSTEM_BATCH_BEGIN | EventKind::SYSTEM_BATCH_COMMIT
            ) =>
            {
                Err(StoreError::corrupt_segment_with_detail(
                    segment_id,
                    format!(
                        "batch marker at offset {offset} should not reach hash-chain validation"
                    ),
                ))
            }
            None => Err(StoreError::corrupt_segment_with_detail(
                segment_id,
                format!(
                    "event at offset {offset} is missing hash_chain; recovery no longer defaults missing hashes"
                ),
            )),
        }
    }
}