use kitsune_p2p_dht_arc::DhtLocation;
use crate::error::{HoloHashError, 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 = "fuzzing")]
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(),
})
}
}
#[cfg(feature = "fuzzing")]
impl<T: HashType + proptest::arbitrary::Arbitrary> proptest::arbitrary::Arbitrary for HoloHash<T>
where
T::Strategy: 'static,
{
type Parameters = ();
type Strategy = proptest::strategy::BoxedStrategy<HoloHash<T>>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
use proptest::strategy::Strategy;
let strat = T::arbitrary().prop_flat_map(move |hash_type| {
let gen_strat = proptest::string::bytes_regex(r"(?-u:.{39})").unwrap();
gen_strat.prop_map(move |mut buf| {
assert_eq!(buf.len(), 39);
buf[0..HOLO_HASH_PREFIX_LEN].copy_from_slice(hash_type.get_prefix());
HoloHash {
hash: buf.to_vec(),
hash_type,
}
})
});
strat.boxed()
}
}
impl<T: HashType> HoloHash<T> {
pub fn try_from_raw_39(hash: Vec<u8>) -> HoloHashResult<Self> {
if hash.len() != HOLO_HASH_FULL_LEN {
return Err(HoloHashError::BadSize);
}
let hash_type = T::try_from_prefix(&hash[0..3])?;
Ok(Self { hash, hash_type })
}
pub fn from_raw_39(hash: Vec<u8>) -> Self {
Self::try_from_raw_39(hash).unwrap()
}
pub fn try_from_raw_36_and_type(mut bytes: Vec<u8>, hash_type: T) -> HoloHashResult<Self> {
if bytes.len() != HOLO_HASH_UNTYPED_LEN {
return Err(HoloHashError::BadSize);
}
let mut hash = hash_type.get_prefix().to_vec();
hash.append(&mut bytes);
Ok(Self { hash, hash_type })
}
pub fn from_raw_36_and_type(bytes: Vec<u8>, hash_type: T) -> Self {
Self::try_from_raw_36_and_type(bytes, hash_type).unwrap()
}
pub(crate) 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 {
holochain_util::hex::bytes_to_hex(&self.hash, false)
}
}
#[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::try_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 for HoloHash<T> {
type HashType = 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::*;
fn assert_type<T: HashType>(t: &str, h: HoloHash<T>) {
assert_eq!(3_688_618_971, h.get_loc().as_u32());
assert_eq!(h.hash_type().hash_name(), t);
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]
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_from_raw_36_panics_with_bad_size() {
DnaHash::from_raw_36(vec![0xdb; 35]);
}
#[test]
fn test_try_from_raw_39_errors_with_bad_size() {
let mut raw = vec![132, 45, 36];
raw.extend(vec![0xdb; 35]);
let res = DnaHash::try_from_raw_39(raw);
assert_eq!(res, Err(HoloHashError::BadSize));
}
#[test]
fn test_try_from_raw_39_errors_with_bad_prefix() {
let res = DnaHash::try_from_raw_39(vec![0xdb; 39]);
assert!(matches!(res, Err(HoloHashError::BadPrefix { .. })));
}
#[test]
#[should_panic]
fn test_from_raw_39_panics_with_bad_size() {
let mut raw = vec![132, 45, 36];
raw.extend(vec![0xdb; 35]);
DnaHash::from_raw_39(raw);
}
#[test]
#[should_panic]
fn test_from_raw_39_panics_with_bad_prefix() {
DnaHash::from_raw_39(vec![0xdb; 39]);
}
#[test]
fn test_try_from_raw_36_and_type_errors_with_bad_size() {
let res = HoloHash::try_from_raw_36_and_type(vec![0xdb; 35], hash_type::Dna);
assert_eq!(res, Err(HoloHashError::BadSize));
}
#[test]
#[should_panic]
fn test_from_raw_36_and_type_panics_with_bad_size() {
HoloHash::from_raw_36_and_type(vec![0xdb; 35], hash_type::Dna);
}
#[test]
fn test_try_from_raw_36_and_type() {
let res = HoloHash::try_from_raw_36_and_type(vec![0xdb; 36], hash_type::Dna);
assert!(res.is_ok());
}
#[test]
fn test_from_raw_36_and_type() {
HoloHash::from_raw_36_and_type(vec![0xdb; 36], hash_type::Dna);
}
#[test]
fn test_try_from_raw_39() {
let mut raw = vec![132, 45, 36];
raw.extend(vec![0xdb; 36]);
let res = DnaHash::try_from_raw_39(raw);
assert!(res.is_ok());
}
#[test]
fn test_from_raw_39() {
let mut raw = vec![132, 45, 36];
raw.extend(vec![0xdb; 36]);
DnaHash::from_raw_39(raw);
}
#[test]
#[cfg(feature = "fuzzing")]
fn proptest_arbitrary_smoke_test() {
use proptest::prelude::*;
proptest!(|(h: DnaHash)| {
assert_eq!(*h.hash_type(), hash_type::Dna);
});
}
}