1#![allow(deprecated)]
7
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use crate::codec::{decode_tree_node, is_directory_node, is_tree_node};
12use crate::directory::is_internal_directory_link;
13use crate::hash::sha256;
14use crate::store::Store;
15use crate::types::{to_hex, Cid, Hash, Link, LinkType, TreeNode};
16
17use crate::encryption::{decrypt_object, ObjectKey};
18
19#[derive(Debug, Clone)]
21pub struct TreeEntry {
22 pub name: String,
23 pub hash: Hash,
24 pub size: u64,
25 pub link_type: LinkType,
27 pub key: Option<ObjectKey>,
29 pub meta: Option<HashMap<String, serde_json::Value>>,
31}
32
33#[derive(Debug, Clone)]
35pub struct WalkEntry {
36 pub path: String,
37 pub hash: Hash,
38 pub link_type: LinkType,
40 pub size: u64,
41 pub key: Option<ObjectKey>,
43}
44
45#[deprecated(
47 since = "0.3.1",
48 note = "use `HashTree`, which covers the same operations"
49)]
50pub struct TreeReader<S: Store> {
51 store: Arc<S>,
52}
53
54impl<S: Store> TreeReader<S> {
55 fn decode_node_or_blob(data: &[u8]) -> Result<Option<TreeNode>, ReaderError> {
56 match decode_tree_node(data) {
57 Ok(node) => Ok(Some(node)),
58 Err(err) if is_tree_node(data) => Err(ReaderError::Codec(err)),
59 Err(_) => Ok(None),
60 }
61 }
62
63 fn decode_linked_file_node(link: &Link, data: &[u8]) -> Result<Option<TreeNode>, ReaderError> {
64 match link.link_type {
65 LinkType::File => match decode_tree_node(data) {
66 Ok(node) => Ok(Some(node)),
67 Err(_) if link.size == data.len() as u64 => Ok(None),
68 Err(err) => Err(ReaderError::Codec(err)),
69 },
70 LinkType::Blob => {
71 if link.size == data.len() as u64 {
72 return Ok(None);
73 }
74
75 match decode_tree_node(data) {
76 Ok(node) if node.node_type == LinkType::File => Ok(Some(node)),
77 _ => Ok(None),
78 }
79 }
80 LinkType::Dir | LinkType::Fanout => Ok(None),
81 }
82 }
83
84 pub fn new(store: Arc<S>) -> Self {
85 Self { store }
86 }
87
88 pub async fn get_blob(&self, hash: &Hash) -> Result<Option<Vec<u8>>, ReaderError> {
90 self.store
91 .get(hash)
92 .await
93 .map_err(|e| ReaderError::Store(e.to_string()))
94 }
95
96 pub async fn get_tree_node(&self, hash: &Hash) -> Result<Option<TreeNode>, ReaderError> {
98 let data = match self
99 .store
100 .get(hash)
101 .await
102 .map_err(|e| ReaderError::Store(e.to_string()))?
103 {
104 Some(d) => d,
105 None => return Ok(None),
106 };
107
108 if !is_tree_node(&data) {
109 return Ok(None); }
111
112 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
113 Ok(Some(node))
114 }
115
116 pub async fn is_tree(&self, hash: &Hash) -> Result<bool, ReaderError> {
118 let data = match self
119 .store
120 .get(hash)
121 .await
122 .map_err(|e| ReaderError::Store(e.to_string()))?
123 {
124 Some(d) => d,
125 None => return Ok(false),
126 };
127 Ok(is_tree_node(&data))
128 }
129
130 pub async fn is_directory(&self, hash: &Hash) -> Result<bool, ReaderError> {
133 let data = match self
134 .store
135 .get(hash)
136 .await
137 .map_err(|e| ReaderError::Store(e.to_string()))?
138 {
139 Some(d) => d,
140 None => return Ok(false),
141 };
142 Ok(is_directory_node(&data))
143 }
144
145 pub async fn get(&self, cid: &Cid) -> Result<Option<Vec<u8>>, ReaderError> {
150 if let Some(key) = cid.key {
151 self.get_encrypted(&cid.hash, &key).await
152 } else {
153 self.read_file(&cid.hash).await
154 }
155 }
156
157 async fn get_encrypted(
159 &self,
160 hash: &Hash,
161 key: &ObjectKey,
162 ) -> Result<Option<Vec<u8>>, ReaderError> {
163 let encrypted_data = match self
164 .store
165 .get(hash)
166 .await
167 .map_err(|e| ReaderError::Store(e.to_string()))?
168 {
169 Some(d) => d,
170 None => return Ok(None),
171 };
172
173 let decrypted = decrypt_object(&encrypted_data, key)
175 .map_err(|e| ReaderError::Decryption(e.to_string()))?;
176
177 if is_tree_node(&decrypted) {
179 let node = decode_tree_node(&decrypted)?;
180 let assembled = self.assemble_chunks(&node).await?;
181 return Ok(Some(assembled));
182 }
183
184 Ok(Some(decrypted))
186 }
187
188 async fn read_link_bytes(&self, cid: &Cid) -> Result<Vec<u8>, ReaderError> {
189 let stored = self
190 .get_blob(&cid.hash)
191 .await?
192 .ok_or_else(|| ReaderError::MissingChunk(to_hex(&cid.hash)))?;
193 match &cid.key {
194 Some(key) => {
195 decrypt_object(&stored, key).map_err(|e| ReaderError::Decryption(e.to_string()))
196 }
197 None => Ok(stored),
198 }
199 }
200
201 pub async fn read_file(&self, hash: &Hash) -> Result<Option<Vec<u8>>, ReaderError> {
204 let data = match self
205 .store
206 .get(hash)
207 .await
208 .map_err(|e| ReaderError::Store(e.to_string()))?
209 {
210 Some(d) => d,
211 None => return Ok(None),
212 };
213
214 if !is_tree_node(&data) {
216 return Ok(Some(data)); }
218
219 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
221 let assembled = self.assemble_chunks(&node).await?;
222 Ok(Some(assembled))
223 }
224
225 pub async fn read_file_range(
232 &self,
233 hash: &Hash,
234 start: u64,
235 end: Option<u64>,
236 ) -> Result<Option<Vec<u8>>, ReaderError> {
237 let data = match self
238 .store
239 .get(hash)
240 .await
241 .map_err(|e| ReaderError::Store(e.to_string()))?
242 {
243 Some(d) => d,
244 None => return Ok(None),
245 };
246
247 if !is_tree_node(&data) {
249 let start_idx = start as usize;
250 let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
251 if start_idx >= data.len() {
252 return Ok(Some(vec![]));
253 }
254 let end_idx = end_idx.min(data.len());
255 return Ok(Some(data[start_idx..end_idx].to_vec()));
256 }
257
258 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
260 let range_data = self.assemble_chunks_range(&node, start, end).await?;
261 Ok(Some(range_data))
262 }
263
264 async fn assemble_chunks_range(
266 &self,
267 node: &TreeNode,
268 start: u64,
269 end: Option<u64>,
270 ) -> Result<Vec<u8>, ReaderError> {
271 let chunks_info = self.collect_chunk_offsets(node).await?;
273
274 if chunks_info.is_empty() {
275 return Ok(vec![]);
276 }
277
278 let total_size: u64 = chunks_info.iter().map(|(_, _, size)| size).sum();
280 let actual_end = end.unwrap_or(total_size).min(total_size);
281
282 if start >= actual_end {
283 return Ok(vec![]);
284 }
285
286 let mut result = Vec::with_capacity((actual_end - start) as usize);
288
289 for (cid, chunk_start, chunk_size) in &chunks_info {
290 let chunk_start = *chunk_start;
291 let chunk_size = *chunk_size;
292 let chunk_end = chunk_start + chunk_size;
293
294 if chunk_end > start && chunk_start < actual_end {
296 let read_start = start.saturating_sub(chunk_start);
297 let read_end_exclusive = actual_end.min(chunk_end) - chunk_start;
298 if read_start >= read_end_exclusive {
299 continue;
300 }
301
302 let chunk_hash = &cid.hash;
303 let chunk_data = if cid.key.is_some() {
304 let bytes = self.read_link_bytes(cid).await?;
305 bytes
306 .get(read_start as usize..read_end_exclusive as usize)
307 .ok_or_else(|| {
308 ReaderError::Store(format!(
309 "invalid chunk size for {}",
310 to_hex(chunk_hash)
311 ))
312 })?
313 .to_vec()
314 } else {
315 self.store
316 .get_range(chunk_hash, read_start, read_end_exclusive - 1)
317 .await
318 .map_err(|e| ReaderError::Store(e.to_string()))?
319 .ok_or_else(|| ReaderError::MissingChunk(to_hex(chunk_hash)))?
320 };
321
322 let expected_len = (read_end_exclusive - read_start) as usize;
323 if chunk_data.len() != expected_len {
324 return Err(ReaderError::Store(format!(
325 "range read for {} returned {} bytes, expected {}",
326 to_hex(chunk_hash),
327 chunk_data.len(),
328 expected_len
329 )));
330 }
331
332 result.extend_from_slice(&chunk_data);
333 }
334
335 if chunk_end >= actual_end {
337 break;
338 }
339 }
340
341 Ok(result)
342 }
343
344 async fn collect_chunk_offsets(
347 &self,
348 node: &TreeNode,
349 ) -> Result<Vec<(Cid, u64, u64)>, ReaderError> {
350 let mut chunks = Vec::new();
351 let mut offset = 0u64;
352 self.collect_chunk_offsets_recursive(node, &mut chunks, &mut offset)
353 .await?;
354 Ok(chunks)
355 }
356
357 async fn collect_chunk_offsets_recursive(
358 &self,
359 node: &TreeNode,
360 chunks: &mut Vec<(Cid, u64, u64)>,
361 offset: &mut u64,
362 ) -> Result<(), ReaderError> {
363 for link in &node.links {
364 if link.link_type == LinkType::Blob {
365 chunks.push((link.to_cid(), *offset, link.size));
366 *offset += link.size;
367 continue;
368 }
369
370 let child_data = self.read_link_bytes(&link.to_cid()).await?;
371
372 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
373 Box::pin(self.collect_chunk_offsets_recursive(&child_node, chunks, offset)).await?;
375 } else {
376 let size = child_data.len() as u64;
378 chunks.push((link.to_cid(), *offset, size));
379 *offset += size;
380 }
381 }
382 Ok(())
383 }
384
385 async fn assemble_chunks(&self, node: &TreeNode) -> Result<Vec<u8>, ReaderError> {
387 let mut parts: Vec<Vec<u8>> = Vec::new();
388
389 for link in &node.links {
390 let child_data = self.read_link_bytes(&link.to_cid()).await?;
391
392 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
393 parts.push(Box::pin(self.assemble_chunks(&child_node)).await?);
395 } else {
396 parts.push(child_data);
398 }
399 }
400
401 let total_length: usize = parts.iter().map(|p| p.len()).sum();
403 let mut result = Vec::with_capacity(total_length);
404 for part in parts {
405 result.extend_from_slice(&part);
406 }
407
408 Ok(result)
409 }
410
411 pub async fn read_file_chunks(&self, hash: &Hash) -> Result<Vec<Vec<u8>>, ReaderError> {
413 let data = match self
414 .store
415 .get(hash)
416 .await
417 .map_err(|e| ReaderError::Store(e.to_string()))?
418 {
419 Some(d) => d,
420 None => return Ok(vec![]),
421 };
422
423 if !is_tree_node(&data) {
424 return Ok(vec![data]);
425 }
426
427 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
428 self.collect_chunks(&node).await
429 }
430
431 async fn collect_chunks(&self, node: &TreeNode) -> Result<Vec<Vec<u8>>, ReaderError> {
433 let mut chunks = Vec::new();
434
435 for link in &node.links {
436 let child_data = self.read_link_bytes(&link.to_cid()).await?;
437
438 if let Some(child_node) = Self::decode_linked_file_node(link, &child_data)? {
439 chunks.extend(Box::pin(self.collect_chunks(&child_node)).await?);
440 } else {
441 chunks.push(child_data);
442 }
443 }
444
445 Ok(chunks)
446 }
447
448 pub async fn list_directory(&self, hash: &Hash) -> Result<Vec<TreeEntry>, ReaderError> {
450 let node = match self.get_tree_node(hash).await? {
451 Some(n) => n,
452 None => return Ok(vec![]),
453 };
454
455 let mut entries = Vec::new();
456
457 for link in &node.links {
458 if is_internal_directory_link(&node, link) {
460 let sub_entries = Box::pin(self.list_directory(&link.hash)).await?;
461 entries.extend(sub_entries);
462 continue;
463 }
464
465 entries.push(TreeEntry {
466 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
467 hash: link.hash,
468 size: link.size,
469 link_type: link.link_type,
470 key: link.key,
471 meta: link.meta.clone(),
472 });
473 }
474
475 Ok(entries)
476 }
477
478 pub async fn resolve_path(
481 &self,
482 root_hash: &Hash,
483 path: &str,
484 ) -> Result<Option<Hash>, ReaderError> {
485 let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
486
487 let mut current_hash = *root_hash;
488
489 for part in parts {
490 let node = match self.get_tree_node(¤t_hash).await? {
491 Some(n) => n,
492 None => return Ok(None),
493 };
494
495 if let Some(link) = self.find_link(&node, part) {
496 current_hash = link.hash;
497 } else {
498 match self.find_in_subtrees(&node, part).await? {
500 Some(hash) => current_hash = hash,
501 None => return Ok(None),
502 }
503 }
504 }
505
506 Ok(Some(current_hash))
507 }
508
509 fn find_link(&self, node: &TreeNode, name: &str) -> Option<Link> {
511 node.links
512 .iter()
513 .find(|l| !is_internal_directory_link(node, l) && l.name.as_deref() == Some(name))
514 .cloned()
515 }
516
517 async fn find_in_subtrees(
519 &self,
520 node: &TreeNode,
521 name: &str,
522 ) -> Result<Option<Hash>, ReaderError> {
523 for link in &node.links {
524 if !is_internal_directory_link(node, link) {
526 continue;
527 }
528
529 let sub_node = match self.get_tree_node(&link.hash).await? {
530 Some(n) => n,
531 None => continue,
532 };
533
534 if let Some(found) = self.find_link(&sub_node, name) {
535 return Ok(Some(found.hash));
536 }
537
538 if let Some(deep_found) = Box::pin(self.find_in_subtrees(&sub_node, name)).await? {
540 return Ok(Some(deep_found));
541 }
542 }
543
544 Ok(None)
545 }
546
547 pub async fn get_size(&self, hash: &Hash) -> Result<u64, ReaderError> {
549 let data = match self
550 .store
551 .get(hash)
552 .await
553 .map_err(|e| ReaderError::Store(e.to_string()))?
554 {
555 Some(d) => d,
556 None => return Ok(0),
557 };
558
559 if !is_tree_node(&data) {
560 return Ok(data.len() as u64);
561 }
562
563 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
564 let mut total = 0u64;
566 for link in &node.links {
567 total += link.size;
568 }
569 Ok(total)
570 }
571
572 pub async fn walk(&self, hash: &Hash, path: &str) -> Result<Vec<WalkEntry>, ReaderError> {
574 let mut entries = Vec::new();
575 self.walk_recursive(hash, path, &mut entries).await?;
576 Ok(entries)
577 }
578
579 async fn walk_recursive(
580 &self,
581 hash: &Hash,
582 path: &str,
583 entries: &mut Vec<WalkEntry>,
584 ) -> Result<(), ReaderError> {
585 let data = match self
586 .store
587 .get(hash)
588 .await
589 .map_err(|e| ReaderError::Store(e.to_string()))?
590 {
591 Some(d) => d,
592 None => return Ok(()),
593 };
594
595 let node = match Self::decode_node_or_blob(&data)? {
596 Some(node) => node,
597 None => {
598 entries.push(WalkEntry {
599 path: path.to_string(),
600 hash: *hash,
601 link_type: LinkType::Blob,
602 size: data.len() as u64,
603 key: None, });
605 return Ok(());
606 }
607 };
608
609 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
610 entries.push(WalkEntry {
611 path: path.to_string(),
612 hash: *hash,
613 link_type: node.node_type,
614 size: node_size,
615 key: None, });
617
618 for link in &node.links {
619 let child_path = match &link.name {
620 Some(name) => {
621 if is_internal_directory_link(&node, link) {
623 Box::pin(self.walk_recursive(&link.hash, path, entries)).await?;
624 continue;
625 }
626 if path.is_empty() {
627 name.clone()
628 } else {
629 format!("{}/{}", path, name)
630 }
631 }
632 None => path.to_string(),
633 };
634
635 Box::pin(self.walk_recursive(&link.hash, &child_path, entries)).await?;
636 }
637
638 Ok(())
639 }
640}
641
642pub async fn verify_tree<S: Store>(
645 store: Arc<S>,
646 root_hash: &Hash,
647) -> Result<VerifyResult, ReaderError> {
648 let mut missing = Vec::new();
649 let mut visited = std::collections::HashSet::new();
650
651 verify_recursive(store, root_hash, &mut missing, &mut visited).await?;
652
653 Ok(VerifyResult {
654 valid: missing.is_empty(),
655 missing,
656 })
657}
658
659async fn verify_recursive<S: Store>(
660 store: Arc<S>,
661 hash: &Hash,
662 missing: &mut Vec<Hash>,
663 visited: &mut std::collections::HashSet<String>,
664) -> Result<(), ReaderError> {
665 let hex = to_hex(hash);
666 if visited.contains(&hex) {
667 return Ok(());
668 }
669 visited.insert(hex);
670
671 let data = match store
672 .get(hash)
673 .await
674 .map_err(|e| ReaderError::Store(e.to_string()))?
675 {
676 Some(d) => d,
677 None => {
678 missing.push(*hash);
679 return Ok(());
680 }
681 };
682
683 if is_tree_node(&data) {
684 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
685 for link in &node.links {
686 Box::pin(verify_recursive(
687 store.clone(),
688 &link.hash,
689 missing,
690 visited,
691 ))
692 .await?;
693 }
694 }
695
696 Ok(())
697}
698
699pub async fn verify_tree_integrity<S: Store>(
705 store: Arc<S>,
706 root_hash: &Hash,
707) -> Result<VerifyIntegrityResult, ReaderError> {
708 let mut missing = Vec::new();
709 let mut corrupted = Vec::new();
710 let mut visited = std::collections::HashSet::new();
711
712 verify_integrity_recursive(store, root_hash, &mut missing, &mut corrupted, &mut visited)
713 .await?;
714
715 Ok(VerifyIntegrityResult {
716 valid: missing.is_empty() && corrupted.is_empty(),
717 missing,
718 corrupted,
719 })
720}
721
722async fn verify_integrity_recursive<S: Store>(
723 store: Arc<S>,
724 hash: &Hash,
725 missing: &mut Vec<Hash>,
726 corrupted: &mut Vec<Hash>,
727 visited: &mut std::collections::HashSet<String>,
728) -> Result<(), ReaderError> {
729 let hex = to_hex(hash);
730 if visited.contains(&hex) {
731 return Ok(());
732 }
733 visited.insert(hex);
734
735 let data = match store
736 .get(hash)
737 .await
738 .map_err(|e| ReaderError::Store(e.to_string()))?
739 {
740 Some(d) => d,
741 None => {
742 missing.push(*hash);
743 return Ok(());
744 }
745 };
746
747 if sha256(&data) != *hash {
749 corrupted.push(*hash);
750 return Ok(());
751 }
752
753 if is_tree_node(&data) {
754 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
755 for link in &node.links {
756 Box::pin(verify_integrity_recursive(
757 store.clone(),
758 &link.hash,
759 missing,
760 corrupted,
761 visited,
762 ))
763 .await?;
764 }
765 }
766
767 Ok(())
768}
769
770#[derive(Debug, Clone)]
772pub struct VerifyResult {
773 pub valid: bool,
774 pub missing: Vec<Hash>,
775}
776
777#[derive(Debug, Clone)]
779pub struct VerifyIntegrityResult {
780 pub valid: bool,
781 pub missing: Vec<Hash>,
782 pub corrupted: Vec<Hash>,
783}
784
785#[derive(Debug, thiserror::Error)]
787pub enum ReaderError {
788 #[error("Store error: {0}")]
789 Store(String),
790 #[error("Codec error: {0}")]
791 Codec(#[from] crate::codec::CodecError),
792 #[error("Missing chunk: {0}")]
793 MissingChunk(String),
794 #[error("Decryption error: {0}")]
795 Decryption(String),
796 #[error("Missing decryption key")]
797 MissingKey,
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use crate::builder::{BuilderConfig, TreeBuilder};
804 use crate::store::MemoryStore;
805 use crate::types::DirEntry;
806
807 fn make_store() -> Arc<MemoryStore> {
808 Arc::new(MemoryStore::new())
809 }
810
811 fn invalid_tree_shape_blob() -> Vec<u8> {
812 #[derive(serde::Serialize)]
813 struct Shape {
814 l: Vec<()>,
815 t: u8,
816 }
817
818 rmp_serde::to_vec_named(&Shape {
819 l: Vec::new(),
820 t: 98,
821 })
822 .unwrap()
823 }
824
825 #[tokio::test]
826 async fn test_get_blob() {
827 let store = make_store();
828 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
829 let reader = TreeReader::new(store);
830
831 let data = vec![1u8, 2, 3, 4, 5];
832 let hash = builder.put_blob(&data).await.unwrap();
833
834 let result = reader.get_blob(&hash).await.unwrap();
835 assert_eq!(result, Some(data));
836 }
837
838 #[tokio::test]
839 async fn test_get_blob_missing() {
840 let store = make_store();
841 let reader = TreeReader::new(store);
842
843 let hash = [0u8; 32];
844 let result = reader.get_blob(&hash).await.unwrap();
845 assert!(result.is_none());
846 }
847
848 #[tokio::test]
849 async fn test_get_tree_node() {
850 let store = make_store();
851 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
852 let reader = TreeReader::new(store);
853
854 let file_hash = builder.put_blob(&[1u8]).await.unwrap();
855 let dir_hash = builder
856 .put_directory(vec![DirEntry::new("test.txt", file_hash).with_size(1)])
857 .await
858 .unwrap();
859
860 let node = reader.get_tree_node(&dir_hash).await.unwrap();
861 assert!(node.is_some());
862 assert_eq!(node.unwrap().links.len(), 1);
863 }
864
865 #[tokio::test]
866 async fn test_get_tree_node_returns_none_for_blob() {
867 let store = make_store();
868 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
869 let reader = TreeReader::new(store);
870
871 let hash = builder.put_blob(&[1u8, 2, 3]).await.unwrap();
872 let node = reader.get_tree_node(&hash).await.unwrap();
873 assert!(node.is_none());
874 }
875
876 #[tokio::test]
877 async fn test_is_tree() {
878 let store = make_store();
879 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
880 let reader = TreeReader::new(store);
881
882 let file_hash = builder.put_blob(&[1u8]).await.unwrap();
883 let dir_hash = builder
884 .put_directory(vec![DirEntry::new("test.txt", file_hash)])
885 .await
886 .unwrap();
887
888 assert!(reader.is_tree(&dir_hash).await.unwrap());
889 assert!(!reader.is_tree(&file_hash).await.unwrap());
890 }
891
892 #[tokio::test]
893 async fn test_read_file_small() {
894 let store = make_store();
895 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()).public());
897 let reader = TreeReader::new(store);
898
899 let data = vec![1u8, 2, 3, 4, 5];
900 let (cid, _size) = builder.put(&data).await.unwrap();
901
902 let result = reader.read_file(&cid.hash).await.unwrap();
903 assert_eq!(result, Some(data));
904 }
905
906 #[tokio::test]
907 async fn test_read_file_chunked() {
908 let store = make_store();
909 let config = BuilderConfig::new(store.clone())
910 .with_chunk_size(100)
911 .public();
912 let builder = TreeBuilder::new(config);
913 let reader = TreeReader::new(store);
914
915 let mut data = vec![0u8; 350];
916 for (i, byte) in data.iter_mut().enumerate() {
917 *byte = (i % 256) as u8;
918 }
919
920 let (cid, _size) = builder.put(&data).await.unwrap();
921 let result = reader.read_file(&cid.hash).await.unwrap();
922
923 assert_eq!(result, Some(data));
924 }
925
926 #[tokio::test]
927 async fn test_list_directory() {
928 let store = make_store();
929 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
930 let reader = TreeReader::new(store);
931
932 let h1 = builder.put_blob(&[1u8]).await.unwrap();
933 let h2 = builder.put_blob(&[2u8]).await.unwrap();
934
935 let dir_hash = builder
936 .put_directory(vec![
937 DirEntry::new("first.txt", h1).with_size(1),
938 DirEntry::new("second.txt", h2).with_size(1),
939 ])
940 .await
941 .unwrap();
942
943 let entries = reader.list_directory(&dir_hash).await.unwrap();
944
945 assert_eq!(entries.len(), 2);
946 assert!(entries.iter().any(|e| e.name == "first.txt"));
947 assert!(entries.iter().any(|e| e.name == "second.txt"));
948 }
949
950 #[tokio::test]
951 async fn test_resolve_path() {
952 let store = make_store();
953 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
954 let reader = TreeReader::new(store);
955
956 let file_data = vec![1u8, 2, 3];
957 let file_hash = builder.put_blob(&file_data).await.unwrap();
958
959 let dir_hash = builder
960 .put_directory(vec![DirEntry::new("test.txt", file_hash)])
961 .await
962 .unwrap();
963
964 let resolved = reader.resolve_path(&dir_hash, "test.txt").await.unwrap();
965 assert_eq!(resolved, Some(file_hash));
966 }
967
968 #[tokio::test]
969 async fn test_resolve_path_nested() {
970 let store = make_store();
971 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
972 let reader = TreeReader::new(store);
973
974 let file_hash = builder.put_blob(&[1u8]).await.unwrap();
975
976 let sub_sub_dir = builder
977 .put_directory(vec![DirEntry::new("deep.txt", file_hash)])
978 .await
979 .unwrap();
980
981 let sub_dir = builder
982 .put_directory(vec![DirEntry::new("level2", sub_sub_dir)])
983 .await
984 .unwrap();
985
986 let root_dir = builder
987 .put_directory(vec![DirEntry::new("level1", sub_dir)])
988 .await
989 .unwrap();
990
991 let resolved = reader
992 .resolve_path(&root_dir, "level1/level2/deep.txt")
993 .await
994 .unwrap();
995 assert_eq!(resolved, Some(file_hash));
996 }
997
998 #[tokio::test]
999 async fn test_get_size() {
1000 let store = make_store();
1001 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
1002 let reader = TreeReader::new(store);
1003
1004 let data = vec![0u8; 123];
1005 let hash = builder.put_blob(&data).await.unwrap();
1006
1007 assert_eq!(reader.get_size(&hash).await.unwrap(), 123);
1008 }
1009
1010 #[tokio::test]
1011 async fn test_walk() {
1012 let store = make_store();
1013 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
1014 let reader = TreeReader::new(store);
1015
1016 let f1 = builder.put_blob(&[1u8]).await.unwrap();
1017 let f2 = builder.put_blob(&[2u8, 3]).await.unwrap();
1018
1019 let sub_dir = builder
1020 .put_directory(vec![DirEntry::new("nested.txt", f2).with_size(2)])
1021 .await
1022 .unwrap();
1023
1024 let root_dir = builder
1025 .put_directory(vec![
1026 DirEntry::new("root.txt", f1).with_size(1),
1027 DirEntry::new("sub", sub_dir),
1028 ])
1029 .await
1030 .unwrap();
1031
1032 let entries = reader.walk(&root_dir, "").await.unwrap();
1033 let paths: Vec<_> = entries.iter().map(|e| e.path.as_str()).collect();
1034
1035 assert!(paths.contains(&""));
1036 assert!(paths.contains(&"root.txt"));
1037 assert!(paths.contains(&"sub"));
1038 assert!(paths.contains(&"sub/nested.txt"));
1039 }
1040
1041 #[tokio::test]
1042 async fn test_verify_tree_valid() {
1043 let store = make_store();
1044 let config = BuilderConfig::new(store.clone())
1045 .with_chunk_size(100)
1046 .public();
1047 let builder = TreeBuilder::new(config);
1048
1049 let data = vec![0u8; 350];
1050 let (cid, _size) = builder.put(&data).await.unwrap();
1051
1052 let result = verify_tree(store, &cid.hash).await.unwrap();
1053 assert!(result.valid);
1054 assert!(result.missing.is_empty());
1055 }
1056
1057 #[tokio::test]
1058 async fn test_verify_tree_missing() {
1059 let store = make_store();
1060 let config = BuilderConfig::new(store.clone())
1061 .with_chunk_size(100)
1062 .public();
1063 let builder = TreeBuilder::new(config);
1064
1065 let data = vec![0u8; 350];
1066 let (cid, _size) = builder.put(&data).await.unwrap();
1067
1068 let keys = store.keys();
1070 if let Some(chunk_to_delete) = keys.iter().find(|k| **k != cid.hash) {
1071 store.delete(chunk_to_delete).await.unwrap();
1072 }
1073
1074 let result = verify_tree(store, &cid.hash).await.unwrap();
1075 assert!(!result.valid);
1076 assert!(!result.missing.is_empty());
1077 }
1078
1079 #[tokio::test]
1080 async fn test_verify_tree_integrity_valid() {
1081 let store = make_store();
1082 let config = BuilderConfig::new(store.clone())
1083 .with_chunk_size(100)
1084 .public();
1085 let builder = TreeBuilder::new(config);
1086
1087 let data = vec![0u8; 350];
1088 let (cid, _size) = builder.put(&data).await.unwrap();
1089
1090 let result = verify_tree_integrity(store, &cid.hash).await.unwrap();
1091 assert!(result.valid);
1092 assert!(result.missing.is_empty());
1093 assert!(result.corrupted.is_empty());
1094 }
1095
1096 #[tokio::test]
1097 async fn test_verify_tree_integrity_missing() {
1098 let store = make_store();
1099 let config = BuilderConfig::new(store.clone())
1100 .with_chunk_size(100)
1101 .public();
1102 let builder = TreeBuilder::new(config);
1103
1104 let data = vec![0u8; 350];
1105 let (cid, _size) = builder.put(&data).await.unwrap();
1106
1107 let keys = store.keys();
1109 if let Some(chunk_to_delete) = keys.iter().find(|k| **k != cid.hash) {
1110 store.delete(chunk_to_delete).await.unwrap();
1111 }
1112
1113 let result = verify_tree_integrity(store, &cid.hash).await.unwrap();
1114 assert!(!result.valid);
1115 assert!(!result.missing.is_empty());
1116 assert!(result.corrupted.is_empty());
1117 }
1118
1119 #[tokio::test]
1120 async fn test_verify_tree_integrity_corrupted_hash_mismatch() {
1121 let store = make_store();
1122 let config = BuilderConfig::new(store.clone())
1123 .with_chunk_size(100)
1124 .public();
1125 let builder = TreeBuilder::new(config);
1126
1127 let data = vec![0u8; 350];
1128 let (cid, _size) = builder.put(&data).await.unwrap();
1129
1130 let keys = store.keys();
1132 let target = keys
1133 .iter()
1134 .find(|k| **k != cid.hash)
1135 .copied()
1136 .expect("expected at least one child chunk");
1137
1138 let mut corrupted = store.get(&target).await.unwrap().unwrap();
1139 corrupted[0] ^= 0xff;
1140 store.delete(&target).await.unwrap();
1141 store.put(target, corrupted).await.unwrap();
1142
1143 let legacy = verify_tree(store.clone(), &cid.hash).await.unwrap();
1145 assert!(legacy.valid);
1146
1147 let strict = verify_tree_integrity(store, &cid.hash).await.unwrap();
1148 assert!(!strict.valid);
1149 assert!(strict.missing.is_empty());
1150 assert!(!strict.corrupted.is_empty());
1151 assert!(strict.corrupted.contains(&target));
1152 }
1153
1154 #[tokio::test]
1155 async fn test_read_file_range_small_blob() {
1156 let store = make_store();
1157 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()).public());
1158 let reader = TreeReader::new(store);
1159
1160 let data = b"Hello, World!";
1161 let hash = builder.put_blob(data).await.unwrap();
1162
1163 let result = reader.read_file_range(&hash, 7, Some(12)).await.unwrap();
1165 assert_eq!(result, Some(b"World".to_vec()));
1166
1167 let result = reader.read_file_range(&hash, 0, Some(5)).await.unwrap();
1169 assert_eq!(result, Some(b"Hello".to_vec()));
1170
1171 let result = reader.read_file_range(&hash, 7, None).await.unwrap();
1173 assert_eq!(result, Some(b"World!".to_vec()));
1174 }
1175
1176 #[tokio::test]
1177 async fn test_read_file_range_chunked() {
1178 let store = make_store();
1179 let config = BuilderConfig::new(store.clone())
1181 .with_chunk_size(100)
1182 .public();
1183 let builder = TreeBuilder::new(config);
1184 let reader = TreeReader::new(store);
1185
1186 let mut data = vec![0u8; 350];
1188 for (i, byte) in data.iter_mut().enumerate() {
1189 *byte = (i % 256) as u8;
1190 }
1191
1192 let (cid, _size) = builder.put(&data).await.unwrap();
1193
1194 let result = reader
1196 .read_file_range(&cid.hash, 50, Some(150))
1197 .await
1198 .unwrap()
1199 .unwrap();
1200 assert_eq!(result.len(), 100);
1201 assert_eq!(result, data[50..150].to_vec());
1202
1203 let result = reader
1205 .read_file_range(&cid.hash, 200, Some(300))
1206 .await
1207 .unwrap()
1208 .unwrap();
1209 assert_eq!(result.len(), 100);
1210 assert_eq!(result, data[200..300].to_vec());
1211
1212 let result = reader
1214 .read_file_range(&cid.hash, 300, None)
1215 .await
1216 .unwrap()
1217 .unwrap();
1218 assert_eq!(result.len(), 50);
1219 assert_eq!(result, data[300..].to_vec());
1220 }
1221
1222 #[tokio::test]
1223 async fn test_chunked_raw_leaf_can_look_like_invalid_tree_node() {
1224 let store = make_store();
1225 let mut data = invalid_tree_shape_blob();
1226 let chunk_size = data.len();
1227 data.extend_from_slice(b"tail");
1228
1229 let config = BuilderConfig::new(store.clone())
1230 .with_chunk_size(chunk_size)
1231 .public();
1232 let builder = TreeBuilder::new(config);
1233 let reader = TreeReader::new(store);
1234
1235 let (cid, _size) = builder.put(&data).await.unwrap();
1236
1237 let full = reader.read_file(&cid.hash).await.unwrap().unwrap();
1238 assert_eq!(full, data);
1239
1240 let range = reader
1241 .read_file_range(&cid.hash, 1, Some((data.len() - 1) as u64))
1242 .await
1243 .unwrap()
1244 .unwrap();
1245 assert_eq!(range, data[1..data.len() - 1].to_vec());
1246
1247 let chunks = reader.read_file_chunks(&cid.hash).await.unwrap();
1248 assert_eq!(chunks.concat(), data);
1249 }
1250
1251 #[tokio::test]
1252 async fn test_read_file_range_entire_file() {
1253 let store = make_store();
1254 let config = BuilderConfig::new(store.clone())
1255 .with_chunk_size(100)
1256 .public();
1257 let builder = TreeBuilder::new(config);
1258 let reader = TreeReader::new(store);
1259
1260 let mut data = vec![0u8; 350];
1261 for (i, byte) in data.iter_mut().enumerate() {
1262 *byte = (i % 256) as u8;
1263 }
1264
1265 let (cid, _size) = builder.put(&data).await.unwrap();
1266
1267 let result = reader
1269 .read_file_range(&cid.hash, 0, None)
1270 .await
1271 .unwrap()
1272 .unwrap();
1273 assert_eq!(result, data);
1274 }
1275
1276 #[tokio::test]
1277 async fn test_read_file_range_out_of_bounds() {
1278 let store = make_store();
1279 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()).public());
1280 let reader = TreeReader::new(store);
1281
1282 let data = b"Short";
1283 let hash = builder.put_blob(data).await.unwrap();
1284
1285 let result = reader.read_file_range(&hash, 100, Some(200)).await.unwrap();
1287 assert_eq!(result, Some(vec![]));
1288
1289 let result = reader.read_file_range(&hash, 0, Some(100)).await.unwrap();
1291 assert_eq!(result, Some(b"Short".to_vec()));
1292 }
1293
1294 #[tokio::test]
1295 async fn test_read_file_range_single_byte() {
1296 let store = make_store();
1297 let config = BuilderConfig::new(store.clone())
1298 .with_chunk_size(100)
1299 .public();
1300 let builder = TreeBuilder::new(config);
1301 let reader = TreeReader::new(store);
1302
1303 let mut data = vec![0u8; 350];
1304 for (i, byte) in data.iter_mut().enumerate() {
1305 *byte = (i % 256) as u8;
1306 }
1307
1308 let (cid, _size) = builder.put(&data).await.unwrap();
1309
1310 let result = reader
1312 .read_file_range(&cid.hash, 100, Some(101))
1313 .await
1314 .unwrap()
1315 .unwrap();
1316 assert_eq!(result.len(), 1);
1317 assert_eq!(result[0], 100);
1318 }
1319}