181 lines
4.8 KiB
Rust
181 lines
4.8 KiB
Rust
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<u8> {
|
|
self.0.to_bytes()
|
|
}
|
|
|
|
pub fn from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, UpEndError> {
|
|
Ok(UpMultihash(
|
|
LargeMultihash::from_bytes(input.as_ref())
|
|
.map_err(|e| UpEndError::HashDecodeError(e.to_string()))?,
|
|
))
|
|
}
|
|
|
|
pub fn from_sha256<T: AsRef<[u8]>>(input: T) -> Result<Self, UpEndError> {
|
|
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<LargeMultihash> 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<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::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<E>(self, str: &str) -> Result<Self::Value, E>
|
|
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<D>(deserializer: D) -> Result<UpMultihash, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
deserializer.deserialize_str(UpMultihashVisitor)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "diesel")]
|
|
impl diesel::types::FromSql<diesel::sql_types::Binary, diesel::sqlite::Sqlite> for UpMultihash {
|
|
fn from_sql(
|
|
bytes: Option<&<diesel::sqlite::Sqlite as diesel::backend::Backend>::RawValue>,
|
|
) -> diesel::deserialize::Result<Self> {
|
|
Ok(UpMultihash(LargeMultihash::from_bytes(
|
|
diesel::not_none!(bytes).read_blob(),
|
|
)?))
|
|
}
|
|
}
|
|
|
|
pub fn sha256hash<T: AsRef<[u8]>>(input: T) -> Result<UpMultihash, AsMultihashError> {
|
|
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<T: AsRef<[u8]>>(vec: T) -> String {
|
|
multibase::encode(multibase::Base::Base58Btc, vec.as_ref())
|
|
}
|
|
|
|
pub fn b58_decode<T: AsRef<str>>(input: T) -> Result<Vec<u8>, 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<std::io::Error> for AsMultihashError {
|
|
fn from(err: std::io::Error) -> Self {
|
|
AsMultihashError(err.to_string())
|
|
}
|
|
}
|
|
|
|
pub trait AsMultihash {
|
|
fn as_multihash(&self) -> Result<UpMultihash, AsMultihashError>;
|
|
}
|
|
|
|
impl<T> AsMultihash for T
|
|
where
|
|
T: AsRef<[u8]>,
|
|
{
|
|
fn as_multihash(&self) -> Result<UpMultihash, AsMultihashError> {
|
|
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());
|
|
}
|
|
}
|