use std::convert::{TryFrom, TryInto};
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::error::HashError;
use crate::{Human, Validate};
pub const HASH_SIZE: usize = BLAKE3_HASH_SIZE;
pub type Blake3ArrayVec = ArrayVec<[u8; HASH_SIZE]>;
#[derive(Clone, Debug, Ord, PartialOrd, Serialize, 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: &[u8]) -> Self {
let blake3_hash = new_blake3(value);
let mut bytes = Vec::new();
blake3_hash
.encode_write(&mut bytes)
.unwrap();
let hex_str = hex::encode(&bytes);
Self(hex_str)
}
pub fn to_bytes(&self) -> Vec<u8> {
hex::decode(&self.0).unwrap()
}
pub fn as_str(&self) -> &str {
&self.0
}
}
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(_), _)) => Ok(()),
_ => Err(HashError::DecodingFailed),
}
}
Err(_) => Err(HashError::InvalidHexEncoding),
}
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Human for Hash {
fn display(&self) -> String {
let offset = MAX_YAMF_HASH_SIZE * 2 - 6;
format!("<Hash {}>", &self.as_str()[offset..])
}
}
impl<'de> Deserialize<'de> for Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let hash: String = Deserialize::deserialize(deserializer)?;
hash.try_into()
.map_err(|err: HashError| serde::de::Error::custom(err.to_string()))
}
}
impl<T: core::borrow::Borrow<[u8]> + Clone> From<&YasmfHash<T>> for Hash {
fn from(yasmf_hash: &YasmfHash<T>) -> Self {
let mut out = [0u8; MAX_YAMF_HASH_SIZE];
let _ = yasmf_hash.encode(&mut out).unwrap();
Self::new(&hex::encode(out)).unwrap()
}
}
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)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use yasmf_hash::YasmfHash;
use crate::Human;
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(&[1, 2, 3]),
Hash::new("0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543")
.unwrap()
);
}
#[test]
fn convert_yasmf() {
let hash = Hash::new_from_bytes(&[1, 2, 3]);
let yasmf_hash = Into::<YasmfHash<Blake3ArrayVec>>::into(&hash);
let hash_restored = Into::<Hash>::into(&yasmf_hash);
assert_eq!(hash, hash_restored);
}
#[test]
fn it_hashes() {
let hash = Hash::new_from_bytes(&[1, 2, 3]);
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 from_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());
}
#[test]
fn string_representation() {
let hash_str = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543";
let hash = Hash::new(hash_str).unwrap();
assert_eq!(hash_str, hash.as_str());
assert_eq!(hash_str, hash.to_string());
assert_eq!(hash_str, format!("{}", hash));
}
#[test]
fn short_representation() {
let hash_str = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543";
let hash = Hash::new(hash_str).unwrap();
assert_eq!(hash.display(), "<Hash 496543>");
}
}