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