Skip to main content

didbot_data/mst/
read.rs

1//! Reading a node back out of a block.
2//!
3//! [`Tree`](super::Tree) builds nodes and hands out their blocks. This is the
4//! other direction, and it is the one that has to be suspicious: a node that
5//! arrives in a CAR file or a firehose frame was encoded by somebody else, and
6//! the encoding has several ways to say something that is not a node.
7//!
8//! Everything structural is checked here, so that a [`Node`] that exists is
9//! one a tree can be walked over:
10//!
11//! * the map is exactly `l` and `e`, and each entry exactly `p`, `k`, `v`
12//!   and `t`. A spare field is a different block with a different CID, so a
13//!   node that carried one and was re-encoded would change its own name.
14//! * a prefix length that reaches past the previous key is refused, and so
15//!   is one that is *shorter* than it could be — if the byte after the shared
16//!   part matches, the encoder should have shared it. Both spellings name the
17//!   same key and only one of them is the block that key belongs in.
18//! * keys strictly ascend. A node whose entries are out of order has no
19//!   defined lookup.
20//! * every key in one node sits at the same layer, which is what a layer
21//!   *is*. A node mixing heights is a tree no other implementation would have
22//!   built.
23//!
24//! What is deliberately not checked is anything needing another block: that a
25//! subtree is one layer down, that it holds keys in the right range, that the
26//! links resolve at all. A node is one block, and those are claims about a
27//! tree — see [`Store`](super::Store), which checks them as it walks.
28
29use crate::cid::Cid;
30use crate::dag_cbor::{self, CborError};
31use crate::value::Value;
32
33use super::{key_height, Entry, Node};
34
35/// Why a block is not a Merkle search tree node.
36#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
37pub enum NodeError {
38    /// The block is not DAG-CBOR at all.
39    #[error("a node block is not dag-cbor: {0}")]
40    Cbor(#[from] CborError),
41    /// The block decoded, and to something that is not a node.
42    #[error("entry {at}: {why}")]
43    Shape {
44        /// Which entry the problem is in, or `0` for the node itself.
45        at: usize,
46        /// What was wrong.
47        why: String,
48    },
49    /// A key that is not UTF-8.
50    ///
51    /// A repository path is a collection NSID and a record key, both of which
52    /// are ASCII by rule. The tree is keyed on bytes and this crate is happy
53    /// to *compare* bytes, but a path it cannot name is one it cannot report
54    /// in a diff or an operation, so it is refused at the door rather than
55    /// lost in the middle.
56    #[error("entry {at}: the key is not valid utf-8")]
57    KeyNotUtf8 {
58        /// Which entry the key is in.
59        at: usize,
60    },
61}
62
63impl NodeError {
64    /// A shape error at one entry.
65    fn shape(at: usize, why: impl Into<String>) -> Self {
66        Self::Shape {
67            at,
68            why: why.into(),
69        }
70    }
71}
72
73/// A node's contents in the form the tree algorithms want them.
74///
75/// The encoding stores each key against the previous one, which is compact
76/// and useless to walk over. This is the same node with the keys rebuilt and
77/// the subtrees interleaved the way the protocol describes them:
78///
79/// ```text
80/// keys:          (0,    1,    2,    3)
81/// values:        (0,    1,    2,    3)
82/// subtrees:   (0,    1,    2,    3,    4)
83/// ```
84///
85/// There is always exactly one more subtree than there are keys: the one
86/// before the first key is the node's `l`, and each entry's `t` is the one
87/// after it.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct Parts {
90    /// The full keys, ascending.
91    pub keys: Vec<String>,
92    /// The record each key names.
93    pub values: Vec<Cid>,
94    /// The subtrees between and around the keys; always `keys.len() + 1`.
95    pub subtrees: Vec<Option<Cid>>,
96}
97
98impl Node {
99    /// Decodes one node out of its block.
100    ///
101    /// ```
102    /// use std::collections::BTreeMap;
103    /// use didbot_data::mst::{Node, Tree};
104    ///
105    /// let tree = Tree::build(&BTreeMap::new());
106    /// let block = &tree.blocks()[tree.root()];
107    /// assert_eq!(Node::decode(block).unwrap(), Node::default());
108    /// ```
109    pub fn decode(block: &[u8]) -> Result<Self, NodeError> {
110        Self::from_value(&dag_cbor::decode(block)?)
111    }
112
113    /// Reads a node out of an already-decoded value.
114    pub fn from_value(value: &Value) -> Result<Self, NodeError> {
115        let Value::Map(fields) = value else {
116            return Err(NodeError::shape(0, "a node is a map"));
117        };
118        if fields.len() != 2 || !fields.contains_key("l") || !fields.contains_key("e") {
119            return Err(NodeError::shape(0, "a node is exactly the fields l and e"));
120        }
121        let left = link_or_null(&fields["l"]).ok_or_else(|| {
122            NodeError::shape(0, "l is a link to the subtree left of every entry, or null")
123        })?;
124        let Value::List(entries) = &fields["e"] else {
125            return Err(NodeError::shape(0, "e is a list of entries"));
126        };
127
128        let mut node = Node {
129            left,
130            entries: Vec::with_capacity(entries.len()),
131        };
132        let mut previous: Vec<u8> = Vec::new();
133        let mut layer: Option<usize> = None;
134        for (at, entry) in entries.iter().enumerate() {
135            let key = read_entry(at, entry, &previous, &mut node)?;
136            let height = key_height(&key);
137            match layer {
138                None => layer = Some(height),
139                Some(layer) if layer == height => {}
140                Some(layer) => {
141                    return Err(NodeError::shape(
142                        at,
143                        format!("{key:?} is at layer {height}, and this node is layer {layer}"),
144                    ))
145                }
146            }
147            previous = key.into_bytes();
148        }
149        Ok(node)
150    }
151
152    /// The node with its keys rebuilt; see [`Parts`].
153    ///
154    /// Every key was checked to be UTF-8 when the node was decoded, and a
155    /// node this crate built has UTF-8 keys because it was handed strings. A
156    /// node assembled by hand out of arbitrary bytes is the one case where
157    /// this is lossy, and it is the case [`Node::decode`] refuses.
158    #[must_use]
159    pub fn parts(&self) -> Parts {
160        let mut parts = Parts {
161            keys: Vec::with_capacity(self.entries.len()),
162            values: Vec::with_capacity(self.entries.len()),
163            subtrees: Vec::with_capacity(self.entries.len() + 1),
164        };
165        parts.subtrees.push(self.left.clone());
166        let mut previous: Vec<u8> = Vec::new();
167        for entry in &self.entries {
168            let mut key = previous[..entry.prefix.min(previous.len())].to_vec();
169            key.extend_from_slice(&entry.suffix);
170            previous = key.clone();
171            parts.keys.push(String::from_utf8_lossy(&key).into_owned());
172            parts.values.push(entry.value.clone());
173            parts.subtrees.push(entry.tree.clone());
174        }
175        parts
176    }
177
178    /// Builds a node from full keys, values and subtrees.
179    ///
180    /// The inverse of [`Node::parts`], and the only way to make a node
181    /// without going through [`Tree::build`](super::Tree::build): the shared
182    /// prefixes are computed here, so a caller cannot hold a node whose
183    /// encoding disagrees with its keys.
184    ///
185    /// # Panics
186    ///
187    /// If there is not exactly one more subtree than there are keys, or if
188    /// the keys and values are of different lengths. Both are the caller
189    /// building something that is not a node.
190    #[must_use]
191    pub fn from_parts(parts: &Parts) -> Self {
192        assert_eq!(
193            parts.keys.len(),
194            parts.values.len(),
195            "a node names one record per key"
196        );
197        assert_eq!(
198            parts.subtrees.len(),
199            parts.keys.len() + 1,
200            "a node has one more subtree than it has keys"
201        );
202        let mut previous = "";
203        let entries = parts
204            .keys
205            .iter()
206            .zip(&parts.values)
207            .zip(&parts.subtrees[1..])
208            .map(|((key, value), tree)| {
209                let prefix = super::common_prefix_len(previous, key);
210                previous = key;
211                Entry {
212                    prefix,
213                    suffix: key.as_bytes()[prefix..].to_vec(),
214                    value: value.clone(),
215                    tree: tree.clone(),
216                }
217            })
218            .collect();
219        Self {
220            left: parts.subtrees[0].clone(),
221            entries,
222        }
223    }
224
225    /// The block this node encodes to, and the CID naming it.
226    #[must_use]
227    pub fn block(&self) -> (Cid, Vec<u8>) {
228        let block = dag_cbor::encode(&self.to_value());
229        (Cid::of_dag_cbor(&block), block)
230    }
231
232    /// Which layer this node is on, where that can be told from the node
233    /// alone.
234    ///
235    /// A node with keys is at the layer its keys are at — they all share one,
236    /// which [`Node::decode`] checks. A node with no keys and no left subtree
237    /// is the empty root, which is layer zero by definition. A node with no
238    /// keys and a left subtree is the empty intermediate the layering rule
239    /// produces, and it has no height of its own: it is one below whatever
240    /// pointed at it, and nothing inside it says what that was.
241    #[must_use]
242    pub fn layer(&self) -> Option<usize> {
243        // A node's first entry shares nothing with a predecessor it does not
244        // have, so its suffix is a whole key — the one key readable without
245        // rebuilding any other.
246        if let Some(entry) = self.entries.first() {
247            return Some(key_height(&String::from_utf8_lossy(&entry.suffix)));
248        }
249        if self.left.is_none() {
250            return Some(0);
251        }
252        None
253    }
254
255    /// Whether this is the empty node: no keys, and nothing below it.
256    ///
257    /// This is the node an account's first commit signs over, and it is also
258    /// what a subtree pointer of `null` means — which is why an empty node is
259    /// never *stored* as a subtree. A tree carrying one would have two
260    /// spellings of "nothing here".
261    #[must_use]
262    pub fn is_empty(&self) -> bool {
263        self.entries.is_empty() && self.left.is_none()
264    }
265}
266
267/// Reads one entry, appends it to `node`, and answers its full key.
268fn read_entry(
269    at: usize,
270    entry: &Value,
271    previous: &[u8],
272    node: &mut Node,
273) -> Result<String, NodeError> {
274    let Value::Map(fields) = entry else {
275        return Err(NodeError::shape(at, "an entry is a map"));
276    };
277    if fields.len() != 4
278        || !["p", "k", "v", "t"]
279            .iter()
280            .all(|key| fields.contains_key(*key))
281    {
282        return Err(NodeError::shape(
283            at,
284            "an entry is exactly the fields p, k, v and t",
285        ));
286    }
287    let Value::Integer(prefix) = fields["p"] else {
288        return Err(NodeError::shape(at, "p is the shared prefix length"));
289    };
290    let prefix = usize::try_from(prefix)
291        .map_err(|_| NodeError::shape(at, "p is a length and cannot be negative"))?;
292    let Value::Bytes(suffix) = &fields["k"] else {
293        return Err(NodeError::shape(at, "k is the rest of the key, as bytes"));
294    };
295    let Value::Link(value) = &fields["v"] else {
296        return Err(NodeError::shape(at, "v is a link to the record"));
297    };
298    let tree = link_or_null(&fields["t"])
299        .ok_or_else(|| NodeError::shape(at, "t is a link to a subtree, or null"))?;
300
301    if prefix > previous.len() {
302        return Err(NodeError::shape(
303            at,
304            format!(
305                "p is {prefix}, and the previous key is {} bytes",
306                previous.len()
307            ),
308        ));
309    }
310    // The encoder shares every byte it can, so a shared byte left unshared is
311    // a second spelling of the same key — a different block for a node that
312    // holds the same thing.
313    if previous.get(prefix).is_some() && previous.get(prefix) == suffix.first() {
314        return Err(NodeError::shape(
315            at,
316            "p is shorter than the prefix these keys share",
317        ));
318    }
319    let mut key = previous[..prefix].to_vec();
320    key.extend_from_slice(suffix);
321    if key.as_slice() <= previous {
322        return Err(NodeError::shape(at, "keys must ascend within a node"));
323    }
324    let key = String::from_utf8(key).map_err(|_| NodeError::KeyNotUtf8 { at })?;
325
326    node.entries.push(Entry {
327        prefix,
328        suffix: suffix.clone(),
329        value: value.clone(),
330        tree,
331    });
332    Ok(key)
333}
334
335/// Reads a field that is a link or `null`, and refuses anything else.
336///
337/// The outer `Option` is "this was not one of those two"; the inner one is
338/// the null. A field that is simply absent never reaches here — the field
339/// count is checked first.
340fn link_or_null(value: &Value) -> Option<Option<Cid>> {
341    match value {
342        Value::Null => Some(None),
343        Value::Link(cid) => Some(Some(cid.clone())),
344        _ => None,
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use std::collections::BTreeMap;
351
352    use super::*;
353    use crate::mst::Tree;
354
355    /// Every node of a built tree decodes back to the node it was built as.
356    /// This is the claim, and it is made over a tree deep enough to have
357    /// entries with shared prefixes, subtrees, and an empty intermediate.
358    #[test]
359    fn every_node_of_a_tree_decodes_to_itself() {
360        let keys: Vec<String> = (0..300)
361            .map(|i| format!("app.bsky.feed.post/3jt6wh3ck{i:03}"))
362            .collect();
363        let leaves: BTreeMap<String, Cid> = keys
364            .iter()
365            .map(|key| (key.clone(), Cid::of_dag_cbor(key.as_bytes())))
366            .collect();
367        let tree = Tree::build(&leaves);
368        for (cid, block) in tree.blocks() {
369            let node = Node::decode(block).expect("a node this crate built");
370            assert_eq!(&node, &tree.nodes()[cid]);
371            assert_eq!(node.block().0, *cid);
372            assert_eq!(Node::from_parts(&node.parts()), node);
373        }
374    }
375
376    /// A node re-encoded from its parts is the same block, which is what
377    /// makes `from_parts` usable for anything that rewrites a tree.
378    #[test]
379    fn parts_round_trip_through_the_encoding() {
380        let mut parts = Parts {
381            keys: vec!["app.bsky.feed.post/aaaa".to_owned()],
382            values: vec![Cid::of_dag_cbor(b"a record")],
383            subtrees: vec![None, None],
384        };
385        parts.keys.push("app.bsky.feed.post/aaab".to_owned());
386        parts.values.push(Cid::of_dag_cbor(b"another record"));
387        parts.subtrees.push(None);
388        let node = Node::from_parts(&parts);
389        assert_eq!(node.parts(), parts);
390        assert_eq!(Node::decode(&node.block().1).unwrap(), node);
391        // The second entry shares all but its last byte.
392        assert_eq!(node.entries[1].prefix, 22);
393    }
394
395    /// A prefix length that reaches past the previous key would index out of
396    /// bounds in a reader that trusted it.
397    #[test]
398    fn a_prefix_past_the_previous_key_is_refused() {
399        let block = node_block(&[("k", 40, b"x")]);
400        assert!(matches!(
401            Node::decode(&block),
402            Err(NodeError::Shape { at: 0, .. })
403        ));
404    }
405
406    /// Two spellings of one key: `p` may not be shorter than the prefix the
407    /// keys actually share, because the tree is content-addressed and a
408    /// second spelling is a second CID for one node.
409    #[test]
410    fn a_prefix_shorter_than_the_shared_one_is_refused() {
411        // "aa" then "ab": the shared prefix is one byte, so p=1 k="b".
412        let good = node_block(&[("", 0, b"aa"), ("", 1, b"b")]);
413        assert!(Node::decode(&good).is_ok());
414        let sloppy = node_block(&[("", 0, b"aa"), ("", 0, b"ab")]);
415        assert!(matches!(
416            Node::decode(&sloppy),
417            Err(NodeError::Shape { at: 1, .. })
418        ));
419    }
420
421    /// Entries out of order have no defined lookup, so the node is not one.
422    #[test]
423    fn keys_must_ascend() {
424        let descending = node_block(&[("", 0, b"bb"), ("", 0, b"aa")]);
425        assert!(matches!(
426            Node::decode(&descending),
427            Err(NodeError::Shape { at: 1, .. })
428        ));
429        let repeated = node_block(&[("", 0, b"aa"), ("", 0, b"aa")]);
430        assert!(Node::decode(&repeated).is_err());
431    }
432
433    /// A field the protocol does not define changes the block's CID, so a
434    /// node carrying one cannot be re-encoded as itself.
435    #[test]
436    fn a_spare_field_is_not_a_node() {
437        let mut map = BTreeMap::from([
438            ("l".to_owned(), Value::Null),
439            ("e".to_owned(), Value::List(Vec::new())),
440        ]);
441        assert!(Node::from_value(&Value::Map(map.clone())).is_ok());
442        map.insert("extra".to_owned(), Value::Integer(1));
443        assert!(Node::from_value(&Value::Map(map)).is_err());
444    }
445
446    /// Every key in a node is at the node's layer. A node mixing them is a
447    /// tree built by rules nobody else uses.
448    #[test]
449    fn a_node_holds_one_layer() {
450        // "asdf" is at layer 0 and "blue" is at layer 1.
451        let mixed = node_block(&[("", 0, b"asdf"), ("", 0, b"blue")]);
452        assert!(matches!(
453            Node::decode(&mixed),
454            Err(NodeError::Shape { at: 1, .. })
455        ));
456    }
457
458    /// Encodes a node from raw entry fields, including ones a correct encoder
459    /// would never write. The first tuple element is unused padding so the
460    /// cases read as (comment, p, k).
461    fn node_block(entries: &[(&str, i64, &[u8])]) -> Vec<u8> {
462        let value = Value::Map(BTreeMap::from([
463            ("l".to_owned(), Value::Null),
464            (
465                "e".to_owned(),
466                Value::List(
467                    entries
468                        .iter()
469                        .map(|(_, prefix, suffix)| {
470                            Value::Map(BTreeMap::from([
471                                ("p".to_owned(), Value::Integer(*prefix)),
472                                ("k".to_owned(), Value::Bytes((*suffix).to_vec())),
473                                ("v".to_owned(), Value::Link(Cid::of_dag_cbor(b"a record"))),
474                                ("t".to_owned(), Value::Null),
475                            ]))
476                        })
477                        .collect(),
478                ),
479            ),
480        ]));
481        dag_cbor::encode(&value)
482    }
483}