1use super::{BASE32, Base32};
2pub(crate) const BLAKE3_BYTES: usize = 32;
3#[cfg(feature = "git2")]
4pub(crate) const GIT_SHA1_BYTES: usize = 20;
5#[cfg(feature = "git2")]
6pub(crate) const GIT_SHA2_BYTES: usize = 32;
7
8#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
10pub enum Hash {
11 None,
14 Blake3([u8; BLAKE3_BYTES]),
15 #[cfg(feature = "git2")]
16 GitSha1([u8; 20]),
17 #[cfg(feature = "git2")]
18 GitSha2([u8; 32]),
19}
20
21pub enum Hasher {
22 Blake3(blake3::Hasher),
23}
24
25impl Default for Hasher {
26 fn default() -> Self {
27 Hasher::Blake3(blake3::Hasher::new())
28 }
29}
30
31impl Hasher {
32 pub fn update(&mut self, bytes: &[u8]) {
33 match self {
34 Hasher::Blake3(h) => {
35 h.update(bytes);
36 }
37 }
38 }
39 pub fn finish(&self) -> Hash {
40 match self {
41 Hasher::Blake3(h) => {
42 let result = h.finalize();
43 let mut hash = [0; BLAKE3_BYTES];
44 hash.clone_from_slice(result.as_bytes());
45 Hash::Blake3(hash)
46 }
47 }
48 }
49}
50
51impl std::fmt::Debug for Hash {
52 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
53 write!(fmt, "{}", self.to_base32())
54 }
55}
56
57#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
59#[repr(u8)]
60pub enum HashAlgorithm {
61 None = 0,
62 Blake3 = 1,
63 GitSha1 = 2,
64 GitSha2 = 3,
65}
66
67impl Hash {
68 pub fn to_bytes(&self) -> [u8; 1 + BLAKE3_BYTES] {
69 match *self {
70 Hash::None => unimplemented!(),
71 Hash::Blake3(ref s) => {
72 let mut out = [0; 1 + BLAKE3_BYTES];
73 out[0] = HashAlgorithm::Blake3 as u8;
74 out[1..].clone_from_slice(s);
75 out
76 }
77 #[cfg(feature = "git2")]
78 Hash::GitSha1(ref s) => {
79 let mut out = [0; 1 + BLAKE3_BYTES];
80 out[0] = HashAlgorithm::GitSha1 as u8;
81 out[1..21].clone_from_slice(s);
82 out
83 }
84 #[cfg(feature = "git2")]
85 Hash::GitSha2(ref s) => {
86 let mut out = [0; 1 + BLAKE3_BYTES];
87 out[0] = HashAlgorithm::GitSha2 as u8;
88 out[1..].clone_from_slice(s);
89 out
90 }
91 }
92 }
93
94 #[allow(clippy::int_plus_one)]
95 pub fn from_bytes(s: &[u8]) -> Option<Self> {
96 if s.len() >= 1 + BLAKE3_BYTES && s[0] == HashAlgorithm::Blake3 as u8 {
97 let mut out = [0; BLAKE3_BYTES];
98 out.clone_from_slice(&s[1..]);
99 return Some(Hash::Blake3(out));
100 }
101
102 #[cfg(feature = "git2")]
103 if s.len() >= 1 + GIT_SHA1_BYTES && s[0] == HashAlgorithm::GitSha1 as u8 {
104 let mut out = [0; GIT_SHA1_BYTES];
105 out.clone_from_slice(&s[1..]);
106 return Some(Hash::GitSha1(out));
107 }
108
109 #[cfg(feature = "git2")]
110 if s.len() >= 1 + GIT_SHA2_BYTES && s[0] == HashAlgorithm::GitSha2 as u8 {
111 let mut out = [0; GIT_SHA2_BYTES];
112 out.clone_from_slice(&s[1..]);
113 return Some(Hash::GitSha2(out));
114 }
115
116 None
117 }
118}
119
120#[cfg(feature = "git2")]
121impl TryInto<git2::Oid> for Hash {
122 type Error = ();
123 fn try_into(self) -> Result<git2::Oid, Self::Error> {
124 match self {
125 Hash::GitSha1(s) => Ok(git2::Oid::from_bytes(&s).unwrap()),
126 Hash::GitSha2(s) => Ok(git2::Oid::from_bytes(&s).unwrap()),
127 _ => Err(()),
128 }
129 }
130}
131
132pub fn prefix_guesses(s: &str) -> impl Iterator<Item = Hash> {
133 #[cfg(not(feature = "git2"))]
134 let hashes = [Hash::None, Hash::Blake3([0; BLAKE3_BYTES])];
135
136 #[cfg(feature = "git2")]
137 let hashes = [
138 Hash::None,
139 Hash::Blake3([0; BLAKE3_BYTES]),
140 Hash::GitSha1([0; GIT_SHA1_BYTES]),
141 Hash::GitSha2([0; GIT_SHA2_BYTES]),
142 ];
143
144 let from = Hash::from_base32(s.as_bytes());
145 let from_is_some = from.is_some();
146 let decode_len = (s.len() * 5) / 8;
147 let s = s.split_at(BASE32.encode_len(decode_len)).0;
148 debug!("decode_len {:?} {:?}", decode_len, from);
149 from.into_iter()
150 .chain(hashes.into_iter().filter_map(move |h| match h {
151 _ if from_is_some => None,
152 Hash::None if s == "A" || s == "AA" => Some(Hash::None),
153 Hash::Blake3(mut b) if b.len() >= decode_len => {
154 match BASE32.decode_mut(s.as_bytes(), &mut b[..decode_len]) {
155 Err(data_encoding::DecodePartial {
156 error:
157 data_encoding::DecodeError {
158 kind: data_encoding::DecodeKind::Symbol,
159 ..
160 },
161 ..
162 }) => None,
163 _ => Some(Hash::Blake3(b)),
164 }
165 }
166 #[cfg(feature = "git2")]
167 Hash::GitSha1(mut b) if b.len() >= decode_len => {
168 match BASE32.decode_mut(s.as_bytes(), &mut b[..decode_len]) {
169 Err(data_encoding::DecodePartial {
170 error:
171 data_encoding::DecodeError {
172 kind: data_encoding::DecodeKind::Symbol,
173 ..
174 },
175 ..
176 }) => None,
177 _ => Some(Hash::GitSha1(b)),
178 }
179 }
180 #[cfg(feature = "git2")]
181 Hash::GitSha2(mut b) if b.len() >= decode_len => {
182 match BASE32.decode_mut(s.as_bytes(), &mut b[..decode_len]) {
183 Err(data_encoding::DecodePartial {
184 error:
185 data_encoding::DecodeError {
186 kind: data_encoding::DecodeKind::Symbol,
187 ..
188 },
189 ..
190 }) => None,
191 _ => Some(Hash::GitSha2(b)),
192 }
193 }
194 x => {
195 debug!("none {:?}", x);
196 None
197 }
198 }))
199}
200
201impl super::Base32 for Hash {
202 fn to_base32(&self) -> String {
204 match *self {
205 Hash::None => BASE32.encode(&[0]),
206 Hash::Blake3(ref s) => {
207 let mut hash = [0; 1 + BLAKE3_BYTES];
208 hash[BLAKE3_BYTES] = HashAlgorithm::Blake3 as u8;
209 hash[..BLAKE3_BYTES].clone_from_slice(s);
210 BASE32.encode(&hash)
211 }
212 #[cfg(feature = "git2")]
213 Hash::GitSha1(ref s) => {
214 let mut hash = [0; 1 + GIT_SHA1_BYTES];
215 hash[20] = HashAlgorithm::GitSha1 as u8;
216 hash[..GIT_SHA1_BYTES].clone_from_slice(s);
217 BASE32.encode(&hash)
218 }
219 #[cfg(feature = "git2")]
220 Hash::GitSha2(ref s) => {
221 let mut hash = [0; 1 + GIT_SHA2_BYTES];
222 hash[20] = HashAlgorithm::GitSha2 as u8;
223 hash[..GIT_SHA2_BYTES].clone_from_slice(s);
224 BASE32.encode(&hash)
225 }
226 }
227 }
228
229 fn from_base32(s: &[u8]) -> Option<Self> {
231 let bytes = if let Ok(s) = BASE32.decode(s) {
232 s
233 } else {
234 return None;
235 };
236 if bytes == [0] {
237 return Some(Hash::None);
238 } else if bytes.len() == BLAKE3_BYTES + 1
239 && bytes[BLAKE3_BYTES] == HashAlgorithm::Blake3 as u8
240 {
241 let mut hash = [0; BLAKE3_BYTES];
242 hash.clone_from_slice(&bytes[..BLAKE3_BYTES]);
243 return Some(Hash::Blake3(hash));
244 }
245
246 #[cfg(feature = "git2")]
247 if bytes.len() == GIT_SHA1_BYTES + 1
248 && bytes[GIT_SHA1_BYTES] == HashAlgorithm::GitSha1 as u8
249 {
250 let mut hash = [0; GIT_SHA1_BYTES];
251 hash.clone_from_slice(&bytes[..GIT_SHA1_BYTES]);
252 return Some(Hash::GitSha1(hash));
253 }
254
255 #[cfg(feature = "git2")]
256 if bytes.len() == GIT_SHA2_BYTES + 1
257 && bytes[GIT_SHA2_BYTES] == HashAlgorithm::GitSha2 as u8
258 {
259 let mut hash = [0; GIT_SHA2_BYTES];
260 hash.clone_from_slice(&bytes[..GIT_SHA2_BYTES]);
261 return Some(Hash::GitSha2(hash));
262 }
263
264 None
265 }
266}
267
268impl std::str::FromStr for Hash {
269 type Err = crate::ParseError;
270 fn from_str(s: &str) -> Result<Self, Self::Err> {
271 if let Some(b) = Self::from_base32(s.as_bytes()) {
272 Ok(b)
273 } else {
274 Err(crate::ParseError { s: s.to_string() })
275 }
276 }
277}
278
279#[test]
280fn from_to() {
281 let mut h = Hasher::default();
282 h.update(b"blabla");
283 let h = h.finish();
284 assert_eq!(Hash::from_base32(&h.to_base32().as_bytes()), Some(h));
285 let h = Hash::None;
286 assert_eq!(Hash::from_base32(&h.to_base32().as_bytes()), Some(h));
287 let b = BASE32.encode(&[19, 18, 17]);
288 assert_eq!(Hash::from_base32(&b.as_bytes()), None);
289}
290
291#[derive(Clone, Copy)]
292#[repr(C)]
293pub struct SerializedHash {
294 pub(crate) t: u8,
295 h: H,
296}
297
298impl std::hash::Hash for SerializedHash {
299 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
300 self.t.hash(hasher);
301 if self.t == HashAlgorithm::Blake3 as u8 {
302 unsafe { self.h.blake3.hash(hasher) }
303 }
304
305 #[cfg(feature = "git2")]
306 if self.t == HashAlgorithm::GitSha1 as u8 {
307 unsafe { self.h.git_sha1.hash(hasher) }
308 }
309
310 #[cfg(feature = "git2")]
311 if self.t == HashAlgorithm::GitSha2 as u8 {
312 unsafe { self.h.git_sha2.hash(hasher) }
313 }
314 }
315}
316
317#[derive(Clone, Copy)]
318pub(crate) union H {
319 none: (),
320 blake3: [u8; BLAKE3_BYTES],
321 #[cfg(feature = "git2")]
322 git_sha1: [u8; GIT_SHA1_BYTES],
323 #[cfg(feature = "git2")]
324 git_sha2: [u8; GIT_SHA2_BYTES],
325}
326
327pub(crate) const HASH_NONE: SerializedHash = SerializedHash {
328 t: HashAlgorithm::None as u8,
329 h: H { none: () },
330};
331
332use std::cmp::Ordering;
333
334impl PartialOrd for SerializedHash {
335 fn partial_cmp(&self, b: &Self) -> Option<Ordering> {
336 Some(self.cmp(b))
337 }
338}
339
340impl Ord for SerializedHash {
341 fn cmp(&self, b: &Self) -> Ordering {
342 match self.t.cmp(&b.t) {
343 Ordering::Equal => {
344 if self.t == HashAlgorithm::Blake3 as u8 {
345 return unsafe { self.h.blake3.cmp(&b.h.blake3) };
346 }
347
348 #[cfg(feature = "git2")]
349 if self.t == HashAlgorithm::GitSha1 as u8 {
350 return unsafe { self.h.git_sha1.cmp(&b.h.git_sha1) };
351 } else if self.t == HashAlgorithm::GitSha2 as u8 {
352 return unsafe { self.h.git_sha2.cmp(&b.h.git_sha2) };
353 }
354
355 Ordering::Equal
356 }
357 o => o,
358 }
359 }
360}
361
362impl PartialEq for SerializedHash {
363 fn eq(&self, b: &Self) -> bool {
364 if self.t == HashAlgorithm::Blake3 as u8 && self.t == b.t {
365 return unsafe { self.h.blake3 == b.h.blake3 };
366 }
367
368 #[cfg(feature = "git2")]
369 if self.t == HashAlgorithm::GitSha1 as u8 && self.t == b.t {
370 return unsafe { self.h.git_sha1 == b.h.git_sha1 };
371 } else if self.t == HashAlgorithm::GitSha2 as u8 && self.t == b.t {
372 return unsafe { self.h.git_sha2 == b.h.git_sha2 };
373 }
374
375 self.t == b.t
376 }
377}
378impl Eq for SerializedHash {}
379
380impl std::fmt::Debug for SerializedHash {
381 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
382 Hash::from(self).fmt(fmt)
383 }
384}
385
386impl<'a> From<&'a SerializedHash> for Hash {
387 fn from(s: &'a SerializedHash) -> Hash {
388 if s.t == HashAlgorithm::Blake3 as u8 {
389 return Hash::Blake3(unsafe { s.h.blake3 });
390 }
391
392 #[cfg(feature = "git2")]
393 if s.t == HashAlgorithm::GitSha1 as u8 {
394 return Hash::GitSha1(unsafe { s.h.git_sha1 });
395 } else if s.t == HashAlgorithm::GitSha2 as u8 {
396 return Hash::GitSha2(unsafe { s.h.git_sha2 });
397 }
398
399 if s.t == HashAlgorithm::None as u8 {
400 Hash::None
401 } else {
402 panic!("Unknown hash algorithm {:?}", s.t)
403 }
404 }
405}
406
407#[cfg(feature = "git2")]
408impl From<git2::Oid> for Hash {
409 fn from(s: git2::Oid) -> Hash {
410 let b = s.as_bytes();
411 if b.len() == 20 {
412 let mut h = [0; GIT_SHA1_BYTES];
413 h.clone_from_slice(b);
414 Hash::GitSha1(h)
415 } else {
416 assert_eq!(b.len(), GIT_SHA2_BYTES);
417 let mut h = [0; GIT_SHA2_BYTES];
418 h.clone_from_slice(b);
419 Hash::GitSha2(h)
420 }
421 }
422}
423
424impl From<SerializedHash> for Hash {
425 fn from(s: SerializedHash) -> Hash {
426 (&s).into()
427 }
428}
429
430impl From<Hash> for SerializedHash {
431 fn from(s: Hash) -> SerializedHash {
432 (&s).into()
433 }
434}
435
436impl<'a> From<&'a Hash> for SerializedHash {
437 fn from(s: &'a Hash) -> Self {
438 match s {
439 Hash::Blake3(s) => SerializedHash {
440 t: HashAlgorithm::Blake3 as u8,
441 h: H { blake3: *s },
442 },
443 #[cfg(feature = "git2")]
444 Hash::GitSha1(s) => SerializedHash {
445 t: HashAlgorithm::GitSha1 as u8,
446 h: H { git_sha1: *s },
447 },
448 #[cfg(feature = "git2")]
449 Hash::GitSha2(s) => SerializedHash {
450 t: HashAlgorithm::GitSha2 as u8,
451 h: H { git_sha2: *s },
452 },
453 Hash::None => SerializedHash {
454 t: 0,
455 h: H { none: () },
456 },
457 }
458 }
459}
460
461impl SerializedHash {
462 pub fn size(b: &[u8]) -> usize {
463 if b[0] == HashAlgorithm::Blake3 as u8 {
464 return 1 + BLAKE3_BYTES;
465 }
466
467 #[cfg(feature = "git2")]
468 if b[0] == HashAlgorithm::GitSha1 as u8 {
469 return 1 + GIT_SHA1_BYTES;
470 }
471
472 #[cfg(feature = "git2")]
473 if b[0] == HashAlgorithm::GitSha2 as u8 {
474 return 1 + GIT_SHA2_BYTES;
475 }
476
477 if b[0] == HashAlgorithm::None as u8 {
478 1
479 } else {
480 panic!("Unknown hash algorithm {:?}", b[0])
481 }
482 }
483}