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    #[inline(always)]
380    pub fn new(algorithm: CrcAlgorithm) -> Self {
381        let (calculator, params) = get_calculator_params(algorithm);
382
383        Self {
384            state: params.init,
385            amount: 0,
386            params,
387            calculator,
388        }
389    }
390
391    /// Creates a new `Digest` instance with custom CRC parameters.
392    ///
393    /// # Examples
394    ///
395    /// ```rust
396    /// use crc_fast::{Digest, CrcParams};
397    ///
398    /// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
399    /// let custom_params = CrcParams::new(
400    ///     "CRC-32/CUSTOM",
401    ///     32,
402    ///     0x04c11db7,
403    ///     0xffffffff,
404    ///     true,
405    ///     0xffffffff,
406    ///     0xcbf43926,
407    /// );
408    ///
409    /// let mut digest = Digest::new_with_params(custom_params);
410    /// digest.update(b"123456789");
411    /// let checksum = digest.finalize();
412    ///
413    /// assert_eq!(checksum, 0xcbf43926);
414    /// ```
415    #[inline(always)]
416    pub fn new_with_params(params: CrcParams) -> Self {
417        let calculator = Calculator::calculate as CalculatorFn;
418
419        Self {
420            state: params.init,
421            amount: 0,
422            params,
423            calculator,
424        }
425    }
426
427    /// Updates the CRC state with the given data.
428    #[inline(always)]
429    pub fn update(&mut self, data: &[u8]) {
430        self.state = (self.calculator)(self.state, data, self.params);
431        self.amount += data.len() as u64;
432    }
433
434    /// Finalizes the CRC computation and returns the result.
435    #[inline(always)]
436    pub fn finalize(&self) -> u64 {
437        self.state ^ self.params.xorout
438    }
439
440    /// Finalizes the CRC computation, resets the state, and returns the result.
441    #[inline(always)]
442    pub fn finalize_reset(&mut self) -> u64 {
443        let result = self.finalize();
444        self.reset();
445
446        result
447    }
448
449    /// Resets the CRC state to its initial value.
450    #[inline(always)]
451    pub fn reset(&mut self) {
452        self.state = self.params.init;
453        self.amount = 0;
454    }
455
456    /// Combines the CRC state with a second `Digest` instance.
457    #[inline(always)]
458    pub fn combine(&mut self, other: &Self) {
459        self.amount += other.amount;
460        let other_crc = other.finalize();
461
462        // note the xorout for the input, since it's already been applied so it has to be removed,
463        // and then re-adding it on the final output
464        self.state = combine::checksums(
465            self.state ^ self.params.xorout,
466            other_crc,
467            other.amount,
468            self.params,
469        ) ^ self.params.xorout;
470    }
471
472    /// Gets the amount of data processed so far
473    #[inline(always)]
474    pub fn get_amount(&self) -> u64 {
475        self.amount
476    }
477}
478
479impl Write for Digest {
480    #[inline(always)]
481    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
482        self.update(buf);
483        Ok(buf.len())
484    }
485
486    #[inline(always)]
487    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
488        let len: usize = bufs
489            .iter()
490            .map(|buf| {
491                self.update(buf);
492                buf.len()
493            })
494            .sum();
495
496        Ok(len)
497    }
498
499    #[inline(always)]
500    fn flush(&mut self) -> std::io::Result<()> {
501        Ok(())
502    }
503
504    #[inline(always)]
505    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
506        self.update(buf);
507
508        Ok(())
509    }
510}
511
512/// Computes the CRC checksum for the given data using the specified algorithm.
513///
514///```rust
515/// use crc_fast::{checksum, CrcAlgorithm::Crc32IsoHdlc};
516/// let checksum = checksum(Crc32IsoHdlc, b"123456789");
517///
518/// assert_eq!(checksum, 0xcbf43926);
519/// ```
520#[inline(always)]
521pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
522    let (calculator, params) = get_calculator_params(algorithm);
523
524    calculator(params.init, buf, params) ^ params.xorout
525}
526
527/// Computes the CRC checksum for the given data using custom CRC parameters.
528///
529/// # Examples
530///
531/// ```rust
532/// use crc_fast::{checksum_with_params, CrcParams};
533///
534/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
535/// let custom_params = CrcParams::new(
536///     "CRC-32/CUSTOM",
537///     32,
538///     0x04c11db7,
539///     0xffffffff,
540///     true,
541///     0xffffffff,
542///     0xcbf43926,
543/// );
544///
545/// let checksum = checksum_with_params(custom_params, b"123456789");
546///
547/// assert_eq!(checksum, 0xcbf43926);
548/// ```
549pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 {
550    let calculator = Calculator::calculate as CalculatorFn;
551
552    calculator(params.init, buf, params) ^ params.xorout
553}
554
555/// Computes the CRC checksum for the given file using the specified algorithm.
556///
557/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
558///
559/// # Errors
560///
561/// This function will return an error if the file cannot be read.
562///
563/// # Examples
564/// ### checksum_file
565///```rust
566/// use std::env;
567/// use crc_fast::{checksum_file, CrcAlgorithm::Crc32IsoHdlc};
568///
569/// // for example/test purposes only, use your own file path
570/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
571/// let file_on_disk = file_path.to_str().unwrap();
572///
573/// let checksum = checksum_file(Crc32IsoHdlc, file_on_disk, None);
574///
575/// assert_eq!(checksum.unwrap(), 0xcbf43926);
576/// ```
577#[inline(always)]
578pub fn checksum_file(
579    algorithm: CrcAlgorithm,
580    path: &str,
581    chunk_size: Option<usize>,
582) -> Result<u64, std::io::Error> {
583    checksum_file_with_digest(Digest::new(algorithm), path, chunk_size)
584}
585
586/// Computes the CRC checksum for the given file using custom CRC parameters.
587///
588/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
589///
590/// # Errors
591///
592/// This function will return an error if the file cannot be read.
593///
594/// # Examples
595///
596/// ```rust
597/// use std::env;
598/// use crc_fast::{checksum_file_with_params, CrcParams};
599///
600/// // for example/test purposes only, use your own file path
601/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
602/// let file_on_disk = file_path.to_str().unwrap();
603///
604/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
605/// let custom_params = CrcParams::new(
606///     "CRC-32/CUSTOM",
607///     32,
608///     0x04c11db7,
609///     0xffffffff,
610///     true,
611///     0xffffffff,
612///     0xcbf43926,
613/// );
614///
615/// let checksum = checksum_file_with_params(custom_params, file_on_disk, None);
616///
617/// assert_eq!(checksum.unwrap(), 0xcbf43926);
618/// ```
619pub fn checksum_file_with_params(
620    params: CrcParams,
621    path: &str,
622    chunk_size: Option<usize>,
623) -> Result<u64, std::io::Error> {
624    checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size)
625}
626
627/// Computes the CRC checksum for the given file using the specified Digest.
628///
629/// # Errors
630///
631/// This function will return an error if the file cannot be read.
632fn checksum_file_with_digest(
633    mut digest: Digest,
634    path: &str,
635    chunk_size: Option<usize>,
636) -> Result<u64, std::io::Error> {
637    let mut file = File::open(path)?;
638
639    // 512KiB KiB was fastest in my benchmarks on an Apple M2 Ultra
640    //
641    // 4KiB ~7GiB/s
642    // 64KiB ~22 GiB/s
643    // 512KiB ~24 GiB/s
644    let chunk_size = chunk_size.unwrap_or(524288);
645
646    let mut buf = vec![0; chunk_size];
647
648    while let Ok(n) = file.read(&mut buf) {
649        if n == 0 {
650            break;
651        }
652        digest.update(&buf[..n]);
653    }
654
655    Ok(digest.finalize())
656}
657
658/// Combines two CRC checksums using the specified algorithm.
659///
660/// # Examples
661///```rust
662/// use crc_fast::{checksum, checksum_combine, CrcAlgorithm::Crc32IsoHdlc};
663///
664/// let checksum_1 = checksum(Crc32IsoHdlc, b"1234");
665/// let checksum_2 = checksum(Crc32IsoHdlc, b"56789");
666/// let checksum = checksum_combine(Crc32IsoHdlc, checksum_1, checksum_2, 5);
667///
668/// assert_eq!(checksum, 0xcbf43926);
669/// ```
670#[inline(always)]
671pub fn checksum_combine(
672    algorithm: CrcAlgorithm,
673    checksum1: u64,
674    checksum2: u64,
675    checksum2_len: u64,
676) -> u64 {
677    let params = get_calculator_params(algorithm).1;
678
679    combine::checksums(checksum1, checksum2, checksum2_len, params)
680}
681
682/// Combines two CRC checksums using custom CRC parameters.
683///
684/// # Examples
685///
686/// ```rust
687/// use crc_fast::{checksum_with_params, checksum_combine_with_params, CrcParams};
688///
689/// // Define custom CRC-32 parameters (equivalent to CRC-32/ISO-HDLC)
690/// let custom_params = CrcParams::new(
691///     "CRC-32/CUSTOM",
692///     32,
693///     0x04c11db7,
694///     0xffffffff,
695///     true,
696///     0xffffffff,
697///     0xcbf43926,
698/// );
699///
700/// let checksum_1 = checksum_with_params(custom_params, b"1234");
701/// let checksum_2 = checksum_with_params(custom_params, b"56789");
702/// let checksum = checksum_combine_with_params(custom_params, checksum_1, checksum_2, 5);
703///
704/// assert_eq!(checksum, 0xcbf43926);
705/// ```
706pub fn checksum_combine_with_params(
707    params: CrcParams,
708    checksum1: u64,
709    checksum2: u64,
710    checksum2_len: u64,
711) -> u64 {
712    combine::checksums(checksum1, checksum2, checksum2_len, params)
713}
714
715/// Returns the target used to calculate the CRC checksum for the specified algorithm.
716///
717/// These strings are informational only, not stable, and shouldn't be relied on to match across
718/// versions.
719///
720/// # Examples
721///```rust
722/// use crc_fast::{get_calculator_target, CrcAlgorithm::Crc32IsoHdlc};
723///
724/// let target = get_calculator_target(Crc32IsoHdlc);
725/// ```
726pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
727    arch::get_target()
728}
729
730/// Returns the calculator function and parameters for the specified CRC algorithm.
731#[inline(always)]
732fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
733    match algorithm {
734        CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
735        CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
736        CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
737        CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
738        CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
739        CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
740        CrcAlgorithm::Crc32Custom => {
741            panic!("Custom CRC-32 requires parameters via CrcParams::new()")
742        }
743        CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
744        CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
745        CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
746        CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
747        CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
748        CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
749        CrcAlgorithm::Crc64Custom => {
750            panic!("Custom CRC-64 requires parameters via CrcParams::new()")
751        }
752        CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
753        CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
754        CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
755        CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
756        CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
757        CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
758        CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
759    }
760}
761
762/// Calculates the CRC-32/ISCSI ("crc32c" in many, but not all, implementations) checksum.
763///
764/// Because both aarch64 and x86 have native hardware support for CRC-32/ISCSI, we can use
765/// fusion techniques to accelerate the calculation beyond what SIMD can do alone.
766#[inline(always)]
767fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
768    // both aarch64 and x86 have native CRC-32/ISCSI support, so we can use fusion
769    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
770    return fusion::crc32_iscsi(state as u32, data) as u64;
771
772    #[cfg(all(not(target_arch = "aarch64"), not(target_arch = "x86_64")))]
773    // fallback to traditional calculation if not aarch64 or x86_64
774    Calculator::calculate(state, data, _params)
775}
776
777/// Calculates the CRC-32/ISO-HDLC ("crc32" in many, but not all, implementations) checksum.
778///
779/// Because aarch64 has native hardware support for CRC-32/ISO-HDLC, we can use fusion techniques
780/// to accelerate the calculation beyond what SIMD can do alone. x86 does not have native support,
781/// so we use the traditional calculation.
782#[inline(always)]
783fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
784    // aarch64 CPUs have native CRC-32/ISO-HDLC support, so we can use the fusion implementation
785    #[cfg(target_arch = "aarch64")]
786    return fusion::crc32_iso_hdlc(state as u32, data) as u64;
787
788    // x86 CPUs don't have native CRC-32/ISO-HDLC support, so there's no fusion to be had, use
789    // traditional calculation
790    #[cfg(not(target_arch = "aarch64"))]
791    Calculator::calculate(state, data, _params)
792}
793
794#[cfg(test)]
795mod lib {
796    #![allow(unused)]
797
798    use super::*;
799    use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
800    use crate::test::enums::AnyCrcTestConfig;
801    use cbindgen::Language::C;
802    use cbindgen::Style::Both;
803    use rand::{rng, Rng};
804    use std::fs::{read, write};
805
806    #[test]
807    fn test_checksum_check() {
808        for config in TEST_ALL_CONFIGS {
809            assert_eq!(
810                checksum(config.get_algorithm(), TEST_CHECK_STRING),
811                config.get_check()
812            );
813        }
814    }
815
816    #[test]
817    fn test_checksum_reference() {
818        for config in TEST_ALL_CONFIGS {
819            assert_eq!(
820                checksum(config.get_algorithm(), TEST_CHECK_STRING),
821                config.checksum_with_reference(TEST_CHECK_STRING)
822            );
823        }
824    }
825
826    #[test]
827    fn test_checksum_with_custom_params() {
828        crate::cache::clear_cache();
829
830        // CRC-32 reflected
831        assert_eq!(
832            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
833            CRC32_ISCSI.check,
834        );
835
836        // CRC-32 forward
837        assert_eq!(
838            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
839            CRC32_BZIP2.check,
840        );
841
842        // CRC-64 reflected
843        assert_eq!(
844            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
845            CRC64_NVME.check,
846        );
847
848        // CRC-64 forward
849        assert_eq!(
850            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
851            CRC64_ECMA_182.check,
852        );
853    }
854
855    #[test]
856    fn test_get_custom_params() {
857        crate::cache::clear_cache();
858
859        assert_eq!(
860            checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING),
861            CRC32_ISCSI.check,
862        );
863
864        assert_eq!(
865            checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING),
866            CRC32_BZIP2.check,
867        );
868
869        assert_eq!(
870            checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING),
871            CRC64_NVME.check,
872        );
873
874        assert_eq!(
875            checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING),
876            CRC64_ECMA_182.check,
877        );
878    }
879
880    #[test]
881    fn test_digest_updates_check() {
882        for config in TEST_ALL_CONFIGS {
883            check_digest(Digest::new(config.get_algorithm()), config.get_check());
884        }
885    }
886
887    #[test]
888    fn test_digest_updates_check_with_custom_params() {
889        crate::cache::clear_cache();
890
891        // CRC-32 reflected
892        check_digest(
893            Digest::new_with_params(get_custom_crc32_reflected()),
894            CRC32_ISCSI.check,
895        );
896
897        // CRC-32 forward
898        check_digest(
899            Digest::new_with_params(get_custom_crc32_forward()),
900            CRC32_BZIP2.check,
901        );
902
903        // CRC-64 reflected
904        check_digest(
905            Digest::new_with_params(get_custom_crc64_reflected()),
906            CRC64_NVME.check,
907        );
908
909        // CRC-64 forward
910        check_digest(
911            Digest::new_with_params(get_custom_crc64_forward()),
912            CRC64_ECMA_182.check,
913        );
914    }
915
916    fn check_digest(mut digest: Digest, check: u64) {
917        digest.update(b"123");
918        digest.update(b"456");
919        digest.update(b"789");
920        assert_eq!(digest.finalize(), check,);
921    }
922
923    #[test]
924    fn test_small_all_lengths() {
925        for config in TEST_ALL_CONFIGS {
926            // Test each length from 1 to 255
927            for len in 1..=255 {
928                test_length(len, config);
929            }
930        }
931    }
932
933    #[test]
934    fn test_medium_lengths() {
935        for config in TEST_ALL_CONFIGS {
936            // Test each length from 256 to 1024, which should fold and include handling remainders
937            for len in 256..=1024 {
938                test_length(len, config);
939            }
940        }
941    }
942
943    #[test]
944    fn test_large_lengths() {
945        for config in TEST_ALL_CONFIGS {
946            // Test 1 MiB just before, at, and just after the folding boundaries
947            for len in 1048575..1048577 {
948                test_length(len, config);
949            }
950        }
951    }
952
953    fn test_length(length: usize, config: &AnyCrcTestConfig) {
954        let mut data = vec![0u8; length];
955        rng().fill(&mut data[..]);
956
957        // Calculate expected CRC using the reference implementation
958        let expected = config.checksum_with_reference(&data);
959
960        let result = checksum(config.get_algorithm(), &data);
961
962        assert_eq!(
963            result,
964            expected,
965            "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
966            config.get_algorithm(),
967            length,
968            expected,
969            result
970        );
971    }
972
973    #[test]
974    fn test_combine() {
975        for config in TEST_ALL_CONFIGS {
976            let algorithm = config.get_algorithm();
977            let check = config.get_check();
978
979            // checksums
980            let checksum1 = checksum(algorithm, "1234".as_ref());
981            let checksum2 = checksum(algorithm, "56789".as_ref());
982
983            // checksum_combine()
984            assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
985
986            // Digest
987            let mut digest1 = Digest::new(algorithm);
988            digest1.update("1234".as_ref());
989
990            let mut digest2 = Digest::new(algorithm);
991            digest2.update("56789".as_ref());
992
993            digest1.combine(&digest2);
994
995            assert_eq!(digest1.finalize(), check)
996        }
997    }
998
999    #[test]
1000    fn test_combine_with_custom_params() {
1001        crate::cache::clear_cache();
1002
1003        // CRC-32 reflected
1004        let crc32_params = get_custom_crc32_reflected();
1005        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1006        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1007        assert_eq!(
1008            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1009            CRC32_ISCSI.check,
1010        );
1011
1012        // CRC-32 forward
1013        let crc32_params = get_custom_crc32_forward();
1014        let checksum1 = checksum_with_params(crc32_params, "1234".as_ref());
1015        let checksum2 = checksum_with_params(crc32_params, "56789".as_ref());
1016        assert_eq!(
1017            checksum_combine_with_params(crc32_params, checksum1, checksum2, 5),
1018            CRC32_BZIP2.check,
1019        );
1020
1021        // CRC-64 reflected
1022        let crc64_params = get_custom_crc64_reflected();
1023        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1024        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1025        assert_eq!(
1026            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1027            CRC64_NVME.check,
1028        );
1029
1030        // CRC-64 forward
1031        let crc64_params = get_custom_crc64_forward();
1032        let checksum1 = checksum_with_params(crc64_params, "1234".as_ref());
1033        let checksum2 = checksum_with_params(crc64_params, "56789".as_ref());
1034        assert_eq!(
1035            checksum_combine_with_params(crc64_params, checksum1, checksum2, 5),
1036            CRC64_ECMA_182.check,
1037        );
1038    }
1039
1040    #[test]
1041    fn test_checksum_file() {
1042        // Create a test file with repeating zeros
1043        let test_file_path = "test/test_crc32_hash_file.bin";
1044        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1045        if let Err(e) = write(test_file_path, &data) {
1046            eprintln!("Skipping test due to write error: {}", e);
1047            return;
1048        }
1049
1050        for config in TEST_ALL_CONFIGS {
1051            let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
1052            assert_eq!(result, config.checksum_with_reference(&data));
1053        }
1054
1055        std::fs::remove_file(test_file_path).unwrap();
1056    }
1057
1058    #[test]
1059    fn test_checksum_file_with_custom_params() {
1060        crate::cache::clear_cache();
1061
1062        // Create a test file with repeating zeros
1063        let test_file_path = "test/test_crc32_hash_file_custom.bin";
1064        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1065        if let Err(e) = write(test_file_path, &data) {
1066            eprintln!("Skipping test due to write error: {}", e);
1067            return;
1068        }
1069
1070        // CRC-32 reflected
1071        check_file(
1072            get_custom_crc32_reflected(),
1073            test_file_path,
1074            CRC32_ISCSI.check,
1075        );
1076
1077        // CRC-32 forward
1078        check_file(
1079            get_custom_crc32_forward(),
1080            test_file_path,
1081            CRC32_BZIP2.check,
1082        );
1083
1084        // CRC-64 reflected
1085        check_file(
1086            get_custom_crc64_reflected(),
1087            test_file_path,
1088            CRC64_NVME.check,
1089        );
1090
1091        // CRC-64 forward
1092        check_file(
1093            get_custom_crc64_forward(),
1094            test_file_path,
1095            CRC64_ECMA_182.check,
1096        );
1097
1098        std::fs::remove_file(test_file_path).unwrap();
1099    }
1100
1101    fn check_file(params: CrcParams, file_path: &str, check: u64) {
1102        let result = checksum_file_with_params(params, file_path, None).unwrap();
1103        assert_eq!(result, check);
1104    }
1105
1106    #[test]
1107    fn test_writer() {
1108        // Create a test file with repeating zeros
1109        let test_file_path = "test/test_crc32_writer_file.bin";
1110        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
1111        if let Err(e) = std::fs::write(test_file_path, &data) {
1112            eprintln!("Skipping test due to write error: {}", e);
1113            return;
1114        }
1115
1116        for config in TEST_ALL_CONFIGS {
1117            let mut digest = Digest::new(config.get_algorithm());
1118            let mut file = File::open(test_file_path).unwrap();
1119            std::io::copy(&mut file, &mut digest).unwrap();
1120            assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
1121        }
1122
1123        std::fs::remove_file(test_file_path).unwrap();
1124    }
1125    #[test]
1126    fn test_digest_reset() {
1127        for config in TEST_ALL_CONFIGS {
1128            let mut digest = Digest::new(config.get_algorithm());
1129            digest.update(b"42");
1130            digest.reset();
1131            digest.update(TEST_CHECK_STRING);
1132            assert_eq!(digest.finalize(), config.get_check());
1133        }
1134    }
1135
1136    #[test]
1137    fn test_digest_finalize_reset() {
1138        for config in TEST_ALL_CONFIGS {
1139            let check = config.get_check();
1140
1141            let mut digest = Digest::new(config.get_algorithm());
1142            digest.update(TEST_CHECK_STRING);
1143            assert_eq!(digest.finalize_reset(), check);
1144
1145            digest.update(TEST_CHECK_STRING);
1146            assert_eq!(digest.finalize(), check);
1147        }
1148    }
1149
1150    #[test]
1151    fn test_digest_finalize_into() {
1152        for config in TEST_ALL_CONFIGS {
1153            let mut digest = Digest::new(config.get_algorithm());
1154            digest.update(TEST_CHECK_STRING);
1155
1156            match digest.params.width {
1157                32 => {
1158                    let mut output = [0u8; 4];
1159                    digest.finalize_into(&mut output).unwrap();
1160                    let result = u32::from_be_bytes(output) as u64;
1161                    assert_eq!(result, config.get_check());
1162                }
1163                64 => {
1164                    let mut output = [0u8; 8];
1165                    digest.finalize_into(&mut output).unwrap();
1166                    let result = u64::from_be_bytes(output);
1167                    assert_eq!(result, config.get_check());
1168                }
1169                _ => panic!("Unsupported CRC width"),
1170            }
1171        }
1172    }
1173
1174    #[test]
1175    fn test_digest_finalize_into_reset() {
1176        for config in TEST_ALL_CONFIGS {
1177            let mut digest = Digest::new(config.get_algorithm());
1178            digest.update(TEST_CHECK_STRING);
1179
1180            let mut output: Vec<u8> = match digest.params.width {
1181                32 => vec![0u8; 4],
1182                64 => vec![0u8; 8],
1183                _ => panic!("Unsupported CRC width"),
1184            };
1185
1186            digest.finalize_into_reset(&mut output).unwrap();
1187            let result = match output.len() {
1188                4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
1189                8 => u64::from_be_bytes(output.try_into().unwrap()),
1190                _ => panic!("Unsupported CRC width"),
1191            };
1192            assert_eq!(result, config.get_check());
1193
1194            digest.update(TEST_CHECK_STRING);
1195            assert_eq!(digest.finalize(), config.get_check());
1196        }
1197    }
1198
1199    /// Tests whether the FFI header is up-to-date
1200    #[test]
1201    fn test_ffi_header() -> Result<(), String> {
1202        #[cfg(target_os = "windows")]
1203        {
1204            // Skip this test on Windows, since CRLF vs LF is a PITA
1205            eprintln!("Skipping test on Windows");
1206
1207            return Ok(());
1208        }
1209
1210        const HEADER: &str = "libcrc_fast.h";
1211
1212        let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
1213
1214        let mut expected = Vec::new();
1215        cbindgen::Builder::new()
1216            .with_crate(crate_dir)
1217            .with_include_guard("CRC_FAST_H")
1218            .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
1219            // exclude internal implementation functions
1220            .exclude_item("crc32_iscsi_impl")
1221            .exclude_item("crc32_iso_hdlc_impl")
1222            .exclude_item("get_iscsi_target")
1223            .exclude_item("get_iso_hdlc_target")
1224            .exclude_item("ISO_HDLC_TARGET")
1225            .exclude_item("ISCSI_TARGET")
1226            .exclude_item("CrcParams")
1227            .rename_item("Digest", "CrcFastDigest")
1228            .with_style(Both)
1229            // generate C header
1230            .with_language(C)
1231            // with C++ compatibility
1232            .with_cpp_compat(true)
1233            .generate()
1234            .map_err(|error| error.to_string())?
1235            .write(&mut expected);
1236
1237        // Convert the expected bytes to string for pattern replacement, since cbindgen
1238        // generates an annoying amount of empty contiguous newlines
1239        let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
1240
1241        // Replace excessive newlines (3 or more consecutive newlines) with 2 newlines
1242        let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
1243        let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
1244
1245        // Convert back to bytes
1246        expected = cleaned_content.into_bytes();
1247
1248        let actual = read(HEADER).map_err(|error| error.to_string())?;
1249
1250        if expected != actual {
1251            write(HEADER, expected).map_err(|error| error.to_string())?;
1252            return Err(format!(
1253                "{HEADER} is not up-to-date, commit the generated file and try again"
1254            ));
1255        }
1256
1257        Ok(())
1258    }
1259
1260    fn get_custom_crc32_reflected() -> CrcParams {
1261        CrcParams::new(
1262            "Custom CRC-32/ISCSI",
1263            32,
1264            CRC32_ISCSI.poly,
1265            CRC32_ISCSI.init,
1266            CRC32_ISCSI.refin,
1267            CRC32_ISCSI.xorout,
1268            CRC32_ISCSI.check,
1269        )
1270    }
1271
1272    fn get_custom_crc32_forward() -> CrcParams {
1273        CrcParams::new(
1274            "Custom CRC-32/BZIP2",
1275            32,
1276            CRC32_BZIP2.poly,
1277            CRC32_BZIP2.init,
1278            CRC32_BZIP2.refin,
1279            CRC32_BZIP2.xorout,
1280            CRC32_BZIP2.check,
1281        )
1282    }
1283
1284    fn get_custom_crc64_reflected() -> CrcParams {
1285        CrcParams::new(
1286            "Custom CRC-64/NVME",
1287            64,
1288            CRC64_NVME.poly,
1289            CRC64_NVME.init,
1290            CRC64_NVME.refin,
1291            CRC64_NVME.xorout,
1292            CRC64_NVME.check,
1293        )
1294    }
1295
1296    fn get_custom_crc64_forward() -> CrcParams {
1297        CrcParams::new(
1298            "Custom CRC-64/ECMA-182",
1299            64,
1300            CRC64_ECMA_182.poly,
1301            CRC64_ECMA_182.init,
1302            CRC64_ECMA_182.refin,
1303            CRC64_ECMA_182.xorout,
1304            CRC64_ECMA_182.check,
1305        )
1306    }
1307
1308    #[test]
1309    fn test_crc_keys_storage_fold_256() {
1310        let test_keys = [
1311            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1312        ];
1313        let storage = CrcKeysStorage::from_keys_fold_256(test_keys);
1314
1315        // Test valid key access
1316        for i in 0..23 {
1317            assert_eq!(storage.get_key(i), test_keys[i]);
1318        }
1319
1320        // Test out-of-bounds access returns 0
1321        assert_eq!(storage.get_key(23), 0);
1322        assert_eq!(storage.get_key(24), 0);
1323        assert_eq!(storage.get_key(100), 0);
1324
1325        // Test key count
1326        assert_eq!(storage.key_count(), 23);
1327    }
1328
1329    #[test]
1330    fn test_crc_keys_storage_future_test() {
1331        let test_keys = [
1332            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1333            25,
1334        ];
1335        let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys);
1336
1337        // Test valid key access
1338        for i in 0..25 {
1339            assert_eq!(storage.get_key(i), test_keys[i]);
1340        }
1341
1342        // Test out-of-bounds access returns 0
1343        assert_eq!(storage.get_key(25), 0);
1344        assert_eq!(storage.get_key(26), 0);
1345        assert_eq!(storage.get_key(100), 0);
1346
1347        // Test key count
1348        assert_eq!(storage.key_count(), 25);
1349    }
1350
1351    #[test]
1352    fn test_crc_params_safe_accessors() {
1353        // Create a test CrcParams with known keys
1354        let test_keys = [
1355            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1356        ];
1357        let params = CrcParams {
1358            algorithm: CrcAlgorithm::Crc32IsoHdlc,
1359            name: "test",
1360            width: 32,
1361            poly: 0x04C11DB7,
1362            init: 0xFFFFFFFF,
1363            refin: true,
1364            refout: true,
1365            xorout: 0xFFFFFFFF,
1366            check: 0xCBF43926,
1367            keys: CrcKeysStorage::from_keys_fold_256(test_keys),
1368        };
1369
1370        // Test valid key access
1371        for i in 0..23 {
1372            assert_eq!(params.get_key(i), test_keys[i]);
1373            assert_eq!(params.get_key_checked(i), Some(test_keys[i]));
1374        }
1375
1376        // Test out-of-bounds access
1377        assert_eq!(params.get_key(23), 0);
1378        assert_eq!(params.get_key(24), 0);
1379        assert_eq!(params.get_key(100), 0);
1380
1381        assert_eq!(params.get_key_checked(23), None);
1382        assert_eq!(params.get_key_checked(24), None);
1383        assert_eq!(params.get_key_checked(100), None);
1384
1385        // Test key count
1386        assert_eq!(params.key_count(), 23);
1387    }
1388
1389    #[test]
1390    fn test_crc_keys_storage_const_constructors() {
1391        // Test that const constructors work in const context
1392        const TEST_KEYS_23: [u64; 23] = [1; 23];
1393        const TEST_KEYS_25: [u64; 25] = [2; 25];
1394
1395        const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23);
1396        const STORAGE_FUTURE: CrcKeysStorage =
1397            CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25);
1398
1399        // Verify the const constructors work correctly
1400        assert_eq!(STORAGE_256.get_key(0), 1);
1401        assert_eq!(STORAGE_256.key_count(), 23);
1402
1403        assert_eq!(STORAGE_FUTURE.get_key(0), 2);
1404        assert_eq!(STORAGE_FUTURE.key_count(), 25);
1405    }
1406
1407    #[test]
1408    fn test_crc_keys_storage_bounds_safety() {
1409        let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]);
1410        let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]);
1411
1412        // Test edge cases for bounds checking
1413        assert_eq!(storage_256.get_key(22), 42); // Last valid index
1414        assert_eq!(storage_256.get_key(23), 0); // First invalid index
1415
1416        assert_eq!(storage_future.get_key(24), 84); // Last valid index
1417        assert_eq!(storage_future.get_key(25), 0); // First invalid index
1418
1419        // Test very large indices
1420        assert_eq!(storage_256.get_key(usize::MAX), 0);
1421        assert_eq!(storage_future.get_key(usize::MAX), 0);
1422    }
1423}