Skip to main content

nodedb_codec/fastlanes/
range.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Random-access and range operations over an encoded FastLanes frame.
4
5use std::mem::size_of;
6
7use crate::bounds::{checked_add, checked_capacity, checked_mul, decoded_len};
8use crate::error::CodecError;
9
10use super::block::{decode_block, skip_block};
11use super::header::{GLOBAL_HEADER_SIZE, expected_block_count, parse_header, validate_frame};
12
13/// Compute byte offsets for each block in an encoded stream.
14///
15/// Returns a Vec of byte offsets — `offsets[i]` is the start position of
16/// block `i` within `data`. O(num_blocks) header scan, no decompression.
17pub fn block_byte_offsets(data: &[u8]) -> Result<Vec<usize>, CodecError> {
18    if data.len() < GLOBAL_HEADER_SIZE {
19        return Err(CodecError::Truncated {
20            expected: GLOBAL_HEADER_SIZE,
21            actual: data.len(),
22        });
23    }
24    let (total_count, num_blocks) = parse_header(data)?;
25    let offset_capacity = checked_capacity(num_blocks, size_of::<usize>(), "FastLanes offsets")?;
26    let mut offsets = Vec::with_capacity(offset_capacity);
27    let mut pos = GLOBAL_HEADER_SIZE;
28    for i in 0..num_blocks {
29        offsets.push(pos);
30        pos = skip_block(data, pos, i, expected_block_count(total_count, i)?)?;
31    }
32    if pos != data.len() {
33        return Err(CodecError::Corrupt {
34            detail: "trailing bytes after FastLanes frame".into(),
35        });
36    }
37    Ok(offsets)
38}
39
40/// Decode a range of blocks [start_block..end_block) from encoded data.
41///
42/// More efficient than calling `decode_single_block` repeatedly — scans
43/// headers once to find start_block, then decodes contiguously.
44pub fn decode_block_range(
45    data: &[u8],
46    start_block: usize,
47    end_block: usize,
48) -> Result<Vec<i64>, CodecError> {
49    if data.len() < GLOBAL_HEADER_SIZE {
50        return Err(CodecError::Truncated {
51            expected: GLOBAL_HEADER_SIZE,
52            actual: data.len(),
53        });
54    }
55    let (total_count, num_blocks) = parse_header(data)?;
56    validate_frame(data, total_count, num_blocks)?;
57    if start_block >= num_blocks || end_block > num_blocks || start_block >= end_block {
58        return Ok(Vec::new());
59    }
60
61    // Skip to start_block.
62    let mut offset = GLOBAL_HEADER_SIZE;
63    for i in 0..start_block {
64        offset = skip_block(data, offset, i, expected_block_count(total_count, i)?)?;
65    }
66
67    let selected_count = (start_block..end_block).try_fold(0usize, |count, index| {
68        checked_add(
69            count,
70            expected_block_count(total_count, index)?,
71            "FastLanes range count",
72        )
73    })?;
74    let selected_bytes = checked_mul(selected_count, size_of::<i64>(), "FastLanes range bytes")?;
75    decoded_len(selected_bytes, "FastLanes range")?;
76    let selected_capacity = checked_capacity(selected_count, size_of::<i64>(), "FastLanes range")?;
77    let mut values = Vec::with_capacity(selected_capacity);
78    for i in start_block..end_block {
79        offset = decode_block(
80            data,
81            offset,
82            &mut values,
83            i,
84            expected_block_count(total_count, i)?,
85        )?;
86    }
87    Ok(values)
88}
89
90/// Number of blocks in an encoded FastLanes stream.
91pub fn block_count(data: &[u8]) -> Result<usize, CodecError> {
92    if data.len() < GLOBAL_HEADER_SIZE {
93        return Err(CodecError::Truncated {
94            expected: GLOBAL_HEADER_SIZE,
95            actual: data.len(),
96        });
97    }
98    Ok(parse_header(data)?.1)
99}
100
101/// Decode a single block by index without decoding the entire stream.
102///
103/// Iterates block headers to reach `block_idx`, then decodes only that
104/// block. For sequential block-at-a-time processing, prefer
105/// [`super::iterator::BlockIterator`] which tracks byte offsets without
106/// re-scanning.
107pub fn decode_single_block(data: &[u8], block_idx: usize) -> Result<Vec<i64>, CodecError> {
108    if data.len() < GLOBAL_HEADER_SIZE {
109        return Err(CodecError::Truncated {
110            expected: GLOBAL_HEADER_SIZE,
111            actual: data.len(),
112        });
113    }
114    let (total_count, num_blocks) = parse_header(data)?;
115    validate_frame(data, total_count, num_blocks)?;
116    if block_idx >= num_blocks {
117        return Err(CodecError::Corrupt {
118            detail: format!("block_idx {block_idx} >= block_count {num_blocks}"),
119        });
120    }
121
122    // Skip to the target block by iterating headers.
123    let mut offset = GLOBAL_HEADER_SIZE;
124    for i in 0..block_idx {
125        offset = skip_block(data, offset, i, expected_block_count(total_count, i)?)?;
126    }
127
128    let expected_count = expected_block_count(total_count, block_idx)?;
129    let value_capacity = checked_capacity(expected_count, size_of::<i64>(), "FastLanes block")?;
130    let mut values = Vec::with_capacity(value_capacity);
131    decode_block(data, offset, &mut values, block_idx, expected_count)?;
132    Ok(values)
133}