Skip to main content

rfc9162_log/
store.rs

1//! The store behind [`AuthenticatedLog`]: sorted runs over [`rfc9162_tiles`],
2//! each run's manifest appended to a log so a signed checkpoint covers the
3//! index as well as the entries.
4//!
5//! Two layouts, one code path:
6//!
7//! - **Shared**: the manifests are entries of the data log itself. One tree,
8//!   one checkpoint signs entries and index. [`Store::open`], [`Index::at`].
9//! - **Split**: the manifests are entries of a second tile log, the index
10//!   log, each carrying the data tree's size and root it indexes. The data
11//!   log holds exactly the caller's entries, which a consumer that already
12//!   signs its data leaves needs; the index log has its own checkpoint under
13//!   the same key. [`Store::open_split`], [`Index::at_split`].
14//!
15//! Inside the store that holds the manifests (the data store when shared,
16//! the index store when split), beside the tiles:
17//!
18//! - `runs/<lo>-<hi>`: one sealed run, the sorted `(key, index)` pairs of
19//!   data entries `lo..=hi` and a Bloom filter over the keys, in the format
20//!   `RUN_MAGIC` describes. Its SHA-256 is the `run_hash` the logged manifest
21//!   carries, so a reader checks the file before trusting a seek. The writer
22//!   writes format v2: each key is stored as the length it shares with the
23//!   key before it and the bytes that follow (front coding), and each index
24//!   as a LEB128 varint offset from `lo`. Runs written as v1 (every key and
25//!   index in full) stay readable; the manifest binds the file's hash, not
26//!   its format.
27//! - `runs/live`: the manifest indices of the runs a lookup goes through, one
28//!   per line, oldest first. A cache: when it is missing or stale the live set
29//!   is rebuilt from the manifest entries in the log.
30//!
31//! A manifest entry starts with `MANIFEST_MAGIC`, carries no lookup keys and,
32//! when shared, lies in the entry range of the run sealed after it.
33//! Compaction logs a manifest that names the manifest indices it replaces;
34//! the replaced runs stay in the store and in the tree, and leave the live
35//! set.
36//!
37//! [`Store`] is the one writer and holds the writer locks. [`Index`] answers
38//! lookups at tree sizes and roots a caller trusts, without the locks, which
39//! is how a verifier that holds signed checkpoints reads the log.
40
41use std::collections::{BTreeSet, HashMap};
42use std::ops::RangeInclusive;
43use std::sync::{Arc, Mutex};
44
45use rfc9162_tiles::bloom::Bloom;
46use rfc9162_tiles::index::{identity, KeyFn};
47use rfc9162_tiles::log::{Reader, Writer};
48use rfc9162_tiles::store::TileStore;
49
50use crate::{AuthenticatedLog, Compaction, Error, Inclusion, Located, Recovery, RunManifest};
51
52/// First bytes of a logged run manifest.
53pub const MANIFEST_MAGIC: &[u8] = b"rfc9162-log/manifest/v2\n";
54/// First bytes of a run file in format v2 (front-coded, one sequence),
55/// which readers accept.
56pub const RUN_MAGIC: &[u8] = b"rfc9162-log/run/v2\n";
57/// First bytes of a run file as the writer writes it (format v3: front-coded
58/// blocks of [`BLOCK_PAIRS`] pairs behind a block index).
59pub const RUN_MAGIC_V3: &[u8] = b"rfc9162-log/run/v3\n";
60/// Pairs per block in a v3 run. Each block restarts its front coding, so a
61/// lookup decodes only the blocks whose key range can hold the key.
62pub const BLOCK_PAIRS: usize = 64;
63/// First bytes of a run file in format v1, which readers still accept.
64pub const RUN_MAGIC_V1: &[u8] = b"rfc9162-log/run/v1\n";
65const LIVE: &str = "runs/live";
66const BITS_PER_KEY: usize = 10;
67
68/// Derives the lookup keys of an entry; shared between the writer's sidecars
69/// and the runs. The identity hash is always a key and need not be returned.
70pub type Keys = Arc<dyn Fn(&[u8]) -> Vec<Vec<u8>> + Send + Sync>;
71
72/// Decoded runs by manifest index, shared between lookups so a run file is
73/// read and checked once per process rather than once per lookup.
74pub type RunCache = Arc<Mutex<HashMap<u64, Arc<Run>>>>;
75
76fn run_path(entries: &RangeInclusive<u64>) -> String {
77    format!("runs/{:020}-{:020}", entries.start(), entries.end())
78}
79
80/// The key function the tiles layer gets: the caller's keys for ordinary
81/// entries, none for manifest entries.
82pub fn tiles_keys(keys: &Keys) -> KeyFn {
83    let keys = Arc::clone(keys);
84    Box::new(move |entry: &[u8]| {
85        if entry.starts_with(MANIFEST_MAGIC) {
86            Vec::new()
87        } else {
88            keys(entry)
89        }
90    })
91}
92
93fn put_u64(out: &mut Vec<u8>, value: u64) {
94    out.extend_from_slice(&value.to_be_bytes());
95}
96
97fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), Error> {
98    let len = u16::try_from(bytes.len()).map_err(|_| Error::Tree("key over 65535 bytes"))?;
99    out.extend_from_slice(&len.to_be_bytes());
100    out.extend_from_slice(bytes);
101    Ok(())
102}
103
104fn put_varint(out: &mut Vec<u8>, mut value: u64) {
105    while value >= 0x80 {
106        out.push((value as u8) | 0x80);
107        value >>= 7;
108    }
109    out.push(value as u8);
110}
111
112/// Bytes `a` and `b` share at their start.
113fn shared_prefix(a: &[u8], b: &[u8]) -> usize {
114    a.iter().zip(b).take_while(|(x, y)| x == y).count()
115}
116
117struct Cursor<'a> {
118    bytes: &'a [u8],
119    at: usize,
120    what: &'static str,
121}
122
123impl<'a> Cursor<'a> {
124    fn take(&mut self, n: usize) -> Result<&'a [u8], Error> {
125        let end = self
126            .at
127            .checked_add(n)
128            .ok_or(Error::Tampered { what: self.what })?;
129        if end > self.bytes.len() {
130            return Err(Error::Tampered { what: self.what });
131        }
132        let out = &self.bytes[self.at..end];
133        self.at = end;
134        Ok(out)
135    }
136    fn u64(&mut self) -> Result<u64, Error> {
137        let b = self.take(8)?;
138        Ok(u64::from_be_bytes(b.try_into().expect("eight bytes")))
139    }
140    fn u32(&mut self) -> Result<u32, Error> {
141        let b = self.take(4)?;
142        Ok(u32::from_be_bytes(b.try_into().expect("four bytes")))
143    }
144    fn bytes(&mut self) -> Result<&'a [u8], Error> {
145        let b = self.take(2)?;
146        let len = u16::from_be_bytes(b.try_into().expect("two bytes")) as usize;
147        self.take(len)
148    }
149    /// A LEB128 varint of at most ten bytes whose value fits in a `u64`.
150    fn varint(&mut self) -> Result<u64, Error> {
151        let mut value = 0u64;
152        for shift in (0..64).step_by(7) {
153            let byte = self.take(1)?[0];
154            let low = u64::from(byte & 0x7f);
155            if shift == 63 && low > 1 {
156                return Err(Error::Tampered { what: self.what });
157            }
158            value |= low << shift;
159            if byte & 0x80 == 0 {
160                return Ok(value);
161            }
162        }
163        Err(Error::Tampered { what: self.what })
164    }
165    fn done(&self) -> Result<(), Error> {
166        if self.at == self.bytes.len() {
167            Ok(())
168        } else {
169            Err(Error::Tampered { what: self.what })
170        }
171    }
172}
173
174/// A logged manifest, as the entry carries it: the run it describes, the
175/// data tree (size and root) at the moment it was sealed, and the manifest
176/// indices of the runs it replaced (none for a seal).
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Logged {
179    /// The run.
180    pub run: RunManifest,
181    /// Entries of the data tree when the run was sealed.
182    pub data_size: u64,
183    /// Root of the data tree when the run was sealed.
184    pub data_root: [u8; 32],
185    /// Manifest indices of the runs this one replaced.
186    pub replaced: Vec<u64>,
187}
188
189impl Logged {
190    fn encode(&self) -> Result<Vec<u8>, Error> {
191        let mut out = MANIFEST_MAGIC.to_vec();
192        put_u64(&mut out, *self.run.entries.start());
193        put_u64(&mut out, *self.run.entries.end());
194        put_bytes(&mut out, &self.run.key_lo)?;
195        put_bytes(&mut out, &self.run.key_hi)?;
196        out.extend_from_slice(&self.run.run_hash);
197        put_u64(&mut out, self.data_size);
198        out.extend_from_slice(&self.data_root);
199        let n = u32::try_from(self.replaced.len())
200            .map_err(|_| Error::Tree("too many replaced runs"))?;
201        out.extend_from_slice(&n.to_be_bytes());
202        for index in &self.replaced {
203            put_u64(&mut out, *index);
204        }
205        Ok(out)
206    }
207
208    /// Decodes the manifest entry at `manifest_index`. `shared` says the
209    /// manifest sits in the data tree, where its index has to lie past the
210    /// run it describes.
211    pub fn decode(bytes: &[u8], manifest_index: u64, shared: bool) -> Result<Self, Error> {
212        let what = "manifest entry";
213        let body = bytes
214            .strip_prefix(MANIFEST_MAGIC)
215            .ok_or(Error::Tampered { what })?;
216        let mut c = Cursor {
217            bytes: body,
218            at: 0,
219            what,
220        };
221        let lo = c.u64()?;
222        let hi = c.u64()?;
223        let key_lo = c.bytes()?.to_vec();
224        let key_hi = c.bytes()?.to_vec();
225        let run_hash: [u8; 32] = c.take(32)?.try_into().expect("32 bytes");
226        let data_size = c.u64()?;
227        let data_root: [u8; 32] = c.take(32)?.try_into().expect("32 bytes");
228        let n = c.u32()?;
229        let mut replaced = Vec::with_capacity(n as usize);
230        for _ in 0..n {
231            replaced.push(c.u64()?);
232        }
233        c.done()?;
234        if lo > hi || hi >= data_size || (shared && hi >= manifest_index) {
235            return Err(Error::Tampered { what });
236        }
237        Ok(Self {
238            run: RunManifest {
239                entries: lo..=hi,
240                key_lo,
241                key_hi,
242                run_hash,
243                manifest_index: Some(manifest_index),
244            },
245            data_size,
246            data_root,
247            replaced,
248        })
249    }
250}
251
252/// A run file, checked against its manifest. Runs in format v1 and v2 are
253/// decoded whole into `pairs`; a v3 run keeps its bytes and its block index
254/// in `blocks` and decodes a block when a lookup reaches it.
255pub struct Run {
256    pairs: Vec<(Vec<u8>, u64)>,
257    blocks: Option<Blocks>,
258    filter: Bloom,
259}
260
261/// The block index and body of a v3 run.
262struct Blocks {
263    lo: u64,
264    hi: u64,
265    body: Vec<u8>,
266    index: Vec<BlockRef>,
267}
268
269/// One block: its first key and where its bytes sit in the body.
270struct BlockRef {
271    first: Vec<u8>,
272    start: usize,
273    end: usize,
274    count: usize,
275}
276
277impl Blocks {
278    /// The pairs of block `b`, checked: the first key matches the index, the
279    /// pairs rise strictly and every index lies in the run's entry range.
280    fn decode(&self, b: usize) -> Result<Vec<(Vec<u8>, u64)>, Error> {
281        let what = "run block";
282        let block = &self.index[b];
283        let mut c = Cursor {
284            bytes: &self.body[block.start..block.end],
285            at: 0,
286            what,
287        };
288        let mut pairs: Vec<(Vec<u8>, u64)> = Vec::with_capacity(block.count);
289        for _ in 0..block.count {
290            let shared = c.varint()?;
291            let unshared = c.varint()?;
292            let previous = pairs.last().map(|(k, _)| k.as_slice()).unwrap_or(&[]);
293            if shared > previous.len() as u64
294                || shared.saturating_add(unshared) > u64::from(u16::MAX)
295            {
296                return Err(Error::Tampered { what });
297            }
298            let mut key = previous[..shared as usize].to_vec();
299            key.extend_from_slice(c.take(unshared as usize)?);
300            let index = self
301                .lo
302                .checked_add(c.varint()?)
303                .ok_or(Error::Tampered { what })?;
304            if index > self.hi {
305                return Err(Error::Tampered { what });
306            }
307            if let Some(last) = pairs.last() {
308                if (&key, index) <= (&last.0, last.1) {
309                    return Err(Error::Tampered { what });
310                }
311            }
312            pairs.push((key, index));
313        }
314        c.done()?;
315        match pairs.first() {
316            Some((key, _)) if *key == block.first => Ok(pairs),
317            _ => Err(Error::Tampered { what }),
318        }
319    }
320
321    /// Blocks that can hold keys from `lo` on: from the last block whose first
322    /// key is below `lo` (the key may start inside it), in order.
323    fn from(&self, lo: &[u8]) -> usize {
324        self.index
325            .partition_point(|b| b.first.as_slice() < lo)
326            .saturating_sub(1)
327    }
328}
329
330impl Run {
331    /// A v3 run: the pairs in blocks of [`BLOCK_PAIRS`], each front-coded from
332    /// an empty key, behind an index of each block's first key, pair count and
333    /// byte length.
334    fn encode_blocks(
335        entries: &RangeInclusive<u64>,
336        pairs: &[(Vec<u8>, u64)],
337    ) -> Result<Vec<u8>, Error> {
338        let lo = *entries.start();
339        let mut bodies: Vec<Vec<u8>> = Vec::new();
340        for chunk in pairs.chunks(BLOCK_PAIRS) {
341            let mut body = Vec::new();
342            let mut previous: &[u8] = &[];
343            for (key, index) in chunk {
344                if key.len() > usize::from(u16::MAX) {
345                    return Err(Error::Tree("key over 65535 bytes"));
346                }
347                let offset = index
348                    .checked_sub(lo)
349                    .ok_or(Error::Tree("a run pair below the run's entry range"))?;
350                let shared = shared_prefix(previous, key);
351                put_varint(&mut body, shared as u64);
352                put_varint(&mut body, (key.len() - shared) as u64);
353                body.extend_from_slice(&key[shared..]);
354                put_varint(&mut body, offset);
355                previous = key;
356            }
357            bodies.push(body);
358        }
359        let mut out = RUN_MAGIC_V3.to_vec();
360        put_u64(&mut out, lo);
361        put_u64(&mut out, *entries.end());
362        put_varint(&mut out, pairs.len() as u64);
363        put_varint(&mut out, bodies.len() as u64);
364        for (chunk, body) in pairs.chunks(BLOCK_PAIRS).zip(&bodies) {
365            let first = &chunk[0].0;
366            put_varint(&mut out, first.len() as u64);
367            out.extend_from_slice(first);
368            put_varint(&mut out, chunk.len() as u64);
369            put_varint(&mut out, body.len() as u64);
370        }
371        for body in &bodies {
372            out.extend_from_slice(body);
373        }
374        let keys: Vec<&[u8]> = pairs.iter().map(|(k, _)| k.as_slice()).collect();
375        let filter = Bloom::build(&keys, BITS_PER_KEY).to_bytes();
376        put_varint(&mut out, filter.len() as u64);
377        out.extend_from_slice(&filter);
378        Ok(out)
379    }
380
381    /// Reads a v3 run's header, block index and filter; blocks stay encoded.
382    fn decode_blocks(body: &[u8], expect: &RunManifest) -> Result<Self, Error> {
383        let what = "run file";
384        let mut c = Cursor {
385            bytes: body,
386            at: 0,
387            what,
388        };
389        let lo = c.u64()?;
390        let hi = c.u64()?;
391        if lo != *expect.entries.start() || hi != *expect.entries.end() {
392            return Err(Error::Tampered { what });
393        }
394        let n = c.varint()?;
395        let nblocks = c.varint()?;
396        if n > (body.len() as u64) / 3 || nblocks > n.max(1) {
397            return Err(Error::Tampered { what });
398        }
399        let mut heads = Vec::with_capacity(nblocks as usize);
400        let mut total = 0u64;
401        for _ in 0..nblocks {
402            let len = c.varint()?;
403            if len > u64::from(u16::MAX) {
404                return Err(Error::Tampered { what });
405            }
406            let first = c.take(len as usize)?.to_vec();
407            let count = c.varint()?;
408            let bytes = c.varint()?;
409            if count == 0 || count > BLOCK_PAIRS as u64 || bytes == 0 {
410                return Err(Error::Tampered { what });
411            }
412            if let Some((previous, _, _)) = heads.last() {
413                if first < *previous {
414                    return Err(Error::Tampered { what });
415                }
416            }
417            total += count;
418            heads.push((first, count as usize, bytes as usize));
419        }
420        if total != n {
421            return Err(Error::Tampered { what });
422        }
423        let mut index = Vec::with_capacity(heads.len());
424        let mut start = 0usize;
425        let mut blob = Vec::new();
426        for (first, count, bytes) in heads {
427            blob.extend_from_slice(c.take(bytes)?);
428            index.push(BlockRef {
429                first,
430                start,
431                end: start + bytes,
432                count,
433            });
434            start += bytes;
435        }
436        let len = usize::try_from(c.varint()?).map_err(|_| Error::Tampered { what })?;
437        let filter = Bloom::from_bytes(c.take(len)?).map_err(|_| Error::Tampered { what })?;
438        c.done()?;
439        Ok(Self {
440            pairs: Vec::new(),
441            blocks: Some(Blocks {
442                lo,
443                hi,
444                body: blob,
445                index,
446            }),
447            filter,
448        })
449    }
450
451    /// Every pair of the run, in order.
452    fn all_pairs(&self) -> Result<Vec<(Vec<u8>, u64)>, Error> {
453        match &self.blocks {
454            None => Ok(self.pairs.clone()),
455            Some(blocks) => {
456                let mut out = Vec::new();
457                for b in 0..blocks.index.len() {
458                    out.extend(blocks.decode(b)?);
459                }
460                Ok(out)
461            }
462        }
463    }
464
465    /// A v2 run (one front-coded sequence); the writer writes v3.
466    #[cfg(test)]
467    fn encode(entries: &RangeInclusive<u64>, pairs: &[(Vec<u8>, u64)]) -> Result<Vec<u8>, Error> {
468        let lo = *entries.start();
469        let mut out = RUN_MAGIC.to_vec();
470        put_u64(&mut out, lo);
471        put_u64(&mut out, *entries.end());
472        put_varint(&mut out, pairs.len() as u64);
473        let mut previous: &[u8] = &[];
474        for (key, index) in pairs {
475            if key.len() > usize::from(u16::MAX) {
476                return Err(Error::Tree("key over 65535 bytes"));
477            }
478            let offset = index
479                .checked_sub(lo)
480                .ok_or(Error::Tree("a run pair below the run's entry range"))?;
481            let shared = shared_prefix(previous, key);
482            put_varint(&mut out, shared as u64);
483            put_varint(&mut out, (key.len() - shared) as u64);
484            out.extend_from_slice(&key[shared..]);
485            put_varint(&mut out, offset);
486            previous = key;
487        }
488        let keys: Vec<&[u8]> = pairs.iter().map(|(k, _)| k.as_slice()).collect();
489        let filter = Bloom::build(&keys, BITS_PER_KEY).to_bytes();
490        put_varint(&mut out, filter.len() as u64);
491        out.extend_from_slice(&filter);
492        Ok(out)
493    }
494
495    #[cfg(test)]
496    fn encode_v1(
497        entries: &RangeInclusive<u64>,
498        pairs: &[(Vec<u8>, u64)],
499    ) -> Result<Vec<u8>, Error> {
500        let mut out = RUN_MAGIC_V1.to_vec();
501        put_u64(&mut out, *entries.start());
502        put_u64(&mut out, *entries.end());
503        let n = u32::try_from(pairs.len()).map_err(|_| Error::Tree("too many pairs in a run"))?;
504        out.extend_from_slice(&n.to_be_bytes());
505        for (key, index) in pairs {
506            put_bytes(&mut out, key)?;
507            put_u64(&mut out, *index);
508        }
509        let keys: Vec<&[u8]> = pairs.iter().map(|(k, _)| k.as_slice()).collect();
510        let filter = Bloom::build(&keys, BITS_PER_KEY).to_bytes();
511        let len = u32::try_from(filter.len()).map_err(|_| Error::Tree("filter too large"))?;
512        out.extend_from_slice(&len.to_be_bytes());
513        out.extend_from_slice(&filter);
514        Ok(out)
515    }
516
517    fn decode(bytes: &[u8], expect: &RunManifest) -> Result<Self, Error> {
518        let what = "run file";
519        if identity(bytes) != expect.run_hash {
520            return Err(Error::Tampered { what });
521        }
522        if let Some(body) = bytes.strip_prefix(RUN_MAGIC_V3) {
523            return Self::decode_blocks(body, expect);
524        }
525        let (v2, body) = if let Some(body) = bytes.strip_prefix(RUN_MAGIC) {
526            (true, body)
527        } else if let Some(body) = bytes.strip_prefix(RUN_MAGIC_V1) {
528            (false, body)
529        } else {
530            return Err(Error::Tampered { what });
531        };
532        let mut c = Cursor {
533            bytes: body,
534            at: 0,
535            what,
536        };
537        let lo = c.u64()?;
538        let hi = c.u64()?;
539        if lo != *expect.entries.start() || hi != *expect.entries.end() {
540            return Err(Error::Tampered { what });
541        }
542        let n = if v2 { c.varint()? } else { u64::from(c.u32()?) };
543        // A pair takes at least three bytes in v2 and ten in v1, so a count the
544        // body cannot hold is refused before anything is allocated for it.
545        if n > (body.len() as u64) / 3 {
546            return Err(Error::Tampered { what });
547        }
548        let mut pairs: Vec<(Vec<u8>, u64)> = Vec::with_capacity(n as usize);
549        for _ in 0..n {
550            let (key, index) = if v2 {
551                let shared = c.varint()?;
552                let unshared = c.varint()?;
553                let previous = pairs.last().map(|(k, _)| k.as_slice()).unwrap_or(&[]);
554                if shared > previous.len() as u64
555                    || shared.saturating_add(unshared) > u64::from(u16::MAX)
556                {
557                    return Err(Error::Tampered { what });
558                }
559                let mut key = previous[..shared as usize].to_vec();
560                key.extend_from_slice(c.take(unshared as usize)?);
561                let index = lo
562                    .checked_add(c.varint()?)
563                    .ok_or(Error::Tampered { what })?;
564                (key, index)
565            } else {
566                let key = c.bytes()?.to_vec();
567                (key, c.u64()?)
568            };
569            if !expect.entries.contains(&index) {
570                return Err(Error::Tampered { what });
571            }
572            pairs.push((key, index));
573        }
574        if pairs.windows(2).any(|w| w[0] >= w[1]) {
575            return Err(Error::Tampered { what });
576        }
577        let len = if v2 { c.varint()? } else { u64::from(c.u32()?) };
578        let len = usize::try_from(len).map_err(|_| Error::Tampered { what })?;
579        let filter = Bloom::from_bytes(c.take(len)?).map_err(|_| Error::Tampered { what })?;
580        c.done()?;
581        Ok(Self {
582            pairs,
583            blocks: None,
584            filter,
585        })
586    }
587
588    /// The entry indices recorded for `key`, as the unit tests read them.
589    #[cfg(test)]
590    fn indices_of(&self, key: &[u8]) -> Vec<u64> {
591        self.lookup(key).expect("a checked run")
592    }
593
594    fn lookup(&self, key: &[u8]) -> Result<Vec<u64>, Error> {
595        if !self.filter.contains(key) {
596            return Ok(Vec::new());
597        }
598        Ok(self
599            .pairs_in(&(key..=key))?
600            .into_iter()
601            .map(|(_, i)| i)
602            .collect())
603    }
604
605    fn pairs_in(&self, range: &RangeInclusive<&[u8]>) -> Result<Vec<(Vec<u8>, u64)>, Error> {
606        let Some(blocks) = &self.blocks else {
607            let start = self
608                .pairs
609                .partition_point(|(k, _)| k.as_slice() < *range.start());
610            return Ok(self.pairs[start..]
611                .iter()
612                .take_while(|(k, _)| k.as_slice() <= *range.end())
613                .cloned()
614                .collect());
615        };
616        let mut out = Vec::new();
617        for b in blocks.from(range.start())..blocks.index.len() {
618            if blocks.index[b].first.as_slice() > *range.end() {
619                break;
620            }
621            out.extend(
622                blocks
623                    .decode(b)?
624                    .into_iter()
625                    .filter(|(k, _)| range.contains(&k.as_slice())),
626            );
627        }
628        Ok(out)
629    }
630}
631
632/// Whether the runs cover `0..=last` without a gap or an overlap.
633fn contiguous(runs: &[RunManifest]) -> bool {
634    let mut next = 0u64;
635    for run in runs {
636        if *run.entries.start() != next {
637            return false;
638        }
639        next = run.entries.end() + 1;
640    }
641    true
642}
643
644/// The live runs whose manifests sit below `size` in the manifest log: the
645/// cache at `runs/live` when every line names a manifest entry below `size`
646/// and the runs are contiguous, else a rebuild from the manifest entries in
647/// `0..size` with the replacements applied. The flag says whether it was
648/// rebuilt.
649fn load_live<S: TileStore>(
650    manifests: &Reader<S>,
651    size: u64,
652    shared: bool,
653) -> Result<(Vec<RunManifest>, bool), Error> {
654    if let Some(bytes) = manifests.store().get(LIVE)? {
655        let text = String::from_utf8_lossy(&bytes);
656        let mut runs = Vec::new();
657        let mut ok = true;
658        for line in text.lines().filter(|l| !l.is_empty()) {
659            let Ok(index) = line.parse::<u64>() else {
660                ok = false;
661                break;
662            };
663            if index >= size {
664                ok = false;
665                break;
666            }
667            match manifests.get(index)? {
668                Some(entry) if entry.starts_with(MANIFEST_MAGIC) => {
669                    runs.push(Logged::decode(&entry, index, shared)?.run);
670                }
671                _ => {
672                    ok = false;
673                    break;
674                }
675            }
676        }
677        if ok && contiguous(&runs) {
678            return Ok((runs, false));
679        }
680    }
681    let mut runs: Vec<RunManifest> = Vec::new();
682    for index in 0..size {
683        let Some(entry) = manifests.get(index)? else {
684            return Err(Error::Tree("entry missing below the tree size"));
685        };
686        if !entry.starts_with(MANIFEST_MAGIC) {
687            continue;
688        }
689        let logged = Logged::decode(&entry, index, shared)?;
690        runs.retain(|r| {
691            !logged
692                .replaced
693                .contains(&r.manifest_index.unwrap_or(u64::MAX))
694        });
695        runs.push(logged.run);
696        runs.sort_by_key(|r| *r.entries.start());
697    }
698    if !contiguous(&runs) {
699        return Err(Error::Tree("logged runs do not cover the sealed range"));
700    }
701    Ok((runs, true))
702}
703
704fn save_live(store: &impl TileStore, runs: &[RunManifest]) -> Result<(), Error> {
705    let mut text = String::new();
706    for run in runs {
707        let index = run
708            .manifest_index
709            .ok_or(Error::Tree("live run without a manifest index"))?;
710        text.push_str(&index.to_string());
711        text.push('\n');
712    }
713    store.put(LIVE, text.as_bytes())?;
714    Ok(())
715}
716
717/// Lookups at tree sizes and roots the caller trusts, without the writer
718/// locks. Every answer carries inclusion paths against those sizes: the
719/// entry's against the data tree, the run manifest's against the tree that
720/// holds the manifests (the same tree when shared), so a caller holding the
721/// signed checkpoints proves each hit with their roots and nothing from the
722/// store.
723pub struct Index<S: TileStore> {
724    data: Reader<S>,
725    manifests: Option<Reader<S>>,
726    keys: Keys,
727    live: Vec<RunManifest>,
728    cache: RunCache,
729}
730
731impl<S: TileStore> Index<S> {
732    /// Opens the lookups on a shared log at `size` with `root`.
733    pub fn at(store: S, size: u64, root: [u8; 32], keys: Keys) -> Result<Self, Error> {
734        Self::with_cache(store, size, root, keys, Arc::default())
735    }
736
737    /// Like [`Index::at`], sharing `cache` with other lookups on the same log.
738    pub fn with_cache(
739        store: S,
740        size: u64,
741        root: [u8; 32],
742        keys: Keys,
743        cache: RunCache,
744    ) -> Result<Self, Error> {
745        let data = Reader::at(store, size, root, tiles_keys(&keys))?;
746        let (live, _) = load_live(&data, size, true)?;
747        Ok(Self {
748            data,
749            manifests: None,
750            keys,
751            live,
752            cache,
753        })
754    }
755
756    /// Opens the lookups on a split log: the data tree at `data_size` with
757    /// `data_root` and the index log at `index_size` with `index_root`.
758    #[allow(clippy::too_many_arguments)]
759    pub fn at_split(
760        data_store: S,
761        data_size: u64,
762        data_root: [u8; 32],
763        index_store: S,
764        index_size: u64,
765        index_root: [u8; 32],
766        keys: Keys,
767        cache: RunCache,
768    ) -> Result<Self, Error> {
769        let data = Reader::at(data_store, data_size, data_root, tiles_keys(&keys))?;
770        let manifests = Reader::at(index_store, index_size, index_root, tiles_keys(&keys))?;
771        let (live, _) = load_live(&manifests, index_size, false)?;
772        // A run that indexes entries past this data tree is not usable here.
773        let live = live
774            .into_iter()
775            .take_while(|run| *run.entries.end() < data_size)
776            .collect();
777        Ok(Self {
778            data,
779            manifests: Some(manifests),
780            keys,
781            live,
782            cache,
783        })
784    }
785
786    /// The runs a lookup goes through, oldest first.
787    #[must_use]
788    pub fn live_runs(&self) -> &[RunManifest] {
789        &self.live
790    }
791
792    /// The data tree size the lookups answer at.
793    #[must_use]
794    pub fn size(&self) -> u64 {
795        self.data.size()
796    }
797
798    /// The tiles reader of the data log, for entries and proofs.
799    pub fn reader(&self) -> &Reader<S> {
800        &self.data
801    }
802
803    /// The tiles reader that holds the manifests: the index log's when split,
804    /// the data log's when shared.
805    pub fn manifest_reader(&self) -> &Reader<S> {
806        self.manifests.as_ref().unwrap_or(&self.data)
807    }
808
809    fn last_sealed(&self) -> Option<u64> {
810        self.live.last().map(|r| *r.entries.end())
811    }
812
813    fn run(&self, run: &RunManifest) -> Result<Arc<Run>, Error> {
814        let manifest_index = run
815            .manifest_index
816            .ok_or(Error::Tree("live run without a manifest index"))?;
817        if let Some(hit) = self
818            .cache
819            .lock()
820            .map_err(|_| Error::Tree("run cache poisoned"))?
821            .get(&manifest_index)
822        {
823            return Ok(Arc::clone(hit));
824        }
825        let path = run_path(&run.entries);
826        let bytes = self
827            .manifest_reader()
828            .store()
829            .get(&path)?
830            .ok_or(Error::Tampered { what: "run file" })?;
831        let decoded = Arc::new(Run::decode(&bytes, run)?);
832        self.cache
833            .lock()
834            .map_err(|_| Error::Tree("run cache poisoned"))?
835            .insert(manifest_index, Arc::clone(&decoded));
836        Ok(decoded)
837    }
838
839    fn inclusion_in(reader: &Reader<S>, index: u64) -> Result<Inclusion, Error> {
840        let path = reader
841            .inclusion(index)?
842            .ok_or(Error::Tree("no inclusion path below the tree size"))?;
843        Ok(Inclusion {
844            index,
845            tree_size: reader.size(),
846            path,
847        })
848    }
849
850    fn located(&self, index: u64, run: Option<&RunManifest>) -> Result<Located, Error> {
851        let run = match run {
852            Some(run) => {
853                let manifest_index = run
854                    .manifest_index
855                    .ok_or(Error::Tree("live run without a manifest index"))?;
856                Some(Self::inclusion_in(self.manifest_reader(), manifest_index)?)
857            }
858            None => None,
859        };
860        Ok(Located {
861            entry: Self::inclusion_in(&self.data, index)?,
862            run,
863        })
864    }
865
866    /// The `(key, index)` pairs of the data entries after the last sealed one.
867    fn tail_pairs(&self) -> Result<Vec<(Vec<u8>, u64)>, Error> {
868        let from = self.last_sealed().map_or(0, |last| last + 1);
869        let mut pairs = Vec::new();
870        for index in from..self.data.size() {
871            let Some(entry) = self.data.get(index)? else {
872                return Err(Error::Tree("entry missing below the tree size"));
873            };
874            if entry.starts_with(MANIFEST_MAGIC) {
875                continue;
876            }
877            pairs.push((identity(&entry).to_vec(), index));
878            for key in (self.keys)(&entry) {
879                pairs.push((key, index));
880            }
881        }
882        Ok(pairs)
883    }
884
885    fn require_coverage(&self) -> Result<(), Error> {
886        if contiguous(&self.live) {
887            Ok(())
888        } else {
889            Err(Error::Tree("live runs do not cover the sealed range"))
890        }
891    }
892
893    /// Every entry carrying `key`, each with the run that located it; see
894    /// [`AuthenticatedLog::find`].
895    pub fn find(&self, key: &[u8]) -> Result<Vec<Located>, Error> {
896        self.require_coverage()?;
897        let mut found = Vec::new();
898        for run in &self.live {
899            if key < run.key_lo.as_slice() || key > run.key_hi.as_slice() {
900                continue;
901            }
902            for index in self.run(run)?.lookup(key)? {
903                found.push(self.located(index, Some(run))?);
904            }
905        }
906        for (k, index) in self.tail_pairs()? {
907            if k == key {
908                found.push(self.located(index, None)?);
909            }
910        }
911        Ok(found)
912    }
913
914    /// Every entry whose keys fall in `range`, in key order, with their runs;
915    /// see [`AuthenticatedLog::range`].
916    pub fn range(&self, range: RangeInclusive<&[u8]>) -> Result<Vec<Located>, Error> {
917        self.require_coverage()?;
918        let mut hits: Vec<(Vec<u8>, u64, Option<usize>)> = Vec::new();
919        for (n, run) in self.live.iter().enumerate() {
920            if run.key_hi.as_slice() < *range.start() || run.key_lo.as_slice() > *range.end() {
921                continue;
922            }
923            for (k, index) in self.run(run)?.pairs_in(&range)? {
924                hits.push((k, index, Some(n)));
925            }
926        }
927        for (k, index) in self.tail_pairs()? {
928            if k.as_slice() >= *range.start() && k.as_slice() <= *range.end() {
929                hits.push((k, index, None));
930            }
931        }
932        hits.sort();
933        hits.into_iter()
934            .map(|(_, index, run)| self.located(index, run.map(|n| &self.live[n])))
935            .collect()
936    }
937}
938
939/// The store: the data writer, the manifest writer when split, the live
940/// runs, and the size a trusted checkpoint covers.
941pub struct Store<S: TileStore> {
942    data: Writer<S>,
943    manifests: Option<Writer<S>>,
944    keys: Keys,
945    live: Vec<RunManifest>,
946    cache: RunCache,
947    trusted: Option<(u64, [u8; 32])>,
948}
949
950impl<S: TileStore> Store<S> {
951    /// Opens a shared log on `store` with the writer lock, rebuilding the
952    /// live set from the logged manifests when the cache does not match them.
953    pub fn open(store: S, keys: Keys) -> Result<Self, Error> {
954        let data = Writer::open(store, tiles_keys(&keys))?;
955        let mut this = Self {
956            data,
957            manifests: None,
958            keys,
959            live: Vec::new(),
960            cache: Arc::default(),
961            trusted: None,
962        };
963        this.reload_live()?;
964        Ok(this)
965    }
966
967    /// Opens a split log: entries go to `data`, run files and manifests to
968    /// `index`. Both writer locks are held.
969    pub fn open_split(data: S, index: S, keys: Keys) -> Result<Self, Error> {
970        let data = Writer::open(data, tiles_keys(&keys))?;
971        let manifests = Writer::open(index, tiles_keys(&keys))?;
972        let mut this = Self {
973            data,
974            manifests: Some(manifests),
975            keys,
976            live: Vec::new(),
977            cache: Arc::default(),
978            trusted: None,
979        };
980        this.reload_live()?;
981        Ok(this)
982    }
983
984    /// Like [`Store::open`], trusting `root` as the root at `size`: what
985    /// [`AuthenticatedLog::recover`] proves the current data tree consistent
986    /// with. The pair comes from a signed checkpoint kept where the store's
987    /// writer cannot reach it.
988    pub fn open_trusting(store: S, keys: Keys, size: u64, root: [u8; 32]) -> Result<Self, Error> {
989        let mut this = Self::open(store, keys)?;
990        this.trusted = Some((size, root));
991        Ok(this)
992    }
993
994    /// The runs a lookup goes through, oldest first.
995    #[must_use]
996    pub fn live_runs(&self) -> &[RunManifest] {
997        &self.live
998    }
999
1000    /// The number of entries in the data tree.
1001    #[must_use]
1002    pub fn size(&self) -> u64 {
1003        self.data.size()
1004    }
1005
1006    /// The root of the data tree.
1007    #[must_use]
1008    pub fn root(&self) -> [u8; 32] {
1009        self.data.root()
1010    }
1011
1012    /// The data writer, for checkpoints.
1013    pub fn writer(&self) -> &Writer<S> {
1014        &self.data
1015    }
1016
1017    /// The index log's writer when split, for its own checkpoint.
1018    pub fn index_writer(&self) -> Option<&Writer<S>> {
1019        self.manifests.as_ref()
1020    }
1021
1022    fn manifest_writer(&self) -> &Writer<S> {
1023        self.manifests.as_ref().unwrap_or(&self.data)
1024    }
1025
1026    fn manifest_store(&self) -> &S {
1027        self.manifest_writer().store()
1028    }
1029
1030    /// Lookups at the current trees, sharing this store's run cache.
1031    pub fn index(&self) -> Result<Index<&S>, Error> {
1032        match &self.manifests {
1033            None => Index::with_cache(
1034                self.data.store(),
1035                self.data.size(),
1036                self.data.root(),
1037                Arc::clone(&self.keys),
1038                Arc::clone(&self.cache),
1039            ),
1040            Some(manifests) => Index::at_split(
1041                self.data.store(),
1042                self.data.size(),
1043                self.data.root(),
1044                manifests.store(),
1045                manifests.size(),
1046                manifests.root(),
1047                Arc::clone(&self.keys),
1048                Arc::clone(&self.cache),
1049            ),
1050        }
1051    }
1052
1053    /// Appends entries in order and returns their index range; the batch is
1054    /// written once. Nothing is written when an entry starts with the
1055    /// manifest magic or is over the tiles entry length.
1056    pub fn append_batch<I>(&mut self, entries: I) -> Result<std::ops::Range<u64>, Error>
1057    where
1058        I: IntoIterator<Item = Vec<u8>>,
1059    {
1060        let entries: Vec<Vec<u8>> = entries.into_iter().collect();
1061        if entries.iter().any(|e| e.starts_with(MANIFEST_MAGIC)) {
1062            return Err(Error::Tree(
1063                "an entry may not start with the manifest magic",
1064            ));
1065        }
1066        Ok(self.data.append_batch(entries)?)
1067    }
1068
1069    fn reload_live(&mut self) -> Result<(), Error> {
1070        let shared = self.manifests.is_none();
1071        let writer = self.manifest_writer();
1072        let reader = Reader::at(
1073            writer.store(),
1074            writer.size(),
1075            writer.root(),
1076            tiles_keys(&self.keys),
1077        )?;
1078        let (live, rebuilt) = load_live(&reader, writer.size(), shared)?;
1079        drop(reader);
1080        if rebuilt {
1081            save_live(self.manifest_store(), &live)?;
1082        }
1083        self.live = live;
1084        Ok(())
1085    }
1086
1087    fn last_sealed(&self) -> Option<u64> {
1088        self.live.last().map(|r| *r.entries.end())
1089    }
1090
1091    fn log_run(
1092        &mut self,
1093        entries: RangeInclusive<u64>,
1094        pairs: Vec<(Vec<u8>, u64)>,
1095        replaced: Vec<u64>,
1096    ) -> Result<RunManifest, Error> {
1097        let bytes = Run::encode_blocks(&entries, &pairs)?;
1098        let path = run_path(&entries);
1099        if !self.manifest_store().create_new(&path, &bytes)? {
1100            return Err(Error::Tree("a run for that entry range is already stored"));
1101        }
1102        let (key_lo, key_hi) = match (pairs.first(), pairs.last()) {
1103            (Some(first), Some(last)) => (first.0.clone(), last.0.clone()),
1104            _ => (Vec::new(), Vec::new()),
1105        };
1106        let run = RunManifest {
1107            entries,
1108            key_lo,
1109            key_hi,
1110            run_hash: identity(&bytes),
1111            manifest_index: None,
1112        };
1113        let logged = Logged {
1114            run,
1115            data_size: self.data.size(),
1116            data_root: self.data.root(),
1117            replaced,
1118        };
1119        let encoded = logged.encode()?;
1120        let manifest_index = match &mut self.manifests {
1121            Some(manifests) => manifests.append(&encoded)?,
1122            None => self.data.append(&encoded)?,
1123        };
1124        let mut run = logged.run;
1125        run.manifest_index = Some(manifest_index);
1126        Ok(run)
1127    }
1128}
1129
1130impl<S: TileStore> AuthenticatedLog for Store<S> {
1131    fn append(&mut self, entry: &[u8]) -> Result<u64, Error> {
1132        Ok(self.append_batch([entry.to_vec()])?.start)
1133    }
1134
1135    fn find(&self, key: &[u8]) -> Result<Vec<Located>, Error> {
1136        self.index()?.find(key)
1137    }
1138
1139    fn range(&self, range: RangeInclusive<&[u8]>) -> Result<Vec<Located>, Error> {
1140        self.index()?.range(range)
1141    }
1142
1143    fn seal(&mut self) -> Result<RunManifest, Error> {
1144        let index = self.index()?;
1145        index.require_coverage()?;
1146        let lo = self.last_sealed().map_or(0, |last| last + 1);
1147        let size = self.data.size();
1148        if lo >= size {
1149            return Err(Error::Tree("nothing to seal"));
1150        }
1151        let mut pairs = index.tail_pairs()?;
1152        drop(index);
1153        if pairs.is_empty() {
1154            // Only manifest entries since the last seal: nothing to index.
1155            // They fall into the range of the next run that has keys.
1156            return Err(Error::Tree("nothing to seal"));
1157        }
1158        pairs.sort();
1159        pairs.dedup();
1160        let run = self.log_run(lo..=size - 1, pairs, Vec::new())?;
1161        self.live.push(run.clone());
1162        save_live(self.manifest_store(), &self.live)?;
1163        Ok(run)
1164    }
1165
1166    fn compact(&mut self) -> Result<Compaction, Error> {
1167        let index = self.index()?;
1168        index.require_coverage()?;
1169        if self.live.len() < 2 {
1170            return Err(Error::Tree("fewer than two runs to compact"));
1171        }
1172        let mut merged: BTreeSet<(Vec<u8>, u64)> = BTreeSet::new();
1173        let mut replaced = Vec::new();
1174        for run in &self.live {
1175            merged.extend(index.run(run)?.all_pairs()?);
1176            replaced.push(
1177                run.manifest_index
1178                    .ok_or(Error::Tree("live run without a manifest index"))?,
1179            );
1180        }
1181        drop(index);
1182        let lo = *self.live[0].entries.start();
1183        let hi = *self.live[self.live.len() - 1].entries.end();
1184        let run = self.log_run(lo..=hi, merged.into_iter().collect(), replaced.clone())?;
1185        let written = run.manifest_index.expect("logged");
1186        self.live = vec![run];
1187        save_live(self.manifest_store(), &self.live)?;
1188        Ok(Compaction { replaced, written })
1189    }
1190
1191    fn recover(&mut self) -> Result<Recovery, Error> {
1192        let (checkpoint_size, checkpoint_root) = self
1193            .trusted
1194            .ok_or(Error::Tree("no trusted checkpoint to recover from"))?;
1195        let size = self.data.size();
1196        if checkpoint_size > size {
1197            return Err(Error::Tampered { what: "tree state" });
1198        }
1199        let consistency = if checkpoint_size == 0 {
1200            Vec::new()
1201        } else {
1202            self.index()?
1203                .reader()
1204                .consistency(checkpoint_size)?
1205                .ok_or(Error::Tree("no consistency path from the checkpoint"))?
1206        };
1207        let old_size =
1208            usize::try_from(checkpoint_size).map_err(|_| Error::Tree("size over usize"))?;
1209        let new_size = usize::try_from(size).map_err(|_| Error::Tree("size over usize"))?;
1210        if checkpoint_size > 0
1211            && !rfc9162::verify_consistency(
1212                old_size,
1213                &rfc9162::hex(&checkpoint_root),
1214                new_size,
1215                &rfc9162::hex(&self.data.root()),
1216                &consistency,
1217            )
1218        {
1219            return Err(Error::Tampered { what: "tree state" });
1220        }
1221        self.reload_live()?;
1222        let (quarantined, _) = self.data.quarantined();
1223        Ok(Recovery {
1224            checkpoint_size,
1225            replayed: size - checkpoint_size,
1226            quarantined: u64::try_from(quarantined.len()).unwrap_or(u64::MAX),
1227            consistency,
1228        })
1229    }
1230}
1231
1232impl From<rfc9162_tiles::Error> for Error {
1233    fn from(e: rfc9162_tiles::Error) -> Self {
1234        match e {
1235            rfc9162_tiles::Error::Io(e) => Self::Store(e),
1236            rfc9162_tiles::Error::Corrupt { .. } => Self::Tampered { what: "tile store" },
1237            other => Self::Tiles(other.to_string()),
1238        }
1239    }
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244    use super::*;
1245
1246    fn pairs() -> Vec<(Vec<u8>, u64)> {
1247        let mut pairs: Vec<(Vec<u8>, u64)> = (0u64..300)
1248            .flat_map(|i| {
1249                [
1250                    (
1251                        format!("module=GROMACS/2026.{}-foss-2026.1", i % 7).into_bytes(),
1252                        1000 + i,
1253                    ),
1254                    (
1255                        format!("path=software/PETSc/3.24.{i}/lib/libpetsc.so").into_bytes(),
1256                        1000 + i,
1257                    ),
1258                    (
1259                        format!("target=x86_64/amd/zen{}", 2 + i % 3).into_bytes(),
1260                        1000 + i,
1261                    ),
1262                ]
1263            })
1264            .collect();
1265        pairs.sort();
1266        pairs
1267    }
1268
1269    fn manifest(entries: RangeInclusive<u64>, bytes: &[u8]) -> RunManifest {
1270        RunManifest {
1271            entries,
1272            key_lo: Vec::new(),
1273            key_hi: Vec::new(),
1274            run_hash: identity(bytes),
1275            manifest_index: None,
1276        }
1277    }
1278
1279    #[test]
1280    fn v2_round_trips_and_answers_lookups() {
1281        let entries = 1000..=1299;
1282        let pairs = pairs();
1283        let bytes = Run::encode(&entries, &pairs).unwrap();
1284        assert!(bytes.starts_with(RUN_MAGIC));
1285        let run = Run::decode(&bytes, &manifest(entries, &bytes)).unwrap();
1286        assert_eq!(run.pairs, pairs);
1287        assert_eq!(run.indices_of(b"target=x86_64/amd/zen3").len(), 100);
1288        assert!(run.indices_of(b"target=x86_64/amd/zen5").is_empty());
1289    }
1290
1291    #[test]
1292    fn v1_runs_stay_readable() {
1293        let entries = 1000..=1299;
1294        let pairs = pairs();
1295        let bytes = Run::encode_v1(&entries, &pairs).unwrap();
1296        let run = Run::decode(&bytes, &manifest(entries, &bytes)).unwrap();
1297        assert_eq!(run.pairs, pairs);
1298    }
1299
1300    #[test]
1301    fn v2_is_smaller_than_v1_on_shared_prefixes() {
1302        let entries = 1000..=1299;
1303        let pairs = pairs();
1304        let v1 = Run::encode_v1(&entries, &pairs).unwrap().len();
1305        let v2 = Run::encode(&entries, &pairs).unwrap().len();
1306        assert!(v2 * 2 < v1, "v2 {v2} bytes, v1 {v1} bytes");
1307    }
1308
1309    #[test]
1310    fn a_shared_length_past_the_previous_key_is_refused() {
1311        let entries = 0..=0;
1312        let mut bytes = RUN_MAGIC.to_vec();
1313        put_u64(&mut bytes, 0);
1314        put_u64(&mut bytes, 0);
1315        put_varint(&mut bytes, 1);
1316        put_varint(&mut bytes, 3);
1317        put_varint(&mut bytes, 1);
1318        bytes.push(b'k');
1319        put_varint(&mut bytes, 0);
1320        put_varint(&mut bytes, 0);
1321        let expect = manifest(entries, &bytes);
1322        assert!(matches!(
1323            Run::decode(&bytes, &expect),
1324            Err(Error::Tampered { .. })
1325        ));
1326    }
1327
1328    #[test]
1329    fn an_index_outside_the_run_is_refused() {
1330        let entries = 10..=11;
1331        let bytes = Run::encode(&(10..=20), &[(b"k".to_vec(), 15)]).unwrap();
1332        let mut patched = RUN_MAGIC.to_vec();
1333        patched.extend_from_slice(&bytes[RUN_MAGIC.len()..]);
1334        // Rewrite the header so lo and hi match the manifest while the
1335        // pair's offset of 5 lands on 15, outside 10..=11.
1336        patched[RUN_MAGIC.len() + 8..RUN_MAGIC.len() + 16].copy_from_slice(&11u64.to_be_bytes());
1337        let expect = manifest(entries, &patched);
1338        assert!(matches!(
1339            Run::decode(&patched, &expect),
1340            Err(Error::Tampered { .. })
1341        ));
1342    }
1343
1344    #[test]
1345    fn an_overlong_varint_is_refused() {
1346        let mut c = Cursor {
1347            bytes: &[0xff; 11],
1348            at: 0,
1349            what: "test",
1350        };
1351        assert!(c.varint().is_err());
1352        let mut out = Vec::new();
1353        put_varint(&mut out, u64::MAX);
1354        let mut c = Cursor {
1355            bytes: &out,
1356            at: 0,
1357            what: "test",
1358        };
1359        assert_eq!(c.varint().unwrap(), u64::MAX);
1360    }
1361}
1362
1363#[cfg(test)]
1364mod block_tests {
1365    use super::*;
1366
1367    fn manifest(entries: RangeInclusive<u64>, bytes: &[u8]) -> RunManifest {
1368        RunManifest {
1369            entries,
1370            key_lo: Vec::new(),
1371            key_hi: Vec::new(),
1372            run_hash: identity(bytes),
1373            manifest_index: None,
1374        }
1375    }
1376
1377    /// 1000 entries; "module=hot" recurs 300 times so it spans several blocks.
1378    fn pairs() -> Vec<(Vec<u8>, u64)> {
1379        let mut pairs: Vec<(Vec<u8>, u64)> = (0u64..1000)
1380            .flat_map(|i| {
1381                let module = if i % 3 == 0 && i < 900 {
1382                    "module=hot".to_string()
1383                } else {
1384                    format!("module=m{i:04}")
1385                };
1386                [
1387                    (module.into_bytes(), i),
1388                    (format!("target=t{}", i % 4).into_bytes(), i),
1389                ]
1390            })
1391            .collect();
1392        pairs.sort();
1393        pairs
1394    }
1395
1396    fn v3(pairs: &[(Vec<u8>, u64)]) -> Run {
1397        let bytes = Run::encode_blocks(&(0..=999), pairs).unwrap();
1398        assert!(bytes.starts_with(RUN_MAGIC_V3));
1399        Run::decode(&bytes, &manifest(0..=999, &bytes)).unwrap()
1400    }
1401
1402    fn brute(pairs: &[(Vec<u8>, u64)], lo: &[u8], hi: &[u8]) -> Vec<(Vec<u8>, u64)> {
1403        pairs
1404            .iter()
1405            .filter(|(k, _)| k.as_slice() >= lo && k.as_slice() <= hi)
1406            .cloned()
1407            .collect()
1408    }
1409
1410    #[test]
1411    fn v3_keeps_blocks_encoded_and_returns_every_pair() {
1412        let pairs = pairs();
1413        let run = v3(&pairs);
1414        let blocks = run.blocks.as_ref().unwrap();
1415        assert_eq!(blocks.index.len(), pairs.len().div_ceil(BLOCK_PAIRS));
1416        assert!(run.pairs.is_empty());
1417        assert_eq!(run.all_pairs().unwrap(), pairs);
1418    }
1419
1420    #[test]
1421    fn a_key_spanning_blocks_is_found_in_full() {
1422        let pairs = pairs();
1423        let run = v3(&pairs);
1424        let expect: Vec<u64> = brute(&pairs, b"module=hot", b"module=hot")
1425            .into_iter()
1426            .map(|(_, i)| i)
1427            .collect();
1428        assert_eq!(expect.len(), 300);
1429        assert_eq!(run.lookup(b"module=hot").unwrap(), expect);
1430        assert!(run.lookup(b"module=absent").unwrap().is_empty());
1431    }
1432
1433    #[test]
1434    fn ranges_match_a_scan_of_the_pairs() {
1435        let pairs = pairs();
1436        let run = v3(&pairs);
1437        for (lo, hi) in [
1438            (&b"module=m0100"[..], &b"module=m0200"[..]),
1439            (b"module=", b"module=\xff"),
1440            (b"target=t1", b"target=t2"),
1441            (b"a", b"z"),
1442            (b"zz", b"zzz"),
1443        ] {
1444            assert_eq!(run.pairs_in(&(lo..=hi)).unwrap(), brute(&pairs, lo, hi));
1445        }
1446    }
1447
1448    #[test]
1449    fn a_lookup_decodes_only_the_blocks_it_needs() {
1450        let pairs = pairs();
1451        let run = v3(&pairs);
1452        let blocks = run.blocks.as_ref().unwrap();
1453        let first = blocks.from(b"target=t3");
1454        let last = blocks
1455            .index
1456            .partition_point(|b| b.first.as_slice() <= &b"target=t3"[..]);
1457        assert!(last - first < blocks.index.len() / 4, "{first}..{last}");
1458    }
1459
1460    #[test]
1461    fn v2_and_v3_answer_alike() {
1462        let pairs = pairs();
1463        let bytes = Run::encode(&(0..=999), &pairs).unwrap();
1464        let old = Run::decode(&bytes, &manifest(0..=999, &bytes)).unwrap();
1465        let new = v3(&pairs);
1466        for key in [&b"module=hot"[..], b"module=m0001", b"target=t0", b"nope"] {
1467            assert_eq!(old.lookup(key).unwrap(), new.lookup(key).unwrap());
1468        }
1469    }
1470
1471    #[test]
1472    fn a_tampered_block_is_refused_when_read() {
1473        let pairs = pairs();
1474        let bytes = Run::encode_blocks(&(0..=999), &pairs).unwrap();
1475        let run = Run::decode(&bytes, &manifest(0..=999, &bytes)).unwrap();
1476        let mut blocks = run.blocks.unwrap();
1477        let b = blocks.index.len() / 2;
1478        let at = blocks.index[b].start;
1479        blocks.body[at] = 0x7f;
1480        assert!(matches!(blocks.decode(b), Err(Error::Tampered { .. })));
1481    }
1482
1483    #[test]
1484    fn a_count_that_disagrees_with_the_blocks_is_refused() {
1485        let pairs = pairs();
1486        let mut bytes = Run::encode_blocks(&(0..=999), &pairs).unwrap();
1487        // n sits right after magic, lo and hi; 2000 pairs encode as 0xd0 0x0f.
1488        let at = RUN_MAGIC_V3.len() + 16;
1489        assert_eq!(&bytes[at..at + 2], &[0xd0, 0x0f]);
1490        bytes[at] = 0xcf;
1491        let expect = manifest(0..=999, &bytes);
1492        assert!(matches!(
1493            Run::decode(&bytes, &expect),
1494            Err(Error::Tampered { .. })
1495        ));
1496    }
1497}