warg_crypto/hash/
dynamic.rs

1use super::{Digest, HashAlgorithm, Sha256};
2use anyhow::Error;
3use serde::{Deserialize, Serialize};
4use std::{fmt, ops::Deref, str::FromStr};
5use thiserror::Error;
6
7pub enum Hasher {
8    Sha256(Sha256),
9}
10
11impl Hasher {
12    pub fn update(&mut self, bytes: &[u8]) {
13        match self {
14            Self::Sha256(d) => d.update(bytes),
15        }
16    }
17
18    pub fn finalize(self) -> AnyHash {
19        let (algo, bytes) = match self {
20            Self::Sha256(d) => (HashAlgorithm::Sha256, d.finalize().deref().into()),
21        };
22
23        AnyHash { algo, bytes }
24    }
25}
26
27impl HashAlgorithm {
28    pub fn hasher(&self) -> Hasher {
29        match self {
30            HashAlgorithm::Sha256 => Hasher::Sha256(Sha256::new()),
31        }
32    }
33
34    pub fn digest(&self, content_bytes: &[u8]) -> AnyHash {
35        let hash_bytes: Vec<u8> = match self {
36            HashAlgorithm::Sha256 => {
37                let mut d = Sha256::new();
38                d.update(content_bytes);
39                d.finalize().deref().into()
40            }
41        };
42
43        AnyHash {
44            algo: *self,
45            bytes: hash_bytes,
46        }
47    }
48}
49
50#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
51pub struct AnyHash {
52    pub(crate) algo: HashAlgorithm,
53    pub(crate) bytes: Vec<u8>,
54}
55
56impl AnyHash {
57    pub fn new(algo: HashAlgorithm, bytes: Vec<u8>) -> AnyHash {
58        AnyHash { algo, bytes }
59    }
60
61    pub fn algorithm(&self) -> HashAlgorithm {
62        self.algo
63    }
64
65    pub fn bytes(&self) -> &[u8] {
66        &self.bytes
67    }
68}
69
70impl fmt::Display for AnyHash {
71    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72        write!(f, "{}:{}", self.algo, hex::encode(self.bytes.as_slice()))
73    }
74}
75
76impl fmt::Debug for AnyHash {
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        write!(f, "{}:{}", self.algo, hex::encode(self.bytes.as_slice()))
79    }
80}
81
82impl FromStr for AnyHash {
83    type Err = AnyHashError;
84
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        let (algo_part, bytes_part) = s
87            .split_once(':')
88            .ok_or_else(|| AnyHashError::IncorrectStructure(s.matches(':').count() + 1))?;
89
90        if bytes_part.chars().any(|c| "ABCDEF".contains(c)) {
91            return Err(AnyHashError::UppercaseHex);
92        }
93
94        let algo = algo_part.parse::<HashAlgorithm>()?;
95        let bytes = hex::decode(bytes_part)?;
96
97        Ok(AnyHash { algo, bytes })
98    }
99}
100
101#[derive(Error, Debug)]
102pub enum AnyHashError {
103    #[error("expected two parts for hash; found {0}")]
104    IncorrectStructure(usize),
105
106    #[error("unable to parse hash algorithm: {0}")]
107    InvalidHashAlgorithm(#[from] Error),
108
109    #[error("hash contained uppercase hex values")]
110    UppercaseHex,
111
112    #[error("hexadecimal decode failed: {0}")]
113    InvalidHex(#[from] hex::FromHexError),
114}
115
116impl Serialize for AnyHash {
117    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
118        serializer.serialize_str(&self.to_string())
119    }
120}
121
122impl<'de> Deserialize<'de> for AnyHash {
123    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124    where
125        D: serde::Deserializer<'de>,
126    {
127        Self::from_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_sha256_labeled_digest() {
137        let input = b"The quick brown fox jumped over the lazy dog";
138        let output = HashAlgorithm::Sha256.digest(input);
139        let output = format!("{}", output);
140
141        let expected = "sha256:7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69";
142
143        assert_eq!(output, expected)
144    }
145
146    #[test]
147    fn test_labeled_digest_parse_rejects_uppercase() {
148        let digest_str = "sha256:7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69";
149        assert!(digest_str.parse::<AnyHash>().is_ok());
150
151        let (algo, encoded) = digest_str.split_once(':').unwrap();
152        let digest_str = String::from(algo) + ":" + &encoded.to_uppercase();
153        assert!(digest_str.parse::<AnyHash>().is_err());
154    }
155
156    #[test]
157    fn test_labeled_digest_roundtrip() {
158        let input = "sha256:7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69";
159        let output = format!("{}", input.parse::<AnyHash>().unwrap());
160        assert_eq!(input, &output);
161    }
162}