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
135use crate::crc32::consts::{
136    CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
137    CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
138};
139
140#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
141use crate::crc32::fusion;
142
143use crate::crc64::consts::{
144    CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
145};
146use crate::structs::Calculator;
147use crate::traits::CrcCalculator;
148use digest::{DynDigest, InvalidBufferSize};
149
150#[cfg(feature = "std")]
151use std::fs::File;
152#[cfg(feature = "std")]
153use std::io::{Read, Write};
154
155mod algorithm;
156mod arch;
157mod cache;
158mod combine;
159mod consts;
160mod crc32;
161mod crc64;
162mod enums;
163mod feature_detection;
164mod ffi;
165mod generate;
166mod structs;
167mod test;
168mod traits;
169
170/// Supported CRC-32 and CRC-64 variants
171#[derive(Debug, Clone, Copy, PartialEq)]
172pub enum CrcAlgorithm {
173    Crc32Aixm,
174    Crc32Autosar,
175    Crc32Base91D,
176    Crc32Bzip2,
177    Crc32CdRomEdc,
178    Crc32Cksum,
179    Crc32Custom, // Custom CRC-32 implementation, not defined in consts
180    Crc32Iscsi,
181    Crc32IsoHdlc,
182    Crc32Jamcrc,
183    Crc32Mef,
184    Crc32Mpeg2,
185    Crc32Xfer,
186    Crc64Custom, // Custom CRC-64 implementation, not defined in consts
187    Crc64Ecma182,
188    Crc64GoIso,
189    Crc64Ms,
190    Crc64Nvme,
191    Crc64Redis,
192    Crc64We,
193    Crc64Xz,
194}
195
196/// Internal storage for CRC folding keys that can accommodate different array sizes.
197/// This enum allows future expansion to support larger folding distances while maintaining
198/// backwards compatibility with existing const definitions.
199#[derive(Clone, Copy, Debug, PartialEq)]
200pub enum CrcKeysStorage {
201    /// Current 23-key format for existing algorithms (supports up to 256-byte folding distances)
202    KeysFold256([u64; 23]),
203    /// Future 25-key format for potential expanded folding distances (testing purposes only)
204    KeysFutureTest([u64; 25]),
205}
206
207impl CrcKeysStorage {
208    /// Safe key access with bounds checking. Returns 0 for out-of-bounds indices.
209    #[inline(always)]
210    const fn get_key(self, index: usize) -> u64 {
211        match self {
212            CrcKeysStorage::KeysFold256(keys) => {
213                if index < 23 {
214                    keys[index]
215                } else {
216                    0
217                }
218            }
219            CrcKeysStorage::KeysFutureTest(keys) => {
220                if index < 25 {
221                    keys[index]
222                } else {
223                    0
224                }
225            }
226        }
227    }
228
229    /// Returns the number of keys available in this storage variant.
230    #[inline(always)]
231    const fn key_count(self) -> usize {
232        match self {
233            CrcKeysStorage::KeysFold256(_) => 23,
234            CrcKeysStorage::KeysFutureTest(_) => 25,
235        }
236    }
237
238    /// Const constructor for 23-key arrays (current format).
239    #[inline(always)]
240    const fn from_keys_fold_256(keys: [u64; 23]) -> Self {
241        CrcKeysStorage::KeysFold256(keys)
242    }
243
244    /// Const constructor for 25-key arrays (future expansion testing).
245    #[inline(always)]
246    #[allow(dead_code)] // Reserved for future expansion
247    const fn from_keys_fold_future_test(keys: [u64; 25]) -> Self {
248        CrcKeysStorage::KeysFutureTest(keys)
249    }
250
251    /// Extracts keys as a [u64; 23] array for FFI compatibility.
252    /// For variants with more than 23 keys, only the first 23 are returned.
253    /// For variants with fewer keys, remaining slots are filled with 0.
254    #[inline(always)]
255    pub fn to_keys_array_23(self) -> [u64; 23] {
256        match self {
257            CrcKeysStorage::KeysFold256(keys) => keys,
258            CrcKeysStorage::KeysFutureTest(keys) => {
259                let mut result = [0u64; 23];
260                result.copy_from_slice(&keys[..23]);
261                result
262            }
263        }
264    }
265}
266
267// Implement PartialEq between CrcKeysStorage and [u64; 23] for test compatibility
268impl PartialEq<[u64; 23]> for CrcKeysStorage {
269    fn eq(&self, other: &[u64; 23]) -> bool {
270        self.to_keys_array_23() == *other
271    }
272}
273
274impl PartialEq<CrcKeysStorage> for [u64; 23] {
275    fn eq(&self, other: &CrcKeysStorage) -> bool {
276        *self == other.to_keys_array_23()
277    }
278}
279
280/// Parameters for CRC computation, including polynomial, initial value, and other settings.
281#[derive(Clone, Copy, Debug)]
282pub struct CrcParams {
283    pub algorithm: CrcAlgorithm,
284    pub name: &'static str,
285    pub width: u8,
286    pub poly: u64,
287    pub init: u64,
288    pub refin: bool,
289    pub refout: bool,
290    pub xorout: u64,
291    pub check: u64,
292    pub keys: CrcKeysStorage,
293}
294
295/// Type alias for a function pointer that represents a CRC calculation function.
296///
297/// The function takes the following parameters:
298/// - `state`: The current state of the CRC computation.
299/// - `data`: A slice of bytes to be processed.
300/// - `params`: The parameters for the CRC computation, such as polynomial, initial value, etc.
301///
302/// The function returns the updated state after processing the data.
303type CalculatorFn = fn(
304    u64,       // state
305    &[u8],     // data
306    CrcParams, // CRC implementation parameters
307) -> u64;
308
309/// Represents a CRC Digest, which is used to compute CRC checksums.
310///
311/// The `Digest` struct maintains the state of the CRC computation, including
312/// the current state, the amount of data processed, the CRC parameters, and
313/// the calculator function used to perform the CRC calculation.
314#[derive(Copy, Clone, Debug)]
315pub struct Digest {
316    /// The current state of the CRC computation.
317    state: u64,
318
319    /// The total amount of data processed so far.
320    amount: u64,
321
322    /// The parameters for the CRC computation, such as polynomial, initial value, etc.
323    params: CrcParams,
324
325    /// The function used to perform the CRC calculation.
326    calculator: CalculatorFn,
327}
328
329impl DynDigest for Digest {
330    #[inline(always)]
331    fn update(&mut self, data: &[u8]) {
332        self.update(data);
333    }
334
335    #[inline(always)]
336    fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
337        if buf.len() != self.output_size() {
338            return Err(InvalidBufferSize);
339        }
340
341        let result = self.finalize();
342        let bytes = if self.output_size() == 4 {
343            result.to_be_bytes()[4..].to_vec() // Take last 4 bytes for 32-bit CRC
344        } else {
345            result.to_be_bytes().to_vec() // Use all 8 bytes for 64-bit CRC
346        };
347        buf.copy_from_slice(&bytes[..self.output_size()]);
348
349        Ok(())
350    }
351
352    #[inline(always)]
353    fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
354        if out.len() != self.output_size() {
355            return Err(InvalidBufferSize);
356        }
357        let result = self.finalize();
358        self.reset();
359        let bytes = if self.output_size() == 4 {
360            result.to_be_bytes()[4..].to_vec() // Take last 4 bytes for 32-bit CRC
361        } else {
362            result.to_be_bytes().to_vec() // Use all 8 bytes for 64-bit CRC
363        };
364        out.copy_from_slice(&bytes[..self.output_size()]);
365        Ok(())
366    }
367
368    #[inline(always)]
369    fn reset(&mut self) {
370        self.reset();
371    }
372
373    #[inline(always)]
374    fn output_size(&self) -> usize {
375        self.params.width as usize / 8
376    }
377
378    fn box_clone(&self) -> Box<dyn DynDigest> {
379        Box::new(*self)
380    }
381}
382
383impl Digest {
384    /// Creates a new `Digest` instance for the specified CRC algorithm.
385    ///
386    /// # Examples
387    ///
388    /// ```rust
389    /// use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
390    ///
391    /// let mut digest = Digest::new(Crc32IsoHdlc);
392    /// digest.update(b"123456789");
393    /// let checksum = digest.finalize();
394    ///
395    /// assert_eq!(checksum, 0xcbf43926);
396    /// ```
397    #[inline(always)]
398    pub fn new(algorithm: CrcAlgorithm) -> Self {
399        let (calculator, params) = get_calculator_params(algorithm);
400
401        Self {
402            state: params.init,
403            amount: 0,
404            params,
405            calculator,
406        }
407    }
408
409    /// Creates a new `Digest` instance for the specified CRC algorithm with a custom initial state.
410    ///
411    /// # Examples
412    ///
413    /// ```rust
414    /// use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
415    ///
416    /// // CRC-32/ISO-HDLC with initial state of 0x00000000, instead of the default initial state
417    /// // of 0xffffffff,
418    /// let mut digest = Digest::new_with_init_state(Crc32IsoHdlc, 0x00000000);
419    /// digest.update(b"123456789");
420    /// let checksum = digest.finalize();
421    ///
422    /// // different initial state, so checksum will be different
423    /// assert_eq!(checksum, 0xd202d277);
424    ///
425    /// let mut digest = Digest::new_with_init_state(Crc32IsoHdlc, 0xffffffff);
426    /// digest.update(b"123456789");
427    /// let checksum = digest.finalize();
428    ///
429    /// // same initial state as the default, so checksum will be the same
430    /// assert_eq!(checksum, 0xcbf43926);
431    /// ```
432    #[inline(always)]
433    pub fn new_with_init_state(algorithm: CrcAlgorithm, init_state: u64) -> Self {
434        let (calculator, params) = get_calculator_params(algorithm);
435
436        Self {
437            state: init_state,
438            amount: 0,
439            params,
440            calculator,
441        }
442    }
443
444    /// Creates a new `Digest` instance with custom CRC parameters.
445    ///
446    /// # Examples
447    ///
448    /// ```rust
449    /// use crc_fast::{Digest, CrcParams};
450    ///
451    /// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
452    /// let custom_params = CrcParams::new(
453    ///     "CRC-32/CUSTOM",
454    ///     32,
455    ///     0x04c11db7,
456    ///     0xffffffff,
457    ///     true,
458    ///     0xffffffff,
459    ///     0xcbf43926,
460    /// );
461    ///
462    /// let mut digest = Digest::new_with_params(custom_params);
463    /// digest.update(b"123456789");
464    /// let checksum = digest.finalize();
465    ///
466    /// assert_eq!(checksum, 0xcbf43926);
467    /// ```
468    #[inline(always)]
469    pub fn new_with_params(params: CrcParams) -> Self {
470        let calculator = Calculator::calculate as CalculatorFn;
471
472        Self {
473            state: params.init,
474            amount: 0,
475            params,
476            calculator,
477        }
478    }
479
480    /// Updates the CRC state with the given data.
481    #[inline(always)]
482    pub fn update(&mut self, data: &[u8]) {
483        self.state = (self.calculator)(self.state, data, self.params);
484        self.amount += data.len() as u64;
485    }
486
487    /// Finalizes the CRC computation and returns the result.
488    #[inline(always)]
489    pub fn finalize(&self) -> u64 {
490        self.state ^ self.params.xorout
491    }
492
493    /// Finalizes the CRC computation, resets the state, and returns the result.
494    #[inline(always)]
495    pub fn finalize_reset(&mut self) -> u64 {
496        let result = self.finalize();
497        self.reset();
498
499        result
500    }
501
502    /// Resets the CRC state to its initial value.
503    #[inline(always)]
504    pub fn reset(&mut self) {
505        self.state = self.params.init;
506        self.amount = 0;
507    }
508
509    /// Combines the CRC state with a second `Digest` instance.
510    #[inline(always)]
511    pub fn combine(&mut self, other: &Self) {
512        self.amount += other.amount;
513        let other_crc = other.finalize();
514
515        // note the xorout for the input, since it's already been applied so it has to be removed,
516        // and then re-adding it on the final output
517        self.state = combine::checksums(
518            self.state ^ self.params.xorout,
519            other_crc,
520            other.amount,
521            self.params,
522        ) ^ self.params.xorout;
523    }
524
525    /// Gets the amount of data processed so far
526    #[inline(always)]
527    pub fn get_amount(&self) -> u64 {
528        self.amount
529    }
530
531    /// Gets the current CRC state.
532    ///
533    /// # Examples
534    /// ```rust
535    /// use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc};
536    ///
537    /// let mut digest = Digest::new(Crc32IsoHdlc);
538    /// digest.update(b"123456789");
539    /// let state = digest.get_state();
540    ///
541    /// // non-finalized state, so it won't match the final checksum
542    /// assert_eq!(state, 0x340bc6d9);
543    ///
544    /// // finalized state will match the checksum
545    /// assert_eq!(digest.finalize(), 0xcbf43926);
546    /// ```
547    #[inline(always)]
548    pub fn get_state(&self) -> u64 {
549        self.state
550    }
551}
552
553#[cfg(feature = "std")]
554impl Write for Digest {
555    #[inline(always)]
556    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
557        self.update(buf);
558        Ok(buf.len())
559    }
560
561    #[inline(always)]
562    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
563        let len: usize = bufs
564            .iter()
565            .map(|buf| {
566                self.update(buf);
567                buf.len()
568            })
569            .sum();
570
571        Ok(len)
572    }
573
574    #[inline(always)]
575    fn flush(&mut self) -> std::io::Result<()> {
576        Ok(())
577    }
578
579    #[inline(always)]
580    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
581        self.update(buf);
582
583        Ok(())
584    }
585}
586
587/// Computes the CRC checksum for the given data using the specified algorithm.
588///
589///```rust
590/// use crc_fast::{checksum, CrcAlgorithm::Crc32IsoHdlc};
591/// let checksum = checksum(Crc32IsoHdlc, b"123456789");
592///
593/// assert_eq!(checksum, 0xcbf43926);
594/// ```
595#[inline(always)]
596pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
597    let (calculator, params) = get_calculator_params(algorithm);
598
599    calculator(params.init, buf, params) ^ params.xorout
600}
601
602/// Computes the CRC checksum for the given data using custom CRC parameters.
603///
604/// # Examples
605///
606/// ```rust
607/// use crc_fast::{checksum_with_params, CrcParams};
608///
609/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
610/// let custom_params = CrcParams::new(
611///     "CRC-32/CUSTOM",
612///     32,
613///     0x04c11db7,
614///     0xffffffff,
615///     true,
616///     0xffffffff,
617///     0xcbf43926,
618/// );
619///
620/// let checksum = checksum_with_params(custom_params, b"123456789");
621///
622/// assert_eq!(checksum, 0xcbf43926);
623/// ```
624pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 {
625    let calculator = Calculator::calculate as CalculatorFn;
626
627    calculator(params.init, buf, params) ^ params.xorout
628}
629
630/// Computes the CRC checksum for the given file using the specified algorithm.
631///
632/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
633///
634/// # Errors
635///
636/// This function will return an error if the file cannot be read.
637///
638/// # Examples
639/// ### checksum_file
640///```rust
641/// use std::env;
642/// use crc_fast::{checksum_file, CrcAlgorithm::Crc32IsoHdlc};
643///
644/// // for example/test purposes only, use your own file path
645/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
646/// let file_on_disk = file_path.to_str().unwrap();
647///
648/// let checksum = checksum_file(Crc32IsoHdlc, file_on_disk, None);
649///
650/// assert_eq!(checksum.unwrap(), 0xcbf43926);
651/// ```
652#[cfg(feature = "std")]
653#[inline(always)]
654pub fn checksum_file(
655    algorithm: CrcAlgorithm,
656    path: &str,
657    chunk_size: Option<usize>,
658) -> Result<u64, std::io::Error> {
659    checksum_file_with_digest(Digest::new(algorithm), path, chunk_size)
660}
661
662/// Computes the CRC checksum for the given file using custom CRC parameters.
663///
664/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
665///
666/// # Errors
667///
668/// This function will return an error if the file cannot be read.
669///
670/// # Examples
671///
672/// ```rust
673/// use std::env;
674/// use crc_fast::{checksum_file_with_params, CrcParams};
675///
676/// // for example/test purposes only, use your own file path
677/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
678/// let file_on_disk = file_path.to_str().unwrap();
679///
680/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
681/// let custom_params = CrcParams::new(
682///     "CRC-32/CUSTOM",
683///     32,
684///     0x04c11db7,
685///     0xffffffff,
686///     true,
687///     0xffffffff,
688///     0xcbf43926,
689/// );
690///
691/// let checksum = checksum_file_with_params(custom_params, file_on_disk, None);
692///
693/// assert_eq!(checksum.unwrap(), 0xcbf43926);
694/// ```
695#[cfg(feature = "std")]
696pub fn checksum_file_with_params(
697    params: CrcParams,
698    path: &str,
699    chunk_size: Option<usize>,
700) -> Result<u64, std::io::Error> {
701    checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size)
702}
703
704/// Computes the CRC checksum for the given file using the specified Digest.
705///
706/// # Errors
707///
708/// This function will return an error if the file cannot be read.
709#[cfg(feature = "std")]
710fn checksum_file_with_digest(
711    mut digest: Digest,
712    path: &str,
713    chunk_size: Option<usize>,
714) -> Result<u64, std::io::Error> {
715    let mut file = File::open(path)?;
716
717    // 512KiB KiB was fastest in my benchmarks on an Apple M2 Ultra
718    //
719    // 4KiB ~7GiB/s
720    // 64KiB ~22 GiB/s
721    // 512KiB ~24 GiB/s
722    let chunk_size = chunk_size.unwrap_or(524288);
723
724    let mut buf = vec![0; chunk_size];
725
726    while let Ok(n) = file.read(&mut buf) {
727        if n == 0 {
728            break;
729        }
730        digest.update(&buf[..n]);
731    }
732
733    Ok(digest.finalize())
734}
735
736/// Combines two CRC checksums using the specified algorithm.
737///
738/// # Examples
739///```rust
740/// use crc_fast::{checksum, checksum_combine, CrcAlgorithm::Crc32IsoHdlc};
741///
742/// let checksum_1 = checksum(Crc32IsoHdlc, b"1234");
743/// let checksum_2 = checksum(Crc32IsoHdlc, b"56789");
744/// let checksum = checksum_combine(Crc32IsoHdlc, checksum_1, checksum_2, 5);
745///
746/// assert_eq!(checksum, 0xcbf43926);
747/// ```
748#[inline(always)]
749pub fn checksum_combine(
750    algorithm: CrcAlgorithm,
751    checksum1: u64,
752    checksum2: u64,
753    checksum2_len: u64,
754) -> u64 {
755    let params = get_calculator_params(algorithm).1;
756
757    combine::checksums(checksum1, checksum2, checksum2_len, params)
758}
759
760/// Combines two CRC checksums using custom CRC parameters.
761///
762/// # Examples
763///
764/// ```rust
765/// use crc_fast::{checksum_with_params, checksum_combine_with_params, CrcParams};
766///
767/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
768/// let custom_params = CrcParams::new(
769///     "CRC-32/CUSTOM",
770///     32,
771///     0x04c11db7,
772///     0xffffffff,
773///     true,
774///     0xffffffff,
775///     0xcbf43926,
776/// );
777///
778/// let checksum_1 = checksum_with_params(custom_params, b"1234");
779/// let checksum_2 = checksum_with_params(custom_params, b"56789");
780/// let checksum = checksum_combine_with_params(custom_params, checksum_1, checksum_2, 5);
781///
782/// assert_eq!(checksum, 0xcbf43926);
783/// ```
784pub fn checksum_combine_with_params(
785    params: CrcParams,
786    checksum1: u64,
787    checksum2: u64,
788    checksum2_len: u64,
789) -> u64 {
790    combine::checksums(checksum1, checksum2, checksum2_len, params)
791}
792
793/// Returns the target used to calculate the CRC checksum for the specified algorithm.
794///
795/// This function provides visibility into the active performance tier being used for CRC calculations.
796/// The target string follows the format `{architecture}-{intrinsics-family}-{intrinsics-features}`,
797/// such as `aarch64-aes-sha3` or `x86_64-avx512-vpclmulqdq`.
798///
799/// The performance tier system provides graceful degradation across different hardware capabilities:
800/// - **AArch64**: `aarch64-aes-sha3` (highest) → `aarch64-aes-pmull` (baseline)
801/// - **x86_64**: `x86_64-avx512-vpclmulqdq` (highest) → `x86_64-avx512-pclmulqdq` (mid) → `x86_64-sse-pclmulqdq` (baseline)
802/// - **x86**: `x86-sse-pclmulqdq` (baseline) → `software-fallback-tables` (fallback)
803/// - **Other architectures**: `software-fallback-tables`
804///
805/// The tier selection is deterministic and consistent across runs on the same hardware,
806/// combining compile-time and runtime feature detection for safety and optimal performance.
807///
808/// These strings are informational only, not stable, and shouldn't be relied on to match across
809/// versions.
810///
811/// # Examples
812///```rust
813/// use crc_fast::{get_calculator_target, CrcAlgorithm::Crc32IsoHdlc};
814///
815/// let target = get_calculator_target(Crc32IsoHdlc);
816/// println!("Using performance tier: {}", target);
817/// // Example outputs:
818/// // "aarch64-aes-sha3" - AArch64 with SHA3 and AES support
819/// // "x86_64-avx512-vpclmulqdq" - x86_64 with VPCLMULQDQ support
820/// // "x86_64-sse-pclmulqdq" - x86_64 baseline with SSE4.1 and PCLMULQDQ
821/// ```
822pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
823    use crate::feature_detection::get_arch_ops;
824
825    let arch_ops = get_arch_ops();
826    arch_ops.get_target_string()
827}
828
829/// Returns the calculator function and parameters for the specified CRC algorithm.
830#[inline(always)]
831fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
832    match algorithm {
833        CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
834        CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
835        CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
836        CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
837        CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
838        CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
839        CrcAlgorithm::Crc32Custom => {
840            panic!("Custom CRC-32 requires parameters via CrcParams::new()")
841        }
842        CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
843        CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
844        CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
845        CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
846        CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
847        CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
848        CrcAlgorithm::Crc64Custom => {
849            panic!("Custom CRC-64 requires parameters via CrcParams::new()")
850        }
851        CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
852        CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
853        CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
854        CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
855        CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
856        CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
857        CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
858    }
859}
860
861/// Calculates the CRC-32/ISCSI ("crc32c" in many, but not all, implementations) checksum.
862///
863/// Because both aarch64 and x86 have native hardware support for CRC-32/ISCSI, we can use
864/// fusion techniques to accelerate the calculation beyond what SIMD can do alone.
865#[inline(always)]
866fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
867    // both aarch64 and x86 have native CRC-32/ISCSI support, so we can use fusion
868    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))]
869    return fusion::crc32_iscsi(state as u32, data) as u64;
870
871    #[cfg(all(
872        not(target_arch = "aarch64"),
873        not(target_arch = "x86_64"),
874        not(target_arch = "x86")
875    ))]
876    // Fallback to traditional calculation for other architectures
877    Calculator::calculate(state, data, _params)
878}
879
880/// Calculates the CRC-32/ISO-HDLC ("crc32" in many, but not all, implementations) checksum.
881///
882/// Because aarch64 has native hardware support for CRC-32/ISO-HDLC, we can use fusion techniques
883/// to accelerate the calculation beyond what SIMD can do alone. x86 does not have native support,
884/// so we use the traditional calculation.
885#[inline(always)]
886fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
887    // aarch64 CPUs have native CRC-32/ISO-HDLC support, so we can use the fusion implementation
888    #[cfg(target_arch = "aarch64")]
889    return fusion::crc32_iso_hdlc(state as u32, data) as u64;
890
891    // x86 CPUs don't have native CRC-32/ISO-HDLC support, so there's no fusion to be had, use
892    // traditional calculation
893    #[cfg(not(target_arch = "aarch64"))]
894    Calculator::calculate(state, data, _params)
895}
896
897#[cfg(test)]
898mod lib {
899    #![allow(unused)]
900
901    use super::*;
902    use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
903    use crate::test::enums::AnyCrcTestConfig;
904    use cbindgen::Language::C;
905    use cbindgen::Style::Both;
906    use rand::{rng, Rng};
907    use std::fs::{read, write};
908
909    #[test]
910    fn test_checksum_check() {
911        for config in TEST_ALL_CONFIGS {
912            assert_eq!(
913                checksum(config.get_algorithm(), TEST_CHECK_STRING),
914                config.get_check()
915            );
916        }
917    }
918
919    #[test]
920    fn test_checksum_reference() {
921        for config in TEST_ALL_CONFIGS {
922            assert_eq!(
923                checksum(config.get_algorithm(), TEST_CHECK_STRING),
924                config.checksum_with_reference(TEST_CHECK_STRING)
925            );
926        }
927    }
928
929    #[test]
930    fn test_checksum_with_custom_params() {
931        crate::cache::clear_cache();
932
933        // CRC-32 reflected
934        assert_eq!(
935            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
936            CRC32_ISCSI.check,
937        );
938
939        // CRC-32 forward
940        assert_eq!(
941            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
942            CRC32_BZIP2.check,
943        );
944
945        // CRC-64 reflected
946        assert_eq!(
947            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
948            CRC64_NVME.check,
949        );
950
951        // CRC-64 forward
952        assert_eq!(
953            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
954            CRC64_ECMA_182.check,
955        );
956    }
957
958    #[test]
959    fn test_get_custom_params() {
960        crate::cache::clear_cache();
961
962        assert_eq!(
963            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
964            CRC32_ISCSI.check,
965        );
966
967        assert_eq!(
968            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
969            CRC32_BZIP2.check,
970        );
971
972        assert_eq!(
973            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
974            CRC64_NVME.check,
975        );
976
977        assert_eq!(
978            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
979            CRC64_ECMA_182.check,
980        );
981    }
982
983    #[test]
984    fn test_get_calculator_target_format() {
985        let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
986
987        // Target string should not be empty
988        assert!(!target.is_empty());
989
990        // Should follow the expected format with valid architecture prefixes
991        let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
992        assert!(
993            valid_prefixes
994                .iter()
995                .any(|prefix| target.starts_with(prefix)),
996            "Target '{}' should start with a valid architecture prefix",
997            target
998        );
999
1000        // Should contain intrinsics family and features information
1001        let parts: Vec<&str> = target.split('-').collect();
1002        assert!(
1003            parts.len() >= 3,
1004            "Target '{}' should have at least 3 parts: architecture-family-features",
1005            target
1006        );
1007    }
1008
1009    #[test]
1010    fn test_get_calculator_target_consistency() {
1011        // Multiple calls should return the same result (deterministic)
1012        let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1013        let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1014        let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1015
1016        assert_eq!(
1017            target1, target2,
1018            "Target should be consistent across different CRC-32 algorithms"
1019        );
1020        assert_eq!(
1021            target1, target3,
1022            "Target should be consistent across CRC-32 and CRC-64 algorithms"
1023        );
1024    }
1025
1026    #[test]
1027    fn test_get_calculator_target_uses_cached_detection() {
1028        // This test verifies that the function uses cached feature detection
1029        // by checking that multiple calls are consistent and don't perform
1030        // redundant feature detection
1031
1032        let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1033        let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1034
1035        assert_eq!(
1036            target1, target2,
1037            "Cached detection should return identical results"
1038        );
1039    }
1040
1041    #[test]
1042    fn test_digest_updates_check() {
1043        for config in TEST_ALL_CONFIGS {
1044            check_digest(Digest::new(config.get_algorithm()), config.get_check());
1045        }
1046    }
1047
1048    #[test]
1049    fn test_digest_updates_check_with_custom_params() {
1050        crate::cache::clear_cache();
1051
1052        // CRC-32 reflected
1053        check_digest(
1054            Digest::new_with_params(get_custom_crc32_reflected()),
1055            CRC32_ISCSI.check,
1056        );
1057
1058        // CRC-32 forward
1059        check_digest(
1060            Digest::new_with_params(get_custom_crc32_forward()),
1061            CRC32_BZIP2.check,
1062        );
1063
1064        // CRC-64 reflected
1065        check_digest(
1066            Digest::new_with_params(get_custom_crc64_reflected()),
1067            CRC64_NVME.check,
1068        );
1069
1070        // CRC-64 forward
1071        check_digest(
1072            Digest::new_with_params(get_custom_crc64_forward()),
1073            CRC64_ECMA_182.check,
1074        );
1075    }
1076
1077    fn check_digest(mut digest: Digest, check: u64) {
1078        digest.update(b"123");
1079        digest.update(b"456");
1080        digest.update(b"789");
1081        assert_eq!(digest.finalize(), check,);
1082    }
1083
1084    #[test]
1085    fn test_small_all_lengths() {
1086        for config in TEST_ALL_CONFIGS {
1087            // Test each length from 1 to 255
1088            for len in 1..=255 {
1089                test_length(len, config);
1090            }
1091        }
1092    }
1093
1094    #[test]
1095    fn test_medium_lengths() {
1096        for config in TEST_ALL_CONFIGS {
1097            // Test each length from 256 to 1024, which should fold and include handling remainders
1098            for len in 256..=1024 {
1099                test_length(len, config);
1100            }
1101        }
1102    }
1103
1104    #[test]
1105    fn test_large_lengths() {
1106        for config in TEST_ALL_CONFIGS {
1107            // Test 1 MiB just before, at, and just after the folding boundaries
1108            for len in 1048575..1048577 {
1109                test_length(len, config);
1110            }
1111        }
1112    }
1113
1114    fn test_length(length: usize, config: &AnyCrcTestConfig) {
1115        let mut data = vec![0u8; length];
1116        rng().fill(&mut data[..]);
1117
1118        // Calculate expected CRC using the reference implementation
1119        let expected = config.checksum_with_reference(&data);
1120
1121        let result = checksum(config.get_algorithm(), &data);
1122
1123        assert_eq!(
1124            result,
1125            expected,
1126            "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1127            config.get_algorithm(),
1128            length,
1129            expected,
1130            result
1131        );
1132    }
1133
1134    #[test]
1135    fn test_combine() {
1136        for config in TEST_ALL_CONFIGS {
1137            let algorithm = config.get_algorithm();
1138            let check = config.get_check();
1139
1140            // checksums
1141            let checksum1 = checksum(algorithm, "1234".as_ref());
1142            let checksum2 = checksum(algorithm, "56789".as_ref());
1143
1144            // checksum_combine()
1145            assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
1146
1147            // Digest
1148            let mut digest1 = Digest::new(algorithm);
1149            digest1.update("1234".as_ref());
1150
1151            let mut digest2 = Digest::new(algorithm);
1152            digest2.update("56789".as_ref());
1153
1154            digest1.combine(&digest2);
1155
1156            assert_eq!(digest1.finalize(), check)
1157        }
1158    }
1159
1160    #[test]
1161    fn test_combine_with_custom_params() {
1162        crate::cache::clear_cache();
1163
1164        // CRC-32 reflected
1165        let crc32_params = get_custom_crc32_reflected();
1166        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1167        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1168        assert_eq!(
1169            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1170            CRC32_ISCSI.check,
1171        );
1172
1173        // CRC-32 forward
1174        let crc32_params = get_custom_crc32_forward();
1175        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1176        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1177        assert_eq!(
1178            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1179            CRC32_BZIP2.check,
1180        );
1181
1182        // CRC-64 reflected
1183        let crc64_params = get_custom_crc64_reflected();
1184        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1185        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1186        assert_eq!(
1187            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1188            CRC64_NVME.check,
1189        );
1190
1191        // CRC-64 forward
1192        let crc64_params = get_custom_crc64_forward();
1193        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1194        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1195        assert_eq!(
1196            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1197            CRC64_ECMA_182.check,
1198        );
1199    }
1200
1201    #[test]
1202    fn test_checksum_file() {
1203        // Create a test file with repeating zeros
1204        let test_file_path = "test/test_crc32_hash_file.bin";
1205        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1206        if let Err(e) = write(test_file_path, &data) {
1207            eprintln!("Skipping test due to write error: {}", e);
1208            return;
1209        }
1210
1211        for config in TEST_ALL_CONFIGS {
1212            let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1213            assert_eq!(result, config.checksum_with_reference(&data));
1214        }
1215
1216        std::fs::remove_file(test_file_path).unwrap();
1217    }
1218
1219    #[test]
1220    fn test_checksum_file_with_custom_params() {
1221        crate::cache::clear_cache();
1222
1223        // Create a test file with repeating zeros
1224        let test_file_path = "test/test_crc32_hash_file_custom.bin";
1225        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1226        if let Err(e) = write(test_file_path, &data) {
1227            eprintln!("Skipping test due to write error: {}", e);
1228            return;
1229        }
1230
1231        // CRC-32 reflected
1232        check_file(
1233            get_custom_crc32_reflected(),
1234            test_file_path,
1235            CRC32_ISCSI.check,
1236        );
1237
1238        // CRC-32 forward
1239        check_file(
1240            get_custom_crc32_forward(),
1241            test_file_path,
1242            CRC32_BZIP2.check,
1243        );
1244
1245        // CRC-64 reflected
1246        check_file(
1247            get_custom_crc64_reflected(),
1248            test_file_path,
1249            CRC64_NVME.check,
1250        );
1251
1252        // CRC-64 forward
1253        check_file(
1254            get_custom_crc64_forward(),
1255            test_file_path,
1256            CRC64_ECMA_182.check,
1257        );
1258
1259        std::fs::remove_file(test_file_path).unwrap();
1260    }
1261
1262    fn check_file(params: CrcParams, file_path: &str, check: u64) {
1263        let result = checksum_file_with_params(params, file_path, None).unwrap();
1264        assert_eq!(result, check);
1265    }
1266
1267    #[test]
1268    fn test_writer() {
1269        // Create a test file with repeating zeros
1270        let test_file_path = "test/test_crc32_writer_file.bin";
1271        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1272        if let Err(e) = std::fs::write(test_file_path, &data) {
1273            eprintln!("Skipping test due to write error: {}", e);
1274            return;
1275        }
1276
1277        for config in TEST_ALL_CONFIGS {
1278            let mut digest = Digest::new(config.get_algorithm());
1279            let mut file = File::open(test_file_path).unwrap();
1280            std::io::copy(&mut file, &mut digest).unwrap();
1281            assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1282        }
1283
1284        std::fs::remove_file(test_file_path).unwrap();
1285    }
1286    #[test]
1287    fn test_digest_reset() {
1288        for config in TEST_ALL_CONFIGS {
1289            let mut digest = Digest::new(config.get_algorithm());
1290            digest.update(b"42");
1291            digest.reset();
1292            digest.update(TEST_CHECK_STRING);
1293            assert_eq!(digest.finalize(), config.get_check());
1294        }
1295    }
1296
1297    #[test]
1298    fn test_digest_finalize_reset() {
1299        for config in TEST_ALL_CONFIGS {
1300            let check = config.get_check();
1301
1302            let mut digest = Digest::new(config.get_algorithm());
1303            digest.update(TEST_CHECK_STRING);
1304            assert_eq!(digest.finalize_reset(), check);
1305
1306            digest.update(TEST_CHECK_STRING);
1307            assert_eq!(digest.finalize(), check);
1308        }
1309    }
1310
1311    #[test]
1312    fn test_digest_finalize_into() {
1313        for config in TEST_ALL_CONFIGS {
1314            let mut digest = Digest::new(config.get_algorithm());
1315            digest.update(TEST_CHECK_STRING);
1316
1317            match digest.params.width {
1318                32 => {
1319                    let mut output = [0u8; 4];
1320                    digest.finalize_into(&mut output).unwrap();
1321                    let result = u32::from_be_bytes(output) as u64;
1322                    assert_eq!(result, config.get_check());
1323                }
1324                64 => {
1325                    let mut output = [0u8; 8];
1326                    digest.finalize_into(&mut output).unwrap();
1327                    let result = u64::from_be_bytes(output);
1328                    assert_eq!(result, config.get_check());
1329                }
1330                _ => panic!("Unsupported CRC width"),
1331            }
1332        }
1333    }
1334
1335    #[test]
1336    fn test_digest_finalize_into_reset() {
1337        for config in TEST_ALL_CONFIGS {
1338            let mut digest = Digest::new(config.get_algorithm());
1339            digest.update(TEST_CHECK_STRING);
1340
1341            let mut output: Vec<u8> = match digest.params.width {
1342                32 => vec![0u8; 4],
1343                64 => vec![0u8; 8],
1344                _ => panic!("Unsupported CRC width"),
1345            };
1346
1347            digest.finalize_into_reset(&mut output).unwrap();
1348            let result = match output.len() {
1349                4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1350                8 => u64::from_be_bytes(output.try_into().unwrap()),
1351                _ => panic!("Unsupported CRC width"),
1352            };
1353            assert_eq!(result, config.get_check());
1354
1355            digest.update(TEST_CHECK_STRING);
1356            assert_eq!(digest.finalize(), config.get_check());
1357        }
1358    }
1359
1360    /// Tests whether the FFI header is up-to-date
1361    #[test]
1362    fn test_ffi_header() -> Result<(), String> {
1363        #[cfg(target_os = "windows")]
1364        {
1365            // Skip this test on Windows, since CRLF vs LF is a PITA
1366            eprintln!("Skipping test on Windows");
1367
1368            return Ok(());
1369        }
1370
1371        const HEADER: &str = "libcrc_fast.h";
1372
1373        let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1374
1375        let mut expected = Vec::new();
1376        cbindgen::Builder::new()
1377            .with_crate(crate_dir)
1378            .with_include_guard("CRC_FAST_H")
1379            .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1380            // exclude internal implementation functions
1381            .exclude_item("crc32_iscsi_impl")
1382            .exclude_item("crc32_iso_hdlc_impl")
1383            .exclude_item("get_iscsi_target")
1384            .exclude_item("get_iso_hdlc_target")
1385            .exclude_item("ISO_HDLC_TARGET")
1386            .exclude_item("ISCSI_TARGET")
1387            .exclude_item("CrcParams")
1388            .rename_item("Digest", "CrcFastDigest")
1389            .with_style(Both)
1390            // generate C header
1391            .with_language(C)
1392            // with C++ compatibility
1393            .with_cpp_compat(true)
1394            .generate()
1395            .map_err(|error| error.to_string())?
1396            .write(&mut expected);
1397
1398        // Convert the expected bytes to string for pattern replacement, since cbindgen
1399        // generates an annoying amount of empty contiguous newlines
1400        let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1401
1402        // Replace excessive newlines (3 or more consecutive newlines) with 2 newlines
1403        let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1404        let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1405
1406        // Convert back to bytes
1407        expected = cleaned_content.into_bytes();
1408
1409        let actual = read(HEADER).map_err(|error| error.to_string())?;
1410
1411        if expected != actual {
1412            write(HEADER, expected).map_err(|error| error.to_string())?;
1413            return Err(format!(
1414                "{HEADER} is not up-to-date, commit the generated file and try again"
1415            ));
1416        }
1417
1418        Ok(())
1419    }
1420
1421    fn get_custom_crc32_reflected() -> CrcParams {
1422        CrcParams::new(
1423            "Custom CRC-32/ISCSI",
1424            32,
1425            CRC32_ISCSI.poly,
1426            CRC32_ISCSI.init,
1427            CRC32_ISCSI.refin,
1428            CRC32_ISCSI.xorout,
1429            CRC32_ISCSI.check,
1430        )
1431    }
1432
1433    fn get_custom_crc32_forward() -> CrcParams {
1434        CrcParams::new(
1435            "Custom CRC-32/BZIP2",
1436            32,
1437            CRC32_BZIP2.poly,
1438            CRC32_BZIP2.init,
1439            CRC32_BZIP2.refin,
1440            CRC32_BZIP2.xorout,
1441            CRC32_BZIP2.check,
1442        )
1443    }
1444
1445    fn get_custom_crc64_reflected() -> CrcParams {
1446        CrcParams::new(
1447            "Custom CRC-64/NVME",
1448            64,
1449            CRC64_NVME.poly,
1450            CRC64_NVME.init,
1451            CRC64_NVME.refin,
1452            CRC64_NVME.xorout,
1453            CRC64_NVME.check,
1454        )
1455    }
1456
1457    fn get_custom_crc64_forward() -> CrcParams {
1458        CrcParams::new(
1459            "Custom CRC-64/ECMA-182",
1460            64,
1461            CRC64_ECMA_182.poly,
1462            CRC64_ECMA_182.init,
1463            CRC64_ECMA_182.refin,
1464            CRC64_ECMA_182.xorout,
1465            CRC64_ECMA_182.check,
1466        )
1467    }
1468
1469    #[test]
1470    fn test_crc_keys_storage_fold_256() {
1471        let test_keys = [
1472            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1473        ];
1474        let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1475
1476        // Test valid key access
1477        for i in 0..23 {
1478            assert_eq!(storage.get_key(i), test_keys[i]);
1479        }
1480
1481        // Test out-of-bounds access returns 0
1482        assert_eq!(storage.get_key(23), 0);
1483        assert_eq!(storage.get_key(24), 0);
1484        assert_eq!(storage.get_key(100), 0);
1485
1486        // Test key count
1487        assert_eq!(storage.key_count(), 23);
1488    }
1489
1490    #[test]
1491    fn test_crc_keys_storage_future_test() {
1492        let test_keys = [
1493            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1494            25,
1495        ];
1496        let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1497
1498        // Test valid key access
1499        for i in 0..25 {
1500            assert_eq!(storage.get_key(i), test_keys[i]);
1501        }
1502
1503        // Test out-of-bounds access returns 0
1504        assert_eq!(storage.get_key(25), 0);
1505        assert_eq!(storage.get_key(26), 0);
1506        assert_eq!(storage.get_key(100), 0);
1507
1508        // Test key count
1509        assert_eq!(storage.key_count(), 25);
1510    }
1511
1512    #[test]
1513    fn test_crc_params_safe_accessors() {
1514        // Create a test CrcParams with known keys
1515        let test_keys = [
1516            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1517        ];
1518        let params = CrcParams {
1519            algorithm: CrcAlgorithm::Crc32IsoHdlc,
1520            name: "test",
1521            width: 32,
1522            poly: 0x04C11DB7,
1523            init: 0xFFFFFFFF,
1524            refin: true,
1525            refout: true,
1526            xorout: 0xFFFFFFFF,
1527            check: 0xCBF43926,
1528            keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1529        };
1530
1531        // Test valid key access
1532        for i in 0..23 {
1533            assert_eq!(params.get_key(i), test_keys[i]);
1534            assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1535        }
1536
1537        // Test out-of-bounds access
1538        assert_eq!(params.get_key(23), 0);
1539        assert_eq!(params.get_key(24), 0);
1540        assert_eq!(params.get_key(100), 0);
1541
1542        assert_eq!(params.get_key_checked(23), None);
1543        assert_eq!(params.get_key_checked(24), None);
1544        assert_eq!(params.get_key_checked(100), None);
1545
1546        // Test key count
1547        assert_eq!(params.key_count(), 23);
1548    }
1549
1550    #[test]
1551    fn test_crc_keys_storage_const_constructors() {
1552        // Test that const constructors work in const context
1553        const TEST_KEYS_23: [u64; 23] = [1; 23];
1554        const TEST_KEYS_25: [u64; 25] = [2; 25];
1555
1556        const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1557        const STORAGE_FUTURE: CrcKeysStorage =
1558            CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1559
1560        // Verify the const constructors work correctly
1561        assert_eq!(STORAGE_256.get_key(0), 1);
1562        assert_eq!(STORAGE_256.key_count(), 23);
1563
1564        assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1565        assert_eq!(STORAGE_FUTURE.key_count(), 25);
1566    }
1567
1568    #[test]
1569    fn test_crc_keys_storage_bounds_safety() {
1570        let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1571        let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1572
1573        // Test edge cases for bounds checking
1574        assert_eq!(storage_256.get_key(22), 42); // Last valid index
1575        assert_eq!(storage_256.get_key(23), 0); // First invalid index
1576
1577        assert_eq!(storage_future.get_key(24), 84); // Last valid index
1578        assert_eq!(storage_future.get_key(25), 0); // First invalid index
1579
1580        // Test very large indices
1581        assert_eq!(storage_256.get_key(usize::MAX), 0);
1582        assert_eq!(storage_future.get_key(usize::MAX), 0);
1583    }
1584}