|
3 | 3 | // For the full copyright and license information, please view the LICENSE |
4 | 4 | // file that was distributed with this source code. |
5 | 5 |
|
6 | | -// spell-checker:ignore memmem algo |
| 6 | +// spell-checker:ignore memmem algo PCLMULQDQ refin xorout |
7 | 7 |
|
8 | 8 | //! Implementations of digest functions, like md5 and sha1. |
9 | 9 | //! |
@@ -122,84 +122,48 @@ impl Digest for Sm3 { |
122 | 122 | } |
123 | 123 | } |
124 | 124 |
|
125 | | -// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 |
126 | | -const CRC_TABLE_LEN: usize = 256; |
| 125 | +pub struct Crc(crc_fast::Digest); |
127 | 126 |
|
128 | | -pub struct Crc { |
129 | | - state: u32, |
130 | | - size: usize, |
131 | | - crc_table: [u32; CRC_TABLE_LEN], |
132 | | -} |
133 | 127 | impl Crc { |
134 | | - fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { |
135 | | - let mut table = [0; CRC_TABLE_LEN]; |
136 | | - |
137 | | - for (i, elt) in table.iter_mut().enumerate().take(CRC_TABLE_LEN) { |
138 | | - *elt = Self::crc_entry(i as u8); |
139 | | - } |
140 | | - |
141 | | - table |
142 | | - } |
143 | | - fn crc_entry(input: u8) -> u32 { |
144 | | - let mut crc = (input as u32) << 24; |
145 | | - |
146 | | - let mut i = 0; |
147 | | - while i < 8 { |
148 | | - let if_condition = crc & 0x8000_0000; |
149 | | - let if_body = (crc << 1) ^ 0x04c1_1db7; |
150 | | - let else_body = crc << 1; |
151 | | - |
152 | | - // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise |
153 | | - // ops |
154 | | - let condition_table = [else_body, if_body]; |
155 | | - |
156 | | - crc = condition_table[(if_condition != 0) as usize]; |
157 | | - i += 1; |
158 | | - } |
159 | | - |
160 | | - crc |
161 | | - } |
162 | | - |
163 | | - fn update(&mut self, input: u8) { |
164 | | - self.state = (self.state << 8) |
165 | | - ^ self.crc_table[((self.state >> 24) as usize ^ input as usize) & 0xFF]; |
| 128 | + /// POSIX cksum SIMD configuration for crc-fast |
| 129 | + /// This uses SIMD instructions (PCLMULQDQ) for fast CRC computation |
| 130 | + fn get_posix_cksum_params() -> crc_fast::CrcParams { |
| 131 | + crc_fast::CrcParams::new( |
| 132 | + "CRC-32/CKSUM", // Name |
| 133 | + 32, // Width |
| 134 | + 0x04c11db7, // Polynomial |
| 135 | + 0x00000000, // Initial CRC value: 0 (not 0xffffffff) |
| 136 | + false, // No input reflection (refin) |
| 137 | + 0xffffffff, // XOR output with 0xffffffff (xorout) |
| 138 | + 0, // Check value (not used) |
| 139 | + ) |
166 | 140 | } |
167 | 141 | } |
168 | 142 |
|
169 | 143 | impl Digest for Crc { |
170 | 144 | fn new() -> Self { |
171 | | - Self { |
172 | | - state: 0, |
173 | | - size: 0, |
174 | | - crc_table: Self::generate_crc_table(), |
175 | | - } |
| 145 | + Self(crc_fast::Digest::new_with_params( |
| 146 | + Self::get_posix_cksum_params(), |
| 147 | + )) |
176 | 148 | } |
177 | 149 |
|
178 | 150 | fn hash_update(&mut self, input: &[u8]) { |
179 | | - for &elt in input { |
180 | | - self.update(elt); |
181 | | - } |
182 | | - self.size += input.len(); |
| 151 | + self.0.update(input); |
183 | 152 | } |
184 | 153 |
|
185 | 154 | fn hash_finalize(&mut self, out: &mut [u8]) { |
186 | | - let mut sz = self.size; |
187 | | - while sz != 0 { |
188 | | - self.update(sz as u8); |
189 | | - sz >>= 8; |
190 | | - } |
191 | | - self.state = !self.state; |
192 | | - out.copy_from_slice(&self.state.to_ne_bytes()); |
| 155 | + let xout = self.0.finalize(); |
| 156 | + out.copy_from_slice(&xout.to_ne_bytes()); |
193 | 157 | } |
194 | 158 |
|
195 | 159 | fn result_str(&mut self) -> String { |
196 | | - let mut _out: Vec<u8> = vec![0; 4]; |
197 | | - self.hash_finalize(&mut _out); |
198 | | - format!("{}", self.state) |
| 160 | + let mut out: [u8; 8] = [0; 8]; |
| 161 | + self.hash_finalize(&mut out); |
| 162 | + u64::from_ne_bytes(out).to_string() |
199 | 163 | } |
200 | 164 |
|
201 | 165 | fn reset(&mut self) { |
202 | | - *self = Self::new(); |
| 166 | + self.0.reset(); |
203 | 167 | } |
204 | 168 |
|
205 | 169 | fn output_bits(&self) -> usize { |
@@ -529,4 +493,133 @@ mod tests { |
529 | 493 |
|
530 | 494 | assert_eq!(result_crlf, result_lf); |
531 | 495 | } |
| 496 | + |
| 497 | + use super::{Crc, Digest}; |
| 498 | + |
| 499 | + #[test] |
| 500 | + fn test_crc_basic_functionality() { |
| 501 | + // Test that our CRC implementation works with basic functionality |
| 502 | + let mut crc1 = Crc::new(); |
| 503 | + let mut crc2 = Crc::new(); |
| 504 | + |
| 505 | + // Same input should give same output |
| 506 | + crc1.hash_update(b"test"); |
| 507 | + crc2.hash_update(b"test"); |
| 508 | + |
| 509 | + let mut out1 = [0u8; 4]; |
| 510 | + let mut out2 = [0u8; 4]; |
| 511 | + crc1.hash_finalize(&mut out1); |
| 512 | + crc2.hash_finalize(&mut out2); |
| 513 | + |
| 514 | + assert_eq!(out1, out2); |
| 515 | + } |
| 516 | + |
| 517 | + #[test] |
| 518 | + fn test_crc_digest_basic() { |
| 519 | + let mut crc = Crc::new(); |
| 520 | + |
| 521 | + // Test empty input |
| 522 | + let mut output = [0u8; 4]; |
| 523 | + crc.hash_finalize(&mut output); |
| 524 | + let empty_result = u32::from_ne_bytes(output); |
| 525 | + |
| 526 | + // Reset and test with "test" string |
| 527 | + crc.reset(); |
| 528 | + crc.hash_update(b"test"); |
| 529 | + crc.hash_finalize(&mut output); |
| 530 | + let test_result = u32::from_ne_bytes(output); |
| 531 | + |
| 532 | + // The result should be different for different inputs |
| 533 | + assert_ne!(empty_result, test_result); |
| 534 | + |
| 535 | + // Test known value: "test" should give 3076352578 |
| 536 | + assert_eq!(test_result, 3076352578); |
| 537 | + } |
| 538 | + |
| 539 | + #[test] |
| 540 | + fn test_crc_digest_incremental() { |
| 541 | + let mut crc1 = Crc::new(); |
| 542 | + let mut crc2 = Crc::new(); |
| 543 | + |
| 544 | + // Test that processing in chunks gives same result as all at once |
| 545 | + let data = b"Hello, World! This is a test string for CRC computation."; |
| 546 | + |
| 547 | + // Process all at once |
| 548 | + crc1.hash_update(data); |
| 549 | + let mut output1 = [0u8; 4]; |
| 550 | + crc1.hash_finalize(&mut output1); |
| 551 | + |
| 552 | + // Process in chunks |
| 553 | + crc2.hash_update(&data[0..10]); |
| 554 | + crc2.hash_update(&data[10..30]); |
| 555 | + crc2.hash_update(&data[30..]); |
| 556 | + let mut output2 = [0u8; 4]; |
| 557 | + crc2.hash_finalize(&mut output2); |
| 558 | + |
| 559 | + assert_eq!(output1, output2); |
| 560 | + } |
| 561 | + |
| 562 | + #[test] |
| 563 | + fn test_crc_slice8_vs_single_byte() { |
| 564 | + // Test that our optimized slice-by-8 gives same results as byte-by-byte |
| 565 | + let test_data = b"This is a longer test string to verify slice-by-8 optimization works correctly with various data sizes including remainders."; |
| 566 | + |
| 567 | + let mut crc_optimized = Crc::new(); |
| 568 | + crc_optimized.hash_update(test_data); |
| 569 | + let mut output_opt = [0u8; 4]; |
| 570 | + crc_optimized.hash_finalize(&mut output_opt); |
| 571 | + |
| 572 | + // Create a reference implementation using hash_update |
| 573 | + let mut crc_reference = Crc::new(); |
| 574 | + for &byte in test_data { |
| 575 | + crc_reference.hash_update(&[byte]); |
| 576 | + } |
| 577 | + let mut output_ref = [0u8; 4]; |
| 578 | + crc_reference.hash_finalize(&mut output_ref); |
| 579 | + |
| 580 | + assert_eq!(output_opt, output_ref); |
| 581 | + } |
| 582 | + |
| 583 | + #[test] |
| 584 | + fn test_crc_known_values() { |
| 585 | + // Test against our CRC implementation values |
| 586 | + // Note: These are the correct values for our POSIX cksum implementation |
| 587 | + let test_cases = [ |
| 588 | + ("", 4294967295u32), |
| 589 | + ("a", 1220704766u32), |
| 590 | + ("abc", 1219131554u32), |
| 591 | + ]; |
| 592 | + |
| 593 | + for (input, expected) in test_cases { |
| 594 | + let mut crc = Crc::new(); |
| 595 | + crc.hash_update(input.as_bytes()); |
| 596 | + let mut output = [0u8; 4]; |
| 597 | + crc.hash_finalize(&mut output); |
| 598 | + let result = u32::from_ne_bytes(output); |
| 599 | + |
| 600 | + assert_eq!(result, expected, "CRC mismatch for input: '{}'", input); |
| 601 | + } |
| 602 | + } |
| 603 | + |
| 604 | + #[test] |
| 605 | + fn test_crc_hash_update_edge_cases() { |
| 606 | + let mut crc = Crc::new(); |
| 607 | + |
| 608 | + // Test with data that's not a multiple of 8 bytes |
| 609 | + let data7 = b"1234567"; // 7 bytes |
| 610 | + crc.hash_update(data7); |
| 611 | + |
| 612 | + let data9 = b"123456789"; // 9 bytes |
| 613 | + let mut crc2 = Crc::new(); |
| 614 | + crc2.hash_update(data9); |
| 615 | + |
| 616 | + // Should not panic and should produce valid results |
| 617 | + let mut out1 = [0u8; 4]; |
| 618 | + let mut out2 = [0u8; 4]; |
| 619 | + crc.hash_finalize(&mut out1); |
| 620 | + crc2.hash_finalize(&mut out2); |
| 621 | + |
| 622 | + // Results should be different for different inputs |
| 623 | + assert_ne!(out1, out2); |
| 624 | + } |
532 | 625 | } |
0 commit comments