#[cfg(any(target_arch = "powerpc64", target_arch = "s390x", target_arch = "riscv64"))]
mod clmul;
pub(crate) mod config;
pub(crate) mod kernels;
pub(crate) mod portable;
#[cfg(target_arch = "x86_64")]
mod x86_64;
#[cfg(target_arch = "aarch64")]
pub(crate) mod aarch64;
#[cfg(target_arch = "powerpc64")]
mod power;
#[cfg(target_arch = "s390x")]
mod s390x;
#[cfg(target_arch = "riscv64")]
mod riscv64;
#[allow(unused_imports)]
pub use config::{Crc32Config, Crc32Force};
#[cfg(any(test, feature = "std"))]
use crate::checksum::common::reference::crc32_bitwise;
use crate::checksum::common::{
combine::{Gf2Matrix32, generate_shift8_matrix_32},
tables::{CRC32_IEEE_POLY, CRC32C_POLY, generate_crc32_tables_16},
};
#[cfg(feature = "diag")]
use crate::checksum::diag::{Crc32Polynomial, Crc32SelectionDiag};
#[allow(unused_imports)]
pub(super) use crate::traits::{Checksum, ChecksumCombine};
mod kernel_tables {
use super::*;
pub static IEEE_TABLES_16: [[u32; 256]; 16] = generate_crc32_tables_16(CRC32_IEEE_POLY);
pub static CRC32C_TABLES_16: [[u32; 256]; 16] = generate_crc32_tables_16(CRC32C_POLY);
}
#[cfg(target_arch = "x86_64")]
pub(crate) const CRC32_FOLD_BLOCK_BYTES: usize = 128;
#[cfg(any(test, feature = "std"))]
#[cfg_attr(all(test, not(feature = "std")), allow(dead_code))]
fn crc32_portable(crc: u32, data: &[u8]) -> u32 {
const THRESHOLD: usize = 64;
if data.len() < THRESHOLD {
portable::crc32_bytewise_ieee(crc, data)
} else {
portable::crc32_slice16_ieee(crc, data)
}
}
#[cfg(any(test, feature = "std"))]
#[cfg_attr(all(test, not(feature = "std")), allow(dead_code))]
fn crc32c_portable(crc: u32, data: &[u8]) -> u32 {
const THRESHOLD: usize = 64;
if data.len() < THRESHOLD {
portable::crc32c_bytewise(crc, data)
} else {
portable::crc32c_slice16(crc, data)
}
}
#[cfg(any(test, feature = "std"))]
fn crc32_reference(crc: u32, data: &[u8]) -> u32 {
crc32_bitwise(CRC32_IEEE_POLY, crc, data)
}
#[cfg(any(test, feature = "std"))]
fn crc32c_reference(crc: u32, data: &[u8]) -> u32 {
crc32_bitwise(CRC32C_POLY, crc, data)
}
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
fn crc32_buffered_threshold() -> usize {
crc32_buffered_threshold_impl()
}
#[cfg(all(feature = "alloc", target_arch = "x86_64"))]
#[inline]
#[must_use]
fn crc32_buffered_threshold_impl() -> usize {
const THRESHOLD: usize = 64;
THRESHOLD
}
#[cfg(all(feature = "alloc", target_arch = "aarch64"))]
#[inline]
#[must_use]
fn crc32_buffered_threshold_impl() -> usize {
const THRESHOLD: usize = 64;
THRESHOLD
}
#[cfg(all(feature = "alloc", not(any(target_arch = "x86_64", target_arch = "aarch64"))))]
#[inline]
#[must_use]
fn crc32_buffered_threshold_impl() -> usize {
const THRESHOLD: usize = 64;
THRESHOLD
}
#[cfg(feature = "alloc")]
#[inline]
#[must_use]
fn crc32c_buffered_threshold() -> usize {
const THRESHOLD: usize = 64;
THRESHOLD
}
#[inline]
#[must_use]
pub(crate) fn crc32_selected_kernel_name(len: usize) -> &'static str {
let cfg = config::get();
match cfg.effective_force {
Crc32Force::Reference => return kernels::REFERENCE,
Crc32Force::Portable => return kernels::PORTABLE,
#[cfg(target_arch = "aarch64")]
Crc32Force::Hwcrc => return "aarch64/hwcrc",
#[cfg(target_arch = "aarch64")]
Crc32Force::Pmull => {
return if len < 64 {
"aarch64/pmull-small"
} else {
"aarch64/pmull-v9s3x2e-s3"
};
}
#[cfg(target_arch = "aarch64")]
Crc32Force::PmullEor3 => {
return "aarch64/pmull-eor3-v9s3x2e-s3";
}
#[cfg(target_arch = "aarch64")]
Crc32Force::Sve2Pmull => {
return if len < 64 {
"aarch64/sve2-pmull-small"
} else {
"aarch64/sve2-pmull"
};
}
_ => {}
}
let table = crate::checksum::kernel_table::active_table();
table.select_names(len).crc32_ieee_name
}
#[inline]
#[must_use]
pub(crate) fn crc32c_selected_kernel_name(len: usize) -> &'static str {
let cfg = config::get();
match cfg.effective_force {
Crc32Force::Reference => return kernels::REFERENCE,
Crc32Force::Portable => return kernels::PORTABLE,
#[cfg(target_arch = "aarch64")]
Crc32Force::Hwcrc => return "aarch64/hwcrc",
#[cfg(target_arch = "aarch64")]
Crc32Force::Pmull => {
return if len < 64 {
"aarch64/pmull-small"
} else {
"aarch64/pmull-v9s3x2e-s3"
};
}
#[cfg(target_arch = "aarch64")]
Crc32Force::PmullEor3 => {
return "aarch64/pmull-eor3-v9s3x2e-s3";
}
#[cfg(target_arch = "aarch64")]
Crc32Force::Sve2Pmull => {
return if len < 64 {
"aarch64/sve2-pmull-small"
} else {
"aarch64/sve2-pmull"
};
}
_ => {}
}
let table = crate::checksum::kernel_table::active_table();
table.select_names(len).crc32c_name
}
#[cfg(feature = "diag")]
#[inline]
#[must_use]
pub(crate) fn diag_crc32_ieee(len: usize) -> Crc32SelectionDiag {
let cfg = config::get();
let selected_kernel = crc32_selected_kernel_name(len);
let reason = if cfg.effective_force != Crc32Force::Auto {
crate::checksum::diag::SelectionReason::Forced
} else {
crate::checksum::diag::SelectionReason::Auto
};
let table = crate::checksum::kernel_table::active_table();
let boundary = if !table.boundaries.is_empty() {
table.boundaries[0]
} else {
64
};
Crc32SelectionDiag {
polynomial: Crc32Polynomial::Ieee,
len,
arch: crate::platform::arch(),
reason,
effective_force: cfg.effective_force,
policy_family: "dispatch",
selected_kernel,
selected_streams: 1,
portable_to_hwcrc: boundary,
hwcrc_to_fusion: boundary,
fusion_to_avx512: usize::MAX,
fusion_to_vpclmul: usize::MAX,
min_bytes_per_lane: usize::MAX,
memory_bound: false,
has_hwcrc: false,
has_fusion: false,
has_vpclmul: false,
has_avx512: false,
has_eor3: false,
has_sve2: false,
}
}
#[cfg(feature = "diag")]
#[inline]
#[must_use]
pub(crate) fn diag_crc32c(len: usize) -> Crc32SelectionDiag {
let cfg = config::get();
let selected_kernel = crc32c_selected_kernel_name(len);
let reason = if cfg.effective_force != Crc32Force::Auto {
crate::checksum::diag::SelectionReason::Forced
} else {
crate::checksum::diag::SelectionReason::Auto
};
let table = crate::checksum::kernel_table::active_table();
let boundary = if !table.boundaries.is_empty() {
table.boundaries[0]
} else {
64
};
Crc32SelectionDiag {
polynomial: Crc32Polynomial::Castagnoli,
len,
arch: crate::platform::arch(),
reason,
effective_force: cfg.effective_force,
policy_family: "dispatch",
selected_kernel,
selected_streams: 1,
portable_to_hwcrc: boundary,
hwcrc_to_fusion: boundary,
fusion_to_avx512: usize::MAX,
fusion_to_vpclmul: usize::MAX,
min_bytes_per_lane: usize::MAX,
memory_bound: false,
has_hwcrc: false,
has_fusion: false,
has_vpclmul: false,
has_avx512: false,
has_eor3: false,
has_sve2: false,
}
}
type Crc32DispatchFn = crate::checksum::dispatchers::Crc32Fn;
#[cfg(feature = "std")]
type Crc32DispatchVectoredFn = fn(u32, &[&[u8]]) -> u32;
#[cfg(feature = "std")]
#[inline]
fn crc32_apply_kernel_vectored(mut crc: u32, bufs: &[&[u8]], kernel: Crc32DispatchFn) -> u32 {
for &buf in bufs {
if !buf.is_empty() {
crc = kernel(crc, buf);
}
}
crc
}
#[inline]
fn crc32_dispatch_auto(crc: u32, data: &[u8]) -> u32 {
let table = crate::checksum::kernel_table::active_table();
let kernel = table.select_fns(data.len()).crc32_ieee;
kernel(crc, data)
}
#[inline]
fn crc32_dispatch_auto_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc_vectored_dispatch!(crate::checksum::kernel_table::active_table(), crc, crc32_ieee, bufs)
}
#[inline]
fn crc32c_dispatch_auto(crc: u32, data: &[u8]) -> u32 {
let table = crate::checksum::kernel_table::active_table();
let kernel = table.select_fns(data.len()).crc32c;
kernel(crc, data)
}
#[inline]
fn crc32c_dispatch_auto_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc_vectored_dispatch!(crate::checksum::kernel_table::active_table(), crc, crc32c, bufs)
}
#[cfg(feature = "std")]
#[inline]
fn crc32_dispatch_reference(crc: u32, data: &[u8]) -> u32 {
crc32_reference(crc, data)
}
#[cfg(feature = "std")]
#[inline]
fn crc32_dispatch_portable(crc: u32, data: &[u8]) -> u32 {
crc32_portable(crc, data)
}
#[cfg(feature = "std")]
#[inline]
fn crc32_dispatch_reference_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32_reference)
}
#[cfg(feature = "std")]
#[inline]
fn crc32_dispatch_portable_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32_portable)
}
#[cfg(feature = "std")]
#[inline]
fn crc32c_dispatch_reference(crc: u32, data: &[u8]) -> u32 {
crc32c_reference(crc, data)
}
#[cfg(feature = "std")]
#[inline]
fn crc32c_dispatch_portable(crc: u32, data: &[u8]) -> u32 {
crc32c_portable(crc, data)
}
#[cfg(feature = "std")]
#[inline]
fn crc32c_dispatch_reference_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32c_reference)
}
#[cfg(feature = "std")]
#[inline]
fn crc32c_dispatch_portable_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32c_portable)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_force_pmull_kernel(len: usize) -> Crc32DispatchFn {
if len < 64 {
kernels::aarch64::CRC32_PMULL_SMALL_KERNEL
} else {
kernels::aarch64::CRC32_PMULL[0]
}
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_force_pmull_kernel(len: usize) -> Crc32DispatchFn {
if len < 64 {
kernels::aarch64::CRC32C_PMULL_SMALL_KERNEL
} else {
kernels::aarch64::CRC32C_PMULL[0]
}
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_force_sve2_pmull_kernel(len: usize) -> Crc32DispatchFn {
if len < 64 {
kernels::aarch64::CRC32_SVE2_PMULL_SMALL_KERNEL
} else {
kernels::aarch64::CRC32_SVE2_PMULL[0]
}
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_force_sve2_pmull_kernel(len: usize) -> Crc32DispatchFn {
if len < 64 {
kernels::aarch64::CRC32C_SVE2_PMULL_SMALL_KERNEL
} else {
kernels::aarch64::CRC32C_SVE2_PMULL[0]
}
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_hwcrc(crc: u32, data: &[u8]) -> u32 {
(kernels::aarch64::CRC32_HWCRC[0])(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_hwcrc_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, kernels::aarch64::CRC32_HWCRC[0])
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_pmull(crc: u32, data: &[u8]) -> u32 {
crc32_force_pmull_kernel(data.len())(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_pmull_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32_force_pmull_kernel(64))
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_pmull_eor3(crc: u32, data: &[u8]) -> u32 {
(kernels::aarch64::CRC32_PMULL_EOR3[0])(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_pmull_eor3_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, kernels::aarch64::CRC32_PMULL_EOR3[0])
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_sve2_pmull(crc: u32, data: &[u8]) -> u32 {
crc32_force_sve2_pmull_kernel(data.len())(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32_dispatch_sve2_pmull_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32_force_sve2_pmull_kernel(64))
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_hwcrc(crc: u32, data: &[u8]) -> u32 {
(kernels::aarch64::CRC32C_HWCRC[0])(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_hwcrc_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, kernels::aarch64::CRC32C_HWCRC[0])
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_pmull(crc: u32, data: &[u8]) -> u32 {
crc32c_force_pmull_kernel(data.len())(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_pmull_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32c_force_pmull_kernel(64))
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_pmull_eor3(crc: u32, data: &[u8]) -> u32 {
(kernels::aarch64::CRC32C_PMULL_EOR3[0])(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_pmull_eor3_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, kernels::aarch64::CRC32C_PMULL_EOR3[0])
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_sve2_pmull(crc: u32, data: &[u8]) -> u32 {
crc32c_force_sve2_pmull_kernel(data.len())(crc, data)
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn crc32c_dispatch_sve2_pmull_vectored(crc: u32, bufs: &[&[u8]]) -> u32 {
crc32_apply_kernel_vectored(crc, bufs, crc32c_force_sve2_pmull_kernel(64))
}
define_crc_dispatch! {
word_ty: u32,
dispatch_fn_ty: Crc32DispatchFn,
dispatch_vectored_fn_ty: Crc32DispatchVectoredFn,
auto_force: Crc32Force::Auto,
force_expr: config::get().effective_force,
active_table: crate::checksum::kernel_table::active_table(),
auto_dispatch: crc32_dispatch_auto,
auto_vectored_dispatch: crc32_dispatch_auto_vectored,
dispatch_cache: CRC32_DISPATCH,
dispatch_vectored_cache: CRC32_DISPATCH_VECTORED,
resolve_dispatch: resolve_crc32_dispatch,
resolve_dispatch_vectored: resolve_crc32_dispatch_vectored,
dispatch: crc32_dispatch,
dispatch_vectored: crc32_dispatch_vectored,
resolved_dispatch: crc32_resolved_dispatch,
runtime_paths: crc32_runtime_paths,
resolve_match: {
Crc32Force::Reference => crc32_dispatch_reference,
Crc32Force::Portable => crc32_dispatch_portable,
#[cfg(target_arch = "aarch64")]
Crc32Force::Hwcrc => crc32_dispatch_hwcrc,
#[cfg(target_arch = "aarch64")]
Crc32Force::Pmull => crc32_dispatch_pmull,
#[cfg(target_arch = "aarch64")]
Crc32Force::PmullEor3 => crc32_dispatch_pmull_eor3,
#[cfg(target_arch = "aarch64")]
Crc32Force::Sve2Pmull => crc32_dispatch_sve2_pmull,
_ => crc32_dispatch_auto,
},
resolve_vectored_match: {
Crc32Force::Reference => crc32_dispatch_reference_vectored,
Crc32Force::Portable => crc32_dispatch_portable_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::Hwcrc => crc32_dispatch_hwcrc_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::Pmull => crc32_dispatch_pmull_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::PmullEor3 => crc32_dispatch_pmull_eor3_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::Sve2Pmull => crc32_dispatch_sve2_pmull_vectored,
_ => crc32_dispatch_auto_vectored,
}
}
define_crc_dispatch! {
word_ty: u32,
dispatch_fn_ty: Crc32DispatchFn,
dispatch_vectored_fn_ty: Crc32DispatchVectoredFn,
auto_force: Crc32Force::Auto,
force_expr: config::get().effective_force,
active_table: crate::checksum::kernel_table::active_table(),
auto_dispatch: crc32c_dispatch_auto,
auto_vectored_dispatch: crc32c_dispatch_auto_vectored,
dispatch_cache: CRC32C_DISPATCH,
dispatch_vectored_cache: CRC32C_DISPATCH_VECTORED,
resolve_dispatch: resolve_crc32c_dispatch,
resolve_dispatch_vectored: resolve_crc32c_dispatch_vectored,
dispatch: crc32c_dispatch,
dispatch_vectored: crc32c_dispatch_vectored,
resolved_dispatch: crc32c_resolved_dispatch,
runtime_paths: crc32c_runtime_paths,
resolve_match: {
Crc32Force::Reference => crc32c_dispatch_reference,
Crc32Force::Portable => crc32c_dispatch_portable,
#[cfg(target_arch = "aarch64")]
Crc32Force::Hwcrc => crc32c_dispatch_hwcrc,
#[cfg(target_arch = "aarch64")]
Crc32Force::Pmull => crc32c_dispatch_pmull,
#[cfg(target_arch = "aarch64")]
Crc32Force::PmullEor3 => crc32c_dispatch_pmull_eor3,
#[cfg(target_arch = "aarch64")]
Crc32Force::Sve2Pmull => crc32c_dispatch_sve2_pmull,
_ => crc32c_dispatch_auto,
},
resolve_vectored_match: {
Crc32Force::Reference => crc32c_dispatch_reference_vectored,
Crc32Force::Portable => crc32c_dispatch_portable_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::Hwcrc => crc32c_dispatch_hwcrc_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::Pmull => crc32c_dispatch_pmull_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::PmullEor3 => crc32c_dispatch_pmull_eor3_vectored,
#[cfg(target_arch = "aarch64")]
Crc32Force::Sve2Pmull => crc32c_dispatch_sve2_pmull_vectored,
_ => crc32c_dispatch_auto_vectored,
}
}
#[derive(Clone, Copy)]
pub struct Crc32 {
state: u32,
dispatch: Crc32DispatchFn,
auto_table: Option<&'static crate::checksum::kernel_table::KernelTable>,
}
pub type Crc32Ieee = Crc32;
impl Crc32 {
const SHIFT8_MATRIX: Gf2Matrix32 = generate_shift8_matrix_32(CRC32_IEEE_POLY);
#[inline]
#[must_use]
pub const fn resume(crc: u32) -> Self {
Self {
state: crc ^ !0,
dispatch: crc32_dispatch,
auto_table: None,
}
}
#[must_use]
pub fn config() -> Crc32Config {
config::get()
}
#[must_use]
pub fn kernel_name_for_len(len: usize) -> &'static str {
crc32_selected_kernel_name(len)
}
}
impl crate::traits::Checksum for Crc32 {
const OUTPUT_SIZE: usize = 4;
type Output = u32;
#[inline]
fn new() -> Self {
let (dispatch, auto_table) = crc32_runtime_paths();
Self {
state: !0,
dispatch,
auto_table,
}
}
#[inline]
fn with_initial(initial: u32) -> Self {
let (dispatch, auto_table) = crc32_runtime_paths();
Self {
state: initial ^ !0,
dispatch,
auto_table,
}
}
#[inline]
fn update(&mut self, data: &[u8]) {
if let Some(table) = self.auto_table {
if data.len() <= 7 {
self.state = portable::crc32_bytewise_ieee(self.state, data);
return;
}
let kernel = table.select_fns(data.len()).crc32_ieee;
self.state = kernel(self.state, data);
} else {
self.state = (self.dispatch)(self.state, data);
}
}
#[inline]
fn update_vectored(&mut self, bufs: &[&[u8]]) {
self.state = crc32_dispatch_vectored(self.state, bufs);
}
#[inline]
fn finalize(&self) -> u32 {
self.state ^ !0
}
#[inline]
fn reset(&mut self) {
self.state = !0;
}
#[inline]
fn checksum(data: &[u8]) -> u32 {
crate::checksum::kernel_table::crc32_ieee(data)
}
}
impl core::fmt::Debug for Crc32 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Crc32").finish_non_exhaustive()
}
}
impl Default for Crc32 {
fn default() -> Self {
<Self as crate::traits::Checksum>::new()
}
}
impl crate::traits::ChecksumCombine for Crc32 {
fn combine(crc_a: u32, crc_b: u32, len_b: usize) -> u32 {
crate::checksum::common::combine::combine_crc32(crc_a, crc_b, len_b, Self::SHIFT8_MATRIX)
}
}
#[cfg(feature = "alloc")]
impl Crc32 {
#[must_use]
pub fn buffered() -> BufferedCrc32 {
BufferedCrc32::new()
}
}
#[derive(Clone, Copy)]
pub struct Crc32C {
state: u32,
dispatch: Crc32DispatchFn,
auto_table: Option<&'static crate::checksum::kernel_table::KernelTable>,
}
pub type Crc32Castagnoli = Crc32C;
impl Crc32C {
const SHIFT8_MATRIX: Gf2Matrix32 = generate_shift8_matrix_32(CRC32C_POLY);
#[inline]
#[must_use]
pub const fn resume(crc: u32) -> Self {
Self {
state: crc ^ !0,
dispatch: crc32c_dispatch,
auto_table: None,
}
}
#[must_use]
pub fn config() -> Crc32Config {
config::get()
}
#[must_use]
pub fn kernel_name_for_len(len: usize) -> &'static str {
crc32c_selected_kernel_name(len)
}
}
impl crate::traits::Checksum for Crc32C {
const OUTPUT_SIZE: usize = 4;
type Output = u32;
#[inline]
fn new() -> Self {
let (dispatch, auto_table) = crc32c_runtime_paths();
Self {
state: !0,
dispatch,
auto_table,
}
}
#[inline]
fn with_initial(initial: u32) -> Self {
let (dispatch, auto_table) = crc32c_runtime_paths();
Self {
state: initial ^ !0,
dispatch,
auto_table,
}
}
#[inline]
fn update(&mut self, data: &[u8]) {
if let Some(table) = self.auto_table {
if data.len() <= 7 {
self.state = portable::crc32c_bytewise(self.state, data);
return;
}
let kernel = table.select_fns(data.len()).crc32c;
self.state = kernel(self.state, data);
} else {
self.state = (self.dispatch)(self.state, data);
}
}
#[inline]
fn update_vectored(&mut self, bufs: &[&[u8]]) {
self.state = crc32c_dispatch_vectored(self.state, bufs);
}
#[inline]
fn finalize(&self) -> u32 {
self.state ^ !0
}
#[inline]
fn reset(&mut self) {
self.state = !0;
}
#[inline]
fn checksum(data: &[u8]) -> u32 {
crate::checksum::kernel_table::crc32c(data)
}
}
impl core::fmt::Debug for Crc32C {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Crc32C").finish_non_exhaustive()
}
}
impl Default for Crc32C {
fn default() -> Self {
<Self as crate::traits::Checksum>::new()
}
}
impl crate::traits::ChecksumCombine for Crc32C {
fn combine(crc_a: u32, crc_b: u32, len_b: usize) -> u32 {
crate::checksum::common::combine::combine_crc32(crc_a, crc_b, len_b, Self::SHIFT8_MATRIX)
}
}
#[cfg(feature = "alloc")]
impl Crc32C {
#[must_use]
pub fn buffered() -> BufferedCrc32C {
BufferedCrc32C::new()
}
}
#[cfg(feature = "alloc")]
const BUFFERED_CRC32_BUFFER_SIZE: usize = 512;
#[cfg(feature = "alloc")]
define_buffered_crc! {
pub struct BufferedCrc32<Crc32> {
buffer_size: BUFFERED_CRC32_BUFFER_SIZE,
threshold_fn: crc32_buffered_threshold,
}
}
#[cfg(feature = "alloc")]
define_buffered_crc! {
pub struct BufferedCrc32C<Crc32C> {
buffer_size: BUFFERED_CRC32_BUFFER_SIZE,
threshold_fn: crc32c_buffered_threshold,
}
}
#[cfg(feature = "diag")]
impl crate::checksum::introspect::KernelIntrospect for Crc32 {
fn kernel_name_for_len(len: usize) -> &'static str {
Self::kernel_name_for_len(len)
}
}
#[cfg(feature = "diag")]
impl crate::checksum::introspect::KernelIntrospect for Crc32C {
fn kernel_name_for_len(len: usize) -> &'static str {
Self::kernel_name_for_len(len)
}
}
#[cfg(test)]
mod tests {
extern crate std;
use alloc::{string::String, vec::Vec};
use super::*;
const TEST_DATA: &[u8] = b"123456789";
#[test]
fn test_crc32_test_vectors() {
assert_eq!(Crc32::checksum(TEST_DATA), 0xCBF4_3926);
assert_eq!(Crc32C::checksum(TEST_DATA), 0xE306_9283);
}
#[test]
fn test_crc32_empty_is_zero() {
assert_eq!(Crc32::checksum(&[]), 0);
assert_eq!(Crc32C::checksum(&[]), 0);
}
#[test]
fn test_streaming_resume_ieee() {
let data = b"The quick brown fox jumps over the lazy dog";
let oneshot = Crc32::checksum(data);
for &split in &[1, data.len() / 4, data.len() / 2, data.len().strict_sub(1)] {
let (a, b) = data.split_at(split);
let crc_a = Crc32::checksum(a);
let mut resumed = Crc32::resume(crc_a);
resumed.update(b);
assert_eq!(resumed.finalize(), oneshot, "Crc32 resume failed at split={split}");
}
}
#[test]
fn test_streaming_resume_castagnoli() {
let data = b"The quick brown fox jumps over the lazy dog";
let oneshot = Crc32C::checksum(data);
for &split in &[1, data.len() / 4, data.len() / 2, data.len().strict_sub(1)] {
let (a, b) = data.split_at(split);
let crc_a = Crc32C::checksum(a);
let mut resumed = Crc32C::resume(crc_a);
resumed.update(b);
assert_eq!(resumed.finalize(), oneshot, "Crc32C resume failed at split={split}");
}
}
#[test]
fn test_kernel_probe_not_empty() {
assert!(!Crc32::kernel_name_for_len(1024).is_empty());
assert!(!Crc32C::kernel_name_for_len(1024).is_empty());
}
#[test]
fn test_kernel_name_for_len_returns_specific_kernel_name() {
let name = Crc32::kernel_name_for_len(0);
assert!(!name.is_empty());
assert!(
name.contains('/') || name.contains("portable") || name.contains("reference"),
"Expected specific kernel name, got: {name}"
);
let name_c = Crc32C::kernel_name_for_len(0);
assert!(!name_c.is_empty());
assert!(
name_c.contains('/') || name_c.contains("portable") || name_c.contains("reference"),
"Expected specific kernel name for CRC32C, got: {name_c}"
);
}
#[test]
fn test_crc32_various_lengths_streaming_matches_oneshot() {
let mut data = [0u8; 512];
for (i, b) in data.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(17).wrapping_add(i as u8);
}
for &len in &[0usize, 1, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128, 255, 256, 400, 512] {
let slice = &data[..len];
let oneshot32 = Crc32::checksum(slice);
let oneshot32c = Crc32C::checksum(slice);
let mut s32 = Crc32::new();
s32.update(slice);
assert_eq!(s32.finalize(), oneshot32, "crc32 len={len}");
let mut s32c = Crc32C::new();
s32c.update(slice);
assert_eq!(s32c.finalize(), oneshot32c, "crc32c len={len}");
let mut c32 = Crc32::new();
for chunk in slice.chunks(37) {
c32.update(chunk);
}
assert_eq!(c32.finalize(), oneshot32, "crc32 chunked len={len}");
let mut c32c = Crc32C::new();
for chunk in slice.chunks(37) {
c32c.update(chunk);
}
assert_eq!(c32c.finalize(), oneshot32c, "crc32c chunked len={len}");
}
}
#[cfg(feature = "alloc")]
#[test]
fn test_buffered_crc32_matches_unbuffered() {
let data: Vec<u8> = (0..2048).map(|i| (i as u8).wrapping_mul(31)).collect();
let expected = Crc32::checksum(&data);
let mut buffered = BufferedCrc32::new();
for chunk in data.chunks(3) {
buffered.update(chunk);
}
assert_eq!(buffered.finalize(), expected);
}
#[cfg(feature = "alloc")]
#[test]
fn test_buffered_crc32c_matches_unbuffered() {
let data: Vec<u8> = (0..2048).map(|i| (i as u8).wrapping_mul(29).wrapping_add(7)).collect();
let expected = Crc32C::checksum(&data);
let mut buffered = BufferedCrc32C::new();
for chunk in data.chunks(3) {
buffered.update(chunk);
}
assert_eq!(buffered.finalize(), expected);
}
#[test]
fn test_crc32_streaming_across_thresholds() {
let table = crate::checksum::kernel_table::active_table();
let mut crc32_thresholds = Vec::with_capacity(table.boundaries.len());
for &boundary in &table.boundaries {
if boundary > 0 && boundary <= (1 << 20) {
crc32_thresholds.push(boundary);
}
}
for &threshold in &crc32_thresholds {
let size = threshold + 256;
let data: Vec<u8> = (0..size).map(|i| (i as u8).wrapping_mul(13)).collect();
let oneshot32 = Crc32::checksum(&data);
let mut h32 = Crc32::new();
h32.update(&data[..16]);
h32.update(&data[16..]);
assert_eq!(h32.finalize(), oneshot32, "crc32 threshold={threshold}");
}
for &threshold in &crc32_thresholds {
let size = threshold + 256;
let data: Vec<u8> = (0..size).map(|i| (i as u8).wrapping_mul(13)).collect();
let oneshot32c = Crc32C::checksum(&data);
let mut h32c = Crc32C::new();
h32c.update(&data[..16]);
h32c.update(&data[16..]);
assert_eq!(h32c.finalize(), oneshot32c, "crc32c threshold={threshold}");
}
}
#[test]
fn test_crc32_forced_kernel_smoke_from_env() {
let force = std::env::var("RSCRYPTO_CRC32_FORCE").unwrap_or_else(|_| String::from("auto"));
if force.trim().is_empty() {
return;
}
let len = 64 * 1024;
let data: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(31).wrapping_add(7)).collect();
let expected = portable::crc32_slice16_ieee(!0, &data) ^ !0;
let got = Crc32::checksum(&data);
assert_eq!(got, expected);
let cfg = Crc32::config();
let kernel = Crc32::kernel_name_for_len(len);
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
let _ = (cfg, kernel);
#[cfg(target_arch = "x86_64")]
{
let caps = crate::platform::caps();
let _ = kernel; if force.eq_ignore_ascii_case("pclmul") || force.eq_ignore_ascii_case("clmul") {
assert_eq!(cfg.requested_force, Crc32Force::Pclmul);
assert!(caps.has(crate::platform::caps::x86::PCLMUL_READY) || cfg.effective_force != Crc32Force::Pclmul);
}
if force.eq_ignore_ascii_case("vpclmul") {
assert_eq!(cfg.requested_force, Crc32Force::Vpclmul);
assert!(caps.has(crate::platform::caps::x86::VPCLMUL_READY) || cfg.effective_force != Crc32Force::Vpclmul);
}
}
#[cfg(target_arch = "aarch64")]
{
let caps = crate::platform::caps();
if force.eq_ignore_ascii_case("hwcrc") || force.eq_ignore_ascii_case("crc") {
assert_eq!(cfg.requested_force, Crc32Force::Hwcrc);
assert!(caps.has(crate::platform::caps::aarch64::CRC_READY) || cfg.effective_force != Crc32Force::Hwcrc);
}
if force.eq_ignore_ascii_case("pmull") {
assert_eq!(cfg.requested_force, Crc32Force::Pmull);
assert!(caps.has(crate::platform::caps::aarch64::PMULL_READY) || cfg.effective_force != Crc32Force::Pmull);
}
if force.eq_ignore_ascii_case("pmull-eor3") || force.eq_ignore_ascii_case("eor3") {
assert_eq!(cfg.requested_force, Crc32Force::PmullEor3);
assert!(
caps.has(crate::platform::caps::aarch64::PMULL_EOR3_READY) || cfg.effective_force != Crc32Force::PmullEor3
);
}
let _ = kernel; }
}
#[test]
fn test_crc32c_forced_kernel_smoke_from_env() {
let force = std::env::var("RSCRYPTO_CRC32_FORCE").unwrap_or_else(|_| String::from("auto"));
if force.trim().is_empty() {
return;
}
let len = 64 * 1024;
let data: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(29).wrapping_add(7)).collect();
let expected = portable::crc32c_slice16(!0, &data) ^ !0;
let got = Crc32C::checksum(&data);
assert_eq!(got, expected);
let cfg = Crc32C::config();
let kernel = Crc32C::kernel_name_for_len(len);
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
let _ = (cfg, kernel);
#[cfg(target_arch = "x86_64")]
{
let caps = crate::platform::caps();
let _ = kernel; if force.eq_ignore_ascii_case("hwcrc") || force.eq_ignore_ascii_case("crc") {
assert_eq!(cfg.requested_force, Crc32Force::Hwcrc);
assert!(caps.has(crate::platform::caps::x86::CRC32C_READY) || cfg.effective_force != Crc32Force::Hwcrc);
}
if force.eq_ignore_ascii_case("pclmul") || force.eq_ignore_ascii_case("clmul") {
assert_eq!(cfg.requested_force, Crc32Force::Pclmul);
assert!(
(caps.has(crate::platform::caps::x86::CRC32C_READY) && caps.has(crate::platform::caps::x86::PCLMUL_READY))
|| cfg.effective_force != Crc32Force::Pclmul
);
}
if force.eq_ignore_ascii_case("vpclmul") {
assert_eq!(cfg.requested_force, Crc32Force::Vpclmul);
assert!(
(caps.has(crate::platform::caps::x86::CRC32C_READY) && caps.has(crate::platform::caps::x86::VPCLMUL_READY))
|| cfg.effective_force != Crc32Force::Vpclmul
);
}
}
#[cfg(target_arch = "aarch64")]
{
let caps = crate::platform::caps();
if force.eq_ignore_ascii_case("hwcrc") || force.eq_ignore_ascii_case("crc") {
assert_eq!(cfg.requested_force, Crc32Force::Hwcrc);
assert!(caps.has(crate::platform::caps::aarch64::CRC_READY) || cfg.effective_force != Crc32Force::Hwcrc);
}
if force.eq_ignore_ascii_case("pmull") {
assert_eq!(cfg.requested_force, Crc32Force::Pmull);
assert!(caps.has(crate::platform::caps::aarch64::PMULL_READY) || cfg.effective_force != Crc32Force::Pmull);
}
if force.eq_ignore_ascii_case("pmull-eor3") || force.eq_ignore_ascii_case("eor3") {
assert_eq!(cfg.requested_force, Crc32Force::PmullEor3);
assert!(
caps.has(crate::platform::caps::aarch64::PMULL_EOR3_READY) || cfg.effective_force != Crc32Force::PmullEor3
);
}
let _ = kernel; }
}
mod cross_check {
use alloc::{vec, vec::Vec};
use super::*;
use crate::checksum::common::{
reference::crc32_bitwise,
tables::{CRC32_IEEE_POLY, CRC32C_POLY},
tests::{STREAMING_CHUNK_SIZES, TEST_LENGTHS},
};
fn generate_test_data(len: usize) -> Vec<u8> {
(0..len)
.map(|i| {
let i = i as u64;
((i.wrapping_mul(2654435761) ^ i.wrapping_mul(0x9E3779B97F4A7C15)) & 0xFF) as u8
})
.collect()
}
fn reference_ieee(data: &[u8]) -> u32 {
crc32_bitwise(CRC32_IEEE_POLY, !0u32, data) ^ !0u32
}
fn reference_castagnoli(data: &[u8]) -> u32 {
crc32_bitwise(CRC32C_POLY, !0u32, data) ^ !0u32
}
#[test]
fn cross_check_ieee_all_lengths() {
for &len in TEST_LENGTHS {
let data = generate_test_data(len);
let reference = reference_ieee(&data);
let actual = Crc32::checksum(&data);
assert_eq!(
actual, reference,
"CRC32-IEEE mismatch at len={len}: actual={actual:#010X}, reference={reference:#010X}"
);
}
}
#[test]
fn cross_check_castagnoli_all_lengths() {
for &len in TEST_LENGTHS {
let data = generate_test_data(len);
let reference = reference_castagnoli(&data);
let actual = Crc32C::checksum(&data);
assert_eq!(
actual, reference,
"CRC32C mismatch at len={len}: actual={actual:#010X}, reference={reference:#010X}"
);
}
}
#[test]
fn cross_check_ieee_all_single_bytes() {
for byte in 0u8..=255 {
let data = [byte];
let reference = reference_ieee(&data);
let actual = Crc32::checksum(&data);
assert_eq!(
actual, reference,
"CRC32-IEEE single-byte mismatch for byte={byte:#04X}"
);
}
}
#[test]
fn cross_check_castagnoli_all_single_bytes() {
for byte in 0u8..=255 {
let data = [byte];
let reference = reference_castagnoli(&data);
let actual = Crc32C::checksum(&data);
assert_eq!(actual, reference, "CRC32C single-byte mismatch for byte={byte:#04X}");
}
}
#[test]
fn cross_check_ieee_streaming_all_chunk_sizes() {
let data = generate_test_data(4096);
let reference = reference_ieee(&data);
for &chunk_size in STREAMING_CHUNK_SIZES {
let mut hasher = Crc32::new();
for chunk in data.chunks(chunk_size) {
hasher.update(chunk);
}
let actual = hasher.finalize();
assert_eq!(
actual, reference,
"CRC32-IEEE streaming mismatch with chunk_size={chunk_size}"
);
}
}
#[test]
fn cross_check_castagnoli_streaming_all_chunk_sizes() {
let data = generate_test_data(4096);
let reference = reference_castagnoli(&data);
for &chunk_size in STREAMING_CHUNK_SIZES {
let mut hasher = Crc32C::new();
for chunk in data.chunks(chunk_size) {
hasher.update(chunk);
}
let actual = hasher.finalize();
assert_eq!(
actual, reference,
"CRC32C streaming mismatch with chunk_size={chunk_size}"
);
}
}
#[test]
fn cross_check_ieee_combine_all_splits() {
let data = generate_test_data(1024);
let reference = reference_ieee(&data);
let small_data = &data[..64];
let small_ref = reference_ieee(small_data);
for split in 0..=small_data.len() {
let (a, b) = small_data.split_at(split);
let crc_a = Crc32::checksum(a);
let crc_b = Crc32::checksum(b);
let combined = Crc32::combine(crc_a, crc_b, b.len());
assert_eq!(combined, small_ref, "CRC32-IEEE combine mismatch at split={split}");
}
let strategic_splits = [0, 1, 15, 16, 17, 63, 64, 65, 127, 128, 129, 255, 256, 512, 1024];
for &split in &strategic_splits {
if split > data.len() {
continue;
}
let (a, b) = data.split_at(split);
let combined = Crc32::combine(Crc32::checksum(a), Crc32::checksum(b), b.len());
assert_eq!(
combined, reference,
"CRC32-IEEE combine mismatch at strategic split={split}"
);
}
}
#[test]
fn cross_check_castagnoli_combine_all_splits() {
let data = generate_test_data(1024);
let reference = reference_castagnoli(&data);
let small_data = &data[..64];
let small_ref = reference_castagnoli(small_data);
for split in 0..=small_data.len() {
let (a, b) = small_data.split_at(split);
let crc_a = Crc32C::checksum(a);
let crc_b = Crc32C::checksum(b);
let combined = Crc32C::combine(crc_a, crc_b, b.len());
assert_eq!(combined, small_ref, "CRC32C combine mismatch at split={split}");
}
let strategic_splits = [0, 1, 15, 16, 17, 63, 64, 65, 127, 128, 129, 255, 256, 512, 1024];
for &split in &strategic_splits {
if split > data.len() {
continue;
}
let (a, b) = data.split_at(split);
let combined = Crc32C::combine(Crc32C::checksum(a), Crc32C::checksum(b), b.len());
assert_eq!(
combined, reference,
"CRC32C combine mismatch at strategic split={split}"
);
}
}
#[test]
fn cross_check_ieee_unaligned_offsets() {
let mut buffer = vec![0u8; 4096 + 64];
for (i, byte) in buffer.iter_mut().enumerate() {
*byte = (((i as u64).wrapping_mul(17)) & 0xFF) as u8;
}
for offset in 0..16 {
let data = &buffer[offset..offset + 1024];
let reference = reference_ieee(data);
let actual = Crc32::checksum(data);
assert_eq!(actual, reference, "CRC32-IEEE unaligned mismatch at offset={offset}");
}
}
#[test]
fn cross_check_castagnoli_unaligned_offsets() {
let mut buffer = vec![0u8; 4096 + 64];
for (i, byte) in buffer.iter_mut().enumerate() {
*byte = (((i as u64).wrapping_mul(17)) & 0xFF) as u8;
}
for offset in 0..16 {
let data = &buffer[offset..offset + 1024];
let reference = reference_castagnoli(data);
let actual = Crc32C::checksum(data);
assert_eq!(actual, reference, "CRC32C unaligned mismatch at offset={offset}");
}
}
#[test]
fn cross_check_ieee_byte_at_a_time_streaming() {
let data = generate_test_data(256);
let reference = reference_ieee(&data);
let mut hasher = Crc32::new();
for &byte in &data {
hasher.update(&[byte]);
}
let actual = hasher.finalize();
assert_eq!(actual, reference, "CRC32-IEEE byte-at-a-time streaming mismatch");
}
#[test]
fn cross_check_castagnoli_byte_at_a_time_streaming() {
let data = generate_test_data(256);
let reference = reference_castagnoli(&data);
let mut hasher = Crc32C::new();
for &byte in &data {
hasher.update(&[byte]);
}
let actual = hasher.finalize();
assert_eq!(actual, reference, "CRC32C byte-at-a-time streaming mismatch");
}
#[test]
fn cross_check_reference_kernel_accessible() {
let data = generate_test_data(1024);
let ieee_ref = crc32_reference(!0u32, &data) ^ !0u32;
let ieee_direct = reference_ieee(&data);
assert_eq!(ieee_ref, ieee_direct, "IEEE reference kernel mismatch");
let c_ref = crc32c_reference(!0u32, &data) ^ !0u32;
let c_direct = reference_castagnoli(&data);
assert_eq!(c_ref, c_direct, "Castagnoli reference kernel mismatch");
}
#[test]
fn cross_check_portable_matches_reference() {
for &len in TEST_LENGTHS {
let data = generate_test_data(len);
let portable_ieee = portable::crc32_slice16_ieee(!0u32, &data) ^ !0u32;
let reference_ieee_val = reference_ieee(&data);
assert_eq!(portable_ieee, reference_ieee_val, "IEEE portable mismatch at len={len}");
let portable_c = portable::crc32c_slice16(!0u32, &data) ^ !0u32;
let reference_c_val = reference_castagnoli(&data);
assert_eq!(portable_c, reference_c_val, "Castagnoli portable mismatch at len={len}");
}
}
}
}
#[cfg(test)]
crate::define_crc_property_tests!(crc32_ieee_props, Crc32);
#[cfg(test)]
crate::define_crc_property_tests!(crc32c_props, Crc32C);