1use std::collections::HashMap;
9use std::sync::Arc;
10
11use crate::codec::encode_and_hash;
12use crate::hash::sha256;
13use crate::store::Store;
14use crate::types::{Cid, DirEntry, Hash, Link, LinkType, TreeNode};
15
16use crate::crypto::{encrypt_chk, EncryptionKey};
17
18pub const DEFAULT_CHUNK_SIZE: usize = 2 * 1024 * 1024;
20
21pub const BEP52_CHUNK_SIZE: usize = 16 * 1024;
23
24pub const DEFAULT_MAX_LINKS: usize = 174;
26
27#[derive(Clone)]
28struct DirectoryFanoutSpan {
29 link: Link,
30 count: usize,
31 first: String,
32 last: String,
33}
34
35fn directory_fanout_meta(
36 count: usize,
37 first: &str,
38 last: &str,
39) -> HashMap<String, serde_json::Value> {
40 let mut meta = HashMap::new();
41 meta.insert("count".to_string(), serde_json::json!(count as u64));
42 meta.insert("first".to_string(), serde_json::json!(first));
43 meta.insert("last".to_string(), serde_json::json!(last));
44 meta
45}
46
47#[derive(Clone)]
49pub struct BuilderConfig<S: Store> {
50 pub store: Arc<S>,
51 pub chunk_size: usize,
52 pub max_links: usize,
53 pub encrypted: bool,
55}
56
57impl<S: Store> BuilderConfig<S> {
58 pub fn new(store: Arc<S>) -> Self {
59 Self {
60 store,
61 chunk_size: DEFAULT_CHUNK_SIZE,
62 max_links: DEFAULT_MAX_LINKS,
63 encrypted: true,
64 }
65 }
66
67 pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
68 self.chunk_size = chunk_size;
69 self
70 }
71
72 pub fn with_max_links(mut self, max_links: usize) -> Self {
73 self.max_links = max_links;
74 self
75 }
76
77 pub fn public(mut self) -> Self {
79 self.encrypted = false;
80 self
81 }
82
83 pub fn encrypted(mut self) -> Self {
85 self.encrypted = true;
86 self
87 }
88}
89
90pub struct TreeBuilder<S: Store> {
92 store: Arc<S>,
93 chunk_size: usize,
94 max_links: usize,
95 encrypted: bool,
96}
97
98impl<S: Store> TreeBuilder<S> {
99 pub fn new(config: BuilderConfig<S>) -> Self {
100 Self {
101 store: config.store,
102 chunk_size: config.chunk_size,
103 max_links: config.max_links,
104 encrypted: config.encrypted,
105 }
106 }
107
108 pub fn is_encrypted(&self) -> bool {
110 self.encrypted
111 }
112
113 pub async fn put_blob(&self, data: &[u8]) -> Result<Hash, BuilderError> {
116 let hash = sha256(data);
117 self.store
118 .put(hash, data.to_vec())
119 .await
120 .map_err(|e| BuilderError::Store(e.to_string()))?;
121 Ok(hash)
122 }
123
124 async fn put_chunk_internal(
127 &self,
128 data: &[u8],
129 ) -> Result<(Hash, Option<EncryptionKey>), BuilderError> {
130 if self.encrypted {
131 let (encrypted, key) =
132 encrypt_chk(data).map_err(|e| BuilderError::Encryption(e.to_string()))?;
133 let hash = sha256(&encrypted);
134 self.store
135 .put(hash, encrypted)
136 .await
137 .map_err(|e| BuilderError::Store(e.to_string()))?;
138 Ok((hash, Some(key)))
139 } else {
140 let hash = self.put_blob(data).await?;
141 Ok((hash, None))
142 }
143 }
144
145 pub async fn put(&self, data: &[u8]) -> Result<(Cid, u64), BuilderError> {
151 let size = data.len() as u64;
152
153 if data.len() <= self.chunk_size {
155 let (hash, key) = self.put_chunk_internal(data).await?;
156 return Ok((Cid { hash, key }, size));
157 }
158
159 let mut links: Vec<Link> = Vec::new();
161 let mut offset = 0;
162
163 while offset < data.len() {
164 let end = (offset + self.chunk_size).min(data.len());
165 let chunk = &data[offset..end];
166 let chunk_size = chunk.len() as u64;
167 let (hash, key) = self.put_chunk_internal(chunk).await?;
168 links.push(Link {
169 hash,
170 name: None,
171 size: chunk_size,
172 key,
173 link_type: LinkType::Blob, meta: None,
175 });
176 offset = end;
177 }
178
179 let (root_hash, root_key, _) = self.build_tree_internal(links, Some(size)).await?;
181
182 Ok((
183 Cid {
184 hash: root_hash,
185 key: root_key,
186 },
187 size,
188 ))
189 }
190
191 async fn build_tree_internal(
194 &self,
195 links: Vec<Link>,
196 total_size: Option<u64>,
197 ) -> Result<(Hash, Option<[u8; 32]>, LinkType), BuilderError> {
198 if links.len() == 1 {
200 if let Some(ts) = total_size {
201 if links[0].size == ts {
202 return Ok((links[0].hash, links[0].key, links[0].link_type));
203 }
204 }
205 }
206
207 if links.len() <= self.max_links {
208 let node = TreeNode {
209 node_type: LinkType::File,
210 links,
211 };
212 let (data, _) = encode_and_hash(&node)?;
213
214 if self.encrypted {
215 let (encrypted, key) =
216 encrypt_chk(&data).map_err(|e| BuilderError::Encryption(e.to_string()))?;
217 let hash = sha256(&encrypted);
218 self.store
219 .put(hash, encrypted)
220 .await
221 .map_err(|e| BuilderError::Store(e.to_string()))?;
222 return Ok((hash, Some(key), LinkType::File));
223 }
224
225 let hash = sha256(&data);
227 self.store
228 .put(hash, data)
229 .await
230 .map_err(|e| BuilderError::Store(e.to_string()))?;
231 return Ok((hash, None, LinkType::File));
232 }
233
234 let mut sub_links = Vec::new();
236 for batch in links.chunks(self.max_links) {
237 let batch_size: u64 = batch.iter().map(|l| l.size).sum();
238 let (hash, key, link_type) =
239 Box::pin(self.build_tree_internal(batch.to_vec(), Some(batch_size))).await?;
240 sub_links.push(Link {
241 hash,
242 name: None,
243 size: batch_size,
244 key,
245 link_type,
246 meta: None,
247 });
248 }
249
250 Box::pin(self.build_tree_internal(sub_links, total_size)).await
251 }
252
253 #[allow(dead_code)]
256 async fn build_tree(
257 &self,
258 links: Vec<Link>,
259 total_size: Option<u64>,
260 ) -> Result<Hash, BuilderError> {
261 if links.len() == 1 {
263 if let Some(ts) = total_size {
264 if links[0].size == ts {
265 return Ok(links[0].hash);
266 }
267 }
268 }
269
270 if links.len() <= self.max_links {
272 let node = TreeNode {
273 node_type: LinkType::File,
274 links,
275 };
276 let (data, hash) = encode_and_hash(&node)?;
277 self.store
278 .put(hash, data)
279 .await
280 .map_err(|e| BuilderError::Store(e.to_string()))?;
281 return Ok(hash);
282 }
283
284 let mut sub_trees: Vec<Link> = Vec::new();
286
287 for batch in links.chunks(self.max_links) {
288 let batch_size: u64 = batch.iter().map(|l| l.size).sum();
289
290 let node = TreeNode {
291 node_type: LinkType::File,
292 links: batch.to_vec(),
293 };
294 let (data, hash) = encode_and_hash(&node)?;
295 self.store
296 .put(hash, data)
297 .await
298 .map_err(|e| BuilderError::Store(e.to_string()))?;
299
300 sub_trees.push(Link {
301 hash,
302 name: None,
303 size: batch_size,
304 key: None,
305 link_type: LinkType::File, meta: None,
307 });
308 }
309
310 Box::pin(self.build_tree(sub_trees, total_size)).await
312 }
313
314 pub async fn put_directory(&self, entries: Vec<DirEntry>) -> Result<Hash, BuilderError> {
317 let mut sorted = entries;
319 sorted.sort_by(|a, b| a.name.cmp(&b.name));
320
321 let links: Vec<Link> = sorted
322 .into_iter()
323 .map(|e| Link {
324 hash: e.hash,
325 name: Some(e.name),
326 size: e.size,
327 key: e.key,
328 link_type: e.link_type,
329 meta: e.meta,
330 })
331 .collect();
332
333 if links.len() <= self.max_links {
335 let node = TreeNode {
336 node_type: LinkType::Dir,
337 links,
338 };
339 let (data, hash) = encode_and_hash(&node)?;
340 self.store
341 .put(hash, data)
342 .await
343 .map_err(|e| BuilderError::Store(e.to_string()))?;
344 return Ok(hash);
345 }
346
347 self.build_directory_by_chunks(links).await
348 }
349
350 async fn build_directory_by_chunks(&self, links: Vec<Link>) -> Result<Hash, BuilderError> {
352 let spans = links
353 .into_iter()
354 .map(|link| {
355 let name = link
356 .name
357 .clone()
358 .unwrap_or_else(|| crate::types::to_hex(&link.hash));
359 DirectoryFanoutSpan {
360 link,
361 count: 1,
362 first: name.clone(),
363 last: name,
364 }
365 })
366 .collect();
367 self.build_directory_fanout_level(spans, LinkType::Dir)
368 .await
369 }
370
371 async fn build_directory_fanout_level(
372 &self,
373 spans: Vec<DirectoryFanoutSpan>,
374 child_node_type: LinkType,
375 ) -> Result<Hash, BuilderError> {
376 let mut sub_trees: Vec<DirectoryFanoutSpan> = Vec::new();
377
378 for batch in spans.chunks(self.max_links) {
379 let Some(first_span) = batch.first() else {
380 continue;
381 };
382 let last_span = batch.last().expect("non-empty fanout batch");
383 let count: usize = batch.iter().map(|span| span.count).sum();
384 let batch_size: u64 = batch.iter().map(|span| span.link.size).sum();
385
386 let node = TreeNode {
387 node_type: child_node_type,
388 links: batch.iter().map(|span| span.link.clone()).collect(),
389 };
390 let (data, hash) = encode_and_hash(&node)?;
391 self.store
392 .put(hash, data)
393 .await
394 .map_err(|e| BuilderError::Store(e.to_string()))?;
395
396 sub_trees.push(DirectoryFanoutSpan {
397 link: Link {
398 hash,
399 name: None,
400 size: batch_size,
401 key: None,
402 link_type: child_node_type,
403 meta: Some(directory_fanout_meta(
404 count,
405 &first_span.first,
406 &last_span.last,
407 )),
408 },
409 count,
410 first: first_span.first.clone(),
411 last: last_span.last.clone(),
412 });
413 }
414
415 if sub_trees.len() <= self.max_links {
416 let node = TreeNode {
417 node_type: LinkType::Fanout,
418 links: sub_trees.into_iter().map(|span| span.link).collect(),
419 };
420 let (data, hash) = encode_and_hash(&node)?;
421 self.store
422 .put(hash, data)
423 .await
424 .map_err(|e| BuilderError::Store(e.to_string()))?;
425 return Ok(hash);
426 }
427
428 Box::pin(self.build_directory_fanout_level(sub_trees, LinkType::Fanout)).await
430 }
431
432 pub async fn put_tree_node(&self, links: Vec<Link>) -> Result<Hash, BuilderError> {
434 let node = TreeNode {
435 node_type: LinkType::Dir,
436 links,
437 };
438
439 let (data, hash) = encode_and_hash(&node)?;
440 self.store
441 .put(hash, data)
442 .await
443 .map_err(|e| BuilderError::Store(e.to_string()))?;
444 Ok(hash)
445 }
446}
447
448pub struct StreamBuilder<S: Store> {
450 store: Arc<S>,
451 chunk_size: usize,
452 max_links: usize,
453
454 buffer: Vec<u8>,
456
457 chunks: Vec<Link>,
459 total_size: u64,
460}
461
462impl<S: Store> StreamBuilder<S> {
463 pub fn new(config: BuilderConfig<S>) -> Self {
464 Self {
465 store: config.store,
466 chunk_size: config.chunk_size,
467 max_links: config.max_links,
468 buffer: Vec::with_capacity(config.chunk_size),
469 chunks: Vec::new(),
470 total_size: 0,
471 }
472 }
473
474 pub async fn append(&mut self, data: &[u8]) -> Result<(), BuilderError> {
476 let mut offset = 0;
477
478 while offset < data.len() {
479 let space = self.chunk_size - self.buffer.len();
480 let to_write = space.min(data.len() - offset);
481
482 self.buffer
483 .extend_from_slice(&data[offset..offset + to_write]);
484 offset += to_write;
485
486 if self.buffer.len() == self.chunk_size {
488 self.flush_chunk().await?;
489 }
490 }
491
492 self.total_size += data.len() as u64;
493 Ok(())
494 }
495
496 async fn flush_chunk(&mut self) -> Result<(), BuilderError> {
498 if self.buffer.is_empty() {
499 return Ok(());
500 }
501
502 let chunk = std::mem::take(&mut self.buffer);
503 let hash = sha256(&chunk);
504 self.store
505 .put(hash, chunk.clone())
506 .await
507 .map_err(|e| BuilderError::Store(e.to_string()))?;
508
509 self.chunks.push(Link {
510 hash,
511 name: None,
512 size: chunk.len() as u64,
513 key: None,
514 link_type: LinkType::Blob, meta: None,
516 });
517
518 self.buffer = Vec::with_capacity(self.chunk_size);
519 Ok(())
520 }
521
522 pub async fn current_root(&mut self) -> Result<Option<Hash>, BuilderError> {
525 if self.chunks.is_empty() && self.buffer.is_empty() {
526 return Ok(None);
527 }
528
529 let mut temp_chunks = self.chunks.clone();
531 if !self.buffer.is_empty() {
532 let chunk = self.buffer.clone();
533 let hash = sha256(&chunk);
534 self.store
535 .put(hash, chunk.clone())
536 .await
537 .map_err(|e| BuilderError::Store(e.to_string()))?;
538 temp_chunks.push(Link {
539 hash,
540 name: None,
541 size: chunk.len() as u64,
542 key: None,
543 link_type: LinkType::Blob, meta: None,
545 });
546 }
547
548 let hash = self
549 .build_tree_from_chunks(&temp_chunks, self.total_size)
550 .await?;
551 Ok(Some(hash))
552 }
553
554 pub async fn finalize(mut self) -> Result<(Hash, u64), BuilderError> {
556 self.flush_chunk().await?;
558
559 if self.chunks.is_empty() {
560 let empty_hash = sha256(&[]);
562 self.store
563 .put(empty_hash, vec![])
564 .await
565 .map_err(|e| BuilderError::Store(e.to_string()))?;
566 return Ok((empty_hash, 0));
567 }
568
569 let hash = self
570 .build_tree_from_chunks(&self.chunks, self.total_size)
571 .await?;
572 Ok((hash, self.total_size))
573 }
574
575 async fn build_tree_from_chunks(
577 &self,
578 chunks: &[Link],
579 total_size: u64,
580 ) -> Result<Hash, BuilderError> {
581 if chunks.len() == 1 {
582 return Ok(chunks[0].hash);
583 }
584
585 if chunks.len() <= self.max_links {
586 let node = TreeNode {
587 node_type: LinkType::File,
588 links: chunks.to_vec(),
589 };
590 let (data, hash) = encode_and_hash(&node)?;
591 self.store
592 .put(hash, data)
593 .await
594 .map_err(|e| BuilderError::Store(e.to_string()))?;
595 return Ok(hash);
596 }
597
598 let mut sub_trees: Vec<Link> = Vec::new();
600 for batch in chunks.chunks(self.max_links) {
601 let batch_size: u64 = batch.iter().map(|l| l.size).sum();
602
603 let node = TreeNode {
604 node_type: LinkType::File,
605 links: batch.to_vec(),
606 };
607 let (data, hash) = encode_and_hash(&node)?;
608 self.store
609 .put(hash, data)
610 .await
611 .map_err(|e| BuilderError::Store(e.to_string()))?;
612
613 sub_trees.push(Link {
614 hash,
615 name: None,
616 size: batch_size,
617 key: None,
618 link_type: LinkType::File, meta: None,
620 });
621 }
622
623 Box::pin(self.build_tree_from_chunks(&sub_trees, total_size)).await
624 }
625
626 pub fn stats(&self) -> StreamStats {
628 StreamStats {
629 chunks: self.chunks.len(),
630 buffered: self.buffer.len(),
631 total_size: self.total_size,
632 }
633 }
634}
635
636#[derive(Debug, Clone, PartialEq)]
637pub struct StreamStats {
638 pub chunks: usize,
639 pub buffered: usize,
640 pub total_size: u64,
641}
642
643#[derive(Debug, thiserror::Error)]
645pub enum BuilderError {
646 #[error("Store error: {0}")]
647 Store(String),
648 #[error("Codec error: {0}")]
649 Codec(#[from] crate::codec::CodecError),
650 #[error("Encryption error: {0}")]
651 Encryption(String),
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657 use crate::store::MemoryStore;
658 use crate::types::to_hex;
659 use std::collections::HashMap;
660
661 fn make_store() -> Arc<MemoryStore> {
662 Arc::new(MemoryStore::new())
663 }
664
665 #[tokio::test]
666 async fn test_put_blob() {
667 let store = make_store();
668 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
669
670 let data = vec![1u8, 2, 3, 4, 5];
671 let hash = builder.put_blob(&data).await.unwrap();
672
673 assert_eq!(hash.len(), 32);
674 assert!(store.has(&hash).await.unwrap());
675
676 let retrieved = store.get(&hash).await.unwrap();
677 assert_eq!(retrieved, Some(data));
678 }
679
680 #[tokio::test]
681 async fn test_put_blob_correct_hash() {
682 let store = make_store();
683 let builder = TreeBuilder::new(BuilderConfig::new(store));
684
685 let data = vec![1u8, 2, 3];
686 let hash = builder.put_blob(&data).await.unwrap();
687 let expected_hash = sha256(&data);
688
689 assert_eq!(to_hex(&hash), to_hex(&expected_hash));
690 }
691
692 #[tokio::test]
693 async fn test_put_small() {
694 let store = make_store();
695 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()).public());
697
698 let data = vec![1u8, 2, 3, 4, 5];
699 let (cid, size) = builder.put(&data).await.unwrap();
700
701 assert_eq!(size, 5);
702 assert!(cid.key.is_none()); let retrieved = store.get(&cid.hash).await.unwrap();
704 assert_eq!(retrieved, Some(data));
705 }
706
707 #[tokio::test]
708 async fn test_put_chunked() {
709 let store = make_store();
710 let config = BuilderConfig::new(store.clone())
711 .with_chunk_size(1024)
712 .public();
713 let builder = TreeBuilder::new(config);
714
715 let mut data = vec![0u8; 1024 * 2 + 100];
716 for i in 0..data.len() {
717 data[i] = (i % 256) as u8;
718 }
719
720 let (_cid, size) = builder.put(&data).await.unwrap();
721 assert_eq!(size, data.len() as u64);
722
723 assert!(store.size() > 1);
725 }
726
727 #[tokio::test]
728 async fn test_put_directory() {
729 let store = make_store();
730 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
731
732 let file1 = vec![1u8, 2, 3];
733 let file2 = vec![4u8, 5, 6, 7];
734
735 let hash1 = builder.put_blob(&file1).await.unwrap();
736 let hash2 = builder.put_blob(&file2).await.unwrap();
737
738 let dir_hash = builder
739 .put_directory(vec![
740 DirEntry::new("a.txt", hash1).with_size(file1.len() as u64),
741 DirEntry::new("b.txt", hash2).with_size(file2.len() as u64),
742 ])
743 .await
744 .unwrap();
745
746 assert!(store.has(&dir_hash).await.unwrap());
747 }
748
749 #[tokio::test]
750 async fn test_put_directory_sorted() {
751 let store = make_store();
752 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
753
754 let hash = builder.put_blob(&[1u8]).await.unwrap();
755
756 let dir_hash = builder
757 .put_directory(vec![
758 DirEntry::new("zebra", hash),
759 DirEntry::new("apple", hash),
760 DirEntry::new("mango", hash),
761 ])
762 .await
763 .unwrap();
764
765 let data = store.get(&dir_hash).await.unwrap().unwrap();
766 let node = crate::codec::decode_tree_node(&data).unwrap();
767
768 let names: Vec<_> = node.links.iter().filter_map(|l| l.name.clone()).collect();
769 assert_eq!(names, vec!["apple", "mango", "zebra"]);
770 }
771
772 #[tokio::test]
773 async fn test_put_tree_node_with_link_meta() {
774 let store = make_store();
775 let builder = TreeBuilder::new(BuilderConfig::new(store.clone()));
776
777 let hash = builder.put_blob(&[1u8]).await.unwrap();
778
779 let mut meta = HashMap::new();
780 meta.insert("version".to_string(), serde_json::json!(2));
781 meta.insert("created".to_string(), serde_json::json!("2024-01-01"));
782
783 let node_hash = builder
784 .put_tree_node(vec![Link {
785 hash,
786 name: Some("test".to_string()),
787 size: 1,
788 key: None,
789 link_type: LinkType::Blob,
790 meta: Some(meta.clone()),
791 }])
792 .await
793 .unwrap();
794
795 let data = store.get(&node_hash).await.unwrap().unwrap();
796 let node = crate::codec::decode_tree_node(&data).unwrap();
797
798 assert!(node.links[0].meta.is_some());
799 let m = node.links[0].meta.as_ref().unwrap();
800 assert_eq!(m.get("version"), Some(&serde_json::json!(2)));
801 }
802
803 #[tokio::test]
804 async fn test_stream_builder() {
805 let store = make_store();
806 let config = BuilderConfig::new(store.clone()).with_chunk_size(100);
807 let mut stream = StreamBuilder::new(config);
808
809 stream.append(&[1u8, 2, 3]).await.unwrap();
810 stream.append(&[4u8, 5]).await.unwrap();
811 stream.append(&[6u8, 7, 8, 9]).await.unwrap();
812
813 let (hash, size) = stream.finalize().await.unwrap();
814
815 assert_eq!(size, 9);
816 assert!(store.has(&hash).await.unwrap());
817 }
818
819 #[tokio::test]
820 async fn test_stream_stats() {
821 let store = make_store();
822 let config = BuilderConfig::new(store).with_chunk_size(100);
823 let mut stream = StreamBuilder::new(config);
824
825 assert_eq!(stream.stats().chunks, 0);
826 assert_eq!(stream.stats().buffered, 0);
827 assert_eq!(stream.stats().total_size, 0);
828
829 stream.append(&[0u8; 50]).await.unwrap();
830 assert_eq!(stream.stats().buffered, 50);
831 assert_eq!(stream.stats().total_size, 50);
832
833 stream.append(&[0u8; 60]).await.unwrap(); assert_eq!(stream.stats().chunks, 1);
835 assert_eq!(stream.stats().buffered, 10);
836 assert_eq!(stream.stats().total_size, 110);
837 }
838
839 #[tokio::test]
840 async fn test_stream_current_root() {
841 let store = make_store();
842 let config = BuilderConfig::new(store).with_chunk_size(100);
843 let mut stream = StreamBuilder::new(config);
844
845 stream.append(&[1u8, 2, 3]).await.unwrap();
846 let root1 = stream.current_root().await.unwrap();
847
848 stream.append(&[4u8, 5, 6]).await.unwrap();
849 let root2 = stream.current_root().await.unwrap();
850
851 assert_ne!(to_hex(&root1.unwrap()), to_hex(&root2.unwrap()));
853 }
854
855 #[tokio::test]
856 async fn test_stream_empty() {
857 let store = make_store();
858 let config = BuilderConfig::new(store.clone());
859 let stream = StreamBuilder::new(config);
860
861 let (hash, size) = stream.finalize().await.unwrap();
862 assert_eq!(size, 0);
863 assert!(store.has(&hash).await.unwrap());
864 }
865
866 #[tokio::test]
867 async fn test_unified_put_public() {
868 let store = make_store();
869 let config = BuilderConfig::new(store.clone()).public();
871 let builder = TreeBuilder::new(config);
872
873 let data = b"Hello, World!";
874 let (cid, size) = builder.put(data).await.unwrap();
875
876 assert_eq!(size, data.len() as u64);
877 assert!(cid.key.is_none()); assert!(store.has(&cid.hash).await.unwrap());
879 }
880
881 #[tokio::test]
882 async fn test_unified_put_encrypted() {
883 use crate::reader::TreeReader;
884
885 let store = make_store();
886 let config = BuilderConfig::new(store.clone());
888 let builder = TreeBuilder::new(config);
889
890 let data = b"Hello, encrypted world!";
891 let (cid, size) = builder.put(data).await.unwrap();
892
893 assert_eq!(size, data.len() as u64);
894 assert!(cid.key.is_some()); let reader = TreeReader::new(store);
898 let retrieved = reader.get(&cid).await.unwrap().unwrap();
899 assert_eq!(retrieved, data);
900 }
901
902 #[tokio::test]
903 async fn test_unified_put_encrypted_chunked() {
904 use crate::reader::TreeReader;
905
906 let store = make_store();
907 let config = BuilderConfig::new(store.clone()).with_chunk_size(100);
908 let builder = TreeBuilder::new(config);
909
910 let data: Vec<u8> = (0..500).map(|i| (i % 256) as u8).collect();
912 let (cid, size) = builder.put(&data).await.unwrap();
913
914 assert_eq!(size, data.len() as u64);
915 assert!(cid.key.is_some());
916
917 let reader = TreeReader::new(store);
919 let retrieved = reader.get(&cid).await.unwrap().unwrap();
920 assert_eq!(retrieved, data);
921 }
922
923 #[tokio::test]
924 async fn test_cid_deterministic() {
925 let store = make_store();
926 let config = BuilderConfig::new(store.clone());
927 let builder = TreeBuilder::new(config);
928
929 let data = b"Same content produces same CID";
930
931 let (cid1, _) = builder.put(data).await.unwrap();
932 let (cid2, _) = builder.put(data).await.unwrap();
933
934 assert_eq!(cid1.hash, cid2.hash);
936 assert_eq!(cid1.key, cid2.key);
937 assert_eq!(cid1.to_string(), cid2.to_string());
938 }
939}