use super::{
TRAILER_START_MARKER, binary_index::Reader as BinaryIndexReader,
hash_index::Reader as HashIndexReader,
};
use crate::io::{LittleEndian, ReadBytesExt};
use crate::{
SeqNo, Slice,
table::{Block, block::Trailer},
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::marker::PhantomData;
use crate::io::Cursor;
macro_rules! read_leb128 {
($buf:expr, $pos:expr) => {{
let buf: &[u8] = $buf;
let start: usize = $pos;
let first = *buf.get(start)?;
if first < 0x80 {
(u64::from(first), start + 1)
} else {
let mut result = u64::from(first & 0x7f);
let mut shift = 7u32;
let mut p = start + 1;
loop {
let byte = *buf.get(p)?;
p += 1;
if shift == 63 && (byte & 0x7e) != 0 {
return None;
}
result |= u64::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
break (result, p);
}
shift += 7;
if shift >= 64 {
return None;
}
}
}
}};
}
pub(crate) use read_leb128;
#[cfg(test)]
fn read_leb128_fn(buf: &[u8], pos: usize) -> Option<(u64, usize)> {
Some(read_leb128!(buf, pos))
}
fn validate_trailer_fields(restart_interval: u8, binary_index_step_size: u8) -> crate::Result<()> {
if restart_interval == 0 {
return Err(crate::Error::InvalidTrailer);
}
if binary_index_step_size != 2 && binary_index_step_size != 4 {
return Err(crate::Error::InvalidTrailer);
}
Ok(())
}
pub trait ParsedItem<M> {
fn compare_key(
&self,
needle: &[u8],
bytes: &[u8],
cmp: &dyn crate::comparator::UserComparator,
) -> core::cmp::Ordering;
fn seqno(&self) -> SeqNo;
fn key_offset(&self) -> usize;
fn key_end_offset(&self) -> usize;
fn materialize(&self, bytes: &Slice) -> M;
}
pub trait Decodable<ParsedItem> {
fn parse_restart_key<'a>(
reader: &mut Cursor<&[u8]>,
offset: usize,
data: &'a [u8],
entries_end: usize,
) -> Option<(&'a [u8], SeqNo)>;
fn parse_full(
reader: &mut Cursor<&[u8]>,
offset: usize,
entries_end: usize,
) -> Option<ParsedItem>;
fn parse_truncated(
reader: &mut Cursor<&[u8]>,
offset: usize,
base_key_offset: usize,
base_key_end: usize,
entries_end: usize,
) -> Option<ParsedItem>;
}
#[derive(Debug)]
struct LoScanner {
offset: usize,
remaining_in_interval: usize,
base_key_offset: Option<usize>,
base_key_end: Option<usize>,
}
#[derive(Debug)]
struct HiScanner {
offset: usize,
ptr_idx: usize,
stack: Vec<usize>, base_key_offset: Option<usize>,
base_key_end: Option<usize>,
}
pub struct Decoder<'a, Item: Decodable<Parsed>, Parsed: ParsedItem<Item>> {
block: &'a Block,
phantom: PhantomData<(Item, Parsed)>,
lo_scanner: LoScanner,
hi_scanner: HiScanner,
restart_interval: u8,
binary_index_step_size: u8,
binary_index_offset: u32,
binary_index_len: u32,
hash_index_len: u32,
hash_index_offset: u32,
cached_entries_end: Option<usize>,
}
#[derive(Clone, Copy, Debug)]
pub struct DecoderMeta {
restart_interval: u8,
binary_index_step_size: u8,
binary_index_offset: u32,
binary_index_len: u32,
hash_index_len: u32,
hash_index_offset: u32,
cached_entries_end: usize,
}
impl<'a, Item: Decodable<Parsed>, Parsed: ParsedItem<Item>> Decoder<'a, Item, Parsed> {
#[must_use]
pub fn restart_interval(&self) -> u8 {
self.restart_interval
}
#[cfg(all(test, feature = "zstd"))]
#[must_use]
pub(crate) fn entries_end_for_test(&self) -> Option<usize> {
self.cached_entries_end
}
#[inline]
fn entries(&self) -> &'a [u8] {
&self.block.data
}
#[must_use]
#[expect(
clippy::expect_used,
reason = "try_new/new always populate cached_entries_end before returning"
)]
pub fn meta(&self) -> DecoderMeta {
DecoderMeta {
restart_interval: self.restart_interval,
binary_index_step_size: self.binary_index_step_size,
binary_index_offset: self.binary_index_offset,
binary_index_len: self.binary_index_len,
hash_index_len: self.hash_index_len,
hash_index_offset: self.hash_index_offset,
cached_entries_end: self
.cached_entries_end
.expect("cached_entries_end populated by constructor"),
}
}
#[must_use]
pub fn from_meta(block: &'a Block, meta: DecoderMeta) -> Self {
Self {
block,
phantom: PhantomData,
lo_scanner: LoScanner {
offset: 0,
remaining_in_interval: 0,
base_key_offset: None,
base_key_end: None,
},
hi_scanner: HiScanner {
offset: 0,
ptr_idx: usize::try_from(meta.binary_index_len).unwrap_or(usize::MAX),
stack: Vec::new(),
base_key_offset: None,
base_key_end: None,
},
restart_interval: meta.restart_interval,
binary_index_step_size: meta.binary_index_step_size,
binary_index_offset: meta.binary_index_offset,
binary_index_len: meta.binary_index_len,
hash_index_len: meta.hash_index_len,
hash_index_offset: meta.hash_index_offset,
cached_entries_end: Some(meta.cached_entries_end),
}
}
pub fn try_new(block: &'a Block) -> crate::Result<Self> {
let trailer = Trailer::try_new(block)?;
let mut reader = trailer.as_slice();
let restart_interval = reader.read_u8().map_err(|_| crate::Error::InvalidTrailer)?;
let binary_index_step_size = reader.read_u8().map_err(|_| crate::Error::InvalidTrailer)?;
validate_trailer_fields(restart_interval, binary_index_step_size)?;
let binary_index_len = reader
.read_u32::<LittleEndian>()
.map_err(|_| crate::Error::InvalidTrailer)?;
let binary_index_offset = reader
.read_u32::<LittleEndian>()
.map_err(|_| crate::Error::InvalidTrailer)?;
let hash_index_len = reader
.read_u32::<LittleEndian>()
.map_err(|_| crate::Error::InvalidTrailer)?;
let hash_index_offset = reader
.read_u32::<LittleEndian>()
.map_err(|_| crate::Error::InvalidTrailer)?;
let mut decoder = Self {
block,
phantom: PhantomData,
lo_scanner: LoScanner {
offset: 0,
remaining_in_interval: 0,
base_key_offset: None,
base_key_end: None,
},
hi_scanner: HiScanner {
offset: 0,
ptr_idx: usize::try_from(binary_index_len).unwrap_or(usize::MAX),
stack: Vec::new(),
base_key_offset: None,
base_key_end: None,
},
restart_interval,
binary_index_step_size,
binary_index_offset,
binary_index_len,
hash_index_len,
hash_index_offset,
cached_entries_end: None,
};
decoder.cached_entries_end = Some(
decoder
.compute_entries_end()
.ok_or(crate::Error::InvalidTrailer)?,
);
Ok(decoder)
}
#[must_use]
#[expect(
clippy::expect_used,
reason = "infallible wrapper for test/non-I/O paths"
)]
pub fn new(block: &'a Block) -> Self {
Self::try_new(block).expect("valid block trailer")
}
#[cfg(feature = "zstd")]
#[must_use]
pub(crate) fn new_forward_headerless(
block: &'a Block,
restart_interval: u8,
entries_end: usize,
) -> Self {
assert!(
restart_interval > 0,
"restart_interval must be non-zero for headerless forward decode",
);
Self {
block,
phantom: PhantomData,
lo_scanner: LoScanner {
offset: 0,
remaining_in_interval: 0,
base_key_offset: None,
base_key_end: None,
},
hi_scanner: HiScanner {
offset: 0,
ptr_idx: 0,
stack: Vec::new(),
base_key_offset: None,
base_key_end: None,
},
restart_interval,
binary_index_step_size: 2,
binary_index_offset: 0,
binary_index_len: 0,
hash_index_len: 0,
hash_index_offset: 0,
cached_entries_end: Some(entries_end),
}
}
#[cfg(feature = "zstd")]
pub(crate) fn scan_restart_offsets(mut self) -> (Vec<u32>, usize, usize) {
let mut restart_offsets = Vec::new();
let mut item_count = 0usize;
let mut last_complete_end = 0usize;
loop {
let is_restart = self.lo_scanner.remaining_in_interval == 0;
let head = self.lo_scanner.offset;
if self.next().is_none() {
break;
}
if is_restart {
#[expect(
clippy::cast_possible_truncation,
reason = "block offsets are far below u32::MAX"
)]
restart_offsets.push(head as u32);
}
item_count += 1;
last_complete_end = self.lo_scanner.offset;
}
(restart_offsets, item_count, last_complete_end)
}
fn binary_index_bounds(&self) -> Option<(usize, usize)> {
let step_size = match self.binary_index_step_size {
2 | 4 => usize::from(self.binary_index_step_size),
_ => return None,
};
let binary_index_len = usize::try_from(self.binary_index_len).ok()?;
if binary_index_len == 0 {
return None;
}
let binary_index_bytes = binary_index_len.checked_mul(step_size)?;
let binary_index_offset = usize::try_from(self.binary_index_offset).ok()?;
let binary_index_end = binary_index_offset.checked_add(binary_index_bytes)?;
let trailer_offset = Trailer::new(self.block).trailer_offset();
let hash_index_len = usize::try_from(self.hash_index_len).ok()?;
let hash_index_offset = usize::try_from(self.hash_index_offset).ok()?;
if (hash_index_len == 0) != (hash_index_offset == 0) {
return None;
}
if hash_index_offset > trailer_offset {
return None;
}
if hash_index_offset > 0 {
let hash_index_end = hash_index_offset.checked_add(hash_index_len)?;
if hash_index_end != trailer_offset {
return None;
}
}
let binary_index_limit = if hash_index_offset > 0 {
hash_index_offset
} else {
trailer_offset
};
if binary_index_offset == 0 || binary_index_end != binary_index_limit {
return None;
}
Some((binary_index_offset, binary_index_end))
}
fn get_binary_index_reader(&self) -> Option<BinaryIndexReader<'_>> {
let (binary_index_offset, _) = self.binary_index_bounds()?;
if self.block.data.get(binary_index_offset - 1).copied()? != TRAILER_START_MARKER {
return None;
}
Some(BinaryIndexReader::new(
&self.block.data,
self.binary_index_offset,
self.binary_index_len,
self.binary_index_step_size,
))
}
pub(crate) fn cached_binary_index_reader(&self) -> Option<BinaryIndexReader<'_>> {
self.get_binary_index_reader()
}
pub(crate) fn cached_hash_index_reader(&self) -> Option<HashIndexReader<'_>> {
if self.hash_index_len == 0 {
return None;
}
Some(HashIndexReader::new(
&self.block.data,
self.hash_index_offset,
self.hash_index_len,
))
}
fn reader_at(data: &[u8], offset: usize) -> Option<Cursor<&[u8]>> {
if offset >= data.len() {
return None;
}
Some(Cursor::new(data.get(offset..)?))
}
fn get_key_at(&self, pos: usize, entries_end: usize) -> Option<(&[u8], SeqNo)> {
if pos >= entries_end {
return None;
}
let bytes = self.entries();
let mut cursor = Self::reader_at(bytes, pos)?;
Item::parse_restart_key(&mut cursor, pos, bytes, entries_end)
}
fn partition_point<F>(&self, pred: F) -> Option<(/* offset */ usize, /* idx */ usize)>
where
F: Fn(&[u8], SeqNo) -> bool,
{
let binary_index = self.get_binary_index_reader()?;
let entries_end = self.entries_end()?;
debug_assert!(
binary_index.len() >= 1,
"binary index should never be empty",
);
let mut left: usize = 0;
let mut right = binary_index.len();
if right == 0 {
return None;
}
while left < right {
let mid = usize::midpoint(left, right);
let offset = binary_index.get(mid);
let (head_key, head_seqno) = self.get_key_at(offset, entries_end)?;
if pred(head_key, head_seqno) {
left = mid + 1;
} else {
right = mid;
}
}
if left == 0 {
return Some((0, 0));
}
if left == binary_index.len() {
let idx = binary_index.len() - 1;
let offset = binary_index.get(idx);
return Some((offset, idx));
}
let offset = binary_index.get(left - 1);
Some((offset, left - 1))
}
fn partition_point_2<F>(&self, pred: F) -> Option<(/* offset */ usize, /* idx */ usize)>
where
F: Fn(&[u8], SeqNo) -> bool,
{
let binary_index = self.get_binary_index_reader()?;
let entries_end = self.entries_end()?;
debug_assert!(
binary_index.len() >= 1,
"binary index should never be empty",
);
let mut left: usize = 0;
let mut right = binary_index.len();
if right == 0 {
return None;
}
while left < right {
let mid = usize::midpoint(left, right);
let offset = binary_index.get(mid);
let (head_key, head_seqno) = self.get_key_at(offset, entries_end)?;
if pred(head_key, head_seqno) {
left = mid + 1;
} else {
right = mid;
}
}
if left == binary_index.len() {
let idx = binary_index.len() - 1;
let offset = binary_index.get(idx);
return Some((offset, idx));
}
let offset = binary_index.get(left);
Some((offset, left))
}
pub fn set_lo_offset(&mut self, offset: usize) {
self.lo_scanner.offset = offset;
}
pub fn reset_back_cursor(&mut self) {
self.hi_scanner.ptr_idx = usize::try_from(self.binary_index_len).unwrap_or(usize::MAX);
self.hi_scanner.stack.clear();
self.hi_scanner.base_key_offset = None;
self.hi_scanner.base_key_end = None;
}
fn poison_back_cursor(&mut self) {
self.clamp_upper_to_lo();
}
fn clamp_upper_to_lo(&mut self) {
self.hi_scanner.offset = self.lo_scanner.offset;
self.hi_scanner.ptr_idx = usize::MAX;
self.hi_scanner.stack.clear();
self.hi_scanner.base_key_offset = Some(0);
self.hi_scanner.base_key_end = Some(0);
}
fn exhaust(&mut self) {
let end = self.entries().len();
self.lo_scanner.offset = end;
self.lo_scanner.remaining_in_interval = 0;
self.lo_scanner.base_key_offset = None;
self.lo_scanner.base_key_end = None;
self.hi_scanner.offset = end;
self.hi_scanner.ptr_idx = usize::MAX;
self.hi_scanner.stack.clear();
self.hi_scanner.base_key_offset = Some(0);
self.hi_scanner.base_key_end = Some(0);
}
#[must_use]
pub fn seek(&mut self, pred: impl Fn(&[u8], SeqNo) -> bool, second_partition: bool) -> bool {
let use_next_restart = second_partition && self.restart_interval == 1;
let result = if use_next_restart {
self.partition_point_2(&pred)
} else {
self.partition_point(&pred)
};
let Some((offset, _)) = result else {
self.exhaust();
return false;
};
if use_next_restart
&& self
.entries_end()
.and_then(|entries_end| self.get_key_at(offset, entries_end))
.is_some_and(|(key, seqno)| pred(key, seqno))
{
self.exhaust();
return false;
}
self.lo_scanner.offset = offset;
self.lo_scanner.remaining_in_interval = 0;
self.lo_scanner.base_key_offset = None;
self.lo_scanner.base_key_end = None;
true
}
#[must_use]
pub fn seek_upper(
&mut self,
pred: impl Fn(&[u8], SeqNo) -> bool,
second_partition: bool,
) -> bool {
let use_next_restart = second_partition && self.restart_interval == 1;
let result = if use_next_restart {
self.partition_point_2(&pred)
} else {
self.partition_point(&pred)
};
let Some((offset, idx)) = result else {
self.exhaust();
return false;
};
self.hi_scanner.offset = offset;
self.hi_scanner.ptr_idx = idx;
self.hi_scanner.stack.clear();
self.hi_scanner.base_key_offset = None;
self.hi_scanner.base_key_end = None;
self.fill_stack();
if self.hi_scanner.stack.is_empty() {
self.clamp_upper_to_lo();
return false;
}
true
}
fn parse_current_item(
reader: &mut Cursor<&[u8]>,
offset: usize,
base_key_offset: Option<usize>,
base_key_end: Option<usize>,
is_restart: bool,
entries_end: usize,
) -> Option<Parsed> {
if is_restart {
Item::parse_full(reader, offset, entries_end)
} else {
#[expect(clippy::expect_used, reason = "we trust the is_restart flag")]
Item::parse_truncated(
reader,
offset,
base_key_offset.expect("should parse truncated item"),
base_key_end.expect("should parse truncated item"),
entries_end,
)
}
}
fn compute_entries_end(&self) -> Option<usize> {
let (binary_index_offset, _) = self.binary_index_bounds()?;
if self.block.data.get(binary_index_offset - 1).copied()? != TRAILER_START_MARKER {
return None;
}
Some(binary_index_offset - 1)
}
fn entries_end(&self) -> Option<usize> {
self.cached_entries_end
}
fn fill_stack(&mut self) {
self.hi_scanner.stack.clear();
self.hi_scanner.base_key_offset = None;
self.hi_scanner.base_key_end = None;
let Some(entries_end) = self.entries_end() else {
self.poison_back_cursor();
return;
};
let Some(binary_index) = self.get_binary_index_reader() else {
self.poison_back_cursor();
return;
};
if self.hi_scanner.ptr_idx >= binary_index.len() {
return;
}
{
self.hi_scanner.offset = binary_index.get(self.hi_scanner.ptr_idx);
let offset = self.hi_scanner.offset;
let Some(mut reader) = Self::reader_at(self.entries(), offset) else {
self.poison_back_cursor();
return;
};
#[expect(
clippy::cast_possible_truncation,
reason = "blocks do not even come close to 4 GiB in size"
)]
let parsed_restart =
Item::parse_full(&mut reader, offset, entries_end).inspect(|item| {
self.hi_scanner.offset += reader.position() as usize;
self.hi_scanner.base_key_offset = Some(item.key_offset());
self.hi_scanner.base_key_end = Some(item.key_end_offset());
});
if parsed_restart.is_some() {
self.hi_scanner.stack.push(offset);
} else {
self.poison_back_cursor();
return;
}
}
for _ in 1..self.restart_interval {
let offset = self.hi_scanner.offset;
let Some(mut reader) = Self::reader_at(self.entries(), offset) else {
self.poison_back_cursor();
return;
};
#[expect(clippy::expect_used, reason = "base key offset is expected to exist")]
#[expect(
clippy::cast_possible_truncation,
reason = "blocks do not even come close to 4 GiB in size"
)]
if Item::parse_truncated(
&mut reader,
offset,
self.hi_scanner.base_key_offset.expect("should exist"),
self.hi_scanner.base_key_end.expect("should exist"),
entries_end,
)
.inspect(|_| {
self.hi_scanner.offset += reader.position() as usize;
})
.is_some()
{
self.hi_scanner.stack.push(offset);
} else {
if offset < entries_end {
self.poison_back_cursor();
return;
}
break;
}
}
}
fn consume_stack_top(&mut self) -> Option<Parsed> {
let offset = self.hi_scanner.stack.pop()?;
let entries_end = self.entries_end()?;
if self.lo_scanner.offset > 0 && offset < self.lo_scanner.offset {
return None;
}
self.hi_scanner.offset = offset;
let is_restart = self.hi_scanner.stack.is_empty();
let mut reader = Self::reader_at(self.entries(), offset)?;
Self::parse_current_item(
&mut reader,
offset,
self.hi_scanner.base_key_offset,
self.hi_scanner.base_key_end,
is_restart,
entries_end,
)
}
pub fn advance_while(&mut self, pred: impl Fn(&Parsed, &[u8]) -> bool) {
let Some(entries_end) = self.entries_end() else {
return;
};
loop {
let hi_offset = if self.hi_scanner.base_key_offset.is_some() {
Some(self.hi_scanner.offset)
} else {
None
};
if hi_offset.is_some_and(|hi| self.lo_scanner.offset >= hi) {
break;
}
let is_restart = self.lo_scanner.remaining_in_interval == 0;
let Some(mut reader) = Self::reader_at(self.entries(), self.lo_scanner.offset) else {
break;
};
let Some(item) = Self::parse_current_item(
&mut reader,
self.lo_scanner.offset,
self.lo_scanner.base_key_offset,
self.lo_scanner.base_key_end,
is_restart,
entries_end,
) else {
break;
};
if !pred(&item, self.entries()) {
break;
}
#[expect(
clippy::cast_possible_truncation,
reason = "blocks do not even come close to 4 GiB in size"
)]
{
let Some(next_offset) = self
.lo_scanner
.offset
.checked_add(reader.position() as usize)
else {
break;
};
if hi_offset.is_some_and(|hi| next_offset > hi) {
break;
}
self.lo_scanner.offset = next_offset;
}
if is_restart {
self.lo_scanner.base_key_offset = Some(item.key_offset());
self.lo_scanner.base_key_end = Some(item.key_end_offset());
self.lo_scanner.remaining_in_interval = usize::from(self.restart_interval) - 1;
} else {
self.lo_scanner.remaining_in_interval -= 1;
}
}
}
pub fn trim_back_to_upper_bound(
&mut self,
cmp: impl Fn(&Parsed, &[u8]) -> core::cmp::Ordering,
) {
let Some(entries_end) = self.entries_end() else {
return;
};
let mut last_popped = None;
loop {
let Some(&offset) = self.hi_scanner.stack.last() else {
break;
};
let is_restart = self.hi_scanner.stack.len() == 1;
let Some(mut reader) = Self::reader_at(self.entries(), offset) else {
break;
};
let Some(item) = Self::parse_current_item(
&mut reader,
offset,
self.hi_scanner.base_key_offset,
self.hi_scanner.base_key_end,
is_restart,
entries_end,
) else {
break;
};
if cmp(&item, self.entries()) != core::cmp::Ordering::Greater {
break;
}
last_popped = Some(offset);
self.hi_scanner.stack.pop();
}
let Some(candidate_offset) = last_popped else {
return;
};
let should_restore = if let Some(&offset) = self.hi_scanner.stack.last() {
let is_restart = self.hi_scanner.stack.len() == 1;
let Some(mut reader) = Self::reader_at(self.entries(), offset) else {
return;
};
let Some(item) = Self::parse_current_item(
&mut reader,
offset,
self.hi_scanner.base_key_offset,
self.hi_scanner.base_key_end,
is_restart,
entries_end,
) else {
return;
};
cmp(&item, self.entries()) == core::cmp::Ordering::Less
} else {
true
};
if should_restore {
self.hi_scanner.stack.push(candidate_offset);
}
let Some(&offset) = self.hi_scanner.stack.last() else {
return;
};
let is_restart = self.hi_scanner.stack.len() == 1;
let Some(mut reader) = Self::reader_at(self.entries(), offset) else {
return;
};
#[expect(
clippy::cast_possible_truncation,
reason = "blocks do not even come close to 4 GiB in size"
)]
if Self::parse_current_item(
&mut reader,
offset,
self.hi_scanner.base_key_offset,
self.hi_scanner.base_key_end,
is_restart,
entries_end,
)
.is_some()
{
self.hi_scanner.offset = offset + reader.position() as usize;
}
}
#[must_use]
pub fn upper_stack_tail_cmp(
&self,
cmp: impl Fn(&Parsed, &[u8]) -> core::cmp::Ordering,
) -> Option<core::cmp::Ordering> {
let &offset = self.hi_scanner.stack.last()?;
let entries_end = self.entries_end()?;
let is_restart = self.hi_scanner.stack.len() == 1;
let mut reader = Self::reader_at(self.entries(), offset)?;
let item = Self::parse_current_item(
&mut reader,
offset,
self.hi_scanner.base_key_offset,
self.hi_scanner.base_key_end,
is_restart,
entries_end,
)?;
Some(cmp(&item, self.entries()))
}
#[must_use]
pub fn advance_upper_restart_interval(&mut self) -> bool {
let Some(next_idx) = self.hi_scanner.ptr_idx.checked_add(1) else {
return false;
};
let Ok(binary_index_len) = usize::try_from(self.binary_index_len) else {
return false;
};
if next_idx >= binary_index_len {
return false;
}
self.hi_scanner.ptr_idx = next_idx;
self.hi_scanner.stack.clear();
self.hi_scanner.base_key_offset = None;
self.hi_scanner.base_key_end = None;
self.fill_stack();
if self.hi_scanner.stack.is_empty() {
self.clamp_upper_to_lo();
return false;
}
true
}
}
impl<Item: Decodable<Parsed>, Parsed: ParsedItem<Item>> Iterator for Decoder<'_, Item, Parsed> {
type Item = Parsed;
fn next(&mut self) -> Option<Self::Item> {
let entries_end = self.entries_end()?;
if self.lo_scanner.offset >= self.entries().len() {
return None;
}
if self.hi_scanner.base_key_offset.is_some()
&& self.lo_scanner.offset >= self.hi_scanner.offset
{
return None;
}
let is_restart: bool = self.lo_scanner.remaining_in_interval == 0;
let mut reader = Cursor::new(self.entries().get(self.lo_scanner.offset..)?);
#[expect(
clippy::cast_possible_truncation,
reason = "blocks do not even come close to 4 GiB in size"
)]
let item = Self::parse_current_item(
&mut reader,
self.lo_scanner.offset,
self.lo_scanner.base_key_offset,
self.lo_scanner.base_key_end,
is_restart,
entries_end,
)
.inspect(|item| {
self.lo_scanner.offset += reader.position() as usize;
if is_restart {
self.lo_scanner.base_key_offset = Some(item.key_offset());
self.lo_scanner.base_key_end = Some(item.key_end_offset());
}
});
if item.is_some() {
if is_restart {
self.lo_scanner.remaining_in_interval = usize::from(self.restart_interval) - 1;
} else {
self.lo_scanner.remaining_in_interval -= 1;
}
} else {
self.lo_scanner.offset = self.entries().len();
self.lo_scanner.remaining_in_interval = 0;
self.lo_scanner.base_key_offset = None;
self.lo_scanner.base_key_end = None;
}
item
}
}
impl<Item: Decodable<Parsed>, Parsed: ParsedItem<Item>> DoubleEndedIterator
for Decoder<'_, Item, Parsed>
{
fn next_back(&mut self) -> Option<Self::Item> {
if let Some(top) = self.consume_stack_top() {
return Some(top);
}
if self.hi_scanner.ptr_idx == usize::MAX {
return None;
}
self.hi_scanner.ptr_idx = self.hi_scanner.ptr_idx.wrapping_sub(1);
if self.hi_scanner.ptr_idx == usize::MAX {
return None;
}
self.fill_stack();
self.consume_stack_top()
}
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::cast_possible_truncation,
reason = "corruption regression tests intentionally mutate encoded bytes and assert parser rejection paths"
)]
mod tests;