Skip to main content

ic_storage/btree/
mod.rs

1//! Fixed-size tree pages with separately allocated key/value chains.
2
3mod cursor;
4mod page;
5#[cfg(test)]
6mod tests;
7mod transaction;
8
9pub use cursor::{Entry, Range};
10use page::{Header, Node, Record};
11use std::{cmp::Ordering, fmt, ops::Bound};
12use transaction::Transaction;
13
14use crate::Memory;
15
16/// A rejected operation. Returned errors never publish a partial mutation.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum Error {
19    /// Creation requires a fresh, zero-sized memory.
20    NotEmpty,
21    /// Memory does not contain this tree's format.
22    Format,
23    /// Stored configuration or comparator identity differs from the caller.
24    Configuration,
25    /// Invalid page, pointer, length, checksum, or tree geometry.
26    Corrupt,
27    /// An input exceeds the configured key or value bound.
28    TooLarge,
29    /// The backing memory refused growth or arithmetic capacity was exhausted.
30    Capacity,
31    /// The comparator rejected malformed input.
32    InvalidKey,
33    /// The lower bound follows the upper bound.
34    InvalidRange,
35}
36
37impl fmt::Display for Error {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "stable B-tree error: {self:?}")
40    }
41}
42impl std::error::Error for Error {}
43
44/// Immutable, bounded decoding limits stored in the tree header.
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub struct Config {
47    /// Largest encoded key accepted by this tree, in bytes.
48    pub max_key_bytes: u32,
49    /// Largest encoded value accepted by this tree, in bytes.
50    pub max_value_bytes: u32,
51}
52impl Default for Config {
53    fn default() -> Self {
54        Self {
55            max_key_bytes: 128 * 1024,
56            max_value_bytes: 4 * 1024 * 1024,
57        }
58    }
59}
60impl Config {
61    fn check(self) -> Result<(), Error> {
62        if self.max_key_bytes == 0
63            || self.max_key_bytes > 1024 * 1024
64            || self.max_value_bytes == 0
65            || self.max_value_bytes > 64 * 1024 * 1024
66        {
67            return Err(Error::Configuration);
68        }
69        Ok(())
70    }
71}
72
73/// A deterministic total order over encoded keys.
74///
75/// `IDENTITY` must change when ordering or accepted key encoding changes.
76/// Equal keys must have identical encodings. The comparator must be immutable
77/// and return `InvalidKey` for malformed keys, including `compare(key, key)`.
78/// Persisted comparator code itself remains an application responsibility.
79pub trait Comparator {
80    /// Stable identity of the encoding and ordering contract.
81    const IDENTITY: [u8; 16];
82    /// Compare two encoded keys, rejecting invalid encodings.
83    ///
84    /// # Errors
85    /// Returns [`Error::InvalidKey`] when an encoded key is invalid.
86    fn compare(&self, left: &[u8], right: &[u8]) -> Result<Ordering, Error>;
87}
88
89/// Ordinary unsigned bytewise ordering for already order-preserving encodings.
90#[derive(Clone, Copy, Debug, Default)]
91pub struct Lexicographic;
92impl Comparator for Lexicographic {
93    const IDENTITY: [u8; 16] = *b"ic-storage-lex01";
94    fn compare(&self, left: &[u8], right: &[u8]) -> Result<Ordering, Error> {
95        Ok(left.cmp(right))
96    }
97}
98
99/// A stable-memory B-tree, independent of DFINITY's collection formats.
100///
101/// Exactly one live tree handle may own a memory region. The memory must not be
102/// mutated through aliases. Mutations stage touched pages, reserve capacity,
103/// then write them. Backing writes must either complete or trap in an atomic IC
104/// message; this is not a crash-safe filesystem database. See the package guide.
105pub struct BTreeMap<M, C = Lexicographic> {
106    memory: M,
107    order: C,
108    header: Header,
109}
110
111impl<M: Memory, C: Comparator> BTreeMap<M, C> {
112    /// Create a tree in an empty memory without overwriting an existing format.
113    ///
114    /// # Errors
115    /// Rejects nonempty memory, invalid limits, and refused growth.
116    pub fn create(memory: M, config: Config, order: C) -> Result<Self, Error> {
117        config.check()?;
118        if memory.size() != 0 {
119            return Err(Error::NotEmpty);
120        }
121        let mut tree = Self {
122            memory,
123            order,
124            header: Header::new(config, C::IDENTITY),
125        };
126        let mut tx = Transaction::new(&tree);
127        let root = tx.allocate()?;
128        tx.put_node(root, &Node::leaf())?;
129        tx.header.root = root;
130        tree.header = tx.commit()?;
131        Ok(tree)
132    }
133
134    /// Open a tree with its exact configuration and comparator identity.
135    ///
136    /// Opening checks the header and root, not every page. Reads validate pages
137    /// as visited; [`Self::validate`] performs a complete structural audit.
138    /// # Errors
139    /// Rejects unknown formats, incompatible limits/order, and observed corruption.
140    pub fn open(memory: M, config: Config, order: C) -> Result<Self, Error> {
141        config.check()?;
142        let header = Header::read(&memory)?;
143        if header.config != config || header.identity != C::IDENTITY {
144            return Err(Error::Configuration);
145        }
146        let tree = Self {
147            memory,
148            order,
149            header,
150        };
151        tree.node(tree.header.root)?;
152        Ok(tree)
153    }
154
155    /// Number of live entries, persisted with each completed mutation.
156    #[must_use]
157    pub const fn len(&self) -> u64 {
158        self.header.len
159    }
160    /// Whether the tree contains no entries.
161    #[must_use]
162    pub const fn is_empty(&self) -> bool {
163        self.header.len == 0
164    }
165    /// Bytes reserved in the backing memory, including reusable free pages.
166    #[must_use]
167    pub fn memory_bytes(&self) -> u64 {
168        self.memory.size().saturating_mul(65_536)
169    }
170    /// Release the sole handle and return its backing memory for later reopening.
171    #[must_use]
172    pub fn into_memory(self) -> M {
173        self.memory
174    }
175
176    /// Read one value by key.
177    /// # Errors
178    /// Rejects malformed keys, oversized keys, and observed storage corruption.
179    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
180        self.find(key)?
181            .map(|(_, node, at)| self.value(node.entries[at]))
182            .transpose()
183    }
184
185    /// Test key membership without reading the value payload.
186    /// # Errors
187    /// Rejects invalid keys and observed key or tree-page corruption.
188    pub fn contains_key(&self, key: &[u8]) -> Result<bool, Error> {
189        Ok(self.find(key)?.is_some())
190    }
191
192    fn find(&self, key: &[u8]) -> Result<Option<(u64, Node, usize)>, Error> {
193        self.check_key(key)?;
194        let mut id = self.header.root;
195        for _ in 0..page::MAX_HEIGHT {
196            let node = self.node(id)?;
197            let (at, found) = self.locate(&node, key)?;
198            if found {
199                return Ok(Some((id, node, at)));
200            }
201            if node.children.is_empty() {
202                return Ok(None);
203            }
204            id = node.children[at];
205        }
206        Err(Error::Corrupt)
207    }
208
209    /// Insert or replace a value, returning the previous value if present.
210    /// # Errors
211    /// Rejects invalid keys, oversized inputs, corruption, or insufficient capacity
212    /// before publishing changes. Memory traps follow the backing contract.
213    pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<Option<Vec<u8>>, Error> {
214        self.check_key(key)?;
215        if value.len() > self.header.config.max_value_bytes as usize {
216            return Err(Error::TooLarge);
217        }
218        let located = self.find(key)?;
219        let mut tx = Transaction::new(self);
220        let previous = if let Some((id, node, at)) = located {
221            Some(tx.replace(id, node, at, value)?)
222        } else {
223            tx.insert(key, value)?;
224            tx.header.len = tx.header.len.checked_add(1).ok_or(Error::Capacity)?;
225            None
226        };
227        self.header = tx.commit()?;
228        Ok(previous)
229    }
230
231    /// Remove a key, returning its previous value.
232    /// # Errors
233    /// Rejects malformed keys and observed corruption without publishing changes.
234    pub fn remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
235        self.check_key(key)?;
236        let mut tx = Transaction::new(self);
237        let Some(previous) = tx.remove(key)? else {
238            // Any speculative balancing remains private to the discarded transaction.
239            return Ok(None);
240        };
241        tx.header.len = tx.header.len.checked_sub(1).ok_or(Error::Corrupt)?;
242        self.header = tx.commit()?;
243        Ok(Some(previous))
244    }
245
246    /// Iterate a bounded range in either direction without reading value payloads.
247    ///
248    /// Bounds use the supplied comparator. Equal exclusive bounds yield an empty
249    /// range. `rev()` and interleaved `next`/`next_back` are supported.
250    /// # Errors
251    /// Rejects invalid bounds; traversal errors are yielded by the iterator.
252    pub fn range(
253        &self,
254        lower: Bound<&[u8]>,
255        upper: Bound<&[u8]>,
256    ) -> Result<Range<'_, M, C>, Error> {
257        Range::new(self, lower, upper)
258    }
259
260    /// Traverse all entries in comparator order, with lazy value reads.
261    #[must_use]
262    pub fn iter(&self) -> Range<'_, M, C> {
263        Range::all(self)
264    }
265
266    /// Check every reachable page, key order, depth, occupancy, entry count and
267    /// free-list ownership. This explicit audit is linear in allocated storage.
268    /// # Errors
269    /// Returns an error on corruption, invalid encoded keys or invalid ordering.
270    pub fn validate(&self) -> Result<(), Error> {
271        transaction::validate(self)
272    }
273
274    fn check_key(&self, key: &[u8]) -> Result<(), Error> {
275        if key.len() > self.header.config.max_key_bytes as usize {
276            return Err(Error::TooLarge);
277        }
278        if self.order.compare(key, key)? != Ordering::Equal {
279            return Err(Error::InvalidKey);
280        }
281        Ok(())
282    }
283    fn node(&self, id: u64) -> Result<Node, Error> {
284        let mut page = [0; page::SIZE];
285        page::read_into(&self.memory, self.header.next, id, &mut page)?;
286        Node::decode(&page, self.header.next, self.header.config)
287    }
288    fn key(&self, record: Record) -> Result<Vec<u8>, Error> {
289        if record.key == 0 {
290            return Ok(record.key_inline
291                [..usize::try_from(record.key_len).map_err(|_| Error::Corrupt)?]
292                .to_vec());
293        }
294        page::blob(&self.memory, self.header.next, record.key, record.key_len)
295    }
296    fn value(&self, record: Record) -> Result<Vec<u8>, Error> {
297        if record.value == 0 {
298            return Ok(record.value_inline
299                [..usize::try_from(record.value_len).map_err(|_| Error::Corrupt)?]
300                .to_vec());
301        }
302        page::blob(
303            &self.memory,
304            self.header.next,
305            record.value,
306            record.value_len,
307        )
308    }
309    fn locate(&self, node: &Node, key: &[u8]) -> Result<(usize, bool), Error> {
310        let (mut lo, mut hi) = (0, node.entries.len());
311        while lo < hi {
312            let mid = lo + (hi - lo) / 2;
313            let record = node.entries[mid];
314            let order = if record.key != 0 && record.key_len <= page::PAYLOAD as u64 {
315                let mut buffer = [0; page::SIZE];
316                page::read_into(&self.memory, self.header.next, record.key, &mut buffer)?;
317                self.order
318                    .compare(page::single_blob(&buffer, record.key_len)?, key)?
319            } else {
320                self.order.compare(&self.key(record)?, key)?
321            };
322            match order {
323                Ordering::Less => lo = mid + 1,
324                Ordering::Equal => return Ok((mid, true)),
325                Ordering::Greater => hi = mid,
326            }
327        }
328        Ok((lo, false))
329    }
330}
331
332impl<'a, M: Memory, C: Comparator> IntoIterator for &'a BTreeMap<M, C> {
333    type Item = Result<Entry<'a, M, C>, Error>;
334    type IntoIter = Range<'a, M, C>;
335    fn into_iter(self) -> Self::IntoIter {
336        self.iter()
337    }
338}