1use serde::{Deserialize, Serialize};
2use sha2::{Digest, Sha256};
3use std::fmt;
4
5use crate::types::Xattr;
6use crate::Error;
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
10pub struct Hash([u8; 32]);
11
12impl Hash {
13 pub const ZERO: Hash = Hash([0u8; 32]);
15
16 pub fn from_bytes(bytes: [u8; 32]) -> Self {
18 Self(bytes)
19 }
20
21 pub fn from_hex(s: &str) -> crate::Result<Self> {
23 let bytes = hex::decode(s).map_err(|_| Error::InvalidHashHex(s.to_string()))?;
24 if bytes.len() != 32 {
25 return Err(Error::InvalidHashHex(s.to_string()));
26 }
27 let mut arr = [0u8; 32];
28 arr.copy_from_slice(&bytes);
29 Ok(Self(arr))
30 }
31
32 pub fn as_bytes(&self) -> &[u8; 32] {
34 &self.0
35 }
36
37 pub fn to_hex(&self) -> String {
39 hex::encode(self.0)
40 }
41
42 pub fn to_path_components(&self) -> (String, String) {
45 let hex = self.to_hex();
46 (hex[..2].to_string(), hex[2..].to_string())
47 }
48}
49
50impl fmt::Display for Hash {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "{}", self.to_hex())
53 }
54}
55
56impl fmt::Debug for Hash {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "Hash({})", &self.to_hex()[..12])
59 }
60}
61
62impl Serialize for Hash {
63 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64 where
65 S: serde::Serializer,
66 {
67 serializer.serialize_str(&self.to_hex())
68 }
69}
70
71impl<'de> Deserialize<'de> for Hash {
72 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73 where
74 D: serde::Deserializer<'de>,
75 {
76 let s = String::deserialize(deserializer)?;
77 Self::from_hex(&s).map_err(serde::de::Error::custom)
78 }
79}
80
81pub const SYMLINK_MODE: u32 = 0o120777;
83
84pub fn compute_blob_hash(
100 inside_uid: u32,
101 inside_gid: u32,
102 mode: u32,
103 xattrs: &[Xattr],
104 content: &[u8],
105) -> Hash {
106 let mut hasher = Sha256::new();
107
108 hasher.update(&inside_uid.to_le_bytes());
110 hasher.update(&inside_gid.to_le_bytes());
111 hasher.update(&mode.to_le_bytes());
112
113 let mut sorted: Vec<_> = xattrs.iter().collect();
115 sorted.sort_by(|a, b| a.name.as_bytes().cmp(b.name.as_bytes()));
116
117 hasher.update(&(sorted.len() as u32).to_le_bytes());
118 for xattr in sorted {
119 hasher.update(&(xattr.name.len() as u32).to_le_bytes());
120 hasher.update(xattr.name.as_bytes());
121 hasher.update(&(xattr.value.len() as u32).to_le_bytes());
122 hasher.update(&xattr.value);
123 }
124
125 hasher.update(content);
127
128 Hash(hasher.finalize().into())
129}
130
131pub fn compute_symlink_hash(
134 inside_uid: u32,
135 inside_gid: u32,
136 xattrs: &[Xattr],
137 target: &str,
138) -> Hash {
139 compute_blob_hash(
140 inside_uid,
141 inside_gid,
142 SYMLINK_MODE,
143 xattrs,
144 target.as_bytes(),
145 )
146}
147
148#[allow(dead_code)]
150pub fn compute_compressed_hash(compressed: &[u8]) -> Hash {
151 let digest = Sha256::digest(compressed);
152 Hash(digest.into())
153}
154
155#[allow(dead_code)]
157pub struct BlobHasher {
158 hasher: Sha256,
159}
160
161impl BlobHasher {
162 pub fn new(inside_uid: u32, inside_gid: u32, mode: u32, xattrs: &[Xattr]) -> Self {
164 let mut hasher = Sha256::new();
165
166 hasher.update(&inside_uid.to_le_bytes());
167 hasher.update(&inside_gid.to_le_bytes());
168 hasher.update(&mode.to_le_bytes());
169
170 let mut sorted: Vec<_> = xattrs.iter().collect();
171 sorted.sort_by(|a, b| a.name.as_bytes().cmp(b.name.as_bytes()));
172
173 hasher.update(&(sorted.len() as u32).to_le_bytes());
174 for xattr in sorted {
175 hasher.update(&(xattr.name.len() as u32).to_le_bytes());
176 hasher.update(xattr.name.as_bytes());
177 hasher.update(&(xattr.value.len() as u32).to_le_bytes());
178 hasher.update(&xattr.value);
179 }
180
181 Self { hasher }
182 }
183
184 pub fn update(&mut self, data: &[u8]) {
186 self.hasher.update(data);
187 }
188
189 pub fn finalize(self) -> Hash {
191 Hash(self.hasher.finalize().into())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_hash_hex_roundtrip() {
201 let original =
202 Hash::from_hex("abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789")
203 .unwrap();
204 let hex = original.to_hex();
205 let parsed = Hash::from_hex(&hex).unwrap();
206 assert_eq!(original, parsed);
207 }
208
209 #[test]
210 fn test_hash_invalid_hex() {
211 assert!(Hash::from_hex("not valid hex").is_err());
212 assert!(Hash::from_hex("abcd").is_err()); assert!(Hash::from_hex(
214 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ff"
215 )
216 .is_err()); }
218
219 #[test]
220 fn test_hash_path_components() {
221 let h = Hash::from_hex("abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789")
222 .unwrap();
223 let (dir, file) = h.to_path_components();
224 assert_eq!(dir, "ab");
225 assert_eq!(
226 file,
227 "cdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
228 );
229 }
230
231 #[test]
232 fn test_hash_ordering() {
233 let h1 = Hash::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
234 .unwrap();
235 let h2 = Hash::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
236 .unwrap();
237 assert!(h1 < h2);
238 }
239
240 #[test]
241 fn test_blob_hash_determinism() {
242 let h1 = compute_blob_hash(0, 0, 0o644, &[], b"hello");
243 let h2 = compute_blob_hash(0, 0, 0o644, &[], b"hello");
244 assert_eq!(h1, h2);
245 }
246
247 #[test]
248 fn test_blob_hash_different_uid() {
249 let h1 = compute_blob_hash(0, 0, 0o644, &[], b"hello");
250 let h2 = compute_blob_hash(1, 0, 0o644, &[], b"hello");
251 assert_ne!(h1, h2);
252 }
253
254 #[test]
255 fn test_blob_hash_different_gid() {
256 let h1 = compute_blob_hash(0, 0, 0o644, &[], b"hello");
257 let h2 = compute_blob_hash(0, 1, 0o644, &[], b"hello");
258 assert_ne!(h1, h2);
259 }
260
261 #[test]
262 fn test_blob_hash_different_mode() {
263 let h1 = compute_blob_hash(0, 0, 0o644, &[], b"hello");
264 let h2 = compute_blob_hash(0, 0, 0o755, &[], b"hello");
265 assert_ne!(h1, h2);
266 }
267
268 #[test]
269 fn test_blob_hash_different_content() {
270 let h1 = compute_blob_hash(0, 0, 0o644, &[], b"hello");
271 let h2 = compute_blob_hash(0, 0, 0o644, &[], b"world");
272 assert_ne!(h1, h2);
273 }
274
275 #[test]
276 fn test_blob_hash_different_xattr() {
277 let x1 = vec![Xattr::new("user.test", vec![1, 2, 3])];
278 let x2 = vec![Xattr::new("user.test", vec![4, 5, 6])];
279
280 let h1 = compute_blob_hash(0, 0, 0o644, &x1, b"hello");
281 let h2 = compute_blob_hash(0, 0, 0o644, &x2, b"hello");
282 assert_ne!(h1, h2);
283 }
284
285 #[test]
286 fn test_blob_hash_xattr_ordering() {
287 let x1 = vec![Xattr::new("user.a", vec![1]), Xattr::new("user.b", vec![2])];
289 let x2 = vec![Xattr::new("user.b", vec![2]), Xattr::new("user.a", vec![1])];
290
291 let h1 = compute_blob_hash(0, 0, 0o644, &x1, b"hello");
292 let h2 = compute_blob_hash(0, 0, 0o644, &x2, b"hello");
293 assert_eq!(h1, h2);
294 }
295
296 #[test]
297 fn test_blob_hash_empty_content() {
298 let h = compute_blob_hash(0, 0, 0o644, &[], b"");
299 assert_ne!(h, Hash::ZERO);
300 }
301
302 #[test]
303 fn test_blob_hash_empty_xattr_value() {
304 let x = vec![Xattr::new("user.empty", vec![])];
305 let h = compute_blob_hash(0, 0, 0o644, &x, b"hello");
306 assert_ne!(h, Hash::ZERO);
307 }
308
309 #[test]
310 fn test_symlink_hash() {
311 let h1 = compute_symlink_hash(0, 0, &[], "/target/path");
312 let h2 = compute_symlink_hash(0, 0, &[], "/target/path");
313 assert_eq!(h1, h2);
314
315 let h3 = compute_symlink_hash(0, 0, &[], "/other/path");
317 assert_ne!(h1, h3);
318 }
319
320 #[test]
321 fn test_streaming_hasher() {
322 let direct = compute_blob_hash(0, 0, 0o644, &[], b"helloworld");
323
324 let mut streaming = BlobHasher::new(0, 0, 0o644, &[]);
325 streaming.update(b"hello");
326 streaming.update(b"world");
327 let streamed = streaming.finalize();
328
329 assert_eq!(direct, streamed);
330 }
331
332 #[test]
333 fn test_hash_serde_json() {
334 let h = Hash::from_hex("abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789")
335 .unwrap();
336 let json = serde_json::to_string(&h).unwrap();
337 assert!(json.contains("abcdef"));
338 let parsed: Hash = serde_json::from_str(&json).unwrap();
339 assert_eq!(h, parsed);
340 }
341}