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 let size = data.len() as u64;
581
582 if data.len() <= self.chunk_size {
584 let (hash, key) = self.put_chunk_internal(data).await?;
585 return Ok((Cid { hash, key }, size));
586 }
587
588 let mut links: Vec<Link> = Vec::new();
590 let mut offset = 0;
591
592 while offset < data.len() {
593 let end = (offset + self.chunk_size).min(data.len());
594 let chunk = &data[offset..end];
595 let chunk_size = (end - offset) as u64;
596
597 let (hash, key) = self.put_chunk_internal(chunk).await?;
598 links.push(Link {
599 hash,
600 name: None,
601 size: chunk_size,
602 key,
603 link_type: LinkType::Blob, meta: None,
605 });
606 offset = end;
607 }
608
609 let (root_hash, root_key, _) = self.build_tree_internal(links, Some(size)).await?;
611 Ok((
612 Cid {
613 hash: root_hash,
614 key: root_key,
615 },
616 size,
617 ))
618 }
619
620 pub async fn put_directory(&self, entries: Vec<DirEntry>) -> Result<Cid, HashTreeError> {
625 let mut sorted = entries;
627 sorted.sort_by(|a, b| a.name.cmp(&b.name));
628
629 let links: Vec<Link> = sorted
630 .into_iter()
631 .map(|e| Link {
632 hash: e.hash,
633 name: Some(e.name),
634 size: e.size,
635 key: e.key,
636 link_type: e.link_type,
637 meta: e.meta,
638 })
639 .collect();
640
641 if links.len() <= self.max_links {
642 return self.put_directory_node(links).await;
643 }
644
645 self.build_directory_by_chunks(links).await
646 }
647
648 async fn put_directory_node(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
649 self.put_tree_node_with_type(LinkType::Dir, links).await
650 }
651
652 async fn put_tree_node_with_type(
653 &self,
654 node_type: LinkType,
655 links: Vec<Link>,
656 ) -> Result<Cid, HashTreeError> {
657 let node = TreeNode { node_type, links };
658 let (data, plain_hash) = encode_and_hash(&node)?;
659
660 if self.encrypted {
661 let (encrypted, key) =
662 encrypt_chk(&data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
663 let hash = sha256(&encrypted);
664 self.store
665 .put(hash, encrypted)
666 .await
667 .map_err(|e| HashTreeError::Store(e.to_string()))?;
668 return Ok(Cid {
669 hash,
670 key: Some(key),
671 });
672 }
673
674 self.store
675 .put(plain_hash, data)
676 .await
677 .map_err(|e| HashTreeError::Store(e.to_string()))?;
678 Ok(Cid {
679 hash: plain_hash,
680 key: None,
681 })
682 }
683
684 async fn build_directory_by_chunks(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
685 let spans = links
686 .into_iter()
687 .map(|link| {
688 let name = link.name.clone().unwrap_or_else(|| to_hex(&link.hash));
689 DirectoryFanoutSpan {
690 link,
691 count: 1,
692 first: name.clone(),
693 last: name,
694 }
695 })
696 .collect();
697 self.build_directory_fanout_level(spans, LinkType::Dir)
698 .await
699 }
700
701 async fn build_directory_fanout_level(
702 &self,
703 spans: Vec<DirectoryFanoutSpan>,
704 child_node_type: LinkType,
705 ) -> Result<Cid, HashTreeError> {
706 let mut sub_trees: Vec<DirectoryFanoutSpan> = Vec::new();
707
708 for batch in spans.chunks(self.max_links) {
709 let Some(first_span) = batch.first() else {
710 continue;
711 };
712 let last_span = batch.last().expect("non-empty fanout batch");
713 let count: usize = batch.iter().map(|span| span.count).sum();
714 let batch_size: u64 = batch.iter().map(|span| span.link.size).sum();
715 let child_links = batch.iter().map(|span| span.link.clone()).collect();
716 let child_cid = self
717 .put_tree_node_with_type(child_node_type, child_links)
718 .await?;
719
720 sub_trees.push(DirectoryFanoutSpan {
721 link: Link {
722 hash: child_cid.hash,
723 name: None,
724 size: batch_size,
725 key: child_cid.key,
726 link_type: child_node_type,
727 meta: Some(directory_fanout_meta(
728 count,
729 &first_span.first,
730 &last_span.last,
731 )),
732 },
733 count,
734 first: first_span.first.clone(),
735 last: last_span.last.clone(),
736 });
737 }
738
739 if sub_trees.len() <= self.max_links {
740 return self
741 .put_tree_node_with_type(
742 LinkType::Fanout,
743 sub_trees.into_iter().map(|span| span.link).collect(),
744 )
745 .await;
746 }
747
748 Box::pin(self.build_directory_fanout_level(sub_trees, LinkType::Fanout)).await
749 }
750
751 pub async fn put_tree_node(&self, links: Vec<Link>) -> Result<Hash, HashTreeError> {
753 let node = TreeNode {
754 node_type: LinkType::Dir,
755 links,
756 };
757
758 let (data, hash) = encode_and_hash(&node)?;
759 self.store
760 .put(hash, data)
761 .await
762 .map_err(|e| HashTreeError::Store(e.to_string()))?;
763 Ok(hash)
764 }
765
766 pub async fn get_blob(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
770 self.store
771 .get(hash)
772 .await
773 .map_err(|e| HashTreeError::Store(e.to_string()))
774 }
775
776 async fn has_stored_chunk(&self, hash: &Hash) -> Result<bool, HashTreeError> {
777 self.store
778 .get(hash)
779 .await
780 .map(|data| data.is_some())
781 .map_err(|e| HashTreeError::Store(e.to_string()))
782 }
783
784 pub async fn get_tree_node(&self, hash: &Hash) -> Result<Option<TreeNode>, HashTreeError> {
786 let data = match self
787 .store
788 .get(hash)
789 .await
790 .map_err(|e| HashTreeError::Store(e.to_string()))?
791 {
792 Some(d) => d,
793 None => return Ok(None),
794 };
795
796 if !is_tree_node(&data) {
797 return Ok(None);
798 }
799
800 let node = decode_tree_node(&data)?;
801 Ok(Some(node))
802 }
803
804 async fn get_cid_root_bytes(&self, cid: &Cid) -> Result<Option<Vec<u8>>, HashTreeError> {
805 let data = match self
806 .store
807 .get(&cid.hash)
808 .await
809 .map_err(|e| HashTreeError::Store(e.to_string()))?
810 {
811 Some(d) => d,
812 None => return Ok(None),
813 };
814
815 let Some(key) = &cid.key else {
816 return Ok(Some(data));
817 };
818
819 let raw_is_tree = is_tree_node(&data);
820 match decrypt_chk(&data, key) {
821 Ok(decrypted) => {
822 if is_tree_node(&decrypted) || !raw_is_tree {
823 Ok(Some(decrypted))
824 } else {
825 Ok(Some(data))
826 }
827 }
828 Err(err) => {
829 if raw_is_tree {
830 Ok(Some(data))
831 } else {
832 Err(HashTreeError::Decryption(err.to_string()))
833 }
834 }
835 }
836 }
837
838 pub async fn get_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
840 let decrypted = match self.get_cid_root_bytes(cid).await? {
841 Some(d) => d,
842 None => return Ok(None),
843 };
844
845 if !is_tree_node(&decrypted) {
846 return Ok(None);
847 }
848
849 let node = decode_tree_node(&decrypted)?;
850 Ok(Some(node))
851 }
852
853 pub async fn get_directory_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
856 let decrypted = match self.get_cid_root_bytes(cid).await? {
857 Some(d) => d,
858 None => return Ok(None),
859 };
860
861 if !is_tree_node(&decrypted) {
862 return Ok(None);
863 }
864
865 let node = decode_tree_node(&decrypted)?;
866
867 if node.node_type == LinkType::File {
869 let mut bytes_read = 0u64;
870 let assembled = self
871 .assemble_chunks_limited(&node, None, &mut bytes_read)
872 .await?;
873 if is_tree_node(&assembled) {
874 let inner_node = decode_tree_node(&assembled)?;
875 return Ok(Some(inner_node));
876 }
877 }
878
879 Ok(Some(node))
880 }
881
882 pub async fn is_tree(&self, hash: &Hash) -> Result<bool, HashTreeError> {
884 let data = match self
885 .store
886 .get(hash)
887 .await
888 .map_err(|e| HashTreeError::Store(e.to_string()))?
889 {
890 Some(d) => d,
891 None => return Ok(false),
892 };
893 Ok(is_tree_node(&data))
894 }
895
896 pub async fn is_dir(&self, cid: &Cid) -> Result<bool, HashTreeError> {
898 Ok(matches!(
899 self.get_directory_node(cid).await?,
900 Some(node) if node.node_type.is_directory_like()
901 ))
902 }
903
904 pub async fn is_directory(&self, hash: &Hash) -> Result<bool, HashTreeError> {
906 let data = match self
907 .store
908 .get(hash)
909 .await
910 .map_err(|e| HashTreeError::Store(e.to_string()))?
911 {
912 Some(d) => d,
913 None => return Ok(false),
914 };
915 Ok(is_directory_node(&data))
916 }
917
918 pub async fn read_file(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
920 self.read_file_with_limit(hash, None).await
921 }
922
923 async fn read_file_with_limit(
925 &self,
926 hash: &Hash,
927 max_size: Option<u64>,
928 ) -> Result<Option<Vec<u8>>, HashTreeError> {
929 let data = match self
930 .store
931 .get(hash)
932 .await
933 .map_err(|e| HashTreeError::Store(e.to_string()))?
934 {
935 Some(d) => d,
936 None => return Ok(None),
937 };
938
939 if !is_tree_node(&data) {
941 Self::ensure_size_limit(max_size, data.len() as u64)?;
942 return Ok(Some(data));
943 }
944
945 let node = decode_tree_node(&data)?;
947 let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
948 Self::ensure_size_limit(max_size, declared_size)?;
949
950 let mut bytes_read = 0u64;
951 let assembled = self
952 .assemble_chunks_limited(&node, max_size, &mut bytes_read)
953 .await?;
954 Ok(Some(assembled))
955 }
956
957 pub async fn read_file_range(
964 &self,
965 hash: &Hash,
966 start: u64,
967 end: Option<u64>,
968 ) -> Result<Option<Vec<u8>>, HashTreeError> {
969 let data = match self
970 .store
971 .get(hash)
972 .await
973 .map_err(|e| HashTreeError::Store(e.to_string()))?
974 {
975 Some(d) => d,
976 None => return Ok(None),
977 };
978
979 if !is_tree_node(&data) {
981 let start_idx = start as usize;
982 let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
983 if start_idx >= data.len() {
984 return Ok(Some(vec![]));
985 }
986 let end_idx = end_idx.min(data.len());
987 return Ok(Some(data[start_idx..end_idx].to_vec()));
988 }
989
990 let node = decode_tree_node(&data)?;
992 let range_data = self.assemble_chunks_range(&node, start, end).await?;
993 Ok(Some(range_data))
994 }
995
996 pub async fn read_file_range_cid(
998 &self,
999 cid: &Cid,
1000 start: u64,
1001 end: Option<u64>,
1002 ) -> Result<Option<Vec<u8>>, HashTreeError> {
1003 if let Some(key) = cid.key {
1004 let data = match self.get_encrypted_root(&cid.hash, &key).await? {
1005 Some(d) => d,
1006 None => return Ok(None),
1007 };
1008
1009 if is_tree_node(&data) {
1010 let node = decode_tree_node(&data)?;
1011 let total_size: u64 = node.links.iter().map(|link| link.size).sum();
1012 let actual_end = end.unwrap_or(total_size).min(total_size);
1013 if start >= actual_end {
1014 return Ok(Some(vec![]));
1015 }
1016
1017 let mut result = Vec::with_capacity((actual_end - start) as usize);
1018 self.append_encrypted_range(&node, start, actual_end, 0, &mut result)
1019 .await?;
1020 return Ok(Some(result));
1021 }
1022
1023 let start_idx = start as usize;
1024 let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
1025 if start_idx >= data.len() {
1026 return Ok(Some(vec![]));
1027 }
1028 let end_idx = end_idx.min(data.len());
1029 return Ok(Some(data[start_idx..end_idx].to_vec()));
1030 }
1031
1032 self.read_file_range(&cid.hash, start, end).await
1033 }
1034
1035 async fn append_encrypted_range(
1036 &self,
1037 node: &TreeNode,
1038 start: u64,
1039 end: u64,
1040 base_offset: u64,
1041 result: &mut Vec<u8>,
1042 ) -> Result<(), HashTreeError> {
1043 let mut current_offset = base_offset;
1044
1045 for link in &node.links {
1046 let child_start = current_offset;
1047 let child_end = child_start.saturating_add(link.size);
1048 current_offset = child_end;
1049
1050 if child_end <= start {
1051 continue;
1052 }
1053 if child_start >= end {
1054 break;
1055 }
1056
1057 let chunk_key = link
1058 .key
1059 .ok_or_else(|| HashTreeError::Encryption("missing chunk key".to_string()))?;
1060
1061 let encrypted_child = self
1062 .store
1063 .get(&link.hash)
1064 .await
1065 .map_err(|e| HashTreeError::Store(e.to_string()))?
1066 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1067 let decrypted_child = decrypt_chk(&encrypted_child, &chunk_key)
1068 .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
1069
1070 if let Some(child_node) = Self::decode_linked_file_node(link, &decrypted_child)? {
1071 Box::pin(self.append_encrypted_range(&child_node, start, end, child_start, result))
1072 .await?;
1073 continue;
1074 }
1075
1076 let slice_start = if start > child_start {
1077 (start - child_start) as usize
1078 } else {
1079 0
1080 };
1081 let slice_end = if end < child_end {
1082 (end - child_start) as usize
1083 } else {
1084 decrypted_child.len()
1085 };
1086 result.extend_from_slice(&decrypted_child[slice_start..slice_end]);
1087 }
1088
1089 Ok(())
1090 }
1091
1092 async fn assemble_chunks_range(
1094 &self,
1095 node: &TreeNode,
1096 start: u64,
1097 end: Option<u64>,
1098 ) -> Result<Vec<u8>, HashTreeError> {
1099 let chunks_info = self.collect_chunk_offsets(node).await?;
1101
1102 if chunks_info.is_empty() {
1103 return Ok(vec![]);
1104 }
1105
1106 let total_size: u64 = chunks_info.iter().map(|(_, _, size)| size).sum();
1108 let actual_end = end.unwrap_or(total_size).min(total_size);
1109
1110 if start >= actual_end {
1111 return Ok(vec![]);
1112 }
1113
1114 let mut result = Vec::with_capacity((actual_end - start) as usize);
1116
1117 for (chunk_hash, chunk_start, chunk_size) in &chunks_info {
1118 let chunk_start = *chunk_start;
1119 let chunk_size = *chunk_size;
1120 let chunk_end = chunk_start + chunk_size;
1121
1122 if chunk_end > start && chunk_start < actual_end {
1124 let read_start = start.saturating_sub(chunk_start);
1125 let read_end_exclusive = actual_end.min(chunk_end) - chunk_start;
1126 if read_start >= read_end_exclusive {
1127 continue;
1128 }
1129
1130 let chunk_data = self
1131 .store
1132 .get_range(chunk_hash, read_start, read_end_exclusive - 1)
1133 .await
1134 .map_err(|e| HashTreeError::Store(e.to_string()))?
1135 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(chunk_hash)))?;
1136
1137 let expected_len = (read_end_exclusive - read_start) as usize;
1138 if chunk_data.len() != expected_len {
1139 return Err(HashTreeError::Store(format!(
1140 "range read for {} returned {} bytes, expected {}",
1141 to_hex(chunk_hash),
1142 chunk_data.len(),
1143 expected_len
1144 )));
1145 }
1146
1147 result.extend_from_slice(&chunk_data);
1148 }
1149
1150 if chunk_end >= actual_end {
1152 break;
1153 }
1154 }
1155
1156 Ok(result)
1157 }
1158
1159 async fn collect_chunk_offsets(
1162 &self,
1163 node: &TreeNode,
1164 ) -> Result<Vec<(Hash, u64, u64)>, HashTreeError> {
1165 let mut chunks = Vec::new();
1166 let mut offset = 0u64;
1167 self.collect_chunk_offsets_recursive(node, &mut chunks, &mut offset)
1168 .await?;
1169 Ok(chunks)
1170 }
1171
1172 async fn collect_chunk_offsets_recursive(
1173 &self,
1174 node: &TreeNode,
1175 chunks: &mut Vec<(Hash, u64, u64)>,
1176 offset: &mut u64,
1177 ) -> Result<(), HashTreeError> {
1178 for link in &node.links {
1179 if link.link_type == LinkType::Blob {
1180 chunks.push((link.hash, *offset, link.size));
1181 *offset += link.size;
1182 continue;
1183 }
1184
1185 let child_data = self
1186 .store
1187 .get(&link.hash)
1188 .await
1189 .map_err(|e| HashTreeError::Store(e.to_string()))?
1190 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1191
1192 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
1193 Box::pin(self.collect_chunk_offsets_recursive(&child_node, chunks, offset)).await?;
1195 } else {
1196 let size = child_data.len() as u64;
1198 chunks.push((link.hash, *offset, size));
1199 *offset += size;
1200 }
1201 }
1202 Ok(())
1203 }
1204
1205 async fn assemble_chunks_limited(
1207 &self,
1208 node: &TreeNode,
1209 max_size: Option<u64>,
1210 bytes_read: &mut u64,
1211 ) -> Result<Vec<u8>, HashTreeError> {
1212 let mut parts: Vec<Vec<u8>> = Vec::new();
1213
1214 for link in &node.links {
1215 let projected = (*bytes_read).saturating_add(link.size);
1216 Self::ensure_size_limit(max_size, projected)?;
1217
1218 let child_data = self
1219 .store
1220 .get(&link.hash)
1221 .await
1222 .map_err(|e| HashTreeError::Store(e.to_string()))?
1223 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1224
1225 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
1226 parts.push(
1227 Box::pin(self.assemble_chunks_limited(&child_node, max_size, bytes_read))
1228 .await?,
1229 );
1230 } else {
1231 let projected = (*bytes_read).saturating_add(child_data.len() as u64);
1232 Self::ensure_size_limit(max_size, projected)?;
1233 *bytes_read = projected;
1234 parts.push(child_data);
1235 }
1236 }
1237
1238 let total_length: usize = parts.iter().map(|p| p.len()).sum();
1240 let mut result = Vec::with_capacity(total_length);
1241 for part in parts {
1242 result.extend_from_slice(&part);
1243 }
1244
1245 Ok(result)
1246 }
1247
1248 pub async fn read_file_chunks(&self, hash: &Hash) -> Result<Vec<Vec<u8>>, HashTreeError> {
1250 let data = match self
1251 .store
1252 .get(hash)
1253 .await
1254 .map_err(|e| HashTreeError::Store(e.to_string()))?
1255 {
1256 Some(d) => d,
1257 None => return Ok(vec![]),
1258 };
1259
1260 if !is_tree_node(&data) {
1261 return Ok(vec![data]);
1262 }
1263
1264 let node = decode_tree_node(&data)?;
1265 self.collect_chunks(&node).await
1266 }
1267
1268 async fn collect_chunks(&self, node: &TreeNode) -> Result<Vec<Vec<u8>>, HashTreeError> {
1269 let mut chunks = Vec::new();
1270
1271 for link in &node.links {
1272 let child_data = self
1273 .store
1274 .get(&link.hash)
1275 .await
1276 .map_err(|e| HashTreeError::Store(e.to_string()))?
1277 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1278
1279 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
1280 chunks.extend(Box::pin(self.collect_chunks(&child_node)).await?);
1281 } else {
1282 chunks.push(child_data);
1283 }
1284 }
1285
1286 Ok(chunks)
1287 }
1288
1289 pub async fn list(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1291 let node = match self.get_node(cid).await? {
1292 Some(n) => n,
1293 None => return Ok(vec![]),
1294 };
1295
1296 let mut entries = Vec::new();
1297
1298 for link in &node.links {
1299 if is_internal_directory_link(&node, link) {
1301 let chunk_cid = Cid {
1302 hash: link.hash,
1303 key: link.key,
1304 };
1305 let sub_entries = Box::pin(self.list(&chunk_cid)).await?;
1306 entries.extend(sub_entries);
1307 continue;
1308 }
1309
1310 entries.push(TreeEntry {
1311 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1312 hash: link.hash,
1313 size: link.size,
1314 link_type: link.link_type,
1315 key: link.key,
1316 meta: link.meta.clone(),
1317 });
1318 }
1319
1320 Ok(entries)
1321 }
1322
1323 pub async fn list_directory(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1326 let node = match self.get_directory_node(cid).await? {
1328 Some(n) => n,
1329 None => return Ok(vec![]),
1330 };
1331
1332 let mut entries = Vec::new();
1333
1334 for link in &node.links {
1335 if is_internal_directory_link(&node, link) {
1337 let sub_cid = Cid {
1338 hash: link.hash,
1339 key: link.key,
1340 };
1341 let sub_entries = Box::pin(self.list_directory(&sub_cid)).await?;
1342 entries.extend(sub_entries);
1343 continue;
1344 }
1345
1346 entries.push(TreeEntry {
1347 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1348 hash: link.hash,
1349 size: link.size,
1350 link_type: link.link_type,
1351 key: link.key,
1352 meta: link.meta.clone(),
1353 });
1354 }
1355
1356 Ok(entries)
1357 }
1358
1359 pub async fn list_directory_required(
1362 &self,
1363 cid: &Cid,
1364 ) -> Result<Vec<TreeEntry>, HashTreeError> {
1365 let node = self
1366 .get_directory_node(cid)
1367 .await?
1368 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&cid.hash)))?;
1369
1370 let mut entries = Vec::new();
1371 for link in &node.links {
1372 if is_internal_directory_link(&node, link) {
1373 let sub_cid = Cid {
1374 hash: link.hash,
1375 key: link.key,
1376 };
1377 let sub_entries = Box::pin(self.list_directory_required(&sub_cid)).await?;
1378 entries.extend(sub_entries);
1379 continue;
1380 }
1381
1382 entries.push(TreeEntry {
1383 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1384 hash: link.hash,
1385 size: link.size,
1386 link_type: link.link_type,
1387 key: link.key,
1388 meta: link.meta.clone(),
1389 });
1390 }
1391
1392 Ok(entries)
1393 }
1394
1395 pub async fn resolve(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1397 let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
1398 if parts.is_empty() {
1399 return Ok(Some(cid.clone()));
1400 }
1401
1402 let mut current_cid = cid.clone();
1403
1404 for part in parts {
1405 let node = match self.get_directory_node(¤t_cid).await? {
1407 Some(n) => n,
1408 None => {
1409 if !self.has_stored_chunk(¤t_cid.hash).await? {
1410 return Err(HashTreeError::MissingChunk(to_hex(¤t_cid.hash)));
1411 }
1412 return Ok(None);
1413 }
1414 };
1415
1416 if let Some(link) = self.find_link(&node, part) {
1417 current_cid = Cid {
1418 hash: link.hash,
1419 key: link.key,
1420 };
1421 } else {
1422 match self
1424 .find_link_in_subtrees_cid(&node, part, ¤t_cid)
1425 .await?
1426 {
1427 Some(link) => {
1428 current_cid = Cid {
1429 hash: link.hash,
1430 key: link.key,
1431 };
1432 }
1433 None => return Ok(None),
1434 }
1435 }
1436 }
1437
1438 Ok(Some(current_cid))
1439 }
1440
1441 pub async fn resolve_path(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1443 self.resolve(cid, path).await
1444 }
1445
1446 fn find_link(&self, node: &TreeNode, name: &str) -> Option<Link> {
1447 node.links
1448 .iter()
1449 .find(|l| !is_internal_directory_link(node, l) && l.name.as_deref() == Some(name))
1450 .cloned()
1451 }
1452
1453 async fn find_link_in_subtrees_cid(
1455 &self,
1456 node: &TreeNode,
1457 name: &str,
1458 _parent_cid: &Cid,
1459 ) -> Result<Option<Link>, HashTreeError> {
1460 for link in &node.links {
1461 if !is_internal_directory_link(node, link) {
1462 continue;
1463 }
1464
1465 let sub_cid = Cid {
1467 hash: link.hash,
1468 key: link.key,
1469 };
1470
1471 let sub_node = match self.get_node(&sub_cid).await? {
1472 Some(n) => n,
1473 None => {
1474 if !self.has_stored_chunk(&sub_cid.hash).await? {
1475 return Err(HashTreeError::MissingChunk(to_hex(&sub_cid.hash)));
1476 }
1477 continue;
1478 }
1479 };
1480
1481 if let Some(found) = self.find_link(&sub_node, name) {
1482 return Ok(Some(found));
1483 }
1484
1485 if let Some(deep_found) =
1486 Box::pin(self.find_link_in_subtrees_cid(&sub_node, name, &sub_cid)).await?
1487 {
1488 return Ok(Some(deep_found));
1489 }
1490 }
1491
1492 Ok(None)
1493 }
1494
1495 pub async fn get_size(&self, hash: &Hash) -> Result<u64, HashTreeError> {
1497 let data = match self
1498 .store
1499 .get(hash)
1500 .await
1501 .map_err(|e| HashTreeError::Store(e.to_string()))?
1502 {
1503 Some(d) => d,
1504 None => return Ok(0),
1505 };
1506
1507 if !is_tree_node(&data) {
1508 return Ok(data.len() as u64);
1509 }
1510
1511 let node = decode_tree_node(&data)?;
1512 let mut total = 0u64;
1514 for link in &node.links {
1515 total += link.size;
1516 }
1517 Ok(total)
1518 }
1519
1520 pub async fn get_size_cid(&self, cid: &Cid) -> Result<u64, HashTreeError> {
1522 if let Some(key) = cid.key {
1523 let data = match self.get_encrypted_root(&cid.hash, &key).await? {
1524 Some(d) => d,
1525 None => return Ok(0),
1526 };
1527 if is_tree_node(&data) {
1528 let node = decode_tree_node(&data)?;
1529 return Ok(node.links.iter().map(|link| link.size).sum());
1530 }
1531 return Ok(data.len() as u64);
1532 }
1533
1534 self.get_size(&cid.hash).await
1535 }
1536
1537 pub async fn set_entry(
1542 &self,
1543 root: &Cid,
1544 path: &[&str],
1545 name: &str,
1546 entry_cid: &Cid,
1547 size: u64,
1548 link_type: LinkType,
1549 ) -> Result<Cid, HashTreeError> {
1550 self.set_entry_with_meta(root, path, name, entry_cid, size, link_type, None)
1551 .await
1552 }
1553
1554 #[allow(clippy::too_many_arguments)]
1557 pub async fn set_entry_with_meta(
1558 &self,
1559 root: &Cid,
1560 path: &[&str],
1561 name: &str,
1562 entry_cid: &Cid,
1563 size: u64,
1564 link_type: LinkType,
1565 meta: Option<std::collections::HashMap<String, serde_json::Value>>,
1566 ) -> Result<Cid, HashTreeError> {
1567 let dir_cid = self.resolve_path_array(root, path).await?;
1568 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1569
1570 let entries = self.list_directory(&dir_cid).await?;
1571 let mut new_entries: Vec<DirEntry> = entries
1572 .into_iter()
1573 .filter(|e| e.name != name)
1574 .map(|e| DirEntry {
1575 name: e.name,
1576 hash: e.hash,
1577 size: e.size,
1578 key: e.key,
1579 link_type: e.link_type,
1580 meta: e.meta,
1581 })
1582 .collect();
1583
1584 new_entries.push(DirEntry {
1585 name: name.to_string(),
1586 hash: entry_cid.hash,
1587 size,
1588 key: entry_cid.key,
1589 link_type,
1590 meta,
1591 });
1592
1593 let new_dir_cid = self.put_directory(new_entries).await?;
1594 self.rebuild_path(root, path, new_dir_cid).await
1595 }
1596
1597 pub async fn remove_entry(
1600 &self,
1601 root: &Cid,
1602 path: &[&str],
1603 name: &str,
1604 ) -> Result<Cid, HashTreeError> {
1605 let dir_cid = self.resolve_path_array(root, path).await?;
1606 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1607
1608 let entries = self.list_directory(&dir_cid).await?;
1609 let new_entries: Vec<DirEntry> = entries
1610 .into_iter()
1611 .filter(|e| e.name != name)
1612 .map(|e| DirEntry {
1613 name: e.name,
1614 hash: e.hash,
1615 size: e.size,
1616 key: e.key,
1617 link_type: e.link_type,
1618 meta: e.meta,
1619 })
1620 .collect();
1621
1622 let new_dir_cid = self.put_directory(new_entries).await?;
1623 self.rebuild_path(root, path, new_dir_cid).await
1624 }
1625
1626 pub async fn rename_entry(
1629 &self,
1630 root: &Cid,
1631 path: &[&str],
1632 old_name: &str,
1633 new_name: &str,
1634 ) -> Result<Cid, HashTreeError> {
1635 if old_name == new_name {
1636 return Ok(root.clone());
1637 }
1638
1639 let dir_cid = self.resolve_path_array(root, path).await?;
1640 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1641
1642 let entries = self.list_directory(&dir_cid).await?;
1643 let entry = entries
1644 .iter()
1645 .find(|e| e.name == old_name)
1646 .ok_or_else(|| HashTreeError::EntryNotFound(old_name.to_string()))?;
1647
1648 let entry_hash = entry.hash;
1649 let entry_size = entry.size;
1650 let entry_key = entry.key;
1651 let entry_link_type = entry.link_type;
1652 let entry_meta = entry.meta.clone();
1653
1654 let new_entries: Vec<DirEntry> = entries
1655 .into_iter()
1656 .filter(|e| e.name != old_name)
1657 .map(|e| DirEntry {
1658 name: e.name,
1659 hash: e.hash,
1660 size: e.size,
1661 key: e.key,
1662 link_type: e.link_type,
1663 meta: e.meta,
1664 })
1665 .chain(std::iter::once(DirEntry {
1666 name: new_name.to_string(),
1667 hash: entry_hash,
1668 size: entry_size,
1669 key: entry_key,
1670 link_type: entry_link_type,
1671 meta: entry_meta,
1672 }))
1673 .collect();
1674
1675 let new_dir_cid = self.put_directory(new_entries).await?;
1676 self.rebuild_path(root, path, new_dir_cid).await
1677 }
1678
1679 pub async fn move_entry(
1682 &self,
1683 root: &Cid,
1684 source_path: &[&str],
1685 name: &str,
1686 target_path: &[&str],
1687 ) -> Result<Cid, HashTreeError> {
1688 let source_dir_cid = self.resolve_path_array(root, source_path).await?;
1689 let source_dir_cid =
1690 source_dir_cid.ok_or_else(|| HashTreeError::PathNotFound(source_path.join("/")))?;
1691
1692 let source_entries = self.list_directory(&source_dir_cid).await?;
1693 let entry = source_entries
1694 .iter()
1695 .find(|e| e.name == name)
1696 .ok_or_else(|| HashTreeError::EntryNotFound(name.to_string()))?;
1697
1698 let entry_cid = Cid {
1699 hash: entry.hash,
1700 key: entry.key,
1701 };
1702 let entry_size = entry.size;
1703 let entry_link_type = entry.link_type;
1704
1705 let new_root = self.remove_entry(root, source_path, name).await?;
1707
1708 self.set_entry(
1710 &new_root,
1711 target_path,
1712 name,
1713 &entry_cid,
1714 entry_size,
1715 entry_link_type,
1716 )
1717 .await
1718 }
1719
1720 async fn resolve_path_array(
1721 &self,
1722 root: &Cid,
1723 path: &[&str],
1724 ) -> Result<Option<Cid>, HashTreeError> {
1725 if path.is_empty() {
1726 return Ok(Some(root.clone()));
1727 }
1728 self.resolve_path(root, &path.join("/")).await
1729 }
1730
1731 async fn rebuild_path(
1732 &self,
1733 root: &Cid,
1734 path: &[&str],
1735 new_child: Cid,
1736 ) -> Result<Cid, HashTreeError> {
1737 if path.is_empty() {
1738 return Ok(new_child);
1739 }
1740
1741 let mut child_cid = new_child;
1742 let parts: Vec<&str> = path.to_vec();
1743
1744 for i in (0..parts.len()).rev() {
1745 let child_name = parts[i];
1746 let parent_path = &parts[..i];
1747
1748 let parent_cid = if parent_path.is_empty() {
1749 root.clone()
1750 } else {
1751 self.resolve_path_array(root, parent_path)
1752 .await?
1753 .ok_or_else(|| HashTreeError::PathNotFound(parent_path.join("/")))?
1754 };
1755
1756 let parent_entries = self.list_directory(&parent_cid).await?;
1757 let new_parent_entries: Vec<DirEntry> = parent_entries
1758 .into_iter()
1759 .map(|e| {
1760 if e.name == child_name {
1761 DirEntry {
1762 name: e.name,
1763 hash: child_cid.hash,
1764 size: 0, key: child_cid.key,
1766 link_type: e.link_type,
1767 meta: e.meta,
1768 }
1769 } else {
1770 DirEntry {
1771 name: e.name,
1772 hash: e.hash,
1773 size: e.size,
1774 key: e.key,
1775 link_type: e.link_type,
1776 meta: e.meta,
1777 }
1778 }
1779 })
1780 .collect();
1781
1782 child_cid = self.put_directory(new_parent_entries).await?;
1783 }
1784
1785 Ok(child_cid)
1786 }
1787
1788 pub fn get_store(&self) -> Arc<S> {
1792 self.store.clone()
1793 }
1794
1795 pub fn chunk_size(&self) -> usize {
1797 self.chunk_size
1798 }
1799
1800 pub fn max_links(&self) -> usize {
1802 self.max_links
1803 }
1804}
1805
1806fn stream_put_batch_target_bytes() -> usize {
1807 std::env::var(STREAM_PUT_BATCH_TARGET_BYTES_ENV)
1808 .ok()
1809 .and_then(|value| value.parse::<usize>().ok())
1810 .filter(|value| *value > 0)
1811 .unwrap_or(DEFAULT_STREAM_PUT_BATCH_TARGET_BYTES)
1812}
1813
1814pub async fn verify_tree<S: Store>(
1816 store: Arc<S>,
1817 root_hash: &Hash,
1818) -> Result<crate::reader::VerifyResult, HashTreeError> {
1819 let mut missing = Vec::new();
1820 let mut visited = std::collections::HashSet::new();
1821
1822 verify_recursive(store, root_hash, &mut missing, &mut visited).await?;
1823
1824 Ok(crate::reader::VerifyResult {
1825 valid: missing.is_empty(),
1826 missing,
1827 })
1828}
1829
1830async fn verify_recursive<S: Store>(
1831 store: Arc<S>,
1832 hash: &Hash,
1833 missing: &mut Vec<Hash>,
1834 visited: &mut std::collections::HashSet<String>,
1835) -> Result<(), HashTreeError> {
1836 let hex = to_hex(hash);
1837 if visited.contains(&hex) {
1838 return Ok(());
1839 }
1840 visited.insert(hex);
1841
1842 let data = match store
1843 .get(hash)
1844 .await
1845 .map_err(|e| HashTreeError::Store(e.to_string()))?
1846 {
1847 Some(d) => d,
1848 None => {
1849 missing.push(*hash);
1850 return Ok(());
1851 }
1852 };
1853
1854 if is_tree_node(&data) {
1855 let node = decode_tree_node(&data)?;
1856 for link in &node.links {
1857 Box::pin(verify_recursive(
1858 store.clone(),
1859 &link.hash,
1860 missing,
1861 visited,
1862 ))
1863 .await?;
1864 }
1865 }
1866
1867 Ok(())
1868}
1869
1870#[cfg(test)]
1871mod tests;