diff --git a/Cargo.toml b/Cargo.toml index a1e3775a..1415bdec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] libipld = "0.16" +pretty_assertions = "1.4" rand = "0.8" testresult = "0.3" test-log = "0.2" diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 0571b77f..e43a47a8 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -70,6 +70,17 @@ pub enum Crud { Destroy(Destroy), } +impl From for arguments::Named { + fn from(crud: Crud) -> Self { + match crud { + Crud::Create(create) => create.into(), + Crud::Read(read) => read.into(), + Crud::Update(update) => update.into(), + Crud::Destroy(destroy) => destroy.into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedCrud { Create(PromisedCreate), diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 86df7070..69acac8c 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -53,6 +54,22 @@ pub struct Create { pub args: Option>, } +impl From for Ipld { + fn from(create: Create) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = create.path { + map.insert("path".to_string(), path.display().to_string().into()); + } + + if let Some(args) = create.args { + map.insert("args".to_string(), args.into()); + } + + Ipld::Map(map) + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/create` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 1f2516f5..3f52084a 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -49,6 +50,18 @@ pub struct Destroy { pub path: Option, } +impl From for Ipld { + fn from(destroy: Destroy) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = destroy.path { + map.insert("path".to_string(), path.display().to_string().into()); + } + + Ipld::Map(map) + } +} + const COMMAND: &'static str = "/crud/destroy"; impl Command for Destroy { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 1289a45a..873636e2 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -49,6 +50,22 @@ pub struct Read { pub args: Option>, } +impl From for Ipld { + fn from(ready: Read) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = ready.path { + map.insert("path".to_string(), Ipld::String(path.display().to_string())); + } + + if let Some(args) = ready.args { + map.insert("args".to_string(), args.into()); + } + + map.into() + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/read` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index ebf0c281..935ab40f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -189,9 +189,19 @@ impl TryFrom> for Update { } } -impl From for Ipld { +impl From for arguments::Named { fn from(create: Update) -> Self { - create.into() + let mut named = arguments::Named::::new(); + + if let Some(path) = create.path { + named.insert("path".to_string(), Ipld::String(path.display().to_string())); + } + + if let Some(args) = create.args { + named.insert("args".to_string(), args.into()); + } + + named } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 2ad81030..f8682e02 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -27,6 +27,15 @@ pub enum Msg { Receive(Receive), } +impl From for arguments::Named { + fn from(msg: Msg) -> Self { + match msg { + Msg::Send(send) => send.into(), + Msg::Receive(receive) => receive.into(), + } + } +} + /// A promised version of the [`Msg`] ability. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedMsg { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index b37fe78c..c85dd4a7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -51,6 +52,16 @@ pub struct Send { pub message: String, } +impl From for arguments::Named { + fn from(send: Send) -> Self { + arguments::Named::from_iter([ + ("to".to_string(), send.to.into()), + ("from".to_string(), send.from.into()), + ("message".to_string(), send.message.into()), + ]) + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 34f74d90..44bd43cf 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -36,6 +36,17 @@ impl ToCommand for Preset { } } +impl From for arguments::Named { + fn from(preset: Preset) -> Self { + match preset { + Preset::Crud(crud) => crud.into(), + Preset::Msg(msg) => msg.into(), + Preset::Ucan(ucan) => ucan.into(), + Preset::Wasm(wasm) => wasm.into(), + } + } +} + #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum PromisedPreset { Crud(PromisedCrud), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 4505fc77..b7ee1313 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -9,6 +9,7 @@ use crate::{ }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::fmt::Debug; /// The fully resolved variant: ready to execute. @@ -19,6 +20,12 @@ pub struct Revoke { // FIXME pub witness } +impl From for arguments::Named { + fn from(revoke: Revoke) -> Self { + arguments::Named::from_iter([("ucan".to_string(), Ipld::Link(revoke.ucan).into())]) + } +} + const COMMAND: &'static str = "/ucan/revoke"; impl Command for Revoke { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index dcbe97de..77f95abc 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -33,6 +33,16 @@ pub struct Run { pub args: Vec, } +impl From for arguments::Named { + fn from(run: Run) -> Self { + arguments::Named::from_iter([ + ("mod".into(), Ipld::from(run.module)), + ("fun".into(), run.function.into()), + ("args".into(), run.args.into()), + ]) + } +} + impl TryFrom> for Run { type Error = (); diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 07b18300..0cea6c02 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,3 +1,4 @@ +use crate::ability::arguments::Named; use crate::{capsule::Capsule, crypto::varsig, did::Did}; use libipld_core::{ cid::Cid, @@ -6,14 +7,14 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; +use signature::SignatureEncoding; use signature::Verifier; -use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; pub trait Envelope: Sized { type DID: Did; - type Payload: Clone + Capsule + TryFrom + Into; + type Payload: Clone + Capsule + TryFrom> + Into>; type VarsigHeader: varsig::Header + Clone; type Encoder: Codec + TryFrom + Into; @@ -29,9 +30,11 @@ pub trait Envelope: Sized { ) -> Self; fn to_ipld_envelope(&self) -> Ipld { + let inner_args: Named = self.payload().clone().into(); + let inner_ipld: Ipld = inner_args.into(); + let wrapped_payload: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) - .into(); + BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into(); let header_bytes: Vec = (*self.varsig_header()).clone().into(); let header: Ipld = vec![header_bytes.into(), wrapped_payload].into(); @@ -42,15 +45,15 @@ pub trait Envelope: Sized { fn try_from_ipld_envelope( ipld: Ipld, - ) -> Result>::Error>> { + ) -> Result>>::Error>> { if let Ipld::List(list) = ipld { if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { - if let (1, Some(inner)) = ( + if let (1, Some(Ipld::Map(inner))) = ( btree.len(), btree.get(::TAG.into()), ) { - let payload = Self::Payload::try_from(inner.clone()) + let payload = Self::Payload::try_from(Named(inner.clone())) .map_err(FromIpldError::CannotParsePayload)?; let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) @@ -100,7 +103,8 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, // FIXME force it to be named args not IPLD + Ipld: Encode, + Named: From, { Self::try_sign_generic(signer, varsig_header, payload) } @@ -125,10 +129,14 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, + Ipld: Encode, + Named: From, { - let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); + let ipld: Ipld = BTreeMap::from_iter([( + Self::Payload::TAG.into(), + Named::::from(payload.clone()).into(), + )]) + .into(); let mut buffer = vec![]; ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) @@ -155,12 +163,16 @@ pub trait Envelope: Sized { /// FIXME fn validate_signature(&self) -> Result<(), ValidateError> where - Ipld: Encode + From, + Ipld: Encode, + Named: From, { let mut encoded = vec![]; - let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) - .into(); + let ipld: Ipld = BTreeMap::from_iter([( + Self::Payload::TAG.to_string(), + Named::::from(self.payload().clone()).into(), + )]) + .into(); + ipld.encode( *varsig::header::Header::codec(self.varsig_header()), &mut encoded, diff --git a/src/delegation.rs b/src/delegation.rs index 67f873a0..e25aa5fe 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -21,6 +21,7 @@ mod payload; pub use agent::Agent; pub use payload::*; +use crate::ability::arguments::Named; use crate::{ capsule::Capsule, crypto::{signature::Envelope, varsig, Nonce}, @@ -126,7 +127,8 @@ impl, C: Codec + Into + TryFrom> Delega impl + Clone, C: Codec + TryFrom + Into> Envelope for Delegation where - Payload: TryFrom, + Payload: TryFrom>, + Named: From>, { type DID = DID; type Payload = Payload; @@ -166,7 +168,7 @@ where impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation where - Payload: TryFrom, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -179,8 +181,8 @@ where impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> Deserialize<'de> for Delegation where - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index c28cf6af..89f86a8b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,4 +1,5 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; +use crate::ability::arguments::Named; use crate::{ crypto::{signature::Envelope, varsig, Nonce}, did::Did, @@ -43,6 +44,8 @@ impl< > Agent<'a, DID, S, V, Enc> where Ipld: Encode, + Payload: TryFrom>, + Named: From>, { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { @@ -65,10 +68,7 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> - where - Payload: TryFrom, - { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -125,10 +125,7 @@ where &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, - ) -> Result<(), ReceiveError> - where - Payload: TryFrom, - { + ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f74e4a94..41d3048b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,12 @@ use super::policy::Predicate; +use crate::ability::arguments::Named; use crate::{ capsule::Capsule, crypto::{varsig, Nonce}, did::{Did, Verifiable}, time::{TimeBoundError, Timestamp}, }; +use core::str::FromStr; use derive_builder::Builder; use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -21,7 +23,7 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Builder)] +#[derive(Debug, Clone, PartialEq, Builder)] // FIXME Serialize, Deserialize, Builder)] pub struct Payload { /// The subject of the [`Delegation`]. /// @@ -110,17 +112,96 @@ impl Verifiable for Payload { } } -impl Deserialize<'de>> TryFrom for Payload { - type Error = SerdeError; +impl TryFrom> for Payload { + type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + fn try_from(args: Named) -> Result { + let mut subject = None; + let mut issuer = None; + let mut audience = None; + let mut via = None; + let mut command = None; + let mut policy = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut not_before = None; + + for (k, ipld) in args { + match k.as_str() { + "sub" => { + subject = Some( + match ipld { + Ipld::Null => None, + Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + } + .ok_or(())?, + ) + } + "iss" => match ipld { + Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "aud" => match ipld { + Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "via" => match ipld { + Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "cmd" => match ipld { + Ipld::String(s) => command = Some(s), + _ => return Err(()), + }, + "pol" => match ipld { + Ipld::List(xs) => { + policy = xs + .iter() + .map(|x| Predicate::try_from(x.clone()).ok()) + .collect(); + } + _ => return Err(()), + }, + "metadata" => match ipld { + Ipld::Map(m) => metadata = Some(m), + _ => return Err(()), + }, + "nonce" => match ipld { + Ipld::Bytes(b) => nonce = Some(Nonce::from(b).into()), + _ => return Err(()), + }, + "exp" => match ipld { + Ipld::Integer(i) => expiration = Some(Timestamp::try_from(i).map_err(|_| ())?), + _ => return Err(()), + }, + "nbf" => match ipld { + Ipld::Integer(i) => not_before = Some(Timestamp::try_from(i).map_err(|_| ())?), + _ => return Err(()), + }, + _ => (), + } + } + + Ok(Payload { + subject, + issuer: issuer.ok_or(())?, + audience: audience.ok_or(())?, + via, + command: command.ok_or(())?, + policy: policy.ok_or(())?, + metadata: metadata.ok_or(())?, + nonce: nonce.ok_or(())?, + expiration: expiration.ok_or(())?, + not_before, + }) } } -impl From> for Ipld { +impl From> for Named { fn from(payload: Payload) -> Self { - let mut btree: BTreeMap = BTreeMap::::from_iter([ + let mut args = Named::::from_iter([ ("iss".to_string(), Ipld::from(payload.issuer.to_string())), ("aud".to_string(), payload.audience.to_string().into()), ("cmd".to_string(), payload.command.into()), @@ -133,20 +214,20 @@ impl From> for Ipld { ]); if let Some(subject) = payload.subject { - btree.insert("sub".to_string(), Ipld::from(subject.to_string())); + args.insert("sub".to_string(), Ipld::from(subject.to_string())); } else { - btree.insert("sub".to_string(), Ipld::Null); + args.insert("sub".to_string(), Ipld::Null); } if let Some(not_before) = payload.not_before { - btree.insert("nbf".to_string(), Ipld::from(not_before)); + args.insert("nbf".to_string(), Ipld::from(not_before)); } if !payload.metadata.is_empty() { - btree.insert("metadata".to_string(), Ipld::Map(payload.metadata)); + args.insert("meta".to_string(), Ipld::Map(payload.metadata)); } - Ipld::from(btree) + args } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index c73dbbf6..d222cc3e 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,11 +1,10 @@ use super::selector::filter::Filter; -use super::selector::{Select, SelectorError}; +use super::selector::{Select, Selector, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; -use multihash::Hasher; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; +use std::{fmt, str::FromStr}; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -13,7 +12,7 @@ use proptest::prelude::*; // FIXME Normal form? // FIXME exract domain gen selectors first? // FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub enum Predicate { // Comparison Equal(Select, ipld::Newtype), @@ -73,24 +72,24 @@ impl Harmonization { impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { - Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, - Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, - Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, - Predicate::LessThan(lhs, rhs_data) => lhs.resolve(data)? < rhs_data, - Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? <= rhs_data, - Predicate::Like(lhs, rhs_data) => glob(&lhs.resolve(data)?, &rhs_data), + Predicate::Equal(lhs, rhs_data) => lhs.get(data)? == rhs_data, + Predicate::GreaterThan(lhs, rhs_data) => lhs.get(data)? > rhs_data, + Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.get(data)? >= rhs_data, + Predicate::LessThan(lhs, rhs_data) => lhs.get(data)? < rhs_data, + Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.get(data)? <= rhs_data, + Predicate::Like(lhs, rhs_data) => glob(&lhs.get(data)?, &rhs_data), Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, Predicate::Every(xs, p) => xs - .resolve(data)? + .get(data)? .to_vec() .iter() .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, Predicate::Some(xs, p) => { let pred = p.clone(); - xs.resolve(data)? + xs.get(data)? .to_vec() .iter() .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? @@ -692,6 +691,91 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } +impl TryFrom for Predicate { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::List(v) => match v.as_slice() { + [Ipld::String(s), inner] if s == "not" => { + let inner = Box::new(Predicate::try_from(inner.clone())?); + Ok(Predicate::Not(inner)) + } + [Ipld::String(op_str), Ipld::String(sel_str), val] => match op_str.as_str() { + "==" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + + Ok(Predicate::Equal(sel, ipld::Newtype(val.clone()))) + } + ">" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::GreaterThan(sel, num)) + } + ">=" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::GreaterThanOrEqual(sel, num)) + } + "<" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::LessThan(sel, num)) + } + "<=" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::LessThanOrEqual(sel, num)) + } + "like" => { + let sel = Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + if let Ipld::String(s) = val { + Ok(Predicate::Like(sel, s.to_string())) + } else { + Err(()) + } + } + "every" => { + let sel = Select::::from_str(sel_str.as_str()) + .map_err(|_| ())?; + + let p = Box::new(Predicate::try_from(val.clone())?); + Ok(Predicate::Every(sel, p)) + } + "some" => { + let sel = Select::::from_str(sel_str.as_str()) + .map_err(|_| ())?; + let p = Box::new(Predicate::try_from(val.clone())?); + Ok(Predicate::Some(sel, p)) + } + _ => Err(()), + }, + [Ipld::String(op_str), lhs, rhs] => match op_str.as_str() { + "and" => { + let lhs = Box::new(Predicate::try_from(lhs.clone())?); + let rhs = Box::new(Predicate::try_from(rhs.clone())?); + Ok(Predicate::And(lhs, rhs)) + } + "or" => { + let lhs = Box::new(Predicate::try_from(lhs.clone())?); + let rhs = Box::new(Predicate::try_from(rhs.clone())?); + Ok(Predicate::Or(lhs, rhs)) + } + _ => Err(()), + }, + _ => Err(()), + }, + _ => Err(()), + } + } +} + impl From for Ipld { fn from(p: Predicate) -> Self { match p { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 965b89a1..e88cd17f 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -8,7 +8,9 @@ pub use error::{ParseError, SelectorErrorReason}; pub use select::Select; pub use selectable::Selectable; +use crate::ipld; use filter::Filter; +use libipld_core::ipld::Ipld; use nom::{ self, branch::alt, @@ -36,6 +38,86 @@ impl Selector { pub fn is_related(&self, other: &Selector) -> bool { self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) } + + // pub fn get(&self, ctx: &Ipld) -> Result { + // let ipld: Ipld = self + // .0 + // .iter() + // .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + // seen_ops.push(op); + // + // match op { + // Filter::Try(inner) => { + // let op: Filter = *inner.clone(); + // let ipld: Ipld = Select::Get::(vec![op]) + // .resolve(ctx) + // .unwrap_or(Ipld::Null); + // + // Ok((ipld, seen_ops)) + // } + // Filter::ArrayIndex(i) => { + // let result = { + // match ipld { + // Ipld::List(xs) => { + // if i.abs() as usize > xs.len() { + // return Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::IndexOutOfBounds, + // )); + // }; + // + // xs.get((xs.len() as i32 + *i) as usize) + // .ok_or(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::IndexOutOfBounds, + // )) + // .cloned() + // } + // // FIXME behaviour on maps? type error + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotAList, + // )), + // } + // }; + // + // Ok((result?, seen_ops)) + // } + // Filter::Field(k) => { + // let result = match ipld { + // Ipld::Map(xs) => xs + // .get(k) + // .ok_or(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::KeyNotFound, + // )) + // .cloned(), + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotAMap, + // )), + // }; + // + // Ok((result?, seen_ops)) + // } + // Filter::Values => { + // let result = match ipld { + // Ipld::List(xs) => Ok(Ipld::List(xs)), + // Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotACollection, + // )), + // }; + // + // Ok((result?, seen_ops)) + // } + // } + // })? + // .0; + // + // Ok(ipld::Newtype(ipld)) + // } } pub fn parse(input: &str) -> IResult<&str, Selector> { diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index e9c32ae2..6d36c02b 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -3,112 +3,108 @@ use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorErro use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::str::FromStr; #[cfg(feature = "test_utils")] use proptest::prelude::*; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Select { - Get(Vec), - Pure(T), +#[derive(Debug, Clone, PartialEq)] +pub struct Select { + filters: Vec, + _marker: std::marker::PhantomData, } impl Select { + pub fn new(filters: Vec) -> Self { + Self { + filters, + _marker: std::marker::PhantomData, + } + } + pub fn is_related(&self, other: &Select) -> bool where Ipld: From + From, { - match (self, other) { - (Select::Pure(lhs_val), Select::Pure(rhs_val)) => { - Ipld::from(lhs_val.clone()) == Ipld::from(rhs_val.clone()) - } - (Select::Get(lhs_path), Select::Get(rhs_path)) => { - Selector(lhs_path.clone()).is_related(&Selector(rhs_path.clone())) - } - _ => false, - } + Selector(self.filters.clone()).is_related(&Selector(other.filters.clone())) } - pub fn resolve(self, ctx: &Ipld) -> Result { - match self { - Select::Pure(inner) => Ok(inner), - Select::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - Filter::Try(inner) => { - let op: Filter = *inner.clone(); - let ipld: Ipld = Select::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - Filter::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )); - }; - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( + + pub fn get(self, ctx: &Ipld) -> Result { + self.filters + .iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + Filter::Try(inner) => { + let op: Filter = *inner.clone(); + let ipld: Ipld = + Select::::new(vec![op]).get(ctx).unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + Filter::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( &seen_ops, - SelectorErrorReason::NotAList, - )), - } - }; + SelectorErrorReason::IndexOutOfBounds, + )); + }; - Ok((result?, seen_ops)) - } - Filter::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) + xs.get((xs.len() as i32 + *i) as usize) .ok_or(SelectorError::from_refs( &seen_ops, - SelectorErrorReason::KeyNotFound, + SelectorErrorReason::IndexOutOfBounds, )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?, seen_ops)) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), } - Filter::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?, seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } + }; + + Ok((result?, seen_ops)) + } + Filter::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + Filter::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) } } @@ -117,28 +113,25 @@ where Ipld: From, { fn from(s: Select) -> Self { - match s { - Select::Get(ops) => Selector(ops).to_string().into(), - Select::Pure(inner) => inner.into(), - } + Selector(s.filters).to_string().into() + } +} + +impl FromStr for Select { + type Err = (); + + fn from_str(s: &str) -> Result { + let selector = Selector::from_str(s).map_err(|_| ())?; + Ok(Select { + filters: selector.0, + _marker: std::marker::PhantomData, + }) } } impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Select::Pure(inner), Select::Pure(other_inner)) => { - if inner == other_inner { - Some(Ordering::Equal) - } else { - None - } - } - (Select::Get(ops), Select::Get(other_ops)) => { - Selector(ops.clone()).partial_cmp(&Selector(other_ops.clone())) - } - _ => None, - } + Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) } } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 4b9373bb..5b958d5f 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,5 +1,5 @@ use super::Store; -use crate::crypto::signature::Envelope; +use crate::ability::arguments::Named; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, @@ -7,10 +7,7 @@ use crate::{ }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; +use std::collections::{BTreeMap, BTreeSet}; use web_time::SystemTime; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -113,8 +110,8 @@ impl< Enc: Codec + TryFrom + Into, > Store for MemoryStore where - Ipld: From>, - delegation::Payload: TryFrom, + Named: From>, + delegation::Payload: TryFrom>, Delegation: Encode, { type DelegationStoreError = String; // FIXME misisng @@ -270,6 +267,7 @@ where #[cfg(test)] mod tests { + use crate::ability::arguments::Named; use crate::ability::command::Command; use crate::crypto::signature::Envelope; use crate::delegation::store::Store; @@ -330,10 +328,8 @@ mod tests { .audience(server.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), - ) - .expect("signature to work"); + .build()?, + )?; // 2. server -a-> device let account_device_ucan = crate::Delegation::try_sign( @@ -345,10 +341,8 @@ mod tests { .audience(device.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), // I don't love this is now failable - ) - .expect("signature to work"); + .build()?, // I don't love this is now failable + )?; // 3. dnslink -d-> account let dnslink_ucan = crate::Delegation::try_sign( @@ -360,30 +354,64 @@ mod tests { .audience(account.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), - ) - .expect("signature to work"); + .build()?, + )?; #[derive(Debug, Clone, PartialEq)] - pub struct AccountInfo {} + pub struct AccountManage; - impl Command for AccountInfo { - const COMMAND: &'static str = "/account/info"; + use crate::invocation::promise::CantResolve; + use crate::invocation::promise::Resolvable; + use crate::ipld; + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } } - impl From for AccountInfo { - fn from(_: Ipld) -> Self { - AccountInfo {} + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() } } - impl From for Ipld { - fn from(_: AccountInfo) -> Self { - Ipld::Null + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } } } + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + // #[derive(Debug, Clone, PartialEq)] // pub struct DnsLinkUpdate { // pub cid: Cid, @@ -398,17 +426,15 @@ mod tests { // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, - varsig_header, + varsig_header.clone(), crate::invocation::PayloadBuilder::default() .subject(account.clone()) .issuer(device.clone()) .audience(Some(server.clone())) - .ability(AccountInfo {}) + .ability(AccountManage) .proofs(vec![]) // FIXME - .build() - .expect("FIXME"), - ) - .expect("FIXME"); + .build()?, + )?; // FIXME reenable // let dnslink_invocation = crate::Invocation::try_sign( @@ -432,7 +458,7 @@ mod tests { varsig::encoding::Preset, > = Default::default(); - let agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); let _ = store.insert( account_device_ucan.cid().expect("FIXME"), @@ -461,11 +487,49 @@ mod tests { SystemTime::now(), ); + use crate::invocation::Agent; + let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); + + let mut agent: Agent< + '_, + crate::invocation::store::MemoryStore, + crate::delegation::store::MemoryStore, + crate::invocation::promise::store::MemoryStore, + AccountManage, + > = Agent::new( + &server, + &server_signer, + &mut inv_store, + &mut del_store, + &mut prom_store, + ); + + let observed = agent.receive(account_invocation, &SystemTime::now()); + assert!(observed.is_ok()); + + let not_account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(server.clone()) + .audience(Some(device.clone())) + .ability(AccountManage) + .proofs(vec![]) // FIXME + .build()?, + )?; + + let observed_other = agent.receive(not_account_invocation, &SystemTime::now()); + assert!(observed_other.is_err()); + Ok(()) } } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 16fe3814..c6e91443 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,7 +8,14 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store< + + + + DID: Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, + > { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; diff --git a/src/did/traits.rs b/src/did/traits.rs index 53bebea9..76e9220f 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,9 +1,8 @@ use did_url::DID; use std::fmt; +use std::str::FromStr; -pub trait Did: - PartialEq + ToString + TryFrom + Into + signature::Verifier + Ord -{ +pub trait Did: PartialEq + ToString + FromStr + signature::Verifier + Ord { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/invocation.rs b/src/invocation.rs index 09eaad20..0d64902a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -21,6 +21,7 @@ pub mod store; pub use agent::Agent; pub use payload::*; +use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::{ crypto::{signature::Envelope, varsig}, @@ -60,6 +61,22 @@ pub struct Invocation< _marker: std::marker::PhantomData, } +impl< + A: Clone + ToCommand, + DID: Clone + did::Did, + V: Clone + varsig::Header, + C: Codec + TryFrom + Into, + > Encode for Invocation +where + Ipld: Encode, + Named: From + From>, + Payload: TryFrom>, +{ + fn encode(&self, c: C, w: &mut W) -> Result<(), libipld_core::error::Error> { + self.to_ipld_envelope().encode(c, w) + } +} + impl, C: Codec + TryFrom + Into> Invocation where @@ -133,8 +150,8 @@ impl< C: Codec + TryFrom + Into, > From> for Ipld where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { fn from(invocation: Invocation) -> Self { invocation.to_ipld_envelope() @@ -148,8 +165,8 @@ impl< C: Codec + TryFrom + Into, > Envelope for Invocation where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { type DID = DID; type Payload = Payload; @@ -193,8 +210,8 @@ impl< C: Codec + TryFrom + Into, > Serialize for Invocation where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -212,9 +229,9 @@ impl< C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where - Ipld: From, - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Named: From, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4fad1b07..aefd5117 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -4,6 +4,7 @@ use super::{ store::Store, Invocation, }; +use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, @@ -33,8 +34,8 @@ use web_time::SystemTime; pub struct Agent< 'a, S: Store, - P: promise::Store, D: delegation::store::Store, + P: promise::Store, T: Resolvable + ToCommand = ability::preset::Preset, DID: Did = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, @@ -56,16 +57,17 @@ pub struct Agent< marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, P, D, T, DID, V, C> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, D, P, T, DID, V, C> where - Ipld: Encode + From, + Ipld: Encode, + Named: From, delegation::Payload: Clone, T: Resolvable + ToCommand + Clone, T::Promised: Clone + ToCommand, DID: Did + Clone, S: Store, - P: promise::Store, D: delegation::store::Store, + P: promise::Store, V: varsig::Header + Clone, C: Codec + Into + TryFrom, { @@ -105,8 +107,8 @@ where >, > where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { let proofs = self .delegation_store @@ -153,8 +155,8 @@ where >, > where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { let proofs = self .delegation_store @@ -188,14 +190,14 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where - Ipld: From + From, - Payload: TryFrom, - arguments::Named: From, + arguments::Named: From + From, + Payload: TryFrom>, Invocation: Clone + Encode, -

>::PromiseStoreError: fmt::Debug, +

::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; + let _ = promised .validate_signature() .map_err(ReceiveError::SigVerifyError)?; @@ -253,9 +255,9 @@ where // FIXME return type ) -> Result, ()> where - Ipld: From, + Named: From, T: From, - Payload: TryFrom, + Payload: TryFrom>, { let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { @@ -301,14 +303,14 @@ pub enum Recipient { #[derive(Debug, Error)] pub enum ReceiveError< T: Resolvable, - P: promise::Store, + P: promise::Store, DID: Did, D, S: Store, V: varsig::Header, C: Codec + TryFrom + Into, > where -

>::PromiseStoreError: fmt::Debug, +

::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] @@ -322,8 +324,8 @@ pub enum ReceiveError< #[source] ::Promised, DID, V, C>>::InvocationStoreError, ), - #[error("promise store error: {0}")] - PromiseStoreError(#[source]

>::PromiseStoreError), + #[error("promise store error: {0:?}")] // FIXME + PromiseStoreError(

::PromiseStoreError), #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), @@ -343,3 +345,47 @@ pub enum InvokeError { #[error("promise store error: {0}")] PromiseResolveError(#[source] ArgsErr), } + +#[cfg(test)] +mod tests { + use super::*; + use rand::thread_rng; + use testresult::TestResult; + + #[test_log::test] + fn test_agent_creation<'a>() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); + + let agent: crate::invocation::agent::Agent< + '_, + crate::invocation::store::MemoryStore, + crate::delegation::store::MemoryStore, + crate::invocation::promise::store::MemoryStore, + > = Agent::new( + &server, + &server_signer, + &mut inv_store, + &mut del_store, + &mut prom_store, + ); + + assert!(false); + + // assert_eq!(agent.did, &did); + // assert_eq!(agent.invocation_store, &invocation_store); + // assert_eq!(agent.delegation_store, &delegation_store); + // assert_eq!(agent.unresolved_promise_index, &unresolved_promise_index); + + Ok(()) + } +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fa281e76..0969db05 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,4 +1,6 @@ use super::promise::Resolvable; +use crate::ability::command::Command; +use crate::invocation::Named; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, @@ -249,13 +251,19 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i@1.0.0-rc.1"; } -impl, DID: Did> From> for arguments::Named { +impl From> for arguments::Named +where + arguments::Named: From, +{ fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ ("iss".into(), payload.issuer.to_string().into()), ("sub".into(), payload.subject.to_string().into()), ("cmd".into(), payload.ability.to_command().into()), - ("args".into(), payload.ability.into()), + ( + "args".into(), + arguments::Named::::from(payload.ability).into(), + ), ( "prf".into(), Ipld::List(payload.proofs.iter().map(Into::into).collect()), @@ -264,7 +272,7 @@ impl, DID: Did> From> for arguments::N ]); if let Some(aud) = payload.audience { - args.insert("aud".into(), aud.into().to_string().into()); + args.insert("aud".into(), aud.to_string().into()); } if let Some(iat) = payload.issued_at { @@ -470,18 +478,108 @@ impl Verifiable for Payload { } } -use crate::ability::command::Command; - -impl + Command, DID: Did> TryFrom for Payload { +impl> + Command, DID: Did> + TryFrom> for Payload +where + >>::Error: fmt::Debug, +{ type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(btree) = ipld { - let payload_ipld = btree.get(A::COMMAND).ok_or(())?; - payload_ipld.clone().try_into().map_err(|_| ()) - } else { - Err(()) + fn try_from(named: arguments::Named) -> Result { + let mut subject = None; + let mut issuer = None; + let mut audience = None; + let mut via = None; + let mut command = None; + let mut args = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut proofs = None; + let mut issued_at = None; + + for (k, v) in named { + match k.as_str() { + "sub" => { + subject = Some( + match v { + Ipld::Null => None, + Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + } + .ok_or(())?, + ) + } + "iss" => match v { + Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "aud" => match v { + Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "via" => match v { + Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "cmd" => match v { + Ipld::String(s) => command = Some(s), + _ => return Err(()), + }, + "args" => match v.try_into() { + Ok(a) => args = Some(a), + Err(_) => return Err(()), + }, + "meta" => match v { + Ipld::Map(m) => metadata = Some(m), + _ => return Err(()), + }, + "nonce" => match v { + Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), + _ => return Err(()), + }, + "exp" => match v { + Ipld::Integer(i) => expiration = Some(i.try_into().map_err(|_| ())?), + _ => return Err(()), + }, + "iat" => match v { + Ipld::Integer(i) => issued_at = Some(i.try_into().map_err(|_| ())?), + _ => return Err(()), + }, + "prf" => match v { + Ipld::List(xs) => { + proofs = Some( + xs.into_iter() + .map(|x| match x { + Ipld::Link(cid) => Ok(cid), + _ => Err(()), + }) + .collect::, ()>>() + .map_err(|_| ())?, + ) + } + _ => return Err(()), + }, + _ => return Err(()), + } } + + let cmd = command.ok_or(())?; + let some_args = args.ok_or(())?; + let ability = ::try_parse(cmd.as_str(), some_args).map_err(|_| ())?; + + Ok(Payload { + issuer: issuer.ok_or(())?, + subject: subject.ok_or(())?, + audience, + ability, + proofs: proofs.ok_or(())?, + cause: None, + metadata: metadata.ok_or(())?, + nonce: nonce.ok_or(())?, + issued_at, + expiration, + }) } } @@ -490,14 +588,14 @@ impl + Command, DID: Did> TryFrom for Payload { /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld -where - Ipld: From, -{ - fn from(payload: Payload) -> Self { - arguments::Named::from(payload).into() - } -} +// impl From> for Ipld +// where +// Named: From, +// { +// fn from(payload: Payload) -> Self { +// arguments::Named::from(payload).into() +// } +// } #[cfg(feature = "test_utils")] impl Arbitrary for Payload diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index 3a221e41..6f587f7a 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -6,12 +6,12 @@ use std::{ convert::Infallible, }; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct MemoryStore { pub index: BTreeMap>, } -impl Store for MemoryStore { +impl Store for MemoryStore { type PromiseStoreError = Infallible; fn put_waiting( diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 02d80686..0e894c84 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -2,7 +2,7 @@ use crate::{did::Did, invocation::promise::Resolvable}; use libipld_core::cid::Cid; use std::collections::BTreeSet; -pub trait Store { +pub trait Store { type PromiseStoreError; fn put_waiting( diff --git a/src/invocation/store.rs b/src/invocation/store.rs index e7e3ad50..846a863d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,11 +1,18 @@ //! Storage for [`Invocation`]s. use super::Invocation; +use crate::ability; use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store, Enc: Codec + Into + TryFrom> { +pub trait Store< + T = crate::ability::preset::Preset, + DID: Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, +> +{ type InvocationStoreError; fn get( @@ -25,8 +32,23 @@ pub trait Store, Enc: Codec + Into + Tr } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, Enc: Codec + Into + TryFrom> { - store: BTreeMap>, +pub struct MemoryStore< + T = crate::ability::preset::PromisedPreset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>, +} + +impl, Enc: Codec + Into + TryFrom> Default + for MemoryStore +{ + fn default() -> Self { + Self { + store: BTreeMap::new(), + } + } } impl, Enc: Codec + Into + TryFrom> diff --git a/src/receipt.rs b/src/receipt.rs index c96a7cf9..38fbf4dd 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,6 +14,7 @@ pub use payload::*; pub use responds::Responds; pub use store::Store; +use crate::ability::arguments; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -51,7 +52,8 @@ impl< C: Codec + TryFrom + Into, > From> for Ipld where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { fn from(rec: Receipt) -> Self { rec.to_ipld_envelope() @@ -65,7 +67,8 @@ impl< C: Codec + TryFrom + Into, > Envelope for Receipt where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { type DID = DID; type Payload = Payload; @@ -109,7 +112,8 @@ impl< C: Codec + TryFrom + Into, > Serialize for Receipt where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -127,8 +131,9 @@ impl< C: Codec + TryFrom + Into, > Deserialize<'de> for Receipt where - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Ipld: From, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4bc40a96..cd3d22ce 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -94,6 +94,47 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/r@1.0.0-rc.1"; } +impl From> for arguments::Named +where + Ipld: From, +{ + fn from(payload: Payload) -> Self { + let out_ipld = match payload.out { + Ok(ok) => BTreeMap::from_iter([("ok".to_string(), Ipld::from(ok))]).into(), + Err(err) => BTreeMap::from_iter([("err".to_string(), err.0.into())]).into(), + }; + + let mut args = arguments::Named::::from_iter([ + ("iss".to_string(), Ipld::String(payload.issuer.to_string())), + ("ran".to_string(), payload.ran.into()), + ("out".to_string(), out_ipld), + ( + "next".to_string(), + Ipld::List( + payload + .next + .clone() + .into_iter() + .map(|x| Ipld::Link(x)) + .collect(), + ), + ), + ( + "prf".to_string(), + Ipld::List(payload.next.into_iter().map(|x| Ipld::Link(x)).collect()), + ), + ("meta".to_string(), payload.metadata.into()), + ("nonce".to_string(), payload.nonce.into()), + ]); + + if let Some(issued_at) = payload.issued_at { + args.insert("iat".to_string(), issued_at.into()); + } + + args + } +} + impl Serialize for Payload where T::Success: Serialize, @@ -106,7 +147,7 @@ where let mut state = serializer.serialize_struct("receipt::Payload", field_count)?; - state.serialize_field("iss", &self.issuer.clone().into().as_str())?; + state.serialize_field("iss", &self.issuer.to_string().as_str())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index a378cf83..2e0f60c4 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -126,6 +126,30 @@ impl From for Ipld { } } +impl TryFrom for Timestamp { + type Error = (); + + fn try_from(ipld: Ipld) -> Result { + match ipld { + // FIXME do bounds checking + Ipld::Integer(secs) => Ok(Timestamp::new( + UNIX_EPOCH + Duration::from_secs(secs as u64), + ) + .map_err(|_| ())?), + _ => Err(()), + } + } +} + +impl TryFrom for Timestamp { + type Error = OutOfRangeError; + + fn try_from(secs: i128) -> Result { + // FIXME do bounds checking + Timestamp::new(UNIX_EPOCH + Duration::from_secs(secs as u64)) + } +} + impl Serialize for Timestamp { fn serialize(&self, serializer: S) -> Result where