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}