1#![allow(clippy::missing_errors_doc)]
2mod error;
3mod implementations;
4mod methods;
5pub use error::*;
6use ps_base64::{base64, sized_encode};
7use ps_buffer::Buffer;
8use ps_ecc::ReedSolomon;
9use ps_pint16::PackedInt;
10use sha2::{Digest, Sha256};
11use std::fmt::Write;
12
13#[cfg(test)]
14pub mod tests;
15
16pub const HASH_SIZE_BIN: usize = 32;
17pub const HASH_SIZE_COMPACT: usize = 42;
18pub const HASH_SIZE: usize = 64;
19pub const HASH_SIZE_TOTAL_BIN: usize = 48;
20pub const PARITY: u8 = 7;
21pub const PARITY_OFFSET: usize = 34;
22pub const PARITY_SIZE: usize = 14;
23pub const SIZE_SIZE: usize = std::mem::size_of::<u16>();
24
25pub const RS: ReedSolomon = match ReedSolomon::new(PARITY) {
26 Ok(rs) => rs,
27 Err(_) => panic!("Failed to construct Reed-Solomon codec."),
28};
29
30#[inline]
31#[must_use]
32pub fn sha256(data: &[u8]) -> [u8; HASH_SIZE_BIN] {
33 let mut hasher = Sha256::new();
34
35 hasher.update(data);
36
37 let result = hasher.finalize();
38
39 result.into()
40}
41
42#[inline]
43#[must_use]
44pub fn blake3(data: &[u8]) -> blake3::Hash {
45 blake3::hash(data)
46}
47
48pub type HashParts = ([u8; HASH_SIZE_BIN], [u8; PARITY_SIZE], PackedInt);
49
50#[derive(Clone, Copy, Eq)]
52#[repr(transparent)]
53pub struct Hash {
54 inner: [u8; HASH_SIZE],
55}
56
57impl Hash {
58 #[allow(clippy::self_named_constructors)]
65 pub fn hash(data: impl AsRef<[u8]>) -> Result<Self, HashError> {
66 let data = data.as_ref();
67 let mut buffer = Buffer::with_capacity(HASH_SIZE)?;
68
69 buffer.extend_from_slice(sha256(data))?;
70 buffer ^= blake3(data).as_bytes().as_slice();
71 buffer.extend_from_slice(PackedInt::from_usize(data.len()).to_16_bits())?;
72 buffer.extend_from_slice(RS.generate_parity(&buffer)?)?;
73
74 let hash = Self {
75 inner: sized_encode::<HASH_SIZE>(&buffer),
76 };
77
78 Ok(hash)
79 }
80
81 pub fn validate(hash: impl AsRef<[u8]>) -> Result<Self, HashValidationError> {
87 let mut hash = base64::decode(hash.as_ref());
88
89 Self::validate_bin_vec(&mut hash)
90 }
91
92 pub fn validate_bin(hash: impl AsRef<[u8]>) -> Result<Self, HashValidationError> {
98 Self::validate_bin_vec(&mut hash.as_ref().to_vec())
99 }
100
101 pub fn validate_bin_vec(hash: &mut Vec<u8>) -> Result<Self, HashValidationError> {
108 hash.resize(HASH_SIZE_TOTAL_BIN, 0xF4);
111
112 let (data, parity) = hash.split_at_mut(PARITY_OFFSET);
113
114 ReedSolomon::correct_detached_in_place(parity, data)?;
115
116 let hash = Self {
117 inner: sized_encode::<HASH_SIZE>(hash),
118 };
119
120 Ok(hash)
121 }
122}
123
124impl AsRef<[u8]> for Hash {
125 fn as_ref(&self) -> &[u8] {
126 self.as_bytes()
127 }
128}
129
130impl std::fmt::Display for Hash {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.write_str(self.as_str())
133 }
134}
135
136impl std::fmt::Debug for Hash {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 for &b in &self.inner {
139 if b.is_ascii_graphic() {
140 f.write_char(b as char)
141 } else {
142 f.write_str(&format!("<0x{b:02X?}>"))
143 }?;
144 }
145
146 Ok(())
147 }
148}
149
150impl core::hash::Hash for Hash {
151 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
152 match decode_parts(&self.inner) {
153 Ok((hash, checksum, length)) => {
154 state.write(&hash);
155 state.write(&checksum);
156 state.write_u16(length.to_inner_u16());
157 }
158 Err(_) => state.write(&self.inner),
159 }
160 }
161}
162
163impl std::ops::Deref for Hash {
164 type Target = str;
165
166 fn deref(&self) -> &Self::Target {
167 self.as_str()
168 }
169}
170
171impl std::ops::Index<usize> for Hash {
172 type Output = u8;
173
174 fn index(&self, index: usize) -> &Self::Output {
175 if index < self.inner.len() {
176 &self.inner[index]
177 } else {
178 &0
179 }
180 }
181}
182
183impl std::ops::Index<std::ops::Range<usize>> for Hash {
184 type Output = str;
185
186 fn index(&self, index: std::ops::Range<usize>) -> &Self::Output {
187 let start = std::cmp::min(index.start, self.inner.len());
188 let end = std::cmp::min(index.end, self.inner.len());
189 let range = start..end;
190
191 &self.as_str()[range]
192 }
193}
194
195impl std::ops::Index<std::ops::RangeFrom<usize>> for Hash {
196 type Output = str;
197
198 fn index(&self, index: std::ops::RangeFrom<usize>) -> &Self::Output {
199 self.index(index.start..HASH_SIZE)
200 }
201}
202
203impl std::ops::Index<std::ops::RangeTo<usize>> for Hash {
204 type Output = str;
205
206 fn index(&self, index: std::ops::RangeTo<usize>) -> &Self::Output {
207 self.index(0..index.end)
208 }
209}
210
211impl std::ops::Index<std::ops::RangeToInclusive<usize>> for Hash {
212 type Output = str;
213
214 fn index(&self, index: std::ops::RangeToInclusive<usize>) -> &Self::Output {
215 &self.as_str()[index]
216 }
217}
218
219impl std::ops::Index<std::ops::RangeFull> for Hash {
220 type Output = str;
221
222 fn index(&self, _: std::ops::RangeFull) -> &Self::Output {
223 self.as_str()
224 }
225}
226
227impl std::ops::Index<std::ops::RangeInclusive<usize>> for Hash {
228 type Output = str;
229
230 fn index(&self, index: std::ops::RangeInclusive<usize>) -> &Self::Output {
231 &self.as_str()[index]
232 }
233}
234
235impl PartialEq for Hash {
236 fn eq(&self, other: &Self) -> bool {
237 let Ok(left) = decode_parts(&self.inner) else {
238 return self.inner == other.inner;
239 };
240
241 let Ok(right) = decode_parts(&other.inner) else {
242 return false;
243 };
244
245 left.0 == right.0 && left.1 == right.1 && left.2 == right.2
246 }
247}
248
249impl Ord for Hash {
250 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
251 let Ok(left) = decode_parts(&self.inner) else {
252 return self.inner.cmp(&other.inner);
253 };
254
255 let Ok(right) = decode_parts(&other.inner) else {
256 return self.inner.cmp(&other.inner);
257 };
258
259 left.0.cmp(&right.0)
260 }
261}
262
263impl PartialOrd for Hash {
264 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
265 Some(self.cmp(other))
266 }
267}
268
269impl From<Hash> for [u8; HASH_SIZE] {
270 fn from(hash: Hash) -> [u8; HASH_SIZE] {
271 hash.inner
272 }
273}
274
275impl From<&Hash> for String {
276 fn from(hash: &Hash) -> Self {
277 hash.to_string()
278 }
279}
280
281impl From<&Hash> for Vec<u8> {
282 fn from(hash: &Hash) -> Self {
283 hash.to_vec()
284 }
285}
286
287impl TryFrom<&[u8]> for Hash {
288 type Error = HashValidationError;
289
290 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
291 Self::validate(value)
292 }
293}
294
295impl TryFrom<&str> for Hash {
296 type Error = HashValidationError;
297
298 fn try_from(value: &str) -> Result<Self, Self::Error> {
299 value.as_bytes().try_into()
300 }
301}
302
303impl Hash {
304 #[must_use]
305 pub const fn as_bytes(&self) -> &[u8; HASH_SIZE] {
306 &self.inner
307 }
308
309 #[must_use]
310 pub fn to_vec(&self) -> Vec<u8> {
311 self.inner.to_vec()
312 }
313
314 pub fn data_max_len(&self) -> Result<usize, PsHashError> {
316 let bits = &self.inner[40..46];
317 let bits = ps_base64::decode(bits);
318 let bits = bits[2..4].try_into()?;
319 let size = PackedInt::from_16_bits(bits).to_usize();
320
321 Ok(size)
322 }
323}
324
325#[must_use]
326pub fn encode_parts(parts: HashParts) -> Hash {
327 let (xored, checksum, length) = parts;
328
329 let mut vec: Vec<u8> = Vec::with_capacity(HASH_SIZE_TOTAL_BIN);
330
331 vec.extend_from_slice(&xored);
332 vec.extend_from_slice(&length.to_16_bits());
333 vec.extend_from_slice(&checksum);
334
335 Hash {
336 inner: ps_base64::sized_encode::<HASH_SIZE>(&vec),
337 }
338}
339
340#[inline]
341pub fn hash(data: impl AsRef<[u8]>) -> Result<Hash, HashError> {
342 Hash::hash(data)
343}
344
345pub fn decode_parts(hash: &[u8]) -> Result<HashParts, PsHashError> {
346 if hash.len() < HASH_SIZE {
347 return Err(PsHashError::InputTooShort);
348 }
349
350 let bytes = ps_base64::decode(hash);
351
352 Ok((
353 bytes[0..HASH_SIZE_BIN].try_into()?,
354 bytes[PARITY_OFFSET..HASH_SIZE_TOTAL_BIN].try_into()?,
355 PackedInt::from_16_bits(&bytes[HASH_SIZE_BIN..PARITY_OFFSET].try_into()?),
356 ))
357}