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;