2023-06-27 21:11:10 +02:00
|
|
|
use crate::error::{AddressComponentsDecodeError, UpEndError};
|
2023-06-29 14:29:38 +02:00
|
|
|
use crate::hash::{
|
|
|
|
b58_decode, b58_encode, AsMultihash, AsMultihashError, LargeMultihash, UpMultihash, IDENTITY,
|
|
|
|
};
|
2021-02-19 21:45:33 +01:00
|
|
|
use serde::de::Visitor;
|
|
|
|
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
|
2023-06-29 13:30:27 +02:00
|
|
|
use std::convert::TryFrom;
|
2021-02-19 21:45:33 +01:00
|
|
|
use std::fmt;
|
2023-06-25 15:29:52 +02:00
|
|
|
use std::hash::Hash;
|
2021-02-07 20:18:55 +01:00
|
|
|
use std::str::FromStr;
|
2023-05-19 17:30:09 +02:00
|
|
|
use url::Url;
|
2021-02-19 21:45:33 +01:00
|
|
|
use uuid::Uuid;
|
2020-09-07 21:21:54 +02:00
|
|
|
|
2023-06-27 21:11:10 +02:00
|
|
|
#[cfg(feature = "wasm")]
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
2021-12-04 18:33:40 +01:00
|
|
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
2020-09-07 21:21:54 +02:00
|
|
|
pub enum Address {
|
2023-06-29 14:29:38 +02:00
|
|
|
Hash(UpMultihash),
|
2021-04-24 00:08:17 +02:00
|
|
|
Uuid(Uuid),
|
2021-03-14 12:41:38 +01:00
|
|
|
Attribute(String),
|
2023-05-19 17:30:09 +02:00
|
|
|
Url(Url),
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
|
|
|
|
2023-06-28 18:36:24 +02:00
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone, inspectable))]
|
2023-06-26 21:20:40 +02:00
|
|
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
2023-06-27 21:11:10 +02:00
|
|
|
pub struct AddressComponents {
|
|
|
|
pub t: String,
|
2023-06-26 21:50:05 +02:00
|
|
|
pub c: Option<String>,
|
2023-06-26 21:20:40 +02:00
|
|
|
}
|
|
|
|
|
2023-08-28 18:12:26 +02:00
|
|
|
#[cfg(feature = "wasm")]
|
|
|
|
#[wasm_bindgen]
|
|
|
|
impl AddressComponents {
|
|
|
|
#[wasm_bindgen(constructor)]
|
|
|
|
pub fn new(t: String, c: Option<String>) -> Self {
|
|
|
|
AddressComponents { t, c }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-29 15:17:06 +02:00
|
|
|
/// multicodec RAW code
|
2023-06-29 13:30:27 +02:00
|
|
|
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;
|
|
|
|
|
2023-06-29 14:29:38 +02:00
|
|
|
pub type UpCid = cid::CidGeneric<256>;
|
2023-05-20 19:24:19 +02:00
|
|
|
|
2020-09-07 21:21:54 +02:00
|
|
|
impl Address {
|
2023-06-27 21:11:10 +02:00
|
|
|
pub fn encode(&self) -> Result<Vec<u8>, UpEndError> {
|
2023-06-29 13:30:27 +02:00
|
|
|
let (codec, hash) = match self {
|
2023-08-01 21:59:23 +02:00
|
|
|
Self::Hash(hash) => (RAW, hash.into()),
|
2023-06-29 13:30:27 +02:00
|
|
|
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.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)?,
|
|
|
|
),
|
2020-09-07 21:21:54 +02:00
|
|
|
};
|
|
|
|
|
2023-06-29 14:29:38 +02:00
|
|
|
let cid = UpCid::new_v1(codec, hash);
|
2023-06-29 13:30:27 +02:00
|
|
|
|
|
|
|
Ok(cid.to_bytes())
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
|
|
|
|
2023-06-27 21:11:10 +02:00
|
|
|
pub fn decode(buffer: &[u8]) -> Result<Self, UpEndError> {
|
2023-06-29 14:29:38 +02:00
|
|
|
let cid = UpCid::try_from(buffer).map_err(|err| {
|
2023-06-27 21:11:10 +02:00
|
|
|
UpEndError::AddressParseError(format!("Error decoding address: {}", err))
|
|
|
|
})?;
|
2022-09-12 23:30:43 +02:00
|
|
|
|
2023-06-29 13:30:27 +02:00
|
|
|
if cid.codec() == RAW {
|
2023-06-29 15:10:31 +02:00
|
|
|
return Ok(Address::Hash(UpMultihash::from(*cid.hash())));
|
2023-06-29 13:30:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 => Ok(Address::Attribute(
|
|
|
|
String::from_utf8(digest).map_err(UpEndError::from_any)?,
|
|
|
|
)),
|
|
|
|
UP_URL => Ok(Address::Url(
|
|
|
|
Url::parse(&String::from_utf8(digest).map_err(UpEndError::from_any)?)
|
|
|
|
.map_err(UpEndError::from_any)?,
|
|
|
|
)),
|
2023-06-27 21:11:10 +02:00
|
|
|
_ => Err(UpEndError::AddressParseError(
|
2023-06-29 13:30:27 +02:00
|
|
|
"Error decoding address: Unknown codec.".to_string(),
|
2023-01-04 21:16:38 +01:00
|
|
|
)),
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
|
|
|
}
|
2023-06-26 21:20:40 +02:00
|
|
|
|
2023-06-29 15:17:06 +02:00
|
|
|
pub fn as_components(&self) -> AddressComponents {
|
2023-06-26 21:20:40 +02:00
|
|
|
// TODO: make this automatically derive from `Address` definition
|
|
|
|
let (entity_type, entity_content) = match self {
|
2023-06-29 15:10:31 +02:00
|
|
|
Address::Hash(uphash) => ("Hash", Some(b58_encode(uphash.to_bytes()))),
|
2023-06-26 21:50:05 +02:00
|
|
|
Address::Uuid(uuid) => ("Uuid", Some(uuid.to_string())),
|
|
|
|
Address::Attribute(attribute) => ("Attribute", Some(attribute.clone())),
|
|
|
|
Address::Url(url) => ("Url", Some(url.to_string())),
|
2023-06-26 21:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
AddressComponents {
|
2023-06-27 21:11:10 +02:00
|
|
|
t: entity_type.to_string(),
|
2023-06-26 21:20:40 +02:00
|
|
|
c: entity_content,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-27 21:11:10 +02:00
|
|
|
pub fn from_components(components: AddressComponents) -> Result<Self, UpEndError> {
|
2023-06-26 21:20:40 +02:00
|
|
|
// TODO: make this automatically derive from `Address` definition
|
2023-06-26 21:50:05 +02:00
|
|
|
let address = match components {
|
2023-06-29 15:17:06 +02:00
|
|
|
AddressComponents { t, c } if t == "Attribute" => {
|
|
|
|
Address::Attribute(c.ok_or(UpEndError::AddressComponentsDecodeError(
|
|
|
|
AddressComponentsDecodeError::MissingValue,
|
|
|
|
))?)
|
|
|
|
}
|
2023-06-27 21:11:10 +02:00
|
|
|
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()),
|
|
|
|
)
|
|
|
|
})?
|
2023-06-26 21:20:40 +02:00
|
|
|
} else {
|
2023-06-27 21:11:10 +02:00
|
|
|
Err(UpEndError::AddressComponentsDecodeError(
|
|
|
|
AddressComponentsDecodeError::MissingValue,
|
|
|
|
))?
|
2023-06-26 21:20:40 +02:00
|
|
|
}),
|
2023-06-27 21:11:10 +02:00
|
|
|
AddressComponents { t, c } if t == "Uuid" => match c {
|
2023-06-26 21:20:40 +02:00
|
|
|
Some(c) => c.parse()?,
|
|
|
|
None => Address::Uuid(Uuid::new_v4()),
|
|
|
|
},
|
2023-06-27 21:11:10 +02:00
|
|
|
AddressComponents { t, .. } => Err(UpEndError::AddressComponentsDecodeError(
|
|
|
|
AddressComponentsDecodeError::UnknownType(t),
|
|
|
|
))?,
|
2023-06-26 21:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(address)
|
|
|
|
}
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
|
|
|
|
2021-02-19 21:45:33 +01:00
|
|
|
impl Serialize for Address {
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
2022-01-26 16:55:23 +01:00
|
|
|
serializer.serialize_str(b58_encode(self.encode().map_err(ser::Error::custom)?).as_str())
|
2021-02-19 21:45:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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<E>(self, str: &str) -> Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
2023-01-04 21:16:38 +01:00
|
|
|
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)))
|
2021-02-19 21:45:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'de> Deserialize<'de> for Address {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Address, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
{
|
|
|
|
deserializer.deserialize_str(AddressVisitor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-07 20:18:55 +01:00
|
|
|
impl FromStr for Address {
|
2023-06-27 21:11:10 +02:00
|
|
|
type Err = UpEndError;
|
2021-02-07 20:18:55 +01:00
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
2023-01-04 21:16:38 +01:00
|
|
|
Address::decode(
|
|
|
|
b58_decode(s)
|
2023-06-27 21:11:10 +02:00
|
|
|
.map_err(|e| {
|
|
|
|
UpEndError::HashDecodeError(format!("Error deserializing address: {}", e))
|
|
|
|
})?
|
2023-01-04 21:16:38 +01:00
|
|
|
.as_ref(),
|
|
|
|
)
|
2021-02-07 20:18:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-01 21:59:23 +02:00
|
|
|
impl fmt::Display for Address {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{}", b58_encode(self.encode().map_err(|_| fmt::Error)?))
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-01 21:59:23 +02:00
|
|
|
impl fmt::Debug for Address {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2022-09-12 23:30:43 +02:00
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"Address<{}>: {}",
|
|
|
|
match self {
|
|
|
|
Address::Hash(_) => "Hash",
|
|
|
|
Address::Uuid(_) => "UUID",
|
|
|
|
Address::Attribute(_) => "Attribute",
|
|
|
|
Address::Url(_) => "URL",
|
|
|
|
},
|
2023-04-24 17:43:49 +02:00
|
|
|
self
|
2022-09-12 23:30:43 +02:00
|
|
|
)
|
2020-09-12 22:50:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-29 14:29:38 +02:00
|
|
|
pub trait Addressable: AsMultihash {
|
|
|
|
fn address(&self) -> Result<Address, AsMultihashError> {
|
|
|
|
Ok(Address::Hash(self.as_multihash()?))
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-29 13:11:48 +02:00
|
|
|
impl<T> Addressable for T where T: AsMultihash {}
|
|
|
|
|
2020-09-07 21:21:54 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-05-19 17:30:09 +02:00
|
|
|
use url::Url;
|
2020-09-07 21:21:54 +02:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2023-06-29 14:29:38 +02:00
|
|
|
use crate::addressing::{Address, IDENTITY};
|
|
|
|
use crate::hash::{LargeMultihash, UpMultihash};
|
2020-09-07 21:21:54 +02:00
|
|
|
|
2023-06-27 21:11:10 +02:00
|
|
|
use super::UpEndError;
|
|
|
|
|
2020-09-07 21:21:54 +02:00
|
|
|
#[test]
|
2023-06-27 21:11:10 +02:00
|
|
|
fn test_hash_codec() -> Result<(), UpEndError> {
|
2023-06-29 15:10:31 +02:00
|
|
|
let addr = Address::Hash(UpMultihash::from(
|
2023-06-29 15:17:06 +02:00
|
|
|
LargeMultihash::wrap(IDENTITY, &[1, 2, 3, 4, 5]).unwrap(),
|
2023-06-29 14:29:38 +02:00
|
|
|
));
|
2022-01-27 10:53:19 +01:00
|
|
|
let encoded = addr.encode()?;
|
|
|
|
let decoded = Address::decode(&encoded)?;
|
|
|
|
assert_eq!(addr, decoded);
|
|
|
|
Ok(())
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-06-27 21:11:10 +02:00
|
|
|
fn test_uuid_codec() -> Result<(), UpEndError> {
|
2021-04-24 00:08:17 +02:00
|
|
|
let addr = Address::Uuid(Uuid::new_v4());
|
2022-01-27 10:53:19 +01:00
|
|
|
let encoded = addr.encode()?;
|
|
|
|
let decoded = Address::decode(&encoded)?;
|
|
|
|
assert_eq!(addr, decoded);
|
|
|
|
Ok(())
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|
2021-03-14 12:41:38 +01:00
|
|
|
|
|
|
|
#[test]
|
2023-06-27 21:11:10 +02:00
|
|
|
fn test_attribute_codec() -> Result<(), UpEndError> {
|
2021-03-14 12:41:38 +01:00
|
|
|
let addr = Address::Attribute(String::from("ATTRIBUTE"));
|
2022-01-27 10:53:19 +01:00
|
|
|
let encoded = addr.encode()?;
|
|
|
|
let decoded = Address::decode(&encoded)?;
|
|
|
|
assert_eq!(addr, decoded);
|
|
|
|
Ok(())
|
2021-03-14 12:41:38 +01:00
|
|
|
}
|
2022-01-27 10:51:26 +01:00
|
|
|
|
|
|
|
#[test]
|
2023-06-27 21:11:10 +02:00
|
|
|
fn test_url_codec() -> Result<(), UpEndError> {
|
2023-05-20 19:24:19 +02:00
|
|
|
let addr = Address::Url(Url::parse("https://upend.dev/an/url/that/is/particularly/long/because/multihash/used/to/have/a/small/limit").unwrap());
|
2022-01-27 10:53:19 +01:00
|
|
|
let encoded = addr.encode()?;
|
|
|
|
let decoded = Address::decode(&encoded)?;
|
|
|
|
assert_eq!(addr, decoded);
|
|
|
|
Ok(())
|
2022-01-27 10:51:26 +01:00
|
|
|
}
|
2020-09-07 21:21:54 +02:00
|
|
|
}
|