1use crate::Hasher;
30use bytes::{Buf, BufMut};
31use commonware_codec::{Error as CodecError, FixedSize, Read, ReadExt, Write};
32use commonware_math::algebra::Random;
33use commonware_utils::{hex, Array, Span};
34use core::{
35 fmt::{Debug, Display},
36 ops::Deref,
37};
38use rand_core::CryptoRngCore;
39
40const SIZE: usize = 4;
42
43const ALGORITHM: crc_fast::CrcAlgorithm = crc_fast::CrcAlgorithm::Crc32Iscsi;
45
46#[derive(Debug)]
50pub struct Crc32 {
51 inner: crc_fast::Digest,
52}
53
54impl Default for Crc32 {
55 fn default() -> Self {
56 Self {
57 inner: crc_fast::Digest::new(ALGORITHM),
58 }
59 }
60}
61
62impl Clone for Crc32 {
63 fn clone(&self) -> Self {
64 Self::default()
66 }
67}
68
69impl Crc32 {
70 #[inline]
74 pub fn checksum(data: &[u8]) -> u32 {
75 crc_fast::checksum(ALGORITHM, data) as u32
76 }
77}
78
79impl Hasher for Crc32 {
80 type Digest = Digest;
81
82 fn update(&mut self, message: &[u8]) -> &mut Self {
83 self.inner.update(message);
84 self
85 }
86
87 fn finalize(&mut self) -> Self::Digest {
88 Self::Digest::from(self.inner.finalize_reset() as u32)
89 }
90
91 fn reset(&mut self) -> &mut Self {
92 self.inner = crc_fast::Digest::new(ALGORITHM);
93 self
94 }
95}
96
97#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
99#[repr(transparent)]
100pub struct Digest(pub [u8; SIZE]);
101
102#[cfg(feature = "arbitrary")]
103impl<'a> arbitrary::Arbitrary<'a> for Digest {
104 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
105 let len = u.int_in_range(0..=256)?;
107 let data = u.bytes(len)?;
108 Ok(Crc32::hash(data))
109 }
110}
111
112impl Digest {
113 #[inline]
115 pub const fn as_u32(&self) -> u32 {
116 u32::from_be_bytes(self.0)
117 }
118}
119
120impl Write for Digest {
121 fn write(&self, buf: &mut impl BufMut) {
122 self.0.write(buf);
123 }
124}
125
126impl Read for Digest {
127 type Cfg = ();
128
129 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
130 let array = <[u8; SIZE]>::read(buf)?;
131 Ok(Self(array))
132 }
133}
134
135impl FixedSize for Digest {
136 const SIZE: usize = SIZE;
137}
138
139impl Span for Digest {}
140
141impl Array for Digest {}
142
143impl From<[u8; SIZE]> for Digest {
144 fn from(value: [u8; SIZE]) -> Self {
145 Self(value)
146 }
147}
148
149impl From<u32> for Digest {
150 fn from(value: u32) -> Self {
151 Self(value.to_be_bytes())
152 }
153}
154
155impl AsRef<[u8]> for Digest {
156 fn as_ref(&self) -> &[u8] {
157 &self.0
158 }
159}
160
161impl Deref for Digest {
162 type Target = [u8];
163 fn deref(&self) -> &[u8] {
164 &self.0
165 }
166}
167
168impl Debug for Digest {
169 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
170 write!(f, "{}", hex(&self.0))
171 }
172}
173
174impl Display for Digest {
175 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
176 write!(f, "{}", hex(&self.0))
177 }
178}
179
180impl crate::Digest for Digest {
181 const EMPTY: Self = Self([0u8; SIZE]);
182}
183
184impl Random for Digest {
185 fn random(mut rng: impl CryptoRngCore) -> Self {
186 let mut array = [0u8; SIZE];
187 rng.fill_bytes(&mut array);
188 Self(array)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::Hasher;
196 use commonware_codec::{DecodeExt, Encode};
197 use crc::{Crc, CRC_32_ISCSI};
198
199 const CRC32C_REF: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
201
202 fn verify(data: &[u8], expected: u32) {
204 assert_eq!(CRC32C_REF.checksum(data), expected);
205 assert_eq!(Crc32::checksum(data), expected);
206 }
207
208 fn sequential_data(len: usize) -> Vec<u8> {
210 (0..len).map(|i| (i & 0xFF) as u8).collect()
211 }
212
213 #[test]
216 fn rfc3720_test_vectors() {
217 verify(&[0x00; 32], 0x8A9136AA);
219
220 verify(&[0xFF; 32], 0x62A8AB43);
222
223 let ascending: Vec<u8> = (0x00..0x20).collect();
225 verify(&ascending, 0x46DD794E);
226
227 let descending: Vec<u8> = (0x00..0x20).rev().collect();
229 verify(&descending, 0x113FDB5C);
230
231 let iscsi_read_pdu: [u8; 48] = [
233 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
234 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14,
235 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
236 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
237 ];
238 verify(&iscsi_read_pdu, 0xD9963A56);
239 }
240
241 #[test]
246 fn external_test_vectors() {
247 verify(b"", 0x00000000);
249 verify(b"123456789", 0xE3069283);
250
251 verify(b"23456789", 0xBFE92A83);
253 verify(b"The quick brown fox jumps over the lazy dog", 0x22620404);
254
255 let sequential_240: Vec<u8> = (0x01..=0xF0).collect();
257 verify(&sequential_240, 0x24C5D375);
258 }
259
260 #[test]
265 fn simd_boundaries() {
266 const BOUNDARY_SIZES: &[usize] = &[
274 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, ];
284
285 const EXPECTED: &[(usize, u32)] = &[
288 (0, 0x00000000),
289 (1, 0x527D5351),
290 (2, 0x030AF4D1),
291 (3, 0x92FD4BFA),
292 (4, 0xD9331AA3),
293 (7, 0xA359ED4C),
294 (8, 0x8A2CBC3B),
295 (9, 0x7144C5A8),
296 (15, 0x68EF03F6),
297 (16, 0xD9C908EB),
298 (17, 0x38435E17),
299 (31, 0xE95CABCB),
300 (32, 0x46DD794E), (33, 0x9F85A26D),
302 (63, 0x7A873004),
303 (64, 0xFB6D36EB),
304 (65, 0x694420FA),
305 (127, 0x6C31BD0C),
306 (128, 0x30D9C515),
307 (129, 0xF514629F),
308 (255, 0x8953C482),
309 (256, 0x9C44184B),
310 (257, 0x8A13A1CE),
311 (511, 0x35348950),
312 (512, 0xAE10EE5A),
313 (513, 0x6814B154),
314 (1023, 0x0C8F24D0),
315 (1024, 0x2CDF6E8F),
316 (1025, 0x8EB48B63),
317 (4095, 0xBCB5BD82),
318 (4096, 0x9C71FE32),
319 (4097, 0x83391BE9),
320 ];
321
322 assert_eq!(
323 BOUNDARY_SIZES,
324 EXPECTED.iter().map(|(size, _)| *size).collect::<Vec<_>>()
325 );
326
327 for &(size, expected) in EXPECTED {
328 let data = sequential_data(size);
329 verify(&data, expected);
330 }
331 }
332
333 #[test]
335 fn chunk_size_independence() {
336 let data = sequential_data(1024);
337 let expected = CRC32C_REF.checksum(&data);
338
339 for chunk_size in 1..=64 {
341 let mut hasher = Crc32::new();
342 for chunk in data.chunks(chunk_size) {
343 hasher.update(chunk);
344 }
345 assert_eq!(hasher.finalize().as_u32(), expected);
346 }
347 }
348
349 #[test]
351 fn alignment_independence() {
352 let base_data: Vec<u8> = (0..256).map(|i| i as u8).collect();
354 let test_len = 64;
355
356 let reference = CRC32C_REF.checksum(&base_data[..test_len]);
358
359 for offset in 0..16 {
362 let data = &base_data[offset..offset + test_len];
363 let expected = CRC32C_REF.checksum(data);
364 assert_eq!(Crc32::checksum(data), expected);
365 }
366
367 verify(&base_data[..test_len], reference);
369 }
370
371 #[test]
372 fn test_crc32_hasher_trait() {
373 let msg = b"hello world";
374
375 let mut hasher = Crc32::new();
377 hasher.update(msg);
378 let digest = hasher.finalize();
379 assert!(Digest::decode(digest.as_ref()).is_ok());
380
381 let expected = CRC32C_REF.checksum(msg);
383 assert_eq!(digest.as_u32(), expected);
384
385 hasher.update(msg);
387 let digest2 = hasher.finalize();
388 assert_eq!(digest, digest2);
389
390 let hash = Crc32::hash(msg);
392 assert_eq!(hash.as_u32(), expected);
393 }
394
395 #[test]
396 fn test_crc32_len() {
397 assert_eq!(Digest::SIZE, SIZE);
398 assert_eq!(SIZE, 4);
399 }
400
401 #[test]
402 fn test_codec() {
403 let msg = b"hello world";
404 let mut hasher = Crc32::new();
405 hasher.update(msg);
406 let digest = hasher.finalize();
407
408 let encoded = digest.encode();
409 assert_eq!(encoded.len(), SIZE);
410 assert_eq!(encoded, digest.as_ref());
411
412 let decoded = Digest::decode(encoded).unwrap();
413 assert_eq!(digest, decoded);
414 }
415
416 #[test]
417 fn test_digest_from_u32() {
418 let value: u32 = 0xDEADBEEF;
419 let digest = Digest::from(value);
420 assert_eq!(digest.as_u32(), value);
421 assert_eq!(digest.0, [0xDE, 0xAD, 0xBE, 0xEF]);
422 }
423
424 #[test]
425 fn test_checksum_returns_u32() {
426 let checksum: u32 = Crc32::checksum(b"test");
428 let expected = CRC32C_REF.checksum(b"test");
429 assert_eq!(checksum, expected);
430 }
431
432 #[cfg(feature = "arbitrary")]
433 mod conformance {
434 use super::*;
435 use commonware_codec::conformance::CodecConformance;
436
437 commonware_conformance::conformance_tests! {
438 CodecConformance<Digest>,
439 }
440 }
441}