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::encryption::{decrypt_object, ObjectKey};
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_object(&data, key) {
119 Ok(decrypted) => Ok(decrypted),
122 Err(_)
123 if key.mode() == crate::encryption::EncryptionMode::LegacyChk
124 && is_tree_node(&data) =>
125 {
126 Ok(data)
127 }
128 Err(error) => Err(HashTreeError::Decryption(error.to_string())),
129 }
130}
131
132pub fn decode_tree_node_by_cid(
135 cid: &Cid,
136 data: Vec<u8>,
137) -> Result<Option<TreeNode>, HashTreeError> {
138 let decrypted = decrypt_cid_root_bytes(cid, data)?;
139 if !is_tree_node(&decrypted) {
140 return Ok(None);
141 }
142 Ok(Some(decode_tree_node(&decrypted)?))
143}
144
145pub struct HashTree<S: Store> {
147 store: Arc<S>,
148 chunk_size: usize,
149 max_links: usize,
150 encrypted: bool,
151 encryption_mode: crate::encryption::EncryptionMode,
152}
153
154impl<S: Store> HashTree<S> {
155 pub fn new(config: HashTreeConfig<S>) -> Self {
156 Self {
157 store: config.store,
158 chunk_size: config.chunk_size,
159 max_links: config.max_links,
160 encrypted: config.encrypted,
161 encryption_mode: crate::encryption::EncryptionMode::default(),
162 }
163 }
164
165 pub fn with_encryption_mode(mut self, mode: crate::encryption::EncryptionMode) -> Self {
167 self.encryption_mode = mode;
168 self
169 }
170
171 pub fn is_encrypted(&self) -> bool {
173 self.encrypted
174 }
175
176 pub async fn put(&self, data: &[u8]) -> Result<(Cid, u64), HashTreeError> {
181 let size = data.len() as u64;
182
183 if data.len() <= self.chunk_size {
185 let (hash, key) = self.put_chunk_internal(data).await?;
186 return Ok((Cid { hash, key }, size));
187 }
188
189 let mut links: Vec<Link> = Vec::new();
191 let mut offset = 0;
192
193 while offset < data.len() {
194 let end = (offset + self.chunk_size).min(data.len());
195 let chunk = &data[offset..end];
196 let chunk_size = chunk.len() as u64;
197 let (hash, key) = self.put_chunk_internal(chunk).await?;
198 links.push(Link {
199 hash,
200 name: None,
201 size: chunk_size,
202 key,
203 link_type: LinkType::Blob, meta: None,
205 });
206 offset = end;
207 }
208
209 let (root_hash, root_key, _) = self.build_tree_internal(links, Some(size)).await?;
211 Ok((
212 Cid {
213 hash: root_hash,
214 key: root_key,
215 },
216 size,
217 ))
218 }
219
220 pub async fn get(
225 &self,
226 cid: &Cid,
227 max_size: Option<u64>,
228 ) -> Result<Option<Vec<u8>>, HashTreeError> {
229 if let Some(key) = cid.key {
230 self.get_encrypted(&cid.hash, &key, max_size).await
231 } else {
232 self.read_file_with_limit(&cid.hash, max_size).await
233 }
234 }
235
236 pub async fn put_stream<R: AsyncRead + Unpin>(
242 &self,
243 reader: R,
244 ) -> Result<(Cid, u64), HashTreeError> {
245 self.put_stream_with_progress(reader, |_| {}).await
246 }
247
248 pub async fn put_stream_with_progress<R, F>(
250 &self,
251 mut reader: R,
252 mut progress: F,
253 ) -> Result<(Cid, u64), HashTreeError>
254 where
255 R: AsyncRead + Unpin,
256 F: FnMut(u64),
257 {
258 let mut buffer = vec![0u8; self.chunk_size];
259 let mut links = Vec::new();
260 let mut total_size: u64 = 0;
261 let mut consistent_key: Option<ObjectKey> = None;
262 let mut pending_store_items = Vec::new();
263 let mut pending_store_bytes = 0usize;
264 let batch_target_bytes = stream_put_batch_target_bytes();
265
266 loop {
267 let mut chunk = Vec::new();
268 let mut bytes_read = 0;
269
270 while bytes_read < self.chunk_size {
272 let n = reader
273 .read(&mut buffer[..self.chunk_size - bytes_read])
274 .await
275 .map_err(|e| HashTreeError::Store(format!("read error: {}", e)))?;
276 if n == 0 {
277 break; }
279 chunk.extend_from_slice(&buffer[..n]);
280 bytes_read += n;
281 }
282
283 if chunk.is_empty() {
284 break; }
286
287 let chunk_len = chunk.len() as u64;
288 total_size += chunk_len;
289
290 let (hash, data, key) = self.prepare_chunk_for_store(&chunk)?;
291
292 if links.is_empty() {
294 consistent_key = key;
295 } else if consistent_key != key {
296 consistent_key = None;
297 }
298
299 links.push(Link {
300 hash,
301 name: None,
302 size: chunk_len,
303 key,
304 link_type: LinkType::Blob, meta: None,
306 });
307
308 pending_store_bytes = pending_store_bytes.saturating_add(data.len());
309 pending_store_items.push((hash, data, chunk_len));
310 if pending_store_bytes >= batch_target_bytes
311 || pending_store_items.len() >= STREAM_PUT_BATCH_MAX_ITEMS
312 {
313 Self::flush_stream_store_batch(
314 self.store.as_ref(),
315 &mut pending_store_items,
316 &mut pending_store_bytes,
317 &mut progress,
318 )
319 .await?;
320 }
321 }
322
323 if links.is_empty() {
324 let (hash, key) = self.put_chunk_internal(&[]).await?;
326 return Ok((Cid { hash, key }, 0));
327 }
328
329 Self::flush_stream_store_batch(
330 self.store.as_ref(),
331 &mut pending_store_items,
332 &mut pending_store_bytes,
333 &mut progress,
334 )
335 .await?;
336
337 let (root_hash, root_key, _) = self.build_tree_internal(links, Some(total_size)).await?;
339 Ok((
340 Cid {
341 hash: root_hash,
342 key: root_key,
343 },
344 total_size,
345 ))
346 }
347
348 async fn put_chunk_internal(
350 &self,
351 data: &[u8],
352 ) -> Result<(Hash, Option<ObjectKey>), HashTreeError> {
353 let (hash, stored_data, key) = self.prepare_chunk_for_store(data)?;
354 self.store
355 .put(hash, stored_data)
356 .await
357 .map_err(|e| HashTreeError::Store(e.to_string()))?;
358 Ok((hash, key))
359 }
360
361 fn prepare_chunk_for_store(
362 &self,
363 data: &[u8],
364 ) -> Result<(Hash, Vec<u8>, Option<ObjectKey>), HashTreeError> {
365 if self.encrypted {
366 let (encrypted, key) = crate::encryption::encrypt_object(data, self.encryption_mode)
367 .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
368 let hash = sha256(&encrypted);
369 Ok((hash, encrypted, Some(key)))
370 } else {
371 let hash = sha256(data);
372 Ok((hash, data.to_vec(), None))
373 }
374 }
375
376 async fn flush_stream_store_batch<F>(
377 store: &S,
378 pending: &mut Vec<(Hash, Vec<u8>, u64)>,
379 pending_bytes: &mut usize,
380 progress: &mut F,
381 ) -> Result<(), HashTreeError>
382 where
383 F: FnMut(u64),
384 {
385 if pending.is_empty() {
386 return Ok(());
387 }
388
389 let mut items = Vec::with_capacity(pending.len());
390 let mut sizes = Vec::with_capacity(pending.len());
391 for (hash, data, size) in pending.drain(..) {
392 items.push((hash, data));
393 sizes.push(size);
394 }
395 *pending_bytes = 0;
396
397 store
398 .put_many(items)
399 .await
400 .map_err(|e| HashTreeError::Store(e.to_string()))?;
401 for size in sizes {
402 progress(size);
403 }
404 Ok(())
405 }
406
407 async fn build_tree_internal(
409 &self,
410 links: Vec<Link>,
411 total_size: Option<u64>,
412 ) -> Result<(Hash, Option<ObjectKey>, LinkType), HashTreeError> {
413 if links.len() == 1 {
415 if let Some(ts) = total_size {
416 if links[0].size == ts {
417 return Ok((links[0].hash, links[0].key, links[0].link_type));
418 }
419 }
420 }
421
422 if links.len() <= self.max_links {
423 let node = TreeNode {
424 node_type: LinkType::File,
425 links,
426 };
427 let (data, _) = encode_and_hash(&node)?;
428
429 if self.encrypted {
430 let (encrypted, key) =
431 crate::encryption::encrypt_object(&data, self.encryption_mode)
432 .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
433 let hash = sha256(&encrypted);
434 self.store
435 .put(hash, encrypted)
436 .await
437 .map_err(|e| HashTreeError::Store(e.to_string()))?;
438 return Ok((hash, Some(key), LinkType::File));
439 }
440
441 let hash = sha256(&data);
443 self.store
444 .put(hash, data)
445 .await
446 .map_err(|e| HashTreeError::Store(e.to_string()))?;
447 return Ok((hash, None, LinkType::File));
448 }
449
450 let mut sub_links = Vec::new();
452 for batch in links.chunks(self.max_links) {
453 let batch_size: u64 = batch.iter().map(|l| l.size).sum();
454 let (hash, key, link_type) =
455 Box::pin(self.build_tree_internal(batch.to_vec(), Some(batch_size))).await?;
456 sub_links.push(Link {
457 hash,
458 name: None,
459 size: batch_size,
460 key,
461 link_type,
462 meta: None,
463 });
464 }
465
466 Box::pin(self.build_tree_internal(sub_links, total_size)).await
467 }
468
469 async fn get_encrypted(
471 &self,
472 hash: &Hash,
473 key: &ObjectKey,
474 max_size: Option<u64>,
475 ) -> Result<Option<Vec<u8>>, HashTreeError> {
476 let decrypted = match self.get_encrypted_root(hash, key).await? {
477 Some(data) => data,
478 None => return Ok(None),
479 };
480
481 if is_tree_node(&decrypted) {
483 let node = decode_tree_node(&decrypted)?;
484 let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
485 Self::ensure_size_limit(max_size, declared_size)?;
486
487 let mut bytes_read = 0u64;
488 let assembled = self
489 .assemble_chunks_limited(&node, max_size, &mut bytes_read)
490 .await?;
491 return Ok(Some(assembled));
492 }
493
494 Self::ensure_size_limit(max_size, decrypted.len() as u64)?;
496 Ok(Some(decrypted))
497 }
498
499 async fn get_encrypted_root(
500 &self,
501 hash: &Hash,
502 key: &ObjectKey,
503 ) -> Result<Option<Vec<u8>>, HashTreeError> {
504 self.get_cid_root_bytes(&Cid {
505 hash: *hash,
506 key: Some(*key),
507 })
508 .await
509 .map_err(|err| match err {
510 HashTreeError::Decryption(message) => HashTreeError::Encryption(message),
511 other => other,
512 })
513 }
514
515 fn ensure_size_limit(max_size: Option<u64>, actual_size: u64) -> Result<(), HashTreeError> {
516 if let Some(max_size) = max_size {
517 if actual_size > max_size {
518 return Err(HashTreeError::SizeLimitExceeded {
519 max_size,
520 actual_size,
521 });
522 }
523 }
524 Ok(())
525 }
526
527 fn decode_linked_file_node(
528 link: &Link,
529 data: &[u8],
530 ) -> Result<Option<TreeNode>, HashTreeError> {
531 match link.link_type {
532 LinkType::File => match decode_tree_node(data) {
533 Ok(node) => Ok(Some(node)),
534 Err(_) if link.size == data.len() as u64 => Ok(None),
535 Err(err) => Err(HashTreeError::Codec(err)),
536 },
537 LinkType::Blob => {
538 if link.size == data.len() as u64 {
539 return Ok(None);
540 }
541
542 match decode_tree_node(data) {
543 Ok(node) if node.node_type == LinkType::File => Ok(Some(node)),
544 _ => Ok(None),
545 }
546 }
547 LinkType::Dir | LinkType::Fanout => Ok(None),
548 }
549 }
550
551 async fn read_link_bytes(&self, link: &Link) -> Result<Vec<u8>, HashTreeError> {
552 let stored = self
553 .get_blob(&link.hash)
554 .await?
555 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
556 match &link.key {
557 Some(key) => crate::encryption::decrypt_object(&stored, key)
558 .map_err(|e| HashTreeError::Decryption(e.to_string())),
559 None => Ok(stored),
560 }
561 }
562
563 pub async fn put_blob(&self, data: &[u8]) -> Result<Hash, HashTreeError> {
568 let hash = sha256(data);
569 self.store
570 .put(hash, data.to_vec())
571 .await
572 .map_err(|e| HashTreeError::Store(e.to_string()))?;
573 Ok(hash)
574 }
575
576 pub async fn put_file(&self, data: &[u8]) -> Result<(Cid, u64), HashTreeError> {
579 self.put(data).await
580 }
581
582 pub async fn put_directory(&self, entries: Vec<DirEntry>) -> Result<Cid, HashTreeError> {
587 let mut sorted = entries;
589 sorted.sort_by(|a, b| a.name.cmp(&b.name));
590
591 let links: Vec<Link> = sorted
592 .into_iter()
593 .map(|e| Link {
594 hash: e.hash,
595 name: Some(e.name),
596 size: e.size,
597 key: e.key,
598 link_type: e.link_type,
599 meta: e.meta,
600 })
601 .collect();
602
603 if links.len() <= self.max_links {
604 return self.put_directory_node(links).await;
605 }
606
607 self.build_directory_by_chunks(links).await
608 }
609
610 async fn put_directory_node(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
611 self.put_tree_node_with_type(LinkType::Dir, links).await
612 }
613
614 async fn put_tree_node_with_type(
615 &self,
616 node_type: LinkType,
617 links: Vec<Link>,
618 ) -> Result<Cid, HashTreeError> {
619 let node = TreeNode { node_type, links };
620 let (data, plain_hash) = encode_and_hash(&node)?;
621
622 if self.encrypted {
623 let (encrypted, key) = crate::encryption::encrypt_object(&data, self.encryption_mode)
624 .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
625 let hash = sha256(&encrypted);
626 self.store
627 .put(hash, encrypted)
628 .await
629 .map_err(|e| HashTreeError::Store(e.to_string()))?;
630 return Ok(Cid {
631 hash,
632 key: Some(key),
633 });
634 }
635
636 self.store
637 .put(plain_hash, data)
638 .await
639 .map_err(|e| HashTreeError::Store(e.to_string()))?;
640 Ok(Cid {
641 hash: plain_hash,
642 key: None,
643 })
644 }
645
646 async fn build_directory_by_chunks(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
647 let spans = links
648 .into_iter()
649 .map(|link| {
650 let name = link.name.clone().unwrap_or_else(|| to_hex(&link.hash));
651 DirectoryFanoutSpan {
652 link,
653 count: 1,
654 first: name.clone(),
655 last: name,
656 }
657 })
658 .collect();
659 self.build_directory_fanout_level(spans, LinkType::Dir)
660 .await
661 }
662
663 async fn build_directory_fanout_level(
664 &self,
665 spans: Vec<DirectoryFanoutSpan>,
666 child_node_type: LinkType,
667 ) -> Result<Cid, HashTreeError> {
668 let mut sub_trees: Vec<DirectoryFanoutSpan> = Vec::new();
669
670 for batch in spans.chunks(self.max_links) {
671 let Some(first_span) = batch.first() else {
672 continue;
673 };
674 let last_span = batch.last().expect("non-empty fanout batch");
675 let count: usize = batch.iter().map(|span| span.count).sum();
676 let batch_size: u64 = batch.iter().map(|span| span.link.size).sum();
677 let child_links = batch.iter().map(|span| span.link.clone()).collect();
678 let child_cid = self
679 .put_tree_node_with_type(child_node_type, child_links)
680 .await?;
681
682 sub_trees.push(DirectoryFanoutSpan {
683 link: Link {
684 hash: child_cid.hash,
685 name: None,
686 size: batch_size,
687 key: child_cid.key,
688 link_type: child_node_type,
689 meta: Some(directory_fanout_meta(
690 count,
691 &first_span.first,
692 &last_span.last,
693 )),
694 },
695 count,
696 first: first_span.first.clone(),
697 last: last_span.last.clone(),
698 });
699 }
700
701 if sub_trees.len() <= self.max_links {
702 return self
703 .put_tree_node_with_type(
704 LinkType::Fanout,
705 sub_trees.into_iter().map(|span| span.link).collect(),
706 )
707 .await;
708 }
709
710 Box::pin(self.build_directory_fanout_level(sub_trees, LinkType::Fanout)).await
711 }
712
713 pub async fn put_tree_node(&self, links: Vec<Link>) -> Result<Hash, HashTreeError> {
715 let node = TreeNode {
716 node_type: LinkType::Dir,
717 links,
718 };
719
720 let (data, hash) = encode_and_hash(&node)?;
721 self.store
722 .put(hash, data)
723 .await
724 .map_err(|e| HashTreeError::Store(e.to_string()))?;
725 Ok(hash)
726 }
727
728 pub async fn get_blob(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
732 self.store
733 .get(hash)
734 .await
735 .map_err(|e| HashTreeError::Store(e.to_string()))
736 }
737
738 async fn has_stored_chunk(&self, hash: &Hash) -> Result<bool, HashTreeError> {
739 self.store
740 .get(hash)
741 .await
742 .map(|data| data.is_some())
743 .map_err(|e| HashTreeError::Store(e.to_string()))
744 }
745
746 pub async fn get_tree_node(&self, hash: &Hash) -> Result<Option<TreeNode>, HashTreeError> {
748 let data = match self
749 .store
750 .get(hash)
751 .await
752 .map_err(|e| HashTreeError::Store(e.to_string()))?
753 {
754 Some(d) => d,
755 None => return Ok(None),
756 };
757
758 if !is_tree_node(&data) {
759 return Ok(None);
760 }
761
762 let node = decode_tree_node(&data)?;
763 Ok(Some(node))
764 }
765
766 pub async fn get_tree_node_by_cid(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
768 self.get_node(cid).await
769 }
770
771 async fn get_cid_root_bytes(&self, cid: &Cid) -> Result<Option<Vec<u8>>, HashTreeError> {
772 let data = match self
773 .store
774 .get(&cid.hash)
775 .await
776 .map_err(|e| HashTreeError::Store(e.to_string()))?
777 {
778 Some(d) => d,
779 None => return Ok(None),
780 };
781
782 decrypt_cid_root_bytes(cid, data).map(Some)
783 }
784
785 pub async fn get_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
787 let data = match self.get_blob(&cid.hash).await? {
788 Some(d) => d,
789 None => return Ok(None),
790 };
791 decode_tree_node_by_cid(cid, data)
792 }
793
794 pub async fn get_directory_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
797 let decrypted = match self.get_cid_root_bytes(cid).await? {
798 Some(d) => d,
799 None => return Ok(None),
800 };
801
802 if !is_tree_node(&decrypted) {
803 return Ok(None);
804 }
805
806 let node = decode_tree_node(&decrypted)?;
807
808 if node.node_type == LinkType::File {
810 let mut bytes_read = 0u64;
811 let assembled = self
812 .assemble_chunks_limited(&node, None, &mut bytes_read)
813 .await?;
814 if is_tree_node(&assembled) {
815 let inner_node = decode_tree_node(&assembled)?;
816 return Ok(Some(inner_node));
817 }
818 }
819
820 Ok(Some(node))
821 }
822
823 pub async fn is_tree(&self, hash: &Hash) -> Result<bool, HashTreeError> {
825 let data = match self
826 .store
827 .get(hash)
828 .await
829 .map_err(|e| HashTreeError::Store(e.to_string()))?
830 {
831 Some(d) => d,
832 None => return Ok(false),
833 };
834 Ok(is_tree_node(&data))
835 }
836
837 pub async fn is_dir(&self, cid: &Cid) -> Result<bool, HashTreeError> {
839 Ok(matches!(
840 self.get_directory_node(cid).await?,
841 Some(node) if node.node_type.is_directory_like()
842 ))
843 }
844
845 pub async fn is_directory(&self, hash: &Hash) -> Result<bool, HashTreeError> {
847 let data = match self
848 .store
849 .get(hash)
850 .await
851 .map_err(|e| HashTreeError::Store(e.to_string()))?
852 {
853 Some(d) => d,
854 None => return Ok(false),
855 };
856 Ok(is_directory_node(&data))
857 }
858
859 pub async fn read_file(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
861 self.read_file_with_limit(hash, None).await
862 }
863
864 async fn read_file_with_limit(
866 &self,
867 hash: &Hash,
868 max_size: Option<u64>,
869 ) -> Result<Option<Vec<u8>>, HashTreeError> {
870 let data = match self
871 .store
872 .get(hash)
873 .await
874 .map_err(|e| HashTreeError::Store(e.to_string()))?
875 {
876 Some(d) => d,
877 None => return Ok(None),
878 };
879
880 if !is_tree_node(&data) {
882 Self::ensure_size_limit(max_size, data.len() as u64)?;
883 return Ok(Some(data));
884 }
885
886 let node = decode_tree_node(&data)?;
888 let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
889 Self::ensure_size_limit(max_size, declared_size)?;
890
891 let mut bytes_read = 0u64;
892 let assembled = self
893 .assemble_chunks_limited(&node, max_size, &mut bytes_read)
894 .await?;
895 Ok(Some(assembled))
896 }
897
898 pub async fn read_file_range(
905 &self,
906 hash: &Hash,
907 start: u64,
908 end: Option<u64>,
909 ) -> Result<Option<Vec<u8>>, HashTreeError> {
910 self.read_file_range_cid(&Cid::public(*hash), start, end)
911 .await
912 }
913
914 pub async fn read_file_range_cid(
916 &self,
917 cid: &Cid,
918 start: u64,
919 end: Option<u64>,
920 ) -> Result<Option<Vec<u8>>, HashTreeError> {
921 let Some(data) = self.get_cid_root_bytes(cid).await? else {
922 return Ok(None);
923 };
924 if !is_tree_node(&data) {
925 let end = end.unwrap_or(data.len() as u64).min(data.len() as u64);
926 return Ok(Some(data[start.min(end) as usize..end as usize].to_vec()));
927 }
928 let node = decode_tree_node(&data)?;
929 let mut result = Vec::new();
930 self.append_range(&node, start, end.unwrap_or(u64::MAX), 0, &mut result)
931 .await?;
932 Ok(Some(result))
933 }
934
935 async fn append_range(
936 &self,
937 node: &TreeNode,
938 start: u64,
939 end: u64,
940 base_offset: u64,
941 result: &mut Vec<u8>,
942 ) -> Result<(), HashTreeError> {
943 let mut current_offset = base_offset;
944
945 for link in &node.links {
946 let child_start = current_offset;
947 let child_end = child_start.saturating_add(link.size);
948 current_offset = child_end;
949
950 if child_end <= start {
951 continue;
952 }
953 if child_start >= end {
954 break;
955 }
956
957 let from = start.saturating_sub(child_start);
958 let to = end.min(child_end) - child_start;
959 if from >= to {
960 continue;
961 }
962
963 if link.link_type == LinkType::Blob && link.key.is_none() {
964 let bytes = self
965 .store
966 .get_range(&link.hash, from, to - 1)
967 .await
968 .map_err(|e| HashTreeError::Store(e.to_string()))?
969 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
970 if bytes.len() as u64 != to - from {
971 return Err(HashTreeError::Store(format!(
972 "unexpected range length for {}",
973 to_hex(&link.hash)
974 )));
975 }
976 result.extend_from_slice(&bytes);
977 continue;
978 }
979
980 let decrypted_child = self.read_link_bytes(link).await?;
981 if let Some(child_node) = Self::decode_linked_file_node(link, &decrypted_child)? {
982 Box::pin(self.append_range(&child_node, start, end, child_start, result)).await?;
983 continue;
984 }
985
986 let bytes = decrypted_child
987 .get(from as usize..to as usize)
988 .ok_or_else(|| {
989 HashTreeError::Store(format!("invalid chunk size for {}", to_hex(&link.hash)))
990 })?;
991 result.extend_from_slice(bytes);
992 }
993
994 Ok(())
995 }
996
997 async fn assemble_chunks_limited(
999 &self,
1000 node: &TreeNode,
1001 max_size: Option<u64>,
1002 bytes_read: &mut u64,
1003 ) -> Result<Vec<u8>, HashTreeError> {
1004 let mut parts: Vec<Vec<u8>> = Vec::new();
1005
1006 for link in &node.links {
1007 let projected = (*bytes_read).saturating_add(link.size);
1008 Self::ensure_size_limit(max_size, projected)?;
1009
1010 let child_data = self.read_link_bytes(link).await?;
1011
1012 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
1013 parts.push(
1014 Box::pin(self.assemble_chunks_limited(&child_node, max_size, bytes_read))
1015 .await?,
1016 );
1017 } else {
1018 let projected = (*bytes_read).saturating_add(child_data.len() as u64);
1019 Self::ensure_size_limit(max_size, projected)?;
1020 *bytes_read = projected;
1021 parts.push(child_data);
1022 }
1023 }
1024
1025 let total_length: usize = parts.iter().map(|p| p.len()).sum();
1027 let mut result = Vec::with_capacity(total_length);
1028 for part in parts {
1029 result.extend_from_slice(&part);
1030 }
1031
1032 Ok(result)
1033 }
1034
1035 pub async fn read_file_chunks(&self, hash: &Hash) -> Result<Vec<Vec<u8>>, HashTreeError> {
1037 let data = match self
1038 .store
1039 .get(hash)
1040 .await
1041 .map_err(|e| HashTreeError::Store(e.to_string()))?
1042 {
1043 Some(d) => d,
1044 None => return Ok(vec![]),
1045 };
1046
1047 if !is_tree_node(&data) {
1048 return Ok(vec![data]);
1049 }
1050
1051 let node = decode_tree_node(&data)?;
1052 self.collect_chunks(&node).await
1053 }
1054
1055 async fn collect_chunks(&self, node: &TreeNode) -> Result<Vec<Vec<u8>>, HashTreeError> {
1056 let mut chunks = Vec::new();
1057
1058 for link in &node.links {
1059 let child_data = self.read_link_bytes(link).await?;
1060
1061 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
1062 chunks.extend(Box::pin(self.collect_chunks(&child_node)).await?);
1063 } else {
1064 chunks.push(child_data);
1065 }
1066 }
1067
1068 Ok(chunks)
1069 }
1070
1071 pub async fn list(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1073 let node = match self.get_node(cid).await? {
1074 Some(n) => n,
1075 None => return Ok(vec![]),
1076 };
1077
1078 let mut entries = Vec::new();
1079
1080 for link in &node.links {
1081 if is_internal_directory_link(&node, link) {
1083 let chunk_cid = Cid {
1084 hash: link.hash,
1085 key: link.key,
1086 };
1087 let sub_entries = Box::pin(self.list(&chunk_cid)).await?;
1088 entries.extend(sub_entries);
1089 continue;
1090 }
1091
1092 entries.push(TreeEntry {
1093 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1094 hash: link.hash,
1095 size: link.size,
1096 link_type: link.link_type,
1097 key: link.key,
1098 meta: link.meta.clone(),
1099 });
1100 }
1101
1102 Ok(entries)
1103 }
1104
1105 pub async fn list_directory(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1108 let node = match self.get_directory_node(cid).await? {
1110 Some(n) => n,
1111 None => return Ok(vec![]),
1112 };
1113
1114 let mut entries = Vec::new();
1115
1116 for link in &node.links {
1117 if is_internal_directory_link(&node, link) {
1119 let sub_cid = Cid {
1120 hash: link.hash,
1121 key: link.key,
1122 };
1123 let sub_entries = Box::pin(self.list_directory(&sub_cid)).await?;
1124 entries.extend(sub_entries);
1125 continue;
1126 }
1127
1128 entries.push(TreeEntry {
1129 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1130 hash: link.hash,
1131 size: link.size,
1132 link_type: link.link_type,
1133 key: link.key,
1134 meta: link.meta.clone(),
1135 });
1136 }
1137
1138 Ok(entries)
1139 }
1140
1141 pub async fn list_directory_required(
1144 &self,
1145 cid: &Cid,
1146 ) -> Result<Vec<TreeEntry>, HashTreeError> {
1147 let node = self
1148 .get_directory_node(cid)
1149 .await?
1150 .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&cid.hash)))?;
1151
1152 let mut entries = Vec::new();
1153 for link in &node.links {
1154 if is_internal_directory_link(&node, link) {
1155 let sub_cid = Cid {
1156 hash: link.hash,
1157 key: link.key,
1158 };
1159 let sub_entries = Box::pin(self.list_directory_required(&sub_cid)).await?;
1160 entries.extend(sub_entries);
1161 continue;
1162 }
1163
1164 entries.push(TreeEntry {
1165 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1166 hash: link.hash,
1167 size: link.size,
1168 link_type: link.link_type,
1169 key: link.key,
1170 meta: link.meta.clone(),
1171 });
1172 }
1173
1174 Ok(entries)
1175 }
1176
1177 pub async fn resolve(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1179 let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
1180 if parts.is_empty() {
1181 return Ok(Some(cid.clone()));
1182 }
1183
1184 let mut current_cid = cid.clone();
1185
1186 for part in parts {
1187 let node = match self.get_directory_node(¤t_cid).await? {
1189 Some(n) => n,
1190 None => {
1191 if !self.has_stored_chunk(¤t_cid.hash).await? {
1192 return Err(HashTreeError::MissingChunk(to_hex(¤t_cid.hash)));
1193 }
1194 return Ok(None);
1195 }
1196 };
1197
1198 if let Some(link) = self.find_link(&node, part) {
1199 current_cid = Cid {
1200 hash: link.hash,
1201 key: link.key,
1202 };
1203 } else {
1204 match self
1206 .find_link_in_subtrees_cid(&node, part, ¤t_cid)
1207 .await?
1208 {
1209 Some(link) => {
1210 current_cid = Cid {
1211 hash: link.hash,
1212 key: link.key,
1213 };
1214 }
1215 None => return Ok(None),
1216 }
1217 }
1218 }
1219
1220 Ok(Some(current_cid))
1221 }
1222
1223 pub async fn resolve_path(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1225 self.resolve(cid, path).await
1226 }
1227
1228 fn find_link(&self, node: &TreeNode, name: &str) -> Option<Link> {
1229 node.links
1230 .iter()
1231 .find(|l| !is_internal_directory_link(node, l) && l.name.as_deref() == Some(name))
1232 .cloned()
1233 }
1234
1235 async fn find_link_in_subtrees_cid(
1237 &self,
1238 node: &TreeNode,
1239 name: &str,
1240 _parent_cid: &Cid,
1241 ) -> Result<Option<Link>, HashTreeError> {
1242 for link in &node.links {
1243 if !is_internal_directory_link(node, link) {
1244 continue;
1245 }
1246
1247 let sub_cid = Cid {
1249 hash: link.hash,
1250 key: link.key,
1251 };
1252
1253 let sub_node = match self.get_node(&sub_cid).await? {
1254 Some(n) => n,
1255 None => {
1256 if !self.has_stored_chunk(&sub_cid.hash).await? {
1257 return Err(HashTreeError::MissingChunk(to_hex(&sub_cid.hash)));
1258 }
1259 continue;
1260 }
1261 };
1262
1263 if let Some(found) = self.find_link(&sub_node, name) {
1264 return Ok(Some(found));
1265 }
1266
1267 if let Some(deep_found) =
1268 Box::pin(self.find_link_in_subtrees_cid(&sub_node, name, &sub_cid)).await?
1269 {
1270 return Ok(Some(deep_found));
1271 }
1272 }
1273
1274 Ok(None)
1275 }
1276
1277 pub async fn get_size(&self, hash: &Hash) -> Result<u64, HashTreeError> {
1279 let data = match self
1280 .store
1281 .get(hash)
1282 .await
1283 .map_err(|e| HashTreeError::Store(e.to_string()))?
1284 {
1285 Some(d) => d,
1286 None => return Ok(0),
1287 };
1288
1289 if !is_tree_node(&data) {
1290 return Ok(data.len() as u64);
1291 }
1292
1293 let node = decode_tree_node(&data)?;
1294 let mut total = 0u64;
1296 for link in &node.links {
1297 total += link.size;
1298 }
1299 Ok(total)
1300 }
1301
1302 pub async fn get_size_cid(&self, cid: &Cid) -> Result<u64, HashTreeError> {
1304 if let Some(key) = cid.key {
1305 let data = match self.get_encrypted_root(&cid.hash, &key).await? {
1306 Some(d) => d,
1307 None => return Ok(0),
1308 };
1309 if is_tree_node(&data) {
1310 let node = decode_tree_node(&data)?;
1311 return Ok(node.links.iter().map(|link| link.size).sum());
1312 }
1313 return Ok(data.len() as u64);
1314 }
1315
1316 self.get_size(&cid.hash).await
1317 }
1318
1319 async fn put_edited_directory(
1323 &self,
1324 original: &Cid,
1325 entries: Vec<DirEntry>,
1326 ) -> Result<Cid, HashTreeError> {
1327 if original
1328 .key
1329 .is_some_and(|key| key.mode() == crate::encryption::EncryptionMode::RndV1)
1330 {
1331 let writer = Self {
1332 store: self.store.clone(),
1333 chunk_size: self.chunk_size,
1334 max_links: self.max_links,
1335 encrypted: true,
1336 encryption_mode: crate::encryption::EncryptionMode::RndV1,
1337 };
1338 writer.put_directory(entries).await
1339 } else {
1340 self.put_directory(entries).await
1341 }
1342 }
1343
1344 pub async fn set_entry(
1347 &self,
1348 root: &Cid,
1349 path: &[&str],
1350 name: &str,
1351 entry_cid: &Cid,
1352 size: u64,
1353 link_type: LinkType,
1354 ) -> Result<Cid, HashTreeError> {
1355 self.set_entry_with_meta(root, path, name, entry_cid, size, link_type, None)
1356 .await
1357 }
1358
1359 #[allow(clippy::too_many_arguments)]
1362 pub async fn set_entry_with_meta(
1363 &self,
1364 root: &Cid,
1365 path: &[&str],
1366 name: &str,
1367 entry_cid: &Cid,
1368 size: u64,
1369 link_type: LinkType,
1370 meta: Option<std::collections::HashMap<String, serde_json::Value>>,
1371 ) -> Result<Cid, HashTreeError> {
1372 let dir_cid = self.resolve_path_array(root, path).await?;
1373 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1374
1375 let entries = self.list_directory(&dir_cid).await?;
1376 let mut new_entries: Vec<DirEntry> = entries
1377 .into_iter()
1378 .filter(|e| e.name != name)
1379 .map(|e| DirEntry {
1380 name: e.name,
1381 hash: e.hash,
1382 size: e.size,
1383 key: e.key,
1384 link_type: e.link_type,
1385 meta: e.meta,
1386 })
1387 .collect();
1388
1389 new_entries.push(DirEntry {
1390 name: name.to_string(),
1391 hash: entry_cid.hash,
1392 size,
1393 key: entry_cid.key,
1394 link_type,
1395 meta,
1396 });
1397
1398 let new_dir_cid = self.put_edited_directory(&dir_cid, new_entries).await?;
1399 self.rebuild_path(root, path, new_dir_cid).await
1400 }
1401
1402 pub async fn remove_entry(
1405 &self,
1406 root: &Cid,
1407 path: &[&str],
1408 name: &str,
1409 ) -> Result<Cid, HashTreeError> {
1410 let dir_cid = self.resolve_path_array(root, path).await?;
1411 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1412
1413 let entries = self.list_directory(&dir_cid).await?;
1414 let new_entries: Vec<DirEntry> = entries
1415 .into_iter()
1416 .filter(|e| e.name != name)
1417 .map(|e| DirEntry {
1418 name: e.name,
1419 hash: e.hash,
1420 size: e.size,
1421 key: e.key,
1422 link_type: e.link_type,
1423 meta: e.meta,
1424 })
1425 .collect();
1426
1427 let new_dir_cid = self.put_edited_directory(&dir_cid, new_entries).await?;
1428 self.rebuild_path(root, path, new_dir_cid).await
1429 }
1430
1431 pub async fn rename_entry(
1434 &self,
1435 root: &Cid,
1436 path: &[&str],
1437 old_name: &str,
1438 new_name: &str,
1439 ) -> Result<Cid, HashTreeError> {
1440 if old_name == new_name {
1441 return Ok(root.clone());
1442 }
1443
1444 let dir_cid = self.resolve_path_array(root, path).await?;
1445 let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1446
1447 let entries = self.list_directory(&dir_cid).await?;
1448 let entry = entries
1449 .iter()
1450 .find(|e| e.name == old_name)
1451 .ok_or_else(|| HashTreeError::EntryNotFound(old_name.to_string()))?;
1452
1453 let entry_hash = entry.hash;
1454 let entry_size = entry.size;
1455 let entry_key = entry.key;
1456 let entry_link_type = entry.link_type;
1457 let entry_meta = entry.meta.clone();
1458
1459 let new_entries: Vec<DirEntry> = entries
1460 .into_iter()
1461 .filter(|e| e.name != old_name)
1462 .map(|e| DirEntry {
1463 name: e.name,
1464 hash: e.hash,
1465 size: e.size,
1466 key: e.key,
1467 link_type: e.link_type,
1468 meta: e.meta,
1469 })
1470 .chain(std::iter::once(DirEntry {
1471 name: new_name.to_string(),
1472 hash: entry_hash,
1473 size: entry_size,
1474 key: entry_key,
1475 link_type: entry_link_type,
1476 meta: entry_meta,
1477 }))
1478 .collect();
1479
1480 let new_dir_cid = self.put_edited_directory(&dir_cid, new_entries).await?;
1481 self.rebuild_path(root, path, new_dir_cid).await
1482 }
1483
1484 pub async fn move_entry(
1487 &self,
1488 root: &Cid,
1489 source_path: &[&str],
1490 name: &str,
1491 target_path: &[&str],
1492 ) -> Result<Cid, HashTreeError> {
1493 let source_dir_cid = self.resolve_path_array(root, source_path).await?;
1494 let source_dir_cid =
1495 source_dir_cid.ok_or_else(|| HashTreeError::PathNotFound(source_path.join("/")))?;
1496
1497 let source_entries = self.list_directory(&source_dir_cid).await?;
1498 let entry = source_entries
1499 .iter()
1500 .find(|e| e.name == name)
1501 .ok_or_else(|| HashTreeError::EntryNotFound(name.to_string()))?;
1502
1503 let entry_cid = Cid {
1504 hash: entry.hash,
1505 key: entry.key,
1506 };
1507 let entry_size = entry.size;
1508 let entry_link_type = entry.link_type;
1509
1510 let new_root = self.remove_entry(root, source_path, name).await?;
1512
1513 self.set_entry(
1515 &new_root,
1516 target_path,
1517 name,
1518 &entry_cid,
1519 entry_size,
1520 entry_link_type,
1521 )
1522 .await
1523 }
1524
1525 async fn resolve_path_array(
1526 &self,
1527 root: &Cid,
1528 path: &[&str],
1529 ) -> Result<Option<Cid>, HashTreeError> {
1530 if path.is_empty() {
1531 return Ok(Some(root.clone()));
1532 }
1533 self.resolve_path(root, &path.join("/")).await
1534 }
1535
1536 async fn rebuild_path(
1537 &self,
1538 root: &Cid,
1539 path: &[&str],
1540 new_child: Cid,
1541 ) -> Result<Cid, HashTreeError> {
1542 if path.is_empty() {
1543 return Ok(new_child);
1544 }
1545
1546 let mut child_cid = new_child;
1547 let parts: Vec<&str> = path.to_vec();
1548
1549 for i in (0..parts.len()).rev() {
1550 let child_name = parts[i];
1551 let parent_path = &parts[..i];
1552
1553 let parent_cid = if parent_path.is_empty() {
1554 root.clone()
1555 } else {
1556 self.resolve_path_array(root, parent_path)
1557 .await?
1558 .ok_or_else(|| HashTreeError::PathNotFound(parent_path.join("/")))?
1559 };
1560
1561 let parent_entries = self.list_directory(&parent_cid).await?;
1562 let new_parent_entries: Vec<DirEntry> = parent_entries
1563 .into_iter()
1564 .map(|e| {
1565 if e.name == child_name {
1566 DirEntry {
1567 name: e.name,
1568 hash: child_cid.hash,
1569 size: 0, key: child_cid.key,
1571 link_type: e.link_type,
1572 meta: e.meta,
1573 }
1574 } else {
1575 DirEntry {
1576 name: e.name,
1577 hash: e.hash,
1578 size: e.size,
1579 key: e.key,
1580 link_type: e.link_type,
1581 meta: e.meta,
1582 }
1583 }
1584 })
1585 .collect();
1586
1587 child_cid = self
1588 .put_edited_directory(&parent_cid, new_parent_entries)
1589 .await?;
1590 }
1591
1592 Ok(child_cid)
1593 }
1594
1595 pub fn get_store(&self) -> Arc<S> {
1599 self.store.clone()
1600 }
1601
1602 pub fn chunk_size(&self) -> usize {
1604 self.chunk_size
1605 }
1606
1607 pub fn max_links(&self) -> usize {
1609 self.max_links
1610 }
1611}
1612
1613fn stream_put_batch_target_bytes() -> usize {
1614 std::env::var(STREAM_PUT_BATCH_TARGET_BYTES_ENV)
1615 .ok()
1616 .and_then(|value| value.parse::<usize>().ok())
1617 .filter(|value| *value > 0)
1618 .unwrap_or(DEFAULT_STREAM_PUT_BATCH_TARGET_BYTES)
1619}
1620
1621pub async fn verify_tree<S: Store>(
1623 store: Arc<S>,
1624 root_hash: &Hash,
1625) -> Result<crate::reader::VerifyResult, HashTreeError> {
1626 let mut missing = Vec::new();
1627 let mut visited = std::collections::HashSet::new();
1628
1629 verify_recursive(store, root_hash, &mut missing, &mut visited).await?;
1630
1631 Ok(crate::reader::VerifyResult {
1632 valid: missing.is_empty(),
1633 missing,
1634 })
1635}
1636
1637async fn verify_recursive<S: Store>(
1638 store: Arc<S>,
1639 hash: &Hash,
1640 missing: &mut Vec<Hash>,
1641 visited: &mut std::collections::HashSet<String>,
1642) -> Result<(), HashTreeError> {
1643 let hex = to_hex(hash);
1644 if visited.contains(&hex) {
1645 return Ok(());
1646 }
1647 visited.insert(hex);
1648
1649 let data = match store
1650 .get(hash)
1651 .await
1652 .map_err(|e| HashTreeError::Store(e.to_string()))?
1653 {
1654 Some(d) => d,
1655 None => {
1656 missing.push(*hash);
1657 return Ok(());
1658 }
1659 };
1660
1661 if is_tree_node(&data) {
1662 let node = decode_tree_node(&data)?;
1663 for link in &node.links {
1664 Box::pin(verify_recursive(
1665 store.clone(),
1666 &link.hash,
1667 missing,
1668 visited,
1669 ))
1670 .await?;
1671 }
1672 }
1673
1674 Ok(())
1675}
1676
1677#[cfg(test)]
1678mod tests;