1#![cfg_attr(
110 feature = "vpclmulqdq",
111 feature(avx512_target_feature, stdarch_x86_avx512)
112)]
113
114use crate::crc32::consts::{
115 CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM,
116 CRC32_ISCSI, CRC32_ISO_HDLC, CRC32_JAMCRC, CRC32_MEF, CRC32_MPEG_2, CRC32_XFER,
117};
118use crate::crc64::consts::{
119 CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ,
120};
121use crate::structs::{Calculator, CrcParams};
122use crate::traits::CrcCalculator;
123use digest::{DynDigest, InvalidBufferSize};
124use std::fs::File;
125use std::io::{Read, Write};
126
127mod algorithm;
128mod arch;
129mod bindings;
130mod combine;
131mod consts;
132mod crc32;
133mod crc64;
134mod enums;
135mod ffi;
136mod generate;
137mod structs;
138mod test;
139mod traits;
140
141#[derive(Debug, Clone, Copy)]
143pub enum CrcAlgorithm {
144 Crc32Aixm,
145 Crc32Autosar,
146 Crc32Base91D,
147 Crc32Bzip2,
148 Crc32CdRomEdc,
149 Crc32Cksum,
150 Crc32Iscsi,
151 Crc32IsoHdlc,
152 Crc32Jamcrc,
153 Crc32Mef,
154 Crc32Mpeg2,
155 Crc32Xfer,
156 Crc64Ecma182,
157 Crc64GoIso,
158 Crc64Ms,
159 Crc64Nvme,
160 Crc64Redis,
161 Crc64We,
162 Crc64Xz,
163}
164
165type CalculatorFn = fn(
174 u64, &[u8], CrcParams, ) -> u64;
178
179#[derive(Copy, Clone, Debug)]
185pub struct Digest {
186 state: u64,
188
189 amount: u64,
191
192 params: CrcParams,
194
195 calculator: CalculatorFn,
197}
198
199impl DynDigest for Digest {
200 #[inline(always)]
201 fn update(&mut self, data: &[u8]) {
202 self.update(data);
203 }
204
205 #[inline(always)]
206 fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
207 if buf.len() != self.output_size() {
208 return Err(InvalidBufferSize);
209 }
210
211 let result = self.finalize();
212 let bytes = if self.output_size() == 4 {
213 result.to_be_bytes()[4..].to_vec() } else {
215 result.to_be_bytes().to_vec() };
217 buf.copy_from_slice(&bytes[..self.output_size()]);
218
219 Ok(())
220 }
221
222 #[inline(always)]
223 fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
224 if out.len() != self.output_size() {
225 return Err(InvalidBufferSize);
226 }
227 let result = self.finalize();
228 self.reset();
229 let bytes = if self.output_size() == 4 {
230 result.to_be_bytes()[4..].to_vec() } else {
232 result.to_be_bytes().to_vec() };
234 out.copy_from_slice(&bytes[..self.output_size()]);
235 Ok(())
236 }
237
238 #[inline(always)]
239 fn reset(&mut self) {
240 self.reset();
241 }
242
243 #[inline(always)]
244 fn output_size(&self) -> usize {
245 self.params.width as usize / 8
246 }
247
248 fn box_clone(&self) -> Box<dyn DynDigest> {
249 Box::new(*self)
250 }
251}
252
253impl Digest {
254 #[inline(always)]
256 pub fn new(algorithm: CrcAlgorithm) -> Self {
257 let (calculator, params) = get_calculator_params(algorithm);
258
259 Self {
260 state: params.init,
261 amount: 0,
262 params,
263 calculator,
264 }
265 }
266
267 #[inline(always)]
269 pub fn update(&mut self, data: &[u8]) {
270 self.state = (self.calculator)(self.state, data, self.params);
271 self.amount += data.len() as u64;
272 }
273
274 #[inline(always)]
276 pub fn finalize(&self) -> u64 {
277 self.state ^ self.params.xorout
278 }
279
280 #[inline(always)]
282 pub fn finalize_reset(&mut self) -> u64 {
283 let result = self.finalize();
284 self.reset();
285
286 result
287 }
288
289 #[inline(always)]
291 pub fn reset(&mut self) {
292 self.state = self.params.init;
293 self.amount = 0;
294 }
295
296 #[inline(always)]
298 pub fn combine(&mut self, other: &Self) {
299 self.amount += other.amount;
300 let other_crc = other.finalize();
301
302 self.state = combine::checksums(
305 self.state ^ self.params.xorout,
306 other_crc,
307 other.amount,
308 self.params,
309 ) ^ self.params.xorout;
310 }
311
312 #[inline(always)]
314 pub fn get_amount(&self) -> u64 {
315 self.amount
316 }
317}
318
319impl Write for Digest {
320 #[inline(always)]
321 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
322 self.update(buf);
323 Ok(buf.len())
324 }
325
326 #[inline(always)]
327 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
328 let len: usize = bufs
329 .iter()
330 .map(|buf| {
331 self.update(buf);
332 buf.len()
333 })
334 .sum();
335
336 Ok(len)
337 }
338
339 #[inline(always)]
340 fn flush(&mut self) -> std::io::Result<()> {
341 Ok(())
342 }
343
344 #[inline(always)]
345 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
346 self.update(buf);
347
348 Ok(())
349 }
350}
351
352#[inline(always)]
361pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
362 let (calculator, params) = get_calculator_params(algorithm);
363
364 calculator(params.init, buf, params) ^ params.xorout
365}
366
367#[inline(always)]
390pub fn checksum_file(
391 algorithm: CrcAlgorithm,
392 path: &str,
393 chunk_size: Option<usize>,
394) -> Result<u64, std::io::Error> {
395 let mut digest = Digest::new(algorithm);
396 let mut file = File::open(path)?;
397
398 let chunk_size = chunk_size.unwrap_or(524288);
404
405 let mut buf = vec![0; chunk_size];
406
407 while let Ok(n) = file.read(&mut buf) {
408 if n == 0 {
409 break;
410 }
411 digest.update(&buf[..n]);
412 }
413
414 Ok(digest.finalize())
415}
416
417#[inline(always)]
430pub fn checksum_combine(
431 algorithm: CrcAlgorithm,
432 checksum1: u64,
433 checksum2: u64,
434 checksum2_len: u64,
435) -> u64 {
436 let params = get_calculator_params(algorithm).1;
437
438 combine::checksums(checksum1, checksum2, checksum2_len, params)
439}
440
441pub fn get_calculator_target(algorithm: CrcAlgorithm) -> String {
450 match algorithm {
451 CrcAlgorithm::Crc32IsoHdlc => {
452 #[cfg(optimized_crc32_iso_hdlc)]
453 unsafe {
454 bindings::get_iso_hdlc_target()
455 }
456 #[cfg(not(optimized_crc32_iso_hdlc))]
457 arch::get_target()
458 }
459 CrcAlgorithm::Crc32Iscsi => {
460 #[cfg(optimized_crc32_iscsi)]
461 unsafe {
462 bindings::get_iscsi_target()
463 }
464 #[cfg(not(optimized_crc32_iscsi))]
465 arch::get_target()
466 }
467 _ => arch::get_target(),
468 }
469}
470
471#[inline(always)]
473fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
474 match algorithm {
475 CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
476 CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
477 CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
478 CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
479 CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
480 CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
481 CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
482 CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
483 CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
484 CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
485 CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
486 CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
487 CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
488 CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
489 CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
490 CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
491 CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
492 CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
493 CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
494 }
495}
496
497#[inline(always)]
504fn crc32_iscsi_calculator(state: u64, data: &[u8], params: CrcParams) -> u64 {
505 #[cfg(optimized_crc32_iscsi)]
506 {
507 bindings::crc32_iscsi(state, data, params)
508 }
509
510 #[cfg(not(optimized_crc32_iscsi))]
511 {
512 Calculator::calculate(state, data, params)
513 }
514}
515
516fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], params: CrcParams) -> u64 {
523 #[cfg(optimized_crc32_iso_hdlc)]
524 {
525 #[cfg(target_arch = "x86_64")]
527 {
528 if data.len() > 1024 && std::arch::is_x86_feature_detected!("vpclmulqdq") {
529 return bindings::crc32_iso_hdlc(state, data, params);
530 }
531
532 Calculator::calculate(state, data, params)
535 }
536
537 #[cfg(not(target_arch = "x86_64"))]
538 return bindings::crc32_iso_hdlc(state, data, params);
540 }
541
542 #[cfg(not(optimized_crc32_iso_hdlc))]
543 {
544 Calculator::calculate(state, data, params)
545 }
546}
547
548#[cfg(test)]
549mod lib {
550 #![allow(unused)]
551
552 use super::*;
553 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
554 use cbindgen::Language::{Cxx, C};
555 use cbindgen::Style::Both;
556 use rand::{rng, Rng};
557 use std::fs::{read, write};
558
559 #[test]
560 fn test_checksum_check() {
561 for config in TEST_ALL_CONFIGS {
562 assert_eq!(
563 checksum(config.get_algorithm(), TEST_CHECK_STRING),
564 config.get_check()
565 );
566 }
567 }
568
569 #[test]
570 fn test_checksum_reference() {
571 for config in TEST_ALL_CONFIGS {
572 assert_eq!(
573 checksum(config.get_algorithm(), TEST_CHECK_STRING),
574 config.checksum_with_reference(TEST_CHECK_STRING)
575 );
576 }
577 }
578
579 #[test]
580 fn test_digest_updates_check() {
581 for config in TEST_ALL_CONFIGS {
582 let mut digest = Digest::new(config.get_algorithm());
583 digest.update(b"123");
584 digest.update(b"456");
585 digest.update(b"789");
586 let result = digest.finalize();
587
588 assert_eq!(result, config.get_check());
589 }
590 }
591
592 #[test]
593 fn test_small_all_lengths() {
594 let mut rng = rng();
595
596 for config in TEST_ALL_CONFIGS {
598 for len in 1..=255 {
600 let mut data = vec![0u8; len];
602 rng.fill(&mut data[..]);
603
604 let expected = config.checksum_with_reference(&data);
606
607 let result = checksum(config.get_algorithm(), &data);
608
609 assert_eq!(result, expected);
610 }
611 }
612 }
613
614 #[test]
615 fn test_medium_lengths() {
616 let mut rng = rng();
617
618 for config in TEST_ALL_CONFIGS {
620 for len in 256..=1024 {
622 let mut data = vec![0u8; len];
624 rng.fill(&mut data[..]);
625
626 let expected = config.checksum_with_reference(&data);
628
629 let result = checksum(config.get_algorithm(), &data);
630
631 assert_eq!(result, expected);
632 }
633 }
634 }
635
636 #[test]
637 fn test_large_lengths() {
638 let mut rng = rng();
639
640 for config in TEST_ALL_CONFIGS {
642 for len in 1048575..1048577 {
644 let mut data = vec![0u8; len];
646 rng.fill(&mut data[..]);
647
648 let expected = config.checksum_with_reference(&data);
650
651 let result = checksum(config.get_algorithm(), &data);
652
653 assert_eq!(result, expected);
654 }
655 }
656 }
657
658 #[test]
659 fn test_combine() {
660 for config in TEST_ALL_CONFIGS {
661 let algorithm = config.get_algorithm();
662 let check = config.get_check();
663
664 let checksum1 = checksum(algorithm, "1234".as_ref());
666 let checksum2 = checksum(algorithm, "56789".as_ref());
667
668 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
670
671 let mut digest1 = Digest::new(algorithm);
673 digest1.update("1234".as_ref());
674
675 let mut digest2 = Digest::new(algorithm);
676 digest2.update("56789".as_ref());
677
678 digest1.combine(&digest2);
679
680 assert_eq!(digest1.finalize(), check)
681 }
682 }
683
684 #[test]
685 fn test_checksum_file() {
686 let test_file_path = "test/test_crc32_hash_file.bin";
688 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
690 eprintln!("Skipping test due to write error: {}", e);
691 return;
692 }
693
694 for config in TEST_ALL_CONFIGS {
695 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
696 assert_eq!(result, config.checksum_with_reference(&data));
697 }
698
699 std::fs::remove_file(test_file_path).unwrap();
700 }
701
702 #[test]
703 fn test_writer() {
704 let test_file_path = "test/test_crc32_writer_file.bin";
706 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
708 eprintln!("Skipping test due to write error: {}", e);
709 return;
710 }
711
712 for config in TEST_ALL_CONFIGS {
713 let mut digest = Digest::new(config.get_algorithm());
714 let mut file = File::open(test_file_path).unwrap();
715 std::io::copy(&mut file, &mut digest).unwrap();
716 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
717 }
718
719 std::fs::remove_file(test_file_path).unwrap();
720 }
721 #[test]
722 fn test_digest_reset() {
723 for config in TEST_ALL_CONFIGS {
724 let mut digest = Digest::new(config.get_algorithm());
725 digest.update(b"42");
726 digest.reset();
727 digest.update(TEST_CHECK_STRING);
728 assert_eq!(digest.finalize(), config.get_check());
729 }
730 }
731
732 #[test]
733 fn test_digest_finalize_reset() {
734 for config in TEST_ALL_CONFIGS {
735 let check = config.get_check();
736
737 let mut digest = Digest::new(config.get_algorithm());
738 digest.update(TEST_CHECK_STRING);
739 assert_eq!(digest.finalize_reset(), check);
740
741 digest.update(TEST_CHECK_STRING);
742 assert_eq!(digest.finalize(), check);
743 }
744 }
745
746 #[test]
747 fn test_digest_finalize_into() {
748 for config in TEST_ALL_CONFIGS {
749 let mut digest = Digest::new(config.get_algorithm());
750 digest.update(TEST_CHECK_STRING);
751
752 match digest.params.width {
753 32 => {
754 let mut output = [0u8; 4];
755 digest.finalize_into(&mut output).unwrap();
756 let result = u32::from_be_bytes(output) as u64;
757 assert_eq!(result, config.get_check());
758 }
759 64 => {
760 let mut output = [0u8; 8];
761 digest.finalize_into(&mut output).unwrap();
762 let result = u64::from_be_bytes(output);
763 assert_eq!(result, config.get_check());
764 }
765 _ => panic!("Unsupported CRC width"),
766 }
767 }
768 }
769
770 #[test]
771 fn test_digest_finalize_into_reset() {
772 for config in TEST_ALL_CONFIGS {
773 let mut digest = Digest::new(config.get_algorithm());
774 digest.update(TEST_CHECK_STRING);
775
776 let mut output: Vec<u8> = match digest.params.width {
777 32 => vec![0u8; 4],
778 64 => vec![0u8; 8],
779 _ => panic!("Unsupported CRC width"),
780 };
781
782 digest.finalize_into_reset(&mut output).unwrap();
783 let result = match output.len() {
784 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
785 8 => u64::from_be_bytes(output.try_into().unwrap()),
786 _ => panic!("Unsupported CRC width"),
787 };
788 assert_eq!(result, config.get_check());
789
790 digest.update(TEST_CHECK_STRING);
791 assert_eq!(digest.finalize(), config.get_check());
792 }
793 }
794
795 #[test]
797 fn test_ffi_header() -> Result<(), String> {
798 #[cfg(target_os = "windows")]
799 {
800 eprintln!("Skipping test on Windows");
802
803 return Ok(());
804 }
805
806 #[cfg(not(target_os = "windows"))]
807 {
808 const HEADER: &str = "libcrc_fast.h";
809
810 let crate_dir =
811 std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
812
813 let mut expected = Vec::new();
814 cbindgen::Builder::new()
815 .with_crate(crate_dir)
816 .with_include_guard("CRC_FAST_H")
817 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
818 .exclude_item("crc32_iscsi_impl")
820 .exclude_item("crc32_iso_hdlc_impl")
821 .exclude_item("get_iscsi_target")
822 .exclude_item("get_iso_hdlc_target")
823 .exclude_item("ISO_HDLC_TARGET")
824 .exclude_item("ISCSI_TARGET")
825 .exclude_item("CrcParams")
826 .rename_item("Digest", "CrcFastDigest")
827 .with_style(Both)
828 .with_language(C)
830 .with_cpp_compat(true)
832 .generate()
833 .map_err(|error| error.to_string())?
834 .write(&mut expected);
835
836 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
839
840 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
842 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
843
844 expected = cleaned_content.into_bytes();
846
847 let actual = read(HEADER).map_err(|error| error.to_string())?;
848
849 if expected != actual {
850 write(HEADER, expected).map_err(|error| error.to_string())?;
851 return Err(format!(
852 "{HEADER} is not up-to-date, commit the generated file and try again"
853 ));
854 }
855
856 Ok(())
857 }
858 }
859
860 #[test]
862 #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
863 fn test_crc32_iso_hdlc_bindings() -> Result<(), String> {
864 build_bindgen("crc32_iso_hdlc", "src/bindings/crc32_iso_hdlc.rs")
865 }
866
867 #[test]
869 #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
870 fn test_crc32_iscsi_bindings() -> Result<(), String> {
871 build_bindgen("crc32_iscsi", "src/bindings/crc32_iscsi.rs")
872 }
873
874 fn build_bindgen(name: &str, bindings_path: &str) -> Result<(), String> {
875 #[cfg(target_arch = "x86")]
883 {
884 eprintln!("Skipping test on x86 for {} to {}", name, bindings_path);
885
886 return Ok(());
887 }
888
889 #[cfg(target_os = "windows")]
891 {
892 eprintln!("Skipping test on Windows");
894
895 return Ok(());
896 }
897
898 #[cfg(not(any(target_arch = "x86", target_os = "windows")))]
899 {
900 let bindings = bindgen::Builder::default()
901 .header(format!("include/{name}.h"))
902 .allowlist_function("crc32_iscsi_impl")
903 .allowlist_function("get_iscsi_target")
904 .allowlist_var("ISCSI_TARGET")
905 .allowlist_function("crc32_iso_hdlc_impl")
906 .allowlist_function("get_iso_hdlc_target")
907 .allowlist_var("ISO_HDLC_TARGET")
908 .generate()
909 .expect("Unable to generate bindings");
910
911 let expected = bindings.to_string().into_bytes();
912
913 let actual = read(bindings_path).map_err(|error| error.to_string())?;
914
915 if expected != actual {
916 bindings
917 .write_to_file(bindings_path)
918 .expect("Couldn't write bindings to SRC!");
919
920 return Err(format!(
921 "{bindings_path} is not up-to-date, commit the generated file and try again"
922 ));
923 }
924
925 Ok(())
926 }
927 }
928}