use std::fmt; use crate::{addressing::Address, error::UpEndError}; use multihash::Hasher; use serde::{ de::{self, Visitor}, ser, Deserialize, Deserializer, Serialize, Serializer, }; /// multihash SHA2-256 code pub const SHA2_256: u64 = 0x12; /// multihash identity code pub const IDENTITY: u64 = 0x00; #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "diesel", derive(diesel::FromSqlRow))] pub struct UpMultihash(LargeMultihash); impl UpMultihash { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } pub fn from_bytes>(input: T) -> Result { Ok(UpMultihash( LargeMultihash::from_bytes(input.as_ref()) .map_err(|e| UpEndError::HashDecodeError(e.to_string()))?, )) } pub fn from_sha256>(input: T) -> Result { Ok(UpMultihash( LargeMultihash::wrap(SHA2_256, input.as_ref()).map_err(UpEndError::from_any)?, )) } } impl std::fmt::Display for UpMultihash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", b58_encode(self.to_bytes())) } } pub(crate) type LargeMultihash = multihash::MultihashGeneric<256>; impl From for UpMultihash { fn from(value: LargeMultihash) -> Self { UpMultihash(value) } } impl From<&UpMultihash> for LargeMultihash { fn from(value: &UpMultihash) -> Self { value.0 } } impl Serialize for UpMultihash { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, { serializer.serialize_str( b58_encode( Address::Hash(self.clone()) .encode() .map_err(ser::Error::custom)?, ) .as_str(), ) } } struct UpMultihashVisitor; impl<'de> Visitor<'de> for UpMultihashVisitor { type Value = UpMultihash; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid UpEnd address (hash/UUID) as a multi-hashed string") } fn visit_str(self, str: &str) -> Result where E: de::Error, { let bytes = b58_decode(str) .map_err(|e| de::Error::custom(format!("Error deserializing UpMultihash: {}", e)))?; Ok(UpMultihash(LargeMultihash::from_bytes(&bytes).map_err( |e| de::Error::custom(format!("Error parsing UpMultihash: {}", e)), )?)) } } impl<'de> Deserialize<'de> for UpMultihash { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(UpMultihashVisitor) } } #[cfg(feature = "diesel")] impl diesel::types::FromSql for UpMultihash { fn from_sql( bytes: Option<&::RawValue>, ) -> diesel::deserialize::Result { Ok(UpMultihash(LargeMultihash::from_bytes( diesel::not_none!(bytes).read_blob(), )?)) } } pub fn sha256hash>(input: T) -> Result { let mut hasher = multihash::Sha2_256::default(); hasher.update(input.as_ref()); Ok(UpMultihash( LargeMultihash::wrap(SHA2_256, hasher.finalize()) .map_err(|e| AsMultihashError(e.to_string()))?, )) } pub fn b58_encode>(vec: T) -> String { multibase::encode(multibase::Base::Base58Btc, vec.as_ref()) } pub fn b58_decode>(input: T) -> Result, UpEndError> { let input = input.as_ref(); let (_base, data) = multibase::decode(input).map_err(|err| UpEndError::HashDecodeError(err.to_string()))?; Ok(data) } #[derive(Debug, Clone)] pub struct AsMultihashError(pub String); impl std::fmt::Display for AsMultihashError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for AsMultihashError {} impl From for AsMultihashError { fn from(err: std::io::Error) -> Self { AsMultihashError(err.to_string()) } } pub trait AsMultihash { fn as_multihash(&self) -> Result; } impl AsMultihash for T where T: AsRef<[u8]>, { fn as_multihash(&self) -> Result { sha256hash(self) } } #[cfg(test)] mod tests { use crate::hash::{b58_decode, b58_encode}; #[test] fn test_encode_decode() { let content = "Hello, World!".as_bytes(); let encoded = b58_encode(content); let decoded = b58_decode(encoded); assert!(decoded.is_ok()); assert_eq!(content, decoded.unwrap()); } }