use crate::entry::Attribute; use crate::error::{AddressComponentsDecodeError, UpEndError}; use crate::hash::{ b58_decode, b58_encode, AsMultihash, AsMultihashError, LargeMultihash, UpMultihash, IDENTITY, }; use serde::de::Visitor; use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; use std::fmt; use std::hash::Hash; use std::str::FromStr; use url::Url; use uuid::Uuid; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; #[derive(Clone, Eq, PartialEq, Hash)] pub enum Address { Hash(UpMultihash), Uuid(Uuid), Attribute(Attribute), Url(Url), } #[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone, inspectable))] #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AddressComponents { pub t: String, pub c: Option, } #[cfg(feature = "wasm")] #[wasm_bindgen] impl AddressComponents { #[wasm_bindgen(constructor)] pub fn new(t: String, c: Option) -> Self { AddressComponents { t, c } } } /// multicodec RAW code const RAW: u64 = 0x55; /// multicodec UpEnd UUID code (reserved area) const UP_UUID: u64 = 0x300001; /// multicodec UpEnd Attribute code (reserved area) const UP_ATTRIBUTE: u64 = 0x300000; /// multicodec URL code (technically `http`) const UP_URL: u64 = 0x01e0; pub type UpCid = cid::CidGeneric<256>; impl Address { pub fn encode(&self) -> Result, UpEndError> { let (codec, hash) = match self { Self::Hash(hash) => (RAW, hash.into()), Self::Uuid(uuid) => ( UP_UUID, LargeMultihash::wrap(IDENTITY, uuid.as_bytes()).map_err(UpEndError::from_any)?, ), Self::Attribute(attribute) => ( UP_ATTRIBUTE, LargeMultihash::wrap(IDENTITY, attribute.to_string().as_bytes()) .map_err(UpEndError::from_any)?, ), Self::Url(url) => ( UP_URL, LargeMultihash::wrap(IDENTITY, url.to_string().as_bytes()) .map_err(UpEndError::from_any)?, ), }; let cid = UpCid::new_v1(codec, hash); Ok(cid.to_bytes()) } pub fn decode(buffer: &[u8]) -> Result { let cid = UpCid::try_from(buffer).map_err(|err| { UpEndError::AddressParseError(format!("Error decoding address: {}", err)) })?; if cid.codec() == RAW { return Ok(Address::Hash(UpMultihash::from(*cid.hash()))); } let hash = cid.hash(); if hash.code() != IDENTITY { return Err(UpEndError::AddressParseError(format!( "Unexpected multihash code \"{}\" for codec \"{}\"", hash.code(), cid.codec() ))); } let digest = cid.hash().digest().to_vec(); match cid.codec() { UP_UUID => Ok(Address::Uuid( Uuid::from_slice(digest.as_slice()).map_err(UpEndError::from_any)?, )), UP_ATTRIBUTE => { let attribute = String::from_utf8(digest).map_err(UpEndError::from_any)?; if attribute.is_empty() { Ok(Address::Attribute(Attribute::null())) } else { Ok(Address::Attribute(attribute.parse()?)) } } UP_URL => Ok(Address::Url( Url::parse(&String::from_utf8(digest).map_err(UpEndError::from_any)?) .map_err(UpEndError::from_any)?, )), _ => Err(UpEndError::AddressParseError( "Error decoding address: Unknown codec.".to_string(), )), } } pub fn as_components(&self) -> AddressComponents { // TODO: make this automatically derive from `Address` definition let (entity_type, entity_content) = match self { Address::Hash(uphash) => ("Hash", Some(b58_encode(uphash.to_bytes()))), Address::Uuid(uuid) => ("Uuid", Some(uuid.to_string())), Address::Attribute(attribute) => ("Attribute", Some(attribute.to_string())), Address::Url(url) => ("Url", Some(url.to_string())), }; AddressComponents { t: entity_type.to_string(), c: entity_content, } } pub fn from_components(components: AddressComponents) -> Result { // TODO: make this automatically derive from `Address` definition let address = match components { AddressComponents { t, c } if t == "Attribute" => Address::Attribute( c.ok_or(UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::MissingValue, ))? .parse()?, ), AddressComponents { t, c } if t == "Url" => Address::Url(if let Some(string) = c { Url::parse(&string).map_err(|e| { UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::UrlDecodeError(e.to_string()), ) })? } else { Err(UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::MissingValue, ))? }), AddressComponents { t, c } if t == "Uuid" => match c { Some(c) => c.parse()?, None => Address::Uuid(Uuid::new_v4()), }, AddressComponents { t, .. } => Err(UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::UnknownType(t), ))?, }; Ok(address) } } impl Serialize for Address { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, { serializer.serialize_str(b58_encode(self.encode().map_err(ser::Error::custom)?).as_str()) } } struct AddressVisitor; impl<'de> Visitor<'de> for AddressVisitor { type Value = Address; 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 address: {}", e)))?; Address::decode(bytes.as_ref()) .map_err(|e| de::Error::custom(format!("Error deserializing address: {}", e))) } } impl<'de> Deserialize<'de> for Address { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(AddressVisitor) } } impl FromStr for Address { type Err = UpEndError; fn from_str(s: &str) -> Result { Address::decode( b58_decode(s) .map_err(|e| { UpEndError::HashDecodeError(format!("Error deserializing address: {}", e)) })? .as_ref(), ) } } impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", b58_encode(self.encode().map_err(|_| fmt::Error)?)) } } impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Address<{}>: {}", match self { Address::Hash(_) => "Hash", Address::Uuid(_) => "UUID", Address::Attribute(_) => "Attribute", Address::Url(_) => "URL", }, self ) } } pub trait Addressable: AsMultihash { fn address(&self) -> Result { Ok(Address::Hash(self.as_multihash()?)) } } impl Addressable for T where T: AsMultihash {} #[cfg(test)] mod tests { use url::Url; use uuid::Uuid; use crate::addressing::{Address, IDENTITY}; use crate::constants::{ TYPE_ATTRIBUTE_ADDRESS, TYPE_HASH_ADDRESS, TYPE_URL_ADDRESS, TYPE_UUID_ADDRESS, }; use crate::hash::{LargeMultihash, UpMultihash}; use super::UpEndError; #[test] fn test_hash_codec() -> Result<(), UpEndError> { let addr = Address::Hash(UpMultihash::from( LargeMultihash::wrap(IDENTITY, &[1, 2, 3, 4, 5]).unwrap(), )); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); let addr = &*TYPE_HASH_ADDRESS; let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, &decoded); Ok(()) } #[test] fn test_uuid_codec() -> Result<(), UpEndError> { let addr = Address::Uuid(Uuid::new_v4()); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); let addr = &*TYPE_UUID_ADDRESS; let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, &decoded); Ok(()) } #[test] fn test_attribute_codec() -> Result<(), UpEndError> { let addr = Address::Attribute("ATTRIBUTE".parse().unwrap()); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); let addr = &*TYPE_ATTRIBUTE_ADDRESS; let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, &decoded); Ok(()) } #[test] fn test_url_codec() -> Result<(), UpEndError> { let addr = Address::Url(Url::parse("https://upend.dev/an/url/that/is/particularly/long/because/multihash/used/to/have/a/small/limit").unwrap()); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); let addr = &*TYPE_URL_ADDRESS; let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, &decoded); Ok(()) } }