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