1use std::pin::Pin;
7use std::sync::Arc;
8
9use futures::io::AsyncRead;
10use futures::stream::{self, Stream};
11use futures::AsyncReadExt;
12
13use crate::builder::{BuilderError, DEFAULT_CHUNK_SIZE, DEFAULT_MAX_LINKS};
14use crate::codec::{
15 decode_tree_node, encode_and_hash, is_directory_node, is_tree_node, try_decode_tree_node,
16};
17use crate::hash::sha256;
18use crate::reader::{ReaderError, TreeEntry, WalkEntry};
19use crate::store::Store;
20use crate::types::{to_hex, Cid, DirEntry, Hash, Link, LinkType, TreeNode};
21
22use crate::crypto::{decrypt_chk, encrypt_chk, EncryptionKey};
23
24#[path = "hashtree/stream.rs"]
25mod read_stream;
26mod walk;
27
28#[derive(Clone)]
30pub struct HashTreeConfig<S: Store> {
31 pub store: Arc<S>,
32 pub chunk_size: usize,
33 pub max_links: usize,
34 pub encrypted: bool,
36}
37
38impl<S: Store> HashTreeConfig<S> {
39 pub fn new(store: Arc<S>) -> Self {
40 Self {
41 store,
42 chunk_size: DEFAULT_CHUNK_SIZE,
43 max_links: DEFAULT_MAX_LINKS,
44 encrypted: true,
45 }
46 }
47
48 pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
49 self.chunk_size = chunk_size;
50 self
51 }
52
53 pub fn with_max_links(mut self, max_links: usize) -> Self {
54 self.max_links = max_links;
55 self
56 }
57
58 pub fn public(mut self) -> Self {
60 self.encrypted = false;
61 self
62 }
63}
64
65#[derive(Debug, thiserror::Error)]
67pub enum HashTreeError {
68 #[error("Store error: {0}")]
69 Store(String),
70 #[error("Codec error: {0}")]
71 Codec(#[from] crate::codec::CodecError),
72 #[error("Missing chunk: {0}")]
73 MissingChunk(String),
74 #[error("Path not found: {0}")]
75 PathNotFound(String),
76 #[error("Entry not found: {0}")]
77 EntryNotFound(String),
78 #[error("Encryption error: {0}")]
79 Encryption(String),
80 #[error("Decryption error: {0}")]
81 Decryption(String),
82 #[error("Content size {actual_size} exceeds max_size {max_size}")]
83 SizeLimitExceeded { max_size: u64, actual_size: u64 },
84}
85
86impl From<BuilderError> for HashTreeError {
87 fn from(e: BuilderError) -> Self {
88 match e {
89 BuilderError::Store(s) => HashTreeError::Store(s),
90 BuilderError::Codec(c) => HashTreeError::Codec(c),
91 BuilderError::Encryption(s) => HashTreeError::Encryption(s),
92 }
93 }
94}
95
96impl From<ReaderError> for HashTreeError {
97 fn from(e: ReaderError) -> Self {
98 match e {
99 ReaderError::Store(s) => HashTreeError::Store(s),
100 ReaderError::Codec(c) => HashTreeError::Codec(c),
101 ReaderError::MissingChunk(s) => HashTreeError::MissingChunk(s),
102 ReaderError::Decryption(s) => HashTreeError::Encryption(s),
103 ReaderError::MissingKey => {
104 HashTreeError::Encryption("missing decryption key".to_string())
105 }
106 }
107 }
108}
109
110pub struct HashTree<S: Store> {
112 store: Arc<S>,
113 chunk_size: usize,
114 max_links: usize,
115 encrypted: bool,
116}
117
118impl<S: Store> HashTree<S> {
119 fn internal_chunk_start(name: &str) -> Option<usize> {
120 let suffix = name.strip_prefix("_chunk_")?;
121 if suffix.is_empty() || !suffix.bytes().all(|byte| byte.is_ascii_digit()) {
122 return None;
123 }
124 suffix.parse().ok()
125 }
126
127 fn node_uses_directory_fanout(node: &TreeNode) -> bool {
128 !node.links.is_empty()
129 && node.links.iter().all(|link| {
130 let Some(name) = link.name.as_deref() else {
131 return false;
132 };
133 Self::internal_chunk_start(name).is_some() && link.link_type == LinkType::Dir
134 })
135 }
136
137 fn is_internal_directory_link_with_fanout(link: &Link, uses_fanout: bool) -> bool {
138 if !uses_fanout || link.link_type != LinkType::Dir {
139 return false;
140 }
141
142 let Some(name) = link.name.as_deref() else {
143 return false;
144 };
145 Self::internal_chunk_start(name).is_some()
146 }
147
148 fn is_internal_directory_link(node: &TreeNode, link: &Link) -> bool {
149 Self::is_internal_directory_link_with_fanout(link, Self::node_uses_directory_fanout(node))
150 }
151
152 pub fn new(config: HashTreeConfig<S>) -> Self {
153 Self {
154 store: config.store,
155 chunk_size: config.chunk_size,
156 max_links: config.max_links,
157 encrypted: config.encrypted,
158 }
159 }
160
161 pub fn is_encrypted(&self) -> bool {
163 self.encrypted
164 }
165
166 pub async fn put(&self, data: &[u8]) -> Result<(Cid, u64), HashTreeError> {
171 let size = data.len() as u64;
172
173 if data.len() <= self.chunk_size {
175 let (hash, key) = self.put_chunk_internal(data).await?;
176 return Ok((Cid { hash, key }, size));
177 }
178
179 let mut links: Vec<Link> = Vec::new();
181 let mut offset = 0;
182
183 while offset < data.len() {
184 let end = (offset + self.chunk_size).min(data.len());
185 let chunk = &data[offset..end];
186 let chunk_size = chunk.len() as u64;
187 let (hash, key) = self.put_chunk_internal(chunk).await?;
188 links.push(Link {
189 hash,
190 name: None,
191 size: chunk_size,
192 key,
193 link_type: LinkType::Blob, meta: None,
195 });
196 offset = end;
197 }
198
199 let (root_hash, root_key) = self.build_tree_internal(links, Some(size)).await?;
201 Ok((
202 Cid {
203 hash: root_hash,
204 key: root_key,
205 },
206 size,
207 ))
208 }
209
210 pub async fn get(
215 &self,
216 cid: &Cid,
217 max_size: Option<u64>,
218 ) -> Result<Option<Vec<u8>>, HashTreeError> {
219 if let Some(key) = cid.key {
220 self.get_encrypted(&cid.hash, &key, max_size).await
221 } else {
222 self.read_file_with_limit(&cid.hash, max_size).await
223 }
224 }
225
226 pub async fn put_stream<R: AsyncRead + Unpin>(
232 &self,
233 mut reader: R,
234 ) -> Result<(Cid, u64), HashTreeError> {
235 let mut buffer = vec![0u8; self.chunk_size];
236 let mut links = Vec::new();
237 let mut total_size: u64 = 0;
238 let mut consistent_key: Option<[u8; 32]> = None;
239
240 loop {
241 let mut chunk = Vec::new();
242 let mut bytes_read = 0;
243
244 while bytes_read < self.chunk_size {
246 let n = reader
247 .read(&mut buffer[..self.chunk_size - bytes_read])
248 .await
249 .map_err(|e| HashTreeError::Store(format!("read error: {}", e)))?;
250 if n == 0 {
251 break; }
253 chunk.extend_from_slice(&buffer[..n]);
254 bytes_read += n;
255 }
256
257 if chunk.is_empty() {
258 break; }
260
261 let chunk_len = chunk.len() as u64;
262 total_size += chunk_len;
263
264 let (hash, key) = self.put_chunk_internal(&chunk).await?;
265
266 if links.is_empty() {
268 consistent_key = key;
269 } else if consistent_key != key {
270 consistent_key = None;
271 }
272
273 links.push(Link {
274 hash,
275 name: None,
276 size: chunk_len,
277 key,
278 link_type: LinkType::Blob, meta: None,
280 });
281 }
282
283 if links.is_empty() {
284 let (hash, key) = self.put_chunk_internal(&[]).await?;
286 return Ok((Cid { hash, key }, 0));
287 }
288
289 let (root_hash, root_key) = self.build_tree_internal(links, Some(total_size)).await?;
291 Ok((
292 Cid {
293 hash: root_hash,
294 key: root_key,
295 },
296 total_size,
297 ))
298 }
299
300 async fn put_chunk_internal(
302 &self,
303 data: &[u8],
304 ) -> Result<(Hash, Option<EncryptionKey>), HashTreeError> {
305 if self.encrypted {
306 let (encrypted, key) =
307 encrypt_chk(data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
308 let hash = sha256(&encrypted);
309 self.store
310 .put(hash, encrypted)
311 .await
312 .map_err(|e| HashTreeError::Store(e.to_string()))?;
313 Ok((hash, Some(key)))
314 } else {
315 let hash = self.put_blob(data).await?;
316 Ok((hash, None))
317 }
318 }
319
320 async fn build_tree_internal(
322 &self,
323 links: Vec<Link>,
324 total_size: Option<u64>,
325 ) -> Result<(Hash, Option<[u8; 32]>), HashTreeError> {
326 if links.len() == 1 {
328 if let Some(ts) = total_size {
329 if links[0].size == ts {
330 return Ok((links[0].hash, links[0].key));
331 }
332 }
333 }
334
335 if links.len() <= self.max_links {
336 let node = TreeNode {
337 node_type: LinkType::File,
338 links,
339 };
340 let (data, _) = encode_and_hash(&node)?;
341
342 if self.encrypted {
343 let (encrypted, key) =
344 encrypt_chk(&data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
345 let hash = sha256(&encrypted);
346 self.store
347 .put(hash, encrypted)
348 .await
349 .map_err(|e| HashTreeError::Store(e.to_string()))?;
350 return Ok((hash, Some(key)));
351 }
352
353 let hash = sha256(&data);
355 self.store
356 .put(hash, data)
357 .await
358 .map_err(|e| HashTreeError::Store(e.to_string()))?;
359 return Ok((hash, None));
360 }
361
362 let mut sub_links = Vec::new();
364 for batch in links.chunks(self.max_links) {
365 let batch_size: u64 = batch.iter().map(|l| l.size).sum();
366 let (hash, key) =
367 Box::pin(self.build_tree_internal(batch.to_vec(), Some(batch_size))).await?;
368 sub_links.push(Link {
369 hash,
370 name: None,
371 size: batch_size,
372 key,
373 link_type: LinkType::File, meta: None,
375 });
376 }
377
378 Box::pin(self.build_tree_internal(sub_links, total_size)).await
379 }
380
381 async fn get_encrypted(
383 &self,
384 hash: &Hash,
385 key: &EncryptionKey,
386 max_size: Option<u64>,
387 ) -> Result<Option<Vec<u8>>, HashTreeError> {
388 let decrypted = match self.get_encrypted_root(hash, key).await? {
389 Some(data) => data,
390 None => return Ok(None),
391 };
392
393 if is_tree_node(&decrypted) {
395 let node = decode_tree_node(&decrypted)?;
396 let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
397 Self::ensure_size_limit(max_size, declared_size)?;
398
399 let mut bytes_read = 0u64;
400 let assembled = self
401 .assemble_encrypted_chunks_limited(&node, max_size, &mut bytes_read)
402 .await?;
403 return Ok(Some(assembled));
404 }
405
406 Self::ensure_size_limit(max_size, decrypted.len() as u64)?;
408 Ok(Some(decrypted))
409 }
410
411 async fn get_encrypted_root(
412 &self,
413 hash: &Hash,
414 key: &EncryptionKey,
415 ) -> Result<Option<Vec<u8>>, HashTreeError> {
416 self.get_cid_root_bytes(&Cid {
417 hash: *hash,
418 key: Some(*key),
419 })
420 .await
421 .map_err(|err| match err {
422 HashTreeError::Decryption(message) => HashTreeError::Encryption(message),
423 other => other,
424 })
425 }
426
427 fn ensure_size_limit(max_size: Option<u64>, actual_size: u64) -> Result<(), HashTreeError> {
428 if let Some(max_size) = max_size {
429 if actual_size > max_size {
430 return Err(HashTreeError::SizeLimitExceeded {
431 max_size,
432 actual_size,
433 });
434 }
435 }
436 Ok(())
437 }
438
439 async fn assemble_encrypted_chunks_limited(
441 &self,
442 node: &TreeNode,
443 max_size: Option<u64>,
444 bytes_read: &mut u64,
445 ) -> Result<Vec<u8>, HashTreeError> {
446 let mut parts: Vec<Vec<u8>> = Vec::new();
447
448 for link in &node.links {
449 let projected = (*bytes_read).saturating_add(link.size);
450 Self::ensure_size_limit(max_size, projected)?;
451
452 let chunk_key = link
453 .key
454 .ok_or_else(|| HashTreeError::Encryption("missing chunk key".to_string()))?;
455
456 let encrypted_child = self
457 .store
458 .get(&link.hash)
459 .await
460 .map_err(|e| HashTreeError::Store(e.to_string()))?
461 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
462
463 let decrypted = decrypt_chk(&encrypted_child, &chunk_key)
464 .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
465
466 if is_tree_node(&decrypted) {
467 let child_node = decode_tree_node(&decrypted)?;
469 let child_data = Box::pin(self.assemble_encrypted_chunks_limited(
470 &child_node,
471 max_size,
472 bytes_read,
473 ))
474 .await?;
475 parts.push(child_data);
476 } else {
477 let projected = (*bytes_read).saturating_add(decrypted.len() as u64);
479 Self::ensure_size_limit(max_size, projected)?;
480 *bytes_read = projected;
481 parts.push(decrypted);
482 }
483 }
484
485 let total_len: usize = parts.iter().map(|p| p.len()).sum();
486 let mut result = Vec::with_capacity(total_len);
487 for part in parts {
488 result.extend_from_slice(&part);
489 }
490
491 Ok(result)
492 }
493
494 pub async fn put_blob(&self, data: &[u8]) -> Result<Hash, HashTreeError> {
499 let hash = sha256(data);
500 self.store
501 .put(hash, data.to_vec())
502 .await
503 .map_err(|e| HashTreeError::Store(e.to_string()))?;
504 Ok(hash)
505 }
506
507 pub async fn put_file(&self, data: &[u8]) -> Result<(Cid, u64), HashTreeError> {
510 let size = data.len() as u64;
511
512 if data.len() <= self.chunk_size {
514 let (hash, key) = self.put_chunk_internal(data).await?;
515 return Ok((Cid { hash, key }, size));
516 }
517
518 let mut links: Vec<Link> = Vec::new();
520 let mut offset = 0;
521
522 while offset < data.len() {
523 let end = (offset + self.chunk_size).min(data.len());
524 let chunk = &data[offset..end];
525 let chunk_size = (end - offset) as u64;
526
527 let (hash, key) = self.put_chunk_internal(chunk).await?;
528 links.push(Link {
529 hash,
530 name: None,
531 size: chunk_size,
532 key,
533 link_type: LinkType::Blob, meta: None,
535 });
536 offset = end;
537 }
538
539 let (root_hash, root_key) = self.build_tree_internal(links, Some(size)).await?;
541 Ok((
542 Cid {
543 hash: root_hash,
544 key: root_key,
545 },
546 size,
547 ))
548 }
549
550 pub async fn put_directory(&self, entries: Vec<DirEntry>) -> Result<Cid, HashTreeError> {
555 let mut sorted = entries;
557 sorted.sort_by(|a, b| a.name.cmp(&b.name));
558
559 let links: Vec<Link> = sorted
560 .into_iter()
561 .map(|e| Link {
562 hash: e.hash,
563 name: Some(e.name),
564 size: e.size,
565 key: e.key,
566 link_type: e.link_type,
567 meta: e.meta,
568 })
569 .collect();
570
571 if links.len() <= self.max_links {
572 return self.put_directory_node(links).await;
573 }
574
575 self.build_directory_by_chunks(links).await
576 }
577
578 async fn put_directory_node(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
579 let node = TreeNode {
580 node_type: LinkType::Dir,
581 links,
582 };
583 let (data, plain_hash) = encode_and_hash(&node)?;
584
585 if self.encrypted {
586 let (encrypted, key) =
587 encrypt_chk(&data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
588 let hash = sha256(&encrypted);
589 self.store
590 .put(hash, encrypted)
591 .await
592 .map_err(|e| HashTreeError::Store(e.to_string()))?;
593 return Ok(Cid {
594 hash,
595 key: Some(key),
596 });
597 }
598
599 self.store
600 .put(plain_hash, data)
601 .await
602 .map_err(|e| HashTreeError::Store(e.to_string()))?;
603 Ok(Cid {
604 hash: plain_hash,
605 key: None,
606 })
607 }
608
609 async fn build_directory_by_chunks(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
610 let indexed_links: Vec<(usize, Link)> = links.into_iter().enumerate().collect();
611 self.build_indexed_directory_chunks(indexed_links).await
612 }
613
614 async fn build_indexed_directory_chunks(
615 &self,
616 links: Vec<(usize, Link)>,
617 ) -> Result<Cid, HashTreeError> {
618 let mut sub_trees: Vec<(usize, Link)> = Vec::new();
619
620 for (i, batch) in links.chunks(self.max_links).enumerate() {
621 let start = batch
622 .first()
623 .map(|(start, _)| *start)
624 .unwrap_or(i * self.max_links);
625 let batch_size: u64 = batch.iter().map(|(_, link)| link.size).sum();
626 let child_links = batch.iter().map(|(_, link)| link.clone()).collect();
627 let child_cid = self.put_directory_node(child_links).await?;
628
629 sub_trees.push((
630 start,
631 Link {
632 hash: child_cid.hash,
633 name: Some(format!("_chunk_{start}")),
634 size: batch_size,
635 key: child_cid.key,
636 link_type: LinkType::Dir,
637 meta: None,
638 },
639 ));
640 }
641
642 if sub_trees.len() <= self.max_links {
643 return self
644 .put_directory_node(sub_trees.into_iter().map(|(_, link)| link).collect())
645 .await;
646 }
647
648 Box::pin(self.build_indexed_directory_chunks(sub_trees)).await
649 }
650
651 pub async fn put_tree_node(&self, links: Vec<Link>) -> Result<Hash, HashTreeError> {
653 let node = TreeNode {
654 node_type: LinkType::Dir,
655 links,
656 };
657
658 let (data, hash) = encode_and_hash(&node)?;
659 self.store
660 .put(hash, data)
661 .await
662 .map_err(|e| HashTreeError::Store(e.to_string()))?;
663 Ok(hash)
664 }
665
666 pub async fn get_blob(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
670 self.store
671 .get(hash)
672 .await
673 .map_err(|e| HashTreeError::Store(e.to_string()))
674 }
675
676 async fn has_stored_chunk(&self, hash: &Hash) -> Result<bool, HashTreeError> {
677 self.store
678 .get(hash)
679 .await
680 .map(|data| data.is_some())
681 .map_err(|e| HashTreeError::Store(e.to_string()))
682 }
683
684 pub async fn get_tree_node(&self, hash: &Hash) -> Result<Option<TreeNode>, HashTreeError> {
686 let data = match self
687 .store
688 .get(hash)
689 .await
690 .map_err(|e| HashTreeError::Store(e.to_string()))?
691 {
692 Some(d) => d,
693 None => return Ok(None),
694 };
695
696 if !is_tree_node(&data) {
697 return Ok(None);
698 }
699
700 let node = decode_tree_node(&data)?;
701 Ok(Some(node))
702 }
703
704 async fn get_cid_root_bytes(&self, cid: &Cid) -> Result<Option<Vec<u8>>, HashTreeError> {
705 let data = match self
706 .store
707 .get(&cid.hash)
708 .await
709 .map_err(|e| HashTreeError::Store(e.to_string()))?
710 {
711 Some(d) => d,
712 None => return Ok(None),
713 };
714
715 let Some(key) = &cid.key else {
716 return Ok(Some(data));
717 };
718
719 let raw_is_tree = is_tree_node(&data);
720 match decrypt_chk(&data, key) {
721 Ok(decrypted) => {
722 if is_tree_node(&decrypted) || !raw_is_tree {
723 Ok(Some(decrypted))
724 } else {
725 Ok(Some(data))
726 }
727 }
728 Err(err) => {
729 if raw_is_tree {
730 Ok(Some(data))
731 } else {
732 Err(HashTreeError::Decryption(err.to_string()))
733 }
734 }
735 }
736 }
737
738 pub async fn get_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
740 let decrypted = match self.get_cid_root_bytes(cid).await? {
741 Some(d) => d,
742 None => return Ok(None),
743 };
744
745 if !is_tree_node(&decrypted) {
746 return Ok(None);
747 }
748
749 let node = decode_tree_node(&decrypted)?;
750 Ok(Some(node))
751 }
752
753 pub async fn get_directory_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
756 let decrypted = match self.get_cid_root_bytes(cid).await? {
757 Some(d) => d,
758 None => return Ok(None),
759 };
760
761 if !is_tree_node(&decrypted) {
762 return Ok(None);
763 }
764
765 let node = decode_tree_node(&decrypted)?;
766
767 if node.node_type == LinkType::File {
769 let mut bytes_read = 0u64;
770 let assembled = self
771 .assemble_chunks_limited(&node, None, &mut bytes_read)
772 .await?;
773 if is_tree_node(&assembled) {
774 let inner_node = decode_tree_node(&assembled)?;
775 return Ok(Some(inner_node));
776 }
777 }
778
779 Ok(Some(node))
780 }
781
782 pub async fn is_tree(&self, hash: &Hash) -> Result<bool, HashTreeError> {
784 let data = match self
785 .store
786 .get(hash)
787 .await
788 .map_err(|e| HashTreeError::Store(e.to_string()))?
789 {
790 Some(d) => d,
791 None => return Ok(false),
792 };
793 Ok(is_tree_node(&data))
794 }
795
796 pub async fn is_dir(&self, cid: &Cid) -> Result<bool, HashTreeError> {
798 Ok(matches!(
799 self.get_directory_node(cid).await?,
800 Some(node) if node.node_type == LinkType::Dir
801 ))
802 }
803
804 pub async fn is_directory(&self, hash: &Hash) -> Result<bool, HashTreeError> {
806 let data = match self
807 .store
808 .get(hash)
809 .await
810 .map_err(|e| HashTreeError::Store(e.to_string()))?
811 {
812 Some(d) => d,
813 None => return Ok(false),
814 };
815 Ok(is_directory_node(&data))
816 }
817
818 pub async fn read_file(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
820 self.read_file_with_limit(hash, None).await
821 }
822
823 async fn read_file_with_limit(
825 &self,
826 hash: &Hash,
827 max_size: Option<u64>,
828 ) -> Result<Option<Vec<u8>>, HashTreeError> {
829 let data = match self
830 .store
831 .get(hash)
832 .await
833 .map_err(|e| HashTreeError::Store(e.to_string()))?
834 {
835 Some(d) => d,
836 None => return Ok(None),
837 };
838
839 if !is_tree_node(&data) {
841 Self::ensure_size_limit(max_size, data.len() as u64)?;
842 return Ok(Some(data));
843 }
844
845 let node = decode_tree_node(&data)?;
847 let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
848 Self::ensure_size_limit(max_size, declared_size)?;
849
850 let mut bytes_read = 0u64;
851 let assembled = self
852 .assemble_chunks_limited(&node, max_size, &mut bytes_read)
853 .await?;
854 Ok(Some(assembled))
855 }
856
857 pub async fn read_file_range(
864 &self,
865 hash: &Hash,
866 start: u64,
867 end: Option<u64>,
868 ) -> Result<Option<Vec<u8>>, HashTreeError> {
869 let data = match self
870 .store
871 .get(hash)
872 .await
873 .map_err(|e| HashTreeError::Store(e.to_string()))?
874 {
875 Some(d) => d,
876 None => return Ok(None),
877 };
878
879 if !is_tree_node(&data) {
881 let start_idx = start as usize;
882 let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
883 if start_idx >= data.len() {
884 return Ok(Some(vec![]));
885 }
886 let end_idx = end_idx.min(data.len());
887 return Ok(Some(data[start_idx..end_idx].to_vec()));
888 }
889
890 let node = decode_tree_node(&data)?;
892 let range_data = self.assemble_chunks_range(&node, start, end).await?;
893 Ok(Some(range_data))
894 }
895
896 pub async fn read_file_range_cid(
898 &self,
899 cid: &Cid,
900 start: u64,
901 end: Option<u64>,
902 ) -> Result<Option<Vec<u8>>, HashTreeError> {
903 if let Some(key) = cid.key {
904 let data = match self.get_encrypted_root(&cid.hash, &key).await? {
905 Some(d) => d,
906 None => return Ok(None),
907 };
908
909 if is_tree_node(&data) {
910 let node = decode_tree_node(&data)?;
911 let total_size: u64 = node.links.iter().map(|link| link.size).sum();
912 let actual_end = end.unwrap_or(total_size).min(total_size);
913 if start >= actual_end {
914 return Ok(Some(vec![]));
915 }
916
917 let mut result = Vec::with_capacity((actual_end - start) as usize);
918 self.append_encrypted_range(&node, start, actual_end, 0, &mut result)
919 .await?;
920 return Ok(Some(result));
921 }
922
923 let start_idx = start as usize;
924 let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
925 if start_idx >= data.len() {
926 return Ok(Some(vec![]));
927 }
928 let end_idx = end_idx.min(data.len());
929 return Ok(Some(data[start_idx..end_idx].to_vec()));
930 }
931
932 self.read_file_range(&cid.hash, start, end).await
933 }
934
935 async fn append_encrypted_range(
936 &self,
937 node: &TreeNode,
938 start: u64,
939 end: u64,
940 base_offset: u64,
941 result: &mut Vec<u8>,
942 ) -> Result<(), HashTreeError> {
943 let mut current_offset = base_offset;
944
945 for link in &node.links {
946 let child_start = current_offset;
947 let child_end = child_start.saturating_add(link.size);
948 current_offset = child_end;
949
950 if child_end <= start {
951 continue;
952 }
953 if child_start >= end {
954 break;
955 }
956
957 let chunk_key = link
958 .key
959 .ok_or_else(|| HashTreeError::Encryption("missing chunk key".to_string()))?;
960
961 let encrypted_child = self
962 .store
963 .get(&link.hash)
964 .await
965 .map_err(|e| HashTreeError::Store(e.to_string()))?
966 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
967 let decrypted_child = decrypt_chk(&encrypted_child, &chunk_key)
968 .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
969
970 if is_tree_node(&decrypted_child) {
971 let child_node = decode_tree_node(&decrypted_child)?;
972 Box::pin(self.append_encrypted_range(&child_node, start, end, child_start, result))
973 .await?;
974 continue;
975 }
976
977 let slice_start = if start > child_start {
978 (start - child_start) as usize
979 } else {
980 0
981 };
982 let slice_end = if end < child_end {
983 (end - child_start) as usize
984 } else {
985 decrypted_child.len()
986 };
987 result.extend_from_slice(&decrypted_child[slice_start..slice_end]);
988 }
989
990 Ok(())
991 }
992
993 async fn assemble_chunks_range(
995 &self,
996 node: &TreeNode,
997 start: u64,
998 end: Option<u64>,
999 ) -> Result<Vec<u8>, HashTreeError> {
1000 let chunks_info = self.collect_chunk_offsets(node).await?;
1002
1003 if chunks_info.is_empty() {
1004 return Ok(vec![]);
1005 }
1006
1007 let total_size: u64 = chunks_info.iter().map(|(_, _, size)| size).sum();
1009 let actual_end = end.unwrap_or(total_size).min(total_size);
1010
1011 if start >= actual_end {
1012 return Ok(vec![]);
1013 }
1014
1015 let mut result = Vec::with_capacity((actual_end - start) as usize);
1017 let mut current_offset = 0u64;
1018
1019 for (chunk_hash, _chunk_offset, chunk_size) in &chunks_info {
1020 let chunk_start = current_offset;
1021 let chunk_end = current_offset + chunk_size;
1022
1023 if chunk_end > start && chunk_start < actual_end {
1025 let chunk_data = self
1027 .store
1028 .get(chunk_hash)
1029 .await
1030 .map_err(|e| HashTreeError::Store(e.to_string()))?
1031 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(chunk_hash)))?;
1032
1033 let slice_start = if start > chunk_start {
1035 (start - chunk_start) as usize
1036 } else {
1037 0
1038 };
1039 let slice_end = if actual_end < chunk_end {
1040 (actual_end - chunk_start) as usize
1041 } else {
1042 chunk_data.len()
1043 };
1044
1045 result.extend_from_slice(&chunk_data[slice_start..slice_end]);
1046 }
1047
1048 current_offset = chunk_end;
1049
1050 if current_offset >= actual_end {
1052 break;
1053 }
1054 }
1055
1056 Ok(result)
1057 }
1058
1059 async fn collect_chunk_offsets(
1062 &self,
1063 node: &TreeNode,
1064 ) -> Result<Vec<(Hash, u64, u64)>, HashTreeError> {
1065 let mut chunks = Vec::new();
1066 let mut offset = 0u64;
1067 self.collect_chunk_offsets_recursive(node, &mut chunks, &mut offset)
1068 .await?;
1069 Ok(chunks)
1070 }
1071
1072 async fn collect_chunk_offsets_recursive(
1073 &self,
1074 node: &TreeNode,
1075 chunks: &mut Vec<(Hash, u64, u64)>,
1076 offset: &mut u64,
1077 ) -> Result<(), HashTreeError> {
1078 for link in &node.links {
1079 let child_data = self
1080 .store
1081 .get(&link.hash)
1082 .await
1083 .map_err(|e| HashTreeError::Store(e.to_string()))?
1084 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1085
1086 if is_tree_node(&child_data) {
1087 let child_node = decode_tree_node(&child_data)?;
1089 Box::pin(self.collect_chunk_offsets_recursive(&child_node, chunks, offset)).await?;
1090 } else {
1091 let size = child_data.len() as u64;
1093 chunks.push((link.hash, *offset, size));
1094 *offset += size;
1095 }
1096 }
1097 Ok(())
1098 }
1099
1100 async fn assemble_chunks_limited(
1102 &self,
1103 node: &TreeNode,
1104 max_size: Option<u64>,
1105 bytes_read: &mut u64,
1106 ) -> Result<Vec<u8>, HashTreeError> {
1107 let mut parts: Vec<Vec<u8>> = Vec::new();
1108
1109 for link in &node.links {
1110 let projected = (*bytes_read).saturating_add(link.size);
1111 Self::ensure_size_limit(max_size, projected)?;
1112
1113 let child_data = self
1114 .store
1115 .get(&link.hash)
1116 .await
1117 .map_err(|e| HashTreeError::Store(e.to_string()))?
1118 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1119
1120 if is_tree_node(&child_data) {
1121 let child_node = decode_tree_node(&child_data)?;
1122 parts.push(
1123 Box::pin(self.assemble_chunks_limited(&child_node, max_size, bytes_read))
1124 .await?,
1125 );
1126 } else {
1127 let projected = (*bytes_read).saturating_add(child_data.len() as u64);
1128 Self::ensure_size_limit(max_size, projected)?;
1129 *bytes_read = projected;
1130 parts.push(child_data);
1131 }
1132 }
1133
1134 let total_length: usize = parts.iter().map(|p| p.len()).sum();
1136 let mut result = Vec::with_capacity(total_length);
1137 for part in parts {
1138 result.extend_from_slice(&part);
1139 }
1140
1141 Ok(result)
1142 }
1143
1144 pub async fn read_file_chunks(&self, hash: &Hash) -> Result<Vec<Vec<u8>>, HashTreeError> {
1146 let data = match self
1147 .store
1148 .get(hash)
1149 .await
1150 .map_err(|e| HashTreeError::Store(e.to_string()))?
1151 {
1152 Some(d) => d,
1153 None => return Ok(vec![]),
1154 };
1155
1156 if !is_tree_node(&data) {
1157 return Ok(vec![data]);
1158 }
1159
1160 let node = decode_tree_node(&data)?;
1161 self.collect_chunks(&node).await
1162 }
1163
1164 async fn collect_chunks(&self, node: &TreeNode) -> Result<Vec<Vec<u8>>, HashTreeError> {
1165 let mut chunks = Vec::new();
1166
1167 for link in &node.links {
1168 let child_data = self
1169 .store
1170 .get(&link.hash)
1171 .await
1172 .map_err(|e| HashTreeError::Store(e.to_string()))?
1173 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1174
1175 if is_tree_node(&child_data) {
1176 let child_node = decode_tree_node(&child_data)?;
1177 chunks.extend(Box::pin(self.collect_chunks(&child_node)).await?);
1178 } else {
1179 chunks.push(child_data);
1180 }
1181 }
1182
1183 Ok(chunks)
1184 }
1185
1186 pub async fn list(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1188 let node = match self.get_node(cid).await? {
1189 Some(n) => n,
1190 None => return Ok(vec![]),
1191 };
1192
1193 let mut entries = Vec::new();
1194
1195 for link in &node.links {
1196 if Self::is_internal_directory_link(&node, link) {
1198 let chunk_cid = Cid {
1199 hash: link.hash,
1200 key: link.key,
1201 };
1202 let sub_entries = Box::pin(self.list(&chunk_cid)).await?;
1203 entries.extend(sub_entries);
1204 continue;
1205 }
1206
1207 entries.push(TreeEntry {
1208 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1209 hash: link.hash,
1210 size: link.size,
1211 link_type: link.link_type,
1212 key: link.key,
1213 meta: link.meta.clone(),
1214 });
1215 }
1216
1217 Ok(entries)
1218 }
1219
1220 pub async fn list_directory(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1223 let node = match self.get_directory_node(cid).await? {
1225 Some(n) => n,
1226 None => return Ok(vec![]),
1227 };
1228
1229 let mut entries = Vec::new();
1230
1231 for link in &node.links {
1232 if Self::is_internal_directory_link(&node, link) {
1234 let sub_cid = Cid {
1235 hash: link.hash,
1236 key: link.key,
1237 };
1238 let sub_entries = Box::pin(self.list_directory(&sub_cid)).await?;
1239 entries.extend(sub_entries);
1240 continue;
1241 }
1242
1243 entries.push(TreeEntry {
1244 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1245 hash: link.hash,
1246 size: link.size,
1247 link_type: link.link_type,
1248 key: link.key,
1249 meta: link.meta.clone(),
1250 });
1251 }
1252
1253 Ok(entries)
1254 }
1255
1256 pub async fn resolve(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1258 let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
1259 if parts.is_empty() {
1260 return Ok(Some(cid.clone()));
1261 }
1262
1263 let mut current_cid = cid.clone();
1264
1265 for part in parts {
1266 let node = match self.get_directory_node(¤t_cid).await? {
1268 Some(n) => n,
1269 None => {
1270 if !self.has_stored_chunk(¤t_cid.hash).await? {
1271 return Err(HashTreeError::MissingChunk(to_hex(¤t_cid.hash)));
1272 }
1273 return Ok(None);
1274 }
1275 };
1276
1277 if let Some(link) = self.find_link(&node, part) {
1278 current_cid = Cid {
1279 hash: link.hash,
1280 key: link.key,
1281 };
1282 } else {
1283 match self
1285 .find_link_in_subtrees_cid(&node, part, ¤t_cid)
1286 .await?
1287 {
1288 Some(link) => {
1289 current_cid = Cid {
1290 hash: link.hash,
1291 key: link.key,
1292 };
1293 }
1294 None => return Ok(None),
1295 }
1296 }
1297 }
1298
1299 Ok(Some(current_cid))
1300 }
1301
1302 pub async fn resolve_path(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1304 self.resolve(cid, path).await
1305 }
1306
1307 fn find_link(&self, node: &TreeNode, name: &str) -> Option<Link> {
1308 node.links
1309 .iter()
1310 .find(|l| l.name.as_deref() == Some(name))
1311 .cloned()
1312 }
1313
1314 async fn find_link_in_subtrees_cid(
1316 &self,
1317 node: &TreeNode,
1318 name: &str,
1319 _parent_cid: &Cid,
1320 ) -> Result<Option<Link>, HashTreeError> {
1321 for link in &node.links {
1322 if !Self::is_internal_directory_link(node, link) {
1323 continue;
1324 }
1325
1326 let sub_cid = Cid {
1328 hash: link.hash,
1329 key: link.key,
1330 };
1331
1332 let sub_node = match self.get_node(&sub_cid).await? {
1333 Some(n) => n,
1334 None => {
1335 if !self.has_stored_chunk(&sub_cid.hash).await? {
1336 return Err(HashTreeError::MissingChunk(to_hex(&sub_cid.hash)));
1337 }
1338 continue;
1339 }
1340 };
1341
1342 if let Some(found) = self.find_link(&sub_node, name) {
1343 return Ok(Some(found));
1344 }
1345
1346 if let Some(deep_found) =
1347 Box::pin(self.find_link_in_subtrees_cid(&sub_node, name, &sub_cid)).await?
1348 {
1349 return Ok(Some(deep_found));
1350 }
1351 }
1352
1353 Ok(None)
1354 }
1355
1356 pub async fn get_size(&self, hash: &Hash) -> Result<u64, HashTreeError> {
1358 let data = match self
1359 .store
1360 .get(hash)
1361 .await
1362 .map_err(|e| HashTreeError::Store(e.to_string()))?
1363 {
1364 Some(d) => d,
1365 None => return Ok(0),
1366 };
1367
1368 if !is_tree_node(&data) {
1369 return Ok(data.len() as u64);
1370 }
1371
1372 let node = decode_tree_node(&data)?;
1373 let mut total = 0u64;
1375 for link in &node.links {
1376 total += link.size;
1377 }
1378 Ok(total)
1379 }
1380
1381 pub async fn get_size_cid(&self, cid: &Cid) -> Result<u64, HashTreeError> {
1383 if let Some(key) = cid.key {
1384 let data = match self.get_encrypted_root(&cid.hash, &key).await? {
1385 Some(d) => d,
1386 None => return Ok(0),
1387 };
1388 if is_tree_node(&data) {
1389 let node = decode_tree_node(&data)?;
1390 return Ok(node.links.iter().map(|link| link.size).sum());
1391 }
1392 return Ok(data.len() as u64);
1393 }
1394
1395 self.get_size(&cid.hash).await
1396 }
1397
1398 pub async fn set_entry(
1403 &self,
1404 root: &Cid,
1405 path: &[&str],
1406 name: &str,
1407 entry_cid: &Cid,
1408 size: u64,
1409 link_type: LinkType,
1410 ) -> Result<Cid, HashTreeError> {
1411 self.set_entry_with_meta(root, path, name, entry_cid, size, link_type, None)
1412 .await
1413 }
1414
1415 pub async fn set_entry_with_meta(
1418 &self,
1419 root: &Cid,
1420 path: &[&str],
1421 name: &str,
1422 entry_cid: &Cid,
1423 size: u64,
1424 link_type: LinkType,
1425 meta: Option<std::collections::HashMap<String, serde_json::Value>>,
1426 ) -> Result<Cid, HashTreeError> {
1427 let dir_cid = self.resolve_path_array(root, path).await?;
1428 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1429
1430 let entries = self.list_directory(&dir_cid).await?;
1431 let mut new_entries: Vec<DirEntry> = entries
1432 .into_iter()
1433 .filter(|e| e.name != name)
1434 .map(|e| DirEntry {
1435 name: e.name,
1436 hash: e.hash,
1437 size: e.size,
1438 key: e.key,
1439 link_type: e.link_type,
1440 meta: e.meta,
1441 })
1442 .collect();
1443
1444 new_entries.push(DirEntry {
1445 name: name.to_string(),
1446 hash: entry_cid.hash,
1447 size,
1448 key: entry_cid.key,
1449 link_type,
1450 meta,
1451 });
1452
1453 let new_dir_cid = self.put_directory(new_entries).await?;
1454 self.rebuild_path(root, path, new_dir_cid).await
1455 }
1456
1457 pub async fn remove_entry(
1460 &self,
1461 root: &Cid,
1462 path: &[&str],
1463 name: &str,
1464 ) -> Result<Cid, HashTreeError> {
1465 let dir_cid = self.resolve_path_array(root, path).await?;
1466 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1467
1468 let entries = self.list_directory(&dir_cid).await?;
1469 let new_entries: Vec<DirEntry> = entries
1470 .into_iter()
1471 .filter(|e| e.name != name)
1472 .map(|e| DirEntry {
1473 name: e.name,
1474 hash: e.hash,
1475 size: e.size,
1476 key: e.key,
1477 link_type: e.link_type,
1478 meta: e.meta,
1479 })
1480 .collect();
1481
1482 let new_dir_cid = self.put_directory(new_entries).await?;
1483 self.rebuild_path(root, path, new_dir_cid).await
1484 }
1485
1486 pub async fn rename_entry(
1489 &self,
1490 root: &Cid,
1491 path: &[&str],
1492 old_name: &str,
1493 new_name: &str,
1494 ) -> Result<Cid, HashTreeError> {
1495 if old_name == new_name {
1496 return Ok(root.clone());
1497 }
1498
1499 let dir_cid = self.resolve_path_array(root, path).await?;
1500 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1501
1502 let entries = self.list_directory(&dir_cid).await?;
1503 let entry = entries
1504 .iter()
1505 .find(|e| e.name == old_name)
1506 .ok_or_else(|| HashTreeError::EntryNotFound(old_name.to_string()))?;
1507
1508 let entry_hash = entry.hash;
1509 let entry_size = entry.size;
1510 let entry_key = entry.key;
1511 let entry_link_type = entry.link_type;
1512 let entry_meta = entry.meta.clone();
1513
1514 let new_entries: Vec<DirEntry> = entries
1515 .into_iter()
1516 .filter(|e| e.name != old_name)
1517 .map(|e| DirEntry {
1518 name: e.name,
1519 hash: e.hash,
1520 size: e.size,
1521 key: e.key,
1522 link_type: e.link_type,
1523 meta: e.meta,
1524 })
1525 .chain(std::iter::once(DirEntry {
1526 name: new_name.to_string(),
1527 hash: entry_hash,
1528 size: entry_size,
1529 key: entry_key,
1530 link_type: entry_link_type,
1531 meta: entry_meta,
1532 }))
1533 .collect();
1534
1535 let new_dir_cid = self.put_directory(new_entries).await?;
1536 self.rebuild_path(root, path, new_dir_cid).await
1537 }
1538
1539 pub async fn move_entry(
1542 &self,
1543 root: &Cid,
1544 source_path: &[&str],
1545 name: &str,
1546 target_path: &[&str],
1547 ) -> Result<Cid, HashTreeError> {
1548 let source_dir_cid = self.resolve_path_array(root, source_path).await?;
1549 let source_dir_cid =
1550 source_dir_cid.ok_or_else(|| HashTreeError::PathNotFound(source_path.join("/")))?;
1551
1552 let source_entries = self.list_directory(&source_dir_cid).await?;
1553 let entry = source_entries
1554 .iter()
1555 .find(|e| e.name == name)
1556 .ok_or_else(|| HashTreeError::EntryNotFound(name.to_string()))?;
1557
1558 let entry_cid = Cid {
1559 hash: entry.hash,
1560 key: entry.key,
1561 };
1562 let entry_size = entry.size;
1563 let entry_link_type = entry.link_type;
1564
1565 let new_root = self.remove_entry(root, source_path, name).await?;
1567
1568 self.set_entry(
1570 &new_root,
1571 target_path,
1572 name,
1573 &entry_cid,
1574 entry_size,
1575 entry_link_type,
1576 )
1577 .await
1578 }
1579
1580 async fn resolve_path_array(
1581 &self,
1582 root: &Cid,
1583 path: &[&str],
1584 ) -> Result<Option<Cid>, HashTreeError> {
1585 if path.is_empty() {
1586 return Ok(Some(root.clone()));
1587 }
1588 self.resolve_path(root, &path.join("/")).await
1589 }
1590
1591 async fn rebuild_path(
1592 &self,
1593 root: &Cid,
1594 path: &[&str],
1595 new_child: Cid,
1596 ) -> Result<Cid, HashTreeError> {
1597 if path.is_empty() {
1598 return Ok(new_child);
1599 }
1600
1601 let mut child_cid = new_child;
1602 let parts: Vec<&str> = path.to_vec();
1603
1604 for i in (0..parts.len()).rev() {
1605 let child_name = parts[i];
1606 let parent_path = &parts[..i];
1607
1608 let parent_cid = if parent_path.is_empty() {
1609 root.clone()
1610 } else {
1611 self.resolve_path_array(root, parent_path)
1612 .await?
1613 .ok_or_else(|| HashTreeError::PathNotFound(parent_path.join("/")))?
1614 };
1615
1616 let parent_entries = self.list_directory(&parent_cid).await?;
1617 let new_parent_entries: Vec<DirEntry> = parent_entries
1618 .into_iter()
1619 .map(|e| {
1620 if e.name == child_name {
1621 DirEntry {
1622 name: e.name,
1623 hash: child_cid.hash,
1624 size: 0, key: child_cid.key,
1626 link_type: e.link_type,
1627 meta: e.meta,
1628 }
1629 } else {
1630 DirEntry {
1631 name: e.name,
1632 hash: e.hash,
1633 size: e.size,
1634 key: e.key,
1635 link_type: e.link_type,
1636 meta: e.meta,
1637 }
1638 }
1639 })
1640 .collect();
1641
1642 child_cid = self.put_directory(new_parent_entries).await?;
1643 }
1644
1645 Ok(child_cid)
1646 }
1647
1648 pub fn get_store(&self) -> Arc<S> {
1652 self.store.clone()
1653 }
1654
1655 pub fn chunk_size(&self) -> usize {
1657 self.chunk_size
1658 }
1659
1660 pub fn max_links(&self) -> usize {
1662 self.max_links
1663 }
1664}
1665
1666pub async fn verify_tree<S: Store>(
1668 store: Arc<S>,
1669 root_hash: &Hash,
1670) -> Result<crate::reader::VerifyResult, HashTreeError> {
1671 let mut missing = Vec::new();
1672 let mut visited = std::collections::HashSet::new();
1673
1674 verify_recursive(store, root_hash, &mut missing, &mut visited).await?;
1675
1676 Ok(crate::reader::VerifyResult {
1677 valid: missing.is_empty(),
1678 missing,
1679 })
1680}
1681
1682async fn verify_recursive<S: Store>(
1683 store: Arc<S>,
1684 hash: &Hash,
1685 missing: &mut Vec<Hash>,
1686 visited: &mut std::collections::HashSet<String>,
1687) -> Result<(), HashTreeError> {
1688 let hex = to_hex(hash);
1689 if visited.contains(&hex) {
1690 return Ok(());
1691 }
1692 visited.insert(hex);
1693
1694 let data = match store
1695 .get(hash)
1696 .await
1697 .map_err(|e| HashTreeError::Store(e.to_string()))?
1698 {
1699 Some(d) => d,
1700 None => {
1701 missing.push(*hash);
1702 return Ok(());
1703 }
1704 };
1705
1706 if is_tree_node(&data) {
1707 let node = decode_tree_node(&data)?;
1708 for link in &node.links {
1709 Box::pin(verify_recursive(
1710 store.clone(),
1711 &link.hash,
1712 missing,
1713 visited,
1714 ))
1715 .await?;
1716 }
1717 }
1718
1719 Ok(())
1720}
1721
1722#[cfg(test)]
1723mod tests;