1use std::{
2 fmt::{Debug, Display, Formatter},
3 marker::PhantomData,
4 ops::DerefMut,
5 str::FromStr,
6};
7
8use digest::{Digest, FixedOutput, HashMarker, Output, OutputSizeUser, Update};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use strum::{Display, EnumString, VariantArray, VariantNames};
11use winnow::{
12 ModalResult,
13 Parser,
14 ascii::dec_uint,
15 combinator::{alt, cut_err, eof, repeat, terminated},
16 error::{StrContext, StrContextValue},
17 token::one_of,
18};
19
20use crate::{
21 Error,
22 digests::{Blake2b512, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
23};
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub enum DigestEncoding {
28 Hex,
30 Dec,
32}
33
34pub trait DigestString: Digest {
37 const ENCODING: DigestEncoding;
39}
40
41impl DigestString for Blake2b512 {
42 const ENCODING: DigestEncoding = DigestEncoding::Hex;
43}
44
45impl DigestString for Md5 {
46 const ENCODING: DigestEncoding = DigestEncoding::Hex;
47}
48
49impl DigestString for Sha1 {
50 const ENCODING: DigestEncoding = DigestEncoding::Hex;
51}
52
53impl DigestString for Sha224 {
54 const ENCODING: DigestEncoding = DigestEncoding::Hex;
55}
56
57impl DigestString for Sha256 {
58 const ENCODING: DigestEncoding = DigestEncoding::Hex;
59}
60
61impl DigestString for Sha384 {
62 const ENCODING: DigestEncoding = DigestEncoding::Hex;
63}
64
65impl DigestString for Sha512 {
66 const ENCODING: DigestEncoding = DigestEncoding::Hex;
67}
68
69impl DigestString for Crc32Cksum {
70 const ENCODING: DigestEncoding = DigestEncoding::Dec;
71}
72
73pub type Blake2b512Checksum = Checksum<Blake2b512>;
77
78pub type Md5Checksum = Checksum<Md5>;
80
81pub type Sha1Checksum = Checksum<Sha1>;
83
84pub type Sha224Checksum = Checksum<Sha224>;
86
87pub type Sha256Checksum = Checksum<Sha256>;
89
90pub type Sha384Checksum = Checksum<Sha384>;
92
93pub type Sha512Checksum = Checksum<Sha512>;
95
96pub type Crc32CksumChecksum = Checksum<Crc32Cksum>;
98
99#[derive(
101 Clone,
102 Copy,
103 Debug,
104 Deserialize,
105 Display,
106 EnumString,
107 Eq,
108 Hash,
109 Ord,
110 PartialEq,
111 PartialOrd,
112 Serialize,
113 VariantNames,
114 VariantArray,
115)]
116pub enum ChecksumAlgorithm {
117 Blake2b512,
119 Md5,
121 Sha1,
123 Sha224,
125 Sha256,
127 Sha384,
129 Sha512,
131 Crc32Cksum,
133}
134
135impl ChecksumAlgorithm {
136 pub fn is_deprecated(&self) -> bool {
161 match self {
162 ChecksumAlgorithm::Md5 | ChecksumAlgorithm::Sha1 | ChecksumAlgorithm::Crc32Cksum => {
163 true
164 }
165 ChecksumAlgorithm::Blake2b512
166 | ChecksumAlgorithm::Sha224
167 | ChecksumAlgorithm::Sha256
168 | ChecksumAlgorithm::Sha384
169 | ChecksumAlgorithm::Sha512 => false,
170 }
171 }
172
173 pub fn non_deprecated_checksums(&self) -> Vec<ChecksumAlgorithm> {
175 <ChecksumAlgorithm as VariantArray>::VARIANTS
176 .iter()
177 .filter(|algo| !algo.is_deprecated())
178 .copied()
179 .collect::<Vec<ChecksumAlgorithm>>()
180 }
181}
182
183#[derive(Clone)]
255pub struct Checksum<D: Digest> {
256 digest: Vec<u8>,
257 _marker: PhantomData<D>,
258}
259
260impl<D: DigestString> Serialize for Checksum<D> {
261 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
266 where
267 S: Serializer,
268 {
269 serializer.serialize_str(&self.to_string())
270 }
271}
272
273impl<'de, D: DigestString> Deserialize<'de> for Checksum<D> {
274 fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
275 where
276 De: Deserializer<'de>,
277 {
278 let s = String::deserialize(deserializer)?;
279 Checksum::from_str(&s).map_err(serde::de::Error::custom)
280 }
281}
282
283impl<D: DigestString> Checksum<D> {
284 pub fn calculate_from(input: impl AsRef<[u8]>) -> Self {
296 let mut hasher = D::new();
297 hasher.update(input);
298
299 Checksum {
300 digest: hasher.finalize()[..].to_vec(),
301 _marker: PhantomData,
302 }
303 }
304
305 pub fn inner(&self) -> &[u8] {
307 &self.digest
308 }
309
310 pub fn parser(input: &mut &str) -> ModalResult<Self> {
320 #[inline]
324 fn hex_digit(input: &mut &str) -> ModalResult<u8> {
325 one_of(('0'..='9', 'a'..='f', 'A'..='F'))
326 .map(|d: char|
327 d.to_digit(16).unwrap().try_into().unwrap())
331 .context(StrContext::Expected(StrContextValue::Description(
332 "ASCII hex digit",
333 )))
334 .parse_next(input)
335 }
336
337 let hex_pair = (hex_digit, hex_digit).map(|(first, second)|
338 (first << 4) + second);
340
341 let digest_bytes = <D as Digest>::output_size();
343
344 let digest = match D::ENCODING {
345 DigestEncoding::Hex => {
346 let digest = cut_err(repeat(digest_bytes, hex_pair))
348 .context(StrContext::Label("hash digest"))
349 .context(StrContext::Expected(StrContextValue::Description(
350 "a hex hash digest with the appropriate length for the given algorithm.",
351 )))
352 .parse_next(input)?;
353
354 cut_err(eof)
355 .context(StrContext::Expected(StrContextValue::Description(
356 "end of checksum. Checksum is too long.",
357 )))
358 .parse_next(input)?;
359
360 digest
361 }
362 DigestEncoding::Dec => {
363 let digest_bits = digest_bytes * 8;
365
366 let max_value: u128 = if digest_bits >= 128 {
374 u128::MAX
378 } else {
379 (1u128 << digest_bits) - 1
380 };
381
382 cut_err(dec_uint::<_, u128, _>)
385 .verify(move |&v| v <= max_value)
386 .map(move |v | v.to_be_bytes()[16 - digest_bytes..].to_vec())
389 .context(StrContext::Label("hash digest"))
390 .context(StrContext::Expected(StrContextValue::Description(
391 "a decimal hash digest with the appropriate length for the given algorithm.",
392 )))
393 .parse_next(input)?
394 }
395 };
396
397 Ok(Self {
398 digest,
399 _marker: PhantomData,
400 })
401 }
402}
403
404impl<D: DigestString> FromStr for Checksum<D> {
405 type Err = Error;
406 fn from_str(s: &str) -> Result<Checksum<D>, Self::Err> {
425 Ok(Checksum::parser.parse(s)?)
426 }
427}
428
429impl<D: DigestString> Display for Checksum<D> {
430 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
431 match D::ENCODING {
432 DigestEncoding::Hex => {
433 write!(
434 fmt,
435 "{}",
436 self.digest
437 .iter()
438 .map(|x| format!("{x:02x?}"))
439 .collect::<Vec<String>>()
440 .join("")
441 )
442 }
443 DigestEncoding::Dec => {
444 let value = self
448 .digest
449 .iter()
450 .fold(0u128, |acc, &byte| (acc << 8) | byte as u128);
451 write!(fmt, "{}", value)
452 }
453 }
454 }
455}
456
457impl<D: DigestString> Debug for Checksum<D> {
460 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
461 Display::fmt(&self, f)
462 }
463}
464
465impl<D: Digest> PartialEq for Checksum<D> {
466 fn eq(&self, other: &Self) -> bool {
467 self.digest == other.digest
468 }
469}
470
471impl<D: Digest> Eq for Checksum<D> {}
472
473impl<D: Digest> Ord for Checksum<D> {
474 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
475 self.digest.cmp(&other.digest)
476 }
477}
478
479impl<D: Digest> PartialOrd for Checksum<D> {
480 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
481 Some(self.cmp(other))
482 }
483}
484
485#[derive(Clone, Debug, Deserialize, Serialize)]
490#[serde(tag = "type")]
491pub enum SkippableChecksum<D: DigestString + Clone> {
492 Skip,
494 #[serde(bound = "D: Digest + Clone")]
496 Checksum {
497 digest: Checksum<D>,
499 },
500}
501
502impl<D: DigestString + Clone> SkippableChecksum<D> {
503 pub fn is_skipped(&self) -> bool {
507 matches!(self, SkippableChecksum::Skip)
508 }
509
510 pub fn parser(input: &mut &str) -> ModalResult<Self> {
520 terminated(
521 alt((
522 "SKIP".value(Self::Skip),
523 Checksum::parser.map(|digest| Self::Checksum { digest }),
524 )),
525 cut_err(eof).context(StrContext::Expected(StrContextValue::Description(
526 "end of checksum.",
527 ))),
528 )
529 .parse_next(input)
530 }
531}
532
533impl<D: DigestString + Clone> FromStr for SkippableChecksum<D> {
534 type Err = Error;
535 fn from_str(s: &str) -> Result<SkippableChecksum<D>, Self::Err> {
556 Ok(Self::parser.parse(s)?)
557 }
558}
559
560impl<D: DigestString + Clone> Display for SkippableChecksum<D> {
561 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
562 let output = match self {
563 SkippableChecksum::Skip => "SKIP".to_string(),
564 SkippableChecksum::Checksum { digest } => digest.to_string(),
565 };
566 write!(fmt, "{output}",)
567 }
568}
569
570impl<D: DigestString + Clone> PartialEq for SkippableChecksum<D> {
571 fn eq(&self, other: &Self) -> bool {
572 match (self, other) {
573 (SkippableChecksum::Skip, SkippableChecksum::Skip) => true,
574 (SkippableChecksum::Skip, SkippableChecksum::Checksum { .. }) => false,
575 (SkippableChecksum::Checksum { .. }, SkippableChecksum::Skip) => false,
576 (
577 SkippableChecksum::Checksum { digest },
578 SkippableChecksum::Checksum {
579 digest: digest_other,
580 },
581 ) => digest == digest_other,
582 }
583 }
584}
585
586#[derive(Clone, Debug)]
591pub struct Crc32Cksum {
592 digest: crc_fast::Digest,
593 len: u64,
594}
595
596impl HashMarker for Crc32Cksum {}
597
598impl Default for Crc32Cksum {
599 fn default() -> Self {
600 Self {
601 digest: crc_fast::Digest::new(crc_fast::CrcAlgorithm::Crc32Cksum),
602 len: 0,
603 }
604 }
605}
606
607impl Update for Crc32Cksum {
608 fn update(&mut self, data: &[u8]) {
609 self.digest.update(data);
610 self.len += data.len() as u64;
611 }
612}
613
614impl OutputSizeUser for Crc32Cksum {
615 type OutputSize = digest::consts::U4;
616}
617
618impl FixedOutput for Crc32Cksum {
619 fn finalize_into(mut self, out: &mut Output<Self>) {
620 if self.len != 0 {
621 let len_bytes = self.len.to_be_bytes();
622
623 let start = len_bytes.iter().position(|&b| b != 0).unwrap_or(7);
625 self.digest.update(&len_bytes[start..]);
626 }
627
628 let crc = self.digest.finalize() as u32;
629 out.deref_mut().clone_from_slice(&crc.to_be_bytes());
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use insta::assert_snapshot;
636 use proptest::prelude::*;
637 use rstest::rstest;
638
639 use super::*;
640 use crate::configure_insta;
641
642 proptest! {
643 #![proptest_config(ProptestConfig::with_cases(1000))]
644
645 #[test]
646 fn valid_checksum_blake2b512_from_string(string in r"[a-f0-9]{128}") {
647 prop_assert_eq!(&string, &format!("{}", Blake2b512Checksum::from_str(&string).unwrap()));
648 }
649
650 #[test]
651 fn invalid_checksum_blake2b512_bigger_size(string in r"[a-f0-9]{129}") {
652 assert!(Blake2b512Checksum::from_str(&string).is_err());
653 }
654
655 #[test]
656 fn invalid_checksum_blake2b512_smaller_size(string in r"[a-f0-9]{127}") {
657 assert!(Blake2b512Checksum::from_str(&string).is_err());
658 }
659
660 #[test]
661 fn invalid_checksum_blake2b512_wrong_chars(string in r"[e-z0-9]{128}") {
662 assert!(Blake2b512Checksum::from_str(&string).is_err());
663 }
664
665 #[test]
666 fn valid_checksum_sha1_from_string(string in r"[a-f0-9]{40}") {
667 prop_assert_eq!(&string, &format!("{}", Sha1Checksum::from_str(&string).unwrap()));
668 }
669
670 #[test]
671 fn invalid_checksum_sha1_from_string_bigger_size(string in r"[a-f0-9]{41}") {
672 assert!(Sha1Checksum::from_str(&string).is_err());
673 }
674
675 #[test]
676 fn invalid_checksum_sha1_from_string_smaller_size(string in r"[a-f0-9]{39}") {
677 assert!(Sha1Checksum::from_str(&string).is_err());
678 }
679
680 #[test]
681 fn invalid_checksum_sha1_from_string_wrong_chars(string in r"[e-z0-9]{40}") {
682 assert!(Sha1Checksum::from_str(&string).is_err());
683 }
684
685 #[test]
686 fn valid_checksum_sha224_from_string(string in r"[a-f0-9]{56}") {
687 prop_assert_eq!(&string, &format!("{}", Sha224Checksum::from_str(&string).unwrap()));
688 }
689
690 #[test]
691 fn invalid_checksum_sha224_from_string_bigger_size(string in r"[a-f0-9]{57}") {
692 assert!(Sha224Checksum::from_str(&string).is_err());
693 }
694
695 #[test]
696 fn invalid_checksum_sha224_from_string_smaller_size(string in r"[a-f0-9]{55}") {
697 assert!(Sha224Checksum::from_str(&string).is_err());
698 }
699
700 #[test]
701 fn invalid_checksum_sha224_from_string_wrong_chars(string in r"[e-z0-9]{56}") {
702 assert!(Sha224Checksum::from_str(&string).is_err());
703 }
704
705 #[test]
706 fn valid_checksum_sha256_from_string(string in r"[a-f0-9]{64}") {
707 prop_assert_eq!(&string, &format!("{}", Sha256Checksum::from_str(&string).unwrap()));
708 }
709
710 #[test]
711 fn invalid_checksum_sha256_from_string_bigger_size(string in r"[a-f0-9]{65}") {
712 assert!(Sha256Checksum::from_str(&string).is_err());
713 }
714
715 #[test]
716 fn invalid_checksum_sha256_from_string_smaller_size(string in r"[a-f0-9]{63}") {
717 assert!(Sha256Checksum::from_str(&string).is_err());
718 }
719
720 #[test]
721 fn invalid_checksum_sha256_from_string_wrong_chars(string in r"[e-z0-9]{64}") {
722 assert!(Sha256Checksum::from_str(&string).is_err());
723 }
724
725 #[test]
726 fn valid_checksum_sha384_from_string(string in r"[a-f0-9]{96}") {
727 prop_assert_eq!(&string, &format!("{}", Sha384Checksum::from_str(&string).unwrap()));
728 }
729
730 #[test]
731 fn invalid_checksum_sha384_from_string_bigger_size(string in r"[a-f0-9]{97}") {
732 assert!(Sha384Checksum::from_str(&string).is_err());
733 }
734
735 #[test]
736 fn invalid_checksum_sha384_from_string_smaller_size(string in r"[a-f0-9]{95}") {
737 assert!(Sha384Checksum::from_str(&string).is_err());
738 }
739
740 #[test]
741 fn invalid_checksum_sha384_from_string_wrong_chars(string in r"[e-z0-9]{96}") {
742 assert!(Sha384Checksum::from_str(&string).is_err());
743 }
744
745 #[test]
746 fn valid_checksum_sha512_from_string(string in r"[a-f0-9]{128}") {
747 prop_assert_eq!(&string, &format!("{}", Sha512Checksum::from_str(&string).unwrap()));
748 }
749
750 #[test]
751 fn invalid_checksum_sha512_from_string_bigger_size(string in r"[a-f0-9]{129}") {
752 assert!(Sha512Checksum::from_str(&string).is_err());
753 }
754
755 #[test]
756 fn invalid_checksum_sha512_from_string_smaller_size(string in r"[a-f0-9]{127}") {
757 assert!(Sha512Checksum::from_str(&string).is_err());
758 }
759
760 #[test]
761 fn invalid_checksum_sha512_from_string_wrong_chars(string in r"[e-z0-9]{128}") {
762 assert!(Sha512Checksum::from_str(&string).is_err());
763 }
764
765 #[test]
766 fn valid_checksum_crc32cksum(sum in 0u32..=u32::MAX) {
767 let decimal_str = format!("{sum}");
768 prop_assert_eq!(
769 &decimal_str,
770 &format!("{}", Crc32CksumChecksum::from_str(decimal_str.as_str()).unwrap())
771 );
772 }
773
774 #[test]
775 fn invalid_checksum_crc32cksum_bigger_size(sum in (u32::MAX as u128)..=u128::MAX) {
776 let decimal_str = format!("{sum}");
777 assert!(Crc32CksumChecksum::from_str(decimal_str.as_str()).is_err());
778 }
779
780 #[test]
781 fn invalid_checksum_crc32cksum_wrong_chars(string in r"[a-f]{9}") {
782 assert!(Crc32CksumChecksum::from_str(&string).is_err());
783 }
784
785 #[test]
786 fn invalid_checksum_crc32cksum_negative(string in r"-[1-9]{9}") {
787 assert!(Crc32CksumChecksum::from_str(&string).is_err());
788 }
789 }
790
791 #[rstest]
792 fn checksum_blake2b512() {
793 let data = "foo\n";
794 let digest = vec![
795 210, 2, 215, 149, 29, 242, 196, 183, 17, 202, 68, 180, 188, 201, 215, 179, 99, 250, 66,
796 82, 18, 126, 5, 140, 26, 145, 14, 192, 91, 108, 208, 56, 215, 28, 194, 18, 33, 192, 49,
797 192, 53, 159, 153, 62, 116, 107, 7, 245, 150, 92, 248, 197, 195, 116, 106, 88, 51, 122,
798 217, 171, 101, 39, 142, 119,
799 ];
800 let hex_digest = "d202d7951df2c4b711ca44b4bcc9d7b363fa4252127e058c1a910ec05b6cd038d71cc21221c031c0359f993e746b07f5965cf8c5c3746a58337ad9ab65278e77";
801
802 let checksum = Blake2b512Checksum::calculate_from(data);
803 assert_eq!(digest, checksum.inner());
804 assert_eq!(format!("{}", &checksum), hex_digest,);
805
806 let checksum = Blake2b512Checksum::from_str(hex_digest).unwrap();
807 assert_eq!(digest, checksum.inner());
808 assert_eq!(format!("{}", &checksum), hex_digest,);
809 }
810
811 #[rstest]
812 fn checksum_sha1() {
813 let data = "foo\n";
814 let digest = vec![
815 241, 210, 210, 249, 36, 233, 134, 172, 134, 253, 247, 179, 108, 148, 188, 223, 50, 190,
816 236, 21,
817 ];
818 let hex_digest = "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15";
819
820 let checksum = Sha1Checksum::calculate_from(data);
821 assert_eq!(digest, checksum.inner());
822 assert_eq!(format!("{}", &checksum), hex_digest,);
823
824 let checksum = Sha1Checksum::from_str(hex_digest).unwrap();
825 assert_eq!(digest, checksum.inner());
826 assert_eq!(format!("{}", &checksum), hex_digest,);
827 }
828
829 #[rstest]
830 fn checksum_sha224() {
831 let data = "foo\n";
832 let digest = vec![
833 231, 213, 227, 110, 141, 71, 12, 62, 81, 3, 254, 221, 46, 79, 42, 165, 195, 10, 178,
834 127, 102, 41, 189, 195, 40, 111, 157, 210,
835 ];
836 let hex_digest = "e7d5e36e8d470c3e5103fedd2e4f2aa5c30ab27f6629bdc3286f9dd2";
837
838 let checksum = Sha224Checksum::calculate_from(data);
839 assert_eq!(digest, checksum.inner());
840 assert_eq!(format!("{}", &checksum), hex_digest,);
841
842 let checksum = Sha224Checksum::from_str(hex_digest).unwrap();
843 assert_eq!(digest, checksum.inner());
844 assert_eq!(format!("{}", &checksum), hex_digest,);
845 }
846
847 #[rstest]
848 fn checksum_sha256() {
849 let data = "foo\n";
850 let digest = vec![
851 181, 187, 157, 128, 20, 160, 249, 177, 214, 30, 33, 231, 150, 215, 141, 204, 223, 19,
852 82, 242, 60, 211, 40, 18, 244, 133, 11, 135, 138, 228, 148, 76,
853 ];
854 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
855
856 let checksum = Sha256Checksum::calculate_from(data);
857 assert_eq!(digest, checksum.inner());
858 assert_eq!(format!("{}", &checksum), hex_digest,);
859
860 let checksum = Sha256Checksum::from_str(hex_digest).unwrap();
861 assert_eq!(digest, checksum.inner());
862 assert_eq!(format!("{}", &checksum), hex_digest,);
863 }
864
865 #[rstest]
866 fn checksum_sha384() {
867 let data = "foo\n";
868 let digest = vec![
869 142, 255, 218, 191, 225, 68, 22, 33, 74, 37, 15, 147, 85, 5, 37, 11, 217, 145, 241, 6,
870 6, 93, 137, 157, 182, 225, 155, 220, 139, 246, 72, 243, 172, 15, 25, 53, 196, 246, 95,
871 232, 247, 152, 40, 155, 26, 13, 30, 6,
872 ];
873 let hex_digest = "8effdabfe14416214a250f935505250bd991f106065d899db6e19bdc8bf648f3ac0f1935c4f65fe8f798289b1a0d1e06";
874
875 let checksum = Sha384Checksum::calculate_from(data);
876 assert_eq!(digest, checksum.inner());
877 assert_eq!(format!("{}", &checksum), hex_digest,);
878
879 let checksum = Sha384Checksum::from_str(hex_digest).unwrap();
880 assert_eq!(digest, checksum.inner());
881 assert_eq!(format!("{}", &checksum), hex_digest,);
882 }
883
884 #[rstest]
885 fn checksum_sha512() {
886 let data = "foo\n";
887 let digest = vec![
888 12, 249, 24, 10, 118, 74, 186, 134, 58, 103, 182, 215, 47, 9, 24, 188, 19, 28, 103,
889 114, 100, 44, 178, 220, 229, 163, 79, 10, 112, 47, 148, 112, 221, 194, 191, 18, 92, 18,
890 25, 139, 25, 149, 194, 51, 195, 75, 74, 253, 52, 108, 84, 162, 51, 76, 53, 10, 148,
891 138, 81, 182, 232, 180, 230, 182,
892 ];
893 let hex_digest = "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6";
894
895 let checksum = Sha512Checksum::calculate_from(data);
896 assert_eq!(digest, checksum.inner());
897 assert_eq!(format!("{}", &checksum), hex_digest);
898
899 let checksum = Sha512Checksum::from_str(hex_digest).unwrap();
900 assert_eq!(digest, checksum.inner());
901 assert_eq!(format!("{}", &checksum), hex_digest);
902 }
903
904 #[rstest]
905 fn checksum_crc32cksum() {
906 let data = "foo\n";
907 let digest = 3915528286u32;
908 let digest_string = format!("{digest}");
909
910 let checksum = Crc32CksumChecksum::calculate_from(data);
911 assert_eq!(digest.to_be_bytes(), checksum.inner());
912 assert_eq!(format!("{}", &checksum), digest_string);
913
914 let checksum = Crc32CksumChecksum::from_str(digest_string.as_str()).unwrap();
915 assert_eq!(digest.to_be_bytes(), checksum.inner());
916 assert_eq!(format!("{}", &checksum), digest_string);
917 }
918
919 #[rstest]
920 #[case::non_hex_digits(
921 "0cf9180a764aba863a67b6d72f0918bc13gggggg642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6"
922 )]
923 #[case::incomplete_pair(" b ")]
924 #[case::incomplete_digest("0cf9180a764aba863a67b6d72f0918bca")]
925 #[case::whitespace(
926 "d2 02 d7 95 1d f2 c4 b7 11 ca 44 b4 bc c9 d7 b3 63 fa 42 52 12 7e 05 8c 1a 91 0e c0 5b 6c d0 38 d7 1c c2 12 21 c0 31 c0 35 9f 99 3e 74 6b 07 f5 96 5c f8 c5 c3 74 6a 58 33 7a d9 ab 65 27 8e 77"
927 )]
928 fn checksum_parse_error(#[case] input: &str) {
929 let Err(Error::ParseError(err_msg)) = Sha512Checksum::from_str(input) else {
930 panic!("'{input}' erroneously parsed as Sha512Checksum")
931 };
932
933 let (test_name, _guard) = configure_insta();
934 assert_snapshot!(test_name, err_msg.to_string());
935 }
936
937 #[rstest]
938 fn skippable_checksum_sha256() {
939 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
940 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
941 assert_eq!(format!("{}", &checksum), hex_digest);
942 }
943
944 #[rstest]
945 fn skippable_checksum_skip() {
946 let hex_digest = "SKIP";
947 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
948
949 assert_eq!(SkippableChecksum::Skip, checksum);
950 assert_eq!(format!("{}", &checksum), hex_digest);
951 }
952}