use crate::Hasher;
use bytes::{Buf, BufMut};
use commonware_codec::{Error as CodecError, FixedSize, Read, ReadExt, Write};
use commonware_math::algebra::Random;
use commonware_utils::{hex, Array, Span};
use core::{
fmt::{Debug, Display},
ops::Deref,
};
use rand_core::CryptoRngCore;
const SIZE: usize = 4;
const ALGORITHM: crc_fast::CrcAlgorithm = crc_fast::CrcAlgorithm::Crc32Iscsi;
#[derive(Debug)]
pub struct Crc32 {
inner: crc_fast::Digest,
}
impl Default for Crc32 {
fn default() -> Self {
Self {
inner: crc_fast::Digest::new(ALGORITHM),
}
}
}
impl Clone for Crc32 {
fn clone(&self) -> Self {
Self::default()
}
}
impl Crc32 {
#[inline]
pub fn checksum(data: &[u8]) -> u32 {
crc_fast::checksum(ALGORITHM, data) as u32
}
}
impl Hasher for Crc32 {
type Digest = Digest;
fn update(&mut self, message: &[u8]) -> &mut Self {
self.inner.update(message);
self
}
fn finalize(&mut self) -> Self::Digest {
Self::Digest::from(self.inner.finalize_reset() as u32)
}
fn reset(&mut self) -> &mut Self {
self.inner = crc_fast::Digest::new(ALGORITHM);
self
}
}
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Digest(pub [u8; SIZE]);
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Digest {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let len = u.int_in_range(0..=256)?;
let data = u.bytes(len)?;
Ok(Crc32::hash(data))
}
}
impl Digest {
#[inline]
pub const fn as_u32(&self) -> u32 {
u32::from_be_bytes(self.0)
}
}
impl Write for Digest {
fn write(&self, buf: &mut impl BufMut) {
self.0.write(buf);
}
}
impl Read for Digest {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let array = <[u8; SIZE]>::read(buf)?;
Ok(Self(array))
}
}
impl FixedSize for Digest {
const SIZE: usize = SIZE;
}
impl Span for Digest {}
impl Array for Digest {}
impl From<[u8; SIZE]> for Digest {
fn from(value: [u8; SIZE]) -> Self {
Self(value)
}
}
impl From<u32> for Digest {
fn from(value: u32) -> Self {
Self(value.to_be_bytes())
}
}
impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Deref for Digest {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl Debug for Digest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", hex(&self.0))
}
}
impl Display for Digest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", hex(&self.0))
}
}
impl crate::Digest for Digest {
const EMPTY: Self = Self([0u8; SIZE]);
}
impl Random for Digest {
fn random(mut rng: impl CryptoRngCore) -> Self {
let mut array = [0u8; SIZE];
rng.fill_bytes(&mut array);
Self(array)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Hasher;
use commonware_codec::{DecodeExt, Encode};
use crc::{Crc, CRC_32_ISCSI};
const CRC32C_REF: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
fn verify(data: &[u8], expected: u32) {
assert_eq!(CRC32C_REF.checksum(data), expected);
assert_eq!(Crc32::checksum(data), expected);
}
fn sequential_data(len: usize) -> Vec<u8> {
(0..len).map(|i| (i & 0xFF) as u8).collect()
}
#[test]
fn rfc3720_test_vectors() {
verify(&[0x00; 32], 0x8A9136AA);
verify(&[0xFF; 32], 0x62A8AB43);
let ascending: Vec<u8> = (0x00..0x20).collect();
verify(&ascending, 0x46DD794E);
let descending: Vec<u8> = (0x00..0x20).rev().collect();
verify(&descending, 0x113FDB5C);
let iscsi_read_pdu: [u8; 48] = [
0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
verify(&iscsi_read_pdu, 0xD9963A56);
}
#[test]
fn external_test_vectors() {
verify(b"", 0x00000000);
verify(b"123456789", 0xE3069283);
verify(b"23456789", 0xBFE92A83);
verify(b"The quick brown fox jumps over the lazy dog", 0x22620404);
let sequential_240: Vec<u8> = (0x01..=0xF0).collect();
verify(&sequential_240, 0x24C5D375);
}
#[test]
fn simd_boundaries() {
const BOUNDARY_SIZES: &[usize] = &[
0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 4095, 4096, 4097, ];
const EXPECTED: &[(usize, u32)] = &[
(0, 0x00000000),
(1, 0x527D5351),
(2, 0x030AF4D1),
(3, 0x92FD4BFA),
(4, 0xD9331AA3),
(7, 0xA359ED4C),
(8, 0x8A2CBC3B),
(9, 0x7144C5A8),
(15, 0x68EF03F6),
(16, 0xD9C908EB),
(17, 0x38435E17),
(31, 0xE95CABCB),
(32, 0x46DD794E), (33, 0x9F85A26D),
(63, 0x7A873004),
(64, 0xFB6D36EB),
(65, 0x694420FA),
(127, 0x6C31BD0C),
(128, 0x30D9C515),
(129, 0xF514629F),
(255, 0x8953C482),
(256, 0x9C44184B),
(257, 0x8A13A1CE),
(511, 0x35348950),
(512, 0xAE10EE5A),
(513, 0x6814B154),
(1023, 0x0C8F24D0),
(1024, 0x2CDF6E8F),
(1025, 0x8EB48B63),
(4095, 0xBCB5BD82),
(4096, 0x9C71FE32),
(4097, 0x83391BE9),
];
assert_eq!(
BOUNDARY_SIZES,
EXPECTED.iter().map(|(size, _)| *size).collect::<Vec<_>>()
);
for &(size, expected) in EXPECTED {
let data = sequential_data(size);
verify(&data, expected);
}
}
#[test]
fn chunk_size_independence() {
let data = sequential_data(1024);
let expected = CRC32C_REF.checksum(&data);
for chunk_size in 1..=64 {
let mut hasher = Crc32::new();
for chunk in data.chunks(chunk_size) {
hasher.update(chunk);
}
assert_eq!(hasher.finalize().as_u32(), expected);
}
}
#[test]
fn alignment_independence() {
let base_data: Vec<u8> = (0..256).map(|i| i as u8).collect();
let test_len = 64;
let reference = CRC32C_REF.checksum(&base_data[..test_len]);
for offset in 0..16 {
let data = &base_data[offset..offset + test_len];
let expected = CRC32C_REF.checksum(data);
assert_eq!(Crc32::checksum(data), expected);
}
verify(&base_data[..test_len], reference);
}
#[test]
fn test_crc32_hasher_trait() {
let msg = b"hello world";
let mut hasher = Crc32::new();
hasher.update(msg);
let digest = hasher.finalize();
assert!(Digest::decode(digest.as_ref()).is_ok());
let expected = CRC32C_REF.checksum(msg);
assert_eq!(digest.as_u32(), expected);
hasher.update(msg);
let digest2 = hasher.finalize();
assert_eq!(digest, digest2);
let hash = Crc32::hash(msg);
assert_eq!(hash.as_u32(), expected);
}
#[test]
fn test_crc32_len() {
assert_eq!(Digest::SIZE, SIZE);
assert_eq!(SIZE, 4);
}
#[test]
fn test_codec() {
let msg = b"hello world";
let mut hasher = Crc32::new();
hasher.update(msg);
let digest = hasher.finalize();
let encoded = digest.encode();
assert_eq!(encoded.len(), SIZE);
assert_eq!(encoded, digest.as_ref());
let decoded = Digest::decode(encoded).unwrap();
assert_eq!(digest, decoded);
}
#[test]
fn test_digest_from_u32() {
let value: u32 = 0xDEADBEEF;
let digest = Digest::from(value);
assert_eq!(digest.as_u32(), value);
assert_eq!(digest.0, [0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn test_checksum_returns_u32() {
let checksum: u32 = Crc32::checksum(b"test");
let expected = CRC32C_REF.checksum(b"test");
assert_eq!(checksum, expected);
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Digest>,
}
}
}