Skip to main content

krabka_remote_storage/
index.rs

1//! Decoders for the Kafka-format index and log bytes that a remote segment
2//! carries.
3//!
4//! The copy path writes a segment's `.index`, `.timeindex`, and `.txnindex`
5//! verbatim, so these decoders mirror the on-disk layouts that
6//! `krabka_log::index::{OffsetIndex, TimeIndex}` and
7//! `krabka_log::txn_index::TxnIndex` wrote locally.
8//!
9//! Every decoder here returns a [`RemoteStorageError`] on malformed input and
10//! never panics. The bytes arrive from an object store, which can return
11//! truncated or corrupt data, so a panic would be a denial-of-service surface
12//! on the read path.
13//!
14//! One submodule per on-disk artifact: `offset` for the `.index` layout and
15//! the byte range a fetch derives from it, `time` for the `.timeindex` layout
16//! and its timestamp floor, `txn` for the `.txnindex` layout and its
17//! range-overlap test, and `batch` for the scans over the `.log` bytes those
18//! sparse indexes can only point near. The offset vocabulary and the two error
19//! constructors they share stay here.
20
21use {
22    crate::error::RemoteStorageError,
23    std::{
24        fmt,
25        io::{self, ErrorKind},
26    },
27};
28
29mod batch;
30mod offset;
31mod time;
32mod txn;
33
34pub use self::{
35    batch::{first_batch_at_or_after, first_record_at_or_after_timestamp},
36    offset::{
37        OffsetIndexEntry, end_position_for, parse_offset_index, position_for_relative_offset,
38    },
39    time::{TimeIndexEntry, parse_time_index, relative_offset_floor_for_timestamp},
40    txn::{AbortedTxnIndexEntry, parse_txn_index, txn_overlaps},
41};
42
43/// Absolute (partition-level) log offset.
44pub type LogOffset = i64;
45/// Record timestamp in milliseconds since the Unix epoch.
46pub type TimestampMs = i64;
47/// Offset relative to a segment's base offset. This is the offset-index key.
48pub type RelativeOffset = u32;
49/// Byte position within a segment's `.log` file. This is the offset-index
50/// value.
51pub type BytePosition = u32;
52
53/// Helper for the `ref_from_bytes` parse error on the remote-read path.
54///
55/// The `zerocopy` cast can fail only on a length mismatch. The bytes come from
56/// the object store, such as S3, which can return corrupt or truncated data.
57/// This helper therefore returns a `RemoteStorageError` instead of a panic,
58/// because a panic would be a `DoS` surface.
59fn corrupt_index(kind: &str) -> RemoteStorageError {
60    RemoteStorageError::Io(io::Error::new(
61        ErrorKind::InvalidData,
62        format!("corrupt remote {kind} index bytes"),
63    ))
64}
65
66/// Wraps a decode failure on a remote segment's `.log` bytes.
67#[must_use]
68pub fn corrupt_log(detail: impl fmt::Display) -> RemoteStorageError {
69    RemoteStorageError::Io(io::Error::new(
70        ErrorKind::InvalidData,
71        format!("corrupt remote log bytes: {detail}"),
72    ))
73}