1use crate::assert_length;
2use crate::error::AiHashError;
3use crate::AiHash;
4use crate::HashType;
5use crate::PrimitiveHashType;
6use crate::AI_HASH_CORE_LEN;
7use crate::AI_HASH_FULL_LEN;
8use crate::AI_HASH_PREFIX_LEN;
9use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
10use std::convert::TryFrom;
11use std::convert::TryInto;
12
13impl<P: PrimitiveHashType> TryFrom<&str> for AiHash<P> {
14 type Error = AiHashError;
15 fn try_from(s: &str) -> Result<Self, AiHashError> {
16 let hash_type = P::new();
17 AiHash::from_raw_39(ai_hash_decode(hash_type.get_prefix(), s)?)
18 }
19}
20
21impl<P: PrimitiveHashType> TryFrom<&String> for AiHash<P> {
22 type Error = AiHashError;
23 fn try_from(s: &String) -> Result<Self, AiHashError> {
24 Self::try_from(s as &str)
25 }
26}
27
28impl<P: PrimitiveHashType> TryFrom<String> for AiHash<P> {
29 type Error = AiHashError;
30 fn try_from(s: String) -> Result<Self, AiHashError> {
31 Self::try_from(&s)
32 }
33}
34
35impl<T: HashType> std::fmt::Display for AiHash<T> {
36 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
37 write!(f, "{}", ai_hash_encode(self.get_raw_39()))
38 }
39}
40
41pub fn ai_hash_encode(data: &[u8]) -> String {
43 format!("u{}", URL_SAFE_NO_PAD.encode(data))
44}
45
46pub fn ai_hash_decode_unchecked(s: &str) -> Result<Vec<u8>, AiHashError> {
48 if &s[..1] != "u" {
49 return Err(AiHashError::NoU);
50 }
51 let s = match URL_SAFE_NO_PAD.decode(&s[1..]) {
52 Err(_) => return Err(AiHashError::BadBase64),
53 Ok(s) => s,
54 };
55 if s.len() != AI_HASH_FULL_LEN {
56 return Err(AiHashError::BadSize);
57 }
58 let loc_bytes =
59 ai_sgd_location_bytes(&s[AI_HASH_PREFIX_LEN..AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN]);
60 let loc_bytes: &[u8] = &loc_bytes;
61 if loc_bytes != &s[AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN..] {
62 return Err(AiHashError::BadChecksum);
63 }
64 assert_length!(AI_HASH_FULL_LEN, &s);
65 Ok(s.to_vec())
66}
67
68pub fn ai_hash_decode(prefix: &[u8], s: &str) -> Result<Vec<u8>, AiHashError> {
70 if &s[..1] != "u" {
71 return Err(AiHashError::NoU);
72 }
73 let s = match URL_SAFE_NO_PAD.decode(&s[1..]) {
74 Err(_) => return Err(AiHashError::BadBase64),
75 Ok(s) => s,
76 };
77 if s.len() != AI_HASH_FULL_LEN {
78 return Err(AiHashError::BadSize);
79 }
80 let actual_prefix: [u8; AI_HASH_PREFIX_LEN] = s[..AI_HASH_PREFIX_LEN].try_into().unwrap();
81 if actual_prefix != prefix {
82 return Err(AiHashError::BadPrefix(
83 format!("{:?}", prefix),
84 actual_prefix,
85 ));
86 }
87 let loc_bytes =
88 ai_sgd_location_bytes(&s[AI_HASH_PREFIX_LEN..AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN]);
89 let loc_bytes: &[u8] = &loc_bytes;
90 if loc_bytes != &s[AI_HASH_PREFIX_LEN + AI_HASH_CORE_LEN..] {
91 return Err(AiHashError::BadChecksum);
92 }
93 assert_length!(AI_HASH_FULL_LEN, &s);
94 Ok(s.to_vec())
95}
96
97pub fn ai_sgd_location_bytes(data: &[u8]) -> Vec<u8> {
99 assert_eq!(32, data.len(), "only 32 byte hashes supported");
102
103 let hash = blake2b_128(data);
104 let mut out = vec![hash[0], hash[1], hash[2], hash[3]];
105 for i in (4..16).step_by(4) {
106 out[0] ^= hash[i];
107 out[1] ^= hash[i + 1];
108 out[2] ^= hash[i + 2];
109 out[3] ^= hash[i + 3];
110 }
111 out
112}
113
114pub fn blake2b_256(data: &[u8]) -> Vec<u8> {
116 let hash = blake2b_simd::Params::new().hash_length(32).hash(data);
117 hash.as_bytes().to_vec()
118}
119
120pub fn blake2b_128(data: &[u8]) -> Vec<u8> {
122 let hash = blake2b_simd::Params::new().hash_length(16).hash(data);
123 hash.as_bytes().to_vec()
124}