1#![cfg_attr(not(feature = "std"), no_std)]
4
5use crate::crc32::consts::{
136 CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
137 CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
138};
139
140#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
141use crate::crc32::fusion;
142
143use crate::crc64::consts::{
144 CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
145};
146use crate::structs::Calculator;
147use crate::traits::CrcCalculator;
148use digest::{DynDigest, InvalidBufferSize};
149
150#[cfg(feature = "std")]
151use std::fs::File;
152#[cfg(feature = "std")]
153use std::io::{Read, Write};
154
155mod algorithm;
156mod arch;
157mod cache;
158mod combine;
159mod consts;
160mod crc32;
161mod crc64;
162mod enums;
163mod feature_detection;
164mod ffi;
165mod generate;
166mod structs;
167mod test;
168mod traits;
169
170#[derive(Debug, Clone, Copy, PartialEq)]
172pub enum CrcAlgorithm {
173 Crc32Aixm,
174 Crc32Autosar,
175 Crc32Base91D,
176 Crc32Bzip2,
177 Crc32CdRomEdc,
178 Crc32Cksum,
179 Crc32Custom, Crc32Iscsi,
181 Crc32IsoHdlc,
182 Crc32Jamcrc,
183 Crc32Mef,
184 Crc32Mpeg2,
185 Crc32Xfer,
186 Crc64Custom, Crc64Ecma182,
188 Crc64GoIso,
189 Crc64Ms,
190 Crc64Nvme,
191 Crc64Redis,
192 Crc64We,
193 Crc64Xz,
194}
195
196#[derive(Clone, Copy, Debug, PartialEq)]
200pub enum CrcKeysStorage {
201 KeysFold256([u64; 23]),
203 KeysFutureTest([u64; 25]),
205}
206
207impl CrcKeysStorage {
208 #[inline(always)]
210 const fn get_key(self, index: usize) -> u64 {
211 match self {
212 CrcKeysStorage::KeysFold256(keys) => {
213 if index < 23 {
214 keys[index]
215 } else {
216 0
217 }
218 }
219 CrcKeysStorage::KeysFutureTest(keys) => {
220 if index < 25 {
221 keys[index]
222 } else {
223 0
224 }
225 }
226 }
227 }
228
229 #[inline(always)]
231 const fn key_count(self) -> usize {
232 match self {
233 CrcKeysStorage::KeysFold256(_) => 23,
234 CrcKeysStorage::KeysFutureTest(_) => 25,
235 }
236 }
237
238 #[inline(always)]
240 const fn from_keys_fold_256(keys: [u64; 23]) -> Self {
241 CrcKeysStorage::KeysFold256(keys)
242 }
243
244 #[inline(always)]
246 #[allow(dead_code)] const fn from_keys_fold_future_test(keys: [u64; 25]) -> Self {
248 CrcKeysStorage::KeysFutureTest(keys)
249 }
250
251 #[inline(always)]
255 pub fn to_keys_array_23(self) -> [u64; 23] {
256 match self {
257 CrcKeysStorage::KeysFold256(keys) => keys,
258 CrcKeysStorage::KeysFutureTest(keys) => {
259 let mut result = [0u64; 23];
260 result.copy_from_slice(&keys[..23]);
261 result
262 }
263 }
264 }
265}
266
267impl PartialEq<[u64; 23]> for CrcKeysStorage {
269 fn eq(&self, other: &[u64; 23]) -> bool {
270 self.to_keys_array_23() == *other
271 }
272}
273
274impl PartialEq<CrcKeysStorage> for [u64; 23] {
275 fn eq(&self, other: &CrcKeysStorage) -> bool {
276 *self == other.to_keys_array_23()
277 }
278}
279
280#[derive(Clone, Copy, Debug)]
282pub struct CrcParams {
283 pub algorithm: CrcAlgorithm,
284 pub name: &'static str,
285 pub width: u8,
286 pub poly: u64,
287 pub init: u64,
288 pub refin: bool,
289 pub refout: bool,
290 pub xorout: u64,
291 pub check: u64,
292 pub keys: CrcKeysStorage,
293}
294
295type CalculatorFn = fn(
304 u64, &[u8], CrcParams, ) -> u64;
308
309#[derive(Copy, Clone, Debug)]
315pub struct Digest {
316 state: u64,
318
319 amount: u64,
321
322 params: CrcParams,
324
325 calculator: CalculatorFn,
327}
328
329impl DynDigest for Digest {
330 #[inline(always)]
331 fn update(&mut self, data: &[u8]) {
332 self.update(data);
333 }
334
335 #[inline(always)]
336 fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
337 if buf.len() != self.output_size() {
338 return Err(InvalidBufferSize);
339 }
340
341 let result = self.finalize();
342 let bytes = if self.output_size() == 4 {
343 result.to_be_bytes()[4..].to_vec() } else {
345 result.to_be_bytes().to_vec() };
347 buf.copy_from_slice(&bytes[..self.output_size()]);
348
349 Ok(())
350 }
351
352 #[inline(always)]
353 fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
354 if out.len() != self.output_size() {
355 return Err(InvalidBufferSize);
356 }
357 let result = self.finalize();
358 self.reset();
359 let bytes = if self.output_size() == 4 {
360 result.to_be_bytes()[4..].to_vec() } else {
362 result.to_be_bytes().to_vec() };
364 out.copy_from_slice(&bytes[..self.output_size()]);
365 Ok(())
366 }
367
368 #[inline(always)]
369 fn reset(&mut self) {
370 self.reset();
371 }
372
373 #[inline(always)]
374 fn output_size(&self) -> usize {
375 self.params.width as usize / 8
376 }
377
378 fn box_clone(&self) -> Box<dyn DynDigest> {
379 Box::new(*self)
380 }
381}
382
383impl Digest {
384 #[inline(always)]
398 pub fn new(algorithm: CrcAlgorithm) -> Self {
399 let (calculator, params) = get_calculator_params(algorithm);
400
401 Self {
402 state: params.init,
403 amount: 0,
404 params,
405 calculator,
406 }
407 }
408
409 #[inline(always)]
433 pub fn new_with_init_state(algorithm: CrcAlgorithm, init_state: u64) -> Self {
434 let (calculator, params) = get_calculator_params(algorithm);
435
436 Self {
437 state: init_state,
438 amount: 0,
439 params,
440 calculator,
441 }
442 }
443
444 #[inline(always)]
469 pub fn new_with_params(params: CrcParams) -> Self {
470 let calculator = Calculator::calculate as CalculatorFn;
471
472 Self {
473 state: params.init,
474 amount: 0,
475 params,
476 calculator,
477 }
478 }
479
480 #[inline(always)]
482 pub fn update(&mut self, data: &[u8]) {
483 self.state = (self.calculator)(self.state, data, self.params);
484 self.amount += data.len() as u64;
485 }
486
487 #[inline(always)]
489 pub fn finalize(&self) -> u64 {
490 self.state ^ self.params.xorout
491 }
492
493 #[inline(always)]
495 pub fn finalize_reset(&mut self) -> u64 {
496 let result = self.finalize();
497 self.reset();
498
499 result
500 }
501
502 #[inline(always)]
504 pub fn reset(&mut self) {
505 self.state = self.params.init;
506 self.amount = 0;
507 }
508
509 #[inline(always)]
511 pub fn combine(&mut self, other: &Self) {
512 self.amount += other.amount;
513 let other_crc = other.finalize();
514
515 self.state = combine::checksums(
518 self.state ^ self.params.xorout,
519 other_crc,
520 other.amount,
521 self.params,
522 ) ^ self.params.xorout;
523 }
524
525 #[inline(always)]
527 pub fn get_amount(&self) -> u64 {
528 self.amount
529 }
530
531 #[inline(always)]
548 pub fn get_state(&self) -> u64 {
549 self.state
550 }
551}
552
553#[cfg(feature = "std")]
554impl Write for Digest {
555 #[inline(always)]
556 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
557 self.update(buf);
558 Ok(buf.len())
559 }
560
561 #[inline(always)]
562 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
563 let len: usize = bufs
564 .iter()
565 .map(|buf| {
566 self.update(buf);
567 buf.len()
568 })
569 .sum();
570
571 Ok(len)
572 }
573
574 #[inline(always)]
575 fn flush(&mut self) -> std::io::Result<()> {
576 Ok(())
577 }
578
579 #[inline(always)]
580 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
581 self.update(buf);
582
583 Ok(())
584 }
585}
586
587#[inline(always)]
596pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
597 let (calculator, params) = get_calculator_params(algorithm);
598
599 calculator(params.init, buf, params) ^ params.xorout
600}
601
602pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 {
625 let calculator = Calculator::calculate as CalculatorFn;
626
627 calculator(params.init, buf, params) ^ params.xorout
628}
629
630#[cfg(feature = "std")]
653#[inline(always)]
654pub fn checksum_file(
655 algorithm: CrcAlgorithm,
656 path: &str,
657 chunk_size: Option<usize>,
658) -> Result<u64, std::io::Error> {
659 checksum_file_with_digest(Digest::new(algorithm), path, chunk_size)
660}
661
662#[cfg(feature = "std")]
696pub fn checksum_file_with_params(
697 params: CrcParams,
698 path: &str,
699 chunk_size: Option<usize>,
700) -> Result<u64, std::io::Error> {
701 checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size)
702}
703
704#[cfg(feature = "std")]
710fn checksum_file_with_digest(
711 mut digest: Digest,
712 path: &str,
713 chunk_size: Option<usize>,
714) -> Result<u64, std::io::Error> {
715 let mut file = File::open(path)?;
716
717 let chunk_size = chunk_size.unwrap_or(524288);
723
724 let mut buf = vec![0; chunk_size];
725
726 while let Ok(n) = file.read(&mut buf) {
727 if n == 0 {
728 break;
729 }
730 digest.update(&buf[..n]);
731 }
732
733 Ok(digest.finalize())
734}
735
736#[inline(always)]
749pub fn checksum_combine(
750 algorithm: CrcAlgorithm,
751 checksum1: u64,
752 checksum2: u64,
753 checksum2_len: u64,
754) -> u64 {
755 let params = get_calculator_params(algorithm).1;
756
757 combine::checksums(checksum1, checksum2, checksum2_len, params)
758}
759
760pub fn checksum_combine_with_params(
785 params: CrcParams,
786 checksum1: u64,
787 checksum2: u64,
788 checksum2_len: u64,
789) -> u64 {
790 combine::checksums(checksum1, checksum2, checksum2_len, params)
791}
792
793pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
823 use crate::feature_detection::get_arch_ops;
824
825 let arch_ops = get_arch_ops();
826 arch_ops.get_target_string()
827}
828
829#[inline(always)]
831fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
832 match algorithm {
833 CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
834 CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
835 CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
836 CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
837 CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
838 CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
839 CrcAlgorithm::Crc32Custom => {
840 panic!("Custom CRC-32 requires parameters via CrcParams::new()")
841 }
842 CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
843 CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
844 CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
845 CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
846 CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
847 CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
848 CrcAlgorithm::Crc64Custom => {
849 panic!("Custom CRC-64 requires parameters via CrcParams::new()")
850 }
851 CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
852 CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
853 CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
854 CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
855 CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
856 CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
857 CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
858 }
859}
860
861#[inline(always)]
866fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
867 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))]
869 return fusion::crc32_iscsi(state as u32, data) as u64;
870
871 #[cfg(all(
872 not(target_arch = "aarch64"),
873 not(target_arch = "x86_64"),
874 not(target_arch = "x86")
875 ))]
876 Calculator::calculate(state, data, _params)
878}
879
880#[inline(always)]
886fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
887 #[cfg(target_arch = "aarch64")]
889 return fusion::crc32_iso_hdlc(state as u32, data) as u64;
890
891 #[cfg(not(target_arch = "aarch64"))]
894 Calculator::calculate(state, data, _params)
895}
896
897#[cfg(test)]
898mod lib {
899 #![allow(unused)]
900
901 use super::*;
902 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
903 use crate::test::enums::AnyCrcTestConfig;
904 use cbindgen::Language::C;
905 use cbindgen::Style::Both;
906 use rand::{rng, Rng};
907 use std::fs::{read, write};
908
909 #[test]
910 fn test_checksum_check() {
911 for config in TEST_ALL_CONFIGS {
912 assert_eq!(
913 checksum(config.get_algorithm(), TEST_CHECK_STRING),
914 config.get_check()
915 );
916 }
917 }
918
919 #[test]
920 fn test_checksum_reference() {
921 for config in TEST_ALL_CONFIGS {
922 assert_eq!(
923 checksum(config.get_algorithm(), TEST_CHECK_STRING),
924 config.checksum_with_reference(TEST_CHECK_STRING)
925 );
926 }
927 }
928
929 #[test]
930 fn test_checksum_with_custom_params() {
931 crate::cache::clear_cache();
932
933 assert_eq!(
935 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
936 CRC32_ISCSI.check,
937 );
938
939 assert_eq!(
941 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
942 CRC32_BZIP2.check,
943 );
944
945 assert_eq!(
947 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
948 CRC64_NVME.check,
949 );
950
951 assert_eq!(
953 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
954 CRC64_ECMA_182.check,
955 );
956 }
957
958 #[test]
959 fn test_get_custom_params() {
960 crate::cache::clear_cache();
961
962 assert_eq!(
963 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
964 CRC32_ISCSI.check,
965 );
966
967 assert_eq!(
968 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
969 CRC32_BZIP2.check,
970 );
971
972 assert_eq!(
973 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
974 CRC64_NVME.check,
975 );
976
977 assert_eq!(
978 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
979 CRC64_ECMA_182.check,
980 );
981 }
982
983 #[test]
984 fn test_get_calculator_target_format() {
985 let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
986
987 assert!(!target.is_empty());
989
990 let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
992 assert!(
993 valid_prefixes
994 .iter()
995 .any(|prefix| target.starts_with(prefix)),
996 "Target '{}' should start with a valid architecture prefix",
997 target
998 );
999
1000 let parts: Vec<&str> = target.split('-').collect();
1002 assert!(
1003 parts.len() >= 3,
1004 "Target '{}' should have at least 3 parts: architecture-family-features",
1005 target
1006 );
1007 }
1008
1009 #[test]
1010 fn test_get_calculator_target_consistency() {
1011 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1013 let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1014 let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1015
1016 assert_eq!(
1017 target1, target2,
1018 "Target should be consistent across different CRC-32 algorithms"
1019 );
1020 assert_eq!(
1021 target1, target3,
1022 "Target should be consistent across CRC-32 and CRC-64 algorithms"
1023 );
1024 }
1025
1026 #[test]
1027 fn test_get_calculator_target_uses_cached_detection() {
1028 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1033 let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1034
1035 assert_eq!(
1036 target1, target2,
1037 "Cached detection should return identical results"
1038 );
1039 }
1040
1041 #[test]
1042 fn test_digest_updates_check() {
1043 for config in TEST_ALL_CONFIGS {
1044 check_digest(Digest::new(config.get_algorithm()), config.get_check());
1045 }
1046 }
1047
1048 #[test]
1049 fn test_digest_updates_check_with_custom_params() {
1050 crate::cache::clear_cache();
1051
1052 check_digest(
1054 Digest::new_with_params(get_custom_crc32_reflected()),
1055 CRC32_ISCSI.check,
1056 );
1057
1058 check_digest(
1060 Digest::new_with_params(get_custom_crc32_forward()),
1061 CRC32_BZIP2.check,
1062 );
1063
1064 check_digest(
1066 Digest::new_with_params(get_custom_crc64_reflected()),
1067 CRC64_NVME.check,
1068 );
1069
1070 check_digest(
1072 Digest::new_with_params(get_custom_crc64_forward()),
1073 CRC64_ECMA_182.check,
1074 );
1075 }
1076
1077 fn check_digest(mut digest: Digest, check: u64) {
1078 digest.update(b"123");
1079 digest.update(b"456");
1080 digest.update(b"789");
1081 assert_eq!(digest.finalize(), check,);
1082 }
1083
1084 #[test]
1085 fn test_small_all_lengths() {
1086 for config in TEST_ALL_CONFIGS {
1087 for len in 1..=255 {
1089 test_length(len, config);
1090 }
1091 }
1092 }
1093
1094 #[test]
1095 fn test_medium_lengths() {
1096 for config in TEST_ALL_CONFIGS {
1097 for len in 256..=1024 {
1099 test_length(len, config);
1100 }
1101 }
1102 }
1103
1104 #[test]
1105 fn test_large_lengths() {
1106 for config in TEST_ALL_CONFIGS {
1107 for len in 1048575..1048577 {
1109 test_length(len, config);
1110 }
1111 }
1112 }
1113
1114 fn test_length(length: usize, config: &AnyCrcTestConfig) {
1115 let mut data = vec![0u8; length];
1116 rng().fill(&mut data[..]);
1117
1118 let expected = config.checksum_with_reference(&data);
1120
1121 let result = checksum(config.get_algorithm(), &data);
1122
1123 assert_eq!(
1124 result,
1125 expected,
1126 "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1127 config.get_algorithm(),
1128 length,
1129 expected,
1130 result
1131 );
1132 }
1133
1134 #[test]
1135 fn test_combine() {
1136 for config in TEST_ALL_CONFIGS {
1137 let algorithm = config.get_algorithm();
1138 let check = config.get_check();
1139
1140 let checksum1 = checksum(algorithm, "1234".as_ref());
1142 let checksum2 = checksum(algorithm, "56789".as_ref());
1143
1144 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
1146
1147 let mut digest1 = Digest::new(algorithm);
1149 digest1.update("1234".as_ref());
1150
1151 let mut digest2 = Digest::new(algorithm);
1152 digest2.update("56789".as_ref());
1153
1154 digest1.combine(&digest2);
1155
1156 assert_eq!(digest1.finalize(), check)
1157 }
1158 }
1159
1160 #[test]
1161 fn test_combine_with_custom_params() {
1162 crate::cache::clear_cache();
1163
1164 let crc32_params = get_custom_crc32_reflected();
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_ISCSI.check,
1171 );
1172
1173 let crc32_params = get_custom_crc32_forward();
1175 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1176 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1177 assert_eq!(
1178 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1179 CRC32_BZIP2.check,
1180 );
1181
1182 let crc64_params = get_custom_crc64_reflected();
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_NVME.check,
1189 );
1190
1191 let crc64_params = get_custom_crc64_forward();
1193 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1194 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1195 assert_eq!(
1196 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1197 CRC64_ECMA_182.check,
1198 );
1199 }
1200
1201 #[test]
1202 fn test_checksum_file() {
1203 let test_file_path = "test/test_crc32_hash_file.bin";
1205 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1207 eprintln!("Skipping test due to write error: {}", e);
1208 return;
1209 }
1210
1211 for config in TEST_ALL_CONFIGS {
1212 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1213 assert_eq!(result, config.checksum_with_reference(&data));
1214 }
1215
1216 std::fs::remove_file(test_file_path).unwrap();
1217 }
1218
1219 #[test]
1220 fn test_checksum_file_with_custom_params() {
1221 crate::cache::clear_cache();
1222
1223 let test_file_path = "test/test_crc32_hash_file_custom.bin";
1225 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1227 eprintln!("Skipping test due to write error: {}", e);
1228 return;
1229 }
1230
1231 check_file(
1233 get_custom_crc32_reflected(),
1234 test_file_path,
1235 CRC32_ISCSI.check,
1236 );
1237
1238 check_file(
1240 get_custom_crc32_forward(),
1241 test_file_path,
1242 CRC32_BZIP2.check,
1243 );
1244
1245 check_file(
1247 get_custom_crc64_reflected(),
1248 test_file_path,
1249 CRC64_NVME.check,
1250 );
1251
1252 check_file(
1254 get_custom_crc64_forward(),
1255 test_file_path,
1256 CRC64_ECMA_182.check,
1257 );
1258
1259 std::fs::remove_file(test_file_path).unwrap();
1260 }
1261
1262 fn check_file(params: CrcParams, file_path: &str, check: u64) {
1263 let result = checksum_file_with_params(params, file_path, None).unwrap();
1264 assert_eq!(result, check);
1265 }
1266
1267 #[test]
1268 fn test_writer() {
1269 let test_file_path = "test/test_crc32_writer_file.bin";
1271 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
1273 eprintln!("Skipping test due to write error: {}", e);
1274 return;
1275 }
1276
1277 for config in TEST_ALL_CONFIGS {
1278 let mut digest = Digest::new(config.get_algorithm());
1279 let mut file = File::open(test_file_path).unwrap();
1280 std::io::copy(&mut file, &mut digest).unwrap();
1281 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1282 }
1283
1284 std::fs::remove_file(test_file_path).unwrap();
1285 }
1286 #[test]
1287 fn test_digest_reset() {
1288 for config in TEST_ALL_CONFIGS {
1289 let mut digest = Digest::new(config.get_algorithm());
1290 digest.update(b"42");
1291 digest.reset();
1292 digest.update(TEST_CHECK_STRING);
1293 assert_eq!(digest.finalize(), config.get_check());
1294 }
1295 }
1296
1297 #[test]
1298 fn test_digest_finalize_reset() {
1299 for config in TEST_ALL_CONFIGS {
1300 let check = config.get_check();
1301
1302 let mut digest = Digest::new(config.get_algorithm());
1303 digest.update(TEST_CHECK_STRING);
1304 assert_eq!(digest.finalize_reset(), check);
1305
1306 digest.update(TEST_CHECK_STRING);
1307 assert_eq!(digest.finalize(), check);
1308 }
1309 }
1310
1311 #[test]
1312 fn test_digest_finalize_into() {
1313 for config in TEST_ALL_CONFIGS {
1314 let mut digest = Digest::new(config.get_algorithm());
1315 digest.update(TEST_CHECK_STRING);
1316
1317 match digest.params.width {
1318 32 => {
1319 let mut output = [0u8; 4];
1320 digest.finalize_into(&mut output).unwrap();
1321 let result = u32::from_be_bytes(output) as u64;
1322 assert_eq!(result, config.get_check());
1323 }
1324 64 => {
1325 let mut output = [0u8; 8];
1326 digest.finalize_into(&mut output).unwrap();
1327 let result = u64::from_be_bytes(output);
1328 assert_eq!(result, config.get_check());
1329 }
1330 _ => panic!("Unsupported CRC width"),
1331 }
1332 }
1333 }
1334
1335 #[test]
1336 fn test_digest_finalize_into_reset() {
1337 for config in TEST_ALL_CONFIGS {
1338 let mut digest = Digest::new(config.get_algorithm());
1339 digest.update(TEST_CHECK_STRING);
1340
1341 let mut output: Vec<u8> = match digest.params.width {
1342 32 => vec![0u8; 4],
1343 64 => vec![0u8; 8],
1344 _ => panic!("Unsupported CRC width"),
1345 };
1346
1347 digest.finalize_into_reset(&mut output).unwrap();
1348 let result = match output.len() {
1349 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1350 8 => u64::from_be_bytes(output.try_into().unwrap()),
1351 _ => panic!("Unsupported CRC width"),
1352 };
1353 assert_eq!(result, config.get_check());
1354
1355 digest.update(TEST_CHECK_STRING);
1356 assert_eq!(digest.finalize(), config.get_check());
1357 }
1358 }
1359
1360 #[test]
1362 fn test_ffi_header() -> Result<(), String> {
1363 #[cfg(target_os = "windows")]
1364 {
1365 eprintln!("Skipping test on Windows");
1367
1368 return Ok(());
1369 }
1370
1371 const HEADER: &str = "libcrc_fast.h";
1372
1373 let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1374
1375 let mut expected = Vec::new();
1376 cbindgen::Builder::new()
1377 .with_crate(crate_dir)
1378 .with_include_guard("CRC_FAST_H")
1379 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1380 .exclude_item("crc32_iscsi_impl")
1382 .exclude_item("crc32_iso_hdlc_impl")
1383 .exclude_item("get_iscsi_target")
1384 .exclude_item("get_iso_hdlc_target")
1385 .exclude_item("ISO_HDLC_TARGET")
1386 .exclude_item("ISCSI_TARGET")
1387 .exclude_item("CrcParams")
1388 .rename_item("Digest", "CrcFastDigest")
1389 .with_style(Both)
1390 .with_language(C)
1392 .with_cpp_compat(true)
1394 .generate()
1395 .map_err(|error| error.to_string())?
1396 .write(&mut expected);
1397
1398 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1401
1402 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1404 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1405
1406 expected = cleaned_content.into_bytes();
1408
1409 let actual = read(HEADER).map_err(|error| error.to_string())?;
1410
1411 if expected != actual {
1412 write(HEADER, expected).map_err(|error| error.to_string())?;
1413 return Err(format!(
1414 "{HEADER} is not up-to-date, commit the generated file and try again"
1415 ));
1416 }
1417
1418 Ok(())
1419 }
1420
1421 fn get_custom_crc32_reflected() -> CrcParams {
1422 CrcParams::new(
1423 "Custom CRC-32/ISCSI",
1424 32,
1425 CRC32_ISCSI.poly,
1426 CRC32_ISCSI.init,
1427 CRC32_ISCSI.refin,
1428 CRC32_ISCSI.xorout,
1429 CRC32_ISCSI.check,
1430 )
1431 }
1432
1433 fn get_custom_crc32_forward() -> CrcParams {
1434 CrcParams::new(
1435 "Custom CRC-32/BZIP2",
1436 32,
1437 CRC32_BZIP2.poly,
1438 CRC32_BZIP2.init,
1439 CRC32_BZIP2.refin,
1440 CRC32_BZIP2.xorout,
1441 CRC32_BZIP2.check,
1442 )
1443 }
1444
1445 fn get_custom_crc64_reflected() -> CrcParams {
1446 CrcParams::new(
1447 "Custom CRC-64/NVME",
1448 64,
1449 CRC64_NVME.poly,
1450 CRC64_NVME.init,
1451 CRC64_NVME.refin,
1452 CRC64_NVME.xorout,
1453 CRC64_NVME.check,
1454 )
1455 }
1456
1457 fn get_custom_crc64_forward() -> CrcParams {
1458 CrcParams::new(
1459 "Custom CRC-64/ECMA-182",
1460 64,
1461 CRC64_ECMA_182.poly,
1462 CRC64_ECMA_182.init,
1463 CRC64_ECMA_182.refin,
1464 CRC64_ECMA_182.xorout,
1465 CRC64_ECMA_182.check,
1466 )
1467 }
1468
1469 #[test]
1470 fn test_crc_keys_storage_fold_256() {
1471 let test_keys = [
1472 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1473 ];
1474 let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1475
1476 for i in 0..23 {
1478 assert_eq!(storage.get_key(i), test_keys[i]);
1479 }
1480
1481 assert_eq!(storage.get_key(23), 0);
1483 assert_eq!(storage.get_key(24), 0);
1484 assert_eq!(storage.get_key(100), 0);
1485
1486 assert_eq!(storage.key_count(), 23);
1488 }
1489
1490 #[test]
1491 fn test_crc_keys_storage_future_test() {
1492 let test_keys = [
1493 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1494 25,
1495 ];
1496 let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1497
1498 for i in 0..25 {
1500 assert_eq!(storage.get_key(i), test_keys[i]);
1501 }
1502
1503 assert_eq!(storage.get_key(25), 0);
1505 assert_eq!(storage.get_key(26), 0);
1506 assert_eq!(storage.get_key(100), 0);
1507
1508 assert_eq!(storage.key_count(), 25);
1510 }
1511
1512 #[test]
1513 fn test_crc_params_safe_accessors() {
1514 let test_keys = [
1516 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1517 ];
1518 let params = CrcParams {
1519 algorithm: CrcAlgorithm::Crc32IsoHdlc,
1520 name: "test",
1521 width: 32,
1522 poly: 0x04C11DB7,
1523 init: 0xFFFFFFFF,
1524 refin: true,
1525 refout: true,
1526 xorout: 0xFFFFFFFF,
1527 check: 0xCBF43926,
1528 keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1529 };
1530
1531 for i in 0..23 {
1533 assert_eq!(params.get_key(i), test_keys[i]);
1534 assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1535 }
1536
1537 assert_eq!(params.get_key(23), 0);
1539 assert_eq!(params.get_key(24), 0);
1540 assert_eq!(params.get_key(100), 0);
1541
1542 assert_eq!(params.get_key_checked(23), None);
1543 assert_eq!(params.get_key_checked(24), None);
1544 assert_eq!(params.get_key_checked(100), None);
1545
1546 assert_eq!(params.key_count(), 23);
1548 }
1549
1550 #[test]
1551 fn test_crc_keys_storage_const_constructors() {
1552 const TEST_KEYS_23: [u64; 23] = [1; 23];
1554 const TEST_KEYS_25: [u64; 25] = [2; 25];
1555
1556 const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1557 const STORAGE_FUTURE: CrcKeysStorage =
1558 CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1559
1560 assert_eq!(STORAGE_256.get_key(0), 1);
1562 assert_eq!(STORAGE_256.key_count(), 23);
1563
1564 assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1565 assert_eq!(STORAGE_FUTURE.key_count(), 25);
1566 }
1567
1568 #[test]
1569 fn test_crc_keys_storage_bounds_safety() {
1570 let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1571 let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1572
1573 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);
1582 assert_eq!(storage_future.get_key(usize::MAX), 0);
1583 }
1584}