use std::convert::TryFrom;
use std::fmt;
use std::hash::Hash as StdHash;
use std::str::FromStr;
use arrayvec::ArrayVec;
use bamboo_rs_core_ed25519_yasmf::yasmf_hash::new_blake3;
use serde::{Deserialize, Serialize};
use yasmf_hash::{YasmfHash, BLAKE3_HASH_SIZE, MAX_YAMF_HASH_SIZE};
use crate::hash::HashError;
use crate::Validate;
pub const HASH_SIZE: usize = BLAKE3_HASH_SIZE;
pub type Blake3ArrayVec = ArrayVec<[u8; HASH_SIZE]>;
#[derive(Clone, Debug, Ord, PartialOrd, Serialize, Deserialize, PartialEq, Eq, StdHash)]
pub struct Hash(String);
impl Hash {
pub fn new(value: &str) -> Result<Self, HashError> {
let hash = Self(String::from(value));
hash.validate()?;
Ok(hash)
}
pub fn new_from_bytes(value: Vec<u8>) -> Result<Self, HashError> {
let blake3_hash = new_blake3(&value);
let mut bytes = Vec::new();
blake3_hash.encode_write(&mut bytes)?;
let hex_str = hex::encode(&bytes);
Ok(Self(hex_str))
}
pub fn to_bytes(&self) -> Vec<u8> {
hex::decode(&self.0).unwrap()
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn short_str(&self) -> &str {
let offset = MAX_YAMF_HASH_SIZE * 2 - 6;
&self.as_str()[offset..]
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<Hash {}>", self.short_str())
}
}
impl<T: core::borrow::Borrow<[u8]> + Clone> TryFrom<YasmfHash<T>> for Hash {
type Error = HashError;
fn try_from(yasmf_hash: YasmfHash<T>) -> Result<Self, Self::Error> {
let mut out = [0u8; MAX_YAMF_HASH_SIZE];
let _ = yasmf_hash.encode(&mut out)?;
Self::new(&hex::encode(out))
}
}
impl From<Hash> for YasmfHash<Blake3ArrayVec> {
fn from(hash: Hash) -> YasmfHash<Blake3ArrayVec> {
let bytes = hash.to_bytes();
let yasmf_hash = YasmfHash::<Blake3ArrayVec>::decode_owned(&bytes).unwrap();
yasmf_hash.0
}
}
impl TryFrom<&str> for Hash {
type Error = HashError;
fn try_from(str: &str) -> Result<Self, Self::Error> {
Self::new(str)
}
}
impl FromStr for Hash {
type Err = HashError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl TryFrom<String> for Hash {
type Error = HashError;
fn try_from(str: String) -> Result<Self, Self::Error> {
Self::new(&str)
}
}
impl Validate for Hash {
type Error = HashError;
fn validate(&self) -> Result<(), Self::Error> {
match hex::decode(&self.0) {
Ok(bytes) => {
if bytes.len() != HASH_SIZE + 2 {
return Err(HashError::InvalidLength(bytes.len(), HASH_SIZE + 2));
}
match YasmfHash::<&[u8]>::decode(&bytes) {
Ok((YasmfHash::Blake3(_), _)) => {}
_ => return Err(HashError::DecodingFailed),
}
}
Err(_) => return Err(HashError::InvalidHexEncoding),
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use yasmf_hash::YasmfHash;
use super::{Blake3ArrayVec, Hash};
#[test]
fn validate() {
assert!(Hash::new("abcdefg").is_err());
assert!(Hash::new("112233445566ff").is_err());
assert!(
Hash::new("01234567812345678123456781234567812345678123456781234567812345678").is_err()
);
assert!(
Hash::new("0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543")
.is_ok()
);
}
#[test]
fn new_from_bytes() {
assert_eq!(
Hash::new_from_bytes(vec![1, 2, 3]).unwrap(),
Hash::new("0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543")
.unwrap()
);
}
#[test]
fn convert_yasmf() {
let hash = Hash::new_from_bytes(vec![1, 2, 3]).unwrap();
let yasmf_hash = Into::<YasmfHash<Blake3ArrayVec>>::into(hash.to_owned());
let hash_restored = TryInto::<Hash>::try_into(yasmf_hash).unwrap();
assert_eq!(hash, hash_restored);
}
#[test]
fn it_hashes() {
let hash = Hash::new_from_bytes(vec![1, 2, 3]).unwrap();
let mut hash_map = HashMap::new();
let key_value = "Value identified by a hash".to_string();
hash_map.insert(&hash, key_value.clone());
let key_value_retrieved = hash_map.get(&hash).unwrap().to_owned();
assert_eq!(key_value, key_value_retrieved)
}
#[test]
fn convert_string() {
let hash_str = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543";
let hash_from_str: Hash = hash_str.try_into().unwrap();
assert_eq!(hash_str, hash_from_str.as_str());
let hash_from_parse: Hash = hash_str.parse().unwrap();
assert_eq!(hash_str, hash_from_parse.as_str());
let hash_from_string = Hash::try_from(String::from(hash_str)).unwrap();
assert_eq!(hash_str, hash_from_string.as_str());
assert_eq!(format!("{}", hash_from_string), "<Hash 496543>");
}
}