crc_fast/
lib.rs

1// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0.
2// Future proofing for no_std support
3#![cfg_attr(not(feature = "std"), no_std)]
4
5//! `crc-fast`
6//! ===========
7//!
8//! Hardware-accelerated CRC calculation for
9//! [all known CRC-32 and CRC-64 variants](https://reveng.sourceforge.io/crc-catalogue/all.htm)
10//! using SIMD intrinsics which can exceed 100GiB/s for CRC-32 and 50GiB/s for CRC-64 on modern
11//! systems.
12//!
13//! # Other languages
14//!
15//! Supplies a C-compatible shared library for use with other non-Rust languages. See
16//! [PHP extension](https://github.com/awesomized/crc-fast-php-ext) example.
17//!
18//! # Background
19//!
20//! The implementation is based on Intel's
21//! [Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction](https://web.archive.org/web/20131224125630/https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf),
22//! white paper though it folds 8-at-a-time, like other modern implementations, rather than the
23//! 4-at-a-time as in Intel's paper.
24//!
25//! Works on `aarch64`, `x86_64`, and `x86` architectures, and is hardware-accelerated and optimized
26//! for each architecture.
27//!
28//! Inspired by [`crc32fast`](https://crates.io/crates/crc32fast),
29//! [`crc64fast`](https://crates.io/crates/crc64fast),
30//! and [`crc64fast-nvme`](https://crates.io/crates/crc64fast-nvme), each of which only accelerates
31//! a single, different CRC variant, and all of them were "reflected" variants.
32//!
33//! In contrast, this library accelerates _every known variant_ (and should accelerate any future
34//! variants without changes), including all the "non-reflected" variants.
35//!
36//! # Usage
37//!
38//! ## Digest
39//!
40//! Implements the [digest::DynDigest](https://docs.rs/digest/latest/digest/trait.DynDigest.html)
41//! trait for easier integration with existing code.
42//!
43//! ```rust
44//! use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
45//!
46//! let mut digest = Digest::new(Crc32IsoHdlc);
47//! digest.update(b"1234");
48//! digest.update(b"56789");
49//! let checksum = digest.finalize();
50//!
51//! assert_eq!(checksum, 0xcbf43926);
52//! ```
53//!
54//! ## Digest Write
55//!
56//! Implements the [std::io::Write](https://doc.rust-lang.org/std/io/trait.Write.html) trait for
57//! easier integration with existing code.
58//!
59//! ```rust
60//! use std::env;
61//! use std::fs::File;
62//! use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
63//!
64//! // for example/test purposes only, use your own file path
65//! let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
66//! let file_on_disk = file_path.to_str().unwrap();
67//!
68//! // actual usage
69//! let mut digest = Digest::new(Crc32IsoHdlc);
70//! let mut file = File::open(file_on_disk).unwrap();
71//! std::io::copy(&mut file, &mut digest).unwrap();
72//! let checksum = digest.finalize();
73//!
74//! assert_eq!(checksum, 0xcbf43926);
75//! ```
76//! ## checksum
77//!```rust
78//! use crc_fast::{checksum, CrcAlgorithm::Crc32IsoHdlc};
79//!
80//! let checksum = checksum(Crc32IsoHdlc, b"123456789");
81//!
82//! assert_eq!(checksum, 0xcbf43926);
83//! ```
84//!
85//! ## checksum_combine
86//!```rust
87//! use crc_fast::{checksum, checksum_combine, CrcAlgorithm::Crc32IsoHdlc};
88//!
89//! let checksum_1 = checksum(Crc32IsoHdlc, b"1234");
90//! let checksum_2 = checksum(Crc32IsoHdlc, b"56789");
91//! let checksum = checksum_combine(Crc32IsoHdlc, checksum_1, checksum_2, 5);
92//!
93//! assert_eq!(checksum, 0xcbf43926);
94//! ```
95//!
96//! ## checksum_file
97//!```rust
98//! use std::env;
99//! use crc_fast::{checksum_file, CrcAlgorithm::Crc32IsoHdlc};
100//!
101//! // for example/test purposes only, use your own file path
102//! let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
103//! let file_on_disk = file_path.to_str().unwrap();
104//!
105//! let checksum = checksum_file(Crc32IsoHdlc, file_on_disk, None);
106//!
107//! assert_eq!(checksum.unwrap(), 0xcbf43926);
108//! ```
109//!
110//! ## Custom CRC Parameters
111//!
112//! For cases where you need to use CRC variants not included in the predefined algorithms,
113//! you can define custom CRC parameters using `CrcParams::new()` and use the `*_with_params` functions.
114//!
115//! ## checksum_with_params
116//!```rust
117//! use crc_fast::{checksum_with_params, CrcParams};
118//!
119//! // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
120//! let custom_params = CrcParams::new(
121//!     "CRC-32/CUSTOM",
122//!     32,
123//!     0x04c11db7,
124//!     0xffffffff,
125//!     true,
126//!     0xffffffff,
127//!     0xcbf43926,
128//! );
129//!
130//! let checksum = checksum_with_params(custom_params, b"123456789");
131//!
132//! assert_eq!(checksum, 0xcbf43926);
133//! ```
134//!
135//! # no_std Support
136//!
137//! Supports `no_std` environments. Use `default-features = false` in Cargo.toml.
138//!
139//! Note: When using this library in a `no_std` environment, the final binary must provide:
140//! - A `#[panic_handler]` (e.g., via the `panic-halt` crate)
141//! - A `#[global_allocator]` if using the `alloc` feature
142
143// Provide a panic handler for no_std library checks
144// Disabled with default-features = false (which binaries should use)
145#[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// Provide a global allocator for no_std + alloc library checks
157// Disabled with default-features = false (which binaries should use)
158#[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::crc16::consts::{
192    CRC16_ARC, CRC16_CDMA2000, CRC16_CMS, CRC16_DDS_110, CRC16_DECT_R, CRC16_DECT_X, CRC16_DNP,
193    CRC16_EN_13757, CRC16_GENIBUS, CRC16_GSM, CRC16_IBM_3740, CRC16_IBM_SDLC,
194    CRC16_ISO_IEC_14443_3_A, CRC16_KERMIT, CRC16_LJ1200, CRC16_M17, CRC16_MAXIM_DOW, CRC16_MCRF4XX,
195    CRC16_MODBUS, CRC16_NRSC_5, CRC16_OPENSAFETY_A, CRC16_OPENSAFETY_B, CRC16_PROFIBUS,
196    CRC16_RIELLO, CRC16_SPI_FUJITSU, CRC16_T10_DIF, CRC16_TELEDISK, CRC16_TMS37157, CRC16_UMTS,
197    CRC16_USB, CRC16_XMODEM,
198};
199
200use crate::crc32::consts::{
201    CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
202    CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
203};
204
205#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
206#[cfg(feature = "std")]
207use crate::crc32::fusion;
208
209use crate::crc64::consts::{
210    CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
211};
212use crate::structs::Calculator;
213use crate::traits::CrcCalculator;
214#[cfg(feature = "alloc")]
215use digest::DynDigest;
216#[cfg(feature = "alloc")]
217use digest::InvalidBufferSize;
218
219#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
220use crate::feature_detection::get_arch_ops;
221#[cfg(feature = "std")]
222use std::fs::File;
223#[cfg(feature = "std")]
224use std::io::{Read, Write};
225
226#[cfg(all(feature = "alloc", not(feature = "std")))]
227extern crate alloc;
228#[cfg(all(feature = "alloc", not(feature = "std")))]
229use alloc::boxed::Box;
230#[cfg(all(feature = "alloc", not(feature = "std")))]
231use alloc::string::String;
232
233mod algorithm;
234mod arch;
235mod cache;
236mod combine;
237mod consts;
238mod crc16;
239mod crc32;
240mod crc64;
241mod enums;
242mod feature_detection;
243#[cfg(feature = "ffi")]
244mod ffi;
245mod generate;
246mod structs;
247mod test;
248mod traits;
249
250/// Supported CRC-16, CRC-32, and CRC-64 variants
251#[derive(Debug, Clone, Copy, PartialEq)]
252pub enum CrcAlgorithm {
253    /// Generic custom CRC variant that works with any supported width (16, 32, 64).
254    /// The actual width is determined by the `width` field in `CrcParams`.
255    CrcCustom,
256    Crc16Arc,
257    Crc16Cdma2000,
258    Crc16Cms,
259    Crc16Dds110,
260    Crc16DectR,
261    Crc16DectX,
262    Crc16Dnp,
263    Crc16En13757,
264    Crc16Genibus,
265    Crc16Gsm,
266    Crc16Ibm3740,
267    Crc16IbmSdlc,
268    Crc16IsoIec144433A,
269    Crc16Kermit,
270    Crc16Lj1200,
271    Crc16M17,
272    Crc16MaximDow,
273    Crc16Mcrf4xx,
274    Crc16Modbus,
275    Crc16Nrsc5,
276    Crc16OpensafetyA,
277    Crc16OpensafetyB,
278    Crc16Profibus,
279    Crc16Riello,
280    Crc16SpiFujitsu,
281    Crc16T10Dif,
282    Crc16Teledisk,
283    Crc16Tms37157,
284    Crc16Umts,
285    Crc16Usb,
286    Crc16Xmodem,
287    Crc32Aixm,
288    Crc32Autosar,
289    Crc32Base91D,
290    Crc32Bzip2,
291    Crc32CdRomEdc,
292    Crc32Cksum,
293    #[deprecated(
294        since = "1.9.0",
295        note = "Use CrcCustom instead, which works with any supported width (16, 32, 64)"
296    )]
297    Crc32Custom, // Custom CRC-32 implementation, not defined in consts
298    Crc32Iscsi,
299    Crc32IsoHdlc,
300    Crc32Jamcrc,
301    Crc32Mef,
302    Crc32Mpeg2,
303    Crc32Xfer,
304    #[deprecated(
305        since = "1.9.0",
306        note = "Use CrcCustom instead, which works with any supported width (16, 32, 64)"
307    )]
308    Crc64Custom, // Custom CRC-64 implementation, not defined in consts
309    Crc64Ecma182,
310    Crc64GoIso,
311    Crc64Ms,
312    Crc64Nvme,
313    Crc64Redis,
314    Crc64We,
315    Crc64Xz,
316}
317
318/// Internal storage for CRC folding keys that can accommodate different array sizes.
319/// This enum allows future expansion to support larger folding distances while maintaining
320/// backwards compatibility with existing const definitions.
321#[derive(Clone, Copy, Debug, PartialEq)]
322pub enum CrcKeysStorage {
323    /// Current 23-key format for existing algorithms (supports up to 256-byte folding distances)
324    KeysFold256([u64; 23]),
325    /// Future 25-key format for potential expanded folding distances (testing purposes only)
326    KeysFutureTest([u64; 25]),
327}
328
329impl CrcKeysStorage {
330    /// Safe key access with bounds checking. Returns 0 for out-of-bounds indices.
331    #[inline(always)]
332    const fn get_key(self, index: usize) -> u64 {
333        match self {
334            CrcKeysStorage::KeysFold256(keys) => {
335                if index < 23 {
336                    keys[index]
337                } else {
338                    0
339                }
340            }
341            CrcKeysStorage::KeysFutureTest(keys) => {
342                if index < 25 {
343                    keys[index]
344                } else {
345                    0
346                }
347            }
348        }
349    }
350
351    /// Returns the number of keys available in this storage variant.
352    #[inline(always)]
353    const fn key_count(self) -> usize {
354        match self {
355            CrcKeysStorage::KeysFold256(_) => 23,
356            CrcKeysStorage::KeysFutureTest(_) => 25,
357        }
358    }
359
360    /// Const constructor for 23-key arrays (current format).
361    #[inline(always)]
362    const fn from_keys_fold_256(keys: [u64; 23]) -> Self {
363        CrcKeysStorage::KeysFold256(keys)
364    }
365
366    /// Const constructor for 25-key arrays (future expansion testing).
367    #[inline(always)]
368    #[allow(dead_code)] // Reserved for future expansion
369    const fn from_keys_fold_future_test(keys: [u64; 25]) -> Self {
370        CrcKeysStorage::KeysFutureTest(keys)
371    }
372
373    /// Extracts keys as a [u64; 23] array for FFI compatibility.
374    /// For variants with more than 23 keys, only the first 23 are returned.
375    /// For variants with fewer keys, remaining slots are filled with 0.
376    #[inline(always)]
377    pub fn to_keys_array_23(self) -> [u64; 23] {
378        match self {
379            CrcKeysStorage::KeysFold256(keys) => keys,
380            CrcKeysStorage::KeysFutureTest(keys) => {
381                let mut result = [0u64; 23];
382                result.copy_from_slice(&keys[..23]);
383                result
384            }
385        }
386    }
387}
388
389// Implement PartialEq between CrcKeysStorage and [u64; 23] for test compatibility
390impl PartialEq<[u64; 23]> for CrcKeysStorage {
391    fn eq(&self, other: &[u64; 23]) -> bool {
392        self.to_keys_array_23() == *other
393    }
394}
395
396impl PartialEq<CrcKeysStorage> for [u64; 23] {
397    fn eq(&self, other: &CrcKeysStorage) -> bool {
398        *self == other.to_keys_array_23()
399    }
400}
401
402/// Parameters for CRC computation, including polynomial, initial value, and other settings.
403#[derive(Clone, Copy, Debug)]
404pub struct CrcParams {
405    pub algorithm: CrcAlgorithm,
406    pub name: &'static str,
407    pub width: u8,
408    pub poly: u64,
409    pub init: u64,
410    /// The init value in "algorithm form" for the SIMD implementation.
411    ///
412    /// For most CRC variants, this equals `init`. However, for reflected CRC-16 variants
413    /// with non-symmetric init values (e.g., CRC-16/ISO-IEC-14443-3-A with init=0xC6C6),
414    /// this stores the bit-reversed init value. This avoids runtime bit-reversal on every
415    /// update() call, which would otherwise be needed because the SIMD algorithm operates
416    /// on data in a different bit order than the catalog specification.
417    ///
418    /// Examples:
419    /// - CRC-16/IBM-SDLC: init=0xFFFF, init_algorithm=0xFFFF (symmetric)
420    /// - CRC-16/ISO-IEC-14443-3-A: init=0xC6C6, init_algorithm=0x6363 (0xC6C6.reverse_bits())
421    pub init_algorithm: u64,
422    pub refin: bool,
423    pub refout: bool,
424    pub xorout: u64,
425    pub check: u64,
426    pub keys: CrcKeysStorage,
427}
428
429/// Type alias for a function pointer that represents a CRC calculation function.
430///
431/// The function takes the following parameters:
432/// - `state`: The current state of the CRC computation.
433/// - `data`: A slice of bytes to be processed.
434/// - `params`: The parameters for the CRC computation, such as polynomial, initial value, etc.
435///
436/// The function returns the updated state after processing the data.
437///
438/// Note: CrcParams is passed by reference to avoid copying the large struct (200+ bytes)
439/// which causes significant overhead for small data sizes.
440type CalculatorFn = fn(
441    u64,        // state
442    &[u8],      // data
443    &CrcParams, // CRC implementation parameters
444) -> u64;
445
446/// Represents a CRC Digest, which is used to compute CRC checksums.
447///
448/// The `Digest` struct maintains the state of the CRC computation, including
449/// the current state, the amount of data processed, the CRC parameters, and
450/// the calculator function used to perform the CRC calculation.
451#[derive(Copy, Clone, Debug)]
452pub struct Digest {
453    /// The current state of the CRC computation.
454    state: u64,
455
456    /// The total amount of data processed so far.
457    amount: u64,
458
459    /// The parameters for the CRC computation, such as polynomial, initial value, etc.
460    params: CrcParams,
461
462    /// The function used to perform the CRC calculation.
463    calculator: CalculatorFn,
464}
465
466#[cfg(feature = "alloc")]
467impl DynDigest for Digest {
468    #[inline(always)]
469    fn update(&mut self, data: &[u8]) {
470        self.update(data);
471    }
472
473    #[inline(always)]
474    fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
475        if buf.len() != self.output_size() {
476            return Err(InvalidBufferSize);
477        }
478
479        let result = self.finalize();
480        let be_bytes = result.to_be_bytes();
481        let start = 8 - self.output_size();
482        buf.copy_from_slice(&be_bytes[start..]);
483
484        Ok(())
485    }
486
487    #[inline(always)]
488    fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
489        if out.len() != self.output_size() {
490            return Err(InvalidBufferSize);
491        }
492        let result = self.finalize();
493        self.reset();
494        let be_bytes = result.to_be_bytes();
495        let start = 8 - self.output_size();
496        out.copy_from_slice(&be_bytes[start..]);
497        Ok(())
498    }
499
500    #[inline(always)]
501    fn reset(&mut self) {
502        self.reset();
503    }
504
505    #[inline(always)]
506    fn output_size(&self) -> usize {
507        self.params.width as usize / 8
508    }
509
510    fn box_clone(&self) -> Box<dyn DynDigest> {
511        Box::new(*self)
512    }
513}
514
515impl Digest {
516    /// Creates a new `Digest` instance for the specified CRC algorithm.
517    ///
518    /// # Examples
519    ///
520    /// ```rust
521    /// use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
522    ///
523    /// let mut digest = Digest::new(Crc32IsoHdlc);
524    /// digest.update(b"123456789");
525    /// let checksum = digest.finalize();
526    ///
527    /// assert_eq!(checksum, 0xcbf43926);
528    /// ```
529    #[inline(always)]
530    pub fn new(algorithm: CrcAlgorithm) -> Self {
531        let (calculator, params) = get_calculator_params(algorithm);
532
533        Self {
534            state: params.init_algorithm,
535            amount: 0,
536            params,
537            calculator,
538        }
539    }
540
541    /// Creates a new `Digest` instance for the specified CRC algorithm with a custom initial state.
542    ///
543    /// # Examples
544    ///
545    /// ```rust
546    /// use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
547    ///
548    /// // CRC-32/ISO-HDLC with initial state of 0x00000000, instead of the default initial state
549    /// // of 0xffffffff,
550    /// let mut digest = Digest::new_with_init_state(Crc32IsoHdlc, 0x00000000);
551    /// digest.update(b"123456789");
552    /// let checksum = digest.finalize();
553    ///
554    /// // different initial state, so checksum will be different
555    /// assert_eq!(checksum, 0xd202d277);
556    ///
557    /// let mut digest = Digest::new_with_init_state(Crc32IsoHdlc, 0xffffffff);
558    /// digest.update(b"123456789");
559    /// let checksum = digest.finalize();
560    ///
561    /// // same initial state as the default, so checksum will be the same
562    /// assert_eq!(checksum, 0xcbf43926);
563    /// ```
564    #[inline(always)]
565    pub fn new_with_init_state(algorithm: CrcAlgorithm, init_state: u64) -> Self {
566        let (calculator, params) = get_calculator_params(algorithm);
567
568        Self {
569            state: init_state,
570            amount: 0,
571            params,
572            calculator,
573        }
574    }
575
576    /// Creates a new `Digest` instance with custom CRC parameters.
577    ///
578    /// # Examples
579    ///
580    /// ```rust
581    /// use crc_fast::{Digest, CrcParams};
582    ///
583    /// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
584    /// let custom_params = CrcParams::new(
585    ///     "CRC-32/CUSTOM",
586    ///     32,
587    ///     0x04c11db7,
588    ///     0xffffffff,
589    ///     true,
590    ///     0xffffffff,
591    ///     0xcbf43926,
592    /// );
593    ///
594    /// let mut digest = Digest::new_with_params(custom_params);
595    /// digest.update(b"123456789");
596    /// let checksum = digest.finalize();
597    ///
598    /// assert_eq!(checksum, 0xcbf43926);
599    /// ```
600    #[inline(always)]
601    pub fn new_with_params(params: CrcParams) -> Self {
602        let calculator = Calculator::calculate as CalculatorFn;
603
604        Self {
605            state: params.init_algorithm,
606            amount: 0,
607            params,
608            calculator,
609        }
610    }
611
612    /// Updates the CRC state with the given data.
613    #[inline(always)]
614    pub fn update(&mut self, data: &[u8]) {
615        self.state = (self.calculator)(self.state, data, &self.params);
616        self.amount += data.len() as u64;
617    }
618
619    /// Finalizes the CRC computation and returns the result.
620    #[inline(always)]
621    pub fn finalize(&self) -> u64 {
622        self.state ^ self.params.xorout
623    }
624
625    /// Finalizes the CRC computation, resets the state, and returns the result.
626    #[inline(always)]
627    pub fn finalize_reset(&mut self) -> u64 {
628        let result = self.finalize();
629        self.reset();
630
631        result
632    }
633
634    /// Resets the CRC state to its initial value.
635    #[inline(always)]
636    pub fn reset(&mut self) {
637        self.state = self.params.init_algorithm;
638        self.amount = 0;
639    }
640
641    /// Combines the CRC state with a second `Digest` instance.
642    #[inline(always)]
643    pub fn combine(&mut self, other: &Self) {
644        self.amount += other.amount;
645        let other_crc = other.finalize();
646
647        // note the xorout for the input, since it's already been applied so it has to be removed,
648        // and then re-adding it on the final output
649        self.state = combine::checksums(
650            self.state ^ self.params.xorout,
651            other_crc,
652            other.amount,
653            &self.params,
654        ) ^ self.params.xorout;
655    }
656
657    /// Gets the amount of data processed so far
658    #[inline(always)]
659    pub fn get_amount(&self) -> u64 {
660        self.amount
661    }
662
663    /// Gets the current CRC state.
664    ///
665    /// # Examples
666    /// ```rust
667    /// use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
668    ///
669    /// let mut digest = Digest::new(Crc32IsoHdlc);
670    /// digest.update(b"123456789");
671    /// let state = digest.get_state();
672    ///
673    /// // non-finalized state, so it won't match the final checksum
674    /// assert_eq!(state, 0x340bc6d9);
675    ///
676    /// // finalized state will match the checksum
677    /// assert_eq!(digest.finalize(), 0xcbf43926);
678    /// ```
679    #[inline(always)]
680    pub fn get_state(&self) -> u64 {
681        self.state
682    }
683}
684
685#[cfg(feature = "std")]
686impl Write for Digest {
687    #[inline(always)]
688    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
689        self.update(buf);
690        Ok(buf.len())
691    }
692
693    #[inline(always)]
694    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
695        let len: usize = bufs
696            .iter()
697            .map(|buf| {
698                self.update(buf);
699                buf.len()
700            })
701            .sum();
702
703        Ok(len)
704    }
705
706    #[inline(always)]
707    fn flush(&mut self) -> std::io::Result<()> {
708        Ok(())
709    }
710
711    #[inline(always)]
712    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
713        self.update(buf);
714
715        Ok(())
716    }
717}
718
719/// Computes the CRC checksum for the given data using the specified algorithm.
720///
721///```rust
722/// use crc_fast::{checksum, CrcAlgorithm::Crc32IsoHdlc};
723/// let checksum = checksum(Crc32IsoHdlc, b"123456789");
724///
725/// assert_eq!(checksum, 0xcbf43926);
726/// ```
727#[inline]
728#[allow(deprecated)]
729pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
730    // avoid using get_calculator_params() here to reduce overhead for small data sizes
731    match algorithm {
732        CrcAlgorithm::Crc16Arc => {
733            Calculator::calculate(CRC16_ARC.init, buf, &CRC16_ARC) ^ CRC16_ARC.xorout
734        }
735        CrcAlgorithm::Crc16Cdma2000 => {
736            Calculator::calculate(CRC16_CDMA2000.init, buf, &CRC16_CDMA2000) ^ CRC16_CDMA2000.xorout
737        }
738        CrcAlgorithm::Crc16Cms => {
739            Calculator::calculate(CRC16_CMS.init, buf, &CRC16_CMS) ^ CRC16_CMS.xorout
740        }
741        CrcAlgorithm::Crc16Dds110 => {
742            Calculator::calculate(CRC16_DDS_110.init, buf, &CRC16_DDS_110) ^ CRC16_DDS_110.xorout
743        }
744        CrcAlgorithm::Crc16DectR => {
745            Calculator::calculate(CRC16_DECT_R.init, buf, &CRC16_DECT_R) ^ CRC16_DECT_R.xorout
746        }
747        CrcAlgorithm::Crc16DectX => {
748            Calculator::calculate(CRC16_DECT_X.init, buf, &CRC16_DECT_X) ^ CRC16_DECT_X.xorout
749        }
750        CrcAlgorithm::Crc16Dnp => {
751            Calculator::calculate(CRC16_DNP.init, buf, &CRC16_DNP) ^ CRC16_DNP.xorout
752        }
753        CrcAlgorithm::Crc16En13757 => {
754            Calculator::calculate(CRC16_EN_13757.init, buf, &CRC16_EN_13757) ^ CRC16_EN_13757.xorout
755        }
756        CrcAlgorithm::Crc16Genibus => {
757            Calculator::calculate(CRC16_GENIBUS.init, buf, &CRC16_GENIBUS) ^ CRC16_GENIBUS.xorout
758        }
759        CrcAlgorithm::Crc16Gsm => {
760            Calculator::calculate(CRC16_GSM.init, buf, &CRC16_GSM) ^ CRC16_GSM.xorout
761        }
762        CrcAlgorithm::Crc16Ibm3740 => {
763            Calculator::calculate(CRC16_IBM_3740.init, buf, &CRC16_IBM_3740) ^ CRC16_IBM_3740.xorout
764        }
765        CrcAlgorithm::Crc16IbmSdlc => {
766            Calculator::calculate(CRC16_IBM_SDLC.init, buf, &CRC16_IBM_SDLC) ^ CRC16_IBM_SDLC.xorout
767        }
768        CrcAlgorithm::Crc16IsoIec144433A => {
769            Calculator::calculate(
770                CRC16_ISO_IEC_14443_3_A.init_algorithm,
771                buf,
772                &CRC16_ISO_IEC_14443_3_A,
773            ) ^ CRC16_ISO_IEC_14443_3_A.xorout
774        }
775        CrcAlgorithm::Crc16Kermit => {
776            Calculator::calculate(CRC16_KERMIT.init, buf, &CRC16_KERMIT) ^ CRC16_KERMIT.xorout
777        }
778        CrcAlgorithm::Crc16Lj1200 => {
779            Calculator::calculate(CRC16_LJ1200.init, buf, &CRC16_LJ1200) ^ CRC16_LJ1200.xorout
780        }
781        CrcAlgorithm::Crc16M17 => {
782            Calculator::calculate(CRC16_M17.init, buf, &CRC16_M17) ^ CRC16_M17.xorout
783        }
784        CrcAlgorithm::Crc16MaximDow => {
785            Calculator::calculate(CRC16_MAXIM_DOW.init, buf, &CRC16_MAXIM_DOW)
786                ^ CRC16_MAXIM_DOW.xorout
787        }
788        CrcAlgorithm::Crc16Mcrf4xx => {
789            Calculator::calculate(CRC16_MCRF4XX.init, buf, &CRC16_MCRF4XX) ^ CRC16_MCRF4XX.xorout
790        }
791        CrcAlgorithm::Crc16Modbus => {
792            Calculator::calculate(CRC16_MODBUS.init, buf, &CRC16_MODBUS) ^ CRC16_MODBUS.xorout
793        }
794        CrcAlgorithm::Crc16Nrsc5 => {
795            Calculator::calculate(CRC16_NRSC_5.init, buf, &CRC16_NRSC_5) ^ CRC16_NRSC_5.xorout
796        }
797        CrcAlgorithm::Crc16OpensafetyA => {
798            Calculator::calculate(CRC16_OPENSAFETY_A.init, buf, &CRC16_OPENSAFETY_A)
799                ^ CRC16_OPENSAFETY_A.xorout
800        }
801        CrcAlgorithm::Crc16OpensafetyB => {
802            Calculator::calculate(CRC16_OPENSAFETY_B.init, buf, &CRC16_OPENSAFETY_B)
803                ^ CRC16_OPENSAFETY_B.xorout
804        }
805        CrcAlgorithm::Crc16Profibus => {
806            Calculator::calculate(CRC16_PROFIBUS.init, buf, &CRC16_PROFIBUS) ^ CRC16_PROFIBUS.xorout
807        }
808        CrcAlgorithm::Crc16Riello => {
809            Calculator::calculate(CRC16_RIELLO.init_algorithm, buf, &CRC16_RIELLO)
810                ^ CRC16_RIELLO.xorout
811        }
812        CrcAlgorithm::Crc16SpiFujitsu => {
813            Calculator::calculate(CRC16_SPI_FUJITSU.init, buf, &CRC16_SPI_FUJITSU)
814                ^ CRC16_SPI_FUJITSU.xorout
815        }
816        CrcAlgorithm::Crc16T10Dif => {
817            Calculator::calculate(CRC16_T10_DIF.init, buf, &CRC16_T10_DIF) ^ CRC16_T10_DIF.xorout
818        }
819        CrcAlgorithm::Crc16Teledisk => {
820            Calculator::calculate(CRC16_TELEDISK.init, buf, &CRC16_TELEDISK) ^ CRC16_TELEDISK.xorout
821        }
822        CrcAlgorithm::Crc16Tms37157 => {
823            Calculator::calculate(CRC16_TMS37157.init_algorithm, buf, &CRC16_TMS37157)
824                ^ CRC16_TMS37157.xorout
825        }
826        CrcAlgorithm::Crc16Umts => {
827            Calculator::calculate(CRC16_UMTS.init, buf, &CRC16_UMTS) ^ CRC16_UMTS.xorout
828        }
829        CrcAlgorithm::Crc16Usb => {
830            Calculator::calculate(CRC16_USB.init, buf, &CRC16_USB) ^ CRC16_USB.xorout
831        }
832        CrcAlgorithm::Crc16Xmodem => {
833            Calculator::calculate(CRC16_XMODEM.init, buf, &CRC16_XMODEM) ^ CRC16_XMODEM.xorout
834        }
835        CrcAlgorithm::Crc32Aixm => {
836            Calculator::calculate(CRC32_AIXM.init, buf, &CRC32_AIXM) ^ CRC32_AIXM.xorout
837        }
838        CrcAlgorithm::Crc32Autosar => {
839            Calculator::calculate(CRC32_AUTOSAR.init, buf, &CRC32_AUTOSAR) ^ CRC32_AUTOSAR.xorout
840        }
841        CrcAlgorithm::Crc32Base91D => {
842            Calculator::calculate(CRC32_BASE91_D.init, buf, &CRC32_BASE91_D) ^ CRC32_BASE91_D.xorout
843        }
844        CrcAlgorithm::Crc32Bzip2 => {
845            Calculator::calculate(CRC32_BZIP2.init, buf, &CRC32_BZIP2) ^ CRC32_BZIP2.xorout
846        }
847        CrcAlgorithm::Crc32CdRomEdc => {
848            Calculator::calculate(CRC32_CD_ROM_EDC.init, buf, &CRC32_CD_ROM_EDC)
849                ^ CRC32_CD_ROM_EDC.xorout
850        }
851        CrcAlgorithm::Crc32Cksum => {
852            Calculator::calculate(CRC32_CKSUM.init, buf, &CRC32_CKSUM) ^ CRC32_CKSUM.xorout
853        }
854        CrcAlgorithm::Crc32Custom => {
855            panic!("Custom CRC-32 requires parameters via CrcParams::new()")
856        }
857        CrcAlgorithm::Crc32Iscsi => {
858            crc32_iscsi_calculator(CRC32_ISCSI.init, buf, &CRC32_ISCSI) ^ CRC32_ISCSI.xorout
859        }
860        CrcAlgorithm::Crc32IsoHdlc => {
861            crc32_iso_hdlc_calculator(CRC32_ISO_HDLC.init, buf, &CRC32_ISO_HDLC)
862                ^ CRC32_ISO_HDLC.xorout
863        }
864        CrcAlgorithm::Crc32Jamcrc => {
865            Calculator::calculate(CRC32_JAMCRC.init, buf, &CRC32_JAMCRC) ^ CRC32_JAMCRC.xorout
866        }
867        CrcAlgorithm::Crc32Mef => {
868            Calculator::calculate(CRC32_MEF.init, buf, &CRC32_MEF) ^ CRC32_MEF.xorout
869        }
870        CrcAlgorithm::Crc32Mpeg2 => {
871            Calculator::calculate(CRC32_MPEG_2.init, buf, &CRC32_MPEG_2) ^ CRC32_MPEG_2.xorout
872        }
873        CrcAlgorithm::Crc32Xfer => {
874            Calculator::calculate(CRC32_XFER.init, buf, &CRC32_XFER) ^ CRC32_XFER.xorout
875        }
876        CrcAlgorithm::CrcCustom => {
877            panic!("Custom CRC requires parameters via CrcParams::new()")
878        }
879        CrcAlgorithm::Crc64Custom => {
880            panic!("Custom CRC-64 requires parameters via CrcParams::new()")
881        }
882        CrcAlgorithm::Crc64Ecma182 => {
883            Calculator::calculate(CRC64_ECMA_182.init, buf, &CRC64_ECMA_182) ^ CRC64_ECMA_182.xorout
884        }
885        CrcAlgorithm::Crc64GoIso => {
886            Calculator::calculate(CRC64_GO_ISO.init, buf, &CRC64_GO_ISO) ^ CRC64_GO_ISO.xorout
887        }
888        CrcAlgorithm::Crc64Ms => {
889            Calculator::calculate(CRC64_MS.init, buf, &CRC64_MS) ^ CRC64_MS.xorout
890        }
891        CrcAlgorithm::Crc64Nvme => {
892            Calculator::calculate(CRC64_NVME.init, buf, &CRC64_NVME) ^ CRC64_NVME.xorout
893        }
894        CrcAlgorithm::Crc64Redis => {
895            Calculator::calculate(CRC64_REDIS.init, buf, &CRC64_REDIS) ^ CRC64_REDIS.xorout
896        }
897        CrcAlgorithm::Crc64We => {
898            Calculator::calculate(CRC64_WE.init, buf, &CRC64_WE) ^ CRC64_WE.xorout
899        }
900        CrcAlgorithm::Crc64Xz => {
901            Calculator::calculate(CRC64_XZ.init, buf, &CRC64_XZ) ^ CRC64_XZ.xorout
902        }
903    }
904}
905
906/// Computes the CRC checksum for the given data using custom CRC parameters.
907///
908/// # Examples
909///
910/// ```rust
911/// use crc_fast::{checksum_with_params, CrcParams};
912///
913/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
914/// let custom_params = CrcParams::new(
915///     "CRC-32/CUSTOM",
916///     32,
917///     0x04c11db7,
918///     0xffffffff,
919///     true,
920///     0xffffffff,
921///     0xcbf43926,
922/// );
923///
924/// let checksum = checksum_with_params(custom_params, b"123456789");
925///
926/// assert_eq!(checksum, 0xcbf43926);
927/// ```
928pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 {
929    let calculator = Calculator::calculate as CalculatorFn;
930
931    calculator(params.init, buf, &params) ^ params.xorout
932}
933
934/// Computes the CRC checksum for the given file using the specified algorithm.
935///
936/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
937///
938/// # Errors
939///
940/// This function will return an error if the file cannot be read.
941///
942/// # Examples
943/// ### checksum_file
944///```rust
945/// use std::env;
946/// use crc_fast::{checksum_file, CrcAlgorithm::Crc32IsoHdlc};
947///
948/// // for example/test purposes only, use your own file path
949/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
950/// let file_on_disk = file_path.to_str().unwrap();
951///
952/// let checksum = checksum_file(Crc32IsoHdlc, file_on_disk, None);
953///
954/// assert_eq!(checksum.unwrap(), 0xcbf43926);
955/// ```
956#[cfg(feature = "std")]
957#[inline(always)]
958pub fn checksum_file(
959    algorithm: CrcAlgorithm,
960    path: &str,
961    chunk_size: Option<usize>,
962) -> Result<u64, std::io::Error> {
963    checksum_file_with_digest(Digest::new(algorithm), path, chunk_size)
964}
965
966/// Computes the CRC checksum for the given file using custom CRC parameters.
967///
968/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
969///
970/// # Errors
971///
972/// This function will return an error if the file cannot be read.
973///
974/// # Examples
975///
976/// ```rust
977/// use std::env;
978/// use crc_fast::{checksum_file_with_params, CrcParams};
979///
980/// // for example/test purposes only, use your own file path
981/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
982/// let file_on_disk = file_path.to_str().unwrap();
983///
984/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
985/// let custom_params = CrcParams::new(
986///     "CRC-32/CUSTOM",
987///     32,
988///     0x04c11db7,
989///     0xffffffff,
990///     true,
991///     0xffffffff,
992///     0xcbf43926,
993/// );
994///
995/// let checksum = checksum_file_with_params(custom_params, file_on_disk, None);
996///
997/// assert_eq!(checksum.unwrap(), 0xcbf43926);
998/// ```
999#[cfg(feature = "std")]
1000pub fn checksum_file_with_params(
1001    params: CrcParams,
1002    path: &str,
1003    chunk_size: Option<usize>,
1004) -> Result<u64, std::io::Error> {
1005    checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size)
1006}
1007
1008/// Computes the CRC checksum for the given file using the specified Digest.
1009///
1010/// # Errors
1011///
1012/// This function will return an error if the file cannot be read.
1013#[cfg(feature = "std")]
1014fn checksum_file_with_digest(
1015    mut digest: Digest,
1016    path: &str,
1017    chunk_size: Option<usize>,
1018) -> Result<u64, std::io::Error> {
1019    let mut file = File::open(path)?;
1020
1021    // 512KiB KiB was fastest in my benchmarks on an Apple M2 Ultra
1022    //
1023    // 4KiB ~7GiB/s
1024    // 64KiB ~22 GiB/s
1025    // 512KiB ~24 GiB/s
1026    let chunk_size = chunk_size.unwrap_or(524288);
1027
1028    let mut buf = vec![0; chunk_size];
1029
1030    while let Ok(n) = file.read(&mut buf) {
1031        if n == 0 {
1032            break;
1033        }
1034        digest.update(&buf[..n]);
1035    }
1036
1037    Ok(digest.finalize())
1038}
1039
1040/// Combines two CRC checksums using the specified algorithm.
1041///
1042/// # Examples
1043///```rust
1044/// use crc_fast::{checksum, checksum_combine, CrcAlgorithm::Crc32IsoHdlc};
1045///
1046/// let checksum_1 = checksum(Crc32IsoHdlc, b"1234");
1047/// let checksum_2 = checksum(Crc32IsoHdlc, b"56789");
1048/// let checksum = checksum_combine(Crc32IsoHdlc, checksum_1, checksum_2, 5);
1049///
1050/// assert_eq!(checksum, 0xcbf43926);
1051/// ```
1052#[inline(always)]
1053pub fn checksum_combine(
1054    algorithm: CrcAlgorithm,
1055    checksum1: u64,
1056    checksum2: u64,
1057    checksum2_len: u64,
1058) -> u64 {
1059    let params = get_calculator_params(algorithm).1;
1060
1061    combine::checksums(checksum1, checksum2, checksum2_len, &params)
1062}
1063
1064/// Combines two CRC checksums using custom CRC parameters.
1065///
1066/// # Examples
1067///
1068/// ```rust
1069/// use crc_fast::{checksum_with_params, checksum_combine_with_params, CrcParams};
1070///
1071/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
1072/// let custom_params = CrcParams::new(
1073///     "CRC-32/CUSTOM",
1074///     32,
1075///     0x04c11db7,
1076///     0xffffffff,
1077///     true,
1078///     0xffffffff,
1079///     0xcbf43926,
1080/// );
1081///
1082/// let checksum_1 = checksum_with_params(custom_params, b"1234");
1083/// let checksum_2 = checksum_with_params(custom_params, b"56789");
1084/// let checksum = checksum_combine_with_params(custom_params, checksum_1, checksum_2, 5);
1085///
1086/// assert_eq!(checksum, 0xcbf43926);
1087/// ```
1088pub fn checksum_combine_with_params(
1089    params: CrcParams,
1090    checksum1: u64,
1091    checksum2: u64,
1092    checksum2_len: u64,
1093) -> u64 {
1094    combine::checksums(checksum1, checksum2, checksum2_len, &params)
1095}
1096
1097/// Returns the target used to calculate the CRC checksum for the specified algorithm.
1098///
1099/// This function provides visibility into the active performance tier being used for CRC calculations.
1100/// The target string follows the format `{architecture}-{intrinsics-family}-{intrinsics-features}`,
1101/// such as `aarch64-aes-sha3` or `x86_64-avx512-vpclmulqdq`.
1102///
1103/// The performance tier system provides graceful degradation across different hardware capabilities:
1104/// - **AArch64**: `aarch64-aes-sha3` (highest) → `aarch64-aes-pmull` (baseline)
1105/// - **x86_64**: `x86_64-avx512-vpclmulqdq` (highest) → `x86_64-avx512-pclmulqdq` (mid) → `x86_64-sse-pclmulqdq` (baseline)
1106/// - **x86**: `x86-sse-pclmulqdq` (baseline) → `software-fallback-tables` (fallback)
1107/// - **Other architectures**: `software-fallback-tables`
1108///
1109/// The tier selection is deterministic and consistent across runs on the same hardware,
1110/// combining compile-time and runtime feature detection for safety and optimal performance.
1111///
1112/// These strings are informational only, not stable, and shouldn't be relied on to match across
1113/// versions.
1114///
1115/// # Examples
1116///```rust
1117/// use crc_fast::{get_calculator_target, CrcAlgorithm::Crc32IsoHdlc};
1118///
1119/// let target = get_calculator_target(Crc32IsoHdlc);
1120/// println!("Using performance tier: {}", target);
1121/// // Example outputs:
1122/// // "aarch64-aes-sha3" - AArch64 with SHA3 and AES support
1123/// // "x86_64-avx512-vpclmulqdq" - x86_64 with VPCLMULQDQ support
1124/// // "x86_64-sse-pclmulqdq" - x86_64 baseline with SSE4.1 and PCLMULQDQ
1125/// ```
1126#[cfg(all(
1127    feature = "alloc",
1128    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
1129))]
1130pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
1131    let arch_ops = get_arch_ops();
1132    arch_ops.get_target_string()
1133}
1134
1135/// Calculates the CRC-32/ISCSI checksum (commonly called "crc32c" in many, but not all,
1136/// implementations).
1137///
1138/// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi
1139///
1140/// Nano-optimized to be faster than calling checksum() for this specific algorithm, since it avoids
1141/// the match statement overhead.
1142///
1143/// # Examples
1144///
1145/// ```rust
1146/// use crc_fast::crc32_iscsi;
1147/// let checksum = crc32_iscsi(b"123456789");
1148/// assert_eq!(checksum, 0xe3069283);
1149/// ```
1150#[inline(always)]
1151pub fn crc32_iscsi(data: &[u8]) -> u32 {
1152    crc32_iscsi_calculator(CRC32_ISCSI.init, data, &CRC32_ISCSI) as u32 ^ CRC32_ISCSI.xorout as u32
1153}
1154
1155/// Calculates the CRC-32/ISO-HDLC checksum (commonly called "crc32" in many, but not all,
1156/// implementations).
1157///
1158/// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iso-hdlc
1159///
1160/// Nano-optimized to be faster than calling checksum() for this specific algorithm, since it avoids
1161/// the match statement overhead.
1162///
1163/// # Examples
1164///
1165/// ```rust
1166/// use crc_fast::crc32_iso_hdlc;
1167/// let checksum = crc32_iso_hdlc(b"123456789");
1168/// assert_eq!(checksum, 0xcbf43926);
1169/// ```
1170#[inline(always)]
1171pub fn crc32_iso_hdlc(data: &[u8]) -> u32 {
1172    crc32_iso_hdlc_calculator(CRC32_ISO_HDLC.init, data, &CRC32_ISO_HDLC) as u32
1173        ^ CRC32_ISO_HDLC.xorout as u32
1174}
1175
1176/// Calculates the CRC-64/NVME checksum.
1177///
1178/// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme
1179///
1180/// Nano-optimized to be faster than calling checksum() for this specific algorithm, since it avoids
1181/// the match statement overhead.
1182///
1183/// # Examples
1184///
1185/// ```rust
1186/// use crc_fast::crc64_nvme;
1187/// let checksum = crc64_nvme(b"123456789");
1188/// assert_eq!(checksum, 0xae8b14860a799888);
1189/// ```
1190#[inline(always)]
1191pub fn crc64_nvme(data: &[u8]) -> u64 {
1192    Calculator::calculate(CRC64_NVME.init, data, &CRC64_NVME) ^ CRC64_NVME.xorout
1193}
1194
1195/// Fallback version of get_calculator_target for unsupported architectures
1196#[cfg(all(
1197    feature = "alloc",
1198    not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))
1199))]
1200pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
1201    extern crate alloc;
1202    use alloc::string::ToString;
1203    "software-fallback-tables".to_string()
1204}
1205
1206/// Returns the calculator function and parameters for the specified CRC algorithm.
1207#[inline(always)]
1208#[allow(deprecated)]
1209fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
1210    match algorithm {
1211        CrcAlgorithm::Crc16Arc => (Calculator::calculate as CalculatorFn, CRC16_ARC),
1212        CrcAlgorithm::Crc16Cdma2000 => (Calculator::calculate as CalculatorFn, CRC16_CDMA2000),
1213        CrcAlgorithm::Crc16Cms => (Calculator::calculate as CalculatorFn, CRC16_CMS),
1214        CrcAlgorithm::Crc16Dds110 => (Calculator::calculate as CalculatorFn, CRC16_DDS_110),
1215        CrcAlgorithm::Crc16DectR => (Calculator::calculate as CalculatorFn, CRC16_DECT_R),
1216        CrcAlgorithm::Crc16DectX => (Calculator::calculate as CalculatorFn, CRC16_DECT_X),
1217        CrcAlgorithm::Crc16Dnp => (Calculator::calculate as CalculatorFn, CRC16_DNP),
1218        CrcAlgorithm::Crc16En13757 => (Calculator::calculate as CalculatorFn, CRC16_EN_13757),
1219        CrcAlgorithm::Crc16Genibus => (Calculator::calculate as CalculatorFn, CRC16_GENIBUS),
1220        CrcAlgorithm::Crc16Gsm => (Calculator::calculate as CalculatorFn, CRC16_GSM),
1221        CrcAlgorithm::Crc16Ibm3740 => (Calculator::calculate as CalculatorFn, CRC16_IBM_3740),
1222        CrcAlgorithm::Crc16IbmSdlc => (Calculator::calculate as CalculatorFn, CRC16_IBM_SDLC),
1223        CrcAlgorithm::Crc16IsoIec144433A => (
1224            Calculator::calculate as CalculatorFn,
1225            CRC16_ISO_IEC_14443_3_A,
1226        ),
1227        CrcAlgorithm::Crc16Kermit => (Calculator::calculate as CalculatorFn, CRC16_KERMIT),
1228        CrcAlgorithm::Crc16Lj1200 => (Calculator::calculate as CalculatorFn, CRC16_LJ1200),
1229        CrcAlgorithm::Crc16M17 => (Calculator::calculate as CalculatorFn, CRC16_M17),
1230        CrcAlgorithm::Crc16MaximDow => (Calculator::calculate as CalculatorFn, CRC16_MAXIM_DOW),
1231        CrcAlgorithm::Crc16Mcrf4xx => (Calculator::calculate as CalculatorFn, CRC16_MCRF4XX),
1232        CrcAlgorithm::Crc16Modbus => (Calculator::calculate as CalculatorFn, CRC16_MODBUS),
1233        CrcAlgorithm::Crc16Nrsc5 => (Calculator::calculate as CalculatorFn, CRC16_NRSC_5),
1234        CrcAlgorithm::Crc16OpensafetyA => {
1235            (Calculator::calculate as CalculatorFn, CRC16_OPENSAFETY_A)
1236        }
1237        CrcAlgorithm::Crc16OpensafetyB => {
1238            (Calculator::calculate as CalculatorFn, CRC16_OPENSAFETY_B)
1239        }
1240        CrcAlgorithm::Crc16Profibus => (Calculator::calculate as CalculatorFn, CRC16_PROFIBUS),
1241        CrcAlgorithm::Crc16Riello => (Calculator::calculate as CalculatorFn, CRC16_RIELLO),
1242        CrcAlgorithm::Crc16SpiFujitsu => (Calculator::calculate as CalculatorFn, CRC16_SPI_FUJITSU),
1243        CrcAlgorithm::Crc16T10Dif => (Calculator::calculate as CalculatorFn, CRC16_T10_DIF),
1244        CrcAlgorithm::Crc16Teledisk => (Calculator::calculate as CalculatorFn, CRC16_TELEDISK),
1245        CrcAlgorithm::Crc16Tms37157 => (Calculator::calculate as CalculatorFn, CRC16_TMS37157),
1246        CrcAlgorithm::Crc16Umts => (Calculator::calculate as CalculatorFn, CRC16_UMTS),
1247        CrcAlgorithm::Crc16Usb => (Calculator::calculate as CalculatorFn, CRC16_USB),
1248        CrcAlgorithm::Crc16Xmodem => (Calculator::calculate as CalculatorFn, CRC16_XMODEM),
1249        CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
1250        CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
1251        CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
1252        CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
1253        CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
1254        CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
1255        CrcAlgorithm::Crc32Custom => {
1256            panic!("Custom CRC-32 requires parameters via CrcParams::new()")
1257        }
1258        CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
1259        CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
1260        CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
1261        CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
1262        CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
1263        CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
1264        CrcAlgorithm::CrcCustom => {
1265            panic!("Custom CRC requires parameters via CrcParams::new()")
1266        }
1267        CrcAlgorithm::Crc64Custom => {
1268            panic!("Custom CRC-64 requires parameters via CrcParams::new()")
1269        }
1270        CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
1271        CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
1272        CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
1273        CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
1274        CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
1275        CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
1276        CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
1277    }
1278}
1279
1280/// Calculates the CRC-32/ISCSI (commonly called "crc32c" in many, but not all, implementations)
1281/// checksum.
1282///
1283/// Because both aarch64 and x86 have native hardware support for CRC-32/ISCSI, we can use
1284/// fusion techniques to accelerate the calculation beyond what SIMD can do alone.
1285#[inline(always)]
1286fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: &CrcParams) -> u64 {
1287    #[cfg(all(target_arch = "aarch64", feature = "std"))]
1288    {
1289        use crate::feature_detection::PerformanceTier;
1290
1291        let arch_ops = get_arch_ops();
1292        match arch_ops.get_tier() {
1293            PerformanceTier::AArch64AesSha3 | PerformanceTier::AArch64Aes => {
1294                return fusion::crc32_iscsi(state as u32, data) as u64;
1295            }
1296            _ => {}
1297        }
1298    }
1299
1300    #[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), feature = "std"))]
1301    {
1302        use crate::feature_detection::PerformanceTier;
1303
1304        let arch_ops = get_arch_ops();
1305        match arch_ops.get_tier() {
1306            PerformanceTier::X86_64Avx512Vpclmulqdq
1307            | PerformanceTier::X86_64Avx512Pclmulqdq
1308            | PerformanceTier::X86_64SsePclmulqdq
1309            | PerformanceTier::X86SsePclmulqdq => {
1310                // fusion path requires both pclmulqdq (checked by tier) and sse4.2 (for CRC32 instructions)
1311                if is_x86_feature_detected!("sse4.2") {
1312                    return fusion::crc32_iscsi(state as u32, data) as u64;
1313                }
1314            }
1315            _ => {}
1316        }
1317    }
1318
1319    Calculator::calculate(state, data, _params)
1320}
1321
1322/// Calculates the CRC-32/ISO-HDLC (commonly called "crc32" in many, but not all, implementations)
1323/// checksum.
1324///
1325/// Because aarch64 has native hardware support for CRC-32/ISO-HDLC, we can use fusion techniques
1326/// to accelerate the calculation beyond what SIMD can do alone. x86 does not have native support,
1327/// so we use the traditional calculation.
1328#[inline(always)]
1329fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: &CrcParams) -> u64 {
1330    #[cfg(all(target_arch = "aarch64", feature = "std"))]
1331    {
1332        use crate::feature_detection::{get_arch_ops, PerformanceTier};
1333        let arch_ops = get_arch_ops();
1334
1335        match arch_ops.get_tier() {
1336            PerformanceTier::AArch64AesSha3 | PerformanceTier::AArch64Aes => {
1337                return fusion::crc32_iso_hdlc(state as u32, data) as u64;
1338            }
1339            _ => {}
1340        }
1341    }
1342
1343    Calculator::calculate(state, data, _params)
1344}
1345
1346#[cfg(test)]
1347mod lib {
1348    #![allow(unused)]
1349
1350    use super::*;
1351    use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
1352    use crate::test::enums::AnyCrcTestConfig;
1353    use cbindgen::Language::C;
1354    use cbindgen::Style::Both;
1355    use rand::{rng, Rng};
1356    use std::fs::{read, write};
1357
1358    #[test]
1359    fn test_checksum_check() {
1360        for config in TEST_ALL_CONFIGS {
1361            assert_eq!(
1362                checksum(config.get_algorithm(), TEST_CHECK_STRING),
1363                config.get_check()
1364            );
1365        }
1366    }
1367
1368    #[test]
1369    fn test_checksum_reference() {
1370        for config in TEST_ALL_CONFIGS {
1371            assert_eq!(
1372                checksum(config.get_algorithm(), TEST_CHECK_STRING),
1373                config.checksum_with_reference(TEST_CHECK_STRING)
1374            );
1375        }
1376    }
1377
1378    #[test]
1379    fn test_checksum_with_custom_params() {
1380        crate::cache::clear_cache();
1381
1382        // CRC-32 reflected
1383        assert_eq!(
1384            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
1385            CRC32_ISCSI.check,
1386        );
1387
1388        // CRC-32 forward
1389        assert_eq!(
1390            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
1391            CRC32_BZIP2.check,
1392        );
1393
1394        // CRC-64 reflected
1395        assert_eq!(
1396            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
1397            CRC64_NVME.check,
1398        );
1399
1400        // CRC-64 forward
1401        assert_eq!(
1402            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
1403            CRC64_ECMA_182.check,
1404        );
1405    }
1406
1407    #[test]
1408    fn test_get_custom_params() {
1409        crate::cache::clear_cache();
1410
1411        assert_eq!(
1412            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
1413            CRC32_ISCSI.check,
1414        );
1415
1416        assert_eq!(
1417            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
1418            CRC32_BZIP2.check,
1419        );
1420
1421        assert_eq!(
1422            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
1423            CRC64_NVME.check,
1424        );
1425
1426        assert_eq!(
1427            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
1428            CRC64_ECMA_182.check,
1429        );
1430    }
1431
1432    #[test]
1433    fn test_get_calculator_target_format() {
1434        let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1435
1436        // Target string should not be empty
1437        assert!(!target.is_empty());
1438
1439        // Should follow the expected format with valid architecture prefixes
1440        let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
1441        assert!(
1442            valid_prefixes
1443                .iter()
1444                .any(|prefix| target.starts_with(prefix)),
1445            "Target '{}' should start with a valid architecture prefix",
1446            target
1447        );
1448
1449        // Should contain intrinsics family and features information
1450        let parts: Vec<&str> = target.split('-').collect();
1451        assert!(
1452            parts.len() >= 3,
1453            "Target '{}' should have at least 3 parts: architecture-family-features",
1454            target
1455        );
1456    }
1457
1458    #[test]
1459    fn test_get_calculator_target_consistency() {
1460        // Multiple calls should return the same result (deterministic)
1461        let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1462        let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1463        let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1464
1465        assert_eq!(
1466            target1, target2,
1467            "Target should be consistent across different CRC-32 algorithms"
1468        );
1469        assert_eq!(
1470            target1, target3,
1471            "Target should be consistent across CRC-32 and CRC-64 algorithms"
1472        );
1473    }
1474
1475    #[test]
1476    fn test_get_calculator_target_uses_cached_detection() {
1477        // This test verifies that the function uses cached feature detection
1478        // by checking that multiple calls are consistent and don't perform
1479        // redundant feature detection
1480
1481        let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1482        let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1483
1484        assert_eq!(
1485            target1, target2,
1486            "Cached detection should return identical results"
1487        );
1488    }
1489
1490    #[test]
1491    fn test_digest_updates_check() {
1492        for config in TEST_ALL_CONFIGS {
1493            check_digest(Digest::new(config.get_algorithm()), config.get_check());
1494        }
1495    }
1496
1497    #[test]
1498    fn test_digest_updates_check_with_custom_params() {
1499        crate::cache::clear_cache();
1500
1501        // CRC-32 reflected
1502        check_digest(
1503            Digest::new_with_params(get_custom_crc32_reflected()),
1504            CRC32_ISCSI.check,
1505        );
1506
1507        // CRC-32 forward
1508        check_digest(
1509            Digest::new_with_params(get_custom_crc32_forward()),
1510            CRC32_BZIP2.check,
1511        );
1512
1513        // CRC-64 reflected
1514        check_digest(
1515            Digest::new_with_params(get_custom_crc64_reflected()),
1516            CRC64_NVME.check,
1517        );
1518
1519        // CRC-64 forward
1520        check_digest(
1521            Digest::new_with_params(get_custom_crc64_forward()),
1522            CRC64_ECMA_182.check,
1523        );
1524    }
1525
1526    fn check_digest(mut digest: Digest, check: u64) {
1527        digest.update(b"123");
1528        digest.update(b"456");
1529        digest.update(b"789");
1530        assert_eq!(digest.finalize(), check,);
1531    }
1532
1533    #[test]
1534    fn test_1024_length() {
1535        for config in TEST_ALL_CONFIGS {
1536            test_length(1024, config);
1537        }
1538    }
1539
1540    /// Skipping for Miri runs due to time constraints, underlying code already covered by other
1541    /// tests.
1542    #[test]
1543    #[cfg_attr(miri, ignore)]
1544    fn test_small_all_lengths() {
1545        for config in TEST_ALL_CONFIGS {
1546            // Test each length from 1 to 255
1547            for len in 1..=255 {
1548                test_length(len, config);
1549            }
1550        }
1551    }
1552
1553    /// Skipping for Miri runs due to time constraints, underlying code already covered by other
1554    /// tests.
1555    #[test]
1556    #[cfg_attr(miri, ignore)]
1557    fn test_medium_lengths() {
1558        for config in TEST_ALL_CONFIGS {
1559            // Test each length from 256 to 1024, which should fold and include handling remainders
1560            for len in 256..=1024 {
1561                test_length(len, config);
1562            }
1563        }
1564    }
1565
1566    /// Skipping for Miri runs due to time constraints, underlying code already covered by other
1567    /// tests.
1568    #[test]
1569    #[cfg_attr(miri, ignore)]
1570    fn test_large_lengths() {
1571        for config in TEST_ALL_CONFIGS {
1572            // Test 1 MiB just before, at, and just after the folding boundaries
1573            for len in 1048575..1048577 {
1574                test_length(len, config);
1575            }
1576        }
1577    }
1578
1579    fn test_length(length: usize, config: &AnyCrcTestConfig) {
1580        let mut data = vec![0u8; length];
1581        rng().fill(&mut data[..]);
1582
1583        // Calculate expected CRC using the reference implementation
1584        let expected = config.checksum_with_reference(&data);
1585
1586        let result = checksum(config.get_algorithm(), &data);
1587
1588        assert_eq!(
1589            result,
1590            expected,
1591            "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1592            config.get_algorithm(),
1593            length,
1594            expected,
1595            result
1596        );
1597    }
1598
1599    #[test]
1600    fn test_combine() {
1601        for config in TEST_ALL_CONFIGS {
1602            let algorithm = config.get_algorithm();
1603            let check = config.get_check();
1604
1605            // checksums
1606            let checksum1 = checksum(algorithm, "1234".as_ref());
1607            let checksum2 = checksum(algorithm, "56789".as_ref());
1608
1609            // checksum_combine()
1610            assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check);
1611
1612            // Digest
1613            let mut digest1 = Digest::new(algorithm);
1614            digest1.update("1234".as_ref());
1615
1616            let mut digest2 = Digest::new(algorithm);
1617            digest2.update("56789".as_ref());
1618
1619            digest1.combine(&digest2);
1620
1621            assert_eq!(digest1.finalize(), check)
1622        }
1623    }
1624
1625    #[test]
1626    fn test_combine_with_custom_params() {
1627        crate::cache::clear_cache();
1628
1629        // CRC-32 reflected
1630        let crc32_params = get_custom_crc32_reflected();
1631        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1632        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1633        assert_eq!(
1634            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1635            CRC32_ISCSI.check,
1636        );
1637
1638        // CRC-32 forward
1639        let crc32_params = get_custom_crc32_forward();
1640        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1641        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1642        assert_eq!(
1643            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1644            CRC32_BZIP2.check,
1645        );
1646
1647        // CRC-64 reflected
1648        let crc64_params = get_custom_crc64_reflected();
1649        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1650        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1651        assert_eq!(
1652            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1653            CRC64_NVME.check,
1654        );
1655
1656        // CRC-64 forward
1657        let crc64_params = get_custom_crc64_forward();
1658        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1659        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1660        assert_eq!(
1661            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1662            CRC64_ECMA_182.check,
1663        );
1664    }
1665
1666    /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already
1667    /// covered by other tests.
1668    #[test]
1669    #[cfg_attr(miri, ignore)]
1670    fn test_checksum_file() {
1671        // Create a test file with repeating zeros
1672        let test_file_path = "test/test_crc32_hash_file.bin";
1673        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1674        if let Err(e) = write(test_file_path, &data) {
1675            eprintln!("Skipping test due to write error: {}", e);
1676            return;
1677        }
1678
1679        for config in TEST_ALL_CONFIGS {
1680            let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1681            assert_eq!(result, config.checksum_with_reference(&data));
1682        }
1683
1684        std::fs::remove_file(test_file_path).unwrap();
1685    }
1686
1687    /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already
1688    /// covered by other tests.
1689    #[test]
1690    #[cfg_attr(miri, ignore)]
1691    fn test_checksum_file_with_custom_params() {
1692        crate::cache::clear_cache();
1693
1694        // Create a test file with repeating zeros
1695        let test_file_path = "test/test_crc32_hash_file_custom.bin";
1696        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1697        if let Err(e) = write(test_file_path, &data) {
1698            eprintln!("Skipping test due to write error: {}", e);
1699            return;
1700        }
1701
1702        // CRC-32 reflected
1703        check_file(
1704            get_custom_crc32_reflected(),
1705            test_file_path,
1706            CRC32_ISCSI.check,
1707        );
1708
1709        // CRC-32 forward
1710        check_file(
1711            get_custom_crc32_forward(),
1712            test_file_path,
1713            CRC32_BZIP2.check,
1714        );
1715
1716        // CRC-64 reflected
1717        check_file(
1718            get_custom_crc64_reflected(),
1719            test_file_path,
1720            CRC64_NVME.check,
1721        );
1722
1723        // CRC-64 forward
1724        check_file(
1725            get_custom_crc64_forward(),
1726            test_file_path,
1727            CRC64_ECMA_182.check,
1728        );
1729
1730        std::fs::remove_file(test_file_path).unwrap();
1731    }
1732
1733    fn check_file(params: CrcParams, file_path: &str, check: u64) {
1734        let result = checksum_file_with_params(params, file_path, None).unwrap();
1735        assert_eq!(result, check);
1736    }
1737
1738    /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already
1739    /// covered by other tests.
1740    #[test]
1741    #[cfg_attr(miri, ignore)]
1742    fn test_writer() {
1743        // Create a test file with repeating zeros
1744        let test_file_path = "test/test_crc32_writer_file.bin";
1745        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1746        if let Err(e) = std::fs::write(test_file_path, &data) {
1747            eprintln!("Skipping test due to write error: {}", e);
1748            return;
1749        }
1750
1751        for config in TEST_ALL_CONFIGS {
1752            let mut digest = Digest::new(config.get_algorithm());
1753            let mut file = File::open(test_file_path).unwrap();
1754            std::io::copy(&mut file, &mut digest).unwrap();
1755            assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1756        }
1757
1758        std::fs::remove_file(test_file_path).unwrap();
1759    }
1760    #[test]
1761    fn test_digest_reset() {
1762        for config in TEST_ALL_CONFIGS {
1763            let mut digest = Digest::new(config.get_algorithm());
1764            digest.update(b"42");
1765            digest.reset();
1766            digest.update(TEST_CHECK_STRING);
1767            assert_eq!(digest.finalize(), config.get_check());
1768        }
1769    }
1770
1771    #[test]
1772    fn test_digest_finalize_reset() {
1773        for config in TEST_ALL_CONFIGS {
1774            let check = config.get_check();
1775
1776            let mut digest = Digest::new(config.get_algorithm());
1777            digest.update(TEST_CHECK_STRING);
1778            assert_eq!(digest.finalize_reset(), check);
1779
1780            digest.update(TEST_CHECK_STRING);
1781            assert_eq!(digest.finalize(), check);
1782        }
1783    }
1784
1785    #[test]
1786    fn test_digest_finalize_into() {
1787        for config in TEST_ALL_CONFIGS {
1788            let mut digest = Digest::new(config.get_algorithm());
1789            digest.update(TEST_CHECK_STRING);
1790
1791            match digest.params.width {
1792                16 => {
1793                    let mut output = [0u8; 2];
1794                    digest.finalize_into(&mut output).unwrap();
1795                    let result = u16::from_be_bytes(output) as u64;
1796                    assert_eq!(result, config.get_check());
1797                }
1798                32 => {
1799                    let mut output = [0u8; 4];
1800                    digest.finalize_into(&mut output).unwrap();
1801                    let result = u32::from_be_bytes(output) as u64;
1802                    assert_eq!(result, config.get_check());
1803                }
1804                64 => {
1805                    let mut output = [0u8; 8];
1806                    digest.finalize_into(&mut output).unwrap();
1807                    let result = u64::from_be_bytes(output);
1808                    assert_eq!(result, config.get_check());
1809                }
1810                _ => panic!("Unsupported CRC width"),
1811            }
1812        }
1813    }
1814
1815    #[test]
1816    fn test_digest_finalize_into_reset() {
1817        for config in TEST_ALL_CONFIGS {
1818            let mut digest = Digest::new(config.get_algorithm());
1819            digest.update(TEST_CHECK_STRING);
1820
1821            let mut output: Vec<u8> = match digest.params.width {
1822                16 => vec![0u8; 2],
1823                32 => vec![0u8; 4],
1824                64 => vec![0u8; 8],
1825                _ => panic!("Unsupported CRC width"),
1826            };
1827
1828            digest.finalize_into_reset(&mut output).unwrap();
1829            let result = match output.len() {
1830                2 => u16::from_be_bytes(output.try_into().unwrap()) as u64,
1831                4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1832                8 => u64::from_be_bytes(output.try_into().unwrap()),
1833                _ => panic!("Unsupported CRC width"),
1834            };
1835            assert_eq!(result, config.get_check());
1836
1837            digest.update(TEST_CHECK_STRING);
1838            assert_eq!(digest.finalize(), config.get_check());
1839        }
1840    }
1841
1842    /// Tests whether the FFI header is up-to-date
1843    #[test]
1844    #[cfg_attr(miri, ignore)]
1845    fn test_ffi_header() -> Result<(), String> {
1846        #[cfg(target_os = "windows")]
1847        {
1848            // Skip this test on Windows, since CRLF vs LF is a PITA
1849            eprintln!("Skipping test on Windows");
1850
1851            return Ok(());
1852        }
1853
1854        const HEADER: &str = "libcrc_fast.h";
1855
1856        let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1857
1858        let mut expected = Vec::new();
1859        cbindgen::Builder::new()
1860            .with_crate(crate_dir)
1861            .with_include_guard("CRC_FAST_H")
1862            .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1863            // exclude internal implementation functions
1864            .exclude_item("crc32_iscsi_impl")
1865            .exclude_item("crc32_iso_hdlc_impl")
1866            .exclude_item("get_iscsi_target")
1867            .exclude_item("get_iso_hdlc_target")
1868            .exclude_item("ISO_HDLC_TARGET")
1869            .exclude_item("ISCSI_TARGET")
1870            .exclude_item("CrcParams")
1871            .rename_item("Digest", "CrcFastDigest")
1872            .with_style(Both)
1873            // generate C header
1874            .with_language(C)
1875            // with C++ compatibility
1876            .with_cpp_compat(true)
1877            .generate()
1878            .map_err(|error| error.to_string())?
1879            .write(&mut expected);
1880
1881        // Convert the expected bytes to string for pattern replacement, since cbindgen
1882        // generates an annoying amount of empty contiguous newlines
1883        let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1884
1885        // Replace excessive newlines (3 or more consecutive newlines) with 2 newlines
1886        let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1887        let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1888
1889        // Convert back to bytes
1890        expected = cleaned_content.into_bytes();
1891
1892        let actual = read(HEADER).map_err(|error| error.to_string())?;
1893
1894        if expected != actual {
1895            write(HEADER, expected).map_err(|error| error.to_string())?;
1896            return Err(format!(
1897                "{HEADER} is not up-to-date, commit the generated file and try again"
1898            ));
1899        }
1900
1901        Ok(())
1902    }
1903
1904    fn get_custom_crc32_reflected() -> CrcParams {
1905        CrcParams::new(
1906            "Custom CRC-32/ISCSI",
1907            32,
1908            CRC32_ISCSI.poly,
1909            CRC32_ISCSI.init,
1910            CRC32_ISCSI.refin,
1911            CRC32_ISCSI.xorout,
1912            CRC32_ISCSI.check,
1913        )
1914    }
1915
1916    fn get_custom_crc32_forward() -> CrcParams {
1917        CrcParams::new(
1918            "Custom CRC-32/BZIP2",
1919            32,
1920            CRC32_BZIP2.poly,
1921            CRC32_BZIP2.init,
1922            CRC32_BZIP2.refin,
1923            CRC32_BZIP2.xorout,
1924            CRC32_BZIP2.check,
1925        )
1926    }
1927
1928    fn get_custom_crc64_reflected() -> CrcParams {
1929        CrcParams::new(
1930            "Custom CRC-64/NVME",
1931            64,
1932            CRC64_NVME.poly,
1933            CRC64_NVME.init,
1934            CRC64_NVME.refin,
1935            CRC64_NVME.xorout,
1936            CRC64_NVME.check,
1937        )
1938    }
1939
1940    fn get_custom_crc64_forward() -> CrcParams {
1941        CrcParams::new(
1942            "Custom CRC-64/ECMA-182",
1943            64,
1944            CRC64_ECMA_182.poly,
1945            CRC64_ECMA_182.init,
1946            CRC64_ECMA_182.refin,
1947            CRC64_ECMA_182.xorout,
1948            CRC64_ECMA_182.check,
1949        )
1950    }
1951
1952    #[test]
1953    #[allow(clippy::needless_range_loop)] // Intentionally testing indexed get_key() method
1954    fn test_crc_keys_storage_fold_256() {
1955        let test_keys = [
1956            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1957        ];
1958        let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1959
1960        // Test valid key access
1961        for i in 0..23 {
1962            assert_eq!(storage.get_key(i), test_keys[i]);
1963        }
1964
1965        // Test out-of-bounds access returns 0
1966        assert_eq!(storage.get_key(23), 0);
1967        assert_eq!(storage.get_key(24), 0);
1968        assert_eq!(storage.get_key(100), 0);
1969
1970        // Test key count
1971        assert_eq!(storage.key_count(), 23);
1972    }
1973
1974    #[test]
1975    #[allow(clippy::needless_range_loop)] // Intentionally testing indexed get_key() method
1976    fn test_crc_keys_storage_future_test() {
1977        let test_keys = [
1978            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1979            25,
1980        ];
1981        let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1982
1983        // Test valid key access
1984        for i in 0..25 {
1985            assert_eq!(storage.get_key(i), test_keys[i]);
1986        }
1987
1988        // Test out-of-bounds access returns 0
1989        assert_eq!(storage.get_key(25), 0);
1990        assert_eq!(storage.get_key(26), 0);
1991        assert_eq!(storage.get_key(100), 0);
1992
1993        // Test key count
1994        assert_eq!(storage.key_count(), 25);
1995    }
1996
1997    #[test]
1998    #[allow(clippy::needless_range_loop)] // Intentionally testing indexed get_key() and get_key_checked() methods
1999    fn test_crc_params_safe_accessors() {
2000        // Create a test CrcParams with known keys
2001        let test_keys = [
2002            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
2003        ];
2004        let params = CrcParams {
2005            algorithm: CrcAlgorithm::Crc32IsoHdlc,
2006            name: "test",
2007            width: 32,
2008            poly: 0x04C11DB7,
2009            init: 0xFFFFFFFF,
2010            init_algorithm: 0xFFFFFFFF,
2011            refin: true,
2012            refout: true,
2013            xorout: 0xFFFFFFFF,
2014            check: 0xCBF43926,
2015            keys: CrcKeysStorage::from_keys_fold_256(test_keys),
2016        };
2017
2018        // Test valid key access
2019        for i in 0..23 {
2020            assert_eq!(params.get_key(i), test_keys[i]);
2021            assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
2022        }
2023
2024        // Test out-of-bounds access
2025        assert_eq!(params.get_key(23), 0);
2026        assert_eq!(params.get_key(24), 0);
2027        assert_eq!(params.get_key(100), 0);
2028
2029        assert_eq!(params.get_key_checked(23), None);
2030        assert_eq!(params.get_key_checked(24), None);
2031        assert_eq!(params.get_key_checked(100), None);
2032
2033        // Test key count
2034        assert_eq!(params.key_count(), 23);
2035    }
2036
2037    #[test]
2038    fn test_crc_keys_storage_const_constructors() {
2039        // Test that const constructors work in const context
2040        const TEST_KEYS_23: [u64; 23] = [1; 23];
2041        const TEST_KEYS_25: [u64; 25] = [2; 25];
2042
2043        const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
2044        const STORAGE_FUTURE: CrcKeysStorage =
2045            CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
2046
2047        // Verify the const constructors work correctly
2048        assert_eq!(STORAGE_256.get_key(0), 1);
2049        assert_eq!(STORAGE_256.key_count(), 23);
2050
2051        assert_eq!(STORAGE_FUTURE.get_key(0), 2);
2052        assert_eq!(STORAGE_FUTURE.key_count(), 25);
2053    }
2054
2055    #[test]
2056    fn test_crc_keys_storage_bounds_safety() {
2057        let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
2058        let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
2059
2060        // Test edge cases for bounds checking
2061        assert_eq!(storage_256.get_key(22), 42); // Last valid index
2062        assert_eq!(storage_256.get_key(23), 0); // First invalid index
2063
2064        assert_eq!(storage_future.get_key(24), 84); // Last valid index
2065        assert_eq!(storage_future.get_key(25), 0); // First invalid index
2066
2067        // Test very large indices
2068        assert_eq!(storage_256.get_key(usize::MAX), 0);
2069        assert_eq!(storage_future.get_key(usize::MAX), 0);
2070    }
2071}