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//! ```no_run
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
108use crate::crc32::consts::{
109    CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
110    CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
111};
112
113#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
114use crate::crc32::fusion;
115
116use crate::crc64::consts::{
117    CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
118};
119use crate::structs::{Calculator, CrcParams};
120use crate::traits::CrcCalculator;
121use digest::{DynDigest, InvalidBufferSize};
122use std::fs::File;
123use std::io::{Read, Write};
124
125mod algorithm;
126mod arch;
127mod combine;
128mod consts;
129mod crc32;
130mod crc64;
131mod enums;
132mod ffi;
133mod generate;
134mod structs;
135mod test;
136mod traits;
137
138/// Supported CRC-32 and CRC-64 variants
139#[derive(Debug, Clone, Copy)]
140pub enum CrcAlgorithm {
141    Crc32Aixm,
142    Crc32Autosar,
143    Crc32Base91D,
144    Crc32Bzip2,
145    Crc32CdRomEdc,
146    Crc32Cksum,
147    Crc32Iscsi,
148    Crc32IsoHdlc,
149    Crc32Jamcrc,
150    Crc32Mef,
151    Crc32Mpeg2,
152    Crc32Xfer,
153    Crc64Ecma182,
154    Crc64GoIso,
155    Crc64Ms,
156    Crc64Nvme,
157    Crc64Redis,
158    Crc64We,
159    Crc64Xz,
160}
161
162/// Type alias for a function pointer that represents a CRC calculation function.
163///
164/// The function takes the following parameters:
165/// - `state`: The current state of the CRC computation.
166/// - `data`: A slice of bytes to be processed.
167/// - `params`: The parameters for the CRC computation, such as polynomial, initial value, etc.
168///
169/// The function returns the updated state after processing the data.
170type CalculatorFn = fn(
171    u64,       // state
172    &[u8],     // data
173    CrcParams, // CRC implementation parameters
174) -> u64;
175
176/// Represents a CRC Digest, which is used to compute CRC checksums.
177///
178/// The `Digest` struct maintains the state of the CRC computation, including
179/// the current state, the amount of data processed, the CRC parameters, and
180/// the calculator function used to perform the CRC calculation.
181#[derive(Copy, Clone, Debug)]
182pub struct Digest {
183    /// The current state of the CRC computation.
184    state: u64,
185
186    /// The total amount of data processed so far.
187    amount: u64,
188
189    /// The parameters for the CRC computation, such as polynomial, initial value, etc.
190    params: CrcParams,
191
192    /// The function used to perform the CRC calculation.
193    calculator: CalculatorFn,
194}
195
196impl DynDigest for Digest {
197    #[inline(always)]
198    fn update(&mut self, data: &[u8]) {
199        self.update(data);
200    }
201
202    #[inline(always)]
203    fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
204        if buf.len() != self.output_size() {
205            return Err(InvalidBufferSize);
206        }
207
208        let result = self.finalize();
209        let bytes = if self.output_size() == 4 {
210            result.to_be_bytes()[4..].to_vec() // Take last 4 bytes for 32-bit CRC
211        } else {
212            result.to_be_bytes().to_vec() // Use all 8 bytes for 64-bit CRC
213        };
214        buf.copy_from_slice(&bytes[..self.output_size()]);
215
216        Ok(())
217    }
218
219    #[inline(always)]
220    fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
221        if out.len() != self.output_size() {
222            return Err(InvalidBufferSize);
223        }
224        let result = self.finalize();
225        self.reset();
226        let bytes = if self.output_size() == 4 {
227            result.to_be_bytes()[4..].to_vec() // Take last 4 bytes for 32-bit CRC
228        } else {
229            result.to_be_bytes().to_vec() // Use all 8 bytes for 64-bit CRC
230        };
231        out.copy_from_slice(&bytes[..self.output_size()]);
232        Ok(())
233    }
234
235    #[inline(always)]
236    fn reset(&mut self) {
237        self.reset();
238    }
239
240    #[inline(always)]
241    fn output_size(&self) -> usize {
242        self.params.width as usize / 8
243    }
244
245    fn box_clone(&self) -> Box<dyn DynDigest> {
246        Box::new(*self)
247    }
248}
249
250impl Digest {
251    /// Creates a new `Digest` instance for the specified CRC algorithm.
252    #[inline(always)]
253    pub fn new(algorithm: CrcAlgorithm) -> Self {
254        let (calculator, params) = get_calculator_params(algorithm);
255
256        Self {
257            state: params.init,
258            amount: 0,
259            params,
260            calculator,
261        }
262    }
263
264    /// Updates the CRC state with the given data.
265    #[inline(always)]
266    pub fn update(&mut self, data: &[u8]) {
267        self.state = (self.calculator)(self.state, data, self.params);
268        self.amount += data.len() as u64;
269    }
270
271    /// Finalizes the CRC computation and returns the result.
272    #[inline(always)]
273    pub fn finalize(&self) -> u64 {
274        self.state ^ self.params.xorout
275    }
276
277    /// Finalizes the CRC computation, resets the state, and returns the result.
278    #[inline(always)]
279    pub fn finalize_reset(&mut self) -> u64 {
280        let result = self.finalize();
281        self.reset();
282
283        result
284    }
285
286    /// Resets the CRC state to its initial value.
287    #[inline(always)]
288    pub fn reset(&mut self) {
289        self.state = self.params.init;
290        self.amount = 0;
291    }
292
293    /// Combines the CRC state with a second `Digest` instance.
294    #[inline(always)]
295    pub fn combine(&mut self, other: &Self) {
296        self.amount += other.amount;
297        let other_crc = other.finalize();
298
299        // note the xorout for the input, since it's already been applied so it has to be removed,
300        // and then re-adding it on the final output
301        self.state = combine::checksums(
302            self.state ^ self.params.xorout,
303            other_crc,
304            other.amount,
305            self.params,
306        ) ^ self.params.xorout;
307    }
308
309    /// Gets the amount of data processed so far
310    #[inline(always)]
311    pub fn get_amount(&self) -> u64 {
312        self.amount
313    }
314}
315
316impl Write for Digest {
317    #[inline(always)]
318    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
319        self.update(buf);
320        Ok(buf.len())
321    }
322
323    #[inline(always)]
324    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
325        let len: usize = bufs
326            .iter()
327            .map(|buf| {
328                self.update(buf);
329                buf.len()
330            })
331            .sum();
332
333        Ok(len)
334    }
335
336    #[inline(always)]
337    fn flush(&mut self) -> std::io::Result<()> {
338        Ok(())
339    }
340
341    #[inline(always)]
342    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
343        self.update(buf);
344
345        Ok(())
346    }
347}
348
349/// Computes the CRC checksum for the given data using the specified algorithm.
350///
351///```rust
352/// use crc_fast::{checksum, CrcAlgorithm::Crc32IsoHdlc};
353/// let checksum = checksum(Crc32IsoHdlc, b"123456789");
354///
355/// assert_eq!(checksum, 0xcbf43926);
356/// ```
357#[inline(always)]
358pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
359    let (calculator, params) = get_calculator_params(algorithm);
360
361    calculator(params.init, buf, params) ^ params.xorout
362}
363
364/// Computes the CRC checksum for the given file using the specified algorithm.
365///
366/// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra
367///
368/// # Errors
369///
370/// This function will return an error if the file cannot be read.
371///
372/// # Examples
373/// ### checksum_file
374///```no_run
375/// use std::env;
376/// use crc_fast::{checksum_file, CrcAlgorithm::Crc32IsoHdlc};
377///
378/// // for example/test purposes only, use your own file path
379/// let file_path = env::current_dir().expect("missing working dir").join("crc-check.txt");
380/// let file_on_disk = file_path.to_str().unwrap();
381///
382/// let checksum = checksum_file(Crc32IsoHdlc, file_on_disk, None);
383///
384/// assert_eq!(checksum.unwrap(), 0xcbf43926);
385/// ```
386#[inline(always)]
387pub fn checksum_file(
388    algorithm: CrcAlgorithm,
389    path: &str,
390    chunk_size: Option<usize>,
391) -> Result<u64, std::io::Error> {
392    let mut digest = Digest::new(algorithm);
393    let mut file = File::open(path)?;
394
395    // 512KiB KiB was fastest in my benchmarks on an Apple M2 Ultra
396    //
397    // 4KiB ~7GiB/s
398    // 64KiB ~22 GiB/s
399    // 512KiB ~24 GiB/s
400    let chunk_size = chunk_size.unwrap_or(524288);
401
402    let mut buf = vec![0; chunk_size];
403
404    while let Ok(n) = file.read(&mut buf) {
405        if n == 0 {
406            break;
407        }
408        digest.update(&buf[..n]);
409    }
410
411    Ok(digest.finalize())
412}
413
414/// Combines two CRC checksums using the specified algorithm.
415///
416/// # Examples
417///```rust
418/// use crc_fast::{checksum, checksum_combine, CrcAlgorithm::Crc32IsoHdlc};
419///
420/// let checksum_1 = checksum(Crc32IsoHdlc, b"1234");
421/// let checksum_2 = checksum(Crc32IsoHdlc, b"56789");
422/// let checksum = checksum_combine(Crc32IsoHdlc, checksum_1, checksum_2, 5);
423///
424/// assert_eq!(checksum, 0xcbf43926);
425/// ```
426#[inline(always)]
427pub fn checksum_combine(
428    algorithm: CrcAlgorithm,
429    checksum1: u64,
430    checksum2: u64,
431    checksum2_len: u64,
432) -> u64 {
433    let params = get_calculator_params(algorithm).1;
434
435    combine::checksums(checksum1, checksum2, checksum2_len, params)
436}
437
438/// Returns the target used to calculate the CRC checksum for the specified algorithm.
439///
440/// These strings are informational only, not stable, and shouldn't be relied on to match across
441/// versions.
442///
443/// # Examples
444///```rust
445/// use crc_fast::{get_calculator_target, CrcAlgorithm::Crc32IsoHdlc};
446///
447/// let target = get_calculator_target(Crc32IsoHdlc);
448/// ```
449pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
450    arch::get_target()
451}
452
453/// Returns the calculator function and parameters for the specified CRC algorithm.
454#[inline(always)]
455fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
456    match algorithm {
457        CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
458        CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
459        CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
460        CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
461        CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
462        CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
463        CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
464        CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
465        CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
466        CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
467        CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
468        CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
469        CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
470        CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
471        CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
472        CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
473        CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
474        CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
475        CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
476    }
477}
478
479/// Calculates the CRC-32/ISCSI ("crc32c" in many, but not all, implementations) checksum.
480///
481/// Because both aarch64 and x86 have native hardware support for CRC-32/ISCSI, we can use
482/// fusion techniques to accelerate the calculation beyond what SIMD can do alone.
483#[inline(always)]
484fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
485    // both aarch64 and x86 have native CRC-32/ISCSI support, so we can use fusion
486    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
487    return fusion::crc32_iscsi(state as u32, data) as u64;
488
489    #[cfg(all(not(target_arch = "aarch64"), not(target_arch = "x86_64")))]
490    // fallback to traditional calculation if not aarch64 or x86_64
491    Calculator::calculate(state, data, _params)
492}
493
494/// Calculates the CRC-32/ISO-HDLC ("crc32" in many, but not all, implementations) checksum.
495///
496/// Because aarch64 has native hardware support for CRC-32/ISO-HDLC, we can use fusion techniques
497/// to accelerate the calculation beyond what SIMD can do alone. x86 does not have native support,
498/// so we use the traditional calculation.
499#[inline(always)]
500fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
501    // aarch64 CPUs have native CRC-32/ISO-HDLC support, so we can use the fusion implementation
502    #[cfg(target_arch = "aarch64")]
503    return fusion::crc32_iso_hdlc(state as u32, data) as u64;
504
505    // x86 CPUs don't have native CRC-32/ISO-HDLC support, so there's no fusion to be had, use
506    // traditional calculation
507    #[cfg(not(target_arch = "aarch64"))]
508    Calculator::calculate(state, data, _params)
509}
510
511#[cfg(test)]
512mod lib {
513    #![allow(unused)]
514
515    use super::*;
516    use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
517    use crate::test::enums::AnyCrcTestConfig;
518    use cbindgen::Language::{Cxx, C};
519    use cbindgen::Style::Both;
520    use rand::{rng, Rng};
521    use std::fs::{read, write};
522
523    #[test]
524    fn test_checksum_check() {
525        for config in TEST_ALL_CONFIGS {
526            assert_eq!(
527                checksum(config.get_algorithm(), TEST_CHECK_STRING),
528                config.get_check()
529            );
530        }
531    }
532
533    #[test]
534    fn test_checksum_reference() {
535        for config in TEST_ALL_CONFIGS {
536            assert_eq!(
537                checksum(config.get_algorithm(), TEST_CHECK_STRING),
538                config.checksum_with_reference(TEST_CHECK_STRING)
539            );
540        }
541    }
542
543    #[test]
544    fn test_digest_updates_check() {
545        for config in TEST_ALL_CONFIGS {
546            let mut digest = Digest::new(config.get_algorithm());
547            digest.update(b"123");
548            digest.update(b"456");
549            digest.update(b"789");
550            let result = digest.finalize();
551
552            assert_eq!(result, config.get_check());
553        }
554    }
555
556    #[test]
557    fn test_small_all_lengths() {
558        for config in TEST_ALL_CONFIGS {
559            // Test each length from 1 to 255
560            for len in 1..=255 {
561                test_length(len, config);
562            }
563        }
564    }
565
566    #[test]
567    fn test_medium_lengths() {
568        for config in TEST_ALL_CONFIGS {
569            // Test each length from 256 to 1024, which should fold and include handling remainders
570            for len in 256..=1024 {
571                test_length(len, config);
572            }
573        }
574    }
575
576    #[test]
577    fn test_large_lengths() {
578        for config in TEST_ALL_CONFIGS {
579            // Test 1 MiB just before, at, and just after the folding boundaries
580            for len in 1048575..1048577 {
581                test_length(len, config);
582            }
583        }
584    }
585
586    fn test_length(length: usize, config: &AnyCrcTestConfig) {
587        let mut data = vec![0u8; length];
588        rng().fill(&mut data[..]);
589
590        // Calculate expected CRC using the reference implementation
591        let expected = config.checksum_with_reference(&data);
592
593        let result = checksum(config.get_algorithm(), &data);
594
595        assert_eq!(
596            result,
597            expected,
598            "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
599            config.get_algorithm(),
600            length,
601            expected,
602            result
603        );
604    }
605
606    #[test]
607    fn test_combine() {
608        for config in TEST_ALL_CONFIGS {
609            let algorithm = config.get_algorithm();
610            let check = config.get_check();
611
612            // checksums
613            let checksum1 = checksum(algorithm, "1234".as_ref());
614            let checksum2 = checksum(algorithm, "56789".as_ref());
615
616            // checksum_combine()
617            assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
618
619            // Digest
620            let mut digest1 = Digest::new(algorithm);
621            digest1.update("1234".as_ref());
622
623            let mut digest2 = Digest::new(algorithm);
624            digest2.update("56789".as_ref());
625
626            digest1.combine(&digest2);
627
628            assert_eq!(digest1.finalize(), check)
629        }
630    }
631
632    #[test]
633    fn test_checksum_file() {
634        // Create a test file with repeating zeros
635        let test_file_path = "test/test_crc32_hash_file.bin";
636        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
637        if let Err(e) = std::fs::write(test_file_path, &data) {
638            eprintln!("Skipping test due to write error: {}", e);
639            return;
640        }
641
642        for config in TEST_ALL_CONFIGS {
643            let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
644            assert_eq!(result, config.checksum_with_reference(&data));
645        }
646
647        std::fs::remove_file(test_file_path).unwrap();
648    }
649
650    #[test]
651    fn test_writer() {
652        // Create a test file with repeating zeros
653        let test_file_path = "test/test_crc32_writer_file.bin";
654        let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros
655        if let Err(e) = std::fs::write(test_file_path, &data) {
656            eprintln!("Skipping test due to write error: {}", e);
657            return;
658        }
659
660        for config in TEST_ALL_CONFIGS {
661            let mut digest = Digest::new(config.get_algorithm());
662            let mut file = File::open(test_file_path).unwrap();
663            std::io::copy(&mut file, &mut digest).unwrap();
664            assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
665        }
666
667        std::fs::remove_file(test_file_path).unwrap();
668    }
669    #[test]
670    fn test_digest_reset() {
671        for config in TEST_ALL_CONFIGS {
672            let mut digest = Digest::new(config.get_algorithm());
673            digest.update(b"42");
674            digest.reset();
675            digest.update(TEST_CHECK_STRING);
676            assert_eq!(digest.finalize(), config.get_check());
677        }
678    }
679
680    #[test]
681    fn test_digest_finalize_reset() {
682        for config in TEST_ALL_CONFIGS {
683            let check = config.get_check();
684
685            let mut digest = Digest::new(config.get_algorithm());
686            digest.update(TEST_CHECK_STRING);
687            assert_eq!(digest.finalize_reset(), check);
688
689            digest.update(TEST_CHECK_STRING);
690            assert_eq!(digest.finalize(), check);
691        }
692    }
693
694    #[test]
695    fn test_digest_finalize_into() {
696        for config in TEST_ALL_CONFIGS {
697            let mut digest = Digest::new(config.get_algorithm());
698            digest.update(TEST_CHECK_STRING);
699
700            match digest.params.width {
701                32 => {
702                    let mut output = [0u8; 4];
703                    digest.finalize_into(&mut output).unwrap();
704                    let result = u32::from_be_bytes(output) as u64;
705                    assert_eq!(result, config.get_check());
706                }
707                64 => {
708                    let mut output = [0u8; 8];
709                    digest.finalize_into(&mut output).unwrap();
710                    let result = u64::from_be_bytes(output);
711                    assert_eq!(result, config.get_check());
712                }
713                _ => panic!("Unsupported CRC width"),
714            }
715        }
716    }
717
718    #[test]
719    fn test_digest_finalize_into_reset() {
720        for config in TEST_ALL_CONFIGS {
721            let mut digest = Digest::new(config.get_algorithm());
722            digest.update(TEST_CHECK_STRING);
723
724            let mut output: Vec<u8> = match digest.params.width {
725                32 => vec![0u8; 4],
726                64 => vec![0u8; 8],
727                _ => panic!("Unsupported CRC width"),
728            };
729
730            digest.finalize_into_reset(&mut output).unwrap();
731            let result = match output.len() {
732                4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
733                8 => u64::from_be_bytes(output.try_into().unwrap()),
734                _ => panic!("Unsupported CRC width"),
735            };
736            assert_eq!(result, config.get_check());
737
738            digest.update(TEST_CHECK_STRING);
739            assert_eq!(digest.finalize(), config.get_check());
740        }
741    }
742
743    /// Tests whether the FFI header is up-to-date
744    #[test]
745    fn test_ffi_header() -> Result<(), String> {
746        #[cfg(target_os = "windows")]
747        {
748            // Skip this test on Windows, since CRLF vs LF is a PITA
749            eprintln!("Skipping test on Windows");
750
751            return Ok(());
752        }
753
754        const HEADER: &str = "libcrc_fast.h";
755
756        let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
757
758        let mut expected = Vec::new();
759        cbindgen::Builder::new()
760            .with_crate(crate_dir)
761            .with_include_guard("CRC_FAST_H")
762            .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
763            // exclude internal implementation functions
764            .exclude_item("crc32_iscsi_impl")
765            .exclude_item("crc32_iso_hdlc_impl")
766            .exclude_item("get_iscsi_target")
767            .exclude_item("get_iso_hdlc_target")
768            .exclude_item("ISO_HDLC_TARGET")
769            .exclude_item("ISCSI_TARGET")
770            .exclude_item("CrcParams")
771            .rename_item("Digest", "CrcFastDigest")
772            .with_style(Both)
773            // generate C header
774            .with_language(C)
775            // with C++ compatibility
776            .with_cpp_compat(true)
777            .generate()
778            .map_err(|error| error.to_string())?
779            .write(&mut expected);
780
781        // Convert the expected bytes to string for pattern replacement, since cbindgen
782        // generates an annoying amount of empty contiguous newlines
783        let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
784
785        // Replace excessive newlines (3 or more consecutive newlines) with 2 newlines
786        let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
787        let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
788
789        // Convert back to bytes
790        expected = cleaned_content.into_bytes();
791
792        let actual = read(HEADER).map_err(|error| error.to_string())?;
793
794        if expected != actual {
795            write(HEADER, expected).map_err(|error| error.to_string())?;
796            return Err(format!(
797                "{HEADER} is not up-to-date, commit the generated file and try again"
798            ));
799        }
800
801        Ok(())
802    }
803}