1use crate::{Block, Cid, CidBuilder, HashAlgorithm, Ipld, Result};
21use bytes::Bytes;
22use std::collections::BTreeMap;
23
24pub fn quick_block(data: &[u8]) -> Result<Block> {
37 Block::new(Bytes::copy_from_slice(data))
38}
39
40pub fn block_with_hash(data: &[u8], algorithm: HashAlgorithm) -> Result<Block> {
50 crate::BlockBuilder::new()
51 .hash_algorithm(algorithm)
52 .build_from_slice(data)
53}
54
55pub fn parse_cid_string(s: &str) -> Result<Cid> {
69 crate::cid::parse_cid(s)
70}
71
72pub fn cid_of(data: &[u8], algorithm: HashAlgorithm) -> Result<Cid> {
82 CidBuilder::new().hash_algorithm(algorithm).build(data)
83}
84
85pub fn sha256_cid(data: &[u8]) -> Result<Cid> {
95 cid_of(data, HashAlgorithm::Sha256)
96}
97
98pub fn sha3_cid(data: &[u8]) -> Result<Cid> {
108 cid_of(data, HashAlgorithm::Sha3_256)
109}
110
111pub fn sha512_cid(data: &[u8]) -> Result<Cid> {
121 cid_of(data, HashAlgorithm::Sha512)
122}
123
124pub fn sha3_512_cid(data: &[u8]) -> Result<Cid> {
134 cid_of(data, HashAlgorithm::Sha3_512)
135}
136
137pub fn blake2b256_cid(data: &[u8]) -> Result<Cid> {
147 cid_of(data, HashAlgorithm::Blake2b256)
148}
149
150pub fn blake2b512_cid(data: &[u8]) -> Result<Cid> {
160 cid_of(data, HashAlgorithm::Blake2b512)
161}
162
163pub fn blake2s256_cid(data: &[u8]) -> Result<Cid> {
173 cid_of(data, HashAlgorithm::Blake2s256)
174}
175
176pub fn blake3_cid(data: &[u8]) -> Result<Cid> {
186 cid_of(data, HashAlgorithm::Blake3)
187}
188
189pub fn blocks_equal(a: &Block, b: &Block) -> bool {
201 a.cid() == b.cid()
202}
203
204pub fn verify_block(block: &Block) -> Result<bool> {
217 block.verify()
218}
219
220pub fn ipld_map<K: Into<String>>(pairs: Vec<(K, Ipld)>) -> Ipld {
233 let mut map = BTreeMap::new();
234 for (k, v) in pairs {
235 map.insert(k.into(), v);
236 }
237 Ipld::Map(map)
238}
239
240pub fn ipld_list(values: Vec<Ipld>) -> Ipld {
254 Ipld::List(values)
255}
256
257pub fn ipld_to_cbor(ipld: &Ipld) -> Result<Vec<u8>> {
268 ipld.to_dag_cbor()
269}
270
271pub fn ipld_from_cbor(data: &[u8]) -> Result<Ipld> {
284 Ipld::from_dag_cbor(data)
285}
286
287pub fn ipld_to_json(ipld: &Ipld) -> Result<String> {
298 ipld.to_dag_json()
299}
300
301pub fn ipld_from_json(data: &str) -> Result<Ipld> {
314 Ipld::from_dag_json(data)
315}
316
317pub fn format_size(bytes: u64) -> String {
329 const KB: u64 = 1024;
330 const MB: u64 = KB * 1024;
331 const GB: u64 = MB * 1024;
332
333 if bytes >= GB {
334 format!("{:.2} GB", bytes as f64 / GB as f64)
335 } else if bytes >= MB {
336 format!("{:.2} MB", bytes as f64 / MB as f64)
337 } else if bytes >= KB {
338 format!("{:.2} KB", bytes as f64 / KB as f64)
339 } else {
340 format!("{} B", bytes)
341 }
342}
343
344pub fn estimate_chunks(data_size: u64) -> usize {
356 const DEFAULT_CHUNK_SIZE: u64 = 256 * 1024; data_size.div_ceil(DEFAULT_CHUNK_SIZE) as usize
358}
359
360pub fn needs_chunking(data_size: u64) -> bool {
371 data_size > crate::MAX_BLOCK_SIZE as u64
372}
373
374#[derive(Debug, Clone)]
380pub struct CidInfo {
381 pub cid_string: String,
383 pub version: u8,
385 pub codec: u64,
387 pub hash_code: u64,
389 pub hash_length: usize,
391}
392
393impl std::fmt::Display for CidInfo {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 write!(
396 f,
397 "CID: {}\n Version: {}\n Codec: 0x{:x}\n Hash: 0x{:x} ({} bytes)",
398 self.cid_string, self.version, self.codec, self.hash_code, self.hash_length
399 )
400 }
401}
402
403pub fn inspect_cid(cid: &Cid) -> CidInfo {
415 CidInfo {
416 cid_string: cid.to_string(),
417 version: match cid.version() {
418 cid::Version::V0 => 0,
419 cid::Version::V1 => 1,
420 },
421 codec: cid.codec(),
422 hash_code: cid.hash().code(),
423 hash_length: cid.hash().digest().len(),
424 }
425}
426
427#[derive(Debug, Clone)]
429pub struct BlockInfo {
430 pub cid: String,
432 pub size: u64,
434 pub size_formatted: String,
436 pub is_valid: bool,
438}
439
440impl std::fmt::Display for BlockInfo {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 write!(
443 f,
444 "Block:\n CID: {}\n Size: {} ({})\n Valid: {}",
445 self.cid, self.size, self.size_formatted, self.is_valid
446 )
447 }
448}
449
450pub fn inspect_block(block: &Block) -> Result<BlockInfo> {
462 let is_valid = block.verify()?;
463 Ok(BlockInfo {
464 cid: block.cid().to_string(),
465 size: block.size(),
466 size_formatted: format_size(block.size()),
467 is_valid,
468 })
469}
470
471pub fn validate_cid_string(s: &str) -> Result<Cid> {
481 parse_cid_string(s)
482}
483
484pub fn validate_blocks(blocks: &[Block]) -> Result<(usize, usize)> {
500 let mut valid = 0;
501 let mut invalid = 0;
502
503 for block in blocks {
504 if block.verify()? {
505 valid += 1;
506 } else {
507 invalid += 1;
508 }
509 }
510
511 Ok((valid, invalid))
512}
513
514pub fn find_invalid_blocks(blocks: &[Block]) -> Result<Vec<usize>> {
529 let mut invalid_indices = Vec::new();
530
531 for (i, block) in blocks.iter().enumerate() {
532 if !block.verify()? {
533 invalid_indices.push(i);
534 }
535 }
536
537 Ok(invalid_indices)
538}
539
540pub fn measure_cid_generation(data: &[u8], algorithm: HashAlgorithm) -> Result<(u64, Cid)> {
554 let start = std::time::Instant::now();
555 let cid = cid_of(data, algorithm)?;
556 let duration = start.elapsed();
557 Ok((duration.as_micros() as u64, cid))
558}
559
560pub fn measure_block_creation(data: &[u8]) -> Result<(u64, Block)> {
573 let start = std::time::Instant::now();
574 let block = quick_block(data)?;
575 let duration = start.elapsed();
576 Ok((duration.as_micros() as u64, block))
577}
578
579pub fn deduplication_ratio(blocks: &[Block]) -> f64 {
599 if blocks.is_empty() {
600 return 0.0;
601 }
602
603 let unique_cids: std::collections::HashSet<_> = blocks.iter().map(|b| b.cid()).collect();
604 unique_cids.len() as f64 / blocks.len() as f64
605}
606
607pub fn count_unique_blocks(blocks: &[Block]) -> usize {
622 let unique_cids: std::collections::HashSet<_> = blocks.iter().map(|b| b.cid()).collect();
623 unique_cids.len()
624}
625
626pub fn total_blocks_size(blocks: &[Block]) -> u64 {
640 blocks.iter().map(|b| b.size()).sum()
641}
642
643pub fn compress_block_data(
664 data: &bytes::Bytes,
665 algorithm: crate::CompressionAlgorithm,
666 level: u8,
667) -> crate::Result<bytes::Bytes> {
668 crate::compress(data, algorithm, level)
669}
670
671pub fn decompress_block_data(
686 compressed: &bytes::Bytes,
687 algorithm: crate::CompressionAlgorithm,
688) -> crate::Result<bytes::Bytes> {
689 crate::decompress(compressed, algorithm)
690}
691
692pub fn estimate_compression_savings(
709 data: &bytes::Bytes,
710 algorithm: crate::CompressionAlgorithm,
711 level: u8,
712) -> crate::Result<f64> {
713 crate::compression_ratio(data, algorithm, level)
714}
715
716pub fn should_compress(
735 data: &bytes::Bytes,
736 algorithm: crate::CompressionAlgorithm,
737 level: u8,
738) -> crate::Result<bool> {
739 if data.len() < 1024 {
741 return Ok(false);
742 }
743
744 if algorithm == crate::CompressionAlgorithm::None {
746 return Ok(false);
747 }
748
749 let ratio = crate::compression_ratio(data, algorithm, level)?;
751 Ok(ratio < 0.8)
752}
753
754pub fn recommended_compression(prefer_ratio_over_speed: bool) -> crate::CompressionAlgorithm {
771 if prefer_ratio_over_speed {
772 crate::CompressionAlgorithm::Zstd } else {
774 crate::CompressionAlgorithm::Lz4 }
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781
782 #[test]
783 fn test_quick_block() {
784 let block = quick_block(b"test data").unwrap();
785 assert_eq!(block.data().as_ref(), b"test data");
786 }
787
788 #[test]
789 fn test_block_with_hash() {
790 let block1 = block_with_hash(b"data", HashAlgorithm::Sha256).unwrap();
791 let block2 = block_with_hash(b"data", HashAlgorithm::Sha3_256).unwrap();
792 assert_ne!(block1.cid(), block2.cid());
794 }
795
796 #[test]
797 fn test_cid_functions() {
798 let sha256 = sha256_cid(b"test").unwrap();
799 let sha3 = sha3_cid(b"test").unwrap();
800 assert_ne!(sha256, sha3);
801 }
802
803 #[test]
804 fn test_all_hash_algorithm_cid_functions() {
805 let data = b"test data for all hash algorithms";
806
807 let sha256 = sha256_cid(data).unwrap();
809 let sha512 = sha512_cid(data).unwrap();
810 let sha3_256 = sha3_cid(data).unwrap();
811 let sha3_512 = sha3_512_cid(data).unwrap();
812 let blake2b256 = blake2b256_cid(data).unwrap();
813 let blake2b512 = blake2b512_cid(data).unwrap();
814 let blake2s256 = blake2s256_cid(data).unwrap();
815 let blake3 = blake3_cid(data).unwrap();
816
817 let cids = vec![
819 sha256, sha512, sha3_256, sha3_512, blake2b256, blake2b512, blake2s256, blake3,
820 ];
821
822 for i in 0..cids.len() {
824 for j in (i + 1)..cids.len() {
825 assert_ne!(cids[i], cids[j], "CID {} and {} should be different", i, j);
826 }
827 }
828 }
829
830 #[test]
831 fn test_hash_algorithm_determinism() {
832 let data = b"determinism test";
833
834 assert_eq!(sha256_cid(data).unwrap(), sha256_cid(data).unwrap());
836 assert_eq!(sha512_cid(data).unwrap(), sha512_cid(data).unwrap());
837 assert_eq!(sha3_cid(data).unwrap(), sha3_cid(data).unwrap());
838 assert_eq!(sha3_512_cid(data).unwrap(), sha3_512_cid(data).unwrap());
839 assert_eq!(blake2b256_cid(data).unwrap(), blake2b256_cid(data).unwrap());
840 assert_eq!(blake2b512_cid(data).unwrap(), blake2b512_cid(data).unwrap());
841 assert_eq!(blake2s256_cid(data).unwrap(), blake2s256_cid(data).unwrap());
842 assert_eq!(blake3_cid(data).unwrap(), blake3_cid(data).unwrap());
843 }
844
845 #[test]
846 fn test_hash_algorithm_names_and_sizes() {
847 use crate::HashAlgorithm;
848
849 assert_eq!(HashAlgorithm::Sha256.name(), "SHA2-256");
851 assert_eq!(HashAlgorithm::Sha512.name(), "SHA2-512");
852 assert_eq!(HashAlgorithm::Sha3_256.name(), "SHA3-256");
853 assert_eq!(HashAlgorithm::Sha3_512.name(), "SHA3-512");
854 assert_eq!(HashAlgorithm::Blake2b256.name(), "BLAKE2b-256");
855 assert_eq!(HashAlgorithm::Blake2b512.name(), "BLAKE2b-512");
856 assert_eq!(HashAlgorithm::Blake2s256.name(), "BLAKE2s-256");
857 assert_eq!(HashAlgorithm::Blake3.name(), "BLAKE3");
858
859 assert_eq!(HashAlgorithm::Sha256.hash_size(), 32);
861 assert_eq!(HashAlgorithm::Sha512.hash_size(), 64);
862 assert_eq!(HashAlgorithm::Sha3_256.hash_size(), 32);
863 assert_eq!(HashAlgorithm::Sha3_512.hash_size(), 64);
864 assert_eq!(HashAlgorithm::Blake2b256.hash_size(), 32);
865 assert_eq!(HashAlgorithm::Blake2b512.hash_size(), 64);
866 assert_eq!(HashAlgorithm::Blake2s256.hash_size(), 32);
867 assert_eq!(HashAlgorithm::Blake3.hash_size(), 32);
868 }
869
870 #[test]
871 fn test_hash_algorithm_all() {
872 use crate::HashAlgorithm;
873
874 let all = HashAlgorithm::all();
875 assert_eq!(all.len(), 8);
876
877 assert!(all.contains(&HashAlgorithm::Sha256));
879 assert!(all.contains(&HashAlgorithm::Sha512));
880 assert!(all.contains(&HashAlgorithm::Sha3_256));
881 assert!(all.contains(&HashAlgorithm::Sha3_512));
882 assert!(all.contains(&HashAlgorithm::Blake2b256));
883 assert!(all.contains(&HashAlgorithm::Blake2b512));
884 assert!(all.contains(&HashAlgorithm::Blake2s256));
885 assert!(all.contains(&HashAlgorithm::Blake3));
886 }
887
888 #[test]
889 fn test_blocks_equal() {
890 let block1 = quick_block(b"same").unwrap();
891 let block2 = quick_block(b"same").unwrap();
892 let block3 = quick_block(b"different").unwrap();
893
894 assert!(blocks_equal(&block1, &block2));
895 assert!(!blocks_equal(&block1, &block3));
896 }
897
898 #[test]
899 fn test_verify_block() {
900 let block = quick_block(b"verify me").unwrap();
901 assert!(verify_block(&block).unwrap());
902 }
903
904 #[test]
905 fn test_ipld_map() {
906 let map = ipld_map(vec![
907 ("key1", Ipld::String("value1".to_string())),
908 ("key2", Ipld::Integer(42)),
909 ]);
910
911 match map {
912 Ipld::Map(m) => {
913 assert_eq!(m.len(), 2);
914 assert!(m.contains_key("key1"));
915 assert!(m.contains_key("key2"));
916 }
917 _ => panic!("Expected map"),
918 }
919 }
920
921 #[test]
922 fn test_ipld_list() {
923 let list = ipld_list(vec![Ipld::Integer(1), Ipld::Integer(2), Ipld::Integer(3)]);
924
925 match list {
926 Ipld::List(l) => assert_eq!(l.len(), 3),
927 _ => panic!("Expected list"),
928 }
929 }
930
931 #[test]
932 fn test_ipld_cbor_roundtrip() {
933 let ipld = Ipld::String("test".to_string());
934 let cbor = ipld_to_cbor(&ipld).unwrap();
935 let decoded = ipld_from_cbor(&cbor).unwrap();
936 assert_eq!(ipld, decoded);
937 }
938
939 #[test]
940 fn test_ipld_json_roundtrip() {
941 let ipld = Ipld::String("test".to_string());
942 let json = ipld_to_json(&ipld).unwrap();
943 let decoded = ipld_from_json(&json).unwrap();
944 assert_eq!(ipld, decoded);
945 }
946
947 #[test]
948 fn test_format_size() {
949 assert_eq!(format_size(512), "512 B");
950 assert_eq!(format_size(1024), "1.00 KB");
951 assert_eq!(format_size(1_048_576), "1.00 MB");
952 assert_eq!(format_size(1_073_741_824), "1.00 GB");
953 assert_eq!(format_size(2_147_483_648), "2.00 GB");
954 }
955
956 #[test]
957 fn test_estimate_chunks() {
958 assert_eq!(estimate_chunks(100), 1);
959 assert_eq!(estimate_chunks(300_000), 2);
960 assert_eq!(estimate_chunks(1_000_000), 4);
961 }
962
963 #[test]
964 fn test_needs_chunking() {
965 assert!(!needs_chunking(100));
966 assert!(!needs_chunking(1_000_000));
967 assert!(!needs_chunking(2_000_000)); assert!(needs_chunking(3_000_000)); assert!(needs_chunking(10_000_000));
970 }
971
972 #[test]
975 fn test_inspect_cid() {
976 let cid = sha256_cid(b"test").unwrap();
977 let info = inspect_cid(&cid);
978 assert_eq!(info.version, 1);
979 assert!(!info.cid_string.is_empty());
980 assert!(info.hash_length > 0);
981 }
982
983 #[test]
984 fn test_inspect_block() {
985 let block = quick_block(b"test data").unwrap();
986 let info = inspect_block(&block).unwrap();
987 assert!(info.is_valid);
988 assert_eq!(info.size, 9_u64);
989 assert!(!info.cid.is_empty());
990 assert!(!info.size_formatted.is_empty());
991 }
992
993 #[test]
994 fn test_cid_info_display() {
995 let cid = sha256_cid(b"test").unwrap();
996 let info = inspect_cid(&cid);
997 let display = format!("{}", info);
998 assert!(display.contains("CID:"));
999 assert!(display.contains("Version:"));
1000 assert!(display.contains("Codec:"));
1001 }
1002
1003 #[test]
1004 fn test_block_info_display() {
1005 let block = quick_block(b"test").unwrap();
1006 let info = inspect_block(&block).unwrap();
1007 let display = format!("{}", info);
1008 assert!(display.contains("Block:"));
1009 assert!(display.contains("CID:"));
1010 assert!(display.contains("Valid:"));
1011 }
1012
1013 #[test]
1014 fn test_validate_blocks() {
1015 let blocks = vec![
1016 quick_block(b"data1").unwrap(),
1017 quick_block(b"data2").unwrap(),
1018 quick_block(b"data3").unwrap(),
1019 ];
1020
1021 let (valid, invalid) = validate_blocks(&blocks).unwrap();
1022 assert_eq!(valid, 3);
1023 assert_eq!(invalid, 0);
1024 }
1025
1026 #[test]
1027 fn test_validate_blocks_empty() {
1028 let blocks: Vec<Block> = vec![];
1029 let (valid, invalid) = validate_blocks(&blocks).unwrap();
1030 assert_eq!(valid, 0);
1031 assert_eq!(invalid, 0);
1032 }
1033
1034 #[test]
1035 fn test_find_invalid_blocks() {
1036 let blocks = vec![
1037 quick_block(b"data1").unwrap(),
1038 quick_block(b"data2").unwrap(),
1039 ];
1040
1041 let invalid = find_invalid_blocks(&blocks).unwrap();
1042 assert_eq!(invalid.len(), 0);
1043 }
1044
1045 #[test]
1046 fn test_measure_cid_generation() {
1047 let (duration, cid) = measure_cid_generation(b"test data", HashAlgorithm::Sha256).unwrap();
1048 assert!(duration > 0);
1049 assert!(!cid.to_string().is_empty());
1050 }
1051
1052 #[test]
1053 fn test_measure_block_creation() {
1054 let (duration, block) = measure_block_creation(b"test data").unwrap();
1055 assert!(duration > 0);
1056 assert_eq!(block.size(), 9_u64);
1057 }
1058
1059 #[test]
1060 fn test_deduplication_ratio() {
1061 let blocks = vec![
1062 quick_block(b"same").unwrap(),
1063 quick_block(b"same").unwrap(),
1064 quick_block(b"different").unwrap(),
1065 ];
1066
1067 let ratio = deduplication_ratio(&blocks);
1068 assert!((ratio - 0.666).abs() < 0.01);
1070 }
1071
1072 #[test]
1073 fn test_deduplication_ratio_all_unique() {
1074 let blocks = vec![
1075 quick_block(b"data1").unwrap(),
1076 quick_block(b"data2").unwrap(),
1077 quick_block(b"data3").unwrap(),
1078 ];
1079
1080 let ratio = deduplication_ratio(&blocks);
1081 assert_eq!(ratio, 1.0);
1082 }
1083
1084 #[test]
1085 fn test_deduplication_ratio_all_same() {
1086 let blocks = vec![
1087 quick_block(b"same").unwrap(),
1088 quick_block(b"same").unwrap(),
1089 quick_block(b"same").unwrap(),
1090 ];
1091
1092 let ratio = deduplication_ratio(&blocks);
1093 assert!((ratio - 0.333).abs() < 0.01);
1095 }
1096
1097 #[test]
1098 fn test_deduplication_ratio_empty() {
1099 let blocks: Vec<Block> = vec![];
1100 let ratio = deduplication_ratio(&blocks);
1101 assert_eq!(ratio, 0.0);
1102 }
1103
1104 #[test]
1105 fn test_count_unique_blocks() {
1106 let blocks = vec![
1107 quick_block(b"same").unwrap(),
1108 quick_block(b"same").unwrap(),
1109 quick_block(b"different").unwrap(),
1110 ];
1111
1112 assert_eq!(count_unique_blocks(&blocks), 2);
1113 }
1114
1115 #[test]
1116 fn test_count_unique_blocks_all_unique() {
1117 let blocks = vec![
1118 quick_block(b"a").unwrap(),
1119 quick_block(b"b").unwrap(),
1120 quick_block(b"c").unwrap(),
1121 ];
1122
1123 assert_eq!(count_unique_blocks(&blocks), 3);
1124 }
1125
1126 #[test]
1127 fn test_count_unique_blocks_empty() {
1128 let blocks: Vec<Block> = vec![];
1129 assert_eq!(count_unique_blocks(&blocks), 0);
1130 }
1131
1132 #[test]
1133 fn test_total_blocks_size() {
1134 let blocks = vec![
1135 quick_block(b"data1").unwrap(), quick_block(b"data2").unwrap(), ];
1138
1139 assert_eq!(total_blocks_size(&blocks), 10);
1140 }
1141
1142 #[test]
1143 fn test_total_blocks_size_empty() {
1144 let blocks: Vec<Block> = vec![];
1145 assert_eq!(total_blocks_size(&blocks), 0);
1146 }
1147
1148 #[test]
1149 fn test_compress_block_data() {
1150 use crate::CompressionAlgorithm;
1151
1152 let data = bytes::Bytes::from("Hello, World! ".repeat(100));
1153 let compressed = compress_block_data(&data, CompressionAlgorithm::Zstd, 5).unwrap();
1154
1155 assert!(compressed.len() < data.len());
1157
1158 let decompressed = decompress_block_data(&compressed, CompressionAlgorithm::Zstd).unwrap();
1160 assert_eq!(data, decompressed);
1161 }
1162
1163 #[test]
1164 fn test_estimate_compression_savings() {
1165 use crate::CompressionAlgorithm;
1166
1167 let data = bytes::Bytes::from("a".repeat(1000));
1168 let ratio = estimate_compression_savings(&data, CompressionAlgorithm::Zstd, 5).unwrap();
1169
1170 assert!(ratio < 0.1);
1172 }
1173
1174 #[test]
1175 fn test_should_compress() {
1176 use crate::CompressionAlgorithm;
1177
1178 let small = bytes::Bytes::from_static(b"Hello");
1180 assert!(!should_compress(&small, CompressionAlgorithm::Zstd, 3).unwrap());
1181
1182 let large = bytes::Bytes::from("a".repeat(10000));
1184 assert!(should_compress(&large, CompressionAlgorithm::Zstd, 3).unwrap());
1185
1186 assert!(!should_compress(&large, CompressionAlgorithm::None, 3).unwrap());
1188 }
1189
1190 #[test]
1191 fn test_recommended_compression() {
1192 use crate::CompressionAlgorithm;
1193
1194 assert_eq!(recommended_compression(true), CompressionAlgorithm::Zstd);
1195 assert_eq!(recommended_compression(false), CompressionAlgorithm::Lz4);
1196 }
1197}