1use std::collections::HashMap;
6use std::sync::Arc;
7
8use crate::codec::{decode_tree_node, is_directory_node, is_tree_node, try_decode_tree_node};
9use crate::store::Store;
10use crate::types::{to_hex, Cid, Hash, Link, LinkType, TreeNode};
11
12use crate::crypto::{decrypt_chk, EncryptionKey};
13
14#[derive(Debug, Clone)]
16pub struct TreeEntry {
17 pub name: String,
18 pub hash: Hash,
19 pub size: u64,
20 pub link_type: LinkType,
22 pub key: Option<[u8; 32]>,
24 pub meta: Option<HashMap<String, serde_json::Value>>,
26}
27
28#[derive(Debug, Clone)]
30pub struct WalkEntry {
31 pub path: String,
32 pub hash: Hash,
33 pub link_type: LinkType,
35 pub size: u64,
36 pub key: Option<[u8; 32]>,
38}
39
40pub struct TreeReader<S: Store> {
42 store: Arc<S>,
43}
44
45impl<S: Store> TreeReader<S> {
46 pub fn new(store: Arc<S>) -> Self {
47 Self { store }
48 }
49
50 pub async fn get_blob(&self, hash: &Hash) -> Result<Option<Vec<u8>>, ReaderError> {
52 self.store
53 .get(hash)
54 .await
55 .map_err(|e| ReaderError::Store(e.to_string()))
56 }
57
58 pub async fn get_tree_node(&self, hash: &Hash) -> Result<Option<TreeNode>, ReaderError> {
60 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
61 Some(d) => d,
62 None => return Ok(None),
63 };
64
65 if !is_tree_node(&data) {
66 return Ok(None); }
68
69 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
70 Ok(Some(node))
71 }
72
73 pub async fn is_tree(&self, hash: &Hash) -> Result<bool, ReaderError> {
75 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
76 Some(d) => d,
77 None => return Ok(false),
78 };
79 Ok(is_tree_node(&data))
80 }
81
82 pub async fn is_directory(&self, hash: &Hash) -> Result<bool, ReaderError> {
85 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
86 Some(d) => d,
87 None => return Ok(false),
88 };
89 Ok(is_directory_node(&data))
90 }
91
92 pub async fn get(&self, cid: &Cid) -> Result<Option<Vec<u8>>, ReaderError> {
97 if let Some(key) = cid.key {
98 self.get_encrypted(&cid.hash, &key).await
99 } else {
100 self.read_file(&cid.hash).await
101 }
102 }
103
104 async fn get_encrypted(
106 &self,
107 hash: &Hash,
108 key: &EncryptionKey,
109 ) -> Result<Option<Vec<u8>>, ReaderError> {
110 let encrypted_data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
111 Some(d) => d,
112 None => return Ok(None),
113 };
114
115 let decrypted = decrypt_chk(&encrypted_data, key)
117 .map_err(|e| ReaderError::Decryption(e.to_string()))?;
118
119 if is_tree_node(&decrypted) {
121 let node = decode_tree_node(&decrypted)?;
122 let assembled = self.assemble_encrypted_chunks(&node).await?;
123 return Ok(Some(assembled));
124 }
125
126 Ok(Some(decrypted))
128 }
129
130 async fn assemble_encrypted_chunks(&self, node: &TreeNode) -> Result<Vec<u8>, ReaderError> {
132 let mut parts: Vec<Vec<u8>> = Vec::new();
133
134 for link in &node.links {
135 let chunk_key = link.key.ok_or(ReaderError::MissingKey)?;
136
137 let encrypted_child = self
138 .store
139 .get(&link.hash)
140 .await
141 .map_err(|e| ReaderError::Store(e.to_string()))?
142 .ok_or_else(|| ReaderError::MissingChunk(to_hex(&link.hash)))?;
143
144 let decrypted = decrypt_chk(&encrypted_child, &chunk_key)
145 .map_err(|e| ReaderError::Decryption(e.to_string()))?;
146
147 if is_tree_node(&decrypted) {
148 let child_node = decode_tree_node(&decrypted)?;
150 let child_data = Box::pin(self.assemble_encrypted_chunks(&child_node)).await?;
151 parts.push(child_data);
152 } else {
153 parts.push(decrypted);
155 }
156 }
157
158 let total_len: usize = parts.iter().map(|p| p.len()).sum();
159 let mut result = Vec::with_capacity(total_len);
160 for part in parts {
161 result.extend_from_slice(&part);
162 }
163
164 Ok(result)
165 }
166
167 pub async fn read_file(&self, hash: &Hash) -> Result<Option<Vec<u8>>, ReaderError> {
170 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
171 Some(d) => d,
172 None => return Ok(None),
173 };
174
175 if !is_tree_node(&data) {
177 return Ok(Some(data)); }
179
180 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
182 let assembled = self.assemble_chunks(&node).await?;
183 Ok(Some(assembled))
184 }
185
186 async fn assemble_chunks(&self, node: &TreeNode) -> Result<Vec<u8>, ReaderError> {
188 let mut parts: Vec<Vec<u8>> = Vec::new();
189
190 for link in &node.links {
191 let child_data = self
192 .store
193 .get(&link.hash)
194 .await
195 .map_err(|e| ReaderError::Store(e.to_string()))?
196 .ok_or_else(|| ReaderError::MissingChunk(to_hex(&link.hash)))?;
197
198 if is_tree_node(&child_data) {
199 let child_node = decode_tree_node(&child_data).map_err(ReaderError::Codec)?;
201 parts.push(Box::pin(self.assemble_chunks(&child_node)).await?);
202 } else {
203 parts.push(child_data);
205 }
206 }
207
208 let total_length: usize = parts.iter().map(|p| p.len()).sum();
210 let mut result = Vec::with_capacity(total_length);
211 for part in parts {
212 result.extend_from_slice(&part);
213 }
214
215 Ok(result)
216 }
217
218 pub async fn read_file_chunks(&self, hash: &Hash) -> Result<Vec<Vec<u8>>, ReaderError> {
220 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
221 Some(d) => d,
222 None => return Ok(vec![]),
223 };
224
225 if !is_tree_node(&data) {
226 return Ok(vec![data]);
227 }
228
229 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
230 self.collect_chunks(&node).await
231 }
232
233 async fn collect_chunks(&self, node: &TreeNode) -> Result<Vec<Vec<u8>>, ReaderError> {
235 let mut chunks = Vec::new();
236
237 for link in &node.links {
238 let child_data = self
239 .store
240 .get(&link.hash)
241 .await
242 .map_err(|e| ReaderError::Store(e.to_string()))?
243 .ok_or_else(|| ReaderError::MissingChunk(to_hex(&link.hash)))?;
244
245 if is_tree_node(&child_data) {
246 let child_node = decode_tree_node(&child_data).map_err(ReaderError::Codec)?;
247 chunks.extend(Box::pin(self.collect_chunks(&child_node)).await?);
248 } else {
249 chunks.push(child_data);
250 }
251 }
252
253 Ok(chunks)
254 }
255
256 pub async fn list_directory(&self, hash: &Hash) -> Result<Vec<TreeEntry>, ReaderError> {
258 let node = match self.get_tree_node(hash).await? {
259 Some(n) => n,
260 None => return Ok(vec![]),
261 };
262
263 let mut entries = Vec::new();
264
265 for link in &node.links {
266 if let Some(ref name) = link.name {
268 if name.starts_with("_chunk_") {
269 let sub_entries = Box::pin(self.list_directory(&link.hash)).await?;
271 entries.extend(sub_entries);
272 continue;
273 }
274
275 if name.starts_with('_') {
277 let sub_entries = Box::pin(self.list_directory(&link.hash)).await?;
278 entries.extend(sub_entries);
279 continue;
280 }
281 }
282
283 entries.push(TreeEntry {
284 name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
285 hash: link.hash,
286 size: link.size,
287 link_type: link.link_type,
288 key: link.key,
289 meta: link.meta.clone(),
290 });
291 }
292
293 Ok(entries)
294 }
295
296 pub async fn resolve_path(&self, root_hash: &Hash, path: &str) -> Result<Option<Hash>, ReaderError> {
299 let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
300
301 let mut current_hash = *root_hash;
302
303 for part in parts {
304 let node = match self.get_tree_node(¤t_hash).await? {
305 Some(n) => n,
306 None => return Ok(None),
307 };
308
309 if let Some(link) = self.find_link(&node, part) {
310 current_hash = link.hash;
311 } else {
312 match self.find_in_subtrees(&node, part).await? {
314 Some(hash) => current_hash = hash,
315 None => return Ok(None),
316 }
317 }
318 }
319
320 Ok(Some(current_hash))
321 }
322
323 fn find_link(&self, node: &TreeNode, name: &str) -> Option<Link> {
325 node.links
326 .iter()
327 .find(|l| l.name.as_deref() == Some(name))
328 .cloned()
329 }
330
331 async fn find_in_subtrees(&self, node: &TreeNode, name: &str) -> Result<Option<Hash>, ReaderError> {
333 for link in &node.links {
334 if !link.name.as_ref().map(|n| n.starts_with('_')).unwrap_or(false) {
336 continue;
337 }
338
339 let sub_node = match self.get_tree_node(&link.hash).await? {
340 Some(n) => n,
341 None => continue,
342 };
343
344 if let Some(found) = self.find_link(&sub_node, name) {
345 return Ok(Some(found.hash));
346 }
347
348 if let Some(deep_found) = Box::pin(self.find_in_subtrees(&sub_node, name)).await? {
350 return Ok(Some(deep_found));
351 }
352 }
353
354 Ok(None)
355 }
356
357 pub async fn get_size(&self, hash: &Hash) -> Result<u64, ReaderError> {
359 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
360 Some(d) => d,
361 None => return Ok(0),
362 };
363
364 if !is_tree_node(&data) {
365 return Ok(data.len() as u64);
366 }
367
368 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
369 let mut total = 0u64;
371 for link in &node.links {
372 total += link.size;
373 }
374 Ok(total)
375 }
376
377 pub async fn walk(&self, hash: &Hash, path: &str) -> Result<Vec<WalkEntry>, ReaderError> {
379 let mut entries = Vec::new();
380 self.walk_recursive(hash, path, &mut entries).await?;
381 Ok(entries)
382 }
383
384 async fn walk_recursive(
385 &self,
386 hash: &Hash,
387 path: &str,
388 entries: &mut Vec<WalkEntry>,
389 ) -> Result<(), ReaderError> {
390 let data = match self.store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
391 Some(d) => d,
392 None => return Ok(()),
393 };
394
395 let node = match try_decode_tree_node(&data) {
396 Some(n) => n,
397 None => {
398 entries.push(WalkEntry {
399 path: path.to_string(),
400 hash: *hash,
401 link_type: LinkType::Blob,
402 size: data.len() as u64,
403 key: None, });
405 return Ok(());
406 }
407 };
408
409 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
410 entries.push(WalkEntry {
411 path: path.to_string(),
412 hash: *hash,
413 link_type: node.node_type,
414 size: node_size,
415 key: None, });
417
418 for link in &node.links {
419 let child_path = match &link.name {
420 Some(name) => {
421 if name.starts_with("_chunk_") || name.starts_with('_') {
423 Box::pin(self.walk_recursive(&link.hash, path, entries)).await?;
424 continue;
425 }
426 if path.is_empty() {
427 name.clone()
428 } else {
429 format!("{}/{}", path, name)
430 }
431 }
432 None => path.to_string(),
433 };
434
435 Box::pin(self.walk_recursive(&link.hash, &child_path, entries)).await?;
436 }
437
438 Ok(())
439 }
440}
441
442pub async fn verify_tree<S: Store>(store: Arc<S>, root_hash: &Hash) -> Result<VerifyResult, ReaderError> {
445 let mut missing = Vec::new();
446 let mut visited = std::collections::HashSet::new();
447
448 verify_recursive(store, root_hash, &mut missing, &mut visited).await?;
449
450 Ok(VerifyResult {
451 valid: missing.is_empty(),
452 missing,
453 })
454}
455
456async fn verify_recursive<S: Store>(
457 store: Arc<S>,
458 hash: &Hash,
459 missing: &mut Vec<Hash>,
460 visited: &mut std::collections::HashSet<String>,
461) -> Result<(), ReaderError> {
462 let hex = to_hex(hash);
463 if visited.contains(&hex) {
464 return Ok(());
465 }
466 visited.insert(hex);
467
468 let data = match store.get(hash).await.map_err(|e| ReaderError::Store(e.to_string()))? {
469 Some(d) => d,
470 None => {
471 missing.push(*hash);
472 return Ok(());
473 }
474 };
475
476 if is_tree_node(&data) {
477 let node = decode_tree_node(&data).map_err(ReaderError::Codec)?;
478 for link in &node.links {
479 Box::pin(verify_recursive(store.clone(), &link.hash, missing, visited)).await?;
480 }
481 }
482
483 Ok(())
484}
485
486#[derive(Debug, Clone)]
488pub struct VerifyResult {
489 pub valid: bool,
490 pub missing: Vec<Hash>,
491}
492
493#[derive(Debug, thiserror::Error)]
495pub enum ReaderError {
496 #[error("Store error: {0}")]
497 Store(String),
498 #[error("Codec error: {0}")]
499 Codec(#[from] crate::codec::CodecError),
500 #[error("Missing chunk: {0}")]
501 MissingChunk(String),
502 #[error("Decryption error: {0}")]
503 Decryption(String),
504 #[error("Missing decryption key")]
505 MissingKey,
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511 use crate::builder::{BuilderConfig, TreeBuilder};
512 use crate::store::MemoryStore;
513 use crate::types::DirEntry;
514
515 fn make_store() -> Arc<MemoryStore> {
516 Arc::new(MemoryStore::new())
517 }
518
519 #[tokio::test]
520 async fn test_get_blob() {
521 let store = make_store();
522 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
523 let reader = TreeReader::new(store);
524
525 let data = vec![1u8, 2, 3, 4, 5];
526 let hash = builder.put_blob(&data).await.unwrap();
527
528 let result = reader.get_blob(&hash).await.unwrap();
529 assert_eq!(result, Some(data));
530 }
531
532 #[tokio::test]
533 async fn test_get_blob_missing() {
534 let store = make_store();
535 let reader = TreeReader::new(store);
536
537 let hash = [0u8; 32];
538 let result = reader.get_blob(&hash).await.unwrap();
539 assert!(result.is_none());
540 }
541
542 #[tokio::test]
543 async fn test_get_tree_node() {
544 let store = make_store();
545 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
546 let reader = TreeReader::new(store);
547
548 let file_hash = builder.put_blob(&[1u8]).await.unwrap();
549 let dir_hash = builder
550 .put_directory(vec![DirEntry::new("test.txt", file_hash).with_size(1)])
551 .await
552 .unwrap();
553
554 let node = reader.get_tree_node(&dir_hash).await.unwrap();
555 assert!(node.is_some());
556 assert_eq!(node.unwrap().links.len(), 1);
557 }
558
559 #[tokio::test]
560 async fn test_get_tree_node_returns_none_for_blob() {
561 let store = make_store();
562 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
563 let reader = TreeReader::new(store);
564
565 let hash = builder.put_blob(&[1u8, 2, 3]).await.unwrap();
566 let node = reader.get_tree_node(&hash).await.unwrap();
567 assert!(node.is_none());
568 }
569
570 #[tokio::test]
571 async fn test_is_tree() {
572 let store = make_store();
573 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
574 let reader = TreeReader::new(store);
575
576 let file_hash = builder.put_blob(&[1u8]).await.unwrap();
577 let dir_hash = builder
578 .put_directory(vec![DirEntry::new("test.txt", file_hash)])
579 .await
580 .unwrap();
581
582 assert!(reader.is_tree(&dir_hash).await.unwrap());
583 assert!(!reader.is_tree(&file_hash).await.unwrap());
584 }
585
586 #[tokio::test]
587 async fn test_read_file_small() {
588 let store = make_store();
589 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()).public());
591 let reader = TreeReader::new(store);
592
593 let data = vec![1u8, 2, 3, 4, 5];
594 let (cid, _size) = builder.put(&data).await.unwrap();
595
596 let result = reader.read_file(&cid.hash).await.unwrap();
597 assert_eq!(result, Some(data));
598 }
599
600 #[tokio::test]
601 async fn test_read_file_chunked() {
602 let store = make_store();
603 let config = BuilderConfig::new(store.clone()).with_chunk_size(100).public();
604 let builder = TreeBuilder::new(config);
605 let reader = TreeReader::new(store);
606
607 let mut data = vec![0u8; 350];
608 for i in 0..data.len() {
609 data[i] = (i % 256) as u8;
610 }
611
612 let (cid, _size) = builder.put(&data).await.unwrap();
613 let result = reader.read_file(&cid.hash).await.unwrap();
614
615 assert_eq!(result, Some(data));
616 }
617
618 #[tokio::test]
619 async fn test_list_directory() {
620 let store = make_store();
621 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
622 let reader = TreeReader::new(store);
623
624 let h1 = builder.put_blob(&[1u8]).await.unwrap();
625 let h2 = builder.put_blob(&[2u8]).await.unwrap();
626
627 let dir_hash = builder
628 .put_directory(
629 vec![
630 DirEntry::new("first.txt", h1).with_size(1),
631 DirEntry::new("second.txt", h2).with_size(1),
632 ],
633 )
634 .await
635 .unwrap();
636
637 let entries = reader.list_directory(&dir_hash).await.unwrap();
638
639 assert_eq!(entries.len(), 2);
640 assert!(entries.iter().any(|e| e.name == "first.txt"));
641 assert!(entries.iter().any(|e| e.name == "second.txt"));
642 }
643
644 #[tokio::test]
645 async fn test_resolve_path() {
646 let store = make_store();
647 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
648 let reader = TreeReader::new(store);
649
650 let file_data = vec![1u8, 2, 3];
651 let file_hash = builder.put_blob(&file_data).await.unwrap();
652
653 let dir_hash = builder
654 .put_directory(vec![DirEntry::new("test.txt", file_hash)])
655 .await
656 .unwrap();
657
658 let resolved = reader.resolve_path(&dir_hash, "test.txt").await.unwrap();
659 assert_eq!(resolved, Some(file_hash));
660 }
661
662 #[tokio::test]
663 async fn test_resolve_path_nested() {
664 let store = make_store();
665 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
666 let reader = TreeReader::new(store);
667
668 let file_hash = builder.put_blob(&[1u8]).await.unwrap();
669
670 let sub_sub_dir = builder
671 .put_directory(vec![DirEntry::new("deep.txt", file_hash)])
672 .await
673 .unwrap();
674
675 let sub_dir = builder
676 .put_directory(vec![DirEntry::new("level2", sub_sub_dir)])
677 .await
678 .unwrap();
679
680 let root_dir = builder
681 .put_directory(vec![DirEntry::new("level1", sub_dir)])
682 .await
683 .unwrap();
684
685 let resolved = reader
686 .resolve_path(&root_dir, "level1/level2/deep.txt")
687 .await
688 .unwrap();
689 assert_eq!(resolved, Some(file_hash));
690 }
691
692 #[tokio::test]
693 async fn test_get_size() {
694 let store = make_store();
695 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
696 let reader = TreeReader::new(store);
697
698 let data = vec![0u8; 123];
699 let hash = builder.put_blob(&data).await.unwrap();
700
701 assert_eq!(reader.get_size(&hash).await.unwrap(), 123);
702 }
703
704 #[tokio::test]
705 async fn test_walk() {
706 let store = make_store();
707 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
708 let reader = TreeReader::new(store);
709
710 let f1 = builder.put_blob(&[1u8]).await.unwrap();
711 let f2 = builder.put_blob(&[2u8, 3]).await.unwrap();
712
713 let sub_dir = builder
714 .put_directory(vec![DirEntry::new("nested.txt", f2).with_size(2)])
715 .await
716 .unwrap();
717
718 let root_dir = builder
719 .put_directory(
720 vec![
721 DirEntry::new("root.txt", f1).with_size(1),
722 DirEntry::new("sub", sub_dir),
723 ],
724 )
725 .await
726 .unwrap();
727
728 let entries = reader.walk(&root_dir, "").await.unwrap();
729 let paths: Vec<_> = entries.iter().map(|e| e.path.as_str()).collect();
730
731 assert!(paths.contains(&""));
732 assert!(paths.contains(&"root.txt"));
733 assert!(paths.contains(&"sub"));
734 assert!(paths.contains(&"sub/nested.txt"));
735 }
736
737 #[tokio::test]
738 async fn test_verify_tree_valid() {
739 let store = make_store();
740 let config = BuilderConfig::new(store.clone()).with_chunk_size(100).public();
741 let builder = TreeBuilder::new(config);
742
743 let data = vec![0u8; 350];
744 let (cid, _size) = builder.put(&data).await.unwrap();
745
746 let result = verify_tree(store, &cid.hash).await.unwrap();
747 assert!(result.valid);
748 assert!(result.missing.is_empty());
749 }
750
751 #[tokio::test]
752 async fn test_verify_tree_missing() {
753 let store = make_store();
754 let config = BuilderConfig::new(store.clone()).with_chunk_size(100).public();
755 let builder = TreeBuilder::new(config);
756
757 let data = vec![0u8; 350];
758 let (cid, _size) = builder.put(&data).await.unwrap();
759
760 let keys = store.keys();
762 if let Some(chunk_to_delete) = keys.iter().find(|k| **k != cid.hash) {
763 store.delete(chunk_to_delete).await.unwrap();
764 }
765
766 let result = verify_tree(store, &cid.hash).await.unwrap();
767 assert!(!result.valid);
768 assert!(!result.missing.is_empty());
769 }
770}