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