crc_fast/
lib.rs

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