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