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    #[cfg(target_arch = "aarch64")]
868    {
869        use std::arch::is_aarch64_feature_detected;
870        if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") {
871            return fusion::crc32_iscsi(state as u32, data) as u64;
872        }
873    }
874
875    // both aarch64 and x86 have native CRC-32/ISCSI support, so we can use fusion
876    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
877    {
878        use std::arch::is_x86_feature_detected;
879        if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") {
880            return fusion::crc32_iscsi(state as u32, data) as u64;
881        }
882    }
883
884    // Fallback to traditional calculation for other architectures, which will eventually fall back
885    // to software tables if necessary
886    Calculator::calculate(state, data, _params)
887}
888
889/// Calculates the CRC-32/ISO-HDLC ("crc32" in many, but not all, implementations) checksum.
890///
891/// Because aarch64 has native hardware support for CRC-32/ISO-HDLC, we can use fusion techniques
892/// to accelerate the calculation beyond what SIMD can do alone. x86 does not have native support,
893/// so we use the traditional calculation.
894#[inline(always)]
895fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
896    // aarch64 CPUs have native CRC-32/ISO-HDLC support, so we can use the fusion implementation
897    #[cfg(target_arch = "aarch64")]
898    {
899        use std::arch::is_aarch64_feature_detected;
900        if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") {
901            return fusion::crc32_iso_hdlc(state as u32, data) as u64;
902        }
903    }
904
905    // x86 CPUs don't have native CRC-32/ISO-HDLC support, so there's no fusion to be had, use
906    // traditional calculation, which will eventually fall back to software tables if necessary
907    Calculator::calculate(state, data, _params)
908}
909
910#[cfg(test)]
911mod lib {
912    #![allow(unused)]
913
914    use super::*;
915    use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
916    use crate::test::enums::AnyCrcTestConfig;
917    use cbindgen::Language::C;
918    use cbindgen::Style::Both;
919    use rand::{rng, Rng};
920    use std::fs::{read, write};
921
922    #[test]
923    fn test_checksum_check() {
924        for config in TEST_ALL_CONFIGS {
925            assert_eq!(
926                checksum(config.get_algorithm(), TEST_CHECK_STRING),
927                config.get_check()
928            );
929        }
930    }
931
932    #[test]
933    fn test_checksum_reference() {
934        for config in TEST_ALL_CONFIGS {
935            assert_eq!(
936                checksum(config.get_algorithm(), TEST_CHECK_STRING),
937                config.checksum_with_reference(TEST_CHECK_STRING)
938            );
939        }
940    }
941
942    #[test]
943    fn test_checksum_with_custom_params() {
944        crate::cache::clear_cache();
945
946        // CRC-32 reflected
947        assert_eq!(
948            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
949            CRC32_ISCSI.check,
950        );
951
952        // CRC-32 forward
953        assert_eq!(
954            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
955            CRC32_BZIP2.check,
956        );
957
958        // CRC-64 reflected
959        assert_eq!(
960            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
961            CRC64_NVME.check,
962        );
963
964        // CRC-64 forward
965        assert_eq!(
966            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
967            CRC64_ECMA_182.check,
968        );
969    }
970
971    #[test]
972    fn test_get_custom_params() {
973        crate::cache::clear_cache();
974
975        assert_eq!(
976            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
977            CRC32_ISCSI.check,
978        );
979
980        assert_eq!(
981            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
982            CRC32_BZIP2.check,
983        );
984
985        assert_eq!(
986            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
987            CRC64_NVME.check,
988        );
989
990        assert_eq!(
991            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
992            CRC64_ECMA_182.check,
993        );
994    }
995
996    #[test]
997    fn test_get_calculator_target_format() {
998        let target = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
999
1000        // Target string should not be empty
1001        assert!(!target.is_empty());
1002
1003        // Should follow the expected format with valid architecture prefixes
1004        let valid_prefixes = ["aarch64-", "x86_64-", "x86-", "software-"];
1005        assert!(
1006            valid_prefixes
1007                .iter()
1008                .any(|prefix| target.starts_with(prefix)),
1009            "Target '{}' should start with a valid architecture prefix",
1010            target
1011        );
1012
1013        // Should contain intrinsics family and features information
1014        let parts: Vec<&str> = target.split('-').collect();
1015        assert!(
1016            parts.len() >= 3,
1017            "Target '{}' should have at least 3 parts: architecture-family-features",
1018            target
1019        );
1020    }
1021
1022    #[test]
1023    fn test_get_calculator_target_consistency() {
1024        // Multiple calls should return the same result (deterministic)
1025        let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1026        let target2 = get_calculator_target(CrcAlgorithm::Crc32Iscsi);
1027        let target3 = get_calculator_target(CrcAlgorithm::Crc64Nvme);
1028
1029        assert_eq!(
1030            target1, target2,
1031            "Target should be consistent across different CRC-32 algorithms"
1032        );
1033        assert_eq!(
1034            target1, target3,
1035            "Target should be consistent across CRC-32 and CRC-64 algorithms"
1036        );
1037    }
1038
1039    #[test]
1040    fn test_get_calculator_target_uses_cached_detection() {
1041        // This test verifies that the function uses cached feature detection
1042        // by checking that multiple calls are consistent and don't perform
1043        // redundant feature detection
1044
1045        let target1 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1046        let target2 = get_calculator_target(CrcAlgorithm::Crc32IsoHdlc);
1047
1048        assert_eq!(
1049            target1, target2,
1050            "Cached detection should return identical results"
1051        );
1052    }
1053
1054    #[test]
1055    fn test_digest_updates_check() {
1056        for config in TEST_ALL_CONFIGS {
1057            check_digest(Digest::new(config.get_algorithm()), config.get_check());
1058        }
1059    }
1060
1061    #[test]
1062    fn test_digest_updates_check_with_custom_params() {
1063        crate::cache::clear_cache();
1064
1065        // CRC-32 reflected
1066        check_digest(
1067            Digest::new_with_params(get_custom_crc32_reflected()),
1068            CRC32_ISCSI.check,
1069        );
1070
1071        // CRC-32 forward
1072        check_digest(
1073            Digest::new_with_params(get_custom_crc32_forward()),
1074            CRC32_BZIP2.check,
1075        );
1076
1077        // CRC-64 reflected
1078        check_digest(
1079            Digest::new_with_params(get_custom_crc64_reflected()),
1080            CRC64_NVME.check,
1081        );
1082
1083        // CRC-64 forward
1084        check_digest(
1085            Digest::new_with_params(get_custom_crc64_forward()),
1086            CRC64_ECMA_182.check,
1087        );
1088    }
1089
1090    fn check_digest(mut digest: Digest, check: u64) {
1091        digest.update(b"123");
1092        digest.update(b"456");
1093        digest.update(b"789");
1094        assert_eq!(digest.finalize(), check,);
1095    }
1096
1097    #[test]
1098    fn test_1024_length() {
1099        for config in TEST_ALL_CONFIGS {
1100            test_length(1024, config);
1101        }
1102    }
1103
1104    /// Skipping for Miri runs due to time constraints, underlying code already covered by other
1105    /// tests.
1106    #[test]
1107    #[cfg_attr(miri, ignore)]
1108    fn test_small_all_lengths() {
1109        for config in TEST_ALL_CONFIGS {
1110            // Test each length from 1 to 255
1111            for len in 1..=255 {
1112                test_length(len, config);
1113            }
1114        }
1115    }
1116
1117    /// Skipping for Miri runs due to time constraints, underlying code already covered by other
1118    /// tests.
1119    #[test]
1120    #[cfg_attr(miri, ignore)]
1121    fn test_medium_lengths() {
1122        for config in TEST_ALL_CONFIGS {
1123            // Test each length from 256 to 1024, which should fold and include handling remainders
1124            for len in 256..=1024 {
1125                test_length(len, config);
1126            }
1127        }
1128    }
1129
1130    /// Skipping for Miri runs due to time constraints, underlying code already covered by other
1131    /// tests.
1132    #[test]
1133    #[cfg_attr(miri, ignore)]
1134    fn test_large_lengths() {
1135        for config in TEST_ALL_CONFIGS {
1136            // Test 1 MiB just before, at, and just after the folding boundaries
1137            for len in 1048575..1048577 {
1138                test_length(len, config);
1139            }
1140        }
1141    }
1142
1143    fn test_length(length: usize, config: &AnyCrcTestConfig) {
1144        let mut data = vec![0u8; length];
1145        rng().fill(&mut data[..]);
1146
1147        // Calculate expected CRC using the reference implementation
1148        let expected = config.checksum_with_reference(&data);
1149
1150        let result = checksum(config.get_algorithm(), &data);
1151
1152        assert_eq!(
1153            result,
1154            expected,
1155            "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
1156            config.get_algorithm(),
1157            length,
1158            expected,
1159            result
1160        );
1161    }
1162
1163    #[test]
1164    fn test_combine() {
1165        for config in TEST_ALL_CONFIGS {
1166            let algorithm = config.get_algorithm();
1167            let check = config.get_check();
1168
1169            // checksums
1170            let checksum1 = checksum(algorithm, "1234".as_ref());
1171            let checksum2 = checksum(algorithm, "56789".as_ref());
1172
1173            // checksum_combine()
1174            assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
1175
1176            // Digest
1177            let mut digest1 = Digest::new(algorithm);
1178            digest1.update("1234".as_ref());
1179
1180            let mut digest2 = Digest::new(algorithm);
1181            digest2.update("56789".as_ref());
1182
1183            digest1.combine(&digest2);
1184
1185            assert_eq!(digest1.finalize(), check)
1186        }
1187    }
1188
1189    #[test]
1190    fn test_combine_with_custom_params() {
1191        crate::cache::clear_cache();
1192
1193        // CRC-32 reflected
1194        let crc32_params = get_custom_crc32_reflected();
1195        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1196        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1197        assert_eq!(
1198            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1199            CRC32_ISCSI.check,
1200        );
1201
1202        // CRC-32 forward
1203        let crc32_params = get_custom_crc32_forward();
1204        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1205        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1206        assert_eq!(
1207            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1208            CRC32_BZIP2.check,
1209        );
1210
1211        // CRC-64 reflected
1212        let crc64_params = get_custom_crc64_reflected();
1213        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1214        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1215        assert_eq!(
1216            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1217            CRC64_NVME.check,
1218        );
1219
1220        // CRC-64 forward
1221        let crc64_params = get_custom_crc64_forward();
1222        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1223        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1224        assert_eq!(
1225            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1226            CRC64_ECMA_182.check,
1227        );
1228    }
1229
1230    /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already
1231    /// covered by other tests.
1232    #[test]
1233    #[cfg_attr(miri, ignore)]
1234    fn test_checksum_file() {
1235        // Create a test file with repeating zeros
1236        let test_file_path = "test/test_crc32_hash_file.bin";
1237        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1238        if let Err(e) = write(test_file_path, &data) {
1239            eprintln!("Skipping test due to write error: {}", e);
1240            return;
1241        }
1242
1243        for config in TEST_ALL_CONFIGS {
1244            let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1245            assert_eq!(result, config.checksum_with_reference(&data));
1246        }
1247
1248        std::fs::remove_file(test_file_path).unwrap();
1249    }
1250
1251    /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already
1252    /// covered by other tests.
1253    #[test]
1254    #[cfg_attr(miri, ignore)]
1255    fn test_checksum_file_with_custom_params() {
1256        crate::cache::clear_cache();
1257
1258        // Create a test file with repeating zeros
1259        let test_file_path = "test/test_crc32_hash_file_custom.bin";
1260        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1261        if let Err(e) = write(test_file_path, &data) {
1262            eprintln!("Skipping test due to write error: {}", e);
1263            return;
1264        }
1265
1266        // CRC-32 reflected
1267        check_file(
1268            get_custom_crc32_reflected(),
1269            test_file_path,
1270            CRC32_ISCSI.check,
1271        );
1272
1273        // CRC-32 forward
1274        check_file(
1275            get_custom_crc32_forward(),
1276            test_file_path,
1277            CRC32_BZIP2.check,
1278        );
1279
1280        // CRC-64 reflected
1281        check_file(
1282            get_custom_crc64_reflected(),
1283            test_file_path,
1284            CRC64_NVME.check,
1285        );
1286
1287        // CRC-64 forward
1288        check_file(
1289            get_custom_crc64_forward(),
1290            test_file_path,
1291            CRC64_ECMA_182.check,
1292        );
1293
1294        std::fs::remove_file(test_file_path).unwrap();
1295    }
1296
1297    fn check_file(params: CrcParams, file_path: &str, check: u64) {
1298        let result = checksum_file_with_params(params, file_path, None).unwrap();
1299        assert_eq!(result, check);
1300    }
1301
1302    /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already
1303    /// covered by other tests.
1304    #[test]
1305    #[cfg_attr(miri, ignore)]
1306    fn test_writer() {
1307        // Create a test file with repeating zeros
1308        let test_file_path = "test/test_crc32_writer_file.bin";
1309        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1310        if let Err(e) = std::fs::write(test_file_path, &data) {
1311            eprintln!("Skipping test due to write error: {}", e);
1312            return;
1313        }
1314
1315        for config in TEST_ALL_CONFIGS {
1316            let mut digest = Digest::new(config.get_algorithm());
1317            let mut file = File::open(test_file_path).unwrap();
1318            std::io::copy(&mut file, &mut digest).unwrap();
1319            assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1320        }
1321
1322        std::fs::remove_file(test_file_path).unwrap();
1323    }
1324    #[test]
1325    fn test_digest_reset() {
1326        for config in TEST_ALL_CONFIGS {
1327            let mut digest = Digest::new(config.get_algorithm());
1328            digest.update(b"42");
1329            digest.reset();
1330            digest.update(TEST_CHECK_STRING);
1331            assert_eq!(digest.finalize(), config.get_check());
1332        }
1333    }
1334
1335    #[test]
1336    fn test_digest_finalize_reset() {
1337        for config in TEST_ALL_CONFIGS {
1338            let check = config.get_check();
1339
1340            let mut digest = Digest::new(config.get_algorithm());
1341            digest.update(TEST_CHECK_STRING);
1342            assert_eq!(digest.finalize_reset(), check);
1343
1344            digest.update(TEST_CHECK_STRING);
1345            assert_eq!(digest.finalize(), check);
1346        }
1347    }
1348
1349    #[test]
1350    fn test_digest_finalize_into() {
1351        for config in TEST_ALL_CONFIGS {
1352            let mut digest = Digest::new(config.get_algorithm());
1353            digest.update(TEST_CHECK_STRING);
1354
1355            match digest.params.width {
1356                32 => {
1357                    let mut output = [0u8; 4];
1358                    digest.finalize_into(&mut output).unwrap();
1359                    let result = u32::from_be_bytes(output) as u64;
1360                    assert_eq!(result, config.get_check());
1361                }
1362                64 => {
1363                    let mut output = [0u8; 8];
1364                    digest.finalize_into(&mut output).unwrap();
1365                    let result = u64::from_be_bytes(output);
1366                    assert_eq!(result, config.get_check());
1367                }
1368                _ => panic!("Unsupported CRC width"),
1369            }
1370        }
1371    }
1372
1373    #[test]
1374    fn test_digest_finalize_into_reset() {
1375        for config in TEST_ALL_CONFIGS {
1376            let mut digest = Digest::new(config.get_algorithm());
1377            digest.update(TEST_CHECK_STRING);
1378
1379            let mut output: Vec<u8> = match digest.params.width {
1380                32 => vec![0u8; 4],
1381                64 => vec![0u8; 8],
1382                _ => panic!("Unsupported CRC width"),
1383            };
1384
1385            digest.finalize_into_reset(&mut output).unwrap();
1386            let result = match output.len() {
1387                4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1388                8 => u64::from_be_bytes(output.try_into().unwrap()),
1389                _ => panic!("Unsupported CRC width"),
1390            };
1391            assert_eq!(result, config.get_check());
1392
1393            digest.update(TEST_CHECK_STRING);
1394            assert_eq!(digest.finalize(), config.get_check());
1395        }
1396    }
1397
1398    /// Tests whether the FFI header is up-to-date
1399    #[test]
1400    #[cfg_attr(miri, ignore)]
1401    fn test_ffi_header() -> Result<(), String> {
1402        #[cfg(target_os = "windows")]
1403        {
1404            // Skip this test on Windows, since CRLF vs LF is a PITA
1405            eprintln!("Skipping test on Windows");
1406
1407            return Ok(());
1408        }
1409
1410        const HEADER: &str = "libcrc_fast.h";
1411
1412        let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1413
1414        let mut expected = Vec::new();
1415        cbindgen::Builder::new()
1416            .with_crate(crate_dir)
1417            .with_include_guard("CRC_FAST_H")
1418            .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1419            // exclude internal implementation functions
1420            .exclude_item("crc32_iscsi_impl")
1421            .exclude_item("crc32_iso_hdlc_impl")
1422            .exclude_item("get_iscsi_target")
1423            .exclude_item("get_iso_hdlc_target")
1424            .exclude_item("ISO_HDLC_TARGET")
1425            .exclude_item("ISCSI_TARGET")
1426            .exclude_item("CrcParams")
1427            .rename_item("Digest", "CrcFastDigest")
1428            .with_style(Both)
1429            // generate C header
1430            .with_language(C)
1431            // with C++ compatibility
1432            .with_cpp_compat(true)
1433            .generate()
1434            .map_err(|error| error.to_string())?
1435            .write(&mut expected);
1436
1437        // Convert the expected bytes to string for pattern replacement, since cbindgen
1438        // generates an annoying amount of empty contiguous newlines
1439        let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1440
1441        // Replace excessive newlines (3 or more consecutive newlines) with 2 newlines
1442        let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1443        let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1444
1445        // Convert back to bytes
1446        expected = cleaned_content.into_bytes();
1447
1448        let actual = read(HEADER).map_err(|error| error.to_string())?;
1449
1450        if expected != actual {
1451            write(HEADER, expected).map_err(|error| error.to_string())?;
1452            return Err(format!(
1453                "{HEADER} is not up-to-date, commit the generated file and try again"
1454            ));
1455        }
1456
1457        Ok(())
1458    }
1459
1460    fn get_custom_crc32_reflected() -> CrcParams {
1461        CrcParams::new(
1462            "Custom CRC-32/ISCSI",
1463            32,
1464            CRC32_ISCSI.poly,
1465            CRC32_ISCSI.init,
1466            CRC32_ISCSI.refin,
1467            CRC32_ISCSI.xorout,
1468            CRC32_ISCSI.check,
1469        )
1470    }
1471
1472    fn get_custom_crc32_forward() -> CrcParams {
1473        CrcParams::new(
1474            "Custom CRC-32/BZIP2",
1475            32,
1476            CRC32_BZIP2.poly,
1477            CRC32_BZIP2.init,
1478            CRC32_BZIP2.refin,
1479            CRC32_BZIP2.xorout,
1480            CRC32_BZIP2.check,
1481        )
1482    }
1483
1484    fn get_custom_crc64_reflected() -> CrcParams {
1485        CrcParams::new(
1486            "Custom CRC-64/NVME",
1487            64,
1488            CRC64_NVME.poly,
1489            CRC64_NVME.init,
1490            CRC64_NVME.refin,
1491            CRC64_NVME.xorout,
1492            CRC64_NVME.check,
1493        )
1494    }
1495
1496    fn get_custom_crc64_forward() -> CrcParams {
1497        CrcParams::new(
1498            "Custom CRC-64/ECMA-182",
1499            64,
1500            CRC64_ECMA_182.poly,
1501            CRC64_ECMA_182.init,
1502            CRC64_ECMA_182.refin,
1503            CRC64_ECMA_182.xorout,
1504            CRC64_ECMA_182.check,
1505        )
1506    }
1507
1508    #[test]
1509    fn test_crc_keys_storage_fold_256() {
1510        let test_keys = [
1511            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1512        ];
1513        let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1514
1515        // Test valid key access
1516        for i in 0..23 {
1517            assert_eq!(storage.get_key(i), test_keys[i]);
1518        }
1519
1520        // Test out-of-bounds access returns 0
1521        assert_eq!(storage.get_key(23), 0);
1522        assert_eq!(storage.get_key(24), 0);
1523        assert_eq!(storage.get_key(100), 0);
1524
1525        // Test key count
1526        assert_eq!(storage.key_count(), 23);
1527    }
1528
1529    #[test]
1530    fn test_crc_keys_storage_future_test() {
1531        let test_keys = [
1532            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1533            25,
1534        ];
1535        let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1536
1537        // Test valid key access
1538        for i in 0..25 {
1539            assert_eq!(storage.get_key(i), test_keys[i]);
1540        }
1541
1542        // Test out-of-bounds access returns 0
1543        assert_eq!(storage.get_key(25), 0);
1544        assert_eq!(storage.get_key(26), 0);
1545        assert_eq!(storage.get_key(100), 0);
1546
1547        // Test key count
1548        assert_eq!(storage.key_count(), 25);
1549    }
1550
1551    #[test]
1552    fn test_crc_params_safe_accessors() {
1553        // Create a test CrcParams with known keys
1554        let test_keys = [
1555            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1556        ];
1557        let params = CrcParams {
1558            algorithm: CrcAlgorithm::Crc32IsoHdlc,
1559            name: "test",
1560            width: 32,
1561            poly: 0x04C11DB7,
1562            init: 0xFFFFFFFF,
1563            refin: true,
1564            refout: true,
1565            xorout: 0xFFFFFFFF,
1566            check: 0xCBF43926,
1567            keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1568        };
1569
1570        // Test valid key access
1571        for i in 0..23 {
1572            assert_eq!(params.get_key(i), test_keys[i]);
1573            assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1574        }
1575
1576        // Test out-of-bounds access
1577        assert_eq!(params.get_key(23), 0);
1578        assert_eq!(params.get_key(24), 0);
1579        assert_eq!(params.get_key(100), 0);
1580
1581        assert_eq!(params.get_key_checked(23), None);
1582        assert_eq!(params.get_key_checked(24), None);
1583        assert_eq!(params.get_key_checked(100), None);
1584
1585        // Test key count
1586        assert_eq!(params.key_count(), 23);
1587    }
1588
1589    #[test]
1590    fn test_crc_keys_storage_const_constructors() {
1591        // Test that const constructors work in const context
1592        const TEST_KEYS_23: [u64; 23] = [1; 23];
1593        const TEST_KEYS_25: [u64; 25] = [2; 25];
1594
1595        const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1596        const STORAGE_FUTURE: CrcKeysStorage =
1597            CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1598
1599        // Verify the const constructors work correctly
1600        assert_eq!(STORAGE_256.get_key(0), 1);
1601        assert_eq!(STORAGE_256.key_count(), 23);
1602
1603        assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1604        assert_eq!(STORAGE_FUTURE.key_count(), 25);
1605    }
1606
1607    #[test]
1608    fn test_crc_keys_storage_bounds_safety() {
1609        let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1610        let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1611
1612        // Test edge cases for bounds checking
1613        assert_eq!(storage_256.get_key(22), 42); // Last valid index
1614        assert_eq!(storage_256.get_key(23), 0); // First invalid index
1615
1616        assert_eq!(storage_future.get_key(24), 84); // Last valid index
1617        assert_eq!(storage_future.get_key(25), 0); // First invalid index
1618
1619        // Test very large indices
1620        assert_eq!(storage_256.get_key(usize::MAX), 0);
1621        assert_eq!(storage_future.get_key(usize::MAX), 0);
1622    }
1623}