heddle_object_model/object/manifest/
build.rs1use std::collections::{BTreeMap, BTreeSet};
11
12use super::node::{
13 MANIFEST_BRANCH_WIDTH, MANIFEST_LEAF_MAX_ENTRIES, MANIFEST_ROUTE_LEVELS, ManifestBranch,
14 ManifestChild, ManifestDecodeError, ManifestKey, ManifestLeaf, ManifestNode, ManifestNodeError,
15 ManifestObject,
16};
17use crate::object::ContentHash;
18
19pub trait ManifestNodeSource {
24 fn node_bytes(&self, hash: &ContentHash) -> Option<&[u8]>;
25}
26
27pub trait ManifestNodeStore: ManifestNodeSource {
30 fn node_hashes(&self) -> Vec<ContentHash>;
31}
32
33impl ManifestNodeSource for BTreeMap<ContentHash, Vec<u8>> {
34 fn node_bytes(&self, hash: &ContentHash) -> Option<&[u8]> {
35 self.get(hash).map(Vec::as_slice)
36 }
37}
38
39impl ManifestNodeStore for BTreeMap<ContentHash, Vec<u8>> {
40 fn node_hashes(&self) -> Vec<ContentHash> {
41 self.keys().copied().collect()
42 }
43}
44
45impl ManifestNodeSource for std::collections::HashMap<ContentHash, Vec<u8>> {
46 fn node_bytes(&self, hash: &ContentHash) -> Option<&[u8]> {
47 self.get(hash).map(Vec::as_slice)
48 }
49}
50
51impl ManifestNodeStore for std::collections::HashMap<ContentHash, Vec<u8>> {
52 fn node_hashes(&self) -> Vec<ContentHash> {
53 self.keys().copied().collect()
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct BuiltManifest {
61 pub root: ContentHash,
62 pub nodes: BTreeMap<ContentHash, Vec<u8>>,
63 pub object_count: u64,
64 pub decoded_bytes: u64,
65}
66
67impl BuiltManifest {
68 pub fn node_hashes(&self) -> Vec<ContentHash> {
70 self.nodes.keys().copied().collect()
71 }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
75pub enum ManifestBuildError {
76 #[error(
77 "object key {key:?} appears twice with different declared sizes ({first} and {second})"
78 )]
79 ConflictingDuplicate {
80 key: ManifestKey,
81 first: u64,
82 second: u64,
83 },
84 #[error("total decoded bytes overflow u64")]
85 DecodedBytesOverflow,
86 #[error(transparent)]
87 Node(#[from] ManifestNodeError),
88}
89
90pub fn build_manifest(
96 objects: impl IntoIterator<Item = ManifestObject>,
97) -> Result<BuiltManifest, ManifestBuildError> {
98 let mut unique: BTreeMap<ManifestKey, ManifestObject> = BTreeMap::new();
99 for object in objects {
100 let key = object.key();
101 if let Some(existing) = unique.get(&key)
102 && existing.decoded_size != object.decoded_size
103 {
104 return Err(ManifestBuildError::ConflictingDuplicate {
105 key,
106 first: existing.decoded_size,
107 second: object.decoded_size,
108 });
109 }
110 unique.insert(key, object);
111 }
112
113 let entries: Vec<ManifestObject> = unique.into_values().collect();
114 let mut nodes = BTreeMap::new();
115 let summary = build_level(&entries, 0, &mut nodes)?;
116 Ok(BuiltManifest {
117 root: summary.hash,
118 nodes,
119 object_count: summary.object_count,
120 decoded_bytes: summary.decoded_bytes,
121 })
122}
123
124struct Summary {
125 hash: ContentHash,
126 object_count: u64,
127 decoded_bytes: u64,
128}
129
130fn build_level(
136 entries: &[ManifestObject],
137 depth: u8,
138 nodes: &mut BTreeMap<ContentHash, Vec<u8>>,
139) -> Result<Summary, ManifestBuildError> {
140 if entries.len() <= MANIFEST_LEAF_MAX_ENTRIES || depth >= MANIFEST_ROUTE_LEVELS {
141 let leaf = ManifestLeaf::new(entries.to_vec())?;
142 let object_count = leaf.object_count();
143 let decoded_bytes = leaf
144 .decoded_bytes()
145 .ok_or(ManifestBuildError::DecodedBytesOverflow)?;
146 let node = ManifestNode::Leaf(leaf);
147 let hash = insert(nodes, &node);
148 return Ok(Summary {
149 hash,
150 object_count,
151 decoded_bytes,
152 });
153 }
154
155 let mut buckets: Vec<Vec<ManifestObject>> = vec![Vec::new(); MANIFEST_BRANCH_WIDTH];
156 for entry in entries {
157 let slot = entry.key().route().group(depth);
158 buckets[usize::from(slot)].push(*entry);
159 }
160
161 let mut children = Vec::new();
162 let mut object_count = 0u64;
163 let mut decoded_bytes = 0u64;
164 for (slot, bucket) in buckets.iter().enumerate() {
165 if bucket.is_empty() {
166 continue;
167 }
168 let child = build_level(bucket, depth + 1, nodes)?;
169 object_count += child.object_count;
170 decoded_bytes = decoded_bytes
171 .checked_add(child.decoded_bytes)
172 .ok_or(ManifestBuildError::DecodedBytesOverflow)?;
173 children.push(ManifestChild {
174 slot: slot as u8,
175 hash: child.hash,
176 object_count: child.object_count,
177 decoded_bytes: child.decoded_bytes,
178 });
179 }
180
181 let branch = ManifestNode::Branch(ManifestBranch::new(depth, children)?);
182 let hash = insert(nodes, &branch);
183 Ok(Summary {
184 hash,
185 object_count,
186 decoded_bytes,
187 })
188}
189
190fn insert(nodes: &mut BTreeMap<ContentHash, Vec<u8>>, node: &ManifestNode) -> ContentHash {
191 let bytes = node.encode();
192 let hash = ContentHash::compute(&bytes);
193 nodes.insert(hash, bytes);
194 hash
195}
196
197#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
198pub enum ManifestExpandError {
199 #[error("manifest node {0} is missing from the node source")]
200 MissingNode(ContentHash),
201 #[error("manifest node {hash}: {source}")]
202 Decode {
203 hash: ContentHash,
204 #[source]
205 source: ManifestDecodeError,
206 },
207 #[error("manifest traversal exceeded the fixed route depth at node {0}")]
208 DepthExceeded(ContentHash),
209}
210
211pub fn expand_manifest<S: ManifestNodeSource + ?Sized>(
216 source: &S,
217 root: &ContentHash,
218) -> Result<Vec<ManifestObject>, ManifestExpandError> {
219 let mut objects = BTreeSet::new();
220 let mut visited = BTreeSet::new();
221 expand_node(source, root, 0, &mut visited, &mut objects)?;
222 Ok(objects.into_iter().collect())
223}
224
225fn expand_node<S: ManifestNodeSource + ?Sized>(
226 source: &S,
227 hash: &ContentHash,
228 depth: u8,
229 visited: &mut BTreeSet<ContentHash>,
230 objects: &mut BTreeSet<ManifestObject>,
231) -> Result<(), ManifestExpandError> {
232 if depth > MANIFEST_ROUTE_LEVELS {
233 return Err(ManifestExpandError::DepthExceeded(*hash));
234 }
235 if !visited.insert(*hash) {
236 return Ok(());
237 }
238 let bytes = source
239 .node_bytes(hash)
240 .ok_or(ManifestExpandError::MissingNode(*hash))?;
241 let node = ManifestNode::decode_addressed(bytes, hash).map_err(|source| {
242 ManifestExpandError::Decode {
243 hash: *hash,
244 source,
245 }
246 })?;
247 match node {
248 ManifestNode::Leaf(leaf) => {
249 objects.extend(leaf.entries().iter().copied());
250 }
251 ManifestNode::Branch(branch) => {
252 for child in branch.children() {
253 expand_node(source, &child.hash, depth + 1, visited, objects)?;
254 }
255 }
256 }
257 Ok(())
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::object::manifest::node::ManifestObjectKind;
264
265 fn object(seed: u32) -> ManifestObject {
266 let mut bytes = [0u8; 32];
267 bytes[..4].copy_from_slice(&seed.to_be_bytes());
268 ManifestObject::new(
269 if seed.is_multiple_of(3) {
270 ManifestObjectKind::Tree
271 } else {
272 ManifestObjectKind::Blob
273 },
274 ContentHash::compute(&bytes),
275 u64::from(seed) * 7 + 1,
276 )
277 }
278
279 fn objects(count: u32) -> Vec<ManifestObject> {
280 (0..count).map(object).collect()
281 }
282
283 #[test]
284 fn empty_set_builds_the_canonical_empty_root() {
285 let built = build_manifest([]).unwrap();
286 assert_eq!(built.root, ManifestNode::empty().address());
287 assert_eq!(built.object_count, 0);
288 assert_eq!(built.decoded_bytes, 0);
289 assert!(
290 expand_manifest(&built.nodes, &built.root)
291 .unwrap()
292 .is_empty()
293 );
294 }
295
296 #[test]
297 fn small_sets_stay_a_single_leaf() {
298 let built = build_manifest(objects(MANIFEST_LEAF_MAX_ENTRIES as u32)).unwrap();
299 assert_eq!(built.nodes.len(), 1);
300 let node = ManifestNode::decode(&built.nodes[&built.root]).unwrap();
301 assert!(matches!(node, ManifestNode::Leaf(_)));
302 }
303
304 #[test]
305 fn exceeding_the_leaf_bound_splits_into_a_branch() {
306 let built = build_manifest(objects(MANIFEST_LEAF_MAX_ENTRIES as u32 + 1)).unwrap();
307 let node = ManifestNode::decode(&built.nodes[&built.root]).unwrap();
308 let ManifestNode::Branch(branch) = node else {
309 panic!("expected a branch root");
310 };
311 assert_eq!(branch.depth(), 0);
312 let total: u64 = branch.children().iter().map(|c| c.object_count).sum();
313 assert_eq!(total, MANIFEST_LEAF_MAX_ENTRIES as u64 + 1);
314 }
315
316 #[test]
317 fn build_is_order_independent_and_root_stable() {
318 let mut forward = objects(200);
319 let mut reversed = forward.clone();
320 reversed.reverse();
321 forward.push(forward[7]);
323
324 let a = build_manifest(forward).unwrap();
325 let b = build_manifest(reversed).unwrap();
326 assert_eq!(a.root, b.root);
327 assert_eq!(a.nodes, b.nodes);
328 assert_eq!(a.object_count, 200);
329 }
330
331 #[test]
332 fn expansion_round_trips_the_object_set_in_key_order() {
333 let mut expected = objects(500);
334 let built = build_manifest(expected.clone()).unwrap();
335 let expanded = expand_manifest(&built.nodes, &built.root).unwrap();
336 expected.sort_by_key(ManifestObject::key);
337 assert_eq!(expanded, expected);
338 assert_eq!(built.object_count, expanded.len() as u64);
339 assert_eq!(
340 built.decoded_bytes,
341 expanded.iter().map(|o| o.decoded_size).sum::<u64>()
342 );
343 }
344
345 #[test]
346 fn replacing_one_object_rewrites_only_a_bounded_path() {
347 let base = objects(2_000);
348 let before = build_manifest(base.clone()).unwrap();
349
350 let mut after_objects = base.clone();
351 after_objects[1_234] = object(999_999);
352 let after = build_manifest(after_objects).unwrap();
353
354 assert_ne!(before.root, after.root);
355 let rewritten = after
356 .nodes
357 .keys()
358 .filter(|hash| !before.nodes.contains_key(*hash))
359 .count();
360 assert!(
363 rewritten <= usize::from(MANIFEST_ROUTE_LEVELS),
364 "rewrote {rewritten} nodes for a single object change"
365 );
366 assert!(
367 rewritten * 8 < before.nodes.len(),
368 "rewrote {rewritten} of {} nodes; structural sharing is not holding",
369 before.nodes.len()
370 );
371 }
372
373 #[test]
374 fn an_unchanged_object_set_reuses_the_root_byte_for_byte() {
375 let set = objects(300);
378 let a = build_manifest(set.clone()).unwrap();
379 let b = build_manifest(set).unwrap();
380 assert_eq!(a.root, b.root);
381 assert_eq!(a.nodes[&a.root], b.nodes[&b.root]);
382 }
383
384 #[test]
385 fn conflicting_duplicate_sizes_are_rejected() {
386 let first = object(1);
387 let second = ManifestObject::new(first.kind, first.hash, first.decoded_size + 1);
388 let err = build_manifest([first, second]).unwrap_err();
389 assert!(matches!(
390 err,
391 ManifestBuildError::ConflictingDuplicate { .. }
392 ));
393 }
394
395 #[test]
396 fn expansion_reports_a_missing_node_rather_than_a_partial_answer() {
397 let built = build_manifest(objects(100)).unwrap();
398 let mut nodes = built.nodes.clone();
399 let victim = *nodes
400 .keys()
401 .find(|hash| **hash != built.root)
402 .expect("branch root has children");
403 nodes.remove(&victim);
404 assert_eq!(
405 expand_manifest(&nodes, &built.root).unwrap_err(),
406 ManifestExpandError::MissingNode(victim)
407 );
408 }
409}