#![allow(clippy::indexing_slicing)]
#![cfg_attr(all(target_arch = "wasm32", target_feature = "simd128"), allow(unsafe_code))]
#[cfg(any(feature = "crc16", feature = "crc32", feature = "crc64"))]
macro_rules! tail_step {
($crc:ident, $table:ident, $byte:expr, $crc_ty:ty) => {
$crc = $table[((($crc ^ ($byte as $crc_ty)) & 0xFF) as usize)] ^ ($crc >> 8);
};
}
#[cfg(any(feature = "crc16", feature = "crc64", all(test, feature = "crc32")))]
macro_rules! tail8_body {
($crc:ident, $data:ident, $table:ident, $crc_ty:ty) => {{
match $data.len() {
0 => {}
1 => {
tail_step!($crc, $table, $data[0], $crc_ty);
}
2 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
}
3 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
tail_step!($crc, $table, $data[2], $crc_ty);
}
4 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
tail_step!($crc, $table, $data[2], $crc_ty);
tail_step!($crc, $table, $data[3], $crc_ty);
}
5 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
tail_step!($crc, $table, $data[2], $crc_ty);
tail_step!($crc, $table, $data[3], $crc_ty);
tail_step!($crc, $table, $data[4], $crc_ty);
}
6 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
tail_step!($crc, $table, $data[2], $crc_ty);
tail_step!($crc, $table, $data[3], $crc_ty);
tail_step!($crc, $table, $data[4], $crc_ty);
tail_step!($crc, $table, $data[5], $crc_ty);
}
7 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
tail_step!($crc, $table, $data[2], $crc_ty);
tail_step!($crc, $table, $data[3], $crc_ty);
tail_step!($crc, $table, $data[4], $crc_ty);
tail_step!($crc, $table, $data[5], $crc_ty);
tail_step!($crc, $table, $data[6], $crc_ty);
}
_ => {
for &byte in $data {
tail_step!($crc, $table, byte, $crc_ty);
}
}
}
$crc
}};
}
#[cfg(feature = "crc32")]
macro_rules! tail4_body {
($crc:ident, $data:ident, $table:ident, $crc_ty:ty) => {{
match $data.len() {
0 => {}
1 => {
tail_step!($crc, $table, $data[0], $crc_ty);
}
2 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
}
3 => {
tail_step!($crc, $table, $data[0], $crc_ty);
tail_step!($crc, $table, $data[1], $crc_ty);
tail_step!($crc, $table, $data[2], $crc_ty);
}
_ => {
for &byte in $data {
tail_step!($crc, $table, byte, $crc_ty);
}
}
}
$crc
}};
}
#[cfg(feature = "crc64")]
#[inline(always)]
fn tail8_64(mut crc: u64, data: &[u8], table: &[u64; 256]) -> u64 {
tail8_body!(crc, data, table, u64)
}
#[cfg(all(test, feature = "crc32"))]
#[inline(always)]
fn tail8_32(mut crc: u32, data: &[u8], table: &[u32; 256]) -> u32 {
tail8_body!(crc, data, table, u32)
}
#[cfg(feature = "crc32")]
#[inline(always)]
fn tail4_32(mut crc: u32, data: &[u8], table: &[u32; 256]) -> u32 {
tail4_body!(crc, data, table, u32)
}
#[cfg(feature = "crc16")]
#[inline(always)]
fn tail8_16(mut crc: u16, data: &[u8], table: &[u16; 256]) -> u16 {
tail8_body!(crc, data, table, u16)
}
#[cfg(feature = "crc16")]
#[inline]
pub(crate) fn slice8_16(mut crc: u16, data: &[u8], tables: &[[u16; 256]; 8]) -> u16 {
let (chunks, remainder) = data.as_chunks::<8>();
for chunk in chunks {
let a = u16::from_le_bytes([chunk[0], chunk[1]]) ^ crc;
let b = u16::from_le_bytes([chunk[2], chunk[3]]);
let c = u16::from_le_bytes([chunk[4], chunk[5]]);
let d = u16::from_le_bytes([chunk[6], chunk[7]]);
crc = tables[7][(a & 0xFF) as usize]
^ tables[6][((a >> 8) & 0xFF) as usize]
^ tables[5][(b & 0xFF) as usize]
^ tables[4][((b >> 8) & 0xFF) as usize]
^ tables[3][(c & 0xFF) as usize]
^ tables[2][((c >> 8) & 0xFF) as usize]
^ tables[1][(d & 0xFF) as usize]
^ tables[0][((d >> 8) & 0xFF) as usize];
}
tail8_16(crc, remainder, &tables[0])
}
#[cfg(feature = "crc24")]
#[inline]
pub(crate) fn slice8_24(crc: u32, data: &[u8], tables: &[[u32; 256]; 8]) -> u32 {
const MASK24: u32 = 0x00FF_FFFF;
let mut state = (crc & MASK24) << 8;
let (chunks, remainder) = data.as_chunks::<8>();
for chunk in chunks {
let a = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) ^ state;
let b = u32::from_be_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
state = tables[7][(a >> 24) as usize]
^ tables[6][((a >> 16) & 0xFF) as usize]
^ tables[5][((a >> 8) & 0xFF) as usize]
^ tables[4][(a & 0xFF) as usize]
^ tables[3][(b >> 24) as usize]
^ tables[2][((b >> 16) & 0xFF) as usize]
^ tables[1][((b >> 8) & 0xFF) as usize]
^ tables[0][(b & 0xFF) as usize];
}
for &byte in remainder {
let index = (((state >> 24) as u8) ^ byte) as usize;
state = tables[0][index] ^ (state << 8);
}
(state >> 8) & MASK24
}
#[cfg(all(test, feature = "crc32"))]
#[inline]
pub fn slice8_32(mut crc: u32, data: &[u8], tables: &[[u32; 256]; 8]) -> u32 {
let (chunks, remainder) = data.as_chunks::<8>();
for chunk in chunks {
let a = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) ^ crc;
let b = u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
crc = tables[7][(a & 0xFF) as usize]
^ tables[6][((a >> 8) & 0xFF) as usize]
^ tables[5][((a >> 16) & 0xFF) as usize]
^ tables[4][(a >> 24) as usize]
^ tables[3][(b & 0xFF) as usize]
^ tables[2][((b >> 8) & 0xFF) as usize]
^ tables[1][((b >> 16) & 0xFF) as usize]
^ tables[0][(b >> 24) as usize];
}
tail8_32(crc, remainder, &tables[0])
}
#[cfg(feature = "crc32")]
#[inline]
fn slice16_32_scalar(mut crc: u32, data: &[u8], tables: &[[u32; 256]; 16]) -> u32 {
let (chunks4, remainder) = data.as_chunks::<4>();
let mut quads = chunks4.chunks_exact(4);
for quad in quads.by_ref() {
let a = u32::from_le_bytes(quad[0]) ^ crc;
let b = u32::from_le_bytes(quad[1]);
let c = u32::from_le_bytes(quad[2]);
let d = u32::from_le_bytes(quad[3]);
crc = tables[15][(a & 0xFF) as usize]
^ tables[14][((a >> 8) & 0xFF) as usize]
^ tables[13][((a >> 16) & 0xFF) as usize]
^ tables[12][(a >> 24) as usize]
^ tables[11][(b & 0xFF) as usize]
^ tables[10][((b >> 8) & 0xFF) as usize]
^ tables[9][((b >> 16) & 0xFF) as usize]
^ tables[8][(b >> 24) as usize]
^ tables[7][(c & 0xFF) as usize]
^ tables[6][((c >> 8) & 0xFF) as usize]
^ tables[5][((c >> 16) & 0xFF) as usize]
^ tables[4][(c >> 24) as usize]
^ tables[3][(d & 0xFF) as usize]
^ tables[2][((d >> 8) & 0xFF) as usize]
^ tables[1][((d >> 16) & 0xFF) as usize]
^ tables[0][(d >> 24) as usize];
}
for chunk in quads.remainder() {
let val = u32::from_le_bytes(*chunk) ^ crc;
crc = tables[3][(val & 0xFF) as usize]
^ tables[2][((val >> 8) & 0xFF) as usize]
^ tables[1][((val >> 16) & 0xFF) as usize]
^ tables[0][(val >> 24) as usize];
}
tail4_32(crc, remainder, &tables[0])
}
#[cfg(all(feature = "crc32", not(all(target_arch = "wasm32", target_feature = "simd128"))))]
#[inline]
pub fn slice16_32(crc: u32, data: &[u8], tables: &[[u32; 256]; 16]) -> u32 {
slice16_32_scalar(crc, data, tables)
}
#[cfg(all(feature = "crc32", target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
pub fn slice16_32(mut crc: u32, data: &[u8], tables: &[[u32; 256]; 16]) -> u32 {
use core::arch::wasm32::v128;
let mut ptr = data.as_ptr();
let mut len = data.len();
while len >= 16 {
let v = unsafe { core::ptr::read_unaligned(ptr as *const v128) };
let a = core::arch::wasm32::u32x4_extract_lane::<0>(v) ^ crc;
let b = core::arch::wasm32::u32x4_extract_lane::<1>(v);
let c = core::arch::wasm32::u32x4_extract_lane::<2>(v);
let d = core::arch::wasm32::u32x4_extract_lane::<3>(v);
crc = tables[15][(a & 0xFF) as usize]
^ tables[14][((a >> 8) & 0xFF) as usize]
^ tables[13][((a >> 16) & 0xFF) as usize]
^ tables[12][(a >> 24) as usize]
^ tables[11][(b & 0xFF) as usize]
^ tables[10][((b >> 8) & 0xFF) as usize]
^ tables[9][((b >> 16) & 0xFF) as usize]
^ tables[8][(b >> 24) as usize]
^ tables[7][(c & 0xFF) as usize]
^ tables[6][((c >> 8) & 0xFF) as usize]
^ tables[5][((c >> 16) & 0xFF) as usize]
^ tables[4][(c >> 24) as usize]
^ tables[3][(d & 0xFF) as usize]
^ tables[2][((d >> 8) & 0xFF) as usize]
^ tables[1][((d >> 16) & 0xFF) as usize]
^ tables[0][(d >> 24) as usize];
ptr = unsafe { ptr.add(16) };
len = len.strict_sub(16);
}
let tail = unsafe { core::slice::from_raw_parts(ptr, len) };
slice16_32_scalar(crc, tail, tables)
}
#[cfg(all(feature = "crc64", any(target_arch = "x86_64", target_arch = "aarch64", test)))]
#[inline]
pub fn slice8_64(mut crc: u64, data: &[u8], tables: &[[u64; 256]; 8]) -> u64 {
let (chunks, remainder) = data.as_chunks::<8>();
for chunk in chunks {
let val = u64::from_le_bytes(*chunk) ^ crc;
crc = tables[7][(val & 0xFF) as usize]
^ tables[6][((val >> 8) & 0xFF) as usize]
^ tables[5][((val >> 16) & 0xFF) as usize]
^ tables[4][((val >> 24) & 0xFF) as usize]
^ tables[3][((val >> 32) & 0xFF) as usize]
^ tables[2][((val >> 40) & 0xFF) as usize]
^ tables[1][((val >> 48) & 0xFF) as usize]
^ tables[0][(val >> 56) as usize];
}
tail8_64(crc, remainder, &tables[0])
}
#[cfg(feature = "crc64")]
#[inline]
fn slice16_64_scalar(mut crc: u64, data: &[u8], tables: &[[u64; 256]; 16]) -> u64 {
let (chunks8, remainder) = data.as_chunks::<8>();
let mut pairs = chunks8.chunks_exact(2);
for pair in pairs.by_ref() {
let a = u64::from_le_bytes(pair[0]) ^ crc;
let b = u64::from_le_bytes(pair[1]);
crc = tables[15][(a & 0xFF) as usize]
^ tables[14][((a >> 8) & 0xFF) as usize]
^ tables[13][((a >> 16) & 0xFF) as usize]
^ tables[12][((a >> 24) & 0xFF) as usize]
^ tables[11][((a >> 32) & 0xFF) as usize]
^ tables[10][((a >> 40) & 0xFF) as usize]
^ tables[9][((a >> 48) & 0xFF) as usize]
^ tables[8][(a >> 56) as usize]
^ tables[7][(b & 0xFF) as usize]
^ tables[6][((b >> 8) & 0xFF) as usize]
^ tables[5][((b >> 16) & 0xFF) as usize]
^ tables[4][((b >> 24) & 0xFF) as usize]
^ tables[3][((b >> 32) & 0xFF) as usize]
^ tables[2][((b >> 40) & 0xFF) as usize]
^ tables[1][((b >> 48) & 0xFF) as usize]
^ tables[0][(b >> 56) as usize];
}
if let [chunk] = pairs.remainder() {
let val = u64::from_le_bytes(*chunk) ^ crc;
crc = tables[7][(val & 0xFF) as usize]
^ tables[6][((val >> 8) & 0xFF) as usize]
^ tables[5][((val >> 16) & 0xFF) as usize]
^ tables[4][((val >> 24) & 0xFF) as usize]
^ tables[3][((val >> 32) & 0xFF) as usize]
^ tables[2][((val >> 40) & 0xFF) as usize]
^ tables[1][((val >> 48) & 0xFF) as usize]
^ tables[0][(val >> 56) as usize];
}
tail8_64(crc, remainder, &tables[0])
}
#[cfg(all(feature = "crc64", not(all(target_arch = "wasm32", target_feature = "simd128"))))]
#[inline]
pub fn slice16_64(crc: u64, data: &[u8], tables: &[[u64; 256]; 16]) -> u64 {
slice16_64_scalar(crc, data, tables)
}
#[cfg(all(feature = "crc64", target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
pub fn slice16_64(mut crc: u64, data: &[u8], tables: &[[u64; 256]; 16]) -> u64 {
use core::arch::wasm32::v128;
let mut ptr = data.as_ptr();
let mut len = data.len();
while len >= 16 {
let v = unsafe { core::ptr::read_unaligned(ptr as *const v128) };
let a = core::arch::wasm32::u64x2_extract_lane::<0>(v) ^ crc;
let b = core::arch::wasm32::u64x2_extract_lane::<1>(v);
crc = tables[15][(a & 0xFF) as usize]
^ tables[14][((a >> 8) & 0xFF) as usize]
^ tables[13][((a >> 16) & 0xFF) as usize]
^ tables[12][((a >> 24) & 0xFF) as usize]
^ tables[11][((a >> 32) & 0xFF) as usize]
^ tables[10][((a >> 40) & 0xFF) as usize]
^ tables[9][((a >> 48) & 0xFF) as usize]
^ tables[8][(a >> 56) as usize]
^ tables[7][(b & 0xFF) as usize]
^ tables[6][((b >> 8) & 0xFF) as usize]
^ tables[5][((b >> 16) & 0xFF) as usize]
^ tables[4][((b >> 24) & 0xFF) as usize]
^ tables[3][((b >> 32) & 0xFF) as usize]
^ tables[2][((b >> 40) & 0xFF) as usize]
^ tables[1][((b >> 48) & 0xFF) as usize]
^ tables[0][(b >> 56) as usize];
ptr = unsafe { ptr.add(16) };
len = len.strict_sub(16);
}
let tail = unsafe { core::slice::from_raw_parts(ptr, len) };
slice16_64_scalar(crc, tail, tables)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "crc16")]
const fn crc16_table_entry(poly: u16, index: u8) -> u16 {
let mut crc = index as u16;
let mut i: u32 = 0;
while i < 8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
i = i.strict_add(1);
}
crc
}
#[cfg(feature = "crc16")]
const fn generate_crc16_tables_8(poly: u16) -> [[u16; 256]; 8] {
let mut tables = [[0u16; 256]; 8];
let mut i = 0u16;
while i < 256 {
tables[0][i as usize] = crc16_table_entry(poly, i as u8);
i = i.strict_add(1);
}
let mut k = 1usize;
while k < 8 {
i = 0;
while i < 256 {
let prev = tables[k - 1][i as usize];
tables[k][i as usize] = tables[0][(prev & 0xFF) as usize] ^ (prev >> 8);
i = i.strict_add(1);
}
k = k.strict_add(1);
}
tables
}
#[test]
#[cfg(feature = "crc32")]
fn test_slice8_32_empty() {
let tables =
crate::checksum::common::tables::generate_crc32_tables_8(crate::checksum::common::tables::CRC32_IEEE_POLY);
let crc = slice8_32(!0, &[], &tables);
assert_eq!(crc, !0);
}
#[test]
#[cfg(feature = "crc32")]
fn test_slice16_32_empty() {
let tables =
crate::checksum::common::tables::generate_crc32_tables_16(crate::checksum::common::tables::CRC32_IEEE_POLY);
let crc = slice16_32(!0, &[], &tables);
assert_eq!(crc, !0);
}
#[test]
#[cfg(feature = "crc32")]
fn test_slice16_32_matches_slice8_32() {
let poly = crate::checksum::common::tables::CRC32_IEEE_POLY;
let tables8 = crate::checksum::common::tables::generate_crc32_tables_8(poly);
let tables16 = crate::checksum::common::tables::generate_crc32_tables_16(poly);
let data = b"The quick brown fox jumps over the lazy dog";
assert_eq!(slice8_32(!0, data, &tables8), slice16_32(!0, data, &tables16));
}
#[cfg(feature = "crc16")]
const CRC16_CCITT_POLY: u16 = 0x8408;
#[test]
#[cfg(feature = "crc16")]
fn test_slice8_16_empty() {
let tables = generate_crc16_tables_8(CRC16_CCITT_POLY);
assert_eq!(slice8_16(!0, &[], &tables), !0);
}
#[cfg(feature = "crc24")]
const CRC24_OPENPGP_INIT: u32 = 0x00B7_04CE;
#[test]
#[cfg(feature = "crc24")]
fn test_slice8_24_empty() {
let tables =
crate::checksum::common::tables::generate_crc24_tables_8(crate::checksum::common::tables::CRC24_OPENPGP_POLY);
assert_eq!(slice8_24(CRC24_OPENPGP_INIT, &[], &tables), CRC24_OPENPGP_INIT);
}
#[test]
#[cfg(feature = "crc24")]
fn test_slice8_24_test_vector_openpgp() {
let tables =
crate::checksum::common::tables::generate_crc24_tables_8(crate::checksum::common::tables::CRC24_OPENPGP_POLY);
let out = slice8_24(CRC24_OPENPGP_INIT, b"123456789", &tables);
assert_eq!(out, 0x0021_CF02);
}
#[cfg(feature = "crc64")]
const fn crc64_table_entry(poly: u64, index: u8) -> u64 {
let mut crc = index as u64;
let mut i: u32 = 0;
while i < 8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
i = i.strict_add(1);
}
crc
}
#[cfg(feature = "crc64")]
const fn generate_crc64_tables_8(poly: u64) -> [[u64; 256]; 8] {
let mut tables = [[0u64; 256]; 8];
let mut i = 0u16;
while i < 256 {
tables[0][i as usize] = crc64_table_entry(poly, i as u8);
i = i.strict_add(1);
}
let mut k = 1usize;
while k < 8 {
i = 0;
while i < 256 {
let prev = tables[k - 1][i as usize];
tables[k][i as usize] = tables[0][(prev & 0xFF) as usize] ^ (prev >> 8);
i = i.strict_add(1);
}
k = k.strict_add(1);
}
tables
}
#[cfg(feature = "crc64")]
const fn generate_crc64_tables_16(poly: u64) -> [[u64; 256]; 16] {
let mut tables = [[0u64; 256]; 16];
let mut i = 0u16;
while i < 256 {
tables[0][i as usize] = crc64_table_entry(poly, i as u8);
i = i.strict_add(1);
}
let mut k = 1usize;
while k < 16 {
i = 0;
while i < 256 {
let prev = tables[k - 1][i as usize];
tables[k][i as usize] = tables[0][(prev & 0xFF) as usize] ^ (prev >> 8);
i = i.strict_add(1);
}
k = k.strict_add(1);
}
tables
}
#[cfg(feature = "crc64")]
const CRC64_XZ_POLY: u64 = 0xC96C_5795_D787_0F42;
#[test]
#[cfg(feature = "crc64")]
fn test_slice8_64_empty() {
let tables = generate_crc64_tables_8(CRC64_XZ_POLY);
assert_eq!(slice8_64(!0, &[], &tables), !0);
}
#[test]
#[cfg(feature = "crc64")]
fn test_slice16_64_empty() {
let tables = generate_crc64_tables_16(CRC64_XZ_POLY);
assert_eq!(slice16_64(!0, &[], &tables), !0);
}
#[test]
#[cfg(feature = "crc64")]
fn test_slice8_64_matches_slice16_64() {
let tables8 = generate_crc64_tables_8(CRC64_XZ_POLY);
let tables16 = generate_crc64_tables_16(CRC64_XZ_POLY);
let data = b"The quick brown fox jumps over the lazy dog";
let a = slice8_64(!0, data, &tables8);
let b = slice16_64(!0, data, &tables16);
assert_eq!(a, b);
}
#[test]
#[cfg(feature = "crc64")]
fn test_slice16_64_incremental() {
let tables = generate_crc64_tables_16(CRC64_XZ_POLY);
let data = b"hello world, this is a longer test string";
let full = slice16_64(!0, data, &tables);
for split in [1, 7, 8, 9, 15, 16, 17, 20] {
if split < data.len() {
let crc1 = slice16_64(!0, &data[..split], &tables);
let crc2 = slice16_64(crc1, &data[split..], &tables);
assert_eq!(crc2, full, "Incremental failed at split {split}");
}
}
}
#[test]
#[cfg(feature = "crc64")]
fn test_crc64_xz_test_vector() {
let tables = generate_crc64_tables_16(CRC64_XZ_POLY);
let crc = slice16_64(!0, b"123456789", &tables) ^ !0;
assert_eq!(crc, 0x995D_C9BB_DF19_39FA);
}
}