tackler_core/kernel/
hash.rs1use crate::tackler;
7use digest::DynDigest;
8use std::fmt::{Debug, Formatter, Write};
9use tackler_api::metadata::Checksum;
10
11#[derive(Clone)]
12pub struct Hash {
13 hash_algo: String,
14 hasher: Box<dyn DynDigest>,
15}
16
17impl Default for Hash {
18 fn default() -> Self {
19 Hash {
20 hash_algo: "SHA-256".to_string(),
21 hasher: Box::new(sha2::Sha256::default()),
22 }
23 }
24}
25
26impl Debug for Hash {
27 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28 write!(f, "hash: {:?}", self.hash_algo)
29 }
30}
31
32impl Hash {
33 const NAMES: &'static [&'static str] =
34 &["SHA-256", "SHA-512", "SHA-512/256", "SHA3-256", "SHA3-512"];
35
36 pub fn from(algo: &str) -> Result<Hash, tackler::Error> {
44 match algo {
45 "SHA-256" => Ok(Hash {
46 hash_algo: "SHA-256".to_string(),
47 hasher: Box::new(sha2::Sha256::default()),
48 }),
49 "SHA-512" => Ok(Hash {
50 hash_algo: "SHA-512".to_string(),
51 hasher: Box::new(sha2::Sha512::default()),
52 }),
53 "SHA-512/256" => Ok(Hash {
54 hash_algo: "SHA-512/256".to_string(),
55 hasher: Box::new(sha2::Sha512_256::default()),
56 }),
57 "SHA3-256" => Ok(Hash {
58 hash_algo: "SHA3-256".to_string(),
59 hasher: Box::new(sha3::Sha3_256::default()),
60 }),
61 "SHA3-512" => Ok(Hash {
62 hash_algo: "SHA3-512".to_string(),
63 hasher: Box::new(sha3::Sha3_512::default()),
64 }),
65 _ => {
66 let mut msg = format!("Unknown hash algorithm: '{algo}'. ");
67 writeln!(msg, "Valid options are: {}", Self::NAMES.join(", "))?;
68 Err(msg.into())
69 }
70 }
71 }
72
73 #[must_use]
75 pub fn checksum(&self, items: &[String], separator: &[u8]) -> Checksum {
76 let mut hasher = self.hasher.clone();
77
78 for i in items {
79 hasher.update(i.as_bytes());
80 hasher.update(separator);
81 }
82 let hash = hasher.finalize();
83
84 Checksum {
85 algorithm: self.hash_algo.clone(),
86 value: hash.iter().fold(String::new(), |mut output, b| {
87 let _ = write!(output, "{b:02x}");
88 output
89 }),
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn hasher_no_input() {
100 let hash = Hash::from("SHA-256").unwrap();
101
102 let uuids = vec![];
103 let cs = hash.checksum(&uuids, "\n".as_bytes());
104
105 assert_eq!(
108 cs.value,
109 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
110 );
111 }
112
113 #[test]
114 fn hasher_empty_input() {
115 let hash = Hash::from("SHA-256").unwrap();
116
117 let uuids = vec![String::new()];
118 let cs = hash.checksum(&uuids, "\n".as_bytes());
119
120 assert_eq!(
123 cs.value,
124 "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
125 );
126 }
127
128 #[test]
129 fn hasher_multiple_rounds() {
130 let hash = Hash::from("SHA-256").unwrap();
131
132 let foo = vec!["foo".to_string()];
133 let cs_foo = hash.checksum(&foo, "\n".as_bytes());
134
135 assert_eq!(
136 cs_foo.value,
137 "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"
138 );
139
140 let bar = vec!["bar".to_string()];
141 let cs_bar = hash.checksum(&bar, "\n".as_bytes());
142 assert_eq!(
143 cs_bar.value,
144 "7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730"
145 );
146 }
147
148 #[test]
149 fn hash_functions() {
151 let mut c = 0;
152 for name in Hash::NAMES {
153 let hash = Hash::from(name);
154 assert!(hash.is_ok());
155 c += 1;
156 }
157 assert_eq!(c, Hash::NAMES.len());
158 }
159
160 #[test]
161 fn hasher_err() {
163 let hash = Hash::from("foo");
164 let mut msg = "Unknown hash algorithm: 'foo'. ".to_string();
165 let _ = writeln!(
166 msg,
167 "Valid options are: SHA-256, SHA-512, SHA-512/256, SHA3-256, SHA3-512"
168 );
169
170 assert!(hash.is_err());
171 assert_eq!(hash.err().unwrap().to_string(), msg);
172 }
173
174 fn get_test_vector() -> Vec<String> {
178 vec![
179 "9c123cbe-4acd-475d-bbcf-96c1fcba58cb".to_string(),
180 "2e546b18-6ce6-4bb3-9f4b-21b77a768a4c".to_string(),
181 "67bdab27-da08-4647-b0d1-57c9ed129657".to_string(),
182 ]
183 }
184
185 #[test]
186 fn hasher_sha2_256() {
187 let hash = Hash::from("SHA-256").unwrap();
188
189 let uuids = get_test_vector();
190 let cs = hash.checksum(&uuids, "\n".as_bytes());
191
192 assert_eq!(
193 cs.value,
194 "16418783ef294f830721159ee59cc3388c8b69c13afba2256cf756c6097fe687"
195 );
196 }
197 #[test]
198 fn hasher_sha2_512() {
199 let hash = Hash::from("SHA-512").unwrap();
200
201 let uuids = get_test_vector();
202 let cs = hash.checksum(&uuids, "\n".as_bytes());
203
204 assert_eq!(
205 cs.value,
206 "51a370f86b7218012c8a7a555a1ae099b32fe83ed032e82481de5fa7ea3a90baa6948a4f559668ad3696a08b0445fe4e5964dba695b45653b4e678ab200ede17"
207 );
208 }
209
210 #[test]
211 fn hasher_sha2_512_256() {
212 let hash = Hash::from("SHA-512/256").unwrap();
213
214 let uuids = get_test_vector();
215 let cs = hash.checksum(&uuids, "\n".as_bytes());
216
217 assert_eq!(
218 cs.value,
219 "fd5e794d47589c83ed5def2485699f232aed7e7df2869ef8b681cbf07af7cb66"
220 );
221 }
222
223 #[test]
224 fn hasher_sha3_256() {
225 let hash = Hash::from("SHA3-256").unwrap();
226
227 let uuids = get_test_vector();
228 let cs = hash.checksum(&uuids, "\n".as_bytes());
229
230 assert_eq!(
231 cs.value,
232 "ee677c3486b0b6c8d61eb3b5cb650762dbf741bb6b050934f4c3c4f6551d1841"
233 );
234 }
235
236 #[test]
237 fn hasher_sha3_512() {
238 let hash = Hash::from("SHA3-512").unwrap();
239
240 let uuids = get_test_vector();
241 let cs = hash.checksum(&uuids, "\n".as_bytes());
242
243 assert_eq!(
244 cs.value,
245 "6cc3e6d3eb1d920ac3439a0b748244aa5f997ba0a813457bae7b99f3f58f035ce18eb3646a35517c19987dfe0bb60ebcc81ee5320f4d2348132fdb55bb8d8a25"
246 );
247 }
248}