use kitsune_p2p_dht_arc::DhtLocation;
use crate::error::HoloHashResult;
use crate::has_hash::HasHash;
use crate::HashType;
use crate::PrimitiveHashType;
#[cfg(feature = "hashing")]
use crate::encode;
pub const HOLO_HASH_PREFIX_LEN: usize = 3;
pub const HOLO_HASH_CORE_LEN: usize = 32;
pub const HOLO_HASH_LOC_LEN: usize = 4;
pub const HOLO_HASH_UNTYPED_LEN: usize = HOLO_HASH_CORE_LEN + HOLO_HASH_LOC_LEN; pub const HOLO_HASH_FULL_LEN: usize = HOLO_HASH_PREFIX_LEN + HOLO_HASH_CORE_LEN + HOLO_HASH_LOC_LEN;
#[macro_export]
macro_rules! assert_length {
($len:expr, $hash:expr) => {
debug_assert_eq!(
$hash.len(),
$len,
"invalid byte count for HoloHash {:?}",
$hash
);
};
}
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct HoloHash<T: HashType> {
hash: Vec<u8>,
hash_type: T,
}
#[cfg(feature = "arbitrary")]
impl<'a, P: PrimitiveHashType> arbitrary::Arbitrary<'a> for HoloHash<P> {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut buf = [0; HOLO_HASH_FULL_LEN];
buf[0..HOLO_HASH_PREFIX_LEN].copy_from_slice(P::static_prefix());
buf[HOLO_HASH_PREFIX_LEN..]
.copy_from_slice(u.bytes(HOLO_HASH_FULL_LEN - HOLO_HASH_PREFIX_LEN)?);
Ok(HoloHash {
hash: buf.to_vec(),
hash_type: P::new(),
})
}
}
impl<T: HashType> HoloHash<T> {
pub fn from_raw_39(hash: Vec<u8>) -> HoloHashResult<Self> {
assert_length!(HOLO_HASH_FULL_LEN, &hash);
let hash_type = T::try_from_prefix(&hash[0..3])?;
Ok(Self { hash, hash_type })
}
pub fn from_raw_39_panicky(hash: Vec<u8>) -> Self {
Self::from_raw_39(hash).expect("the specified hash_type does not match the prefix bytes")
}
pub fn from_raw_36_and_type(mut bytes: Vec<u8>, hash_type: T) -> Self {
assert_length!(HOLO_HASH_UNTYPED_LEN, &bytes);
let mut hash = hash_type.get_prefix().to_vec();
hash.append(&mut bytes);
assert_length!(HOLO_HASH_FULL_LEN, &hash);
Self { hash, hash_type }
}
pub fn retype<TT: HashType>(mut self, hash_type: TT) -> HoloHash<TT> {
let prefix = hash_type.get_prefix();
self.hash[0..HOLO_HASH_PREFIX_LEN].copy_from_slice(&prefix[0..HOLO_HASH_PREFIX_LEN]);
HoloHash {
hash: self.hash,
hash_type,
}
}
pub fn hash_type(&self) -> &T {
&self.hash_type
}
pub fn get_raw_39(&self) -> &[u8] {
&self.hash[..]
}
pub fn get_raw_36(&self) -> &[u8] {
let bytes = &self.hash[HOLO_HASH_PREFIX_LEN..];
assert_length!(HOLO_HASH_UNTYPED_LEN, bytes);
bytes
}
pub fn get_raw_32(&self) -> &[u8] {
let bytes = &self.hash[HOLO_HASH_PREFIX_LEN..HOLO_HASH_PREFIX_LEN + HOLO_HASH_CORE_LEN];
assert_length!(HOLO_HASH_CORE_LEN, bytes);
bytes
}
pub fn get_loc(&self) -> DhtLocation {
DhtLocation::new(bytes_to_loc(
&self.hash[HOLO_HASH_FULL_LEN - HOLO_HASH_LOC_LEN..],
))
}
pub fn into_inner(self) -> Vec<u8> {
assert_length!(HOLO_HASH_FULL_LEN, &self.hash);
self.hash
}
pub fn to_hex(&self) -> String {
use std::fmt::Write;
let mut s = String::with_capacity(self.hash.len());
for b in &self.hash {
write!(&mut s, "{:02x}", b).ok();
}
s
}
}
#[cfg(feature = "hashing")]
impl<T: HashType> HoloHash<T> {
pub fn from_raw_32_and_type(mut hash: Vec<u8>, hash_type: T) -> Self {
assert_length!(HOLO_HASH_CORE_LEN, &hash);
hash.append(&mut encode::holo_dht_location_bytes(&hash));
assert_length!(HOLO_HASH_UNTYPED_LEN, &hash);
HoloHash::from_raw_36_and_type(hash, hash_type)
}
}
impl<P: PrimitiveHashType> HoloHash<P> {
pub fn from_raw_36(hash: Vec<u8>) -> Self {
assert_length!(HOLO_HASH_UNTYPED_LEN, &hash);
Self::from_raw_36_and_type(hash, P::new())
}
#[cfg(feature = "hashing")]
pub fn from_raw_32(hash: Vec<u8>) -> Self {
Self::from_raw_32_and_type(hash, P::new())
}
}
impl<T: HashType> AsRef<[u8]> for HoloHash<T> {
fn as_ref(&self) -> &[u8] {
assert_length!(HOLO_HASH_FULL_LEN, &self.hash);
&self.hash
}
}
#[cfg(feature = "rusqlite")]
impl<T: HashType> rusqlite::ToSql for HoloHash<T> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::Borrowed(self.as_ref().into()))
}
}
#[cfg(feature = "rusqlite")]
impl<T: HashType> rusqlite::types::FromSql for HoloHash<T> {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
Vec::<u8>::column_result(value).and_then(|bytes| {
Self::from_raw_39(bytes).map_err(|_| rusqlite::types::FromSqlError::InvalidType)
})
}
}
impl<T: HashType> IntoIterator for HoloHash<T> {
type Item = u8;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.hash.into_iter()
}
}
impl<T: HashType> HasHash<T> for HoloHash<T> {
fn as_hash(&self) -> &HoloHash<T> {
self
}
fn into_hash(self) -> HoloHash<T> {
self
}
}
impl<T: HashType> std::fmt::Debug for HoloHash<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}({})", self.hash_type().hash_name(), self))?;
Ok(())
}
}
fn bytes_to_loc(bytes: &[u8]) -> u32 {
(bytes[0] as u32)
+ ((bytes[1] as u32) << 8)
+ ((bytes[2] as u32) << 16)
+ ((bytes[3] as u32) << 24)
}
#[cfg(test)]
mod tests {
use crate::*;
#[cfg(not(feature = "encoding"))]
fn assert_type<T: HashType>(t: &str, h: HoloHash<T>) {
assert_eq!(3_688_618_971, h.get_loc());
assert_eq!(
"[219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219]",
format!("{:?}", h.get_raw_32()),
);
}
#[test]
#[cfg(not(feature = "encoding"))]
fn test_enum_types() {
assert_type(
"DnaHash",
DnaHash::from_raw_36(vec![0xdb; HOLO_HASH_UNTYPED_LEN]),
);
assert_type(
"NetIdHash",
NetIdHash::from_raw_36(vec![0xdb; HOLO_HASH_UNTYPED_LEN]),
);
assert_type(
"AgentPubKey",
AgentPubKey::from_raw_36(vec![0xdb; HOLO_HASH_UNTYPED_LEN]),
);
assert_type(
"EntryHash",
EntryHash::from_raw_36(vec![0xdb; HOLO_HASH_UNTYPED_LEN]),
);
assert_type(
"DhtOpHash",
DhtOpHash::from_raw_36(vec![0xdb; HOLO_HASH_UNTYPED_LEN]),
);
assert_type!(
"ExternalHash",
ExternalHash::from_raw_36(vec![0xdb; HOLO_HASH_UNTYPED_LEN]),
);
}
#[test]
#[should_panic]
fn test_fails_with_bad_size() {
DnaHash::from_raw_36(vec![0xdb; 35]);
}
}