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(target_arch = "aarch64")]
868 {
869 use std::arch::is_aarch64_feature_detected;
870 if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") {
871 return fusion::crc32_iscsi(state as u32, data) as u64;
872 }
873 }
874
875 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
877 {
878 use std::arch::is_x86_feature_detected;
879 if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") {
880 return fusion::crc32_iscsi(state as u32, data) as u64;
881 }
882 }
883
884 Calculator::calculate(state, data, _params)
887}
888
889#[inline(always)]
895fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
896 #[cfg(target_arch = "aarch64")]
898 {
899 use std::arch::is_aarch64_feature_detected;
900 if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") {
901 return fusion::crc32_iso_hdlc(state as u32, data) as u64;
902 }
903 }
904
905 Calculator::calculate(state, data, _params)
908}
909
910#[cfg(test)]
911mod lib {
912 #![allow(unused)]
913
914 use super::*;
915 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
916 use crate::test::enums::AnyCrcTestConfig;
917 use cbindgen::Language::C;
918 use cbindgen::Style::Both;
919 use rand::{rng, Rng};
920 use std::fs::{read, write};
921
922 #[test]
923 fn test_checksum_check() {
924 for config in TEST_ALL_CONFIGS {
925 assert_eq!(
926 checksum(config.get_algorithm(), TEST_CHECK_STRING),
927 config.get_check()
928 );
929 }
930 }
931
932 #[test]
933 fn test_checksum_reference() {
934 for config in TEST_ALL_CONFIGS {
935 assert_eq!(
936 checksum(config.get_algorithm(), TEST_CHECK_STRING),
937 config.checksum_with_reference(TEST_CHECK_STRING)
938 );
939 }
940 }
941
942 #[test]
943 fn test_checksum_with_custom_params() {
944 crate::cache::clear_cache();
945
946 assert_eq!(
948 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
949 CRC32_ISCSI.check,
950 );
951
952 assert_eq!(
954 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
955 CRC32_BZIP2.check,
956 );
957
958 assert_eq!(
960 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
961 CRC64_NVME.check,
962 );
963
964 assert_eq!(
966 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
967 CRC64_ECMA_182.check,
968 );
969 }
970
971 #[test]
972 fn test_get_custom_params() {
973 crate::cache::clear_cache();
974
975 assert_eq!(
976 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
977 CRC32_ISCSI.check,
978 );
979
980 assert_eq!(
981 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
982 CRC32_BZIP2.check,
983 );
984
985 assert_eq!(
986 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
987 CRC64_NVME.check,
988 );
989
990 assert_eq!(
991 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
992 CRC64_ECMA_182.check,
993 );
994 }
995
996 #[test]
997 fn test_get_calculator_target_format() {
998 let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
999
1000 assert!(!target.is_empty());
1002
1003 let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
1005 assert!(
1006 valid_prefixes
1007 .iter()
1008 .any(|prefix| target.starts_with(prefix)),
1009 "Target '{}' should start with a valid architecture prefix",
1010 target
1011 );
1012
1013 let parts: Vec<&str> = target.split('-').collect();
1015 assert!(
1016 parts.len() >= 3,
1017 "Target '{}' should have at least 3 parts: architecture-family-features",
1018 target
1019 );
1020 }
1021
1022 #[test]
1023 fn test_get_calculator_target_consistency() {
1024 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1026 let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1027 let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1028
1029 assert_eq!(
1030 target1, target2,
1031 "Target should be consistent across different CRC-32 algorithms"
1032 );
1033 assert_eq!(
1034 target1, target3,
1035 "Target should be consistent across CRC-32 and CRC-64 algorithms"
1036 );
1037 }
1038
1039 #[test]
1040 fn test_get_calculator_target_uses_cached_detection() {
1041 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1046 let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1047
1048 assert_eq!(
1049 target1, target2,
1050 "Cached detection should return identical results"
1051 );
1052 }
1053
1054 #[test]
1055 fn test_digest_updates_check() {
1056 for config in TEST_ALL_CONFIGS {
1057 check_digest(Digest::new(config.get_algorithm()), config.get_check());
1058 }
1059 }
1060
1061 #[test]
1062 fn test_digest_updates_check_with_custom_params() {
1063 crate::cache::clear_cache();
1064
1065 check_digest(
1067 Digest::new_with_params(get_custom_crc32_reflected()),
1068 CRC32_ISCSI.check,
1069 );
1070
1071 check_digest(
1073 Digest::new_with_params(get_custom_crc32_forward()),
1074 CRC32_BZIP2.check,
1075 );
1076
1077 check_digest(
1079 Digest::new_with_params(get_custom_crc64_reflected()),
1080 CRC64_NVME.check,
1081 );
1082
1083 check_digest(
1085 Digest::new_with_params(get_custom_crc64_forward()),
1086 CRC64_ECMA_182.check,
1087 );
1088 }
1089
1090 fn check_digest(mut digest: Digest, check: u64) {
1091 digest.update(b"123");
1092 digest.update(b"456");
1093 digest.update(b"789");
1094 assert_eq!(digest.finalize(), check,);
1095 }
1096
1097 #[test]
1098 fn test_1024_length() {
1099 for config in TEST_ALL_CONFIGS {
1100 test_length(1024, config);
1101 }
1102 }
1103
1104 #[test]
1107 #[cfg_attr(miri, ignore)]
1108 fn test_small_all_lengths() {
1109 for config in TEST_ALL_CONFIGS {
1110 for len in 1..=255 {
1112 test_length(len, config);
1113 }
1114 }
1115 }
1116
1117 #[test]
1120 #[cfg_attr(miri, ignore)]
1121 fn test_medium_lengths() {
1122 for config in TEST_ALL_CONFIGS {
1123 for len in 256..=1024 {
1125 test_length(len, config);
1126 }
1127 }
1128 }
1129
1130 #[test]
1133 #[cfg_attr(miri, ignore)]
1134 fn test_large_lengths() {
1135 for config in TEST_ALL_CONFIGS {
1136 for len in 1048575..1048577 {
1138 test_length(len, config);
1139 }
1140 }
1141 }
1142
1143 fn test_length(length: usize, config: &AnyCrcTestConfig) {
1144 let mut data = vec![0u8; length];
1145 rng().fill(&mut data[..]);
1146
1147 let expected = config.checksum_with_reference(&data);
1149
1150 let result = checksum(config.get_algorithm(), &data);
1151
1152 assert_eq!(
1153 result,
1154 expected,
1155 "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1156 config.get_algorithm(),
1157 length,
1158 expected,
1159 result
1160 );
1161 }
1162
1163 #[test]
1164 fn test_combine() {
1165 for config in TEST_ALL_CONFIGS {
1166 let algorithm = config.get_algorithm();
1167 let check = config.get_check();
1168
1169 let checksum1 = checksum(algorithm, "1234".as_ref());
1171 let checksum2 = checksum(algorithm, "56789".as_ref());
1172
1173 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
1175
1176 let mut digest1 = Digest::new(algorithm);
1178 digest1.update("1234".as_ref());
1179
1180 let mut digest2 = Digest::new(algorithm);
1181 digest2.update("56789".as_ref());
1182
1183 digest1.combine(&digest2);
1184
1185 assert_eq!(digest1.finalize(), check)
1186 }
1187 }
1188
1189 #[test]
1190 fn test_combine_with_custom_params() {
1191 crate::cache::clear_cache();
1192
1193 let crc32_params = get_custom_crc32_reflected();
1195 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1196 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1197 assert_eq!(
1198 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1199 CRC32_ISCSI.check,
1200 );
1201
1202 let crc32_params = get_custom_crc32_forward();
1204 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1205 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1206 assert_eq!(
1207 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1208 CRC32_BZIP2.check,
1209 );
1210
1211 let crc64_params = get_custom_crc64_reflected();
1213 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1214 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1215 assert_eq!(
1216 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1217 CRC64_NVME.check,
1218 );
1219
1220 let crc64_params = get_custom_crc64_forward();
1222 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1223 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1224 assert_eq!(
1225 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1226 CRC64_ECMA_182.check,
1227 );
1228 }
1229
1230 #[test]
1233 #[cfg_attr(miri, ignore)]
1234 fn test_checksum_file() {
1235 let test_file_path = "test/test_crc32_hash_file.bin";
1237 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1239 eprintln!("Skipping test due to write error: {}", e);
1240 return;
1241 }
1242
1243 for config in TEST_ALL_CONFIGS {
1244 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1245 assert_eq!(result, config.checksum_with_reference(&data));
1246 }
1247
1248 std::fs::remove_file(test_file_path).unwrap();
1249 }
1250
1251 #[test]
1254 #[cfg_attr(miri, ignore)]
1255 fn test_checksum_file_with_custom_params() {
1256 crate::cache::clear_cache();
1257
1258 let test_file_path = "test/test_crc32_hash_file_custom.bin";
1260 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1262 eprintln!("Skipping test due to write error: {}", e);
1263 return;
1264 }
1265
1266 check_file(
1268 get_custom_crc32_reflected(),
1269 test_file_path,
1270 CRC32_ISCSI.check,
1271 );
1272
1273 check_file(
1275 get_custom_crc32_forward(),
1276 test_file_path,
1277 CRC32_BZIP2.check,
1278 );
1279
1280 check_file(
1282 get_custom_crc64_reflected(),
1283 test_file_path,
1284 CRC64_NVME.check,
1285 );
1286
1287 check_file(
1289 get_custom_crc64_forward(),
1290 test_file_path,
1291 CRC64_ECMA_182.check,
1292 );
1293
1294 std::fs::remove_file(test_file_path).unwrap();
1295 }
1296
1297 fn check_file(params: CrcParams, file_path: &str, check: u64) {
1298 let result = checksum_file_with_params(params, file_path, None).unwrap();
1299 assert_eq!(result, check);
1300 }
1301
1302 #[test]
1305 #[cfg_attr(miri, ignore)]
1306 fn test_writer() {
1307 let test_file_path = "test/test_crc32_writer_file.bin";
1309 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
1311 eprintln!("Skipping test due to write error: {}", e);
1312 return;
1313 }
1314
1315 for config in TEST_ALL_CONFIGS {
1316 let mut digest = Digest::new(config.get_algorithm());
1317 let mut file = File::open(test_file_path).unwrap();
1318 std::io::copy(&mut file, &mut digest).unwrap();
1319 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1320 }
1321
1322 std::fs::remove_file(test_file_path).unwrap();
1323 }
1324 #[test]
1325 fn test_digest_reset() {
1326 for config in TEST_ALL_CONFIGS {
1327 let mut digest = Digest::new(config.get_algorithm());
1328 digest.update(b"42");
1329 digest.reset();
1330 digest.update(TEST_CHECK_STRING);
1331 assert_eq!(digest.finalize(), config.get_check());
1332 }
1333 }
1334
1335 #[test]
1336 fn test_digest_finalize_reset() {
1337 for config in TEST_ALL_CONFIGS {
1338 let check = config.get_check();
1339
1340 let mut digest = Digest::new(config.get_algorithm());
1341 digest.update(TEST_CHECK_STRING);
1342 assert_eq!(digest.finalize_reset(), check);
1343
1344 digest.update(TEST_CHECK_STRING);
1345 assert_eq!(digest.finalize(), check);
1346 }
1347 }
1348
1349 #[test]
1350 fn test_digest_finalize_into() {
1351 for config in TEST_ALL_CONFIGS {
1352 let mut digest = Digest::new(config.get_algorithm());
1353 digest.update(TEST_CHECK_STRING);
1354
1355 match digest.params.width {
1356 32 => {
1357 let mut output = [0u8; 4];
1358 digest.finalize_into(&mut output).unwrap();
1359 let result = u32::from_be_bytes(output) as u64;
1360 assert_eq!(result, config.get_check());
1361 }
1362 64 => {
1363 let mut output = [0u8; 8];
1364 digest.finalize_into(&mut output).unwrap();
1365 let result = u64::from_be_bytes(output);
1366 assert_eq!(result, config.get_check());
1367 }
1368 _ => panic!("Unsupported CRC width"),
1369 }
1370 }
1371 }
1372
1373 #[test]
1374 fn test_digest_finalize_into_reset() {
1375 for config in TEST_ALL_CONFIGS {
1376 let mut digest = Digest::new(config.get_algorithm());
1377 digest.update(TEST_CHECK_STRING);
1378
1379 let mut output: Vec<u8> = match digest.params.width {
1380 32 => vec![0u8; 4],
1381 64 => vec![0u8; 8],
1382 _ => panic!("Unsupported CRC width"),
1383 };
1384
1385 digest.finalize_into_reset(&mut output).unwrap();
1386 let result = match output.len() {
1387 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1388 8 => u64::from_be_bytes(output.try_into().unwrap()),
1389 _ => panic!("Unsupported CRC width"),
1390 };
1391 assert_eq!(result, config.get_check());
1392
1393 digest.update(TEST_CHECK_STRING);
1394 assert_eq!(digest.finalize(), config.get_check());
1395 }
1396 }
1397
1398 #[test]
1400 #[cfg_attr(miri, ignore)]
1401 fn test_ffi_header() -> Result<(), String> {
1402 #[cfg(target_os = "windows")]
1403 {
1404 eprintln!("Skipping test on Windows");
1406
1407 return Ok(());
1408 }
1409
1410 const HEADER: &str = "libcrc_fast.h";
1411
1412 let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1413
1414 let mut expected = Vec::new();
1415 cbindgen::Builder::new()
1416 .with_crate(crate_dir)
1417 .with_include_guard("CRC_FAST_H")
1418 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1419 .exclude_item("crc32_iscsi_impl")
1421 .exclude_item("crc32_iso_hdlc_impl")
1422 .exclude_item("get_iscsi_target")
1423 .exclude_item("get_iso_hdlc_target")
1424 .exclude_item("ISO_HDLC_TARGET")
1425 .exclude_item("ISCSI_TARGET")
1426 .exclude_item("CrcParams")
1427 .rename_item("Digest", "CrcFastDigest")
1428 .with_style(Both)
1429 .with_language(C)
1431 .with_cpp_compat(true)
1433 .generate()
1434 .map_err(|error| error.to_string())?
1435 .write(&mut expected);
1436
1437 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1440
1441 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1443 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1444
1445 expected = cleaned_content.into_bytes();
1447
1448 let actual = read(HEADER).map_err(|error| error.to_string())?;
1449
1450 if expected != actual {
1451 write(HEADER, expected).map_err(|error| error.to_string())?;
1452 return Err(format!(
1453 "{HEADER} is not up-to-date, commit the generated file and try again"
1454 ));
1455 }
1456
1457 Ok(())
1458 }
1459
1460 fn get_custom_crc32_reflected() -> CrcParams {
1461 CrcParams::new(
1462 "Custom CRC-32/ISCSI",
1463 32,
1464 CRC32_ISCSI.poly,
1465 CRC32_ISCSI.init,
1466 CRC32_ISCSI.refin,
1467 CRC32_ISCSI.xorout,
1468 CRC32_ISCSI.check,
1469 )
1470 }
1471
1472 fn get_custom_crc32_forward() -> CrcParams {
1473 CrcParams::new(
1474 "Custom CRC-32/BZIP2",
1475 32,
1476 CRC32_BZIP2.poly,
1477 CRC32_BZIP2.init,
1478 CRC32_BZIP2.refin,
1479 CRC32_BZIP2.xorout,
1480 CRC32_BZIP2.check,
1481 )
1482 }
1483
1484 fn get_custom_crc64_reflected() -> CrcParams {
1485 CrcParams::new(
1486 "Custom CRC-64/NVME",
1487 64,
1488 CRC64_NVME.poly,
1489 CRC64_NVME.init,
1490 CRC64_NVME.refin,
1491 CRC64_NVME.xorout,
1492 CRC64_NVME.check,
1493 )
1494 }
1495
1496 fn get_custom_crc64_forward() -> CrcParams {
1497 CrcParams::new(
1498 "Custom CRC-64/ECMA-182",
1499 64,
1500 CRC64_ECMA_182.poly,
1501 CRC64_ECMA_182.init,
1502 CRC64_ECMA_182.refin,
1503 CRC64_ECMA_182.xorout,
1504 CRC64_ECMA_182.check,
1505 )
1506 }
1507
1508 #[test]
1509 fn test_crc_keys_storage_fold_256() {
1510 let test_keys = [
1511 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1512 ];
1513 let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1514
1515 for i in 0..23 {
1517 assert_eq!(storage.get_key(i), test_keys[i]);
1518 }
1519
1520 assert_eq!(storage.get_key(23), 0);
1522 assert_eq!(storage.get_key(24), 0);
1523 assert_eq!(storage.get_key(100), 0);
1524
1525 assert_eq!(storage.key_count(), 23);
1527 }
1528
1529 #[test]
1530 fn test_crc_keys_storage_future_test() {
1531 let test_keys = [
1532 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1533 25,
1534 ];
1535 let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1536
1537 for i in 0..25 {
1539 assert_eq!(storage.get_key(i), test_keys[i]);
1540 }
1541
1542 assert_eq!(storage.get_key(25), 0);
1544 assert_eq!(storage.get_key(26), 0);
1545 assert_eq!(storage.get_key(100), 0);
1546
1547 assert_eq!(storage.key_count(), 25);
1549 }
1550
1551 #[test]
1552 fn test_crc_params_safe_accessors() {
1553 let test_keys = [
1555 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1556 ];
1557 let params = CrcParams {
1558 algorithm: CrcAlgorithm::Crc32IsoHdlc,
1559 name: "test",
1560 width: 32,
1561 poly: 0x04C11DB7,
1562 init: 0xFFFFFFFF,
1563 refin: true,
1564 refout: true,
1565 xorout: 0xFFFFFFFF,
1566 check: 0xCBF43926,
1567 keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1568 };
1569
1570 for i in 0..23 {
1572 assert_eq!(params.get_key(i), test_keys[i]);
1573 assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1574 }
1575
1576 assert_eq!(params.get_key(23), 0);
1578 assert_eq!(params.get_key(24), 0);
1579 assert_eq!(params.get_key(100), 0);
1580
1581 assert_eq!(params.get_key_checked(23), None);
1582 assert_eq!(params.get_key_checked(24), None);
1583 assert_eq!(params.get_key_checked(100), None);
1584
1585 assert_eq!(params.key_count(), 23);
1587 }
1588
1589 #[test]
1590 fn test_crc_keys_storage_const_constructors() {
1591 const TEST_KEYS_23: [u64; 23] = [1; 23];
1593 const TEST_KEYS_25: [u64; 25] = [2; 25];
1594
1595 const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1596 const STORAGE_FUTURE: CrcKeysStorage =
1597 CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1598
1599 assert_eq!(STORAGE_256.get_key(0), 1);
1601 assert_eq!(STORAGE_256.key_count(), 23);
1602
1603 assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1604 assert_eq!(STORAGE_FUTURE.key_count(), 25);
1605 }
1606
1607 #[test]
1608 fn test_crc_keys_storage_bounds_safety() {
1609 let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1610 let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1611
1612 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);
1621 assert_eq!(storage_future.get_key(usize::MAX), 0);
1622 }
1623}