Skip to main content

decdn_bao_range/
ranged_store.rs

1//! The `(hash, total_bytes)` blob view every range-aware backend implements.
2//!
3//! [`RangedStore`] is what keeps this crate iroh-blobs-free: it speaks byte
4//! offsets and `ChunkRanges` only, so the node's iroh-blobs cache and the
5//! CLI's `.partial` sidecar can both satisfy it (#578).
6
7/// A boxed future returned by a [`RangedStore`] method, borrowing the store
8/// for `'a`.
9pub type RangedFuture<'a, T> =
10    core::pin::Pin<Box<dyn core::future::Future<Output = Result<T, RangedStoreError>> + Send + 'a>>;
11
12/// What a [`RangedStore`] operation can fail with.
13///
14/// A variant that wraps a cause names only itself in `Display` and returns the
15/// cause from `source()`, so a chain renderer (`{:#}` or `{:?}` on an
16/// `anyhow::Error`, or a `source()` walk) prints each cause once. Render it
17/// through one, not plain `Display`.
18#[derive(Debug, thiserror::Error)]
19pub enum RangedStoreError {
20    /// The requested range is not chunk-group aligned, or does not verify
21    /// against the outboard.
22    #[error("range alignment")]
23    Alignment(#[from] crate::RangeVerifyError),
24    /// Finalization was asked for while ranges are still missing.
25    #[error("blob is incomplete: cannot finalize")]
26    Incomplete,
27    /// The underlying store (file, blob store) failed.
28    #[error("backend")]
29    Backend(#[source] Box<dyn std::error::Error + Send + Sync>),
30}
31
32/// A source-agnostic, range-aware view of ONE blob `(hash, total_bytes)`.
33/// Speaks `ChunkRanges` + byte offsets only — no hash type — so `bao-range`
34/// stays iroh-blobs-free (#578). Both the node (iroh-blobs cache) and the
35/// client (`.partial` sidecar) backends implement it and pass one shared
36/// conformance suite.
37pub trait RangedStore: Send + Sync {
38    /// The blob length in bytes the store currently works to. A backend keyed
39    /// by size fixes it at construction. A backend keyed by offset reports its
40    /// current bound, which becomes the proven size once a verified final
41    /// chunk proves one.
42    fn total_bytes(&self) -> u64;
43    /// Chunk ranges that verify right now.
44    fn present_ranges(&self) -> RangedFuture<'_, bao_tree::ChunkRanges>;
45    /// `[byte_offset, byte_offset+byte_len)` (`byte_len == 0` ⇒ to end) minus what is present.
46    fn missing_ranges(
47        &self,
48        byte_offset: u64,
49        byte_len: u64,
50    ) -> RangedFuture<'_, bao_tree::ChunkRanges>;
51    /// Verify the interleaved `bao_bytes` for `range` against the root in
52    /// transit, write data + proof, record the range. Idempotent per range.
53    fn admit(&self, range: crate::AlignedRange, bao_bytes: bytes::Bytes) -> RangedFuture<'_, ()>;
54    /// Plaintext bytes for the held `[byte_offset, byte_offset+byte_len)` span.
55    /// The returned span is collected into `Bytes`, so callers (e.g. a
56    /// future gap-driven driver) should request gap-sized spans rather than
57    /// whole blobs to stay O(gap) in memory.
58    fn read(&self, byte_offset: u64, byte_len: u64) -> RangedFuture<'_, bytes::Bytes>;
59    /// The whole blob is present.
60    fn is_complete(&self) -> RangedFuture<'_, bool>;
61    /// Whole-blob wrap-up: verify complete (+ promote/materialize on backends
62    /// that need it). Errors `Incomplete` if any range is still missing.
63    fn finalize(&self) -> RangedFuture<'_, ()>;
64}
65
66#[cfg(test)]
67mod tests;