1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[repr(C)]
10#[serde_with::serde_as]
11#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Hash {
13 Blake2b512(#[serde_as(as = "serde_with::Bytes")] [u8; 64]),
14}
15
16const HASH_B64_CFG: base64::Config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
17const HASH_BLK2512_PFX: &str = "blake2b512:";
18
19impl fmt::Display for Hash {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 let (kind, bytes) = match self {
22 Hash::Blake2b512(ref x) => (HASH_BLK2512_PFX, x),
23 };
24 write!(f, "{}{}", kind, base64::encode_config(bytes, HASH_B64_CFG))
25 }
26}
27
28impl fmt::Debug for Hash {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 <Hash as fmt::Display>::fmt(self, f)
32 }
33}
34
35#[derive(Clone, Debug, thiserror::Error, PartialEq)]
36pub enum HashDecodeError {
37 #[error("base64 decoding error: {0}")]
38 Base64(#[from] base64::DecodeError),
39
40 #[error("concrete hash part is too short (got {got}, expected {expected})")]
41 TooShort { got: usize, expected: usize },
42
43 #[error("invalid hash prefix '{0}'")]
44 InvalidPrefix(String),
45}
46
47impl core::str::FromStr for Hash {
48 type Err = HashDecodeError;
49
50 fn from_str(s: &str) -> Result<Hash, HashDecodeError> {
51 if let Some(x) = s.strip_prefix(HASH_BLK2512_PFX) {
52 let mut buf = [0u8; 64];
53 let dcl = base64::decode_config_slice(x, HASH_B64_CFG, &mut buf).map_err(|x| {
54 use base64::DecodeError as Bdce;
55 let offset = HASH_BLK2512_PFX.len();
56 match x {
57 Bdce::InvalidByte(a, b) => Bdce::InvalidByte(offset + a, b),
58 Bdce::InvalidLength => Bdce::InvalidLength,
59 Bdce::InvalidLastSymbol(a, b) => Bdce::InvalidLastSymbol(offset + a, b),
60 }
61 })?;
62 if dcl < buf.len() {
63 return Err(HashDecodeError::TooShort {
64 got: x.len(),
65 expected: buf.len(),
66 });
67 }
68 Ok(Hash::Blake2b512(buf))
69 } else {
70 let truncp = s.find(':').unwrap_or(s.len());
71 Err(HashDecodeError::InvalidPrefix(s[..truncp].to_string()))
72 }
73 }
74}
75
76pub fn calculate_hash(dat: &[u8]) -> Hash {
78 use blake2::Digest;
79 let mut hasher = blake2::Blake2b512::new();
80 hasher.update(dat);
81 let tmp = hasher.finalize();
82 let mut ret = [0u8; 64];
83 ret.copy_from_slice(tmp.as_slice());
84 Hash::Blake2b512(ret)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn hash_parse_err_invalid_prefix() {
93 assert_eq!(
94 "hello:1234".parse::<Hash>(),
95 Err(HashDecodeError::InvalidPrefix("hello".to_string()))
96 );
97 }
98
99 #[test]
100 fn hash_parse_err_base64() {
101 assert_eq!(
102 "blake2b512:.".parse::<Hash>(),
103 Err(HashDecodeError::Base64(base64::DecodeError::InvalidByte(
104 11, b'.'
105 )))
106 );
107 }
108
109 const GTH: Hash = Hash::Blake2b512([
110 207, 114, 247, 238, 107, 232, 17, 55, 229, 186, 214, 166, 184, 208, 96, 252, 67, 32, 28,
111 203, 113, 194, 111, 24, 149, 157, 137, 127, 183, 118, 121, 156, 14, 32, 34, 132, 138, 243,
112 141, 153, 87, 76, 109, 145, 247, 109, 108, 230, 13, 210, 5, 38, 56, 76, 18, 41, 96, 233,
113 122, 235, 55, 66, 107, 150,
114 ]);
115
116 #[test]
117 fn ex0_calc_hash() {
118 assert_eq!(calculate_hash("Guten Tag!".as_bytes()), GTH);
119 }
120
121 const GTH_STR: &str = "blake2b512:z3L37mvoETflutamuNBg_EMgHMtxwm8YlZ2Jf7d2eZwOICKEivONmVdMbZH3bWzmDdIFJjhMEilg6XrrN0Jrlg";
122
123 #[test]
124 fn ex0_hash_str() {
125 assert_eq!(GTH.to_string(), GTH_STR);
126 assert_eq!(GTH_STR.parse::<Hash>(), Ok(GTH));
127 }
128}