1use std::fmt;
8use std::str::FromStr;
9
10use serde::de::{self, Visitor};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13use crate::error::Error;
14
15pub const HASH_LEN: usize = 32;
17
18pub const HASH_STR_LEN: usize = 52;
20
21pub const SHORT_LEN: usize = 8;
23
24const ALPHABET: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
26
27#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct ObjectHash([u8; HASH_LEN]);
30
31pub type Hash = ObjectHash;
33
34impl ObjectHash {
35 pub fn of(bytes: &[u8]) -> Self {
37 Self(blake3::hash(bytes).into())
38 }
39
40 pub fn from_raw(bytes: [u8; HASH_LEN]) -> Self {
42 Self(bytes)
43 }
44
45 pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
47 &self.0
48 }
49
50 pub fn to_base32(self) -> String {
52 base32_encode(&self.0)
53 }
54
55 pub fn short(self) -> String {
57 let mut s = self.to_base32();
58 s.truncate(SHORT_LEN);
59 s
60 }
61
62 pub fn from_base32(s: &str) -> Result<Self, Error> {
64 let decoded = base32_decode(s)?;
65 let bytes: [u8; HASH_LEN] = decoded.as_slice().try_into().map_err(|_| {
66 Error::InvalidHash(format!("expected {HASH_LEN} bytes, got {}", decoded.len()))
67 })?;
68 Ok(Self(bytes))
69 }
70}
71
72impl fmt::Display for ObjectHash {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.write_str(&self.to_base32())
75 }
76}
77
78impl fmt::Debug for ObjectHash {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "Hash({})", self.short())
81 }
82}
83
84impl FromStr for ObjectHash {
85 type Err = Error;
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 Self::from_base32(s)
88 }
89}
90
91impl Serialize for ObjectHash {
92 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
93 s.serialize_bytes(&self.0)
94 }
95}
96
97impl<'de> Deserialize<'de> for ObjectHash {
98 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
99 struct HashVisitor;
100 impl<'de> Visitor<'de> for HashVisitor {
101 type Value = ObjectHash;
102 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "a 32-byte BLAKE3 digest")
104 }
105 fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<ObjectHash, E> {
106 let b: [u8; HASH_LEN] = v
107 .try_into()
108 .map_err(|_| E::invalid_length(v.len(), &self))?;
109 Ok(ObjectHash(b))
110 }
111 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<ObjectHash, A::Error> {
112 let mut b = [0u8; HASH_LEN];
113 for (i, slot) in b.iter_mut().enumerate() {
114 *slot = seq
115 .next_element()?
116 .ok_or_else(|| de::Error::invalid_length(i, &self))?;
117 }
118 Ok(ObjectHash(b))
119 }
120 }
121 d.deserialize_bytes(HashVisitor)
122 }
123}
124
125fn base32_encode(data: &[u8]) -> String {
127 let mut out = String::with_capacity(data.len().div_ceil(5) * 8);
128 let mut buf: u32 = 0;
129 let mut bits: u32 = 0;
130 for &byte in data {
131 buf = (buf << 8) | u32::from(byte);
132 bits += 8;
133 while bits >= 5 {
134 bits -= 5;
135 out.push(ALPHABET[((buf >> bits) & 0x1f) as usize] as char);
136 }
137 }
138 if bits > 0 {
139 out.push(ALPHABET[((buf << (5 - bits)) & 0x1f) as usize] as char);
140 }
141 out
142}
143
144fn base32_decode(s: &str) -> Result<Vec<u8>, Error> {
146 let mut out = Vec::with_capacity(s.len() * 5 / 8);
147 let mut buf: u32 = 0;
148 let mut bits: u32 = 0;
149 for ch in s.chars() {
150 let lc = ch.to_ascii_lowercase();
151 let val = match lc {
152 'a'..='z' => lc as u32 - 'a' as u32,
153 '2'..='7' => lc as u32 - '2' as u32 + 26,
154 _ => {
155 return Err(Error::InvalidHash(format!(
156 "illegal base32 character {ch:?}"
157 )));
158 }
159 };
160 buf = (buf << 5) | val;
161 bits += 5;
162 if bits >= 8 {
163 bits -= 8;
164 out.push(((buf >> bits) & 0xff) as u8);
165 }
166 }
167 Ok(out)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn hash_string_is_52_chars_and_roundtrips() {
176 let h = Hash::of(b"hello gpp");
177 let s = h.to_base32();
178 assert_eq!(s.len(), HASH_STR_LEN);
179 assert_eq!(Hash::from_base32(&s).unwrap(), h);
180 }
181
182 #[test]
183 fn decode_is_case_insensitive() {
184 let h = Hash::of(b"case test");
185 let lower = h.to_base32();
186 let upper = lower.to_uppercase();
187 assert_eq!(Hash::from_base32(&upper).unwrap(), h);
188 }
189
190 #[test]
191 fn short_form_is_prefix() {
192 let h = Hash::of(b"short");
193 assert_eq!(h.short().len(), SHORT_LEN);
194 assert!(h.to_base32().starts_with(&h.short()));
195 }
196
197 #[test]
198 fn rejects_non_alphabet() {
199 assert!(Hash::from_base32("not valid!!").is_err());
200 assert!(Hash::from_base32("0189").is_err()); }
202
203 #[test]
204 fn distinct_inputs_distinct_hashes() {
205 assert_ne!(Hash::of(b"a"), Hash::of(b"b"));
206 }
207}