1use std::{
2 fmt::{Debug, Display, Formatter},
3 marker::PhantomData,
4 ops::DerefMut,
5 str::FromStr,
6};
7
8use alpm_parsers::traits::AlpmParser;
9use digest::{Digest, FixedOutput, HashMarker, Output, OutputSizeUser, Update};
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use strum::{Display, EnumString, VariantArray, VariantNames};
13use winnow::{
14 ModalResult,
15 Parser,
16 ascii::dec_uint,
17 combinator::{alt, cut_err, not, repeat},
18 error::{StrContext, StrContextValue},
19 token::one_of,
20};
21
22use crate::{
23 Error,
24 digests::{Blake2b512, Md5, Sha1, Sha224, Sha256, Sha384, Sha512},
25};
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum DigestEncoding {
30 Hex,
32 Dec,
34}
35
36pub trait DigestString: Digest {
39 const ENCODING: DigestEncoding;
41}
42
43impl DigestString for Blake2b512 {
44 const ENCODING: DigestEncoding = DigestEncoding::Hex;
45}
46
47impl DigestString for Md5 {
48 const ENCODING: DigestEncoding = DigestEncoding::Hex;
49}
50
51impl DigestString for Sha1 {
52 const ENCODING: DigestEncoding = DigestEncoding::Hex;
53}
54
55impl DigestString for Sha224 {
56 const ENCODING: DigestEncoding = DigestEncoding::Hex;
57}
58
59impl DigestString for Sha256 {
60 const ENCODING: DigestEncoding = DigestEncoding::Hex;
61}
62
63impl DigestString for Sha384 {
64 const ENCODING: DigestEncoding = DigestEncoding::Hex;
65}
66
67impl DigestString for Sha512 {
68 const ENCODING: DigestEncoding = DigestEncoding::Hex;
69}
70
71impl DigestString for Crc32Cksum {
72 const ENCODING: DigestEncoding = DigestEncoding::Dec;
73}
74
75pub type Blake2b512Checksum = Checksum<Blake2b512>;
79
80pub type Md5Checksum = Checksum<Md5>;
82
83pub type Sha1Checksum = Checksum<Sha1>;
85
86pub type Sha224Checksum = Checksum<Sha224>;
88
89pub type Sha256Checksum = Checksum<Sha256>;
91
92pub type Sha384Checksum = Checksum<Sha384>;
94
95pub type Sha512Checksum = Checksum<Sha512>;
97
98pub type Crc32CksumChecksum = Checksum<Crc32Cksum>;
100
101#[derive(
103 Clone,
104 Copy,
105 Debug,
106 Display,
107 EnumString,
108 Eq,
109 Hash,
110 Ord,
111 PartialEq,
112 PartialOrd,
113 VariantNames,
114 VariantArray,
115)]
116#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
117pub enum ChecksumAlgorithm {
118 Blake2b512,
120 Md5,
122 Sha1,
124 Sha224,
126 Sha256,
128 Sha384,
130 Sha512,
132 Crc32Cksum,
134}
135
136impl ChecksumAlgorithm {
137 pub fn is_deprecated(&self) -> bool {
162 match self {
163 ChecksumAlgorithm::Md5 | ChecksumAlgorithm::Sha1 | ChecksumAlgorithm::Crc32Cksum => {
164 true
165 }
166 ChecksumAlgorithm::Blake2b512
167 | ChecksumAlgorithm::Sha224
168 | ChecksumAlgorithm::Sha256
169 | ChecksumAlgorithm::Sha384
170 | ChecksumAlgorithm::Sha512 => false,
171 }
172 }
173
174 pub fn non_deprecated_checksums(&self) -> Vec<ChecksumAlgorithm> {
176 <ChecksumAlgorithm as VariantArray>::VARIANTS
177 .iter()
178 .filter(|algo| !algo.is_deprecated())
179 .copied()
180 .collect::<Vec<ChecksumAlgorithm>>()
181 }
182}
183
184#[derive(Clone)]
259pub struct Checksum<D: Digest> {
260 digest: Vec<u8>,
261 _marker: PhantomData<D>,
262}
263
264#[cfg(feature = "serde")]
265impl<D: DigestString> Serialize for Checksum<D> {
266 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
271 where
272 S: Serializer,
273 {
274 serializer.serialize_str(&self.to_string())
275 }
276}
277
278#[cfg(feature = "serde")]
279impl<'de, D: DigestString> Deserialize<'de> for Checksum<D> {
280 fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
281 where
282 De: Deserializer<'de>,
283 {
284 let s = String::deserialize(deserializer)?;
285 Checksum::from_str(&s).map_err(serde::de::Error::custom)
286 }
287}
288
289impl<D: DigestString> Checksum<D> {
290 pub fn calculate_from(input: impl AsRef<[u8]>) -> Self {
302 let mut hasher = D::new();
303 hasher.update(input);
304
305 Checksum {
306 digest: hasher.finalize()[..].to_vec(),
307 _marker: PhantomData,
308 }
309 }
310
311 pub fn inner(&self) -> &[u8] {
313 &self.digest
314 }
315}
316
317impl<D: DigestString> AlpmParser for Checksum<D> {
318 fn parser(input: &mut &str) -> ModalResult<Self> {
327 #[inline]
331 fn hex_digit(input: &mut &str) -> ModalResult<u8> {
332 one_of(('0'..='9', 'a'..='f', 'A'..='F'))
333 .map(|d: char|
334 d.to_digit(16).unwrap().try_into().unwrap())
338 .context(StrContext::Expected(StrContextValue::Description(
339 "ASCII hex digit",
340 )))
341 .parse_next(input)
342 }
343
344 let hex_pair = (hex_digit, hex_digit).map(|(first, second)|
345 (first << 4) + second);
347
348 let digest_bytes = <D as Digest>::output_size();
350
351 let digest = match D::ENCODING {
352 DigestEncoding::Hex => {
353 let digest = repeat(digest_bytes, hex_pair)
355 .context(StrContext::Label("hash digest"))
356 .context(StrContext::Expected(StrContextValue::Description(
357 "a hex hash digest with the appropriate length for the given algorithm.",
358 )))
359 .parse_next(input)?;
360
361 cut_err(not(hex_digit))
364 .context(StrContext::Expected(StrContextValue::Description(
365 "end of checksum (checksum is too long).",
366 )))
367 .parse_next(input)?;
368
369 digest
370 }
371 DigestEncoding::Dec => {
372 let digest_bits = digest_bytes * 8;
374
375 let max_value: u128 = if digest_bits >= 128 {
383 u128::MAX
387 } else {
388 (1u128 << digest_bits) - 1
389 };
390
391 dec_uint::<_, u128, _>
394 .verify(move |&v| v <= max_value)
395 .map(move |v | v.to_be_bytes()[16 - digest_bytes..].to_vec())
398 .context(StrContext::Label("hash digest"))
399 .context(StrContext::Expected(StrContextValue::Description(
400 "a decimal hash digest with the appropriate length for the given algorithm.",
401 )))
402 .parse_next(input)?
403 }
404 };
405
406 Ok(Self {
407 digest,
408 _marker: PhantomData,
409 })
410 }
411
412 fn delimiter_error_context<'a, O, P>(
413 parser: P,
414 ) -> impl Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>
415 where
416 P: Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>,
417 {
418 parser
419 .context(StrContext::Label("character in checksum"))
420 .context(StrContext::Expected(StrContextValue::Description(
421 "a string consisting of solely decimal or hexadecimal chars.",
422 )))
423 }
424}
425
426impl<D: DigestString> FromStr for Checksum<D> {
427 type Err = Error;
428 fn from_str(s: &str) -> Result<Checksum<D>, Self::Err> {
447 Ok(Checksum::parser.parse(s)?)
448 }
449}
450
451impl<D: DigestString> Display for Checksum<D> {
452 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
453 match D::ENCODING {
454 DigestEncoding::Hex => {
455 write!(
456 fmt,
457 "{}",
458 self.digest
459 .iter()
460 .map(|x| format!("{x:02x?}"))
461 .collect::<Vec<String>>()
462 .join("")
463 )
464 }
465 DigestEncoding::Dec => {
466 let value = self
470 .digest
471 .iter()
472 .fold(0u128, |acc, &byte| (acc << 8) | byte as u128);
473 write!(fmt, "{}", value)
474 }
475 }
476 }
477}
478
479impl<D: DigestString> Debug for Checksum<D> {
482 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
483 Display::fmt(&self, f)
484 }
485}
486
487impl<D: Digest> PartialEq for Checksum<D> {
488 fn eq(&self, other: &Self) -> bool {
489 self.digest == other.digest
490 }
491}
492
493impl<D: Digest> Eq for Checksum<D> {}
494
495impl<D: Digest> Ord for Checksum<D> {
496 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
497 self.digest.cmp(&other.digest)
498 }
499}
500
501impl<D: Digest> PartialOrd for Checksum<D> {
502 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
503 Some(self.cmp(other))
504 }
505}
506
507#[derive(Clone, Debug)]
512#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
513#[cfg_attr(feature = "serde", serde(tag = "type"))]
514pub enum SkippableChecksum<D: DigestString + Clone> {
515 Skip,
517 #[cfg_attr(feature = "serde", serde(bound = "D: Digest + Clone"))]
519 Checksum {
520 digest: Checksum<D>,
522 },
523}
524
525impl<D: DigestString + Clone> SkippableChecksum<D> {
526 pub fn is_skipped(&self) -> bool {
530 matches!(self, SkippableChecksum::Skip)
531 }
532}
533
534impl<D: DigestString + Clone> AlpmParser for SkippableChecksum<D> {
535 fn parser(input: &mut &str) -> ModalResult<Self> {
544 alt((
545 "SKIP".value(Self::Skip),
546 Checksum::parser.map(|digest| Self::Checksum { digest }),
547 ))
548 .context(StrContext::Expected(StrContextValue::Description(
549 "a hash digest with the appropriate length for the given algorithm, or an uppercase 'SKIP'",
550 )))
551 .parse_next(input)
552 }
553
554 fn delimiter_error_context<'a, O, P>(
555 parser: P,
556 ) -> impl Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>
557 where
558 P: Parser<&'a str, O, winnow::error::ErrMode<winnow::error::ContextError>>,
559 {
560 parser.context(StrContext::Expected(StrContextValue::Description(
561 "end of checksum.",
562 )))
563 }
564}
565
566impl<D: DigestString + Clone> FromStr for SkippableChecksum<D> {
567 type Err = Error;
568 fn from_str(s: &str) -> Result<SkippableChecksum<D>, Self::Err> {
589 Ok(Self::parser.parse(s)?)
590 }
591}
592
593impl<D: DigestString + Clone> Display for SkippableChecksum<D> {
594 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
595 let output = match self {
596 SkippableChecksum::Skip => "SKIP".to_string(),
597 SkippableChecksum::Checksum { digest } => digest.to_string(),
598 };
599 write!(fmt, "{output}",)
600 }
601}
602
603impl<D: DigestString + Clone> PartialEq for SkippableChecksum<D> {
604 fn eq(&self, other: &Self) -> bool {
605 match (self, other) {
606 (SkippableChecksum::Skip, SkippableChecksum::Skip) => true,
607 (SkippableChecksum::Skip, SkippableChecksum::Checksum { .. }) => false,
608 (SkippableChecksum::Checksum { .. }, SkippableChecksum::Skip) => false,
609 (
610 SkippableChecksum::Checksum { digest },
611 SkippableChecksum::Checksum {
612 digest: digest_other,
613 },
614 ) => digest == digest_other,
615 }
616 }
617}
618
619#[derive(Clone, Debug)]
624pub struct Crc32Cksum {
625 digest: crc_fast::Digest,
626 len: u64,
627}
628
629impl HashMarker for Crc32Cksum {}
630
631impl Default for Crc32Cksum {
632 fn default() -> Self {
633 Self {
634 digest: crc_fast::Digest::new(crc_fast::CrcAlgorithm::Crc32Cksum),
635 len: 0,
636 }
637 }
638}
639
640impl Update for Crc32Cksum {
641 fn update(&mut self, data: &[u8]) {
642 self.digest.update(data);
643 self.len += data.len() as u64;
644 }
645}
646
647impl OutputSizeUser for Crc32Cksum {
648 type OutputSize = digest::consts::U4;
649}
650
651impl FixedOutput for Crc32Cksum {
652 fn finalize_into(mut self, out: &mut Output<Self>) {
653 if self.len != 0 {
654 let len_bytes = self.len.to_be_bytes();
655
656 let start = len_bytes.iter().position(|&b| b != 0).unwrap_or(7);
658 self.digest.update(&len_bytes[start..]);
659 }
660
661 let crc = self.digest.finalize() as u32;
662 out.deref_mut().clone_from_slice(&crc.to_be_bytes());
663 }
664}
665
666#[cfg(test)]
667mod tests {
668 use insta::assert_snapshot;
669 use proptest::prelude::*;
670 use rstest::rstest;
671
672 use super::*;
673 use crate::configure_insta;
674
675 proptest! {
676 #![proptest_config(ProptestConfig::with_cases(1000))]
677
678 #[test]
679 fn valid_checksum_blake2b512_from_string(string in r"[a-f0-9]{128}") {
680 prop_assert_eq!(&string, &format!("{}", Blake2b512Checksum::from_str(&string).unwrap()));
681 }
682
683 #[test]
684 fn invalid_checksum_blake2b512_bigger_size(string in r"[a-f0-9]{129}") {
685 assert!(Blake2b512Checksum::from_str(&string).is_err());
686 }
687
688 #[test]
689 fn invalid_checksum_blake2b512_smaller_size(string in r"[a-f0-9]{127}") {
690 assert!(Blake2b512Checksum::from_str(&string).is_err());
691 }
692
693 #[test]
694 fn invalid_checksum_blake2b512_wrong_chars(string in r"[e-z0-9]{128}") {
695 assert!(Blake2b512Checksum::from_str(&string).is_err());
696 }
697
698 #[test]
699 fn valid_checksum_sha1_from_string(string in r"[a-f0-9]{40}") {
700 prop_assert_eq!(&string, &format!("{}", Sha1Checksum::from_str(&string).unwrap()));
701 }
702
703 #[test]
704 fn invalid_checksum_sha1_from_string_bigger_size(string in r"[a-f0-9]{41}") {
705 assert!(Sha1Checksum::from_str(&string).is_err());
706 }
707
708 #[test]
709 fn invalid_checksum_sha1_from_string_smaller_size(string in r"[a-f0-9]{39}") {
710 assert!(Sha1Checksum::from_str(&string).is_err());
711 }
712
713 #[test]
714 fn invalid_checksum_sha1_from_string_wrong_chars(string in r"[e-z0-9]{40}") {
715 assert!(Sha1Checksum::from_str(&string).is_err());
716 }
717
718 #[test]
719 fn valid_checksum_sha224_from_string(string in r"[a-f0-9]{56}") {
720 prop_assert_eq!(&string, &format!("{}", Sha224Checksum::from_str(&string).unwrap()));
721 }
722
723 #[test]
724 fn invalid_checksum_sha224_from_string_bigger_size(string in r"[a-f0-9]{57}") {
725 assert!(Sha224Checksum::from_str(&string).is_err());
726 }
727
728 #[test]
729 fn invalid_checksum_sha224_from_string_smaller_size(string in r"[a-f0-9]{55}") {
730 assert!(Sha224Checksum::from_str(&string).is_err());
731 }
732
733 #[test]
734 fn invalid_checksum_sha224_from_string_wrong_chars(string in r"[e-z0-9]{56}") {
735 assert!(Sha224Checksum::from_str(&string).is_err());
736 }
737
738 #[test]
739 fn valid_checksum_sha256_from_string(string in r"[a-f0-9]{64}") {
740 prop_assert_eq!(&string, &format!("{}", Sha256Checksum::from_str(&string).unwrap()));
741 }
742
743 #[test]
744 fn invalid_checksum_sha256_from_string_bigger_size(string in r"[a-f0-9]{65}") {
745 assert!(Sha256Checksum::from_str(&string).is_err());
746 }
747
748 #[test]
749 fn invalid_checksum_sha256_from_string_smaller_size(string in r"[a-f0-9]{63}") {
750 assert!(Sha256Checksum::from_str(&string).is_err());
751 }
752
753 #[test]
754 fn invalid_checksum_sha256_from_string_wrong_chars(string in r"[e-z0-9]{64}") {
755 assert!(Sha256Checksum::from_str(&string).is_err());
756 }
757
758 #[test]
759 fn valid_checksum_sha384_from_string(string in r"[a-f0-9]{96}") {
760 prop_assert_eq!(&string, &format!("{}", Sha384Checksum::from_str(&string).unwrap()));
761 }
762
763 #[test]
764 fn invalid_checksum_sha384_from_string_bigger_size(string in r"[a-f0-9]{97}") {
765 assert!(Sha384Checksum::from_str(&string).is_err());
766 }
767
768 #[test]
769 fn invalid_checksum_sha384_from_string_smaller_size(string in r"[a-f0-9]{95}") {
770 assert!(Sha384Checksum::from_str(&string).is_err());
771 }
772
773 #[test]
774 fn invalid_checksum_sha384_from_string_wrong_chars(string in r"[e-z0-9]{96}") {
775 assert!(Sha384Checksum::from_str(&string).is_err());
776 }
777
778 #[test]
779 fn valid_checksum_sha512_from_string(string in r"[a-f0-9]{128}") {
780 prop_assert_eq!(&string, &format!("{}", Sha512Checksum::from_str(&string).unwrap()));
781 }
782
783 #[test]
784 fn invalid_checksum_sha512_from_string_bigger_size(string in r"[a-f0-9]{129}") {
785 assert!(Sha512Checksum::from_str(&string).is_err());
786 }
787
788 #[test]
789 fn invalid_checksum_sha512_from_string_smaller_size(string in r"[a-f0-9]{127}") {
790 assert!(Sha512Checksum::from_str(&string).is_err());
791 }
792
793 #[test]
794 fn invalid_checksum_sha512_from_string_wrong_chars(string in r"[e-z0-9]{128}") {
795 assert!(Sha512Checksum::from_str(&string).is_err());
796 }
797
798 #[test]
799 fn valid_checksum_crc32cksum(sum in 0u32..=u32::MAX) {
800 let decimal_str = format!("{sum}");
801 prop_assert_eq!(
802 &decimal_str,
803 &format!("{}", Crc32CksumChecksum::from_str(decimal_str.as_str()).unwrap())
804 );
805 }
806
807 #[test]
808 fn invalid_checksum_crc32cksum_bigger_size(sum in (u32::MAX as u128)..=u128::MAX) {
809 let decimal_str = format!("{sum}");
810 assert!(Crc32CksumChecksum::from_str(decimal_str.as_str()).is_err());
811 }
812
813 #[test]
814 fn invalid_checksum_crc32cksum_wrong_chars(string in r"[a-f]{9}") {
815 assert!(Crc32CksumChecksum::from_str(&string).is_err());
816 }
817
818 #[test]
819 fn invalid_checksum_crc32cksum_negative(string in r"-[1-9]{9}") {
820 assert!(Crc32CksumChecksum::from_str(&string).is_err());
821 }
822 }
823
824 #[rstest]
825 fn checksum_blake2b512() {
826 let data = "foo\n";
827 let digest = vec![
828 210, 2, 215, 149, 29, 242, 196, 183, 17, 202, 68, 180, 188, 201, 215, 179, 99, 250, 66,
829 82, 18, 126, 5, 140, 26, 145, 14, 192, 91, 108, 208, 56, 215, 28, 194, 18, 33, 192, 49,
830 192, 53, 159, 153, 62, 116, 107, 7, 245, 150, 92, 248, 197, 195, 116, 106, 88, 51, 122,
831 217, 171, 101, 39, 142, 119,
832 ];
833 let hex_digest = "d202d7951df2c4b711ca44b4bcc9d7b363fa4252127e058c1a910ec05b6cd038d71cc21221c031c0359f993e746b07f5965cf8c5c3746a58337ad9ab65278e77";
834
835 let checksum = Blake2b512Checksum::calculate_from(data);
836 assert_eq!(digest, checksum.inner());
837 assert_eq!(format!("{}", checksum), hex_digest,);
838
839 let checksum = Blake2b512Checksum::from_str(hex_digest).unwrap();
840 assert_eq!(digest, checksum.inner());
841 assert_eq!(format!("{}", checksum), hex_digest,);
842 }
843
844 #[rstest]
845 fn checksum_sha1() {
846 let data = "foo\n";
847 let digest = vec![
848 241, 210, 210, 249, 36, 233, 134, 172, 134, 253, 247, 179, 108, 148, 188, 223, 50, 190,
849 236, 21,
850 ];
851 let hex_digest = "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15";
852
853 let checksum = Sha1Checksum::calculate_from(data);
854 assert_eq!(digest, checksum.inner());
855 assert_eq!(format!("{}", checksum), hex_digest,);
856
857 let checksum = Sha1Checksum::from_str(hex_digest).unwrap();
858 assert_eq!(digest, checksum.inner());
859 assert_eq!(format!("{}", checksum), hex_digest,);
860 }
861
862 #[rstest]
863 fn checksum_sha224() {
864 let data = "foo\n";
865 let digest = vec![
866 231, 213, 227, 110, 141, 71, 12, 62, 81, 3, 254, 221, 46, 79, 42, 165, 195, 10, 178,
867 127, 102, 41, 189, 195, 40, 111, 157, 210,
868 ];
869 let hex_digest = "e7d5e36e8d470c3e5103fedd2e4f2aa5c30ab27f6629bdc3286f9dd2";
870
871 let checksum = Sha224Checksum::calculate_from(data);
872 assert_eq!(digest, checksum.inner());
873 assert_eq!(format!("{}", checksum), hex_digest,);
874
875 let checksum = Sha224Checksum::from_str(hex_digest).unwrap();
876 assert_eq!(digest, checksum.inner());
877 assert_eq!(format!("{}", checksum), hex_digest,);
878 }
879
880 #[rstest]
881 fn checksum_sha256() {
882 let data = "foo\n";
883 let digest = vec![
884 181, 187, 157, 128, 20, 160, 249, 177, 214, 30, 33, 231, 150, 215, 141, 204, 223, 19,
885 82, 242, 60, 211, 40, 18, 244, 133, 11, 135, 138, 228, 148, 76,
886 ];
887 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
888
889 let checksum = Sha256Checksum::calculate_from(data);
890 assert_eq!(digest, checksum.inner());
891 assert_eq!(format!("{}", checksum), hex_digest,);
892
893 let checksum = Sha256Checksum::from_str(hex_digest).unwrap();
894 assert_eq!(digest, checksum.inner());
895 assert_eq!(format!("{}", checksum), hex_digest,);
896 }
897
898 #[rstest]
899 fn checksum_sha384() {
900 let data = "foo\n";
901 let digest = vec![
902 142, 255, 218, 191, 225, 68, 22, 33, 74, 37, 15, 147, 85, 5, 37, 11, 217, 145, 241, 6,
903 6, 93, 137, 157, 182, 225, 155, 220, 139, 246, 72, 243, 172, 15, 25, 53, 196, 246, 95,
904 232, 247, 152, 40, 155, 26, 13, 30, 6,
905 ];
906 let hex_digest = "8effdabfe14416214a250f935505250bd991f106065d899db6e19bdc8bf648f3ac0f1935c4f65fe8f798289b1a0d1e06";
907
908 let checksum = Sha384Checksum::calculate_from(data);
909 assert_eq!(digest, checksum.inner());
910 assert_eq!(format!("{}", checksum), hex_digest,);
911
912 let checksum = Sha384Checksum::from_str(hex_digest).unwrap();
913 assert_eq!(digest, checksum.inner());
914 assert_eq!(format!("{}", checksum), hex_digest,);
915 }
916
917 #[rstest]
918 fn checksum_sha512() {
919 let data = "foo\n";
920 let digest = vec![
921 12, 249, 24, 10, 118, 74, 186, 134, 58, 103, 182, 215, 47, 9, 24, 188, 19, 28, 103,
922 114, 100, 44, 178, 220, 229, 163, 79, 10, 112, 47, 148, 112, 221, 194, 191, 18, 92, 18,
923 25, 139, 25, 149, 194, 51, 195, 75, 74, 253, 52, 108, 84, 162, 51, 76, 53, 10, 148,
924 138, 81, 182, 232, 180, 230, 182,
925 ];
926 let hex_digest = "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6";
927
928 let checksum = Sha512Checksum::calculate_from(data);
929 assert_eq!(digest, checksum.inner());
930 assert_eq!(format!("{}", checksum), hex_digest);
931
932 let checksum = Sha512Checksum::from_str(hex_digest).unwrap();
933 assert_eq!(digest, checksum.inner());
934 assert_eq!(format!("{}", checksum), hex_digest);
935 }
936
937 #[rstest]
938 fn checksum_crc32cksum() {
939 let data = "foo\n";
940 let digest = 3915528286u32;
941 let digest_string = format!("{digest}");
942
943 let checksum = Crc32CksumChecksum::calculate_from(data);
944 assert_eq!(digest.to_be_bytes(), checksum.inner());
945 assert_eq!(format!("{}", checksum), digest_string);
946
947 let checksum = Crc32CksumChecksum::from_str(digest_string.as_str()).unwrap();
948 assert_eq!(digest.to_be_bytes(), checksum.inner());
949 assert_eq!(format!("{}", checksum), digest_string);
950 }
951
952 #[rstest]
953 #[case::non_hex_digits(
954 "0cf9180a764aba863a67b6d72f0918bc13gggggg642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6"
955 )]
956 #[case::incomplete_pair(" b ")]
957 #[case::incomplete_digest("0cf9180a764aba863a67b6d72f0918bca")]
958 #[case::whitespace(
959 "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"
960 )]
961 fn checksum_parse_error(#[case] input: &str) {
962 let Err(Error::ParseError(err_msg)) = Sha512Checksum::from_str(input) else {
963 panic!("'{input}' erroneously parsed as Sha512Checksum")
964 };
965
966 let (test_name, _guard) = configure_insta();
967 assert_snapshot!(test_name, err_msg.to_string());
968 }
969
970 #[rstest]
971 fn skippable_checksum_sha256() {
972 let hex_digest = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c";
973 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
974 assert_eq!(format!("{}", checksum), hex_digest);
975 }
976
977 #[rstest]
978 fn skippable_checksum_skip() {
979 let hex_digest = "SKIP";
980 let checksum = SkippableChecksum::<Sha256>::from_str(hex_digest).unwrap();
981
982 assert_eq!(SkippableChecksum::Skip, checksum);
983 assert_eq!(format!("{}", checksum), hex_digest);
984 }
985}