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