1use crate::crc32::consts::{
109 CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
110 CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
111};
112
113#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
114use crate::crc32::fusion;
115
116use crate::crc64::consts::{
117 CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
118};
119use crate::structs::{Calculator, CrcParams};
120use crate::traits::CrcCalculator;
121use digest::{DynDigest, InvalidBufferSize};
122use std::fs::File;
123use std::io::{Read, Write};
124
125mod algorithm;
126mod arch;
127mod combine;
128mod consts;
129mod crc32;
130mod crc64;
131mod enums;
132mod ffi;
133mod generate;
134mod structs;
135mod test;
136mod traits;
137
138#[derive(Debug, Clone, Copy)]
140pub enum CrcAlgorithm {
141 Crc32Aixm,
142 Crc32Autosar,
143 Crc32Base91D,
144 Crc32Bzip2,
145 Crc32CdRomEdc,
146 Crc32Cksum,
147 Crc32Iscsi,
148 Crc32IsoHdlc,
149 Crc32Jamcrc,
150 Crc32Mef,
151 Crc32Mpeg2,
152 Crc32Xfer,
153 Crc64Ecma182,
154 Crc64GoIso,
155 Crc64Ms,
156 Crc64Nvme,
157 Crc64Redis,
158 Crc64We,
159 Crc64Xz,
160}
161
162type CalculatorFn = fn(
171 u64, &[u8], CrcParams, ) -> u64;
175
176#[derive(Copy, Clone, Debug)]
182pub struct Digest {
183 state: u64,
185
186 amount: u64,
188
189 params: CrcParams,
191
192 calculator: CalculatorFn,
194}
195
196impl DynDigest for Digest {
197 #[inline(always)]
198 fn update(&mut self, data: &[u8]) {
199 self.update(data);
200 }
201
202 #[inline(always)]
203 fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
204 if buf.len() != self.output_size() {
205 return Err(InvalidBufferSize);
206 }
207
208 let result = self.finalize();
209 let bytes = if self.output_size() == 4 {
210 result.to_be_bytes()[4..].to_vec() } else {
212 result.to_be_bytes().to_vec() };
214 buf.copy_from_slice(&bytes[..self.output_size()]);
215
216 Ok(())
217 }
218
219 #[inline(always)]
220 fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
221 if out.len() != self.output_size() {
222 return Err(InvalidBufferSize);
223 }
224 let result = self.finalize();
225 self.reset();
226 let bytes = if self.output_size() == 4 {
227 result.to_be_bytes()[4..].to_vec() } else {
229 result.to_be_bytes().to_vec() };
231 out.copy_from_slice(&bytes[..self.output_size()]);
232 Ok(())
233 }
234
235 #[inline(always)]
236 fn reset(&mut self) {
237 self.reset();
238 }
239
240 #[inline(always)]
241 fn output_size(&self) -> usize {
242 self.params.width as usize / 8
243 }
244
245 fn box_clone(&self) -> Box<dyn DynDigest> {
246 Box::new(*self)
247 }
248}
249
250impl Digest {
251 #[inline(always)]
253 pub fn new(algorithm: CrcAlgorithm) -> Self {
254 let (calculator, params) = get_calculator_params(algorithm);
255
256 Self {
257 state: params.init,
258 amount: 0,
259 params,
260 calculator,
261 }
262 }
263
264 #[inline(always)]
266 pub fn update(&mut self, data: &[u8]) {
267 self.state = (self.calculator)(self.state, data, self.params);
268 self.amount += data.len() as u64;
269 }
270
271 #[inline(always)]
273 pub fn finalize(&self) -> u64 {
274 self.state ^ self.params.xorout
275 }
276
277 #[inline(always)]
279 pub fn finalize_reset(&mut self) -> u64 {
280 let result = self.finalize();
281 self.reset();
282
283 result
284 }
285
286 #[inline(always)]
288 pub fn reset(&mut self) {
289 self.state = self.params.init;
290 self.amount = 0;
291 }
292
293 #[inline(always)]
295 pub fn combine(&mut self, other: &Self) {
296 self.amount += other.amount;
297 let other_crc = other.finalize();
298
299 self.state = combine::checksums(
302 self.state ^ self.params.xorout,
303 other_crc,
304 other.amount,
305 self.params,
306 ) ^ self.params.xorout;
307 }
308
309 #[inline(always)]
311 pub fn get_amount(&self) -> u64 {
312 self.amount
313 }
314}
315
316impl Write for Digest {
317 #[inline(always)]
318 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
319 self.update(buf);
320 Ok(buf.len())
321 }
322
323 #[inline(always)]
324 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
325 let len: usize = bufs
326 .iter()
327 .map(|buf| {
328 self.update(buf);
329 buf.len()
330 })
331 .sum();
332
333 Ok(len)
334 }
335
336 #[inline(always)]
337 fn flush(&mut self) -> std::io::Result<()> {
338 Ok(())
339 }
340
341 #[inline(always)]
342 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
343 self.update(buf);
344
345 Ok(())
346 }
347}
348
349#[inline(always)]
358pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
359 let (calculator, params) = get_calculator_params(algorithm);
360
361 calculator(params.init, buf, params) ^ params.xorout
362}
363
364#[inline(always)]
387pub fn checksum_file(
388 algorithm: CrcAlgorithm,
389 path: &str,
390 chunk_size: Option<usize>,
391) -> Result<u64, std::io::Error> {
392 let mut digest = Digest::new(algorithm);
393 let mut file = File::open(path)?;
394
395 let chunk_size = chunk_size.unwrap_or(524288);
401
402 let mut buf = vec![0; chunk_size];
403
404 while let Ok(n) = file.read(&mut buf) {
405 if n == 0 {
406 break;
407 }
408 digest.update(&buf[..n]);
409 }
410
411 Ok(digest.finalize())
412}
413
414#[inline(always)]
427pub fn checksum_combine(
428 algorithm: CrcAlgorithm,
429 checksum1: u64,
430 checksum2: u64,
431 checksum2_len: u64,
432) -> u64 {
433 let params = get_calculator_params(algorithm).1;
434
435 combine::checksums(checksum1, checksum2, checksum2_len, params)
436}
437
438pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String {
450 arch::get_target()
451}
452
453#[inline(always)]
455fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
456 match algorithm {
457 CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
458 CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
459 CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
460 CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
461 CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
462 CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
463 CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
464 CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
465 CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
466 CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
467 CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
468 CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
469 CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
470 CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
471 CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
472 CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
473 CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
474 CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
475 CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
476 }
477}
478
479#[inline(always)]
484fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
485 #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
487 return fusion::crc32_iscsi(state as u32, data) as u64;
488
489 #[cfg(all(not(target_arch = "aarch64"), not(target_arch = "x86_64")))]
490 Calculator::calculate(state, data, _params)
492}
493
494#[inline(always)]
500fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 {
501 #[cfg(target_arch = "aarch64")]
503 return fusion::crc32_iso_hdlc(state as u32, data) as u64;
504
505 #[cfg(not(target_arch = "aarch64"))]
508 Calculator::calculate(state, data, _params)
509}
510
511#[cfg(test)]
512mod lib {
513 #![allow(unused)]
514
515 use super::*;
516 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
517 use crate::test::enums::AnyCrcTestConfig;
518 use cbindgen::Language::{Cxx, C};
519 use cbindgen::Style::Both;
520 use rand::{rng, Rng};
521 use std::fs::{read, write};
522
523 #[test]
524 fn test_checksum_check() {
525 for config in TEST_ALL_CONFIGS {
526 assert_eq!(
527 checksum(config.get_algorithm(), TEST_CHECK_STRING),
528 config.get_check()
529 );
530 }
531 }
532
533 #[test]
534 fn test_checksum_reference() {
535 for config in TEST_ALL_CONFIGS {
536 assert_eq!(
537 checksum(config.get_algorithm(), TEST_CHECK_STRING),
538 config.checksum_with_reference(TEST_CHECK_STRING)
539 );
540 }
541 }
542
543 #[test]
544 fn test_digest_updates_check() {
545 for config in TEST_ALL_CONFIGS {
546 let mut digest = Digest::new(config.get_algorithm());
547 digest.update(b"123");
548 digest.update(b"456");
549 digest.update(b"789");
550 let result = digest.finalize();
551
552 assert_eq!(result, config.get_check());
553 }
554 }
555
556 #[test]
557 fn test_small_all_lengths() {
558 for config in TEST_ALL_CONFIGS {
559 for len in 1..=255 {
561 test_length(len, config);
562 }
563 }
564 }
565
566 #[test]
567 fn test_medium_lengths() {
568 for config in TEST_ALL_CONFIGS {
569 for len in 256..=1024 {
571 test_length(len, config);
572 }
573 }
574 }
575
576 #[test]
577 fn test_large_lengths() {
578 for config in TEST_ALL_CONFIGS {
579 for len in 1048575..1048577 {
581 test_length(len, config);
582 }
583 }
584 }
585
586 fn test_length(length: usize, config: &AnyCrcTestConfig) {
587 let mut data = vec![0u8; length];
588 rng().fill(&mut data[..]);
589
590 let expected = config.checksum_with_reference(&data);
592
593 let result = checksum(config.get_algorithm(), &data);
594
595 assert_eq!(
596 result,
597 expected,
598 "Failed for algorithm: {:?}, length: {}, expected: {:#x}, got: {:#x}",
599 config.get_algorithm(),
600 length,
601 expected,
602 result
603 );
604 }
605
606 #[test]
607 fn test_combine() {
608 for config in TEST_ALL_CONFIGS {
609 let algorithm = config.get_algorithm();
610 let check = config.get_check();
611
612 let checksum1 = checksum(algorithm, "1234".as_ref());
614 let checksum2 = checksum(algorithm, "56789".as_ref());
615
616 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
618
619 let mut digest1 = Digest::new(algorithm);
621 digest1.update("1234".as_ref());
622
623 let mut digest2 = Digest::new(algorithm);
624 digest2.update("56789".as_ref());
625
626 digest1.combine(&digest2);
627
628 assert_eq!(digest1.finalize(), check)
629 }
630 }
631
632 #[test]
633 fn test_checksum_file() {
634 let test_file_path = "test/test_crc32_hash_file.bin";
636 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
638 eprintln!("Skipping test due to write error: {}", e);
639 return;
640 }
641
642 for config in TEST_ALL_CONFIGS {
643 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
644 assert_eq!(result, config.checksum_with_reference(&data));
645 }
646
647 std::fs::remove_file(test_file_path).unwrap();
648 }
649
650 #[test]
651 fn test_writer() {
652 let test_file_path = "test/test_crc32_writer_file.bin";
654 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
656 eprintln!("Skipping test due to write error: {}", e);
657 return;
658 }
659
660 for config in TEST_ALL_CONFIGS {
661 let mut digest = Digest::new(config.get_algorithm());
662 let mut file = File::open(test_file_path).unwrap();
663 std::io::copy(&mut file, &mut digest).unwrap();
664 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
665 }
666
667 std::fs::remove_file(test_file_path).unwrap();
668 }
669 #[test]
670 fn test_digest_reset() {
671 for config in TEST_ALL_CONFIGS {
672 let mut digest = Digest::new(config.get_algorithm());
673 digest.update(b"42");
674 digest.reset();
675 digest.update(TEST_CHECK_STRING);
676 assert_eq!(digest.finalize(), config.get_check());
677 }
678 }
679
680 #[test]
681 fn test_digest_finalize_reset() {
682 for config in TEST_ALL_CONFIGS {
683 let check = config.get_check();
684
685 let mut digest = Digest::new(config.get_algorithm());
686 digest.update(TEST_CHECK_STRING);
687 assert_eq!(digest.finalize_reset(), check);
688
689 digest.update(TEST_CHECK_STRING);
690 assert_eq!(digest.finalize(), check);
691 }
692 }
693
694 #[test]
695 fn test_digest_finalize_into() {
696 for config in TEST_ALL_CONFIGS {
697 let mut digest = Digest::new(config.get_algorithm());
698 digest.update(TEST_CHECK_STRING);
699
700 match digest.params.width {
701 32 => {
702 let mut output = [0u8; 4];
703 digest.finalize_into(&mut output).unwrap();
704 let result = u32::from_be_bytes(output) as u64;
705 assert_eq!(result, config.get_check());
706 }
707 64 => {
708 let mut output = [0u8; 8];
709 digest.finalize_into(&mut output).unwrap();
710 let result = u64::from_be_bytes(output);
711 assert_eq!(result, config.get_check());
712 }
713 _ => panic!("Unsupported CRC width"),
714 }
715 }
716 }
717
718 #[test]
719 fn test_digest_finalize_into_reset() {
720 for config in TEST_ALL_CONFIGS {
721 let mut digest = Digest::new(config.get_algorithm());
722 digest.update(TEST_CHECK_STRING);
723
724 let mut output: Vec<u8> = match digest.params.width {
725 32 => vec![0u8; 4],
726 64 => vec![0u8; 8],
727 _ => panic!("Unsupported CRC width"),
728 };
729
730 digest.finalize_into_reset(&mut output).unwrap();
731 let result = match output.len() {
732 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
733 8 => u64::from_be_bytes(output.try_into().unwrap()),
734 _ => panic!("Unsupported CRC width"),
735 };
736 assert_eq!(result, config.get_check());
737
738 digest.update(TEST_CHECK_STRING);
739 assert_eq!(digest.finalize(), config.get_check());
740 }
741 }
742
743 #[test]
745 fn test_ffi_header() -> Result<(), String> {
746 #[cfg(target_os = "windows")]
747 {
748 eprintln!("Skipping test on Windows");
750
751 return Ok(());
752 }
753
754 const HEADER: &str = "libcrc_fast.h";
755
756 let crate_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
757
758 let mut expected = Vec::new();
759 cbindgen::Builder::new()
760 .with_crate(crate_dir)
761 .with_include_guard("CRC_FAST_H")
762 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
763 .exclude_item("crc32_iscsi_impl")
765 .exclude_item("crc32_iso_hdlc_impl")
766 .exclude_item("get_iscsi_target")
767 .exclude_item("get_iso_hdlc_target")
768 .exclude_item("ISO_HDLC_TARGET")
769 .exclude_item("ISCSI_TARGET")
770 .exclude_item("CrcParams")
771 .rename_item("Digest", "CrcFastDigest")
772 .with_style(Both)
773 .with_language(C)
775 .with_cpp_compat(true)
777 .generate()
778 .map_err(|error| error.to_string())?
779 .write(&mut expected);
780
781 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
784
785 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
787 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
788
789 expected = cleaned_content.into_bytes();
791
792 let actual = read(HEADER).map_err(|error| error.to_string())?;
793
794 if expected != actual {
795 write(HEADER, expected).map_err(|error| error.to_string())?;
796 return Err(format!(
797 "{HEADER} is not up-to-date, commit the generated file and try again"
798 ));
799 }
800
801 Ok(())
802 }
803}