1#![cfg_attr(not(feature = "std"), no_std)]
4
5#[cfg(all(
146 feature = "panic-handler",
147 not(feature = "std"),
148 not(test),
149 not(doctest)
150))]
151#[panic_handler]
152fn panic(_info: &core::panic::PanicInfo) -> ! {
153 loop {}
154}
155
156#[cfg(all(
159 feature = "panic-handler",
160 feature = "alloc",
161 not(feature = "std"),
162 not(test),
163 not(doctest)
164))]
165#[global_allocator]
166static ALLOCATOR: StubAllocator = StubAllocator;
167
168#[cfg(all(
169 feature = "panic-handler",
170 feature = "alloc",
171 not(feature = "std"),
172 not(test),
173 not(doctest)
174))]
175struct StubAllocator;
176
177#[cfg(all(
178 feature = "panic-handler",
179 feature = "alloc",
180 not(feature = "std"),
181 not(test),
182 not(doctest)
183))]
184unsafe impl core::alloc::GlobalAlloc for StubAllocator {
185 unsafe fn alloc(&self, _layout: core::alloc::Layout) -> *mut u8 {
186 core::ptr::null_mut()
187 }
188 unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {}
189}
190
191use crate::crc32::consts::{
192 CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
193 CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
194};
195
196#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
197#[cfg(feature = "std")]
198use crate::crc32::fusion;
199
200use crate::crc64::consts::{
201 CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
202};
203use crate::structs::Calculator;
204use crate::traits::CrcCalculator;
205#[cfg(feature = "alloc")]
206use digest::DynDigest;
207#[cfg(feature = "alloc")]
208use digest::InvalidBufferSize;
209
210#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
211use crate::feature_detection::get_arch_ops;
212#[cfg(feature = "std")]
213use std::fs::File;
214#[cfg(feature = "std")]
215use std::io::{Read, Write};
216
217#[cfg(all(feature = "alloc", not(feature = "std")))]
218extern crate alloc;
219#[cfg(all(feature = "alloc", not(feature = "std")))]
220use alloc::boxed::Box;
221#[cfg(all(feature = "alloc", not(feature = "std")))]
222use alloc::string::String;
223
224mod algorithm;
225mod arch;
226mod cache;
227mod combine;
228mod consts;
229mod crc32;
230mod crc64;
231mod enums;
232mod feature_detection;
233#[cfg(feature = "ffi")]
234mod ffi;
235mod generate;
236mod structs;
237mod test;
238mod traits;
239
240#[derive(Debug, Clone, Copy, PartialEq)]
242pub enum CrcAlgorithm {
243 Crc32Aixm,
244 Crc32Autosar,
245 Crc32Base91D,
246 Crc32Bzip2,
247 Crc32CdRomEdc,
248 Crc32Cksum,
249 Crc32Custom, Crc32Iscsi,
251 Crc32IsoHdlc,
252 Crc32Jamcrc,
253 Crc32Mef,
254 Crc32Mpeg2,
255 Crc32Xfer,
256 Crc64Custom, Crc64Ecma182,
258 Crc64GoIso,
259 Crc64Ms,
260 Crc64Nvme,
261 Crc64Redis,
262 Crc64We,
263 Crc64Xz,
264}
265
266#[derive(Clone, Copy, Debug, PartialEq)]
270pub enum CrcKeysStorage {
271 KeysFold256([u64; 23]),
273 KeysFutureTest([u64; 25]),
275}
276
277impl CrcKeysStorage {
278 #[inline(always)]
280 const fn get_key(self, index: usize) -> u64 {
281 match self {
282 CrcKeysStorage::KeysFold256(keys) => {
283 if index < 23 {
284 keys[index]
285 } else {
286 0
287 }
288 }
289 CrcKeysStorage::KeysFutureTest(keys) => {
290 if index < 25 {
291 keys[index]
292 } else {
293 0
294 }
295 }
296 }
297 }
298
299 #[inline(always)]
301 const fn key_count(self) -> usize {
302 match self {
303 CrcKeysStorage::KeysFold256(_) => 23,
304 CrcKeysStorage::KeysFutureTest(_) => 25,
305 }
306 }
307
308 #[inline(always)]
310 const fn from_keys_fold_256(keys: [u64; 23]) -> Self {
311 CrcKeysStorage::KeysFold256(keys)
312 }
313
314 #[inline(always)]
316 #[allow(dead_code)] const fn from_keys_fold_future_test(keys: [u64; 25]) -> Self {
318 CrcKeysStorage::KeysFutureTest(keys)
319 }
320
321 #[inline(always)]
325 pub fn to_keys_array_23(self) -> [u64; 23] {
326 match self {
327 CrcKeysStorage::KeysFold256(keys) => keys,
328 CrcKeysStorage::KeysFutureTest(keys) => {
329 let mut result = [0u64; 23];
330 result.copy_from_slice(&keys[..23]);
331 result
332 }
333 }
334 }
335}
336
337impl PartialEq<[u64; 23]> for CrcKeysStorage {
339 fn eq(&self, other: &[u64; 23]) -> bool {
340 self.to_keys_array_23() == *other
341 }
342}
343
344impl PartialEq<CrcKeysStorage> for [u64; 23] {
345 fn eq(&self, other: &CrcKeysStorage) -> bool {
346 *self == other.to_keys_array_23()
347 }
348}
349
350#[derive(Clone, Copy, Debug)]
352pub struct CrcParams {
353 pub algorithm: CrcAlgorithm,
354 pub name: &'static str,
355 pub width: u8,
356 pub poly: u64,
357 pub init: u64,
358 pub refin: bool,
359 pub refout: bool,
360 pub xorout: u64,
361 pub check: u64,
362 pub keys: CrcKeysStorage,
363}
364
365type CalculatorFn = fn(
374 u64, &[u8], CrcParams, ) -> u64;
378
379#[derive(Copy, Clone, Debug)]
385pub struct Digest {
386 state: u64,
388
389 amount: u64,
391
392 params: CrcParams,
394
395 calculator: CalculatorFn,
397}
398
399#[cfg(feature = "alloc")]
400impl DynDigest for Digest {
401 #[inline(always)]
402 fn update(&mut self, data: &[u8]) {
403 self.update(data);
404 }
405
406 #[inline(always)]
407 fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
408 if buf.len() != self.output_size() {
409 return Err(InvalidBufferSize);
410 }
411
412 let result = self.finalize();
413 let bytes = if self.output_size() == 4 {
414 result.to_be_bytes()[4..].to_vec() } else {
416 result.to_be_bytes().to_vec() };
418 buf.copy_from_slice(&bytes[..self.output_size()]);
419
420 Ok(())
421 }
422
423 #[inline(always)]
424 fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
425 if out.len() != self.output_size() {
426 return Err(InvalidBufferSize);
427 }
428 let result = self.finalize();
429 self.reset();
430 let bytes = if self.output_size() == 4 {
431 result.to_be_bytes()[4..].to_vec() } else {
433 result.to_be_bytes().to_vec() };
435 out.copy_from_slice(&bytes[..self.output_size()]);
436 Ok(())
437 }
438
439 #[inline(always)]
440 fn reset(&mut self) {
441 self.reset();
442 }
443
444 #[inline(always)]
445 fn output_size(&self) -> usize {
446 self.params.width as usize / 8
447 }
448
449 fn box_clone(&self) -> Box<dyn DynDigest> {
450 Box::new(*self)
451 }
452}
453
454impl Digest {
455 #[inline(always)]
469 pub fn new(algorithm: CrcAlgorithm) -> Self {
470 let (calculator, params) = get_calculator_params(algorithm);
471
472 Self {
473 state: params.init,
474 amount: 0,
475 params,
476 calculator,
477 }
478 }
479
480 #[inline(always)]
504 pub fn new_with_init_state(algorithm: CrcAlgorithm, init_state: u64) -> Self {
505 let (calculator, params) = get_calculator_params(algorithm);
506
507 Self {
508 state: init_state,
509 amount: 0,
510 params,
511 calculator,
512 }
513 }
514
515 #[inline(always)]
540 pub fn new_with_params(params: CrcParams) -> Self {
541 let calculator = Calculator::calculate as CalculatorFn;
542
543 Self {
544 state: params.init,
545 amount: 0,
546 params,
547 calculator,
548 }
549 }
550
551 #[inline(always)]
553 pub fn update(&mut self, data: &[u8]) {
554 self.state = (self.calculator)(self.state, data, self.params);
555 self.amount += data.len() as u64;
556 }
557
558 #[inline(always)]
560 pub fn finalize(&self) -> u64 {
561 self.state ^ self.params.xorout
562 }
563
564 #[inline(always)]
566 pub fn finalize_reset(&mut self) -> u64 {
567 let result = self.finalize();
568 self.reset();
569
570 result
571 }
572
573 #[inline(always)]
575 pub fn reset(&mut self) {
576 self.state = self.params.init;
577 self.amount = 0;
578 }
579
580 #[inline(always)]
582 pub fn combine(&mut self, other: &Self) {
583 self.amount += other.amount;
584 let other_crc = other.finalize();
585
586 self.state = combine::checksums(
589 self.state ^ self.params.xorout,
590 other_crc,
591 other.amount,
592 self.params,
593 ) ^ self.params.xorout;
594 }
595
596 #[inline(always)]
598 pub fn get_amount(&self) -> u64 {
599 self.amount
600 }
601
602 #[inline(always)]
619 pub fn get_state(&self) -> u64 {
620 self.state
621 }
622}
623
624#[cfg(feature = "std")]
625impl Write for Digest {
626 #[inline(always)]
627 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
628 self.update(buf);
629 Ok(buf.len())
630 }
631
632 #[inline(always)]
633 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
634 let len: usize = bufs
635 .iter()
636 .map(|buf| {
637 self.update(buf);
638 buf.len()
639 })
640 .sum();
641
642 Ok(len)
643 }
644
645 #[inline(always)]
646 fn flush(&mut self) -> std::io::Result<()> {
647 Ok(())
648 }
649
650 #[inline(always)]
651 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
652 self.update(buf);
653
654 Ok(())
655 }
656}
657
658#[inline(always)]
667pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
668 let (calculator, params) = get_calculator_params(algorithm);
669
670 calculator(params.init, buf, params) ^ params.xorout
671}
672
673pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 {
696 let calculator = Calculator::calculate as CalculatorFn;
697
698 calculator(params.init, buf, params) ^ params.xorout
699}
700
701#[cfg(feature = "std")]
724#[inline(always)]
725pub fn checksum_file(
726 algorithm: CrcAlgorithm,
727 path: &str,
728 chunk_size: Option<usize>,
729) -> Result<u64, std::io::Error> {
730 checksum_file_with_digest(Digest::new(algorithm), path, chunk_size)
731}
732
733#[cfg(feature = "std")]
767pub fn checksum_file_with_params(
768 params: CrcParams,
769 path: &str,
770 chunk_size: Option<usize>,
771) -> Result<u64, std::io::Error> {
772 checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size)
773}
774
775#[cfg(feature = "std")]
781fn checksum_file_with_digest(
782 mut digest: Digest,
783 path: &str,
784 chunk_size: Option<usize>,
785) -> Result<u64, std::io::Error> {
786 let mut file = File::open(path)?;
787
788 let chunk_size = chunk_size.unwrap_or(524288);
794
795 let mut buf = vec![0; chunk_size];
796
797 while let Ok(n) = file.read(&mut buf) {
798 if n == 0 {
799 break;
800 }
801 digest.update(&buf[..n]);
802 }
803
804 Ok(digest.finalize())
805}
806
807#[inline(always)]
820pub fn checksum_combine(
821 algorithm: CrcAlgorithm,
822 checksum1: u64,
823 checksum2: u64,
824 checksum2_len: u64,
825) -> u64 {
826 let params = get_calculator_params(algorithm).1;
827
828 combine::checksums(checksum1, checksum2, checksum2_len, params)
829}
830
831pub fn checksum_combine_with_params(
856 params: CrcParams,
857 checksum1: u64,
858 checksum2: u64,
859 checksum2_len: u64,
860) -> u64 {
861 combine::checksums(checksum1, checksum2, checksum2_len, params)
862}
863
864#[cfg(all(
894 feature = "alloc",
895 any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
896))]
897pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
898 let arch_ops = get_arch_ops();
899 arch_ops.get_target_string()
900}
901
902#[cfg(all(
904 feature = "alloc",
905 not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))
906))]
907pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
908 extern crate alloc;
909 use alloc::string::ToString;
910 "software-fallback-tables".to_string()
911}
912
913#[inline(always)]
915fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
916 match algorithm {
917 CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
918 CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
919 CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
920 CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
921 CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
922 CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
923 CrcAlgorithm::Crc32Custom => {
924 panic!("Custom CRC-32 requires parameters via CrcParams::new()")
925 }
926 CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
927 CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
928 CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
929 CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
930 CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
931 CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
932 CrcAlgorithm::Crc64Custom => {
933 panic!("Custom CRC-64 requires parameters via CrcParams::new()")
934 }
935 CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
936 CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
937 CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
938 CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
939 CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
940 CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
941 CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
942 }
943}
944
945#[inline(always)]
950fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
951 #[cfg(all(target_arch = "aarch64", feature = "std"))]
952 {
953 use crate::feature_detection::PerformanceTier;
954
955 let arch_ops = get_arch_ops();
956 match arch_ops.get_tier() {
957 PerformanceTier::AArch64AesSha3 | PerformanceTier::AArch64Aes => {
958 return fusion::crc32_iscsi(state as u32, data) as u64;
959 }
960 _ => {}
961 }
962 }
963
964 #[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), feature = "std"))]
965 {
966 use crate::feature_detection::PerformanceTier;
967
968 let arch_ops = get_arch_ops();
969 match arch_ops.get_tier() {
970 PerformanceTier::X86_64Avx512Vpclmulqdq
971 | PerformanceTier::X86_64Avx512Pclmulqdq
972 | PerformanceTier::X86_64SsePclmulqdq
973 | PerformanceTier::X86SsePclmulqdq => {
974 return fusion::crc32_iscsi(state as u32, data) as u64;
975 }
976 _ => {}
977 }
978 }
979
980 Calculator::calculate(state, data, _params)
981}
982
983#[inline(always)]
989fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
990 #[cfg(all(target_arch = "aarch64", feature = "std"))]
991 {
992 use crate::feature_detection::{get_arch_ops, PerformanceTier};
993 let arch_ops = get_arch_ops();
994
995 match arch_ops.get_tier() {
996 PerformanceTier::AArch64AesSha3 | PerformanceTier::AArch64Aes => {
997 return fusion::crc32_iso_hdlc(state as u32, data) as u64;
998 }
999 _ => {}
1000 }
1001 }
1002
1003 Calculator::calculate(state, data, _params)
1004}
1005
1006#[cfg(test)]
1007mod lib {
1008 #![allow(unused)]
1009
1010 use super::*;
1011 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
1012 use crate::test::enums::AnyCrcTestConfig;
1013 use cbindgen::Language::C;
1014 use cbindgen::Style::Both;
1015 use rand::{rng, Rng};
1016 use std::fs::{read, write};
1017
1018 #[test]
1019 fn test_checksum_check() {
1020 for config in TEST_ALL_CONFIGS {
1021 assert_eq!(
1022 checksum(config.get_algorithm(), TEST_CHECK_STRING),
1023 config.get_check()
1024 );
1025 }
1026 }
1027
1028 #[test]
1029 fn test_checksum_reference() {
1030 for config in TEST_ALL_CONFIGS {
1031 assert_eq!(
1032 checksum(config.get_algorithm(), TEST_CHECK_STRING),
1033 config.checksum_with_reference(TEST_CHECK_STRING)
1034 );
1035 }
1036 }
1037
1038 #[test]
1039 fn test_checksum_with_custom_params() {
1040 crate::cache::clear_cache();
1041
1042 assert_eq!(
1044 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
1045 CRC32_ISCSI.check,
1046 );
1047
1048 assert_eq!(
1050 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
1051 CRC32_BZIP2.check,
1052 );
1053
1054 assert_eq!(
1056 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
1057 CRC64_NVME.check,
1058 );
1059
1060 assert_eq!(
1062 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
1063 CRC64_ECMA_182.check,
1064 );
1065 }
1066
1067 #[test]
1068 fn test_get_custom_params() {
1069 crate::cache::clear_cache();
1070
1071 assert_eq!(
1072 checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
1073 CRC32_ISCSI.check,
1074 );
1075
1076 assert_eq!(
1077 checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
1078 CRC32_BZIP2.check,
1079 );
1080
1081 assert_eq!(
1082 checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
1083 CRC64_NVME.check,
1084 );
1085
1086 assert_eq!(
1087 checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
1088 CRC64_ECMA_182.check,
1089 );
1090 }
1091
1092 #[test]
1093 fn test_get_calculator_target_format() {
1094 let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1095
1096 assert!(!target.is_empty());
1098
1099 let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
1101 assert!(
1102 valid_prefixes
1103 .iter()
1104 .any(|prefix| target.starts_with(prefix)),
1105 "Target '{}' should start with a valid architecture prefix",
1106 target
1107 );
1108
1109 let parts: Vec<&str> = target.split('-').collect();
1111 assert!(
1112 parts.len() >= 3,
1113 "Target '{}' should have at least 3 parts: architecture-family-features",
1114 target
1115 );
1116 }
1117
1118 #[test]
1119 fn test_get_calculator_target_consistency() {
1120 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1122 let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1123 let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1124
1125 assert_eq!(
1126 target1, target2,
1127 "Target should be consistent across different CRC-32 algorithms"
1128 );
1129 assert_eq!(
1130 target1, target3,
1131 "Target should be consistent across CRC-32 and CRC-64 algorithms"
1132 );
1133 }
1134
1135 #[test]
1136 fn test_get_calculator_target_uses_cached_detection() {
1137 let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1142 let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1143
1144 assert_eq!(
1145 target1, target2,
1146 "Cached detection should return identical results"
1147 );
1148 }
1149
1150 #[test]
1151 fn test_digest_updates_check() {
1152 for config in TEST_ALL_CONFIGS {
1153 check_digest(Digest::new(config.get_algorithm()), config.get_check());
1154 }
1155 }
1156
1157 #[test]
1158 fn test_digest_updates_check_with_custom_params() {
1159 crate::cache::clear_cache();
1160
1161 check_digest(
1163 Digest::new_with_params(get_custom_crc32_reflected()),
1164 CRC32_ISCSI.check,
1165 );
1166
1167 check_digest(
1169 Digest::new_with_params(get_custom_crc32_forward()),
1170 CRC32_BZIP2.check,
1171 );
1172
1173 check_digest(
1175 Digest::new_with_params(get_custom_crc64_reflected()),
1176 CRC64_NVME.check,
1177 );
1178
1179 check_digest(
1181 Digest::new_with_params(get_custom_crc64_forward()),
1182 CRC64_ECMA_182.check,
1183 );
1184 }
1185
1186 fn check_digest(mut digest: Digest, check: u64) {
1187 digest.update(b"123");
1188 digest.update(b"456");
1189 digest.update(b"789");
1190 assert_eq!(digest.finalize(), check,);
1191 }
1192
1193 #[test]
1194 fn test_1024_length() {
1195 for config in TEST_ALL_CONFIGS {
1196 test_length(1024, config);
1197 }
1198 }
1199
1200 #[test]
1203 #[cfg_attr(miri, ignore)]
1204 fn test_small_all_lengths() {
1205 for config in TEST_ALL_CONFIGS {
1206 for len in 1..=255 {
1208 test_length(len, config);
1209 }
1210 }
1211 }
1212
1213 #[test]
1216 #[cfg_attr(miri, ignore)]
1217 fn test_medium_lengths() {
1218 for config in TEST_ALL_CONFIGS {
1219 for len in 256..=1024 {
1221 test_length(len, config);
1222 }
1223 }
1224 }
1225
1226 #[test]
1229 #[cfg_attr(miri, ignore)]
1230 fn test_large_lengths() {
1231 for config in TEST_ALL_CONFIGS {
1232 for len in 1048575..1048577 {
1234 test_length(len, config);
1235 }
1236 }
1237 }
1238
1239 fn test_length(length: usize, config: &AnyCrcTestConfig) {
1240 let mut data = vec![0u8; length];
1241 rng().fill(&mut data[..]);
1242
1243 let expected = config.checksum_with_reference(&data);
1245
1246 let result = checksum(config.get_algorithm(), &data);
1247
1248 assert_eq!(
1249 result,
1250 expected,
1251 "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1252 config.get_algorithm(),
1253 length,
1254 expected,
1255 result
1256 );
1257 }
1258
1259 #[test]
1260 fn test_combine() {
1261 for config in TEST_ALL_CONFIGS {
1262 let algorithm = config.get_algorithm();
1263 let check = config.get_check();
1264
1265 let checksum1 = checksum(algorithm, "1234".as_ref());
1267 let checksum2 = checksum(algorithm, "56789".as_ref());
1268
1269 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
1271
1272 let mut digest1 = Digest::new(algorithm);
1274 digest1.update("1234".as_ref());
1275
1276 let mut digest2 = Digest::new(algorithm);
1277 digest2.update("56789".as_ref());
1278
1279 digest1.combine(&digest2);
1280
1281 assert_eq!(digest1.finalize(), check)
1282 }
1283 }
1284
1285 #[test]
1286 fn test_combine_with_custom_params() {
1287 crate::cache::clear_cache();
1288
1289 let crc32_params = get_custom_crc32_reflected();
1291 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1292 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1293 assert_eq!(
1294 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1295 CRC32_ISCSI.check,
1296 );
1297
1298 let crc32_params = get_custom_crc32_forward();
1300 let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1301 let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1302 assert_eq!(
1303 checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1304 CRC32_BZIP2.check,
1305 );
1306
1307 let crc64_params = get_custom_crc64_reflected();
1309 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1310 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1311 assert_eq!(
1312 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1313 CRC64_NVME.check,
1314 );
1315
1316 let crc64_params = get_custom_crc64_forward();
1318 let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1319 let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1320 assert_eq!(
1321 checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1322 CRC64_ECMA_182.check,
1323 );
1324 }
1325
1326 #[test]
1329 #[cfg_attr(miri, ignore)]
1330 fn test_checksum_file() {
1331 let test_file_path = "test/test_crc32_hash_file.bin";
1333 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1335 eprintln!("Skipping test due to write error: {}", e);
1336 return;
1337 }
1338
1339 for config in TEST_ALL_CONFIGS {
1340 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1341 assert_eq!(result, config.checksum_with_reference(&data));
1342 }
1343
1344 std::fs::remove_file(test_file_path).unwrap();
1345 }
1346
1347 #[test]
1350 #[cfg_attr(miri, ignore)]
1351 fn test_checksum_file_with_custom_params() {
1352 crate::cache::clear_cache();
1353
1354 let test_file_path = "test/test_crc32_hash_file_custom.bin";
1356 let data = vec![0u8; 1024 * 1024]; if let Err(e) = write(test_file_path, &data) {
1358 eprintln!("Skipping test due to write error: {}", e);
1359 return;
1360 }
1361
1362 check_file(
1364 get_custom_crc32_reflected(),
1365 test_file_path,
1366 CRC32_ISCSI.check,
1367 );
1368
1369 check_file(
1371 get_custom_crc32_forward(),
1372 test_file_path,
1373 CRC32_BZIP2.check,
1374 );
1375
1376 check_file(
1378 get_custom_crc64_reflected(),
1379 test_file_path,
1380 CRC64_NVME.check,
1381 );
1382
1383 check_file(
1385 get_custom_crc64_forward(),
1386 test_file_path,
1387 CRC64_ECMA_182.check,
1388 );
1389
1390 std::fs::remove_file(test_file_path).unwrap();
1391 }
1392
1393 fn check_file(params: CrcParams, file_path: &str, check: u64) {
1394 let result = checksum_file_with_params(params, file_path, None).unwrap();
1395 assert_eq!(result, check);
1396 }
1397
1398 #[test]
1401 #[cfg_attr(miri, ignore)]
1402 fn test_writer() {
1403 let test_file_path = "test/test_crc32_writer_file.bin";
1405 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
1407 eprintln!("Skipping test due to write error: {}", e);
1408 return;
1409 }
1410
1411 for config in TEST_ALL_CONFIGS {
1412 let mut digest = Digest::new(config.get_algorithm());
1413 let mut file = File::open(test_file_path).unwrap();
1414 std::io::copy(&mut file, &mut digest).unwrap();
1415 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1416 }
1417
1418 std::fs::remove_file(test_file_path).unwrap();
1419 }
1420 #[test]
1421 fn test_digest_reset() {
1422 for config in TEST_ALL_CONFIGS {
1423 let mut digest = Digest::new(config.get_algorithm());
1424 digest.update(b"42");
1425 digest.reset();
1426 digest.update(TEST_CHECK_STRING);
1427 assert_eq!(digest.finalize(), config.get_check());
1428 }
1429 }
1430
1431 #[test]
1432 fn test_digest_finalize_reset() {
1433 for config in TEST_ALL_CONFIGS {
1434 let check = config.get_check();
1435
1436 let mut digest = Digest::new(config.get_algorithm());
1437 digest.update(TEST_CHECK_STRING);
1438 assert_eq!(digest.finalize_reset(), check);
1439
1440 digest.update(TEST_CHECK_STRING);
1441 assert_eq!(digest.finalize(), check);
1442 }
1443 }
1444
1445 #[test]
1446 fn test_digest_finalize_into() {
1447 for config in TEST_ALL_CONFIGS {
1448 let mut digest = Digest::new(config.get_algorithm());
1449 digest.update(TEST_CHECK_STRING);
1450
1451 match digest.params.width {
1452 32 => {
1453 let mut output = [0u8; 4];
1454 digest.finalize_into(&mut output).unwrap();
1455 let result = u32::from_be_bytes(output) as u64;
1456 assert_eq!(result, config.get_check());
1457 }
1458 64 => {
1459 let mut output = [0u8; 8];
1460 digest.finalize_into(&mut output).unwrap();
1461 let result = u64::from_be_bytes(output);
1462 assert_eq!(result, config.get_check());
1463 }
1464 _ => panic!("Unsupported CRC width"),
1465 }
1466 }
1467 }
1468
1469 #[test]
1470 fn test_digest_finalize_into_reset() {
1471 for config in TEST_ALL_CONFIGS {
1472 let mut digest = Digest::new(config.get_algorithm());
1473 digest.update(TEST_CHECK_STRING);
1474
1475 let mut output: Vec<u8> = match digest.params.width {
1476 32 => vec![0u8; 4],
1477 64 => vec![0u8; 8],
1478 _ => panic!("Unsupported CRC width"),
1479 };
1480
1481 digest.finalize_into_reset(&mut output).unwrap();
1482 let result = match output.len() {
1483 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1484 8 => u64::from_be_bytes(output.try_into().unwrap()),
1485 _ => panic!("Unsupported CRC width"),
1486 };
1487 assert_eq!(result, config.get_check());
1488
1489 digest.update(TEST_CHECK_STRING);
1490 assert_eq!(digest.finalize(), config.get_check());
1491 }
1492 }
1493
1494 #[test]
1496 #[cfg_attr(miri, ignore)]
1497 fn test_ffi_header() -> Result<(), String> {
1498 #[cfg(target_os = "windows")]
1499 {
1500 eprintln!("Skipping test on Windows");
1502
1503 return Ok(());
1504 }
1505
1506 const HEADER: &str = "libcrc_fast.h";
1507
1508 let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1509
1510 let mut expected = Vec::new();
1511 cbindgen::Builder::new()
1512 .with_crate(crate_dir)
1513 .with_include_guard("CRC_FAST_H")
1514 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1515 .exclude_item("crc32_iscsi_impl")
1517 .exclude_item("crc32_iso_hdlc_impl")
1518 .exclude_item("get_iscsi_target")
1519 .exclude_item("get_iso_hdlc_target")
1520 .exclude_item("ISO_HDLC_TARGET")
1521 .exclude_item("ISCSI_TARGET")
1522 .exclude_item("CrcParams")
1523 .rename_item("Digest", "CrcFastDigest")
1524 .with_style(Both)
1525 .with_language(C)
1527 .with_cpp_compat(true)
1529 .generate()
1530 .map_err(|error| error.to_string())?
1531 .write(&mut expected);
1532
1533 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1536
1537 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1539 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1540
1541 expected = cleaned_content.into_bytes();
1543
1544 let actual = read(HEADER).map_err(|error| error.to_string())?;
1545
1546 if expected != actual {
1547 write(HEADER, expected).map_err(|error| error.to_string())?;
1548 return Err(format!(
1549 "{HEADER} is not up-to-date, commit the generated file and try again"
1550 ));
1551 }
1552
1553 Ok(())
1554 }
1555
1556 fn get_custom_crc32_reflected() -> CrcParams {
1557 CrcParams::new(
1558 "Custom CRC-32/ISCSI",
1559 32,
1560 CRC32_ISCSI.poly,
1561 CRC32_ISCSI.init,
1562 CRC32_ISCSI.refin,
1563 CRC32_ISCSI.xorout,
1564 CRC32_ISCSI.check,
1565 )
1566 }
1567
1568 fn get_custom_crc32_forward() -> CrcParams {
1569 CrcParams::new(
1570 "Custom CRC-32/BZIP2",
1571 32,
1572 CRC32_BZIP2.poly,
1573 CRC32_BZIP2.init,
1574 CRC32_BZIP2.refin,
1575 CRC32_BZIP2.xorout,
1576 CRC32_BZIP2.check,
1577 )
1578 }
1579
1580 fn get_custom_crc64_reflected() -> CrcParams {
1581 CrcParams::new(
1582 "Custom CRC-64/NVME",
1583 64,
1584 CRC64_NVME.poly,
1585 CRC64_NVME.init,
1586 CRC64_NVME.refin,
1587 CRC64_NVME.xorout,
1588 CRC64_NVME.check,
1589 )
1590 }
1591
1592 fn get_custom_crc64_forward() -> CrcParams {
1593 CrcParams::new(
1594 "Custom CRC-64/ECMA-182",
1595 64,
1596 CRC64_ECMA_182.poly,
1597 CRC64_ECMA_182.init,
1598 CRC64_ECMA_182.refin,
1599 CRC64_ECMA_182.xorout,
1600 CRC64_ECMA_182.check,
1601 )
1602 }
1603
1604 #[test]
1605 #[allow(clippy::needless_range_loop)] fn test_crc_keys_storage_fold_256() {
1607 let test_keys = [
1608 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1609 ];
1610 let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1611
1612 for i in 0..23 {
1614 assert_eq!(storage.get_key(i), test_keys[i]);
1615 }
1616
1617 assert_eq!(storage.get_key(23), 0);
1619 assert_eq!(storage.get_key(24), 0);
1620 assert_eq!(storage.get_key(100), 0);
1621
1622 assert_eq!(storage.key_count(), 23);
1624 }
1625
1626 #[test]
1627 #[allow(clippy::needless_range_loop)] fn test_crc_keys_storage_future_test() {
1629 let test_keys = [
1630 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1631 25,
1632 ];
1633 let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1634
1635 for i in 0..25 {
1637 assert_eq!(storage.get_key(i), test_keys[i]);
1638 }
1639
1640 assert_eq!(storage.get_key(25), 0);
1642 assert_eq!(storage.get_key(26), 0);
1643 assert_eq!(storage.get_key(100), 0);
1644
1645 assert_eq!(storage.key_count(), 25);
1647 }
1648
1649 #[test]
1650 #[allow(clippy::needless_range_loop)] fn test_crc_params_safe_accessors() {
1652 let test_keys = [
1654 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1655 ];
1656 let params = CrcParams {
1657 algorithm: CrcAlgorithm::Crc32IsoHdlc,
1658 name: "test",
1659 width: 32,
1660 poly: 0x04C11DB7,
1661 init: 0xFFFFFFFF,
1662 refin: true,
1663 refout: true,
1664 xorout: 0xFFFFFFFF,
1665 check: 0xCBF43926,
1666 keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1667 };
1668
1669 for i in 0..23 {
1671 assert_eq!(params.get_key(i), test_keys[i]);
1672 assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1673 }
1674
1675 assert_eq!(params.get_key(23), 0);
1677 assert_eq!(params.get_key(24), 0);
1678 assert_eq!(params.get_key(100), 0);
1679
1680 assert_eq!(params.get_key_checked(23), None);
1681 assert_eq!(params.get_key_checked(24), None);
1682 assert_eq!(params.get_key_checked(100), None);
1683
1684 assert_eq!(params.key_count(), 23);
1686 }
1687
1688 #[test]
1689 fn test_crc_keys_storage_const_constructors() {
1690 const TEST_KEYS_23: [u64; 23] = [1; 23];
1692 const TEST_KEYS_25: [u64; 25] = [2; 25];
1693
1694 const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1695 const STORAGE_FUTURE: CrcKeysStorage =
1696 CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1697
1698 assert_eq!(STORAGE_256.get_key(0), 1);
1700 assert_eq!(STORAGE_256.key_count(), 23);
1701
1702 assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1703 assert_eq!(STORAGE_FUTURE.key_count(), 25);
1704 }
1705
1706 #[test]
1707 fn test_crc_keys_storage_bounds_safety() {
1708 let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1709 let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1710
1711 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);
1720 assert_eq!(storage_future.get_key(usize::MAX), 0);
1721 }
1722}