crc_fast/
lib.rs

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