crc_fast/
lib.rs

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