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