use std::convert::TryFrom;
use arrayvec::ArrayVec;
use bamboo_rs_core::yamf_hash::new_blake2b;
use serde::{Deserialize, Serialize};
use yamf_hash::{YamfHash, BLAKE2B_HASH_SIZE, MAX_YAMF_HASH_SIZE};
use crate::hash::HashError;
use crate::Validate;
pub type Blake2BArrayVec = ArrayVec<[u8; BLAKE2B_HASH_SIZE]>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "db-sqlx",
derive(sqlx::Type, sqlx::FromRow),
sqlx(transparent)
)]
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 blake2b_hash = new_blake2b(&value);
let mut bytes = Vec::new();
blake2b_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()
}
}
impl<T: core::borrow::Borrow<[u8]>> TryFrom<YamfHash<T>> for Hash {
type Error = HashError;
fn try_from(yamf_hash: YamfHash<T>) -> Result<Self, Self::Error> {
let mut out = [0u8; MAX_YAMF_HASH_SIZE];
let _ = yamf_hash.encode(&mut out)?;
Self::new(&hex::encode(out))
}
}
impl From<Hash> for YamfHash<Blake2BArrayVec> {
fn from(hash: Hash) -> YamfHash<Blake2BArrayVec> {
let bytes = hash.to_bytes();
let yamf_hash = YamfHash::<Blake2BArrayVec>::decode_owned(&bytes).unwrap();
yamf_hash.0
}
}
impl Validate for Hash {
type Error = HashError;
fn validate(&self) -> Result<(), Self::Error> {
match hex::decode(self.0.to_owned()) {
Ok(bytes) => {
if bytes.len() != BLAKE2B_HASH_SIZE + 2 {
return Err(HashError::InvalidLength);
}
match YamfHash::<&[u8]>::decode(&bytes) {
Ok((YamfHash::Blake2b(_), _)) => {}
_ => return Err(HashError::DecodingFailed),
}
}
Err(_) => return Err(HashError::InvalidHexEncoding),
}
Ok(())
}
}
impl PartialEq for Hash {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use yamf_hash::YamfHash;
use super::{Blake2BArrayVec, 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("004069db5208a271c53de8a1b6220e6a4d7fcccd89e6c0c7e75c833e34dc68d932624f2ccf27513f42fb7d0e4390a99b225bad41ba14a6297537246dbe4e6ce150e8").is_ok()
);
}
#[test]
fn new_from_bytes() {
assert_eq!(Hash::new_from_bytes(vec![1, 2, 3]).unwrap(), Hash::new("0040cf94f6d605657e90c543b0c919070cdaaf7209c5e1ea58acb8f3568fa2114268dc9ac3bafe12af277d286fce7dc59b7c0c348973c4e9dacbe79485e56ac2a702").unwrap());
}
#[test]
fn convert_yamf_hash() {
let hash = Hash::new_from_bytes(vec![1, 2, 3]).unwrap();
let yamf_hash = Into::<YamfHash<Blake2BArrayVec>>::into(hash.to_owned());
let hash_restored = TryInto::<Hash>::try_into(yamf_hash).unwrap();
assert_eq!(hash, hash_restored);
}
}