1use std::{
2 fmt::{Debug, Display, Formatter},
3 marker::PhantomData,
4 str::FromStr,
5};
6
7use alpm_parsers::prelude::*;
8use digest::{Digest, Output};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use strum::{Display, EnumString, VariantArray, VariantNames};
12use winnow::{
13 ascii::dec_uint,
14 combinator::{alt, cut_err, not, repeat},
15 error::ErrMode,
16 token::one_of,
17};
18
19use crate::{
20 Error,
21 digests::{Blake2b512, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
22};
23
24mod crc32;
25
26pub use crc32::Crc32Cksum;
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum DigestEncoding {
31 Hex,
33 Dec,
35}
36
37pub trait DigestString: Digest {
40 const ENCODING: DigestEncoding;
42
43 const NAME: &str;
45}
46
47impl DigestString for Blake2b512 {
48 const ENCODING: DigestEncoding = DigestEncoding::Hex;
49 const NAME: &str = "BLAKE2b-512";
50}
51
52impl DigestString for Md5 {
53 const ENCODING: DigestEncoding = DigestEncoding::Hex;
54 const NAME: &str = "MD5";
55}
56
57impl DigestString for Sha1 {
58 const ENCODING: DigestEncoding = DigestEncoding::Hex;
59 const NAME: &str = "SHA-1";
60}
61
62impl DigestString for Sha224 {
63 const ENCODING: DigestEncoding = DigestEncoding::Hex;
64 const NAME: &str = "SHA-224";
65}
66
67impl DigestString for Sha256 {
68 const ENCODING: DigestEncoding = DigestEncoding::Hex;
69 const NAME: &str = "SHA-256";
70}
71
72impl DigestString for Sha384 {
73 const ENCODING: DigestEncoding = DigestEncoding::Hex;
74 const NAME: &str = "SHA-384";
75}
76
77impl DigestString for Sha512 {
78 const ENCODING: DigestEncoding = DigestEncoding::Hex;
79 const NAME: &str = "SHA-512";
80}
81
82impl DigestString for Crc32Cksum {
83 const ENCODING: DigestEncoding = DigestEncoding::Dec;
84 const NAME: &str = "CRC-32";
85}
86
87pub type Blake2b512Checksum = Checksum<Blake2b512>;
91
92pub type Md5Checksum = Checksum<Md5>;
94
95pub type Sha1Checksum = Checksum<Sha1>;
97
98pub type Sha224Checksum = Checksum<Sha224>;
100
101pub type Sha256Checksum = Checksum<Sha256>;
103
104pub type Sha384Checksum = Checksum<Sha384>;
106
107pub type Sha512Checksum = Checksum<Sha512>;
109
110pub type Crc32CksumChecksum = Checksum<Crc32Cksum>;
112
113#[derive(
115 Clone,
116 Copy,
117 Debug,
118 Display,
119 EnumString,
120 Eq,
121 Hash,
122 Ord,
123 PartialEq,
124 PartialOrd,
125 VariantNames,
126 VariantArray,
127)]
128#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
129pub enum ChecksumAlgorithm {
130 Blake2b512,
132 Md5,
134 Sha1,
136 Sha224,
138 Sha256,
140 Sha384,
142 Sha512,
144 Crc32Cksum,
146}
147
148impl ChecksumAlgorithm {
149 pub fn is_deprecated(&self) -> bool {
174 match self {
175 ChecksumAlgorithm::Md5 | ChecksumAlgorithm::Sha1 | ChecksumAlgorithm::Crc32Cksum => {
176 true
177 }
178 ChecksumAlgorithm::Blake2b512
179 | ChecksumAlgorithm::Sha224
180 | ChecksumAlgorithm::Sha256
181 | ChecksumAlgorithm::Sha384
182 | ChecksumAlgorithm::Sha512 => false,
183 }
184 }
185
186 pub fn non_deprecated_checksums(&self) -> Vec<ChecksumAlgorithm> {
188 <ChecksumAlgorithm as VariantArray>::VARIANTS
189 .iter()
190 .filter(|algo| !algo.is_deprecated())
191 .copied()
192 .collect::<Vec<ChecksumAlgorithm>>()
193 }
194}
195
196#[derive(Clone)]
271pub struct Checksum<D: Digest> {
272 digest: Vec<u8>,
273 _marker: PhantomData<D>,
274}
275
276impl<D: Digest> From<Output<D>> for Checksum<D> {
277 fn from(digest: Output<D>) -> Self {
296 Self {
297 digest: digest.to_vec(),
298 _marker: PhantomData,
299 }
300 }
301}
302
303#[cfg(feature = "serde")]
304impl<D: DigestString> Serialize for Checksum<D> {
305 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
310 where
311 S: Serializer,
312 {
313 serializer.serialize_str(&self.to_string())
314 }
315}
316
317#[cfg(feature = "serde")]
318impl<'de, D: DigestString> Deserialize<'de> for Checksum<D> {
319 fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
320 where
321 De: Deserializer<'de>,
322 {
323 let s = String::deserialize(deserializer)?;
324 Checksum::from_str(&s).map_err(serde::de::Error::custom)
325 }
326}
327
328impl<D: DigestString> Checksum<D> {
329 pub fn calculate_from(input: impl AsRef<[u8]>) -> Self {
341 let mut hasher = D::new();
342 hasher.update(input);
343
344 Checksum {
345 digest: hasher.finalize()[..].to_vec(),
346 _marker: PhantomData,
347 }
348 }
349
350 pub fn calculate_from_reader(mut reader: impl std::io::Read) -> Result<Self, std::io::Error> {
376 struct HashWriter<D: Digest>(D);
381
382 impl<D: Digest> std::io::Write for HashWriter<D> {
383 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
384 self.0.update(buf);
385 Ok(buf.len())
386 }
387
388 fn flush(&mut self) -> std::io::Result<()> {
390 Ok(())
391 }
392 }
393
394 let mut writer = HashWriter(D::new());
395 std::io::copy(&mut reader, &mut writer)?;
396
397 Ok(writer.0.finalize().into())
398 }
399}
400
401impl<D: DigestString> AlpmParser for Checksum<D> {
402 #[expect(
412 clippy::arithmetic_side_effects,
413 clippy::expect_used,
414 clippy::indexing_slicing
415 )]
416 fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
417 let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
418 #[inline]
422 fn hex_digit<'a>(input: &mut Input<'a>) -> PResult<'a, u8> {
423 one_of(('0'..='9', 'a'..='f', 'A'..='F'))
424 .map(|d: char|
425 u8::try_from(d.to_digit(16).expect("the character is in the set [0-9a-fA-F]"))
428 .expect("an ASCII hex digit fits into a u8"))
429 .parse_next(input)
430 }
431
432 let hex_pair = (hex_digit, hex_digit).map(|(first, second)|
433 (first << 4) + second);
435
436 let digest_bytes = <D as Digest>::output_size();
438
439 let digest = match D::ENCODING {
440 DigestEncoding::Hex => {
441 let digest = repeat(digest_bytes, hex_pair)
443 .expected_text("ASCII hex digit")
444 .label("hash digest")
445 .description(format!(
446 "Expected a hex {} hash digest consisting of {digest_bytes} bytes.",
447 D::NAME
448 ))
449 .parse_next(input)?;
450
451 cut_err(not(hex_digit))
455 .expected_text("end of checksum (checksum is too long).")
456 .parse_next(input)?;
457
458 digest
459 }
460 DigestEncoding::Dec => {
461 let digest_bits = digest_bytes * 8;
463
464 let max_value: u128 = if digest_bits >= 128 {
473 u128::MAX
477 } else {
478 (1u128 << digest_bits) - 1
479 };
480
481 dec_uint::<_, u128, _>
484 .verify(move |&v| v <= max_value)
485 .map(move |v| v.to_be_bytes()[16 - digest_bytes..].to_vec())
489 .label("hash digest")
490 .description(format!(
491 "Expected a decimal {} hash digest consisting of {digest_bytes} bytes.",
492 D::NAME
493 ))
494 .parse_next(input)?
495 }
496 };
497
498 Ok(Self {
499 digest,
500 _marker: PhantomData,
501 })
502 };
503
504 parser.layer("checksum").parse_next(input)
505 }
506
507 fn delimiter_error_context<'a, O, P>(
508 parser: P,
509 ) -> impl Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>
510 where
511 P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>,
512 {
513 parser
514 .label("character in checksum")
515 .expected_text("a string consisting of solely decimal or hexadecimal chars.")
516 .layer("checksum")
517 }
518}
519
520impl<D: DigestString> AsRef<[u8]> for Checksum<D> {
521 fn as_ref(&self) -> &[u8] {
523 &self.digest
524 }
525}
526
527impl<D: DigestString> FromStr for Checksum<D> {
528 type Err = Error;
529 fn from_str(s: &str) -> Result<Checksum<D>, Self::Err> {
548 Ok(Checksum::parser.parse(Input::new(s))?)
549 }
550}
551
552impl<D: DigestString> Display for Checksum<D> {
553 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
554 match D::ENCODING {
555 DigestEncoding::Hex => {
556 write!(
557 fmt,
558 "{}",
559 self.digest
560 .iter()
561 .map(|x| format!("{x:02x?}"))
562 .collect::<Vec<String>>()
563 .join("")
564 )
565 }
566 DigestEncoding::Dec => {
567 let value = self
571 .digest
572 .iter()
573 .fold(0u128, |acc, &byte| (acc << 8) | byte as u128);
574 write!(fmt, "{}", value)
575 }
576 }
577 }
578}
579
580impl<D: DigestString> Debug for Checksum<D> {
583 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
584 Display::fmt(&self, f)
585 }
586}
587
588impl<D: Digest> PartialEq for Checksum<D> {
589 fn eq(&self, other: &Self) -> bool {
590 self.digest == other.digest
591 }
592}
593
594impl<D: Digest> Eq for Checksum<D> {}
595
596impl<D: Digest> Ord for Checksum<D> {
597 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
598 self.digest.cmp(&other.digest)
599 }
600}
601
602impl<D: Digest> PartialOrd for Checksum<D> {
603 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
604 Some(self.cmp(other))
605 }
606}
607
608#[derive(Clone, Debug)]
613#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
614#[cfg_attr(feature = "serde", serde(tag = "type"))]
615pub enum SkippableChecksum<D: DigestString + Clone> {
616 Skip,
618 #[cfg_attr(feature = "serde", serde(bound = "D: Digest + Clone"))]
620 Checksum {
621 digest: Checksum<D>,
623 },
624}
625
626impl<D: DigestString + Clone> SkippableChecksum<D> {
627 pub fn is_skipped(&self) -> bool {
631 matches!(self, SkippableChecksum::Skip)
632 }
633}
634
635impl<D: DigestString + Clone> AlpmParser for SkippableChecksum<D> {
636 fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
645 alt((
646 "SKIP".value(Self::Skip),
647 Checksum::parser.map(|digest| Self::Checksum { digest }),
648 ))
649 .expected_text("a hash digest with the appropriate length for the given algorithm, or an uppercase 'SKIP'")
650 .layer("skippable checksum")
651 .parse_next(input)
652 }
653
654 fn delimiter_error_context<'a, O, P>(
655 parser: P,
656 ) -> impl Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>
657 where
658 P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>,
659 {
660 parser
661 .expected_text("end of checksum.")
662 .layer("skippable checksum")
663 }
664}
665
666impl<D: DigestString + Clone> FromStr for SkippableChecksum<D> {
667 type Err = Error;
668 fn from_str(s: &str) -> Result<SkippableChecksum<D>, Self::Err> {
689 Ok(Self::parser.parse(Input::new(s))?)
690 }
691}
692
693impl<D: DigestString + Clone> Display for SkippableChecksum<D> {
694 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
695 let output = match self {
696 SkippableChecksum::Skip => "SKIP".to_string(),
697 SkippableChecksum::Checksum { digest } => digest.to_string(),
698 };
699 write!(fmt, "{output}",)
700 }
701}
702
703impl<D: DigestString + Clone> PartialEq for SkippableChecksum<D> {
704 fn eq(&self, other: &Self) -> bool {
705 match (self, other) {
706 (SkippableChecksum::Skip, SkippableChecksum::Skip) => true,
707 (SkippableChecksum::Skip, SkippableChecksum::Checksum { .. }) => false,
708 (SkippableChecksum::Checksum { .. }, SkippableChecksum::Skip) => false,
709 (
710 SkippableChecksum::Checksum { digest },
711 SkippableChecksum::Checksum {
712 digest: digest_other,
713 },
714 ) => digest == digest_other,
715 }
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use insta::assert_snapshot;
722 use proptest::prelude::*;
723 use rstest::rstest;
724 use testresult::TestResult;
725
726 use super::*;
727 use crate::configure_insta;
728
729 proptest! {
730 #![proptest_config(ProptestConfig::with_cases(1000))]
731
732 #[test]
733 fn valid_checksum_blake2b512_from_string(string in r"[a-f0-9]{128}") {
734 prop_assert_eq!(&string, &format!("{}", Blake2b512Checksum::from_str(&string)?));
735 }
736
737 #[test]
738 fn invalid_checksum_blake2b512_bigger_size(string in r"[a-f0-9]{129}") {
739 assert!(Blake2b512Checksum::from_str(&string).is_err());
740 }
741
742 #[test]
743 fn invalid_checksum_blake2b512_smaller_size(string in r"[a-f0-9]{127}") {
744 assert!(Blake2b512Checksum::from_str(&string).is_err());
745 }
746
747 #[test]
748 fn invalid_checksum_blake2b512_wrong_chars(string in r"[e-z0-9]{128}") {
749 assert!(Blake2b512Checksum::from_str(&string).is_err());
750 }
751
752 #[test]
753 fn valid_checksum_sha1_from_string(string in r"[a-f0-9]{40}") {
754 prop_assert_eq!(&string, &format!("{}", Sha1Checksum::from_str(&string)?));
755 }
756
757 #[test]
758 fn invalid_checksum_sha1_from_string_bigger_size(string in r"[a-f0-9]{41}") {
759 assert!(Sha1Checksum::from_str(&string).is_err());
760 }
761
762 #[test]
763 fn invalid_checksum_sha1_from_string_smaller_size(string in r"[a-f0-9]{39}") {
764 assert!(Sha1Checksum::from_str(&string).is_err());
765 }
766
767 #[test]
768 fn invalid_checksum_sha1_from_string_wrong_chars(string in r"[e-z0-9]{40}") {
769 assert!(Sha1Checksum::from_str(&string).is_err());
770 }
771
772 #[test]
773 fn valid_checksum_sha224_from_string(string in r"[a-f0-9]{56}") {
774 prop_assert_eq!(&string, &format!("{}", Sha224Checksum::from_str(&string)?));
775 }
776
777 #[test]
778 fn invalid_checksum_sha224_from_string_bigger_size(string in r"[a-f0-9]{57}") {
779 assert!(Sha224Checksum::from_str(&string).is_err());
780 }
781
782 #[test]
783 fn invalid_checksum_sha224_from_string_smaller_size(string in r"[a-f0-9]{55}") {
784 assert!(Sha224Checksum::from_str(&string).is_err());
785 }
786
787 #[test]
788 fn invalid_checksum_sha224_from_string_wrong_chars(string in r"[e-z0-9]{56}") {
789 assert!(Sha224Checksum::from_str(&string).is_err());
790 }
791
792 #[test]
793 fn valid_checksum_sha256_from_string(string in r"[a-f0-9]{64}") {
794 prop_assert_eq!(&string, &format!("{}", Sha256Checksum::from_str(&string)?));
795 }
796
797 #[test]
798 fn invalid_checksum_sha256_from_string_bigger_size(string in r"[a-f0-9]{65}") {
799 assert!(Sha256Checksum::from_str(&string).is_err());
800 }
801
802 #[test]
803 fn invalid_checksum_sha256_from_string_smaller_size(string in r"[a-f0-9]{63}") {
804 assert!(Sha256Checksum::from_str(&string).is_err());
805 }
806
807 #[test]
808 fn invalid_checksum_sha256_from_string_wrong_chars(string in r"[e-z0-9]{64}") {
809 assert!(Sha256Checksum::from_str(&string).is_err());
810 }
811
812 #[test]
813 fn valid_checksum_sha384_from_string(string in r"[a-f0-9]{96}") {
814 prop_assert_eq!(&string, &format!("{}", Sha384Checksum::from_str(&string)?));
815 }
816
817 #[test]
818 fn invalid_checksum_sha384_from_string_bigger_size(string in r"[a-f0-9]{97}") {
819 assert!(Sha384Checksum::from_str(&string).is_err());
820 }
821
822 #[test]
823 fn invalid_checksum_sha384_from_string_smaller_size(string in r"[a-f0-9]{95}") {
824 assert!(Sha384Checksum::from_str(&string).is_err());
825 }
826
827 #[test]
828 fn invalid_checksum_sha384_from_string_wrong_chars(string in r"[e-z0-9]{96}") {
829 assert!(Sha384Checksum::from_str(&string).is_err());
830 }
831
832 #[test]
833 fn valid_checksum_sha512_from_string(string in r"[a-f0-9]{128}") {
834 prop_assert_eq!(&string, &format!("{}", Sha512Checksum::from_str(&string)?));
835 }
836
837 #[test]
838 fn invalid_checksum_sha512_from_string_bigger_size(string in r"[a-f0-9]{129}") {
839 assert!(Sha512Checksum::from_str(&string).is_err());
840 }
841
842 #[test]
843 fn invalid_checksum_sha512_from_string_smaller_size(string in r"[a-f0-9]{127}") {
844 assert!(Sha512Checksum::from_str(&string).is_err());
845 }
846
847 #[test]
848 fn invalid_checksum_sha512_from_string_wrong_chars(string in r"[e-z0-9]{128}") {
849 assert!(Sha512Checksum::from_str(&string).is_err());
850 }
851
852 #[test]
853 fn valid_checksum_crc32cksum(sum in 0u32..=u32::MAX) {
854 let decimal_str = format!("{sum}");
855 prop_assert_eq!(
856 &decimal_str,
857 &format!("{}", Crc32CksumChecksum::from_str(decimal_str.as_str())?)
858 );
859 }
860
861 #[test]
862 fn invalid_checksum_crc32cksum_bigger_size(sum in (u32::MAX as u128)..=u128::MAX) {
863 let decimal_str = format!("{sum}");
864 assert!(Crc32CksumChecksum::from_str(decimal_str.as_str()).is_err());
865 }
866
867 #[test]
868 fn invalid_checksum_crc32cksum_wrong_chars(string in r"[a-f]{9}") {
869 assert!(Crc32CksumChecksum::from_str(&string).is_err());
870 }
871
872 #[test]
873 fn invalid_checksum_crc32cksum_negative(string in r"-[1-9]{9}") {
874 assert!(Crc32CksumChecksum::from_str(&string).is_err());
875 }
876 }
877
878 #[rstest]
879 fn checksum_blake2b512() -> TestResult {
880 let data = "foo\n";
881 let digest = vec![
882 210, 2, 215, 149, 29, 242, 196, 183, 17, 202, 68, 180, 188, 201, 215, 179, 99, 250, 66,
883 82, 18, 126, 5, 140, 26, 145, 14, 192, 91, 108, 208, 56, 215, 28, 194, 18, 33, 192, 49,
884 192, 53, 159, 153, 62, 116, 107, 7, 245, 150, 92, 248, 197, 195, 116, 106, 88, 51, 122,
885 217, 171, 101, 39, 142, 119,
886 ];
887 let hex_digest = "d202d7951df2c4b711ca44b4bcc9d7b363fa4252127e058c1a910ec05b6cd038d71cc21221c031c0359f993e746b07f5965cf8c5c3746a58337ad9ab65278e77";
888
889 let checksum = Blake2b512Checksum::calculate_from(data);
890 assert_eq!(digest, checksum.as_ref());
891 assert_eq!(format!("{}", checksum), hex_digest);
892
893 let checksum = Blake2b512Checksum::from_str(hex_digest)?;
894 assert_eq!(digest, checksum.as_ref());
895 assert_eq!(format!("{}", checksum), hex_digest);
896
897 Ok(())
898 }
899
900 #[rstest]
901 fn checksum_sha1() -> TestResult {
902 let data = "foo\n";
903 let digest = vec![
904 241, 210, 210, 249, 36, 233, 134, 172, 134, 253, 247, 179, 108, 148, 188, 223, 50, 190,
905 236, 21,
906 ];
907 let hex_digest = "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15";
908
909 let checksum = Sha1Checksum::calculate_from(data);
910 assert_eq!(digest, checksum.as_ref());
911 assert_eq!(format!("{}", checksum), hex_digest);
912
913 let checksum = Sha1Checksum::from_str(hex_digest)?;
914 assert_eq!(digest, checksum.as_ref());
915 assert_eq!(format!("{}", checksum), hex_digest);
916
917 Ok(())
918 }
919
920 #[rstest]
921 fn checksum_sha224() -> TestResult {
922 let data = "foo\n";
923 let digest = vec![
924 231, 213, 227, 110, 141, 71, 12, 62, 81, 3, 254, 221, 46, 79, 42, 165, 195, 10, 178,
925 127, 102, 41, 189, 195, 40, 111, 157, 210,
926 ];
927 let hex_digest = "e7d5e36e8d470c3e5103fedd2e4f2aa5c30ab27f6629bdc3286f9dd2";
928
929 let checksum = Sha224Checksum::calculate_from(data);
930 assert_eq!(digest, checksum.as_ref());
931 assert_eq!(format!("{}", checksum), hex_digest);
932
933 let checksum = Sha224Checksum::from_str(hex_digest)?;
934 assert_eq!(digest, checksum.as_ref());
935 assert_eq!(format!("{}", checksum), hex_digest);
936
937 Ok(())
938 }
939
940 #[rstest]
941 fn checksum_sha256() -> TestResult {
942 let data = "foo\n";
943 let digest = vec![
944 181, 187, 157, 128, 20, 160, 249, 177, 214, 30, 33, 231, 150, 215, 141, 204, 223, 19,
945 82, 242, 60, 211, 40, 18, 244, 133, 11, 135, 138, 228, 148, 76,
946 ];
947 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
948
949 let checksum = Sha256Checksum::calculate_from(data);
950 assert_eq!(digest, checksum.as_ref());
951 assert_eq!(format!("{}", checksum), hex_digest);
952
953 let checksum = Sha256Checksum::from_str(hex_digest)?;
954 assert_eq!(digest, checksum.as_ref());
955 assert_eq!(format!("{}", checksum), hex_digest);
956
957 Ok(())
958 }
959
960 #[rstest]
961 fn checksum_sha384() -> TestResult {
962 let data = "foo\n";
963 let digest = vec![
964 142, 255, 218, 191, 225, 68, 22, 33, 74, 37, 15, 147, 85, 5, 37, 11, 217, 145, 241, 6,
965 6, 93, 137, 157, 182, 225, 155, 220, 139, 246, 72, 243, 172, 15, 25, 53, 196, 246, 95,
966 232, 247, 152, 40, 155, 26, 13, 30, 6,
967 ];
968 let hex_digest = "8effdabfe14416214a250f935505250bd991f106065d899db6e19bdc8bf648f3ac0f1935c4f65fe8f798289b1a0d1e06";
969
970 let checksum = Sha384Checksum::calculate_from(data);
971 assert_eq!(digest, checksum.as_ref());
972 assert_eq!(format!("{}", checksum), hex_digest);
973
974 let checksum = Sha384Checksum::from_str(hex_digest)?;
975 assert_eq!(digest, checksum.as_ref());
976 assert_eq!(format!("{}", checksum), hex_digest);
977
978 Ok(())
979 }
980
981 #[rstest]
982 fn checksum_sha512() -> TestResult {
983 let data = "foo\n";
984 let digest = vec![
985 12, 249, 24, 10, 118, 74, 186, 134, 58, 103, 182, 215, 47, 9, 24, 188, 19, 28, 103,
986 114, 100, 44, 178, 220, 229, 163, 79, 10, 112, 47, 148, 112, 221, 194, 191, 18, 92, 18,
987 25, 139, 25, 149, 194, 51, 195, 75, 74, 253, 52, 108, 84, 162, 51, 76, 53, 10, 148,
988 138, 81, 182, 232, 180, 230, 182,
989 ];
990 let hex_digest = "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6";
991
992 let checksum = Sha512Checksum::calculate_from(data);
993 assert_eq!(digest, checksum.as_ref());
994 assert_eq!(format!("{}", checksum), hex_digest);
995
996 let checksum = Sha512Checksum::from_str(hex_digest)?;
997 assert_eq!(digest, checksum.as_ref());
998 assert_eq!(format!("{}", checksum), hex_digest);
999
1000 Ok(())
1001 }
1002
1003 #[rstest]
1004 fn checksum_crc32cksum() -> TestResult {
1005 let data = "foo\n";
1006 let digest = 3915528286u32;
1007 let digest_string = format!("{digest}");
1008
1009 let checksum = Crc32CksumChecksum::calculate_from(data);
1010 assert_eq!(digest.to_be_bytes(), checksum.as_ref());
1011 assert_eq!(format!("{}", checksum), digest_string);
1012
1013 let checksum = Crc32CksumChecksum::from_str(digest_string.as_str())?;
1014 assert_eq!(digest.to_be_bytes(), checksum.as_ref());
1015 assert_eq!(format!("{}", checksum), digest_string);
1016
1017 Ok(())
1018 }
1019
1020 #[rstest]
1021 #[case::non_hex_digits(
1022 "0cf9180a764aba863a67b6d72f0918bc13gggggg642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6"
1023 )]
1024 #[case::incomplete_pair(" b ")]
1025 #[case::incomplete_digest("0cf9180a764aba863a67b6d72f0918bca")]
1026 #[case::whitespace(
1027 "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"
1028 )]
1029 fn checksum_parse_error(#[case] input: &str) {
1030 let (test_name, _guard) = configure_insta();
1031
1032 let Err(Error::ParseError(err_msg)) = Sha512Checksum::from_str(input) else {
1033 panic!("'{input}' erroneously parsed as Sha512Checksum")
1034 };
1035
1036 assert_snapshot!(test_name, err_msg.to_string());
1037 }
1038
1039 #[rstest]
1040 fn skippable_checksum_sha256() -> TestResult {
1041 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
1042 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest)?;
1043 assert_eq!(format!("{}", checksum), hex_digest);
1044
1045 Ok(())
1046 }
1047
1048 #[rstest]
1049 fn skippable_checksum_skip() -> TestResult {
1050 let hex_digest = "SKIP";
1051 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest)?;
1052
1053 assert_eq!(SkippableChecksum::Skip, checksum);
1054 assert_eq!(format!("{}", checksum), hex_digest);
1055
1056 Ok(())
1057 }
1058}