1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Package checksums (i.e. SHA-256 digests)

use crate::{Error, Result};
use serde::{de, ser, Deserialize, Serialize};
use std::{fmt, str::FromStr};

/// Cryptographic checksum (SHA-256) for a package
#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Checksum {
    /// SHA-256 digest of a package
    Sha256([u8; 32]),
}

impl Checksum {
    /// Is this checksum SHA-256?
    pub fn is_sha256(&self) -> bool {
        self.as_sha256().is_some()
    }

    /// If this is a SHA-256 checksum, get the raw bytes
    pub fn as_sha256(&self) -> Option<[u8; 32]> {
        match self {
            Checksum::Sha256(digest) => Some(*digest),
        }
    }
}

impl From<[u8; 32]> for Checksum {
    fn from(bytes: [u8; 32]) -> Checksum {
        Checksum::Sha256(bytes)
    }
}

impl FromStr for Checksum {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        if s.len() != 64 {
            return Err(Error::Parse(format!(
                "invalid checksum: expected 64 hex chars, got {}",
                s.len()
            )));
        }

        let mut digest = [0u8; 32];

        for (i, byte) in digest.iter_mut().enumerate() {
            *byte = u8::from_str_radix(&s[(i * 2)..=(i * 2) + 1], 16)?;
        }

        Ok(Checksum::Sha256(digest))
    }
}

impl fmt::Debug for Checksum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Checksum::Sha256(_) => write!(f, "Sha256({:x})", self),
        }
    }
}

impl fmt::Display for Checksum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:x}", self)
    }
}

impl fmt::LowerHex for Checksum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Checksum::Sha256(digest) => {
                for b in digest {
                    write!(f, "{:02x}", b)?;
                }
            }
        }

        Ok(())
    }
}

impl fmt::UpperHex for Checksum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Checksum::Sha256(digest) => {
                for b in digest {
                    write!(f, "{:02X}", b)?;
                }
            }
        }

        Ok(())
    }
}

impl<'de> Deserialize<'de> for Checksum {
    fn deserialize<D: de::Deserializer<'de>>(
        deserializer: D,
    ) -> std::result::Result<Self, D::Error> {
        let hex = String::deserialize(deserializer)?;
        hex.parse().map_err(de::Error::custom)
    }
}

impl Serialize for Checksum {
    fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
        self.to_string().serialize(serializer)
    }
}

#[cfg(test)]
mod tests {
    use super::{Checksum, Error};

    #[test]
    fn checksum_round_trip() {
        let checksum_str = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5";
        let checksum = checksum_str.parse::<Checksum>().unwrap();
        assert_eq!(checksum_str, checksum.to_string());
    }

    #[test]
    fn invalid_checksum() {
        // Missing one hex letter
        let invalid_str = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f";
        let error = invalid_str.parse::<Checksum>().err().unwrap();
        assert!(matches!(error, Error::Parse(_)));
    }
}