use crate::io::Cursor;
use crate::io::{ReadBytesExt, WriteBytesExt};
use crate::{SeqNo, UserKey};
use crate::{
coding::{Decode, Encode},
table::{
block::{BlockOffset, Decodable, Encodable, TRAILER_START_MARKER, decoder::read_leb128},
index_block::IndexBlockParsedItem,
util::SliceIndexes,
},
};
#[cfg(not(feature = "std"))]
use crate::io::Seek;
#[cfg(not(feature = "std"))]
use crate::io::{VarintReader, VarintWriter};
#[cfg(feature = "std")]
use std::io::Seek;
#[cfg(feature = "std")]
use varint_rs::{VarintReader, VarintWriter};
#[derive(Copy, Clone, Debug, Default)]
pub struct BlockHandle {
offset: BlockOffset,
size: u32,
}
impl BlockHandle {
#[must_use]
pub fn new(offset: BlockOffset, size: u32) -> Self {
Self { offset, size }
}
#[must_use]
pub fn size(&self) -> u32 {
self.size
}
#[must_use]
pub fn offset(&self) -> BlockOffset {
self.offset
}
}
impl Encode for BlockHandle {
fn encode_into<W: crate::io::Write>(&self, writer: &mut W) -> Result<(), crate::Error> {
writer.write_u64_varint(*self.offset)?;
writer.write_u32_varint(self.size)?;
Ok(())
}
}
impl Decode for BlockHandle {
fn decode_from<R: crate::io::Read>(reader: &mut R) -> Result<Self, crate::Error>
where
Self: Sized,
{
let offset = reader.read_u64_varint()?;
let size = reader.read_u32_varint()?;
Ok(Self {
offset: BlockOffset(offset),
size,
})
}
}
#[derive(Clone, Debug)]
pub struct KeyedBlockHandle {
end_key: UserKey,
seqno: SeqNo,
inner: BlockHandle,
}
impl AsRef<BlockHandle> for KeyedBlockHandle {
fn as_ref(&self) -> &BlockHandle {
&self.inner
}
}
impl KeyedBlockHandle {
#[must_use]
pub fn into_inner(self) -> BlockHandle {
self.inner
}
#[must_use]
pub fn new(end_key: UserKey, seqno: SeqNo, handle: BlockHandle) -> Self {
Self {
end_key,
seqno,
inner: handle,
}
}
#[must_use]
pub fn seqno(&self) -> SeqNo {
self.seqno
}
pub fn shift(&mut self, delta: BlockOffset) {
self.inner.offset += delta;
}
#[must_use]
pub fn size(&self) -> u32 {
self.inner.size()
}
#[must_use]
pub fn offset(&self) -> BlockOffset {
self.inner.offset()
}
#[must_use]
pub fn end_key(&self) -> &UserKey {
&self.end_key
}
}
#[cfg(test)]
impl PartialEq for KeyedBlockHandle {
fn eq(&self, other: &Self) -> bool {
self.offset() == other.offset()
}
}
impl Encodable<BlockOffset> for KeyedBlockHandle {
fn encode_full_into<W: crate::io::Write>(
&self,
writer: &mut W,
state: &mut BlockOffset,
) -> crate::Result<()> {
writer.write_u8(0)?;
self.inner.encode_into(writer)?;
writer.write_u64_varint(self.seqno)?;
#[expect(clippy::cast_possible_truncation, reason = "keys are u16 long max")]
writer.write_u16_varint(self.end_key.len() as u16)?; writer.write_all(&self.end_key)?;
*state = BlockOffset(*self.offset() + u64::from(self.size()));
Ok(())
}
fn encode_truncated_into<W: crate::io::Write>(
&self,
writer: &mut W,
_state: &mut BlockOffset,
shared_len: usize,
) -> crate::Result<()> {
writer.write_u8(1)?;
self.inner.encode_into(writer)?;
writer.write_u64_varint(self.seqno)?;
#[expect(clippy::cast_possible_truncation, reason = "keys are u16 long max")]
writer.write_u16_varint(shared_len as u16)?;
#[expect(
clippy::expect_used,
reason = "the shared len should not be greater than key length"
)]
let truncated_end_key = self.end_key.get(shared_len..).expect("should be in bounds");
let rest_len = truncated_end_key.len();
#[expect(clippy::cast_possible_truncation, reason = "keys are u16 long max")]
writer.write_u16_varint(rest_len as u16)?;
writer.write_all(truncated_end_key)?;
Ok(())
}
fn key(&self) -> &[u8] {
&self.end_key
}
}
impl Decodable<IndexBlockParsedItem> for KeyedBlockHandle {
fn parse_full(
reader: &mut Cursor<&[u8]>,
offset: usize,
entries_end: usize,
) -> Option<IndexBlockParsedItem> {
let marker = reader.read_u8().ok()?;
debug_assert!(
marker != 2 && marker != 3,
"stale inline seqno-bounds marker {marker} in index full entry (pre-release format, unsupported)"
);
if marker == TRAILER_START_MARKER {
return None;
}
if marker != 0 {
return None;
}
let handle = BlockHandle::decode_from(reader).ok()?;
let seqno = reader.read_u64_varint().ok()?;
let key_len: usize = reader.read_u16_varint().ok()?.into();
#[expect(
clippy::cast_possible_truncation,
reason = "blocks tend to be some megabytes in size at most, so position should fit into usize"
)]
let key_start = offset.checked_add(reader.position() as usize)?;
#[expect(
clippy::cast_possible_wrap,
reason = "key_len is bounded by u16::MAX, no wrap expected"
)]
let offset_i64 = key_len as i64;
if key_start > entries_end {
return None;
}
let key_end = key_start.checked_add(key_len)?;
if key_end > entries_end {
return None;
}
reader.seek_relative(offset_i64).ok()?;
Some(IndexBlockParsedItem {
prefix: None,
end_key: SliceIndexes(key_start, key_end),
offset: handle.offset(),
size: handle.size(),
seqno,
})
}
fn parse_restart_key<'a>(
reader: &mut Cursor<&[u8]>,
offset: usize,
data: &'a [u8],
entries_end: usize,
) -> Option<(&'a [u8], SeqNo)> {
let buf: &[u8] = reader.get_ref();
let mut pos = usize::try_from(reader.position()).ok()?;
let marker = *buf.get(pos)?;
pos += 1;
debug_assert!(
marker != 2 && marker != 3,
"stale inline seqno-bounds marker {marker} in index restart head (pre-release format, unsupported)"
);
if marker == TRAILER_START_MARKER {
return None;
}
if marker != 0 {
return None;
}
let (_file_offset, np) = read_leb128!(buf, pos);
pos = np;
let (size, np) = read_leb128!(buf, pos);
u32::try_from(size).ok()?;
pos = np;
let (seqno, np) = read_leb128!(buf, pos);
pos = np;
let (key_len_raw, np) = read_leb128!(buf, pos);
pos = np;
let key_len = usize::try_from(key_len_raw)
.ok()
.filter(|&k| u16::try_from(k).is_ok())?;
let key_start = offset.checked_add(pos)?;
let key_end = key_start.checked_add(key_len)?;
if key_end > entries_end {
return None;
}
pos = pos.checked_add(key_len)?;
let key = data.get(key_start..key_end)?;
reader.set_position(pos as u64);
Some((key, seqno))
}
fn parse_truncated(
reader: &mut Cursor<&[u8]>,
offset: usize,
base_key_offset: usize,
base_key_end: usize,
entries_end: usize,
) -> Option<IndexBlockParsedItem> {
let marker = reader.read_u8().ok()?;
debug_assert!(
marker != 2 && marker != 3,
"stale inline seqno-bounds marker {marker} in index truncated entry (pre-release format, unsupported)"
);
if marker == TRAILER_START_MARKER {
return None;
}
if marker != 1 {
return None;
}
let handle = BlockHandle::decode_from(reader).ok()?;
let seqno = reader.read_u64_varint().ok()?;
let shared_prefix_len: usize = reader.read_u16_varint().ok()?.into();
let rest_key_len: usize = reader.read_u16_varint().ok()?.into();
#[expect(
clippy::cast_possible_truncation,
reason = "blocks tend to be some megabytes in size at most, so position should fit into usize"
)]
let key_start = offset.checked_add(reader.position() as usize)?;
if key_start > entries_end {
return None;
}
let remaining_suffix_bytes = entries_end.checked_sub(key_start)?;
if rest_key_len > remaining_suffix_bytes {
return None;
}
if base_key_offset > offset {
return None;
}
if base_key_end < base_key_offset || base_key_end > offset {
return None;
}
let prefix_end = base_key_offset.checked_add(shared_prefix_len)?;
if prefix_end > base_key_end {
return None;
}
#[expect(
clippy::cast_possible_wrap,
reason = "rest_key_len is bounded by u16::MAX, no wrap expected"
)]
let rest_key_len_i64 = rest_key_len as i64;
reader.seek_relative(rest_key_len_i64).ok()?;
let end_key_end = key_start.checked_add(rest_key_len)?;
Some(IndexBlockParsedItem {
prefix: Some(SliceIndexes(base_key_offset, prefix_end)),
end_key: SliceIndexes(key_start, end_key_end),
offset: handle.offset(),
size: handle.size(),
seqno,
})
}
}
#[cfg(test)]
#[expect(clippy::unwrap_used, reason = "test code")]
mod tests;