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