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
179pub struct Digest {
185 state: u64,
187
188 amount: u64,
190
191 params: CrcParams,
193
194 calculator: CalculatorFn,
196}
197
198impl DynDigest for Digest {
199 #[inline(always)]
200 fn update(&mut self, data: &[u8]) {
201 self.update(data);
202 }
203
204 #[inline(always)]
205 fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> {
206 if buf.len() != self.output_size() {
207 return Err(InvalidBufferSize);
208 }
209
210 let result = self.finalize();
211 let bytes = if self.output_size() == 4 {
212 result.to_be_bytes()[4..].to_vec() } else {
214 result.to_be_bytes().to_vec() };
216 buf.copy_from_slice(&bytes[..self.output_size()]);
217
218 Ok(())
219 }
220
221 #[inline(always)]
222 fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> {
223 if out.len() != self.output_size() {
224 return Err(InvalidBufferSize);
225 }
226 let result = self.finalize();
227 self.reset();
228 let bytes = if self.output_size() == 4 {
229 result.to_be_bytes()[4..].to_vec() } else {
231 result.to_be_bytes().to_vec() };
233 out.copy_from_slice(&bytes[..self.output_size()]);
234 Ok(())
235 }
236
237 #[inline(always)]
238 fn reset(&mut self) {
239 self.reset();
240 }
241
242 #[inline(always)]
243 fn output_size(&self) -> usize {
244 self.params.width as usize / 8
245 }
246}
247
248impl Digest {
249 #[inline(always)]
251 pub fn new(algorithm: CrcAlgorithm) -> Self {
252 let (calculator, params) = get_calculator_params(algorithm);
253
254 Self {
255 state: params.init,
256 amount: 0,
257 params,
258 calculator,
259 }
260 }
261
262 #[inline(always)]
264 pub fn update(&mut self, data: &[u8]) {
265 self.state = (self.calculator)(self.state, data, self.params);
266 self.amount += data.len() as u64;
267 }
268
269 #[inline(always)]
271 pub fn finalize(&self) -> u64 {
272 self.state ^ self.params.xorout
273 }
274
275 #[inline(always)]
277 pub fn finalize_reset(&mut self) -> u64 {
278 let result = self.finalize();
279 self.reset();
280
281 result
282 }
283
284 #[inline(always)]
286 pub fn reset(&mut self) {
287 self.state = self.params.init;
288 self.amount = 0;
289 }
290
291 #[inline(always)]
293 pub fn combine(&mut self, other: &Self) {
294 self.amount += other.amount;
295 let other_crc = other.finalize();
296
297 self.state = combine::checksums(
300 self.state ^ self.params.xorout,
301 other_crc,
302 other.amount,
303 self.params,
304 ) ^ self.params.xorout;
305 }
306
307 #[inline(always)]
309 pub fn get_amount(&self) -> u64 {
310 self.amount
311 }
312}
313
314impl Write for Digest {
315 #[inline(always)]
316 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
317 self.update(buf);
318 Ok(buf.len())
319 }
320
321 #[inline(always)]
322 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
323 let len: usize = bufs
324 .iter()
325 .map(|buf| {
326 self.update(buf);
327 buf.len()
328 })
329 .sum();
330
331 Ok(len)
332 }
333
334 #[inline(always)]
335 fn flush(&mut self) -> std::io::Result<()> {
336 Ok(())
337 }
338
339 #[inline(always)]
340 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
341 self.update(buf);
342
343 Ok(())
344 }
345}
346
347#[inline(always)]
356pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 {
357 let (calculator, params) = get_calculator_params(algorithm);
358
359 calculator(params.init, buf, params) ^ params.xorout
360}
361
362#[inline(always)]
385pub fn checksum_file(
386 algorithm: CrcAlgorithm,
387 path: &str,
388 chunk_size: Option<usize>,
389) -> Result<u64, std::io::Error> {
390 let mut digest = Digest::new(algorithm);
391 let mut file = File::open(path)?;
392
393 let chunk_size = chunk_size.unwrap_or(524288);
399
400 let mut buf = vec![0; chunk_size];
401
402 while let Ok(n) = file.read(&mut buf) {
403 if n == 0 {
404 break;
405 }
406 digest.update(&buf[..n]);
407 }
408
409 Ok(digest.finalize())
410}
411
412#[inline(always)]
425pub fn checksum_combine(
426 algorithm: CrcAlgorithm,
427 checksum1: u64,
428 checksum2: u64,
429 checksum2_len: u64,
430) -> u64 {
431 let params = get_calculator_params(algorithm).1;
432
433 combine::checksums(checksum1, checksum2, checksum2_len, params)
434}
435
436pub fn get_calculator_target(algorithm: CrcAlgorithm) -> String {
445 match algorithm {
446 CrcAlgorithm::Crc32IsoHdlc => {
447 #[cfg(optimized_crc32_iso_hdlc)]
448 unsafe {
449 bindings::get_iso_hdlc_target()
450 }
451 #[cfg(not(optimized_crc32_iso_hdlc))]
452 arch::get_target()
453 }
454 CrcAlgorithm::Crc32Iscsi => {
455 #[cfg(optimized_crc32_iscsi)]
456 unsafe {
457 bindings::get_iscsi_target()
458 }
459 #[cfg(not(optimized_crc32_iscsi))]
460 arch::get_target()
461 }
462 _ => arch::get_target(),
463 }
464}
465
466#[inline(always)]
468fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) {
469 match algorithm {
470 CrcAlgorithm::Crc32Aixm => (Calculator::calculate as CalculatorFn, CRC32_AIXM),
471 CrcAlgorithm::Crc32Autosar => (Calculator::calculate as CalculatorFn, CRC32_AUTOSAR),
472 CrcAlgorithm::Crc32Base91D => (Calculator::calculate as CalculatorFn, CRC32_BASE91_D),
473 CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2),
474 CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC),
475 CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM),
476 CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI),
477 CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC),
478 CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC),
479 CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF),
480 CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2),
481 CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER),
482 CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182),
483 CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO),
484 CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS),
485 CrcAlgorithm::Crc64Nvme => (Calculator::calculate as CalculatorFn, CRC64_NVME),
486 CrcAlgorithm::Crc64Redis => (Calculator::calculate as CalculatorFn, CRC64_REDIS),
487 CrcAlgorithm::Crc64We => (Calculator::calculate as CalculatorFn, CRC64_WE),
488 CrcAlgorithm::Crc64Xz => (Calculator::calculate as CalculatorFn, CRC64_XZ),
489 }
490}
491
492#[inline(always)]
499fn crc32_iscsi_calculator(state: u64, data: &[u8], params: CrcParams) -> u64 {
500 #[cfg(optimized_crc32_iscsi)]
501 {
502 bindings::crc32_iscsi(state, data, params)
503 }
504
505 #[cfg(not(optimized_crc32_iscsi))]
506 {
507 Calculator::calculate(state, data, params)
508 }
509}
510
511fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], params: CrcParams) -> u64 {
518 #[cfg(optimized_crc32_iso_hdlc)]
519 {
520 #[cfg(target_arch = "x86_64")]
522 {
523 if data.len() > 1024 && std::arch::is_x86_feature_detected!("vpclmulqdq") {
524 return bindings::crc32_iso_hdlc(state, data, params);
525 }
526
527 Calculator::calculate(state, data, params)
530 }
531
532 #[cfg(not(target_arch = "x86_64"))]
533 return bindings::crc32_iso_hdlc(state, data, params);
535 }
536
537 #[cfg(not(optimized_crc32_iso_hdlc))]
538 {
539 Calculator::calculate(state, data, params)
540 }
541}
542
543#[cfg(test)]
544mod lib {
545 #![allow(unused)]
546
547 use super::*;
548 use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING};
549 use cbindgen::Language::{Cxx, C};
550 use cbindgen::Style::Both;
551 use rand::{rng, Rng};
552 use std::fs::{read, write};
553
554 #[test]
555 fn test_checksum_check() {
556 for config in TEST_ALL_CONFIGS {
557 assert_eq!(
558 checksum(config.get_algorithm(), TEST_CHECK_STRING),
559 config.get_check()
560 );
561 }
562 }
563
564 #[test]
565 fn test_checksum_reference() {
566 for config in TEST_ALL_CONFIGS {
567 assert_eq!(
568 checksum(config.get_algorithm(), TEST_CHECK_STRING),
569 config.checksum_with_reference(TEST_CHECK_STRING)
570 );
571 }
572 }
573
574 #[test]
575 fn test_digest_updates_check() {
576 for config in TEST_ALL_CONFIGS {
577 let mut digest = Digest::new(config.get_algorithm());
578 digest.update(b"123");
579 digest.update(b"456");
580 digest.update(b"789");
581 let result = digest.finalize();
582
583 assert_eq!(result, config.get_check());
584 }
585 }
586
587 #[test]
588 fn test_small_all_lengths() {
589 let mut rng = rng();
590
591 for config in TEST_ALL_CONFIGS {
593 for len in 1..=255 {
595 let mut data = vec![0u8; len];
597 rng.fill(&mut data[..]);
598
599 let expected = config.checksum_with_reference(&data);
601
602 let result = checksum(config.get_algorithm(), &data);
603
604 assert_eq!(result, expected);
605 }
606 }
607 }
608
609 #[test]
610 fn test_medium_lengths() {
611 let mut rng = rng();
612
613 for config in TEST_ALL_CONFIGS {
615 for len in 256..=1024 {
617 let mut data = vec![0u8; len];
619 rng.fill(&mut data[..]);
620
621 let expected = config.checksum_with_reference(&data);
623
624 let result = checksum(config.get_algorithm(), &data);
625
626 assert_eq!(result, expected);
627 }
628 }
629 }
630
631 #[test]
632 fn test_large_lengths() {
633 let mut rng = rng();
634
635 for config in TEST_ALL_CONFIGS {
637 for len in 1048575..1048577 {
639 let mut data = vec![0u8; len];
641 rng.fill(&mut data[..]);
642
643 let expected = config.checksum_with_reference(&data);
645
646 let result = checksum(config.get_algorithm(), &data);
647
648 assert_eq!(result, expected);
649 }
650 }
651 }
652
653 #[test]
654 fn test_combine() {
655 for config in TEST_ALL_CONFIGS {
656 let algorithm = config.get_algorithm();
657 let check = config.get_check();
658
659 let checksum1 = checksum(algorithm, "1234".as_ref());
661 let checksum2 = checksum(algorithm, "56789".as_ref());
662
663 assert_eq!(checksum_combine(algorithm, checksum1, checksum2, 5), check,);
665
666 let mut digest1 = Digest::new(algorithm);
668 digest1.update("1234".as_ref());
669
670 let mut digest2 = Digest::new(algorithm);
671 digest2.update("56789".as_ref());
672
673 digest1.combine(&digest2);
674
675 assert_eq!(digest1.finalize(), check)
676 }
677 }
678
679 #[test]
680 fn test_checksum_file() {
681 let test_file_path = "test/test_crc32_hash_file.bin";
683 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
685 eprintln!("Skipping test due to write error: {}", e);
686 return;
687 }
688
689 for config in TEST_ALL_CONFIGS {
690 let result = checksum_file(config.get_algorithm(), test_file_path, None).unwrap();
691 assert_eq!(result, config.checksum_with_reference(&data));
692 }
693
694 std::fs::remove_file(test_file_path).unwrap();
695 }
696
697 #[test]
698 fn test_writer() {
699 let test_file_path = "test/test_crc32_writer_file.bin";
701 let data = vec![0u8; 1024 * 1024]; if let Err(e) = std::fs::write(test_file_path, &data) {
703 eprintln!("Skipping test due to write error: {}", e);
704 return;
705 }
706
707 for config in TEST_ALL_CONFIGS {
708 let mut digest = Digest::new(config.get_algorithm());
709 let mut file = File::open(test_file_path).unwrap();
710 std::io::copy(&mut file, &mut digest).unwrap();
711 assert_eq!(digest.finalize(), config.checksum_with_reference(&data));
712 }
713
714 std::fs::remove_file(test_file_path).unwrap();
715 }
716 #[test]
717 fn test_digest_reset() {
718 for config in TEST_ALL_CONFIGS {
719 let mut digest = Digest::new(config.get_algorithm());
720 digest.update(b"42");
721 digest.reset();
722 digest.update(TEST_CHECK_STRING);
723 assert_eq!(digest.finalize(), config.get_check());
724 }
725 }
726
727 #[test]
728 fn test_digest_finalize_reset() {
729 for config in TEST_ALL_CONFIGS {
730 let check = config.get_check();
731
732 let mut digest = Digest::new(config.get_algorithm());
733 digest.update(TEST_CHECK_STRING);
734 assert_eq!(digest.finalize_reset(), check);
735
736 digest.update(TEST_CHECK_STRING);
737 assert_eq!(digest.finalize(), check);
738 }
739 }
740
741 #[test]
742 fn test_digest_finalize_into() {
743 for config in TEST_ALL_CONFIGS {
744 let mut digest = Digest::new(config.get_algorithm());
745 digest.update(TEST_CHECK_STRING);
746
747 match digest.params.width {
748 32 => {
749 let mut output = [0u8; 4];
750 digest.finalize_into(&mut output).unwrap();
751 let result = u32::from_be_bytes(output) as u64;
752 assert_eq!(result, config.get_check());
753 }
754 64 => {
755 let mut output = [0u8; 8];
756 digest.finalize_into(&mut output).unwrap();
757 let result = u64::from_be_bytes(output);
758 assert_eq!(result, config.get_check());
759 }
760 _ => panic!("Unsupported CRC width"),
761 }
762 }
763 }
764
765 #[test]
766 fn test_digest_finalize_into_reset() {
767 for config in TEST_ALL_CONFIGS {
768 let mut digest = Digest::new(config.get_algorithm());
769 digest.update(TEST_CHECK_STRING);
770
771 let mut output: Vec<u8> = match digest.params.width {
772 32 => vec![0u8; 4],
773 64 => vec![0u8; 8],
774 _ => panic!("Unsupported CRC width"),
775 };
776
777 digest.finalize_into_reset(&mut output).unwrap();
778 let result = match output.len() {
779 4 => u32::from_be_bytes(output.try_into().unwrap()) as u64,
780 8 => u64::from_be_bytes(output.try_into().unwrap()),
781 _ => panic!("Unsupported CRC width"),
782 };
783 assert_eq!(result, config.get_check());
784
785 digest.update(TEST_CHECK_STRING);
786 assert_eq!(digest.finalize(), config.get_check());
787 }
788 }
789
790 #[test]
792 fn test_ffi_header() -> Result<(), String> {
793 #[cfg(target_os = "windows")]
794 {
795 eprintln!("Skipping test on Windows");
797
798 return Ok(());
799 }
800
801 #[cfg(not(target_os = "windows"))]
802 {
803 const HEADER: &str = "libcrc_fast.h";
804
805 let crate_dir =
806 std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?;
807
808 let mut expected = Vec::new();
809 cbindgen::Builder::new()
810 .with_crate(crate_dir)
811 .with_include_guard("CRC_FAST_H")
812 .with_header("/* crc_fast library C/C++ API - Copyright 2025 Don MacAskill */\n/* This header is auto-generated. Do not edit directly. */\n")
813 .exclude_item("crc32_iscsi_impl")
815 .exclude_item("crc32_iso_hdlc_impl")
816 .exclude_item("get_iscsi_target")
817 .exclude_item("get_iso_hdlc_target")
818 .exclude_item("ISO_HDLC_TARGET")
819 .exclude_item("ISCSI_TARGET")
820 .exclude_item("CrcParams")
821 .rename_item("Digest", "CrcFastDigest")
822 .with_style(Both)
823 .with_language(C)
825 .with_cpp_compat(true)
827 .generate()
828 .map_err(|error| error.to_string())?
829 .write(&mut expected);
830
831 let header_content = String::from_utf8(expected).map_err(|error| error.to_string())?;
834
835 let regex = regex::Regex::new(r"\n{3,}").map_err(|error| error.to_string())?;
837 let cleaned_content = regex.replace_all(&header_content, "\n\n").to_string();
838
839 expected = cleaned_content.into_bytes();
841
842 let actual = read(HEADER).map_err(|error| error.to_string())?;
843
844 if expected != actual {
845 write(HEADER, expected).map_err(|error| error.to_string())?;
846 return Err(format!(
847 "{HEADER} is not up-to-date, commit the generated file and try again"
848 ));
849 }
850
851 Ok(())
852 }
853 }
854
855 #[test]
857 fn test_crc32_iso_hdlc_bindings() -> Result<(), String> {
858 build_bindgen("crc32_iso_hdlc", "src/bindings/crc32_iso_hdlc.rs")
859 }
860
861 #[test]
863 fn test_crc32_iscsi_bindings() -> Result<(), String> {
864 build_bindgen("crc32_iscsi", "src/bindings/crc32_iscsi.rs")
865 }
866
867 fn build_bindgen(name: &str, bindings_path: &str) -> Result<(), String> {
868 #[cfg(target_arch = "x86")]
876 {
877 eprintln!("Skipping test on x86 for {} to {}", name, bindings_path);
878
879 return Ok(());
880 }
881
882 #[cfg(target_os = "windows")]
884 {
885 eprintln!("Skipping test on Windows");
887
888 return Ok(());
889 }
890
891 #[cfg(not(any(target_arch = "x86", target_os = "windows")))]
892 {
893 let bindings = bindgen::Builder::default()
894 .header(format!("include/{name}.h"))
895 .allowlist_function("crc32_iscsi_impl")
896 .allowlist_function("get_iscsi_target")
897 .allowlist_var("ISCSI_TARGET")
898 .allowlist_function("crc32_iso_hdlc_impl")
899 .allowlist_function("get_iso_hdlc_target")
900 .allowlist_var("ISO_HDLC_TARGET")
901 .generate()
902 .expect("Unable to generate bindings");
903
904 let expected = bindings.to_string().into_bytes();
905
906 let actual = read(bindings_path).map_err(|error| error.to_string())?;
907
908 if expected != actual {
909 bindings
910 .write_to_file(bindings_path)
911 .expect("Couldn't write bindings to SRC!");
912
913 return Err(format!(
914 "{bindings_path} is not up-to-date, commit the generated file and try again"
915 ));
916 }
917
918 Ok(())
919 }
920 }
921}