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 proptest::prelude::*;
636 use rstest::rstest;
637
638 use super::*;
639
640 proptest! {
641 #![proptest_config(ProptestConfig::with_cases(1000))]
642
643 #[test]
644 fn valid_checksum_blake2b512_from_string(string in r"[a-f0-9]{128}") {
645 prop_assert_eq!(&string, &format!("{}", Blake2b512Checksum::from_str(&string).unwrap()));
646 }
647
648 #[test]
649 fn invalid_checksum_blake2b512_bigger_size(string in r"[a-f0-9]{129}") {
650 assert!(Blake2b512Checksum::from_str(&string).is_err());
651 }
652
653 #[test]
654 fn invalid_checksum_blake2b512_smaller_size(string in r"[a-f0-9]{127}") {
655 assert!(Blake2b512Checksum::from_str(&string).is_err());
656 }
657
658 #[test]
659 fn invalid_checksum_blake2b512_wrong_chars(string in r"[e-z0-9]{128}") {
660 assert!(Blake2b512Checksum::from_str(&string).is_err());
661 }
662
663 #[test]
664 fn valid_checksum_sha1_from_string(string in r"[a-f0-9]{40}") {
665 prop_assert_eq!(&string, &format!("{}", Sha1Checksum::from_str(&string).unwrap()));
666 }
667
668 #[test]
669 fn invalid_checksum_sha1_from_string_bigger_size(string in r"[a-f0-9]{41}") {
670 assert!(Sha1Checksum::from_str(&string).is_err());
671 }
672
673 #[test]
674 fn invalid_checksum_sha1_from_string_smaller_size(string in r"[a-f0-9]{39}") {
675 assert!(Sha1Checksum::from_str(&string).is_err());
676 }
677
678 #[test]
679 fn invalid_checksum_sha1_from_string_wrong_chars(string in r"[e-z0-9]{40}") {
680 assert!(Sha1Checksum::from_str(&string).is_err());
681 }
682
683 #[test]
684 fn valid_checksum_sha224_from_string(string in r"[a-f0-9]{56}") {
685 prop_assert_eq!(&string, &format!("{}", Sha224Checksum::from_str(&string).unwrap()));
686 }
687
688 #[test]
689 fn invalid_checksum_sha224_from_string_bigger_size(string in r"[a-f0-9]{57}") {
690 assert!(Sha224Checksum::from_str(&string).is_err());
691 }
692
693 #[test]
694 fn invalid_checksum_sha224_from_string_smaller_size(string in r"[a-f0-9]{55}") {
695 assert!(Sha224Checksum::from_str(&string).is_err());
696 }
697
698 #[test]
699 fn invalid_checksum_sha224_from_string_wrong_chars(string in r"[e-z0-9]{56}") {
700 assert!(Sha224Checksum::from_str(&string).is_err());
701 }
702
703 #[test]
704 fn valid_checksum_sha256_from_string(string in r"[a-f0-9]{64}") {
705 prop_assert_eq!(&string, &format!("{}", Sha256Checksum::from_str(&string).unwrap()));
706 }
707
708 #[test]
709 fn invalid_checksum_sha256_from_string_bigger_size(string in r"[a-f0-9]{65}") {
710 assert!(Sha256Checksum::from_str(&string).is_err());
711 }
712
713 #[test]
714 fn invalid_checksum_sha256_from_string_smaller_size(string in r"[a-f0-9]{63}") {
715 assert!(Sha256Checksum::from_str(&string).is_err());
716 }
717
718 #[test]
719 fn invalid_checksum_sha256_from_string_wrong_chars(string in r"[e-z0-9]{64}") {
720 assert!(Sha256Checksum::from_str(&string).is_err());
721 }
722
723 #[test]
724 fn valid_checksum_sha384_from_string(string in r"[a-f0-9]{96}") {
725 prop_assert_eq!(&string, &format!("{}", Sha384Checksum::from_str(&string).unwrap()));
726 }
727
728 #[test]
729 fn invalid_checksum_sha384_from_string_bigger_size(string in r"[a-f0-9]{97}") {
730 assert!(Sha384Checksum::from_str(&string).is_err());
731 }
732
733 #[test]
734 fn invalid_checksum_sha384_from_string_smaller_size(string in r"[a-f0-9]{95}") {
735 assert!(Sha384Checksum::from_str(&string).is_err());
736 }
737
738 #[test]
739 fn invalid_checksum_sha384_from_string_wrong_chars(string in r"[e-z0-9]{96}") {
740 assert!(Sha384Checksum::from_str(&string).is_err());
741 }
742
743 #[test]
744 fn valid_checksum_sha512_from_string(string in r"[a-f0-9]{128}") {
745 prop_assert_eq!(&string, &format!("{}", Sha512Checksum::from_str(&string).unwrap()));
746 }
747
748 #[test]
749 fn invalid_checksum_sha512_from_string_bigger_size(string in r"[a-f0-9]{129}") {
750 assert!(Sha512Checksum::from_str(&string).is_err());
751 }
752
753 #[test]
754 fn invalid_checksum_sha512_from_string_smaller_size(string in r"[a-f0-9]{127}") {
755 assert!(Sha512Checksum::from_str(&string).is_err());
756 }
757
758 #[test]
759 fn invalid_checksum_sha512_from_string_wrong_chars(string in r"[e-z0-9]{128}") {
760 assert!(Sha512Checksum::from_str(&string).is_err());
761 }
762
763 #[test]
764 fn valid_checksum_crc32cksum(sum in 0u32..=u32::MAX) {
765 let decimal_str = format!("{sum}");
766 prop_assert_eq!(
767 &decimal_str,
768 &format!("{}", Crc32CksumChecksum::from_str(decimal_str.as_str()).unwrap())
769 );
770 }
771
772 #[test]
773 fn invalid_checksum_crc32cksum_bigger_size(sum in (u32::MAX as u128)..=u128::MAX) {
774 let decimal_str = format!("{sum}");
775 assert!(Crc32CksumChecksum::from_str(decimal_str.as_str()).is_err());
776 }
777
778 #[test]
779 fn invalid_checksum_crc32cksum_wrong_chars(string in r"[a-f]{9}") {
780 assert!(Crc32CksumChecksum::from_str(&string).is_err());
781 }
782
783 #[test]
784 fn invalid_checksum_crc32cksum_negative(string in r"-[1-9]{9}") {
785 assert!(Crc32CksumChecksum::from_str(&string).is_err());
786 }
787 }
788
789 #[rstest]
790 fn checksum_blake2b512() {
791 let data = "foo\n";
792 let digest = vec![
793 210, 2, 215, 149, 29, 242, 196, 183, 17, 202, 68, 180, 188, 201, 215, 179, 99, 250, 66,
794 82, 18, 126, 5, 140, 26, 145, 14, 192, 91, 108, 208, 56, 215, 28, 194, 18, 33, 192, 49,
795 192, 53, 159, 153, 62, 116, 107, 7, 245, 150, 92, 248, 197, 195, 116, 106, 88, 51, 122,
796 217, 171, 101, 39, 142, 119,
797 ];
798 let hex_digest = "d202d7951df2c4b711ca44b4bcc9d7b363fa4252127e058c1a910ec05b6cd038d71cc21221c031c0359f993e746b07f5965cf8c5c3746a58337ad9ab65278e77";
799
800 let checksum = Blake2b512Checksum::calculate_from(data);
801 assert_eq!(digest, checksum.inner());
802 assert_eq!(format!("{}", &checksum), hex_digest,);
803
804 let checksum = Blake2b512Checksum::from_str(hex_digest).unwrap();
805 assert_eq!(digest, checksum.inner());
806 assert_eq!(format!("{}", &checksum), hex_digest,);
807 }
808
809 #[rstest]
810 fn checksum_sha1() {
811 let data = "foo\n";
812 let digest = vec![
813 241, 210, 210, 249, 36, 233, 134, 172, 134, 253, 247, 179, 108, 148, 188, 223, 50, 190,
814 236, 21,
815 ];
816 let hex_digest = "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15";
817
818 let checksum = Sha1Checksum::calculate_from(data);
819 assert_eq!(digest, checksum.inner());
820 assert_eq!(format!("{}", &checksum), hex_digest,);
821
822 let checksum = Sha1Checksum::from_str(hex_digest).unwrap();
823 assert_eq!(digest, checksum.inner());
824 assert_eq!(format!("{}", &checksum), hex_digest,);
825 }
826
827 #[rstest]
828 fn checksum_sha224() {
829 let data = "foo\n";
830 let digest = vec![
831 231, 213, 227, 110, 141, 71, 12, 62, 81, 3, 254, 221, 46, 79, 42, 165, 195, 10, 178,
832 127, 102, 41, 189, 195, 40, 111, 157, 210,
833 ];
834 let hex_digest = "e7d5e36e8d470c3e5103fedd2e4f2aa5c30ab27f6629bdc3286f9dd2";
835
836 let checksum = Sha224Checksum::calculate_from(data);
837 assert_eq!(digest, checksum.inner());
838 assert_eq!(format!("{}", &checksum), hex_digest,);
839
840 let checksum = Sha224Checksum::from_str(hex_digest).unwrap();
841 assert_eq!(digest, checksum.inner());
842 assert_eq!(format!("{}", &checksum), hex_digest,);
843 }
844
845 #[rstest]
846 fn checksum_sha256() {
847 let data = "foo\n";
848 let digest = vec![
849 181, 187, 157, 128, 20, 160, 249, 177, 214, 30, 33, 231, 150, 215, 141, 204, 223, 19,
850 82, 242, 60, 211, 40, 18, 244, 133, 11, 135, 138, 228, 148, 76,
851 ];
852 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
853
854 let checksum = Sha256Checksum::calculate_from(data);
855 assert_eq!(digest, checksum.inner());
856 assert_eq!(format!("{}", &checksum), hex_digest,);
857
858 let checksum = Sha256Checksum::from_str(hex_digest).unwrap();
859 assert_eq!(digest, checksum.inner());
860 assert_eq!(format!("{}", &checksum), hex_digest,);
861 }
862
863 #[rstest]
864 fn checksum_sha384() {
865 let data = "foo\n";
866 let digest = vec![
867 142, 255, 218, 191, 225, 68, 22, 33, 74, 37, 15, 147, 85, 5, 37, 11, 217, 145, 241, 6,
868 6, 93, 137, 157, 182, 225, 155, 220, 139, 246, 72, 243, 172, 15, 25, 53, 196, 246, 95,
869 232, 247, 152, 40, 155, 26, 13, 30, 6,
870 ];
871 let hex_digest = "8effdabfe14416214a250f935505250bd991f106065d899db6e19bdc8bf648f3ac0f1935c4f65fe8f798289b1a0d1e06";
872
873 let checksum = Sha384Checksum::calculate_from(data);
874 assert_eq!(digest, checksum.inner());
875 assert_eq!(format!("{}", &checksum), hex_digest,);
876
877 let checksum = Sha384Checksum::from_str(hex_digest).unwrap();
878 assert_eq!(digest, checksum.inner());
879 assert_eq!(format!("{}", &checksum), hex_digest,);
880 }
881
882 #[rstest]
883 fn checksum_sha512() {
884 let data = "foo\n";
885 let digest = vec![
886 12, 249, 24, 10, 118, 74, 186, 134, 58, 103, 182, 215, 47, 9, 24, 188, 19, 28, 103,
887 114, 100, 44, 178, 220, 229, 163, 79, 10, 112, 47, 148, 112, 221, 194, 191, 18, 92, 18,
888 25, 139, 25, 149, 194, 51, 195, 75, 74, 253, 52, 108, 84, 162, 51, 76, 53, 10, 148,
889 138, 81, 182, 232, 180, 230, 182,
890 ];
891 let hex_digest = "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6";
892
893 let checksum = Sha512Checksum::calculate_from(data);
894 assert_eq!(digest, checksum.inner());
895 assert_eq!(format!("{}", &checksum), hex_digest);
896
897 let checksum = Sha512Checksum::from_str(hex_digest).unwrap();
898 assert_eq!(digest, checksum.inner());
899 assert_eq!(format!("{}", &checksum), hex_digest);
900 }
901
902 #[rstest]
903 fn checksum_crc32cksum() {
904 let data = "foo\n";
905 let digest = 3915528286u32;
906 let digest_string = format!("{digest}");
907
908 let checksum = Crc32CksumChecksum::calculate_from(data);
909 assert_eq!(digest.to_be_bytes(), checksum.inner());
910 assert_eq!(format!("{}", &checksum), digest_string);
911
912 let checksum = Crc32CksumChecksum::from_str(digest_string.as_str()).unwrap();
913 assert_eq!(digest.to_be_bytes(), checksum.inner());
914 assert_eq!(format!("{}", &checksum), digest_string);
915 }
916
917 #[rstest]
918 #[case::non_hex_digits(
919 "0cf9180a764aba863a67b6d72f0918bc13gggggg642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6",
920 "expected ASCII hex digit"
921 )]
922 #[case::incomplete_pair(" b ", "expected ASCII hex digit")]
923 #[case::incomplete_digest("0cf9180a764aba863a67b6d72f0918bca", "expected ASCII hex digit")]
924 #[case::whitespace(
925 "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",
926 "expected ASCII hex digit"
927 )]
928 fn checksum_parse_error(#[case] input: &str, #[case] err_snippet: &str) {
929 let Err(Error::ParseError(err_msg)) = Sha512Checksum::from_str(input) else {
930 panic!("'{input}' did not fail to parse as expected")
931 };
932 assert!(
933 err_msg.contains(err_snippet),
934 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
935 );
936 }
937
938 #[rstest]
939 fn skippable_checksum_sha256() {
940 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
941 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
942 assert_eq!(format!("{}", &checksum), hex_digest);
943 }
944
945 #[rstest]
946 fn skippable_checksum_skip() {
947 let hex_digest = "SKIP";
948 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
949
950 assert_eq!(SkippableChecksum::Skip, checksum);
951 assert_eq!(format!("{}", &checksum), hex_digest);
952 }
953}