1use crate::crc32::consts::{
134 CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
135 CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
136};
137
138#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
139use crate::crc32::fusion;
140
141use crate::crc64::consts::{
142 CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
143};
144use crate::structs::Calculator;
145use crate::traits::CrcCalculator;
146use digest::{DynDigest, InvalidBufferSize};
147use std::fs::File;
148use std::io::{Read, Write};
149
150mod algorithm;
151mod arch;
152mod cache;
153mod combine;
154mod consts;
155mod crc32;
156mod crc64;
157mod enums;
158mod feature_detection;
159mod ffi;
160mod generate;
161mod structs;
162mod test;
163mod traits;
164
165#[derive(Debug, Clone, Copy, PartialEq)]
167pub enum CrcAlgorithm {
168 Crc32Aixm,
169 Crc32Autosar,
170 Crc32Base91D,
171 Crc32Bzip2,
172 Crc32CdRomEdc,
173 Crc32Cksum,
174 Crc32Custom, Crc32Iscsi,
176 Crc32IsoHdlc,
177 Crc32Jamcrc,
178 Crc32Mef,
179 Crc32Mpeg2,
180 Crc32Xfer,
181 Crc64Custom, Crc64Ecma182,
183 Crc64GoIso,
184 Crc64Ms,
185 Crc64Nvme,
186 Crc64Redis,
187 Crc64We,
188 Crc64Xz,
189}
190
191#[derive(Clone, Copy, Debug, PartialEq)]
195pub enum CrcKeysStorage {
196 KeysFold256([u64; 23]),
198 KeysFutureTest([u64; 25]),
200}
201
202impl CrcKeysStorage {
203 #[inline(always)]
205 const fn get_key(self, index: usize) -> u64 {
206 match self {
207 CrcKeysStorage::KeysFold256(keys) => {
208 if index < 23 {
209 keys[index]
210 } else {
211 0
212 }
213 }
214 CrcKeysStorage::KeysFutureTest(keys) => {
215 if index < 25 {
216 keys[index]
217 } else {
218 0
219 }
220 }
221 }
222 }
223
224 #[inline(always)]
226 const fn key_count(self) -> usize {
227 match self {
228 CrcKeysStorage::KeysFold256(_) => 23,
229 CrcKeysStorage::KeysFutureTest(_) => 25,
230 }
231 }
232
233 #[inline(always)]
235 const fn from_keys_fold_256(keys: [u64; 23]) -> Self {
236 CrcKeysStorage::KeysFold256(keys)
237 }
238
239 #[inline(always)]
241 #[allow(dead_code)] const fn from_keys_fold_future_test(keys: [u64; 25]) -> Self {
243 CrcKeysStorage::KeysFutureTest(keys)
244 }
245
246 #[inline(always)]
250 pub fn to_keys_array_23(self) -> [u64; 23] {
251 match self {
252 CrcKeysStorage::KeysFold256(keys) => keys,
253 CrcKeysStorage::KeysFutureTest(keys) => {
254 let mut result = [0u64; 23];
255 result.copy_from_slice(&keys[..23]);
256 result
257 }
258 }
259 }
260}
261
262impl PartialEq<[u64; 23]> for CrcKeysStorage {
264 fn eq(&self, other: &[u64; 23]) -> bool {
265 self.to_keys_array_23() == *other
266 }
267}
268
269impl PartialEq<CrcKeysStorage> for [u64; 23] {
270 fn eq(&self, other: &CrcKeysStorage) -> bool {
271 *self == other.to_keys_array_23()
272 }
273}
274
275#[derive(Clone, Copy, Debug)]
277pub struct CrcParams {
278 pub algorithm: CrcAlgorithm,
279 pub name: &'static str,
280 pub width: u8,
281 pub poly: u64,
282 pub init: u64,
283 pub refin: bool,
284 pub refout: bool,
285 pub xorout: u64,
286 pub check: u64,
287 pub keys: CrcKeysStorage,
288}
289
290type CalculatorFn = fn(
299 u64, &[u8], CrcParams, ) -> u64;
303
304#[derive(Copy, Clone, Debug)]
310pub struct Digest {
311 state: u64,
313
314 amount: u64,
316
317 params: CrcParams,
319
320 calculator: CalculatorFn,
322}
323
324impl DynDigest for Digest {
325 #[inline(always)]
326 fn update(&mut self, data: &[u8]) {
327 self.update(data);
328 }
329
330 #[inline(always)]
331 fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
332 if buf.len() != self.output_size() {
333 return Err(InvalidBufferSize);
334 }
335
336 let result = self.finalize();
337 let bytes = if self.output_size() == 4 {
338 result.to_be_bytes()[4..].to_vec() } else {
340 result.to_be_bytes().to_vec() };
342 buf.copy_from_slice(&bytes[..self.output_size()]);
343
344 Ok(())
345 }
346
347 #[inline(always)]
348 fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
349 if out.len() != self.output_size() {
350 return Err(InvalidBufferSize);
351 }
352 let result = self.finalize();
353 self.reset();
354 let bytes = if self.output_size() == 4 {
355 result.to_be_bytes()[4..].to_vec() } else {
357 result.to_be_bytes().to_vec() };
359 out.copy_from_slice(&bytes[..self.output_size()]);
360 Ok(())
361 }
362
363 #[inline(always)]
364 fn reset(&mut self) {
365 self.reset();
366 }
367
368 #[inline(always)]
369 fn output_size(&self) -> usize {
370 self.params.width as usize / 8
371 }
372
373 fn box_clone(&self) -> Box<dyn DynDigest> {
374 Box::new(*self)
375 }
376}
377
378impl Digest {
379 #[inline(always)]
393 pub fn new(algorithm: CrcAlgorithm) -> Self {
394 let (calculator, params) = get_calculator_params(algorithm);
395
396 Self {
397 state: params.init,
398 amount: 0,
399 params,
400 calculator,
401 }
402 }
403
404 #[inline(always)]
428 pub fn new_with_init_state(algorithm: CrcAlgorithm, init_state: u64) -> Self {
429 let (calculator, params) = get_calculator_params(algorithm);
430
431 Self {
432 state: init_state,
433 amount: 0,
434 params,
435 calculator,
436 }
437 }
438
439 #[inline(always)]
464 pub fn new_with_params(params: CrcParams) -> Self {
465 let calculator = Calculator::calculate as CalculatorFn;
466
467 Self {
468 state: params.init,
469 amount: 0,
470 params,
471 calculator,
472 }
473 }
474
475 #[inline(always)]
477 pub fn update(&mut self, data: &[u8]) {
478 self.state = (self.calculator)(self.state, data, self.params);
479 self.amount += data.len() as u64;
480 }
481
482 #[inline(always)]
484 pub fn finalize(&self) -> u64 {
485 self.state ^ self.params.xorout
486 }
487
488 #[inline(always)]
490 pub fn finalize_reset(&mut self) -> u64 {
491 let result = self.finalize();
492 self.reset();
493
494 result
495 }
496
497 #[inline(always)]
499 pub fn reset(&mut self) {
500 self.state = self.params.init;
501 self.amount = 0;
502 }
503
504 #[inline(always)]
506 pub fn combine(&mut self, other: &Self) {
507 self.amount += other.amount;
508 let other_crc = other.finalize();
509
510 self.state = combine::checksums(
513 self.state ^ self.params.xorout,
514 other_crc,
515 other.amount,
516 self.params,
517 ) ^ self.params.xorout;
518 }
519
520 #[inline(always)]
522 pub fn get_amount(&self) -> u64 {
523 self.amount
524 }
525
526 #[inline(always)]
543 pub fn get_state(&self) -> u64 {
544 self.state
545 }
546}
547
548impl Write for Digest {
549 #[inline(always)]
550 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
551 self.update(buf);
552 Ok(buf.len())
553 }
554
555 #[inline(always)]
556 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
557 let len: usize = bufs
558 .iter()
559 .map(|buf| {
560 self.update(buf);
561 buf.len()
562 })
563 .sum();
564
565 Ok(len)
566 }
567
568 #[inline(always)]
569 fn flush(&mut self) -> std::io::Result<()> {
570 Ok(())
571 }
572
573 #[inline(always)]
574 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
575 self.update(buf);
576
577 Ok(())
578 }
579}
580
581#[inline(always)]
590pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
591 let (calculator, params) = get_calculator_params(algorithm);
592
593 calculator(params.init, buf, params) ^ params.xorout
594}
595
596pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 {
619 let calculator = Calculator::calculate as CalculatorFn;
620
621 calculator(params.init, buf, params) ^ params.xorout
622}
623
624#[inline(always)]
647pub fn checksum_file(
648 algorithm: CrcAlgorithm,
649 path: &str,
650 chunk_size: Option<usize>,
651) -> Result<u64, std::io::Error> {
652 checksum_file_with_digest(Digest::new(algorithm), path, chunk_size)
653}
654
655pub fn checksum_file_with_params(
689 params: CrcParams,
690 path: &str,
691 chunk_size: Option<usize>,
692) -> Result<u64, std::io::Error> {
693 checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size)
694}
695
696fn checksum_file_with_digest(
702 mut digest: Digest,
703 path: &str,
704 chunk_size: Option<usize>,
705) -> Result<u64, std::io::Error> {
706 let mut file = File::open(path)?;
707
708 let chunk_size = chunk_size.unwrap_or(524288);
714
715 let mut buf = vec![0; chunk_size];
716
717 while let Ok(n) = file.read(&mut buf) {
718 if n == 0 {
719 break;
720 }
721 digest.update(&buf[..n]);
722 }
723
724 Ok(digest.finalize())
725}
726
727#[inline(always)]
740pub fn checksum_combine(
741 algorithm: CrcAlgorithm,
742 checksum1: u64,
743 checksum2: u64,
744 checksum2_len: u64,
745) -> u64 {
746 let params = get_calculator_params(algorithm).1;
747
748 combine::checksums(checksum1, checksum2, checksum2_len, params)
749}
750
751pub fn checksum_combine_with_params(
776 params: CrcParams,
777 checksum1: u64,
778 checksum2: u64,
779 checksum2_len: u64,
780) -> u64 {
781 combine::checksums(checksum1, checksum2, checksum2_len, params)
782}
783
784pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
814 use crate::feature_detection::get_arch_ops;
815
816 let arch_ops = get_arch_ops();
817 arch_ops.get_target_string()
818}
819
820#[inline(always)]
822fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
823 match algorithm {
824 CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
825 CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
826 CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
827 CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
828 CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
829 CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
830 CrcAlgorithm::Crc32Custom => {
831 panic!("Custom CRC-32 requires parameters via CrcParams::new()")
832 }
833 CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
834 CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
835 CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
836 CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
837 CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
838 CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
839 CrcAlgorithm::Crc64Custom => {
840 panic!("Custom CRC-64 requires parameters via CrcParams::new()")
841 }
842 CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
843 CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
844 CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
845 CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
846 CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
847 CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
848 CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
849 }
850}
851
852#[inline(always)]
857fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
858 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))]
860 return fusion::crc32_iscsi(state as u32, data) as u64;
861
862 #[cfg(all(
863 not(target_arch = "aarch64"),
864 not(target_arch = "x86_64"),
865 not(target_arch = "x86")
866 ))]
867 Calculator::calculate(state, data, _params)
869}
870
871#[inline(always)]
877fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
878 #[cfg(target_arch = "aarch64")]
880 return fusion::crc32_iso_hdlc(state as u32, data) as u64;
881
882 #[cfg(not(target_arch = "aarch64"))]
885 Calculator::calculate(state, data, _params)
886}
887
888#[cfg(test)]
889mod lib {
890 #![allow(unused)]
891
892 use super::*;
893 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
894 use crate::test::enums::AnyCrcTestConfig;
895 use cbindgen::Language::C;
896 use cbindgen::Style::Both;
897 use rand::{rng, Rng};
898 use std::fs::{read, write};
899
900 #[test]
901 fn test_checksum_check() {
902 for config in TEST_ALL_CONFIGS {
903 assert_eq!(
904 checksum(config.get_algorithm(), TEST_CHECK_STRING),
905 config.get_check()
906 );
907 }
908 }
909
910 #[test]
911 fn test_checksum_reference() {
912 for config in TEST_ALL_CONFIGS {
913 assert_eq!(
914 checksum(config.get_algorithm(), TEST_CHECK_STRING),
915 config.checksum_with_reference(TEST_CHECK_STRING)
916 );
917 }
918 }
919
920 #[test]
921 fn test_checksum_with_custom_params() {
922 crate::cache::clear_cache();
923
924 assert_eq!(
926 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
927 CRC32_ISCSI.check,
928 );
929
930 assert_eq!(
932 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
933 CRC32_BZIP2.check,
934 );
935
936 assert_eq!(
938 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
939 CRC64_NVME.check,
940 );
941
942 assert_eq!(
944 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
945 CRC64_ECMA_182.check,
946 );
947 }
948
949 #[test]
950 fn test_get_custom_params() {
951 crate::cache::clear_cache();
952
953 assert_eq!(
954 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
955 CRC32_ISCSI.check,
956 );
957
958 assert_eq!(
959 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
960 CRC32_BZIP2.check,
961 );
962
963 assert_eq!(
964 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
965 CRC64_NVME.check,
966 );
967
968 assert_eq!(
969 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
970 CRC64_ECMA_182.check,
971 );
972 }
973
974 #[test]
975 fn test_get_calculator_target_format() {
976 let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
977
978 assert!(!target.is_empty());
980
981 let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
983 assert!(
984 valid_prefixes
985 .iter()
986 .any(|prefix| target.starts_with(prefix)),
987 "Target '{}' should start with a valid architecture prefix",
988 target
989 );
990
991 let parts: Vec<&str> = target.split('-').collect();
993 assert!(
994 parts.len() >= 3,
995 "Target '{}' should have at least 3 parts: architecture-family-features",
996 target
997 );
998 }
999
1000 #[test]
1001 fn test_get_calculator_target_consistency() {
1002 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1004 let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1005 let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1006
1007 assert_eq!(
1008 target1, target2,
1009 "Target should be consistent across different CRC-32 algorithms"
1010 );
1011 assert_eq!(
1012 target1, target3,
1013 "Target should be consistent across CRC-32 and CRC-64 algorithms"
1014 );
1015 }
1016
1017 #[test]
1018 fn test_get_calculator_target_uses_cached_detection() {
1019 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1024 let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1025
1026 assert_eq!(
1027 target1, target2,
1028 "Cached detection should return identical results"
1029 );
1030 }
1031
1032 #[test]
1033 fn test_digest_updates_check() {
1034 for config in TEST_ALL_CONFIGS {
1035 check_digest(Digest::new(config.get_algorithm()), config.get_check());
1036 }
1037 }
1038
1039 #[test]
1040 fn test_digest_updates_check_with_custom_params() {
1041 crate::cache::clear_cache();
1042
1043 check_digest(
1045 Digest::new_with_params(get_custom_crc32_reflected()),
1046 CRC32_ISCSI.check,
1047 );
1048
1049 check_digest(
1051 Digest::new_with_params(get_custom_crc32_forward()),
1052 CRC32_BZIP2.check,
1053 );
1054
1055 check_digest(
1057 Digest::new_with_params(get_custom_crc64_reflected()),
1058 CRC64_NVME.check,
1059 );
1060
1061 check_digest(
1063 Digest::new_with_params(get_custom_crc64_forward()),
1064 CRC64_ECMA_182.check,
1065 );
1066 }
1067
1068 fn check_digest(mut digest: Digest, check: u64) {
1069 digest.update(b"123");
1070 digest.update(b"456");
1071 digest.update(b"789");
1072 assert_eq!(digest.finalize(), check,);
1073 }
1074
1075 #[test]
1076 fn test_small_all_lengths() {
1077 for config in TEST_ALL_CONFIGS {
1078 for len in 1..=255 {
1080 test_length(len, config);
1081 }
1082 }
1083 }
1084
1085 #[test]
1086 fn test_medium_lengths() {
1087 for config in TEST_ALL_CONFIGS {
1088 for len in 256..=1024 {
1090 test_length(len, config);
1091 }
1092 }
1093 }
1094
1095 #[test]
1096 fn test_large_lengths() {
1097 for config in TEST_ALL_CONFIGS {
1098 for len in 1048575..1048577 {
1100 test_length(len, config);
1101 }
1102 }
1103 }
1104
1105 fn test_length(length: usize, config: &AnyCrcTestConfig) {
1106 let mut data = vec![0u8; length];
1107 rng().fill(&mut data[..]);
1108
1109 let expected = config.checksum_with_reference(&data);
1111
1112 let result = checksum(config.get_algorithm(), &data);
1113
1114 assert_eq!(
1115 result,
1116 expected,
1117 "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1118 config.get_algorithm(),
1119 length,
1120 expected,
1121 result
1122 );
1123 }
1124
1125 #[test]
1126 fn test_combine() {
1127 for config in TEST_ALL_CONFIGS {
1128 let algorithm = config.get_algorithm();
1129 let check = config.get_check();
1130
1131 let checksum1 = checksum(algorithm, "1234".as_ref());
1133 let checksum2 = checksum(algorithm, "56789".as_ref());
1134
1135 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
1137
1138 let mut digest1 = Digest::new(algorithm);
1140 digest1.update("1234".as_ref());
1141
1142 let mut digest2 = Digest::new(algorithm);
1143 digest2.update("56789".as_ref());
1144
1145 digest1.combine(&digest2);
1146
1147 assert_eq!(digest1.finalize(), check)
1148 }
1149 }
1150
1151 #[test]
1152 fn test_combine_with_custom_params() {
1153 crate::cache::clear_cache();
1154
1155 let crc32_params = get_custom_crc32_reflected();
1157 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1158 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1159 assert_eq!(
1160 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1161 CRC32_ISCSI.check,
1162 );
1163
1164 let crc32_params = get_custom_crc32_forward();
1166 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1167 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1168 assert_eq!(
1169 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1170 CRC32_BZIP2.check,
1171 );
1172
1173 let crc64_params = get_custom_crc64_reflected();
1175 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1176 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1177 assert_eq!(
1178 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1179 CRC64_NVME.check,
1180 );
1181
1182 let crc64_params = get_custom_crc64_forward();
1184 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1185 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1186 assert_eq!(
1187 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1188 CRC64_ECMA_182.check,
1189 );
1190 }
1191
1192 #[test]
1193 fn test_checksum_file() {
1194 let test_file_path = "test/test_crc32_hash_file.bin";
1196 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1198 eprintln!("Skipping test due to write error: {}", e);
1199 return;
1200 }
1201
1202 for config in TEST_ALL_CONFIGS {
1203 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1204 assert_eq!(result, config.checksum_with_reference(&data));
1205 }
1206
1207 std::fs::remove_file(test_file_path).unwrap();
1208 }
1209
1210 #[test]
1211 fn test_checksum_file_with_custom_params() {
1212 crate::cache::clear_cache();
1213
1214 let test_file_path = "test/test_crc32_hash_file_custom.bin";
1216 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1218 eprintln!("Skipping test due to write error: {}", e);
1219 return;
1220 }
1221
1222 check_file(
1224 get_custom_crc32_reflected(),
1225 test_file_path,
1226 CRC32_ISCSI.check,
1227 );
1228
1229 check_file(
1231 get_custom_crc32_forward(),
1232 test_file_path,
1233 CRC32_BZIP2.check,
1234 );
1235
1236 check_file(
1238 get_custom_crc64_reflected(),
1239 test_file_path,
1240 CRC64_NVME.check,
1241 );
1242
1243 check_file(
1245 get_custom_crc64_forward(),
1246 test_file_path,
1247 CRC64_ECMA_182.check,
1248 );
1249
1250 std::fs::remove_file(test_file_path).unwrap();
1251 }
1252
1253 fn check_file(params: CrcParams, file_path: &str, check: u64) {
1254 let result = checksum_file_with_params(params, file_path, None).unwrap();
1255 assert_eq!(result, check);
1256 }
1257
1258 #[test]
1259 fn test_writer() {
1260 let test_file_path = "test/test_crc32_writer_file.bin";
1262 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
1264 eprintln!("Skipping test due to write error: {}", e);
1265 return;
1266 }
1267
1268 for config in TEST_ALL_CONFIGS {
1269 let mut digest = Digest::new(config.get_algorithm());
1270 let mut file = File::open(test_file_path).unwrap();
1271 std::io::copy(&mut file, &mut digest).unwrap();
1272 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1273 }
1274
1275 std::fs::remove_file(test_file_path).unwrap();
1276 }
1277 #[test]
1278 fn test_digest_reset() {
1279 for config in TEST_ALL_CONFIGS {
1280 let mut digest = Digest::new(config.get_algorithm());
1281 digest.update(b"42");
1282 digest.reset();
1283 digest.update(TEST_CHECK_STRING);
1284 assert_eq!(digest.finalize(), config.get_check());
1285 }
1286 }
1287
1288 #[test]
1289 fn test_digest_finalize_reset() {
1290 for config in TEST_ALL_CONFIGS {
1291 let check = config.get_check();
1292
1293 let mut digest = Digest::new(config.get_algorithm());
1294 digest.update(TEST_CHECK_STRING);
1295 assert_eq!(digest.finalize_reset(), check);
1296
1297 digest.update(TEST_CHECK_STRING);
1298 assert_eq!(digest.finalize(), check);
1299 }
1300 }
1301
1302 #[test]
1303 fn test_digest_finalize_into() {
1304 for config in TEST_ALL_CONFIGS {
1305 let mut digest = Digest::new(config.get_algorithm());
1306 digest.update(TEST_CHECK_STRING);
1307
1308 match digest.params.width {
1309 32 => {
1310 let mut output = [0u8; 4];
1311 digest.finalize_into(&mut output).unwrap();
1312 let result = u32::from_be_bytes(output) as u64;
1313 assert_eq!(result, config.get_check());
1314 }
1315 64 => {
1316 let mut output = [0u8; 8];
1317 digest.finalize_into(&mut output).unwrap();
1318 let result = u64::from_be_bytes(output);
1319 assert_eq!(result, config.get_check());
1320 }
1321 _ => panic!("Unsupported CRC width"),
1322 }
1323 }
1324 }
1325
1326 #[test]
1327 fn test_digest_finalize_into_reset() {
1328 for config in TEST_ALL_CONFIGS {
1329 let mut digest = Digest::new(config.get_algorithm());
1330 digest.update(TEST_CHECK_STRING);
1331
1332 let mut output: Vec<u8> = match digest.params.width {
1333 32 => vec![0u8; 4],
1334 64 => vec![0u8; 8],
1335 _ => panic!("Unsupported CRC width"),
1336 };
1337
1338 digest.finalize_into_reset(&mut output).unwrap();
1339 let result = match output.len() {
1340 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1341 8 => u64::from_be_bytes(output.try_into().unwrap()),
1342 _ => panic!("Unsupported CRC width"),
1343 };
1344 assert_eq!(result, config.get_check());
1345
1346 digest.update(TEST_CHECK_STRING);
1347 assert_eq!(digest.finalize(), config.get_check());
1348 }
1349 }
1350
1351 #[test]
1353 fn test_ffi_header() -> Result<(), String> {
1354 #[cfg(target_os = "windows")]
1355 {
1356 eprintln!("Skipping test on Windows");
1358
1359 return Ok(());
1360 }
1361
1362 const HEADER: &str = "libcrc_fast.h";
1363
1364 let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1365
1366 let mut expected = Vec::new();
1367 cbindgen::Builder::new()
1368 .with_crate(crate_dir)
1369 .with_include_guard("CRC_FAST_H")
1370 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1371 .exclude_item("crc32_iscsi_impl")
1373 .exclude_item("crc32_iso_hdlc_impl")
1374 .exclude_item("get_iscsi_target")
1375 .exclude_item("get_iso_hdlc_target")
1376 .exclude_item("ISO_HDLC_TARGET")
1377 .exclude_item("ISCSI_TARGET")
1378 .exclude_item("CrcParams")
1379 .rename_item("Digest", "CrcFastDigest")
1380 .with_style(Both)
1381 .with_language(C)
1383 .with_cpp_compat(true)
1385 .generate()
1386 .map_err(|error| error.to_string())?
1387 .write(&mut expected);
1388
1389 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1392
1393 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1395 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1396
1397 expected = cleaned_content.into_bytes();
1399
1400 let actual = read(HEADER).map_err(|error| error.to_string())?;
1401
1402 if expected != actual {
1403 write(HEADER, expected).map_err(|error| error.to_string())?;
1404 return Err(format!(
1405 "{HEADER} is not up-to-date, commit the generated file and try again"
1406 ));
1407 }
1408
1409 Ok(())
1410 }
1411
1412 fn get_custom_crc32_reflected() -> CrcParams {
1413 CrcParams::new(
1414 "Custom CRC-32/ISCSI",
1415 32,
1416 CRC32_ISCSI.poly,
1417 CRC32_ISCSI.init,
1418 CRC32_ISCSI.refin,
1419 CRC32_ISCSI.xorout,
1420 CRC32_ISCSI.check,
1421 )
1422 }
1423
1424 fn get_custom_crc32_forward() -> CrcParams {
1425 CrcParams::new(
1426 "Custom CRC-32/BZIP2",
1427 32,
1428 CRC32_BZIP2.poly,
1429 CRC32_BZIP2.init,
1430 CRC32_BZIP2.refin,
1431 CRC32_BZIP2.xorout,
1432 CRC32_BZIP2.check,
1433 )
1434 }
1435
1436 fn get_custom_crc64_reflected() -> CrcParams {
1437 CrcParams::new(
1438 "Custom CRC-64/NVME",
1439 64,
1440 CRC64_NVME.poly,
1441 CRC64_NVME.init,
1442 CRC64_NVME.refin,
1443 CRC64_NVME.xorout,
1444 CRC64_NVME.check,
1445 )
1446 }
1447
1448 fn get_custom_crc64_forward() -> CrcParams {
1449 CrcParams::new(
1450 "Custom CRC-64/ECMA-182",
1451 64,
1452 CRC64_ECMA_182.poly,
1453 CRC64_ECMA_182.init,
1454 CRC64_ECMA_182.refin,
1455 CRC64_ECMA_182.xorout,
1456 CRC64_ECMA_182.check,
1457 )
1458 }
1459
1460 #[test]
1461 fn test_crc_keys_storage_fold_256() {
1462 let test_keys = [
1463 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1464 ];
1465 let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1466
1467 for i in 0..23 {
1469 assert_eq!(storage.get_key(i), test_keys[i]);
1470 }
1471
1472 assert_eq!(storage.get_key(23), 0);
1474 assert_eq!(storage.get_key(24), 0);
1475 assert_eq!(storage.get_key(100), 0);
1476
1477 assert_eq!(storage.key_count(), 23);
1479 }
1480
1481 #[test]
1482 fn test_crc_keys_storage_future_test() {
1483 let test_keys = [
1484 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1485 25,
1486 ];
1487 let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1488
1489 for i in 0..25 {
1491 assert_eq!(storage.get_key(i), test_keys[i]);
1492 }
1493
1494 assert_eq!(storage.get_key(25), 0);
1496 assert_eq!(storage.get_key(26), 0);
1497 assert_eq!(storage.get_key(100), 0);
1498
1499 assert_eq!(storage.key_count(), 25);
1501 }
1502
1503 #[test]
1504 fn test_crc_params_safe_accessors() {
1505 let test_keys = [
1507 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1508 ];
1509 let params = CrcParams {
1510 algorithm: CrcAlgorithm::Crc32IsoHdlc,
1511 name: "test",
1512 width: 32,
1513 poly: 0x04C11DB7,
1514 init: 0xFFFFFFFF,
1515 refin: true,
1516 refout: true,
1517 xorout: 0xFFFFFFFF,
1518 check: 0xCBF43926,
1519 keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1520 };
1521
1522 for i in 0..23 {
1524 assert_eq!(params.get_key(i), test_keys[i]);
1525 assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1526 }
1527
1528 assert_eq!(params.get_key(23), 0);
1530 assert_eq!(params.get_key(24), 0);
1531 assert_eq!(params.get_key(100), 0);
1532
1533 assert_eq!(params.get_key_checked(23), None);
1534 assert_eq!(params.get_key_checked(24), None);
1535 assert_eq!(params.get_key_checked(100), None);
1536
1537 assert_eq!(params.key_count(), 23);
1539 }
1540
1541 #[test]
1542 fn test_crc_keys_storage_const_constructors() {
1543 const TEST_KEYS_23: [u64; 23] = [1; 23];
1545 const TEST_KEYS_25: [u64; 25] = [2; 25];
1546
1547 const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1548 const STORAGE_FUTURE: CrcKeysStorage =
1549 CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1550
1551 assert_eq!(STORAGE_256.get_key(0), 1);
1553 assert_eq!(STORAGE_256.key_count(), 23);
1554
1555 assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1556 assert_eq!(STORAGE_FUTURE.key_count(), 25);
1557 }
1558
1559 #[test]
1560 fn test_crc_keys_storage_bounds_safety() {
1561 let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1562 let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1563
1564 assert_eq!(storage_256.get_key(22), 42); assert_eq!(storage_256.get_key(23), 0); assert_eq!(storage_future.get_key(24), 84); assert_eq!(storage_future.get_key(25), 0); assert_eq!(storage_256.get_key(usize::MAX), 0);
1573 assert_eq!(storage_future.get_key(usize::MAX), 0);
1574 }
1575}