1use std::{
2 fmt::{Debug, Display, Formatter},
3 marker::PhantomData,
4 str::FromStr,
5};
6
7use alpm_parsers::traits::AlpmParser;
8use digest::{Digest, Output};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use strum::{Display, EnumString, VariantArray, VariantNames};
12use winnow::{
13 ModalResult,
14 Parser,
15 ascii::dec_uint,
16 combinator::{alt, cut_err, not, repeat},
17 error::{StrContext, StrContextValue},
18 token::one_of,
19};
20
21use crate::{
22 Error,
23 digests::{Blake2b512, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
24};
25
26mod crc32;
27
28pub use crc32::Crc32Cksum;
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum DigestEncoding {
33 Hex,
35 Dec,
37}
38
39pub trait DigestString: Digest {
42 const ENCODING: DigestEncoding;
44}
45
46impl DigestString for Blake2b512 {
47 const ENCODING: DigestEncoding = DigestEncoding::Hex;
48}
49
50impl DigestString for Md5 {
51 const ENCODING: DigestEncoding = DigestEncoding::Hex;
52}
53
54impl DigestString for Sha1 {
55 const ENCODING: DigestEncoding = DigestEncoding::Hex;
56}
57
58impl DigestString for Sha224 {
59 const ENCODING: DigestEncoding = DigestEncoding::Hex;
60}
61
62impl DigestString for Sha256 {
63 const ENCODING: DigestEncoding = DigestEncoding::Hex;
64}
65
66impl DigestString for Sha384 {
67 const ENCODING: DigestEncoding = DigestEncoding::Hex;
68}
69
70impl DigestString for Sha512 {
71 const ENCODING: DigestEncoding = DigestEncoding::Hex;
72}
73
74impl DigestString for Crc32Cksum {
75 const ENCODING: DigestEncoding = DigestEncoding::Dec;
76}
77
78pub type Blake2b512Checksum = Checksum<Blake2b512>;
82
83pub type Md5Checksum = Checksum<Md5>;
85
86pub type Sha1Checksum = Checksum<Sha1>;
88
89pub type Sha224Checksum = Checksum<Sha224>;
91
92pub type Sha256Checksum = Checksum<Sha256>;
94
95pub type Sha384Checksum = Checksum<Sha384>;
97
98pub type Sha512Checksum = Checksum<Sha512>;
100
101pub type Crc32CksumChecksum = Checksum<Crc32Cksum>;
103
104#[derive(
106 Clone,
107 Copy,
108 Debug,
109 Display,
110 EnumString,
111 Eq,
112 Hash,
113 Ord,
114 PartialEq,
115 PartialOrd,
116 VariantNames,
117 VariantArray,
118)]
119#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
120pub enum ChecksumAlgorithm {
121 Blake2b512,
123 Md5,
125 Sha1,
127 Sha224,
129 Sha256,
131 Sha384,
133 Sha512,
135 Crc32Cksum,
137}
138
139impl ChecksumAlgorithm {
140 pub fn is_deprecated(&self) -> bool {
165 match self {
166 ChecksumAlgorithm::Md5 | ChecksumAlgorithm::Sha1 | ChecksumAlgorithm::Crc32Cksum => {
167 true
168 }
169 ChecksumAlgorithm::Blake2b512
170 | ChecksumAlgorithm::Sha224
171 | ChecksumAlgorithm::Sha256
172 | ChecksumAlgorithm::Sha384
173 | ChecksumAlgorithm::Sha512 => false,
174 }
175 }
176
177 pub fn non_deprecated_checksums(&self) -> Vec<ChecksumAlgorithm> {
179 <ChecksumAlgorithm as VariantArray>::VARIANTS
180 .iter()
181 .filter(|algo| !algo.is_deprecated())
182 .copied()
183 .collect::<Vec<ChecksumAlgorithm>>()
184 }
185}
186
187#[derive(Clone)]
262pub struct Checksum<D: Digest> {
263 digest: Vec<u8>,
264 _marker: PhantomData<D>,
265}
266
267impl<D: Digest> From<Output<D>> for Checksum<D> {
268 fn from(digest: Output<D>) -> Self {
287 Self {
288 digest: digest.to_vec(),
289 _marker: PhantomData,
290 }
291 }
292}
293
294#[cfg(feature = "serde")]
295impl<D: DigestString> Serialize for Checksum<D> {
296 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
301 where
302 S: Serializer,
303 {
304 serializer.serialize_str(&self.to_string())
305 }
306}
307
308#[cfg(feature = "serde")]
309impl<'de, D: DigestString> Deserialize<'de> for Checksum<D> {
310 fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
311 where
312 De: Deserializer<'de>,
313 {
314 let s = String::deserialize(deserializer)?;
315 Checksum::from_str(&s).map_err(serde::de::Error::custom)
316 }
317}
318
319impl<D: DigestString> Checksum<D> {
320 pub fn calculate_from(input: impl AsRef<[u8]>) -> Self {
332 let mut hasher = D::new();
333 hasher.update(input);
334
335 Checksum {
336 digest: hasher.finalize()[..].to_vec(),
337 _marker: PhantomData,
338 }
339 }
340
341 pub fn inner(&self) -> &[u8] {
343 &self.digest
344 }
345
346 pub fn calculate_from_reader(mut reader: impl std::io::Read) -> Result<Self, std::io::Error> {
372 struct HashWriter<D: Digest>(D);
377
378 impl<D: Digest> std::io::Write for HashWriter<D> {
379 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
380 self.0.update(buf);
381 Ok(buf.len())
382 }
383
384 fn flush(&mut self) -> std::io::Result<()> {
386 Ok(())
387 }
388 }
389
390 let mut writer = HashWriter(D::new());
391 std::io::copy(&mut reader, &mut writer)?;
392
393 Ok(writer.0.finalize().into())
394 }
395}
396
397impl<D: DigestString> AlpmParser for Checksum<D> {
398 fn parser(input: &mut &str) -> ModalResult<Self> {
407 #[inline]
411 fn hex_digit(input: &mut &str) -> ModalResult<u8> {
412 one_of(('0'..='9', 'a'..='f', 'A'..='F'))
413 .map(|d: char|
414 d.to_digit(16).unwrap().try_into().unwrap())
418 .context(StrContext::Expected(StrContextValue::Description(
419 "ASCII hex digit",
420 )))
421 .parse_next(input)
422 }
423
424 let hex_pair = (hex_digit, hex_digit).map(|(first, second)|
425 (first << 4) + second);
427
428 let digest_bytes = <D as Digest>::output_size();
430
431 let digest = match D::ENCODING {
432 DigestEncoding::Hex => {
433 let digest = repeat(digest_bytes, hex_pair)
435 .context(StrContext::Label("hash digest"))
436 .context(StrContext::Expected(StrContextValue::Description(
437 "a hex hash digest with the appropriate length for the given algorithm.",
438 )))
439 .parse_next(input)?;
440
441 cut_err(not(hex_digit))
444 .context(StrContext::Expected(StrContextValue::Description(
445 "end of checksum (checksum is too long).",
446 )))
447 .parse_next(input)?;
448
449 digest
450 }
451 DigestEncoding::Dec => {
452 let digest_bits = digest_bytes * 8;
454
455 let max_value: u128 = if digest_bits >= 128 {
463 u128::MAX
467 } else {
468 (1u128 << digest_bits) - 1
469 };
470
471 dec_uint::<_, u128, _>
474 .verify(move |&v| v <= max_value)
475 .map(move |v | v.to_be_bytes()[16 - digest_bytes..].to_vec())
478 .context(StrContext::Label("hash digest"))
479 .context(StrContext::Expected(StrContextValue::Description(
480 "a decimal hash digest with the appropriate length for the given algorithm.",
481 )))
482 .parse_next(input)?
483 }
484 };
485
486 Ok(Self {
487 digest,
488 _marker: PhantomData,
489 })
490 }
491
492 fn delimiter_error_context<'a, O, P>(
493 parser: P,
494 ) -> impl Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>
495 where
496 P: Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>,
497 {
498 parser
499 .context(StrContext::Label("character in checksum"))
500 .context(StrContext::Expected(StrContextValue::Description(
501 "a string consisting of solely decimal or hexadecimal chars.",
502 )))
503 }
504}
505
506impl<D: DigestString> FromStr for Checksum<D> {
507 type Err = Error;
508 fn from_str(s: &str) -> Result<Checksum<D>, Self::Err> {
527 Ok(Checksum::parser.parse(s)?)
528 }
529}
530
531impl<D: DigestString> Display for Checksum<D> {
532 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
533 match D::ENCODING {
534 DigestEncoding::Hex => {
535 write!(
536 fmt,
537 "{}",
538 self.digest
539 .iter()
540 .map(|x| format!("{x:02x?}"))
541 .collect::<Vec<String>>()
542 .join("")
543 )
544 }
545 DigestEncoding::Dec => {
546 let value = self
550 .digest
551 .iter()
552 .fold(0u128, |acc, &byte| (acc << 8) | byte as u128);
553 write!(fmt, "{}", value)
554 }
555 }
556 }
557}
558
559impl<D: DigestString> Debug for Checksum<D> {
562 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
563 Display::fmt(&self, f)
564 }
565}
566
567impl<D: Digest> PartialEq for Checksum<D> {
568 fn eq(&self, other: &Self) -> bool {
569 self.digest == other.digest
570 }
571}
572
573impl<D: Digest> Eq for Checksum<D> {}
574
575impl<D: Digest> Ord for Checksum<D> {
576 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
577 self.digest.cmp(&other.digest)
578 }
579}
580
581impl<D: Digest> PartialOrd for Checksum<D> {
582 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
583 Some(self.cmp(other))
584 }
585}
586
587#[derive(Clone, Debug)]
592#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
593#[cfg_attr(feature = "serde", serde(tag = "type"))]
594pub enum SkippableChecksum<D: DigestString + Clone> {
595 Skip,
597 #[cfg_attr(feature = "serde", serde(bound = "D: Digest + Clone"))]
599 Checksum {
600 digest: Checksum<D>,
602 },
603}
604
605impl<D: DigestString + Clone> SkippableChecksum<D> {
606 pub fn is_skipped(&self) -> bool {
610 matches!(self, SkippableChecksum::Skip)
611 }
612}
613
614impl<D: DigestString + Clone> AlpmParser for SkippableChecksum<D> {
615 fn parser(input: &mut &str) -> ModalResult<Self> {
624 alt((
625 "SKIP".value(Self::Skip),
626 Checksum::parser.map(|digest| Self::Checksum { digest }),
627 ))
628 .context(StrContext::Expected(StrContextValue::Description(
629 "a hash digest with the appropriate length for the given algorithm, or an uppercase 'SKIP'",
630 )))
631 .parse_next(input)
632 }
633
634 fn delimiter_error_context<'a, O, P>(
635 parser: P,
636 ) -> impl Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>
637 where
638 P: Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>,
639 {
640 parser.context(StrContext::Expected(StrContextValue::Description(
641 "end of checksum.",
642 )))
643 }
644}
645
646impl<D: DigestString + Clone> FromStr for SkippableChecksum<D> {
647 type Err = Error;
648 fn from_str(s: &str) -> Result<SkippableChecksum<D>, Self::Err> {
669 Ok(Self::parser.parse(s)?)
670 }
671}
672
673impl<D: DigestString + Clone> Display for SkippableChecksum<D> {
674 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
675 let output = match self {
676 SkippableChecksum::Skip => "SKIP".to_string(),
677 SkippableChecksum::Checksum { digest } => digest.to_string(),
678 };
679 write!(fmt, "{output}",)
680 }
681}
682
683impl<D: DigestString + Clone> PartialEq for SkippableChecksum<D> {
684 fn eq(&self, other: &Self) -> bool {
685 match (self, other) {
686 (SkippableChecksum::Skip, SkippableChecksum::Skip) => true,
687 (SkippableChecksum::Skip, SkippableChecksum::Checksum { .. }) => false,
688 (SkippableChecksum::Checksum { .. }, SkippableChecksum::Skip) => false,
689 (
690 SkippableChecksum::Checksum { digest },
691 SkippableChecksum::Checksum {
692 digest: digest_other,
693 },
694 ) => digest == digest_other,
695 }
696 }
697}
698
699#[cfg(test)]
700mod tests {
701 use insta::assert_snapshot;
702 use proptest::prelude::*;
703 use rstest::rstest;
704
705 use super::*;
706 use crate::configure_insta;
707
708 proptest! {
709 #![proptest_config(ProptestConfig::with_cases(1000))]
710
711 #[test]
712 fn valid_checksum_blake2b512_from_string(string in r"[a-f0-9]{128}") {
713 prop_assert_eq!(&string, &format!("{}", Blake2b512Checksum::from_str(&string).unwrap()));
714 }
715
716 #[test]
717 fn invalid_checksum_blake2b512_bigger_size(string in r"[a-f0-9]{129}") {
718 assert!(Blake2b512Checksum::from_str(&string).is_err());
719 }
720
721 #[test]
722 fn invalid_checksum_blake2b512_smaller_size(string in r"[a-f0-9]{127}") {
723 assert!(Blake2b512Checksum::from_str(&string).is_err());
724 }
725
726 #[test]
727 fn invalid_checksum_blake2b512_wrong_chars(string in r"[e-z0-9]{128}") {
728 assert!(Blake2b512Checksum::from_str(&string).is_err());
729 }
730
731 #[test]
732 fn valid_checksum_sha1_from_string(string in r"[a-f0-9]{40}") {
733 prop_assert_eq!(&string, &format!("{}", Sha1Checksum::from_str(&string).unwrap()));
734 }
735
736 #[test]
737 fn invalid_checksum_sha1_from_string_bigger_size(string in r"[a-f0-9]{41}") {
738 assert!(Sha1Checksum::from_str(&string).is_err());
739 }
740
741 #[test]
742 fn invalid_checksum_sha1_from_string_smaller_size(string in r"[a-f0-9]{39}") {
743 assert!(Sha1Checksum::from_str(&string).is_err());
744 }
745
746 #[test]
747 fn invalid_checksum_sha1_from_string_wrong_chars(string in r"[e-z0-9]{40}") {
748 assert!(Sha1Checksum::from_str(&string).is_err());
749 }
750
751 #[test]
752 fn valid_checksum_sha224_from_string(string in r"[a-f0-9]{56}") {
753 prop_assert_eq!(&string, &format!("{}", Sha224Checksum::from_str(&string).unwrap()));
754 }
755
756 #[test]
757 fn invalid_checksum_sha224_from_string_bigger_size(string in r"[a-f0-9]{57}") {
758 assert!(Sha224Checksum::from_str(&string).is_err());
759 }
760
761 #[test]
762 fn invalid_checksum_sha224_from_string_smaller_size(string in r"[a-f0-9]{55}") {
763 assert!(Sha224Checksum::from_str(&string).is_err());
764 }
765
766 #[test]
767 fn invalid_checksum_sha224_from_string_wrong_chars(string in r"[e-z0-9]{56}") {
768 assert!(Sha224Checksum::from_str(&string).is_err());
769 }
770
771 #[test]
772 fn valid_checksum_sha256_from_string(string in r"[a-f0-9]{64}") {
773 prop_assert_eq!(&string, &format!("{}", Sha256Checksum::from_str(&string).unwrap()));
774 }
775
776 #[test]
777 fn invalid_checksum_sha256_from_string_bigger_size(string in r"[a-f0-9]{65}") {
778 assert!(Sha256Checksum::from_str(&string).is_err());
779 }
780
781 #[test]
782 fn invalid_checksum_sha256_from_string_smaller_size(string in r"[a-f0-9]{63}") {
783 assert!(Sha256Checksum::from_str(&string).is_err());
784 }
785
786 #[test]
787 fn invalid_checksum_sha256_from_string_wrong_chars(string in r"[e-z0-9]{64}") {
788 assert!(Sha256Checksum::from_str(&string).is_err());
789 }
790
791 #[test]
792 fn valid_checksum_sha384_from_string(string in r"[a-f0-9]{96}") {
793 prop_assert_eq!(&string, &format!("{}", Sha384Checksum::from_str(&string).unwrap()));
794 }
795
796 #[test]
797 fn invalid_checksum_sha384_from_string_bigger_size(string in r"[a-f0-9]{97}") {
798 assert!(Sha384Checksum::from_str(&string).is_err());
799 }
800
801 #[test]
802 fn invalid_checksum_sha384_from_string_smaller_size(string in r"[a-f0-9]{95}") {
803 assert!(Sha384Checksum::from_str(&string).is_err());
804 }
805
806 #[test]
807 fn invalid_checksum_sha384_from_string_wrong_chars(string in r"[e-z0-9]{96}") {
808 assert!(Sha384Checksum::from_str(&string).is_err());
809 }
810
811 #[test]
812 fn valid_checksum_sha512_from_string(string in r"[a-f0-9]{128}") {
813 prop_assert_eq!(&string, &format!("{}", Sha512Checksum::from_str(&string).unwrap()));
814 }
815
816 #[test]
817 fn invalid_checksum_sha512_from_string_bigger_size(string in r"[a-f0-9]{129}") {
818 assert!(Sha512Checksum::from_str(&string).is_err());
819 }
820
821 #[test]
822 fn invalid_checksum_sha512_from_string_smaller_size(string in r"[a-f0-9]{127}") {
823 assert!(Sha512Checksum::from_str(&string).is_err());
824 }
825
826 #[test]
827 fn invalid_checksum_sha512_from_string_wrong_chars(string in r"[e-z0-9]{128}") {
828 assert!(Sha512Checksum::from_str(&string).is_err());
829 }
830
831 #[test]
832 fn valid_checksum_crc32cksum(sum in 0u32..=u32::MAX) {
833 let decimal_str = format!("{sum}");
834 prop_assert_eq!(
835 &decimal_str,
836 &format!("{}", Crc32CksumChecksum::from_str(decimal_str.as_str()).unwrap())
837 );
838 }
839
840 #[test]
841 fn invalid_checksum_crc32cksum_bigger_size(sum in (u32::MAX as u128)..=u128::MAX) {
842 let decimal_str = format!("{sum}");
843 assert!(Crc32CksumChecksum::from_str(decimal_str.as_str()).is_err());
844 }
845
846 #[test]
847 fn invalid_checksum_crc32cksum_wrong_chars(string in r"[a-f]{9}") {
848 assert!(Crc32CksumChecksum::from_str(&string).is_err());
849 }
850
851 #[test]
852 fn invalid_checksum_crc32cksum_negative(string in r"-[1-9]{9}") {
853 assert!(Crc32CksumChecksum::from_str(&string).is_err());
854 }
855 }
856
857 #[rstest]
858 fn checksum_blake2b512() {
859 let data = "foo\n";
860 let digest = vec![
861 210, 2, 215, 149, 29, 242, 196, 183, 17, 202, 68, 180, 188, 201, 215, 179, 99, 250, 66,
862 82, 18, 126, 5, 140, 26, 145, 14, 192, 91, 108, 208, 56, 215, 28, 194, 18, 33, 192, 49,
863 192, 53, 159, 153, 62, 116, 107, 7, 245, 150, 92, 248, 197, 195, 116, 106, 88, 51, 122,
864 217, 171, 101, 39, 142, 119,
865 ];
866 let hex_digest = "d202d7951df2c4b711ca44b4bcc9d7b363fa4252127e058c1a910ec05b6cd038d71cc21221c031c0359f993e746b07f5965cf8c5c3746a58337ad9ab65278e77";
867
868 let checksum = Blake2b512Checksum::calculate_from(data);
869 assert_eq!(digest, checksum.inner());
870 assert_eq!(format!("{}", checksum), hex_digest,);
871
872 let checksum = Blake2b512Checksum::from_str(hex_digest).unwrap();
873 assert_eq!(digest, checksum.inner());
874 assert_eq!(format!("{}", checksum), hex_digest,);
875 }
876
877 #[rstest]
878 fn checksum_sha1() {
879 let data = "foo\n";
880 let digest = vec![
881 241, 210, 210, 249, 36, 233, 134, 172, 134, 253, 247, 179, 108, 148, 188, 223, 50, 190,
882 236, 21,
883 ];
884 let hex_digest = "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15";
885
886 let checksum = Sha1Checksum::calculate_from(data);
887 assert_eq!(digest, checksum.inner());
888 assert_eq!(format!("{}", checksum), hex_digest,);
889
890 let checksum = Sha1Checksum::from_str(hex_digest).unwrap();
891 assert_eq!(digest, checksum.inner());
892 assert_eq!(format!("{}", checksum), hex_digest,);
893 }
894
895 #[rstest]
896 fn checksum_sha224() {
897 let data = "foo\n";
898 let digest = vec![
899 231, 213, 227, 110, 141, 71, 12, 62, 81, 3, 254, 221, 46, 79, 42, 165, 195, 10, 178,
900 127, 102, 41, 189, 195, 40, 111, 157, 210,
901 ];
902 let hex_digest = "e7d5e36e8d470c3e5103fedd2e4f2aa5c30ab27f6629bdc3286f9dd2";
903
904 let checksum = Sha224Checksum::calculate_from(data);
905 assert_eq!(digest, checksum.inner());
906 assert_eq!(format!("{}", checksum), hex_digest,);
907
908 let checksum = Sha224Checksum::from_str(hex_digest).unwrap();
909 assert_eq!(digest, checksum.inner());
910 assert_eq!(format!("{}", checksum), hex_digest,);
911 }
912
913 #[rstest]
914 fn checksum_sha256() {
915 let data = "foo\n";
916 let digest = vec![
917 181, 187, 157, 128, 20, 160, 249, 177, 214, 30, 33, 231, 150, 215, 141, 204, 223, 19,
918 82, 242, 60, 211, 40, 18, 244, 133, 11, 135, 138, 228, 148, 76,
919 ];
920 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
921
922 let checksum = Sha256Checksum::calculate_from(data);
923 assert_eq!(digest, checksum.inner());
924 assert_eq!(format!("{}", checksum), hex_digest,);
925
926 let checksum = Sha256Checksum::from_str(hex_digest).unwrap();
927 assert_eq!(digest, checksum.inner());
928 assert_eq!(format!("{}", checksum), hex_digest,);
929 }
930
931 #[rstest]
932 fn checksum_sha384() {
933 let data = "foo\n";
934 let digest = vec![
935 142, 255, 218, 191, 225, 68, 22, 33, 74, 37, 15, 147, 85, 5, 37, 11, 217, 145, 241, 6,
936 6, 93, 137, 157, 182, 225, 155, 220, 139, 246, 72, 243, 172, 15, 25, 53, 196, 246, 95,
937 232, 247, 152, 40, 155, 26, 13, 30, 6,
938 ];
939 let hex_digest = "8effdabfe14416214a250f935505250bd991f106065d899db6e19bdc8bf648f3ac0f1935c4f65fe8f798289b1a0d1e06";
940
941 let checksum = Sha384Checksum::calculate_from(data);
942 assert_eq!(digest, checksum.inner());
943 assert_eq!(format!("{}", checksum), hex_digest,);
944
945 let checksum = Sha384Checksum::from_str(hex_digest).unwrap();
946 assert_eq!(digest, checksum.inner());
947 assert_eq!(format!("{}", checksum), hex_digest,);
948 }
949
950 #[rstest]
951 fn checksum_sha512() {
952 let data = "foo\n";
953 let digest = vec![
954 12, 249, 24, 10, 118, 74, 186, 134, 58, 103, 182, 215, 47, 9, 24, 188, 19, 28, 103,
955 114, 100, 44, 178, 220, 229, 163, 79, 10, 112, 47, 148, 112, 221, 194, 191, 18, 92, 18,
956 25, 139, 25, 149, 194, 51, 195, 75, 74, 253, 52, 108, 84, 162, 51, 76, 53, 10, 148,
957 138, 81, 182, 232, 180, 230, 182,
958 ];
959 let hex_digest = "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6";
960
961 let checksum = Sha512Checksum::calculate_from(data);
962 assert_eq!(digest, checksum.inner());
963 assert_eq!(format!("{}", checksum), hex_digest);
964
965 let checksum = Sha512Checksum::from_str(hex_digest).unwrap();
966 assert_eq!(digest, checksum.inner());
967 assert_eq!(format!("{}", checksum), hex_digest);
968 }
969
970 #[rstest]
971 fn checksum_crc32cksum() {
972 let data = "foo\n";
973 let digest = 3915528286u32;
974 let digest_string = format!("{digest}");
975
976 let checksum = Crc32CksumChecksum::calculate_from(data);
977 assert_eq!(digest.to_be_bytes(), checksum.inner());
978 assert_eq!(format!("{}", checksum), digest_string);
979
980 let checksum = Crc32CksumChecksum::from_str(digest_string.as_str()).unwrap();
981 assert_eq!(digest.to_be_bytes(), checksum.inner());
982 assert_eq!(format!("{}", checksum), digest_string);
983 }
984
985 #[rstest]
986 #[case::non_hex_digits(
987 "0cf9180a764aba863a67b6d72f0918bc13gggggg642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6"
988 )]
989 #[case::incomplete_pair(" b ")]
990 #[case::incomplete_digest("0cf9180a764aba863a67b6d72f0918bca")]
991 #[case::whitespace(
992 "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"
993 )]
994 fn checksum_parse_error(#[case] input: &str) {
995 let Err(Error::ParseError(err_msg)) = Sha512Checksum::from_str(input) else {
996 panic!("'{input}' erroneously parsed as Sha512Checksum")
997 };
998
999 let (test_name, _guard) = configure_insta();
1000 assert_snapshot!(test_name, err_msg.to_string());
1001 }
1002
1003 #[rstest]
1004 fn skippable_checksum_sha256() {
1005 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
1006 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
1007 assert_eq!(format!("{}", checksum), hex_digest);
1008 }
1009
1010 #[rstest]
1011 fn skippable_checksum_skip() {
1012 let hex_digest = "SKIP";
1013 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
1014
1015 assert_eq!(SkippableChecksum::Skip, checksum);
1016 assert_eq!(format!("{}", checksum), hex_digest);
1017 }
1018}