cargo_lock/package/
checksum.rs

1//! Package checksums (i.e. SHA-256 digests)
2
3use crate::{Error, Result};
4use serde::{Deserialize, Serialize, de, ser};
5use std::{fmt, str::FromStr};
6
7/// Cryptographic checksum (SHA-256) for a package
8#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
9pub enum Checksum {
10    /// SHA-256 digest of a package
11    Sha256([u8; 32]),
12}
13
14impl Checksum {
15    /// Is this checksum SHA-256?
16    pub fn is_sha256(&self) -> bool {
17        self.as_sha256().is_some()
18    }
19
20    /// If this is a SHA-256 checksum, get the raw bytes
21    pub fn as_sha256(&self) -> Option<[u8; 32]> {
22        match self {
23            Checksum::Sha256(digest) => Some(*digest),
24        }
25    }
26}
27
28impl From<[u8; 32]> for Checksum {
29    fn from(bytes: [u8; 32]) -> Checksum {
30        Checksum::Sha256(bytes)
31    }
32}
33
34impl FromStr for Checksum {
35    type Err = Error;
36
37    fn from_str(s: &str) -> Result<Self> {
38        if s.len() != 64 {
39            return Err(Error::Parse(format!(
40                "invalid checksum: expected 64 hex chars, got {}",
41                s.len()
42            )));
43        }
44
45        let mut digest = [0u8; 32];
46
47        for (i, byte) in digest.iter_mut().enumerate() {
48            *byte = u8::from_str_radix(&s[(i * 2)..=(i * 2) + 1], 16)?;
49        }
50
51        Ok(Checksum::Sha256(digest))
52    }
53}
54
55impl fmt::Debug for Checksum {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            Checksum::Sha256(_) => write!(f, "Sha256({self:x})"),
59        }
60    }
61}
62
63impl fmt::Display for Checksum {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "{self:x}")
66    }
67}
68
69impl fmt::LowerHex for Checksum {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Checksum::Sha256(digest) => {
73                for b in digest {
74                    write!(f, "{b:02x}")?;
75                }
76            }
77        }
78
79        Ok(())
80    }
81}
82
83impl fmt::UpperHex for Checksum {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Checksum::Sha256(digest) => {
87                for b in digest {
88                    write!(f, "{b:02X}")?;
89                }
90            }
91        }
92
93        Ok(())
94    }
95}
96
97impl<'de> Deserialize<'de> for Checksum {
98    fn deserialize<D: de::Deserializer<'de>>(
99        deserializer: D,
100    ) -> std::result::Result<Self, D::Error> {
101        let hex = String::deserialize(deserializer)?;
102        hex.parse().map_err(de::Error::custom)
103    }
104}
105
106impl Serialize for Checksum {
107    fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
108        self.to_string().serialize(serializer)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::{Checksum, Error};
115
116    #[test]
117    fn checksum_round_trip() {
118        let checksum_str = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5";
119        let checksum = checksum_str.parse::<Checksum>().unwrap();
120        assert_eq!(checksum_str, checksum.to_string());
121    }
122
123    #[test]
124    fn invalid_checksum() {
125        // Missing one hex letter
126        let invalid_str = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f";
127        let error = invalid_str.parse::<Checksum>().err().unwrap();
128        assert!(matches!(error, Error::Parse(_)));
129    }
130}