1use bytes::Bytes;
4use ipfrs_core::{Block, Cid, Result};
5use sha2::{Digest, Sha256};
6use std::collections::HashMap;
7
8#[inline]
12pub fn compute_cid(data: &[u8]) -> Cid {
13 let hash = Sha256::digest(data);
14
15 let mut multihash = Vec::with_capacity(34);
17 multihash.push(0x12); multihash.push(32); multihash.extend_from_slice(&hash);
20
21 Cid::try_from(multihash).unwrap_or_else(|_| {
24 let cid_bytes = format!("bafkreei{}", hex::encode(&hash[..16]));
26 Cid::try_from(cid_bytes.as_bytes().to_vec())
27 .expect("fallback CID from hex-encoded hash is always valid")
28 })
29}
30
31#[inline]
33pub fn create_block(data: Vec<u8>) -> Result<Block> {
34 let cid = compute_cid(&data);
35 Ok(Block::from_parts(cid, Bytes::from(data)))
36}
37
38pub fn generate_random_block(size: usize, seed: u64) -> Vec<u8> {
40 let mut rng = fastrand::Rng::with_seed(seed);
41 let mut data = vec![0u8; size];
42
43 for chunk in data.chunks_mut(8) {
44 let val = rng.u64(..);
45 let bytes = val.to_le_bytes();
46 let len = chunk.len().min(8);
47 chunk[..len].copy_from_slice(&bytes[..len]);
48 }
49
50 data
51}
52
53pub fn generate_compressible_data(size: usize) -> Vec<u8> {
55 let mut data = vec![0u8; size];
56 let pattern = b"IPFS is a distributed file system. ";
57
58 for (i, byte) in data.iter_mut().enumerate() {
59 *byte = pattern[i % pattern.len()];
60 }
61
62 data
63}
64
65pub fn generate_incompressible_data(size: usize, seed: u64) -> Vec<u8> {
67 generate_random_block(size, seed)
68}
69
70pub fn create_blocks_batch(data_vec: Vec<Vec<u8>>) -> Result<HashMap<Cid, Block>> {
75 let mut blocks = HashMap::with_capacity(data_vec.len());
76
77 for data in data_vec {
78 let block = create_block(data)?;
79 blocks.insert(*block.cid(), block);
80 }
81
82 Ok(blocks)
83}
84
85pub fn generate_random_blocks(count: usize, size: usize, start_seed: u64) -> Result<Vec<Block>> {
89 let mut blocks = Vec::with_capacity(count);
90
91 for i in 0..count {
92 let seed = start_seed.wrapping_add(i as u64);
93 let data = generate_random_block(size, seed);
94 let block = create_block(data)?;
95 blocks.push(block);
96 }
97
98 Ok(blocks)
99}
100
101pub fn generate_compressible_blocks(count: usize, size: usize) -> Result<Vec<Block>> {
105 let patterns = [
106 b"IPFS is a distributed file system. ".to_vec(),
107 b"Content addressing with CIDs. ".to_vec(),
108 b"Merkle DAGs for data structures. ".to_vec(),
109 b"Peer-to-peer networking protocol. ".to_vec(),
110 b"Immutable data storage layer. ".to_vec(),
111 ];
112
113 let mut blocks = Vec::with_capacity(count);
114
115 for i in 0..count {
116 let pattern = &patterns[i % patterns.len()];
117 let mut data = vec![0u8; size];
118
119 for (j, byte) in data.iter_mut().enumerate() {
120 *byte = pattern[j % pattern.len()];
121 }
122
123 let block = create_block(data)?;
124 blocks.push(block);
125 }
126
127 Ok(blocks)
128}
129
130pub fn generate_mixed_size_blocks(small: usize, medium: usize, large: usize) -> Result<Vec<Block>> {
135 let mut blocks = Vec::with_capacity(small + medium + large);
136 let mut seed = 0u64;
137
138 for _ in 0..small {
140 let data = generate_random_block(1024, seed);
141 blocks.push(create_block(data)?);
142 seed = seed.wrapping_add(1);
143 }
144
145 for _ in 0..medium {
147 let data = generate_random_block(64 * 1024, seed);
148 blocks.push(create_block(data)?);
149 seed = seed.wrapping_add(1);
150 }
151
152 for _ in 0..large {
154 let data = generate_random_block(1024 * 1024, seed);
155 blocks.push(create_block(data)?);
156 seed = seed.wrapping_add(1);
157 }
158
159 Ok(blocks)
160}
161
162pub fn generate_dedup_dataset(unique: usize, duplicate_factor: usize) -> Result<Vec<Block>> {
167 let mut blocks = Vec::new();
168
169 let unique_blocks = generate_random_blocks(unique, 4096, 42)?;
171
172 for _ in 0..duplicate_factor {
174 blocks.extend(unique_blocks.iter().cloned());
175 }
176
177 Ok(blocks)
178}
179
180pub fn extract_cids(blocks: &[Block]) -> Vec<Cid> {
182 blocks.iter().map(|b| *b.cid()).collect()
183}
184
185pub fn compute_total_size(blocks: &[Block]) -> usize {
187 blocks.iter().map(|b| b.data().len()).sum()
188}
189
190pub fn group_blocks_by_size(blocks: &[Block]) -> (Vec<Block>, Vec<Block>, Vec<Block>) {
197 let mut small = Vec::new();
198 let mut medium = Vec::new();
199 let mut large = Vec::new();
200
201 for block in blocks {
202 let size = block.data().len();
203 if size < 16 * 1024 {
204 small.push(block.clone());
205 } else if size < 256 * 1024 {
206 medium.push(block.clone());
207 } else {
208 large.push(block.clone());
209 }
210 }
211
212 (small, medium, large)
213}
214
215pub fn validate_block_integrity(block: &Block) -> bool {
219 let computed_cid = compute_cid(block.data());
220 computed_cid == *block.cid()
221}
222
223pub fn validate_blocks_batch(blocks: &[Block]) -> Vec<(Cid, bool)> {
227 blocks
228 .iter()
229 .map(|block| (*block.cid(), validate_block_integrity(block)))
230 .collect()
231}
232
233#[derive(Debug, Clone)]
235pub struct BlockStatistics {
236 pub count: usize,
238 pub total_size: usize,
240 pub avg_size: f64,
242 pub min_size: usize,
244 pub max_size: usize,
246 pub median_size: usize,
248}
249
250impl BlockStatistics {
251 pub fn from_blocks(blocks: &[Block]) -> Self {
253 if blocks.is_empty() {
254 return Self {
255 count: 0,
256 total_size: 0,
257 avg_size: 0.0,
258 min_size: 0,
259 max_size: 0,
260 median_size: 0,
261 };
262 }
263
264 let mut sizes: Vec<usize> = blocks.iter().map(|b| b.data().len()).collect();
265 sizes.sort_unstable();
266
267 let count = blocks.len();
268 let total_size: usize = sizes.iter().sum();
269 let avg_size = total_size as f64 / count as f64;
270 let min_size = sizes[0];
271 let max_size = sizes[count - 1];
272 let median_size = sizes[count / 2];
273
274 Self {
275 count,
276 total_size,
277 avg_size,
278 min_size,
279 max_size,
280 median_size,
281 }
282 }
283
284 pub fn estimated_memory_overhead(&self) -> usize {
286 self.count * 64
288 }
289
290 pub fn total_memory_footprint(&self) -> usize {
292 self.total_size + self.estimated_memory_overhead()
293 }
294}
295
296pub fn filter_blocks_by_size(blocks: &[Block], min_size: usize, max_size: usize) -> Vec<Block> {
298 blocks
299 .iter()
300 .filter(|block| {
301 let size = block.data().len();
302 size >= min_size && size <= max_size
303 })
304 .cloned()
305 .collect()
306}
307
308pub fn sort_blocks_by_size_asc(blocks: &mut [Block]) {
310 blocks.sort_by_key(|b| b.data().len());
311}
312
313pub fn sort_blocks_by_size_desc(blocks: &mut [Block]) {
315 blocks.sort_by_key(|b| std::cmp::Reverse(b.data().len()));
316}
317
318pub fn find_duplicates(blocks: &[Block]) -> HashMap<Cid, usize> {
322 let mut counts = HashMap::new();
323 for block in blocks {
324 *counts.entry(*block.cid()).or_insert(0) += 1;
325 }
326 counts.retain(|_, count| *count > 1);
327 counts
328}
329
330pub fn deduplicate_blocks(blocks: &[Block]) -> Vec<Block> {
332 let mut seen = std::collections::HashSet::new();
333 let mut result = Vec::new();
334
335 for block in blocks {
336 if seen.insert(*block.cid()) {
337 result.push(block.clone());
338 }
339 }
340
341 result
342}
343
344pub fn estimate_compression_ratio(data: &[u8]) -> f64 {
348 if data.is_empty() {
349 return 1.0;
350 }
351
352 let mut counts = [0u64; 256];
354 for &byte in data {
355 counts[byte as usize] += 1;
356 }
357
358 let len = data.len() as f64;
359 let mut entropy = 0.0;
360
361 for &count in &counts {
362 if count > 0 {
363 let p = count as f64 / len;
364 entropy -= p * p.log2();
365 }
366 }
367
368 (entropy / 8.0).min(1.0)
370}
371
372pub fn sample_blocks(blocks: &[Block], count: usize) -> Vec<Block> {
376 if blocks.len() <= count {
377 return blocks.to_vec();
378 }
379
380 let step = blocks.len() / count;
381 blocks.iter().step_by(step).take(count).cloned().collect()
382}
383
384pub fn generate_pattern_blocks(count: usize, size: usize, pattern: &str) -> Result<Vec<Block>> {
388 match pattern {
389 "sequential" => {
390 let mut blocks = Vec::new();
391 for i in 0..count {
392 let mut data = vec![0u8; size];
393 for (j, byte) in data.iter_mut().enumerate() {
394 *byte = ((i + j) % 256) as u8;
395 }
396 blocks.push(create_block(data)?);
397 }
398 Ok(blocks)
399 }
400 "random" => generate_random_blocks(count, size, 42),
401 "compressible" => generate_compressible_blocks(count, size),
402 "sparse" => {
403 let mut blocks = Vec::new();
404 for _ in 0..count {
405 let mut data = vec![0u8; size];
406 let mut rng = fastrand::Rng::new();
408 for _ in 0..size / 10 {
409 let idx = rng.usize(..size);
410 data[idx] = rng.u8(1..);
411 }
412 blocks.push(create_block(data)?);
413 }
414 Ok(blocks)
415 }
416 _ => generate_random_blocks(count, size, 42), }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_compute_cid() {
426 let data = b"hello world";
427 let cid1 = compute_cid(data);
428 let cid2 = compute_cid(data);
429
430 assert_eq!(cid1, cid2);
432 }
433
434 #[test]
435 fn test_create_block() {
436 let data = b"hello world".to_vec();
437 let block = create_block(data.clone()).unwrap();
438 assert_eq!(block.data(), &data);
439 }
440
441 #[test]
442 fn test_generate_random_block() {
443 let block1 = generate_random_block(100, 42);
444 let block2 = generate_random_block(100, 42);
445 let block3 = generate_random_block(100, 43);
446
447 assert_eq!(block1, block2); assert_ne!(block1, block3); }
450
451 #[test]
452 fn test_generate_compressible_data() {
453 let data = generate_compressible_data(1000);
454 assert_eq!(data.len(), 1000);
455
456 let pattern = b"IPFS is a distributed file system. ";
458 for i in 0..10 {
459 assert_eq!(data[i], pattern[i % pattern.len()]);
460 }
461 }
462
463 #[test]
464 fn test_create_blocks_batch() {
465 let data_vec = vec![b"block1".to_vec(), b"block2".to_vec(), b"block3".to_vec()];
466
467 let blocks = create_blocks_batch(data_vec).unwrap();
468 assert_eq!(blocks.len(), 3);
469 }
470
471 #[test]
472 fn test_generate_random_blocks() {
473 let blocks = generate_random_blocks(10, 1024, 42).unwrap();
474 assert_eq!(blocks.len(), 10);
475
476 for block in &blocks {
478 assert_eq!(block.data().len(), 1024);
479 }
480
481 let cid1 = blocks[0].cid();
483 let cid2 = blocks[1].cid();
484 assert_ne!(cid1, cid2);
485 }
486
487 #[test]
488 fn test_generate_compressible_blocks() {
489 let blocks = generate_compressible_blocks(5, 1024).unwrap();
490 assert_eq!(blocks.len(), 5);
491
492 for block in &blocks {
493 assert_eq!(block.data().len(), 1024);
494 }
495 }
496
497 #[test]
498 fn test_generate_mixed_size_blocks() {
499 let blocks = generate_mixed_size_blocks(2, 3, 1).unwrap();
500 assert_eq!(blocks.len(), 6); assert_eq!(blocks[0].data().len(), 1024); assert_eq!(blocks[2].data().len(), 64 * 1024); assert_eq!(blocks[5].data().len(), 1024 * 1024); }
507
508 #[test]
509 fn test_generate_dedup_dataset() {
510 let blocks = generate_dedup_dataset(10, 3).unwrap();
511 assert_eq!(blocks.len(), 30); for i in 0..10 {
515 assert_eq!(blocks[i].cid(), blocks[i + 10].cid());
516 }
517 }
518
519 #[test]
520 fn test_extract_cids() {
521 let blocks = generate_random_blocks(5, 1024, 42).unwrap();
522 let cids = extract_cids(&blocks);
523 assert_eq!(cids.len(), 5);
524 assert_eq!(cids[0], *blocks[0].cid());
525 }
526
527 #[test]
528 fn test_compute_total_size() {
529 let blocks = generate_mixed_size_blocks(2, 2, 1).unwrap();
530 let total = compute_total_size(&blocks);
531
532 let expected = 2 * 1024 + 2 * 64 * 1024 + 1024 * 1024;
534 assert_eq!(total, expected);
535 }
536
537 #[test]
538 fn test_group_blocks_by_size() {
539 let blocks = generate_mixed_size_blocks(3, 2, 1).unwrap();
540 let (small, medium, large) = group_blocks_by_size(&blocks);
541
542 assert_eq!(small.len(), 3);
543 assert_eq!(medium.len(), 2);
544 assert_eq!(large.len(), 1);
545 }
546
547 #[test]
548 fn test_validate_block_integrity() {
549 let data = b"test data".to_vec();
550 let block = create_block(data).unwrap();
551 assert!(validate_block_integrity(&block));
552 }
553
554 #[test]
555 fn test_validate_blocks_batch() {
556 let blocks = generate_random_blocks(5, 1024, 42).unwrap();
557 let results = validate_blocks_batch(&blocks);
558 assert_eq!(results.len(), 5);
559 for (_, is_valid) in results {
560 assert!(is_valid);
561 }
562 }
563
564 #[test]
565 fn test_block_statistics() {
566 let blocks = generate_mixed_size_blocks(2, 3, 1).unwrap();
567 let stats = BlockStatistics::from_blocks(&blocks);
568
569 assert_eq!(stats.count, 6);
570 assert!(stats.avg_size > 0.0);
571 assert!(stats.min_size <= stats.max_size);
572 assert!(stats.total_memory_footprint() > stats.total_size);
573 }
574
575 #[test]
576 fn test_filter_blocks_by_size() {
577 let blocks = generate_mixed_size_blocks(5, 5, 5).unwrap();
578 let filtered = filter_blocks_by_size(&blocks, 2000, 100_000);
579
580 for block in &filtered {
582 let size = block.data().len();
583 assert!((2000..=100_000).contains(&size));
584 }
585 }
586
587 #[test]
588 fn test_sort_blocks_by_size() {
589 let mut blocks = generate_mixed_size_blocks(2, 2, 2).unwrap();
590
591 sort_blocks_by_size_asc(&mut blocks);
592 for i in 1..blocks.len() {
593 assert!(blocks[i - 1].data().len() <= blocks[i].data().len());
594 }
595
596 sort_blocks_by_size_desc(&mut blocks);
597 for i in 1..blocks.len() {
598 assert!(blocks[i - 1].data().len() >= blocks[i].data().len());
599 }
600 }
601
602 #[test]
603 fn test_find_duplicates() {
604 let unique_blocks = generate_random_blocks(5, 1024, 42).unwrap();
605 let mut all_blocks = unique_blocks.clone();
606 all_blocks.extend(unique_blocks.clone()); let duplicates = find_duplicates(&all_blocks);
609 assert_eq!(duplicates.len(), 5); for (_, count) in duplicates {
612 assert_eq!(count, 2);
613 }
614 }
615
616 #[test]
617 fn test_deduplicate_blocks() {
618 let unique_blocks = generate_random_blocks(5, 1024, 42).unwrap();
619 let mut all_blocks = unique_blocks.clone();
620 all_blocks.extend(unique_blocks.clone());
621
622 let deduped = deduplicate_blocks(&all_blocks);
623 assert_eq!(deduped.len(), 5);
624 }
625
626 #[test]
627 fn test_estimate_compression_ratio() {
628 let compressible = generate_compressible_data(1000);
630 let compressible_ratio = estimate_compression_ratio(&compressible);
631
632 let random = generate_random_block(1000, 42);
634 let random_ratio = estimate_compression_ratio(&random);
635
636 assert!(compressible_ratio < random_ratio);
638 }
639
640 #[test]
641 fn test_sample_blocks() {
642 let blocks = generate_random_blocks(100, 1024, 42).unwrap();
643
644 let sample = sample_blocks(&blocks, 10);
645 assert_eq!(sample.len(), 10);
646
647 let sample_all = sample_blocks(&blocks, 200);
649 assert_eq!(sample_all.len(), 100);
650 }
651
652 #[test]
653 fn test_generate_pattern_blocks() {
654 let sequential = generate_pattern_blocks(5, 1024, "sequential").unwrap();
655 assert_eq!(sequential.len(), 5);
656
657 let random = generate_pattern_blocks(5, 1024, "random").unwrap();
658 assert_eq!(random.len(), 5);
659
660 let compressible = generate_pattern_blocks(5, 1024, "compressible").unwrap();
661 assert_eq!(compressible.len(), 5);
662
663 let sparse = generate_pattern_blocks(5, 1024, "sparse").unwrap();
664 assert_eq!(sparse.len(), 5);
665
666 let sparse_data = sparse[0].data();
668 let zero_count = sparse_data.iter().filter(|&&b| b == 0).count();
669 assert!(zero_count > sparse_data.len() * 8 / 10); }
671}