From 521b53dbc4ee8a6826662f56a96ff8b88824d23c Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 22 May 2023 15:59:14 +0200 Subject: [PATCH] =?UTF-8?q?Remove=20`identity=5Fagent`=20&`identity=5Fcomm?= =?UTF-8?q?`=20=F0=9F=A5=B2=EF=B8=8F=20(#1168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 3 - identity_agent/Cargo.toml | 40 -- identity_agent/README.md | 192 ---------- identity_agent/benches/agent.rs | 144 ------- identity_agent/benches/didcomm.rs | 130 ------- identity_agent/src/agent/agent.rs | 261 ------------- identity_agent/src/agent/agent_builder.rs | 206 ---------- identity_agent/src/agent/agent_state.rs | 14 - identity_agent/src/agent/config.rs | 18 - identity_agent/src/agent/endpoint.rs | 142 ------- identity_agent/src/agent/errors.rs | 117 ------ identity_agent/src/agent/handler.rs | 106 ------ identity_agent/src/agent/mod.rs | 23 -- identity_agent/src/agent/request.rs | 35 -- identity_agent/src/agent/request_context.rs | 32 -- identity_agent/src/didcomm/agent.rs | 297 --------------- identity_agent/src/didcomm/agent_builder.rs | 161 -------- identity_agent/src/didcomm/dcpm.rs | 63 ---- identity_agent/src/didcomm/handler.rs | 143 ------- identity_agent/src/didcomm/mod.rs | 16 - identity_agent/src/didcomm/request.rs | 26 -- identity_agent/src/didcomm/thread_id.rs | 23 -- identity_agent/src/lib.rs | 21 -- identity_agent/src/p2p/behaviour.rs | 78 ---- identity_agent/src/p2p/event_loop.rs | 239 ------------ identity_agent/src/p2p/message.rs | 44 --- identity_agent/src/p2p/mod.rs | 12 - identity_agent/src/p2p/net_commander.rs | 154 -------- identity_agent/src/tests/didcomm.rs | 281 -------------- identity_agent/src/tests/handler.rs | 354 ------------------ identity_agent/src/tests/mod.rs | 79 ---- identity_agent/src/tests/presentation.rs | 161 -------- .../src/tests/remote_account/error.rs | 12 - .../src/tests/remote_account/handler.rs | 62 --- .../src/tests/remote_account/mod.rs | 11 - .../src/tests/remote_account/requests.rs | 47 --- .../src/tests/remote_account/tests.rs | 63 ---- identity_comm/Cargo.toml | 27 -- identity_comm/README.md | 21 -- identity_comm/src/envelope/encrypted.rs | 130 ------- identity_comm/src/envelope/mod.rs | 13 - identity_comm/src/envelope/plaintext.rs | 38 -- identity_comm/src/envelope/signed.rs | 100 ----- identity_comm/src/envelope/traits.rs | 9 - identity_comm/src/error.rs | 18 - identity_comm/src/lib.rs | 24 -- identity_comm/src/message/mod.rs | 6 - identity_comm/src/message/traits.rs | 65 ---- identity_comm/src/types.rs | 5 - 49 files changed, 4266 deletions(-) delete mode 100644 identity_agent/Cargo.toml delete mode 100644 identity_agent/README.md delete mode 100644 identity_agent/benches/agent.rs delete mode 100644 identity_agent/benches/didcomm.rs delete mode 100644 identity_agent/src/agent/agent.rs delete mode 100644 identity_agent/src/agent/agent_builder.rs delete mode 100644 identity_agent/src/agent/agent_state.rs delete mode 100644 identity_agent/src/agent/config.rs delete mode 100644 identity_agent/src/agent/endpoint.rs delete mode 100644 identity_agent/src/agent/errors.rs delete mode 100644 identity_agent/src/agent/handler.rs delete mode 100644 identity_agent/src/agent/mod.rs delete mode 100644 identity_agent/src/agent/request.rs delete mode 100644 identity_agent/src/agent/request_context.rs delete mode 100644 identity_agent/src/didcomm/agent.rs delete mode 100644 identity_agent/src/didcomm/agent_builder.rs delete mode 100644 identity_agent/src/didcomm/dcpm.rs delete mode 100644 identity_agent/src/didcomm/handler.rs delete mode 100644 identity_agent/src/didcomm/mod.rs delete mode 100644 identity_agent/src/didcomm/request.rs delete mode 100644 identity_agent/src/didcomm/thread_id.rs delete mode 100644 identity_agent/src/lib.rs delete mode 100644 identity_agent/src/p2p/behaviour.rs delete mode 100644 identity_agent/src/p2p/event_loop.rs delete mode 100644 identity_agent/src/p2p/message.rs delete mode 100644 identity_agent/src/p2p/mod.rs delete mode 100644 identity_agent/src/p2p/net_commander.rs delete mode 100644 identity_agent/src/tests/didcomm.rs delete mode 100644 identity_agent/src/tests/handler.rs delete mode 100644 identity_agent/src/tests/mod.rs delete mode 100644 identity_agent/src/tests/presentation.rs delete mode 100644 identity_agent/src/tests/remote_account/error.rs delete mode 100644 identity_agent/src/tests/remote_account/handler.rs delete mode 100644 identity_agent/src/tests/remote_account/mod.rs delete mode 100644 identity_agent/src/tests/remote_account/requests.rs delete mode 100644 identity_agent/src/tests/remote_account/tests.rs delete mode 100644 identity_comm/Cargo.toml delete mode 100644 identity_comm/README.md delete mode 100644 identity_comm/src/envelope/encrypted.rs delete mode 100644 identity_comm/src/envelope/mod.rs delete mode 100644 identity_comm/src/envelope/plaintext.rs delete mode 100644 identity_comm/src/envelope/signed.rs delete mode 100644 identity_comm/src/envelope/traits.rs delete mode 100644 identity_comm/src/error.rs delete mode 100644 identity_comm/src/lib.rs delete mode 100644 identity_comm/src/message/mod.rs delete mode 100644 identity_comm/src/message/traits.rs delete mode 100644 identity_comm/src/types.rs diff --git a/Cargo.toml b/Cargo.toml index 08b629b76a..4a4d978fc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,6 @@ [workspace] resolver = "2" members = [ - # Temporarily excluded for the 0.7.0-alpha.5 release. - # "identity_agent", - # "identity_comm", "identity_core", "identity_credential", "identity_did", diff --git a/identity_agent/Cargo.toml b/identity_agent/Cargo.toml deleted file mode 100644 index d499e3a25a..0000000000 --- a/identity_agent/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "identity_agent" -version = "0.7.0-alpha.5" -authors.workspace = true -edition.workspace = true -homepage.workspace = true -keywords = ["iota", "tangle", "identity", "p2p", "agent"] -license.workspace = true -publish = false -readme = "./README.md" -repository.workspace = true -description = "A peer-to-peer communication framework for building SSI agents on IOTA Identity" - -[dependencies] -async-trait = { version = "0.1", default-features = false } -dashmap = { version = "5.3", default-features = false } -futures = { version = "0.3", default-features = false } -identity_core = { version = "=0.7.0-alpha.5", path = "../identity_core", default-features = false } -libp2p = { version = "0.45", default-features = false, features = ["tcp-tokio", "dns-tokio", "websocket", "request-response", "noise", "yamux"] } -log = { version = "0.4", default-features = false } -serde = { version = "1.0", default-features = false, features = ["derive"] } -serde_json.workspace = true -thiserror = { version = "1.0", default-features = false } -tokio = { version = "1.21", default-features = false, features = ["rt", "time"] } -uuid = { version = "0.8", default-features = false, features = ["v4", "serde"] } - -[dev-dependencies] -criterion = { version = "0.3", default-features = false, features = ["stable"] } -identity_iota_core = { path = "../identity_iota_core", default-features = false } -pretty_env_logger = { version = "0.4", default-features = false } -rand = "0.8.5" -tokio = { version = "*", default-features = false, features = ["sync", "macros"] } - -[[bench]] -name = "agent" -harness = false - -[[bench]] -name = "didcomm" -harness = false diff --git a/identity_agent/README.md b/identity_agent/README.md deleted file mode 100644 index 38d99606bf..0000000000 --- a/identity_agent/README.md +++ /dev/null @@ -1,192 +0,0 @@ -# IOTA Identity Agent - -The identity agent is a peer-to-peer communication framework for building SSI agents on IOTA Identity. It is intended to host implementations of the [DIDComm protocols](https://wiki.iota.org/identity.rs/specs/didcomm/overview) with future updates. Together with these protocols, this will, for example, allow for did-authenticated communication between two identities to exchange verifiable credentials or presentations. - -For a high-level and less technical introduction, see the [blog post](https://blog.iota.org/the-iota-identity-actor-explained/) on the agent (formerly known as identity actor). - -The most important dependency of the agent is libp2p. [What is libp2p?](https://docs.libp2p.io/introduction/what-is-libp2p/) - -> The one-liner pitch is that libp2p is a modular system of _protocols_, _specifications_ and _libraries_ that enable the development of peer-to-peer network applications. - -We use libp2p because it can easily secure transports using the noise protocol, is agnostic of transports (so agents could conceivably communicate over TCP, websockets or Bluetooth), and because of how flexible it is we can make it suit the agent nicely. - -## Building an agent - -```rust,ignore -let id_keys: IdentityKeypair = IdentityKeypair::generate_ed25519(); -let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse()?; - -let mut agent: Agent = AgentBuilder::new() - .keypair(id_keys) - .build() - .await?; - -agent.start_listening(addr).await?; -``` - -To build a minimal working agent, we generate a new `IdentityKeypair` from which the `AgentId` of the agent is derived. The `AgentId` is an alias for a `libp2p::PeerId`, which allows for cryptographically verifiable identification of a peer. This decouples the identity concept from the underlying network address, which is important if the agent roams across networks. If we want the agent to have the same `AgentId` across program executions, we need to store this keypair. Next we create the address for the agent to listen on. A `Multiaddr` is the address format in libp2p to encode addresses of various transports. Finally, we build the agent with a default transport, that supports DNS resolution and can use TCP or websockets. - -## Processing incoming requests - -To make the agent do something useful, we need handlers. A handler is some state with associated behavior that processes incoming requests. It will be invoked if the agent is able to deserialize the incoming request to the type the handler expects. The `Handler` is a trait that looks like this: - -```rust,ignore -#[async_trait::async_trait] -pub trait Handler: Debug + 'static { - async fn handle(&self, request: RequestContext) -> REQ::Response; -} -``` - -- It takes `&self` so it can modify its state through appropriate mechanisms, such as locks. A handler will thus typically implement a shallow copy mechanism (e.g. using `Arc`) to share state. -- It takes the request it wants to handle, which needs to implement the `HandlerRequest` trait and needs to return the defined response type. -- This trait can be implemented multiple times so the same handler can process different request types. - -Here is an example of a handler being attached on an `AgentBuilder`. We implement `RemoteAccounts`, an exemplary type that manages `Account`s remotely. - -```rust,ignore -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum RemoteAccountsError { - IdentityNotFound, -} - -/// The struct that will be sent over the network. -/// When received by an agent, the contained DID is looked up. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RemoteAccountsGet(pub IotaDID); - -impl HandlerRequest for RemoteAccountsGet { - /// The result of the lookup procedure, either the corresponding IotaDocument, or an error. - type Response = Result; - - /// `Endpoint`s are identifiers for requests which lets the remote agent determine the appropriate handler to invoke. - fn endpoint() -> Endpoint { - "remote_accounts/get".try_into().unwrap() - } -} - -/// Our thread-safe type that holds accounts that can be looked up by their DID. -#[derive(Debug, Clone, Default)] -pub struct RemoteAccounts { - accounts: Arc>, -} - -#[async_trait::async_trait] -impl Handler for RemoteAccounts { - /// To handle the request, we take the HandlerRequest type wrapped in a RequestContext, which provides some - /// useful information about the caller like their `AgentId`. - async fn handle(&self, request: RequestContext) -> Result { - self - .accounts - .get(&request.input.0) - .map(|account| account.document().to_owned()) - .ok_or(RemoteAccountsError::IdentityNotFound) - } -} - -/// To build the agent with our custom functionality, we first build the agent itself -/// and attach the handler. -async fn build_agent() { - let mut builder = AgentBuilder::new(); - builder.attach(RemoteAccounts::default()); -} -``` - -An agent that receives a request will check whether a handler is attached that can handle the request's `Endpoint` and if so, will invoke it. In our case, the agent will call the `handle` function of the handler when a `RemoteAccountsGet` request is received. If we wanted, we could attach more handlers to the same agent, and even implement `Handler` for `RemoteAccounts` multiple times, in order to handle different request types. - -## Sending requests - -To invoke a handler on a remote agent, we send a type that implements `HandlerRequest`, such as `RemoteAccountsGet`. - -```rust,ignore -let mut agent: Agent = builder.build().await?; - -agent.add_agent_address(remote_agent_id, addr).await?; - -let result: Result = agent - .send_request(remote_agent_id, RemoteAccountsGet("did:iota:...".parse()?)) - .await?; -``` - -After building the agent and adding the address of the remote agent, we can send a request. The agent takes care of serializing the request, and attempts to deserialize the response into `::Response`. - -## Agent modes - -We've just seen an example of a synchronous request, one where we invoke a handler on a remote agent and wait for it to finish execution and return a result. Next to the `Agent` type we also have a `DidCommAgent` type. The latter additionally supports an asynchronous mode, where we send a request without waiting for the result of the handler invocation. Instead, we can explicitly await a request: - -```rust,ignore -async fn didcomm_protocol(agent_id: AgentId, didcomm_agent: &DidCommAgent) -> AgentResult<()> { - let thread_id: ThreadId = ThreadId::new(); - - didcomm_agent - .send_didcomm_request(agent_id, &thread_id, PresentationOffer::default()) - .await?; - - let request: DidCommPlaintextMessage = didcomm_agent.await_didcomm_request(&thread_id).await?; - - Ok(()) -} -``` - -This request mode is implemented to support the implementation of [DIDComm](https://identity.foundation/didcomm-messaging/spec/) protocols, which is why a separate `DidCommAgent` is defined that extends the `Agent`s functionality and handles the specifics of DIDComm. Note that the base `Agent` doesn't support the asynchronous mode, but the `DidCommAgent` supports the sychronous mode. - -Here, the protocol expects us to first send a `PresentationOffer` request to the remote agent. This method call returns successfully if the request can be deserialized properly and if an appropriate handler exists on the remote agent, but the call might return before the handler on the remote has finished. According to the protocol we implement, we should expect the remote to send us a `PresentationRequest` so we explicitly call `await_didcomm_request` to await the incoming request on the same `ThreadId` that we sent our previous request on. This allows for imperative protocol implementations within a single handler. This is nice to have, because the alternative would be that each request invokes a separate handler in an agent, which would force protocol implementors to hold the state in some shared state, rather than implicitly in the function (such as the `thread_id` here). This setup is intended for DIDComm protocols, as it directly implements DIDComm concepts such as threads. - -## Examples - -There are currently no examples for the agent in the `examples` directory. This is mostly due to the instability of the agent. Still, there are two "examples" for each mode of operation as part of the `tests` module, the remote account as a synchronous example, and the IOTA DIDComm presentation protocol as an asynchronous example (this doesn't implement the actual protocol, it just asserts that requests can be sent back and forth as expected). The DIDComm example in particular is very simple and minimal and mostly exists as a proof of concept for the async mode, but it also serves as an example for how a DIDComm protocol could potentially be implemented. - -### DIDComm example setup - -The async mode didcomm examples are worth explaining a little more. The implementation difficulty for these protocols comes mostly because of how flexible they are. In the presentation protocol for example, both the holder and verifier can initiate the exchange. On the agent level this means either calling the protocol explicitly to initiate it, or attaching a handler to let the agent handle the protocol in the background when a remote agent initiates. Thus, there is one function that implements the actual protocol for each of the roles (i.e. _holder_ and _verifier_ in the `presentation` protocol). As an example, this is what the signature of the holder role would look like: - -```rust,ignore -pub(crate) async fn presentation_holder_handler( - mut agent: DidCommAgent, - agent_id: AgentId, - request: Option>, -) -> AgentResult<()> { ... } -``` - -The holder can call this function to initiate the protocol imperatively by passing `None` as `request`. On the other hand, if the verifier initiates, the holder defines a handler that will inject the received `request`: - -```rust,ignore -#[async_trait::async_trait] -impl DidCommHandler> for DidCommState { - async fn handle(&self, agent: DidCommAgent, request: RequestContext>) { - let result = presentation_holder_handler(agent, request.agent_id, Some(request.input)).await; - - if let Err(err) = result { - log::error!("presentation holder handler errored: {err:?}"); - } - } -} -``` - -and attaches it: - -```rust,ignore -didcomm_builder.attach::, _>(DidCommState::new()); -``` - -`DidCommState` holds the state for one or more DIDComm protocols. When a `PresentationRequest` is received, it calls the protocol function (`presentation_holder_handler`) to run through the protocol. This allows us to nicely reuse the `presentation_holder_handler` function as the core protocol implementation and only requires defining a thin handler method. The verifier can follow the same pattern for their side of the protocol. - -## Implementation Details - -This section goes into some details of agent internals. - -### Agent internals - -The overall architecture can be seen as four layers. A libp2p layer, a commander layer to interact with the libp2p layer, the raw agent layer (which uses the commander) and the `DidCommAgent` on top. This architecture is strongly inspired by [stronghold-p2p](https://github.com/iotaledger/stronghold.rs/tree/dev/p2p). - -- The p2p layer consists of a `libp2p::RequestResponse` protocol, which enforces on a type level that each request has a response. This naturally maps to the sync mode of the identity agent where each request has some response, as well as to the async mode where each request will be acknowledged. -- This layer has an `EventLoop` that concurrently polls the libp2p `Swarm` to handle its events as well as commands that are sent to it from the `NetCommander`. -- The commander layer, or `NetCommander` communicates with the event loop via channels and is thus the interface for the `EventLoop`. -- When the agent is built, it spawns an `EventLoop` in the background and interacts with it using the `NetCommander`. -- On incoming requests, the `EventLoop` spawns a new task and injects a clone of the agent into it (see `EventLoop::run` and its argument). - -### DidCommAgent internals - -- In async mode, the `DidCommAgent` returns an acknowledgment if 1) a handler for the endpoint or a thread exists and 2) if the request can be deserialized into the expected type for the handler or thread (e.g. a DIDComm plaintext message) -- Timeouts can occur in two ways and both are configured via `AgentBuilder::timeout`. - - A request sender can receive an `InboundFailure::Timeout` if the peer did not respond within the configured timeout. This happens on the event loop level and is handled by the `RequestResponse` protocol. - - `DidCommAgent::await_didcomm_request` can time out. This is the same timeout value as for the underlying `RequestResponse` protocol. In such a case, the event loop will receive a timeout error, but since no entry in the thread hash map is waiting for a response, it is silently dropped. Thus, `await_didcomm_request` implements its own timeout, and automatically uses the same duration as the underlying protocol to ensure consistent behaviour. For this reason, the `await_didcomm_request` timeout is a per-agent configuration value, and not a parameter on the function, although that would also be possible if desired. diff --git a/identity_agent/benches/agent.rs b/identity_agent/benches/agent.rs deleted file mode 100644 index f9162533f2..0000000000 --- a/identity_agent/benches/agent.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use criterion::criterion_group; -use criterion::criterion_main; -use criterion::Criterion; -use identity_agent::agent::Agent; -use identity_agent::agent::AgentBuilder; -use identity_agent::agent::AgentId; -use identity_agent::Multiaddr; - -use identity_iota_core::IotaDID; -use identity_iota_core::IotaDocument; -use identity_iota_core::NetworkName; -use remote_account::IdentityCreate; -use remote_account::RemoteAccount; - -async fn setup() -> (Agent, AgentId, Agent) { - let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); - let mut builder = AgentBuilder::new(); - - let remote_account = RemoteAccount::new(); - builder.attach::(remote_account); - - let mut receiver: Agent = builder.build().await.unwrap(); - - let addr = receiver.start_listening(addr).await.unwrap(); - let receiver_agent_id = receiver.agent_id(); - - let mut sender: Agent = AgentBuilder::new().build().await.unwrap(); - - sender.add_agent_address(receiver_agent_id, addr).await.unwrap(); - - (receiver, receiver_agent_id, sender) -} - -fn fake_document() -> IotaDocument { - let rand_bytes: [u8; 32] = rand::random(); - let network_name = NetworkName::try_from("iota").unwrap(); - let did = IotaDID::new(&rand_bytes, &network_name); - IotaDocument::new_with_id(did) -} - -fn bench_remote_account(c: &mut Criterion) { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - - let (receiver, receiver_agent_id, sender) = runtime.block_on(setup()); - - let mut group = c.benchmark_group("remote_account"); - - group.bench_function("IdentityCreate", |bencher| { - bencher.to_async(&runtime).iter(|| { - let mut sender_clone: Agent = sender.clone(); - - let doc = fake_document(); - - async move { - sender_clone - .send_request(receiver_agent_id, IdentityCreate(doc)) - .await - .unwrap() - .unwrap(); - } - }); - }); - - group.finish(); - - runtime.block_on(async move { - sender.shutdown().await.unwrap(); - receiver.shutdown().await.unwrap(); - }); -} - -criterion_group!(benches, bench_remote_account); - -criterion_main!(benches); - -mod remote_account { - use dashmap::DashMap; - use identity_agent::agent::Endpoint; - use identity_agent::agent::Handler; - use identity_agent::agent::HandlerRequest; - use identity_agent::agent::RequestContext; - use identity_iota_core::IotaDID; - use identity_iota_core::IotaDocument; - use serde::Deserialize; - use serde::Serialize; - use std::sync::Arc; - - /// A proof-of-concept implementation of a remote account - /// which holds and manages a collection of DID documents. - #[derive(Debug, Clone)] - pub(crate) struct RemoteAccount { - documents: Arc>, - } - - impl RemoteAccount { - pub(crate) fn new() -> Self { - Self { - documents: Arc::new(DashMap::new()), - } - } - } - - /// Can be sent to a `RemoteAccount` to instruct it to add a document. - #[derive(Debug, Clone, Serialize, Deserialize)] - pub(crate) struct IdentityCreate(pub(crate) IotaDocument); - - impl HandlerRequest for IdentityCreate { - type Response = Result<(), RemoteAccountError>; - - fn endpoint() -> Endpoint { - "remote_account/create".try_into().unwrap() - } - } - - #[async_trait::async_trait] - impl Handler for RemoteAccount { - async fn handle(&self, request: RequestContext) -> Result<(), RemoteAccountError> { - let document = request.input.0; - - if document.id().is_placeholder() { - return Err(RemoteAccountError::PlaceholderDID); - } - - self.documents.insert(document.id().to_owned(), document); - Ok(()) - } - } - - /// The error type for the [`RemoteAccount`]. - #[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] - #[non_exhaustive] - pub(crate) enum RemoteAccountError { - #[error("identity not found")] - IdentityNotFound, - #[error("placeholder DIDs cannot be managed")] - PlaceholderDID, - } -} diff --git a/identity_agent/benches/didcomm.rs b/identity_agent/benches/didcomm.rs deleted file mode 100644 index 7bfae1ed6f..0000000000 --- a/identity_agent/benches/didcomm.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use criterion::criterion_group; -use criterion::criterion_main; -use criterion::Criterion; - -use identity_agent::agent::AgentId; -use identity_agent::didcomm::DidCommAgent; -use identity_agent::didcomm::DidCommAgentBuilder; -use identity_agent::didcomm::DidCommAgentIdentity; -use identity_agent::didcomm::DidCommPlaintextMessage; -use identity_agent::didcomm::ThreadId; -use identity_agent::Multiaddr; - -use test_handler::PresentationOffer; -use test_handler::PresentationRequest; -use test_handler::TestHandler; - -async fn setup() -> (DidCommAgent, AgentId, DidCommAgent) { - let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); - let mut builder = DidCommAgentBuilder::new().identity(DidCommAgentIdentity::new()); - - builder.attach_didcomm(TestHandler); - - let mut receiver: DidCommAgent = builder.build().await.unwrap(); - - let addr = receiver.start_listening(addr).await.unwrap(); - let receiver_agent_id = receiver.agent_id(); - - let mut sender: DidCommAgent = DidCommAgentBuilder::new() - .identity(DidCommAgentIdentity::new()) - .build() - .await - .unwrap(); - - sender.add_agent_address(receiver_agent_id, addr).await.unwrap(); - - (receiver, receiver_agent_id, sender) -} - -fn bench_didcomm_requests(c: &mut Criterion) { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - - let (receiver, receiver_agent_id, sender) = runtime.block_on(setup()); - - let mut group = c.benchmark_group("didcomm_requests"); - - group.bench_function("send and await", |bencher| { - bencher.to_async(&runtime).iter(|| { - let mut sender_clone: DidCommAgent = sender.clone(); - - let thread_id: ThreadId = ThreadId::new(); - - async move { - sender_clone - .send_didcomm_request(receiver_agent_id, &thread_id, PresentationRequest::default()) - .await - .unwrap(); - - let _: DidCommPlaintextMessage = - sender_clone.await_didcomm_request(&thread_id).await.unwrap(); - } - }); - }); - - group.finish(); - - runtime.block_on(async move { - sender.shutdown().await.unwrap(); - receiver.shutdown().await.unwrap(); - }); -} - -criterion_group!(benches, bench_didcomm_requests); - -criterion_main!(benches); - -mod test_handler { - use identity_agent::agent::Endpoint; - use identity_agent::agent::RequestContext; - use identity_agent::didcomm::DidCommAgent; - use identity_agent::didcomm::DidCommHandler; - use identity_agent::didcomm::DidCommPlaintextMessage; - use identity_agent::didcomm::DidCommRequest; - use serde::Deserialize; - use serde::Serialize; - - #[derive(Debug, Clone)] - pub struct TestHandler; - - #[derive(Clone, Debug, Deserialize, Serialize, Default)] - pub(crate) struct PresentationRequest(u8); - - impl DidCommRequest for PresentationRequest { - fn endpoint() -> Endpoint { - "didcomm/presentation_request".try_into().unwrap() - } - } - - #[derive(Clone, Debug, Deserialize, Serialize, Default)] - pub(crate) struct PresentationOffer(u16); - - impl DidCommRequest for PresentationOffer { - fn endpoint() -> Endpoint { - "didcomm/presentation_offer".try_into().unwrap() - } - } - - #[async_trait::async_trait] - impl DidCommHandler> for TestHandler { - async fn handle( - &self, - mut agent: DidCommAgent, - request: RequestContext>, - ) { - agent - .send_didcomm_request( - request.agent_id, - request.input.thread_id(), - PresentationOffer(request.input.body().0 as u16), - ) - .await - .unwrap(); - } - } -} diff --git a/identity_agent/src/agent/agent.rs b/identity_agent/src/agent/agent.rs deleted file mode 100644 index 6587c884e1..0000000000 --- a/identity_agent/src/agent/agent.rs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use std::sync::Arc; - -use identity_core::common::OneOrMany; -use libp2p::request_response::InboundFailure; -use libp2p::request_response::RequestId; -use libp2p::request_response::ResponseChannel; -use libp2p::Multiaddr; - -use crate::agent::errors::ErrorLocation; -use crate::agent::AbstractHandler; -use crate::agent::AgentState; -use crate::agent::Endpoint; -use crate::agent::Error; -use crate::agent::HandlerRequest; -use crate::agent::RemoteSendError; -use crate::agent::RequestContext; -use crate::agent::RequestMode; -use crate::agent::Result as AgentResult; -use crate::p2p::InboundRequest; -use crate::p2p::NetCommander; -use crate::p2p::RequestMessage; -use crate::p2p::ResponseMessage; - -/// A map from an endpoint to the handler that handles its requests. -pub(crate) type HandlerMap = HashMap>; - -/// The cryptographic identifier of an agent on the network. -pub type AgentId = libp2p::PeerId; - -/// An agent can be used to send requests to other, remote agents, and fowards incoming requests -/// to attached handlers. -/// -/// An agent is a frontend for an event loop running in the background, which invokes -/// user-attached handlers. Agents can be cloned without cloning the event loop, and doing so -/// is a cheap operation. -/// Handlers are attached at agent build time, using the [`AgentBuilder`](crate::agent::AgentBuilder). -/// -/// After shutting down the event loop of an agent using [`Agent::shutdown`], other clones of the -/// agent will receive [`Error::Shutdown`] when attempting to interact with the event loop. -#[derive(Debug)] -pub struct Agent { - commander: NetCommander, - state: Arc, -} - -// Implement Clone for the sake of documenting that it is a cheap operation in this case. -impl Clone for Agent { - /// Produce a shallow copy of the agent, which uses the same event loop as the - /// agent that it was cloned from. - fn clone(&self) -> Self { - Self { - commander: self.commander.clone(), - state: self.state.clone(), - } - } -} - -impl Agent { - pub(crate) fn new(commander: NetCommander, state: Arc) -> Agent { - Self { commander, state } - } - - pub(crate) fn state(&self) -> &AgentState { - self.state.as_ref() - } - - /// Returns the [`AgentId`] that other peers can securely identify this agent with. - pub fn agent_id(&self) -> AgentId { - self.state().agent_id - } - - pub(crate) fn commander_mut(&mut self) -> &mut NetCommander { - &mut self.commander - } - - /// Start listening on the given `address`. Returns the first address that the agent started listening on, which may - /// be different from `address` itself, for example when passing addresses like `/ip4/0.0.0.0/tcp/0`. Even when - /// passing a single address, multiple addresses may end up being listened on. To obtain all those addresses, use - /// [`Agent::addresses`]. Note that even when the same address is passed, the returned address is not deterministic, - /// and should thus not be relied upon. - pub async fn start_listening(&mut self, address: Multiaddr) -> AgentResult { - self.commander_mut().start_listening(address).await - } - - /// Return all addresses that are currently being listened on. - pub async fn addresses(&mut self) -> AgentResult> { - self.commander_mut().get_addresses().await - } - - /// Shut this agent down. This will break the event loop in the background immediately, - /// returning an error for all current handlers that interact with their copy of the - /// agent or those waiting on messages. The agent will thus stop listening on all addresses. - /// - /// Calling this and other methods, which interact with the event loop, on an agent that was shutdown - /// will return [`Error::Shutdown`]. - pub async fn shutdown(mut self) -> AgentResult<()> { - // Consuming self drops the internal commander. If this is the last copy of the commander, - // the event loop will break as a result. However, if copies exist, such as in running handlers, - // this function will return while the event loop keeps running. Ideally we could then join on the background task - // to wait for all handlers to finish gracefully. This was not implemented that way, because of a previous - // dependency on wasm_bindgen_futures::spawn_local which does not return a JoinHandle. It would be an option to - // change it, now that we're using tokio exclusively. - // The current implementation uses a non-graceful exit, which breaks the event loop immediately - // and returns an error through all open channels that require a result. - self.commander_mut().shutdown().await - } - - /// Associate the given `agent_id` with an `address`. This `address`, or another one that was added, - /// will be use to send requests to `agent_id`. - pub async fn add_agent_address(&mut self, agent_id: AgentId, address: Multiaddr) -> AgentResult<()> { - self - .commander_mut() - .add_addresses(agent_id, OneOrMany::One(address)) - .await - } - - /// Associate the given `agent_id` with multiple `addresses`. One of the `addresses`, or another one that was added, - /// will be use to send requests to `agent_id`. - pub async fn add_agent_addresses(&mut self, agent_id: AgentId, addresses: Vec) -> AgentResult<()> { - self - .commander_mut() - .add_addresses(agent_id, OneOrMany::Many(addresses)) - .await - } - - /// Sends a synchronous request to an agent, identified through `agent_id`, and returns its response. - /// - /// An address needs to be available for the given `agent_id`, which can be added - /// with [`Agent::add_agent_address`] or [`Agent::add_agent_addresses`]. - pub async fn send_request( - &mut self, - agent_id: AgentId, - request: REQ, - ) -> AgentResult { - let endpoint: Endpoint = REQ::endpoint(); - let request_mode: RequestMode = REQ::request_mode(); - - let request_vec = serde_json::to_vec(&request).map_err(|err| Error::SerializationFailure { - location: ErrorLocation::Local, - context: "send request".to_owned(), - error_message: err.to_string(), - })?; - - log::debug!("sending request on endpoint `{endpoint}`"); - - let request: RequestMessage = RequestMessage::new(endpoint, request_mode, request_vec); - - let response: ResponseMessage = self.commander_mut().send_request(agent_id, request).await?; - - let response: Vec = - serde_json::from_slice::, RemoteSendError>>(&response.0).map_err(|err| { - Error::DeserializationFailure { - location: ErrorLocation::Local, - context: "send request (result)".to_owned(), - error_message: err.to_string(), - } - })??; - - serde_json::from_slice::(&response).map_err(|err| Error::DeserializationFailure { - location: ErrorLocation::Local, - context: "send request".to_owned(), - error_message: err.to_string(), - }) - } - - /// Let this agent handle the given `request`, by invoking the appropriate handler, if attached. - /// This consumes the agent because it passes itself to the handler. - /// The agent will thus typically be cloned before calling this method. - pub(crate) fn handle_request(mut self, request: InboundRequest) { - if request.request_mode == RequestMode::Synchronous { - self.handle_sync_request(request) - } else { - tokio::spawn(async move { - if let Err(error) = send_response( - self.commander_mut(), - Result::<(), RemoteSendError>::Err(RemoteSendError::UnexpectedRequest( - "asynchronous requests are not supported".to_owned(), - )), - request.response_channel, - request.request_id, - ) - .await - { - log::error!( - "unable to respond to synchronous request on endpoint `{}` due to: {error}", - request.endpoint - ); - } - }); - } - } - - #[inline(always)] - pub(crate) fn handle_sync_request(mut self, request: InboundRequest) { - tokio::spawn(async move { - match self.state.handlers.get(&request.endpoint) { - Some(handler) => { - let context: RequestContext> = - RequestContext::new(request.input, request.peer_id, request.endpoint.clone()); - let result: Result, RemoteSendError> = handler.handle(context).await; - - if let Err(error) = send_response( - self.commander_mut(), - result, - request.response_channel, - request.request_id, - ) - .await - { - log::error!( - "unable to respond to synchronous request on endpoint `{}` due to: {error}", - request.endpoint - ); - } - } - None => { - endpoint_not_found(&mut self, request).await; - } - } - }); - } -} - -pub(crate) async fn send_response( - commander: &mut NetCommander, - response: Result, - channel: ResponseChannel, - request_id: RequestId, -) -> AgentResult> { - let response: Vec = serde_json::to_vec(&response).map_err(|err| crate::agent::Error::SerializationFailure { - location: ErrorLocation::Local, - context: "send response".to_owned(), - error_message: err.to_string(), - })?; - commander.send_response(response, channel, request_id).await -} - -#[inline(always)] -async fn endpoint_not_found(handler: &mut Agent, request: InboundRequest) { - let response: Result, RemoteSendError> = - Err(RemoteSendError::UnexpectedRequest(request.endpoint.to_string())); - - let send_result = send_response( - handler.commander_mut(), - response, - request.response_channel, - request.request_id, - ) - .await; - - if let Err(err) = send_result { - log::error!( - "could not return error for request on endpoint `{}` due to: {err:?}", - request.endpoint - ); - } -} diff --git a/identity_agent/src/agent/agent_builder.rs b/identity_agent/src/agent/agent_builder.rs deleted file mode 100644 index fa45a0f658..0000000000 --- a/identity_agent/src/agent/agent_builder.rs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use std::iter; -use std::sync::Arc; -use std::time::Duration; - -use futures::channel::mpsc; -use futures::AsyncRead; -use futures::AsyncWrite; -use futures::FutureExt; -use libp2p::core::transport::upgrade; -use libp2p::core::Executor; -use libp2p::core::Transport; -use libp2p::dns::TokioDnsConfig; -use libp2p::identity::Keypair; -use libp2p::noise::AuthenticKeypair; -use libp2p::noise::Keypair as NoiseKeypair; -use libp2p::noise::NoiseConfig; -use libp2p::noise::X25519Spec; -use libp2p::request_response::ProtocolSupport; -use libp2p::request_response::RequestResponse; -use libp2p::request_response::RequestResponseConfig; -use libp2p::swarm::SwarmBuilder; -use libp2p::tcp::TokioTcpConfig; -use libp2p::websocket::WsConfig; -use libp2p::yamux::YamuxConfig; -use libp2p::Swarm; - -use crate::agent::AbstractHandler; -use crate::agent::Agent; -use crate::agent::AgentConfig; -use crate::agent::AgentId; -use crate::agent::AgentState; -use crate::agent::Error; -use crate::agent::Handler; -use crate::agent::HandlerMap; -use crate::agent::HandlerRequest; -use crate::agent::HandlerWrapper; -use crate::agent::Result as AgentResult; -use crate::p2p::AgentProtocol; -use crate::p2p::AgentRequestResponseCodec; -use crate::p2p::EventLoop; -use crate::p2p::InboundRequest; -use crate::p2p::NetCommander; - -/// A builder for [`Agent`]s to customize its configuration and attach handlers. -pub struct AgentBuilder { - pub(crate) keypair: Option, - pub(crate) config: AgentConfig, - pub(crate) handlers: HandlerMap, -} - -impl AgentBuilder { - /// Create a new builder with the default configuration. - pub fn new() -> AgentBuilder { - Self { - keypair: None, - config: AgentConfig::default(), - handlers: HashMap::new(), - } - } - - /// Set the keypair from which the `AgentId` of the agent is derived. - /// - /// If unset, a new keypair is generated. - #[must_use] - pub fn keypair(mut self, keypair: Keypair) -> Self { - self.keypair = Some(keypair); - self - } - - /// Sets the timeout for the underlying libp2p [`RequestResponse`] protocol. - #[must_use] - pub fn timeout(mut self, timeout: Duration) -> Self { - self.config.timeout = timeout; - self - } - - /// Attaches a [`Handler`] to this agent. - /// - /// This means that when the agent receives a request of type `REQ`, it will invoke this handler. - /// - /// Calling this method with a `REQ` type whose endpoint is already attached to a handler - /// will overwrite the previous attachment. - pub fn attach(&mut self, handler: HND) - where - HND: Handler + Send + Sync, - REQ: HandlerRequest + Send + Sync, - REQ::Response: Send, - { - self.handlers.insert( - REQ::endpoint(), - Box::new(HandlerWrapper::new(handler)) as Box, - ); - } - - /// Build the handler with a default transport which supports DNS, TCP and WebSocket capabilities. - pub async fn build(self) -> AgentResult { - let transport: _ = { - let dns_tcp_transport: TokioDnsConfig<_> = TokioDnsConfig::system(TokioTcpConfig::new().nodelay(true)) - .map_err(|err| Error::TransportError("building transport", libp2p::TransportError::Other(err)))?; - let ws_transport: WsConfig<_> = WsConfig::new( - TokioDnsConfig::system(TokioTcpConfig::new().nodelay(true)) - .map_err(|err| Error::TransportError("building transport", libp2p::TransportError::Other(err)))?, - ); - dns_tcp_transport.or_transport(ws_transport) - }; - - self.build_with_transport(transport).await - } - - /// Build the agent with a custom transport. - pub async fn build_with_transport(self, transport: TRA) -> AgentResult - where - TRA: Transport + Sized + Send + Sync + 'static, - TRA::Output: AsyncRead + AsyncWrite + Unpin + Send + 'static, - TRA::Dial: Send + 'static, - TRA::Listener: Send + 'static, - TRA::ListenerUpgrade: Send + 'static, - TRA::Error: Send + Sync, - { - let executor = Box::new(|fut| { - tokio::spawn(fut); - }); - - let (event_loop, handler_state, net_commander): (EventLoop, AgentState, NetCommander) = - self.build_constituents(transport, executor.clone()).await?; - - let agent: Agent = Agent::new(net_commander, Arc::new(handler_state)); - let agent_clone: Agent = agent.clone(); - - let event_handler = move |event: InboundRequest| { - agent_clone.clone().handle_request(event); - }; - - executor.exec(event_loop.run(event_handler).boxed()); - - Ok(agent) - } - - /// Build the agent constituents with a custom transport and custom executor. - pub(crate) async fn build_constituents( - self, - transport: TRA, - executor: Box, - ) -> AgentResult<(EventLoop, AgentState, NetCommander)> - where - TRA: Transport + Sized + Send + Sync + 'static, - TRA::Output: AsyncRead + AsyncWrite + Unpin + Send + 'static, - TRA::Dial: Send + 'static, - TRA::Listener: Send + 'static, - TRA::ListenerUpgrade: Send + 'static, - TRA::Error: Send + Sync, - { - let (noise_keypair, agent_id): (AuthenticKeypair<_>, AgentId) = { - let keypair: Keypair = self.keypair.unwrap_or_else(Keypair::generate_ed25519); - let noise_keypair = NoiseKeypair::::new() - .into_authentic(&keypair) - .expect("ed25519 keypair should be convertible into x25519"); - let agent_id = keypair.public().to_peer_id(); - (noise_keypair, agent_id) - }; - - let swarm: Swarm> = { - let mut config: RequestResponseConfig = RequestResponseConfig::default(); - config.set_request_timeout(self.config.timeout); - - let behaviour = RequestResponse::new( - AgentRequestResponseCodec(), - iter::once((AgentProtocol(), ProtocolSupport::Full)), - config, - ); - - let transport: _ = transport - .upgrade(upgrade::Version::V1) - .authenticate(NoiseConfig::xx(noise_keypair).into_authenticated()) - .multiplex(YamuxConfig::default()) - .boxed(); - - SwarmBuilder::new(transport, behaviour, agent_id) - .executor(executor) - .build() - }; - - let (cmd_sender, cmd_receiver): _ = mpsc::channel(10); - - let event_loop: EventLoop = EventLoop::new(swarm, cmd_receiver); - let net_commander: NetCommander = NetCommander::new(cmd_sender); - - let agent_state: AgentState = AgentState { - agent_id, - config: self.config, - handlers: self.handlers, - }; - - Ok((event_loop, agent_state, net_commander)) - } -} - -impl Default for AgentBuilder { - fn default() -> Self { - Self::new() - } -} diff --git a/identity_agent/src/agent/agent_state.rs b/identity_agent/src/agent/agent_state.rs deleted file mode 100644 index c88927f973..0000000000 --- a/identity_agent/src/agent/agent_state.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::agent::AgentConfig; -use crate::agent::AgentId; -use crate::agent::HandlerMap; - -/// The internal state of an `Agent`. -#[derive(Debug)] -pub(crate) struct AgentState { - pub(crate) agent_id: AgentId, - pub(crate) config: AgentConfig, - pub(crate) handlers: HandlerMap, -} diff --git a/identity_agent/src/agent/config.rs b/identity_agent/src/agent/config.rs deleted file mode 100644 index a5d9db04fd..0000000000 --- a/identity_agent/src/agent/config.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::time::Duration; - -/// Configuration options for a [`Agent`](crate::handler::Agent). -#[derive(Debug, Clone)] -pub(crate) struct AgentConfig { - pub(crate) timeout: Duration, -} - -impl Default for AgentConfig { - fn default() -> Self { - Self { - timeout: Duration::from_secs(30), - } - } -} diff --git a/identity_agent/src/agent/endpoint.rs b/identity_agent/src/agent/endpoint.rs deleted file mode 100644 index c5f40bffc6..0000000000 --- a/identity_agent/src/agent/endpoint.rs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::borrow::Cow; -use std::fmt::Display; -use std::str::Split; - -use serde::Deserialize; -use serde::Serialize; - -use crate::agent::Error; -use crate::agent::Result as AgentResult; - -/// A path-like identifier for a handler request. As an example, `identity/resolve` -/// could be the endpoint of the "resolve" request within the "identity" namespace. -/// -/// The namespace and request are separated by a slash and only allow alphabetic ascii characters and `_`. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(try_from = "String", into = "String")] -pub struct Endpoint { - name: Cow<'static, str>, -} - -impl Endpoint { - /// Checks whether the given `string` is a valid [`Endpoint`]. - fn validate(string: &str) -> AgentResult<()> { - let mut split: Split<'_, char> = string.split('/'); - - // Once the Never (`!`) type lands in stable Rust, we can try to map the `None` variant to ! instead. - let namespace: &str = split.next().expect("split always returns at least one element"); - let request: &str = split.next().ok_or(Error::InvalidEndpoint)?; - - let is_valid_segment = - |segment: &str| !segment.is_empty() && segment.chars().all(|c| c.is_ascii_alphabetic() || c == '_'); - - if !is_valid_segment(namespace) || !is_valid_segment(request) { - return Err(Error::InvalidEndpoint); - } - - if split.next().is_some() { - return Err(Error::InvalidEndpoint); - } - - Ok(()) - } -} - -impl TryFrom<&'static str> for Endpoint { - type Error = Error; - - /// Creates a new endpoint from a string. Returns an [`Error::InvalidEndpoint`] - /// if disallowed characters are encountered. - fn try_from(endpoint: &'static str) -> Result { - Self::validate(endpoint)?; - - Ok(Self { - name: Cow::Borrowed(endpoint), - }) - } -} - -impl TryFrom for Endpoint { - type Error = Error; - - /// Creates a new endpoint from a string. Returns an [`Error::InvalidEndpoint`] - /// if disallowed characters are encountered. - fn try_from(endpoint: String) -> Result { - Self::validate(&endpoint)?; - - Ok(Self { - name: Cow::Owned(endpoint), - }) - } -} - -impl Display for Endpoint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.name.as_ref()) - } -} - -impl AsRef for Endpoint { - fn as_ref(&self) -> &str { - self.name.as_ref() - } -} - -impl From for String { - fn from(endpoint: Endpoint) -> Self { - endpoint.name.into_owned() - } -} - -#[cfg(test)] -mod tests { - use identity_core::convert::FromJson; - - use crate::agent::Endpoint; - use crate::agent::Error; - - #[test] - fn test_endpoint_invalid() { - for invalid_endpoint in [ - "", - "/", - "//", - "a/", - "/b", - "a/b/", - "a/b/c", - "a/b/c/d", - "1a/b", - "a/b2", - "námespace/endpoint", - "namespace/hyphenated-word", - ] { - assert!(matches!( - Endpoint::try_from(invalid_endpoint).unwrap_err(), - Error::InvalidEndpoint - ),); - } - } - - #[test] - fn test_endpoint_valid() { - for valid_endpoint in ["a/b", "longer/word", "longer_endpoint/underscored_word"] { - assert!( - Endpoint::try_from(valid_endpoint).is_ok(), - "expected `{valid_endpoint}` to be a valid endpoint" - ); - } - } - - #[test] - fn test_endpoint_deserialization_validates() { - let err = Endpoint::from_json(r#"{ "name": "a/b/invalid" }"#).unwrap_err(); - assert!(matches!( - err, - identity_core::Error::DecodeJSON(serde_json::Error { .. }) - )); - } -} diff --git a/identity_agent/src/agent/errors.rs b/identity_agent/src/agent/errors.rs deleted file mode 100644 index f99b0ce353..0000000000 --- a/identity_agent/src/agent/errors.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use libp2p::request_response::OutboundFailure; - -use crate::didcomm::ThreadId; - -/// The `Result` type for the agent. -pub type Result = std::result::Result; - -/// Errors that can occur during agent execution. -#[non_exhaustive] -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[non_exhaustive] - #[error("transport error during {0}")] - TransportError(&'static str, #[source] libp2p::TransportError), - #[error("invalid endpoint")] - InvalidEndpoint, - #[non_exhaustive] - #[error("failure during sending an outbound request and receiving the response")] - OutboundFailure(#[source] OutboundFailure), - #[error("unexpected request `{0}`")] - UnexpectedRequest(String), - #[error("handler invocation error: {0}")] - HandlerInvocationError(String), - #[non_exhaustive] - #[error("{location} serialization failed during {context} due to: {error_message}")] - SerializationFailure { - location: ErrorLocation, - context: String, - error_message: String, - }, - #[error("{location} deserialization failed during {context} due to: {error_message}")] - DeserializationFailure { - location: ErrorLocation, - context: String, - error_message: String, - }, - #[error("thread with id `{0}` not found")] - ThreadNotFound(ThreadId), - #[error("awaiting message timed out on thread `{0}`")] - AwaitTimeout(ThreadId), - #[error("handler was shutdown")] - Shutdown, - #[error("handler identity missing")] - IdentityMissing, -} - -/// Errors that can occur on the remote agent. -#[non_exhaustive] -#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] -pub enum RemoteSendError { - #[error("unexpected request: {0}")] - UnexpectedRequest(String), - #[error("handler invocation error: {0}")] - HandlerInvocationError(String), - #[error("{location} serialization failed during {context} due to: {error_message}")] - SerializationFailure { - location: ErrorLocation, - context: String, - error_message: String, - }, - #[error("{location} deserialization failed during {context} due to: {error_message}")] - DeserializationFailure { - location: ErrorLocation, - context: String, - error_message: String, - }, -} - -impl From for Error { - fn from(err: RemoteSendError) -> Self { - match err { - RemoteSendError::UnexpectedRequest(req) => Error::UnexpectedRequest(req), - RemoteSendError::HandlerInvocationError(err) => Error::HandlerInvocationError(err), - RemoteSendError::DeserializationFailure { - location, - context, - error_message, - } => Error::DeserializationFailure { - location, - context, - error_message, - }, - RemoteSendError::SerializationFailure { - location, - context, - error_message, - } => Error::SerializationFailure { - location, - context, - error_message, - }, - } - } -} - -/// The location of an error, either locally or remotely. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub enum ErrorLocation { - /// The error occured locally. - Local, - /// The error occured remotely. - Remote, -} - -impl std::fmt::Display for ErrorLocation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let display = match self { - ErrorLocation::Local => "local", - ErrorLocation::Remote => "remote", - }; - - f.write_str(display) - } -} diff --git a/identity_agent/src/agent/handler.rs b/identity_agent/src/agent/handler.rs deleted file mode 100644 index bdcc5cddfd..0000000000 --- a/identity_agent/src/agent/handler.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Debug; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; - -use crate::agent::ErrorLocation; -use crate::agent::HandlerRequest; -use crate::agent::RemoteSendError; -use crate::agent::RequestContext; - -/// A boxed future that is `Send`. -pub(crate) type BoxFuture<'me, T> = Pin + Send + 'me>>; - -/// Handlers are objects that encapsulate state and behavior. -/// -/// Handlers handle one or more requests by implementing this trait one or more times -/// for different `HandlerRequest` types. -/// -/// The requests for a handler are handled synchronously, meaning that the calling agent waits for -/// the handler to return its result before continuing. -#[async_trait::async_trait] -pub trait Handler: Debug + 'static { - /// Called when the agent receives a request of type `REQ`. - /// The result will be returned to the calling agent. - async fn handle(&self, request: RequestContext) -> REQ::Response; -} - -/// A trait that wraps a synchronous handler implementation and erases its type. -/// This allows holding handlers with different concrete types in the same collection. -pub(crate) trait AbstractHandler: Debug + Send + Sync + 'static { - fn handle(&self, request: RequestContext>) -> BoxFuture<'_, Result, RemoteSendError>>; -} - -/// A wrapper around synchronous handler implementations that is used for -/// type erasure together with [`AbstractHandler`]. -#[derive(Debug)] -pub(crate) struct HandlerWrapper -where - REQ: HandlerRequest + Send + Sync, - HND: Handler + Send + Sync, -{ - handler: HND, - _phantom_req: PhantomData, -} - -impl HandlerWrapper -where - REQ: HandlerRequest + Send + Sync, - HND: Handler + Send + Sync, -{ - pub(crate) fn new(handler: HND) -> Self { - Self { - handler, - _phantom_req: PhantomData, - } - } -} - -impl AbstractHandler for HandlerWrapper -where - REQ: HandlerRequest + Send + Sync, - REQ::Response: Send, - HND: Handler + Send + Sync, -{ - fn handle(&self, request: RequestContext>) -> BoxFuture<'_, Result, RemoteSendError>> { - let future = async move { - let req: REQ = - serde_json::from_slice(&request.input).map_err(|error| RemoteSendError::DeserializationFailure { - location: ErrorLocation::Remote, - context: format!( - "deserializing the received bytes into the handler's expected type `{}`", - std::any::type_name::() - ), - error_message: error.to_string(), - })?; - - let req: RequestContext = request.convert(req); - let result: REQ::Response = self.handler.handle(req).await; - serialize_response::(&result) - }; - - Box::pin(future) - } -} - -#[inline(always)] -fn serialize_response(input: &REQ::Response) -> Result, RemoteSendError> { - log::debug!( - "attempt response serialization into {:?}", - std::any::type_name::() - ); - - let response: Vec = serde_json::to_vec(&input).map_err(|error| RemoteSendError::SerializationFailure { - location: ErrorLocation::Remote, - context: format!( - "serializing the handler's response into `{}`", - std::any::type_name::() - ), - error_message: error.to_string(), - })?; - - Ok(response) -} diff --git a/identity_agent/src/agent/mod.rs b/identity_agent/src/agent/mod.rs deleted file mode 100644 index 45e84559da..0000000000 --- a/identity_agent/src/agent/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[allow(clippy::module_inception)] -mod agent; -mod agent_builder; -mod agent_state; -mod config; -mod endpoint; -mod errors; -mod handler; -mod request; -mod request_context; - -pub use agent::*; -pub use agent_builder::*; -pub(crate) use agent_state::*; -pub(crate) use config::*; -pub use endpoint::*; -pub use errors::*; -pub use handler::*; -pub use request::*; -pub use request_context::*; diff --git a/identity_agent/src/agent/request.rs b/identity_agent/src/agent/request.rs deleted file mode 100644 index 034d846bb6..0000000000 --- a/identity_agent/src/agent/request.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Debug; - -use serde::de::DeserializeOwned; -use serde::Deserialize; -use serde::Serialize; - -use crate::agent::Endpoint; - -/// Expresses the synchronicity of a request at runtime, i.e. whether a request -/// is handled synchronously or asynchronously. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum RequestMode { - Synchronous, - Asynchronous, -} - -/// A request sent to a handler with a response of type `Response`. -/// -/// This request is sent synchronously, which means waiting for -/// the result of that invocation on the remote agent. -pub trait HandlerRequest: Debug + Serialize + DeserializeOwned + Send + 'static { - /// The response type for this request. - type Response: Debug + Serialize + DeserializeOwned + 'static; - - /// The unique identifier for this request. See [`Endpoint`] for more details. - fn endpoint() -> Endpoint; - - /// Whether this request is synchronous or asynchronous. - fn request_mode() -> RequestMode { - RequestMode::Synchronous - } -} diff --git a/identity_agent/src/agent/request_context.rs b/identity_agent/src/agent/request_context.rs deleted file mode 100644 index 62c6148dd4..0000000000 --- a/identity_agent/src/agent/request_context.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::agent::AgentId; -use crate::agent::Endpoint; - -/// A request paired with some context such as the sender's peer id. -#[non_exhaustive] -#[derive(Debug, Clone)] -pub struct RequestContext { - /// The request type. - pub input: T, - /// The [`AgentId`] of the sender. - pub agent_id: AgentId, - /// The [`Endpoint`] of this request. - pub endpoint: Endpoint, -} - -impl RequestContext { - pub(crate) fn new(input: T, agent_id: AgentId, endpoint: Endpoint) -> Self { - Self { - input, - agent_id, - endpoint, - } - } - - /// Convert this context's inner type to another one. - pub(crate) fn convert(self, input: I) -> RequestContext { - RequestContext::new(input, self.agent_id, self.endpoint) - } -} diff --git a/identity_agent/src/didcomm/agent.rs b/identity_agent/src/didcomm/agent.rs deleted file mode 100644 index a0a85c2a21..0000000000 --- a/identity_agent/src/didcomm/agent.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use std::sync::Arc; - -use dashmap::DashMap; -use futures::channel::oneshot; -use libp2p::Multiaddr; -use serde::de::DeserializeOwned; - -use crate::agent::Agent; -use crate::agent::AgentId; -use crate::agent::Endpoint; -use crate::agent::Error; -use crate::agent::ErrorLocation; -use crate::agent::HandlerRequest; -use crate::agent::RemoteSendError; -use crate::agent::RequestMode; -use crate::agent::Result as AgentResult; -use crate::didcomm::dcpm::DidCommPlaintextMessage; -use crate::didcomm::AbstractDidCommHandler; -use crate::didcomm::DidCommRequest; -use crate::didcomm::ThreadId; -use crate::p2p::InboundRequest; -use crate::p2p::NetCommander; -use crate::p2p::RequestMessage; -use crate::p2p::ThreadRequest; - -/// The identity of a [`DidCommAgent`]. -/// -/// Note: Currently an incomplete implementation. -#[derive(Debug, Clone, Default)] -pub struct DidCommAgentIdentity { - // TODO: This type is meant to be used in a future update. -} - -impl DidCommAgentIdentity { - pub fn new() -> Self { - Self {} - } -} - -/// The internal state of a [`DidCommAgent`]. -#[derive(Debug)] -pub struct DidCommAgentState { - pub(crate) handlers: DidCommHandlerMap, - pub(crate) threads_receiver: DashMap>, - pub(crate) threads_sender: DashMap>, - // TODO: See above. - #[allow(dead_code)] - pub(crate) identity: DidCommAgentIdentity, -} - -impl DidCommAgentState { - pub(crate) fn new(handlers: DidCommHandlerMap, identity: DidCommAgentIdentity) -> Self { - Self { - handlers, - threads_receiver: DashMap::new(), - threads_sender: DashMap::new(), - identity, - } - } -} - -/// A [`DidCommAgent`] is an extension of an [`Agent`] with support for sending and awaiting [`DidCommRequest`]s. -/// -/// An agent can be used to send requests to other, remote agents, and fowards incoming requests -/// to attached handlers. It is a frontend for an event loop running in the background, which invokes -/// user-attached handlers. Agents can be cloned without cloning the event loop, and doing so -/// is a cheap operation. -/// -/// Handlers are attached at agent build time, using the [`DidCommAgentBuilder`](crate::didcomm::DidCommAgentBuilder). -/// -/// While an [`Agent`] only supports attachements of synchronous [`Handler`](crate::agent::Handler)s, -/// a [`DidCommAgent`] additionally supports asynchronous [`DidCommHandler`](crate::didcomm::DidCommHandler)s. -/// -/// After shutting down the event loop of an agent using [`DidCommAgent::shutdown`], other clones of the -/// agent will receive [`Error::Shutdown`] when attempting to interact with the event loop. -#[derive(Debug, Clone)] -pub struct DidCommAgent { - pub(crate) agent: Agent, - pub(crate) state: Arc, -} - -impl DidCommAgent { - pub(crate) fn commander_mut(&mut self) -> &mut NetCommander { - self.agent.commander_mut() - } - - /// Let this agent handle the given `request`, by invoking the appropriate handler, if attached. - /// This consumes the agent because it passes itself to the handler. - /// The agent will thus typically be cloned before calling this method. - pub(crate) fn handle_request(self, request: InboundRequest) { - match request.request_mode { - RequestMode::Asynchronous => self.handle_async_request(request), - RequestMode::Synchronous => self.agent.handle_sync_request(request), - } - } - - /// See [`Agent::start_listening`]. - pub async fn start_listening(&mut self, address: Multiaddr) -> AgentResult { - self.agent.start_listening(address).await - } - - /// See [`Agent::agent_id`]. - pub fn agent_id(&self) -> AgentId { - self.agent.agent_id() - } - - /// See [`Agent::addresses`]. - pub async fn addresses(&mut self) -> AgentResult> { - self.agent.addresses().await - } - - /// See [`Agent::add_agent_address`]. - pub async fn add_agent_address(&mut self, agent_id: AgentId, address: Multiaddr) -> AgentResult<()> { - self.agent.add_agent_address(agent_id, address).await - } - - /// See [`Agent::add_agent_addresses`]. - pub async fn add_agent_addresses(&mut self, agent_id: AgentId, addresses: Vec) -> AgentResult<()> { - self.agent.add_agent_addresses(agent_id, addresses).await - } - - /// See [`Agent::shutdown`]. - pub async fn shutdown(self) -> AgentResult<()> { - self.agent.shutdown().await - } - - /// See [`Agent::send_request`]. - pub async fn send_request( - &mut self, - agent_id: AgentId, - request: REQ, - ) -> AgentResult { - self.agent.send_request(agent_id, request).await - } - - /// Sends an asynchronous DIDComm request to an agent. - /// - /// To receive a possible response, call [`DidCommAgent::await_didcomm_request`] with the same `thread_id`. - pub async fn send_didcomm_request( - &mut self, - agent_id: AgentId, - thread_id: &ThreadId, - message: REQ, - ) -> AgentResult<()> { - let endpoint: Endpoint = REQ::endpoint(); - let request_mode: RequestMode = REQ::request_mode(); - - let dcpm = DidCommPlaintextMessage::new(thread_id.to_owned(), endpoint.to_string(), message); - - self.create_thread_channels(thread_id); - - let dcpm_vec = serde_json::to_vec(&dcpm).map_err(|err| Error::SerializationFailure { - location: ErrorLocation::Local, - context: "send message".to_owned(), - error_message: err.to_string(), - })?; - - log::debug!("sending DIDComm request on endpoint `{endpoint}`"); - - let message: RequestMessage = RequestMessage::new(endpoint, request_mode, dcpm_vec); - - let response = self.commander_mut().send_request(agent_id, message).await?; - - serde_json::from_slice::>(&response.0).map_err(|err| { - Error::DeserializationFailure { - location: ErrorLocation::Local, - context: "send message".to_owned(), - error_message: err.to_string(), - } - })??; - - Ok(()) - } - - /// Wait for a message on a given `thread_id`. This can only be called successfully if - /// [`DidCommAgent::send_didcomm_request`] was called on the same `thread_id` previously. - /// Calling `send_didcomm_request` multiple times still only allows to await one message on the thread. - /// - /// This will return a timeout error if no message is received within the duration passed - /// to [`DidCommAgentBuilder::timeout`](crate::didcomm::DidCommAgentBuilder::timeout). - pub async fn await_didcomm_request( - &mut self, - thread_id: &ThreadId, - ) -> AgentResult> { - if let Some(receiver) = self.state.threads_receiver.remove(thread_id) { - // Receiving + Deserialization - let inbound_request = tokio::time::timeout(self.agent.state().config.timeout, receiver.1) - .await - .map_err(|_| Error::AwaitTimeout(receiver.0.clone()))? - .map_err(|_| Error::ThreadNotFound(receiver.0))?; - - let message: DidCommPlaintextMessage = - serde_json::from_slice(inbound_request.input.as_ref()).map_err(|err| Error::DeserializationFailure { - location: ErrorLocation::Local, - context: "await message".to_owned(), - error_message: err.to_string(), - })?; - - log::debug!("awaited message {}", inbound_request.endpoint); - - Ok(message) - } else { - log::warn!("attempted to wait for a message on thread {thread_id:?}, which does not exist"); - Err(Error::ThreadNotFound(thread_id.to_owned())) - } - } - - /// Creates the channels used to await a message on a thread. - fn create_thread_channels(&mut self, thread_id: &ThreadId) { - let (sender, receiver) = oneshot::channel(); - - // The logic is that for every received message on a thread, - // there must be a preceding `send_didcomm_request` on that same thread. - // Note that on the receiving handler, the very first message of a protocol - // is not awaited through `await_didcomm_request`, so it does not need to follow these rules. - self.state.threads_sender.insert(thread_id.to_owned(), sender); - self.state.threads_receiver.insert(thread_id.to_owned(), receiver); - } - - #[inline(always)] - pub(crate) fn handle_async_request(mut self, request: InboundRequest) { - tokio::spawn(async move { - match self.state.handlers.get(&request.endpoint) { - Some(handler) => { - let handler: &dyn AbstractDidCommHandler = handler.as_ref(); - - handler.handle(self.clone(), request).await; - } - None => { - handler_not_found(&mut self, request).await; - } - } - }); - } -} - -/// Invoked when no handler was found that can handle the received request. -/// Attempts to find a thread waiting for the received message, -/// otherwise returns an error to the calling agent. -async fn handler_not_found(handler: &mut DidCommAgent, request: InboundRequest) { - let result: Result<(), RemoteSendError> = - match serde_json::from_slice::>(&request.input) { - Err(error) => Err(RemoteSendError::DeserializationFailure { - location: ErrorLocation::Remote, - context: "DIDComm plaintext message deserialization".to_owned(), - error_message: error.to_string(), - }), - Ok(plaintext_msg) => { - let thread_id = plaintext_msg.thread_id(); - - match handler.state.threads_sender.remove(thread_id) { - Some(sender) => { - let thread_request = ThreadRequest { - endpoint: request.endpoint, - input: request.input, - }; - - if sender.1.send(thread_request).is_err() { - log::warn!("unable to send request with thread id `{thread_id}`"); - } - - Ok(()) - } - None => { - log::info!( - "no handler or thread found for the received message `{}`", - request.endpoint - ); - // The assumption is that DID authentication is done before this point, so this is not - // considered an information leak, e.g. to enumerate thread ids. - Err(RemoteSendError::UnexpectedRequest(format!( - "thread id `{thread_id}` not found" - ))) - } - } - } - }; - - let send_result = crate::agent::send_response( - handler.commander_mut(), - result, - request.response_channel, - request.request_id, - ) - .await; - - if let Err(err) = send_result { - log::error!("could not acknowledge request due to: {err:?}"); - } -} - -/// A map from an endpoint to the handler that handles its requests. -pub(crate) type DidCommHandlerMap = HashMap>; diff --git a/identity_agent/src/didcomm/agent_builder.rs b/identity_agent/src/didcomm/agent_builder.rs deleted file mode 100644 index 510fd6db71..0000000000 --- a/identity_agent/src/didcomm/agent_builder.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; - -use futures::AsyncRead; -use futures::AsyncWrite; -use futures::FutureExt; -use libp2p::core::Executor; -use libp2p::dns::TokioDnsConfig; -use libp2p::identity::Keypair; -use libp2p::tcp::TokioTcpConfig; -use libp2p::websocket::WsConfig; -use libp2p::Transport; - -use crate::agent::Agent; -use crate::agent::AgentBuilder; -use crate::agent::AgentState; -use crate::agent::Error; -use crate::agent::Handler; -use crate::agent::HandlerRequest; -use crate::agent::Result as AgentResult; -use crate::didcomm::AbstractDidCommHandler; -use crate::didcomm::DidCommAgent; -use crate::didcomm::DidCommAgentIdentity; -use crate::didcomm::DidCommAgentState; -use crate::didcomm::DidCommHandler; -use crate::didcomm::DidCommHandlerMap; -use crate::didcomm::DidCommHandlerWrapper; -use crate::didcomm::DidCommRequest; -use crate::p2p::EventLoop; -use crate::p2p::InboundRequest; -use crate::p2p::NetCommander; - -/// A builder for [`DidCommAgent`]s to customize its configuration and attach handlers. -pub struct DidCommAgentBuilder { - inner: AgentBuilder, - identity: Option, - didcomm_handlers: DidCommHandlerMap, -} - -impl DidCommAgentBuilder { - /// Create a new builder with the default configuration. - pub fn new() -> DidCommAgentBuilder { - Self { - inner: AgentBuilder::new(), - identity: None, - didcomm_handlers: HashMap::new(), - } - } - - /// See [`AgentBuilder::keypair`]. - #[must_use] - pub fn keypair(mut self, keypair: Keypair) -> Self { - self.inner.keypair = Some(keypair); - self - } - - /// Sets the timeout for [`DidCommAgent::await_didcomm_request`] and the underlying libp2p - /// [`RequestResponse`](libp2p::request_response::RequestResponse) protocol. - #[must_use] - pub fn timeout(mut self, timeout: Duration) -> Self { - self.inner.config.timeout = timeout; - self - } - - /// Set the [`DidCommAgentIdentity`] that will be used for DIDComm related tasks, such as en- and decryption. - #[must_use] - pub fn identity(mut self, identity: DidCommAgentIdentity) -> Self { - self.identity = Some(identity); - self - } - - /// Attaches a [`DidCommHandler`] to this agent. - /// - /// This means that when the agent receives a request of type `REQ`, it will invoke this handler. - /// - /// Calling this method with a `REQ` type whose endpoint is already attached to a handler - /// will overwrite the previous attachment. - pub fn attach_didcomm(&mut self, handler: HND) - where - HND: DidCommHandler + Send + Sync, - REQ: DidCommRequest + Send + Sync, - { - self.didcomm_handlers.insert( - REQ::endpoint(), - Box::new(DidCommHandlerWrapper::new(handler)) as Box, - ); - } - - /// See [`AgentBuilder::attach`]. - pub fn attach(&mut self, handler: HND) - where - HND: Handler + Send + Sync, - REQ: HandlerRequest + Send + Sync, - REQ::Response: Send, - { - self.inner.attach(handler); - } - - /// See [`AgentBuilder::build`]. - pub async fn build(self) -> AgentResult { - let transport: _ = { - let dns_tcp_transport: TokioDnsConfig<_> = TokioDnsConfig::system(TokioTcpConfig::new().nodelay(true)) - .map_err(|err| Error::TransportError("building transport", libp2p::TransportError::Other(err)))?; - let ws_transport: WsConfig<_> = WsConfig::new( - TokioDnsConfig::system(TokioTcpConfig::new().nodelay(true)) - .map_err(|err| Error::TransportError("building transport", libp2p::TransportError::Other(err)))?, - ); - dns_tcp_transport.or_transport(ws_transport) - }; - - self.build_with_transport(transport).await - } - - /// See [`AgentBuilder::build_with_transport`]. - pub async fn build_with_transport(self, transport: TRA) -> AgentResult - where - TRA: Transport + Sized + Send + Sync + 'static, - TRA::Output: AsyncRead + AsyncWrite + Unpin + Send + 'static, - TRA::Dial: Send + 'static, - TRA::Listener: Send + 'static, - TRA::ListenerUpgrade: Send + 'static, - TRA::Error: Send + Sync, - { - let executor = Box::new(|fut| { - tokio::spawn(fut); - }); - - let (event_loop, handler_state, net_commander): (EventLoop, AgentState, NetCommander) = - self.inner.build_constituents(transport, executor.clone()).await?; - - let state: DidCommAgentState = - DidCommAgentState::new(self.didcomm_handlers, self.identity.ok_or(Error::IdentityMissing)?); - - let agent: Agent = Agent::new(net_commander, Arc::new(handler_state)); - - let didcomm_agent: DidCommAgent = DidCommAgent { - agent, - state: Arc::new(state), - }; - - let didcomm_agent_clone: DidCommAgent = didcomm_agent.clone(); - - let event_handler = move |event: InboundRequest| { - didcomm_agent_clone.clone().handle_request(event); - }; - - executor.exec(event_loop.run(event_handler).boxed()); - - Ok(didcomm_agent) - } -} - -impl Default for DidCommAgentBuilder { - fn default() -> Self { - Self::new() - } -} diff --git a/identity_agent/src/didcomm/dcpm.rs b/identity_agent/src/didcomm/dcpm.rs deleted file mode 100644 index dd46e3aee2..0000000000 --- a/identity_agent/src/didcomm/dcpm.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::agent::Endpoint; -use crate::didcomm::DidCommRequest; -use crate::didcomm::ThreadId; - -/// A DIDComm Plaintext Message. Implementation is currently rudimentary. -/// -/// See also: . -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DidCommPlaintextMessage { - pub(crate) typ: String, - pub(crate) id: ThreadId, - pub(crate) thid: Option, - pub(crate) pthid: Option, - #[serde(rename = "type")] - pub(crate) type_: String, - pub(crate) from: String, - pub(crate) to: String, - pub(crate) created_time: u32, - pub(crate) expires_time: u32, - pub(crate) body: T, -} - -impl DidCommPlaintextMessage { - pub(crate) fn new(id: ThreadId, type_: String, body: T) -> Self { - DidCommPlaintextMessage { - id, - type_, - body, - typ: String::new(), - thid: None, - pthid: None, - from: String::new(), - to: String::new(), - created_time: 0, - expires_time: 0, - } - } - - /// Returns the `ThreadId` of the message. - pub fn thread_id(&self) -> &ThreadId { - match self.thid.as_ref() { - Some(thid) => thid, - None => &self.id, - } - } - - /// Returns the body of the message. - pub fn body(&self) -> &T { - &self.body - } -} - -impl DidCommRequest for DidCommPlaintextMessage -where - T: DidCommRequest, -{ - fn endpoint() -> Endpoint { - T::endpoint() - } -} diff --git a/identity_agent/src/didcomm/handler.rs b/identity_agent/src/didcomm/handler.rs deleted file mode 100644 index 8e962e8a77..0000000000 --- a/identity_agent/src/didcomm/handler.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Debug; -use std::marker::PhantomData; - -use libp2p::request_response::RequestId; -use libp2p::request_response::ResponseChannel; -use serde::Serialize; - -use crate::agent::BoxFuture; -use crate::agent::Endpoint; -use crate::agent::ErrorLocation; -use crate::agent::RemoteSendError; -use crate::agent::RequestContext; -use crate::didcomm::DidCommAgent; -use crate::didcomm::DidCommRequest; -use crate::p2p::InboundRequest; -use crate::p2p::NetCommander; -use crate::p2p::ResponseMessage; - -/// Handlers are objects that encapsulate state and behavior. -/// -/// A DidCommHandler handles one or more requests by implementing this trait one or more times -/// for different `DidCommRequest` types. -/// -/// The requests for a DidCommHandler are handled asynchronously, meaning that the calling agent does -/// not wait for the handler to complete its invocation. If that is desired, the [`Handler`](crate::agent::Handler) -/// trait should be implemented instead. -#[async_trait::async_trait] -pub trait DidCommHandler: Debug + 'static { - /// Called when the agent receives a request of type `REQ`. - async fn handle(&self, handler: DidCommAgent, request: RequestContext); -} - -/// A trait that wraps a DidCommHandler implementation and erases its type. -/// This allows holding handlers with different concrete types in the same collection. -pub(crate) trait AbstractDidCommHandler: Debug + Send + Sync + 'static { - fn handle(&self, handler: DidCommAgent, request: InboundRequest) -> BoxFuture<'_, ()>; -} - -/// A wrapper around asynchronous handler implementations that is used for -/// type erasure together with [`AbstractAsyncHandler`]. -#[derive(Debug)] -pub(crate) struct DidCommHandlerWrapper -where - REQ: DidCommRequest + Send + Sync, - HND: DidCommHandler + Send + Sync, -{ - handler: HND, - _phantom_req: PhantomData, -} - -impl DidCommHandlerWrapper -where - REQ: DidCommRequest + Send + Sync, - HND: DidCommHandler + Send + Sync, -{ - pub(crate) fn new(handler: HND) -> Self { - Self { - handler, - _phantom_req: PhantomData, - } - } -} - -impl AbstractDidCommHandler for DidCommHandlerWrapper -where - REQ: DidCommRequest + Send + Sync, - HND: DidCommHandler + Send + Sync, -{ - fn handle(&self, mut agent: DidCommAgent, request: InboundRequest) -> BoxFuture<'_, ()> { - let future: _ = async move { - let req: REQ = match serde_json::from_slice::<'_, REQ>(&request.input).map_err(|error| { - RemoteSendError::DeserializationFailure { - location: ErrorLocation::Remote, - context: format!( - "deserializing the received bytes into the handler's expected type `{}`", - std::any::type_name::() - ), - error_message: error.to_string(), - } - }) { - Ok(req) => { - // Acknowledge request was received and understood. - send_didcomm_response( - agent.commander_mut(), - Ok(()), - &request.endpoint, - request.response_channel, - request.request_id, - ) - .await; - - req - } - Err(err) => { - send_didcomm_response( - agent.commander_mut(), - Result::<(), RemoteSendError>::Err(err), - &request.endpoint, - request.response_channel, - request.request_id, - ) - .await; - - // Abort because there is no request to handle and/or the calling agent is unresponsive. - return; - } - }; - - let context: RequestContext = RequestContext::new(req, request.peer_id, request.endpoint); - - self.handler.handle(agent, context).await; - }; - - Box::pin(future) - } -} - -async fn send_didcomm_response( - commander: &mut NetCommander, - response: Result, - endpoint: &Endpoint, - channel: ResponseChannel, - request_id: RequestId, -) { - match crate::agent::send_response(commander, response, channel, request_id).await { - Ok(Err(err)) => { - log::error!( - "could not send error for request on endpoint `{}` due to: {err:?}", - endpoint - ); - } - Err(err) => { - log::error!( - "could not send error for request on endpoint `{}` due to: {err:?}", - endpoint - ); - } - Ok(_) => (), - } -} diff --git a/identity_agent/src/didcomm/mod.rs b/identity_agent/src/didcomm/mod.rs deleted file mode 100644 index af00a32f10..0000000000 --- a/identity_agent/src/didcomm/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod agent; -mod agent_builder; -mod dcpm; -mod handler; -mod request; -mod thread_id; - -pub use agent::*; -pub use agent_builder::*; -pub use dcpm::*; -pub use handler::*; -pub use request::*; -pub use thread_id::*; diff --git a/identity_agent/src/didcomm/request.rs b/identity_agent/src/didcomm/request.rs deleted file mode 100644 index 5524334dd3..0000000000 --- a/identity_agent/src/didcomm/request.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Debug; - -use serde::de::DeserializeOwned; -use serde::Serialize; - -use crate::agent::Endpoint; -use crate::agent::RequestMode; - -/// A message that can be sent to a remote handler without an explicit response. -/// -/// This request is sent asynchronously, which means sending the request without waiting for -/// the result of that invocation on the remote agent. -/// However, an acknowledgment is returned to signal that an -/// appropriate handler exists that can handle the request, or an error, if the opposite is true. -pub trait DidCommRequest: Debug + Serialize + DeserializeOwned + Send + 'static { - /// The unique identifier for this request. See [`Endpoint`] for more details. - fn endpoint() -> Endpoint; - - /// Whether this request is synchronous or asynchronous. - fn request_mode() -> RequestMode { - RequestMode::Asynchronous - } -} diff --git a/identity_agent/src/didcomm/thread_id.rs b/identity_agent/src/didcomm/thread_id.rs deleted file mode 100644 index 9f5abc2df8..0000000000 --- a/identity_agent/src/didcomm/thread_id.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Display; - -use uuid::Uuid; - -/// An identifier for a DIDComm messaging thread. -#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct ThreadId(Uuid); - -impl ThreadId { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self(Uuid::new_v4()) - } -} - -impl Display for ThreadId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/identity_agent/src/lib.rs b/identity_agent/src/lib.rs deleted file mode 100644 index 7636710a4e..0000000000 --- a/identity_agent/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#![doc = include_str!("../README.md")] -#![forbid(unsafe_code)] -#![warn( - rust_2018_idioms, - unreachable_pub, - rustdoc::broken_intra_doc_links, - rustdoc::private_intra_doc_links, - rustdoc::private_doc_tests -)] - -pub mod agent; -pub mod didcomm; -mod p2p; -#[cfg(test)] -mod tests; - -pub use libp2p::identity::Keypair as IdentityKeypair; -pub use libp2p::Multiaddr; diff --git a/identity_agent/src/p2p/behaviour.rs b/identity_agent/src/p2p/behaviour.rs deleted file mode 100644 index 583481e9a0..0000000000 --- a/identity_agent/src/p2p/behaviour.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::io::{self}; - -use futures::AsyncRead; -use futures::AsyncWrite; -use futures::AsyncWriteExt; -use libp2p::core::upgrade; -use libp2p::core::ProtocolName; -use libp2p::request_response::RequestResponseCodec; - -use crate::p2p::RequestMessage; -use crate::p2p::ResponseMessage; - -/// The protocol of the agent. -#[derive(Debug, Clone)] -pub(crate) struct AgentProtocol(); - -/// Defines the request and response types for the libp2p RequestResponse layer. -#[derive(Clone)] -pub(crate) struct AgentRequestResponseCodec(); - -impl ProtocolName for AgentProtocol { - fn protocol_name(&self) -> &[u8] { - "/agent/0.1.0".as_bytes() - } -} - -#[async_trait::async_trait] -impl RequestResponseCodec for AgentRequestResponseCodec { - type Protocol = AgentProtocol; - type Request = RequestMessage; - type Response = ResponseMessage; - - async fn read_request(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - let vec = upgrade::read_length_prefixed(io, 1_000_000).await?; - - let request: RequestMessage = RequestMessage::from_bytes(vec.as_ref())?; - - Ok(request) - } - - async fn read_response(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - let vec = upgrade::read_length_prefixed(io, 1_000_000).await?; - - Ok(ResponseMessage(vec)) - } - - async fn write_request(&mut self, _protocol: &Self::Protocol, io: &mut T, request: Self::Request) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - let bytes: Vec = request.to_bytes()?; - - upgrade::write_length_prefixed(io, bytes).await?; - io.close().await - } - - async fn write_response( - &mut self, - _protocol: &Self::Protocol, - io: &mut T, - ResponseMessage(data): Self::Response, - ) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - upgrade::write_length_prefixed(io, data).await?; - io.close().await - } -} diff --git a/identity_agent/src/p2p/event_loop.rs b/identity_agent/src/p2p/event_loop.rs deleted file mode 100644 index af5f49042f..0000000000 --- a/identity_agent/src/p2p/event_loop.rs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use std::ops::ControlFlow; - -use futures::channel::mpsc; -use futures::channel::oneshot; -use futures::FutureExt; -use futures::StreamExt; -use libp2p::core::connection::ListenerId; -use libp2p::request_response::InboundFailure; -use libp2p::request_response::OutboundFailure; -use libp2p::request_response::RequestId; -use libp2p::request_response::RequestResponse; -use libp2p::request_response::RequestResponseEvent; -use libp2p::request_response::RequestResponseMessage; -use libp2p::request_response::ResponseChannel; -use libp2p::swarm::SwarmEvent; -use libp2p::Multiaddr; -use libp2p::PeerId; -use libp2p::Swarm; -use libp2p::TransportError; - -use crate::agent::Endpoint; -use crate::agent::RequestMode; -use crate::p2p::AgentRequestResponseCodec; -use crate::p2p::RequestMessage; -use crate::p2p::ResponseMessage; -use crate::p2p::SwarmCommand; - -/// The background loop that handles libp2p swarm events and `NetCommander` commands simultaneously. -pub(crate) struct EventLoop { - swarm: Swarm>, - command_channel: mpsc::Receiver, - await_response: HashMap>>, - await_response_sent: HashMap>>, - await_listen: HashMap>>>, -} - -impl EventLoop { - /// Create a new `EventLoop` from the given `swarm` and the receiving end of a channel. The sender - /// part needs to be passed to a `NetCommander`, which allows it to send request to this loop. - pub(crate) fn new( - swarm: Swarm>, - command_channel: mpsc::Receiver, - ) -> Self { - EventLoop { - swarm, - command_channel, - await_response: HashMap::new(), - await_response_sent: HashMap::new(), - await_listen: HashMap::new(), - } - } - - /// Block on this event loop until it terminates, simultaneously handling incoming events from other agents - /// as well as request from the corresponding `NetCommander`s. - pub(crate) async fn run(mut self, event_handler: F) - where - F: Fn(InboundRequest), - { - loop { - futures::select_biased! { - event = self.swarm.select_next_some() => self.handle_swarm_event(event, &event_handler).await, - command = self.command_channel.next().fuse() => { - if let Some(c) = command { - if let ControlFlow::Break(_) = self.handle_command(c) { - break; - } - } else { - break; - } - }, - } - } - } - - async fn handle_swarm_event( - &mut self, - event: SwarmEvent, THandleErr>, - event_handler: &F, - ) where - F: Fn(InboundRequest), - { - match event { - SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: RequestResponseMessage::Request { - channel, - request, - request_id, - }, - peer, - }) => { - event_handler(InboundRequest { - peer_id: peer, - endpoint: request.endpoint, - request_mode: request.request_mode, - input: request.data, - response_channel: channel, - request_id, - }); - } - SwarmEvent::Behaviour(RequestResponseEvent::Message { - message: RequestResponseMessage::Response { request_id, response }, - .. - }) => { - if let Some(response_channel) = self.await_response.remove(&request_id) { - let _ = response_channel.send(Ok(response)); - } - } - SwarmEvent::Behaviour(RequestResponseEvent::OutboundFailure { request_id, error, .. }) => { - if let Some(response_channel) = self.await_response.remove(&request_id) { - let _ = response_channel.send(Err(error)); - } - } - SwarmEvent::Behaviour(RequestResponseEvent::InboundFailure { error, request_id, .. }) => { - if let Some(response_channel) = self.await_response_sent.remove(&request_id) { - let _ = response_channel.send(Err(error)); - } - } - SwarmEvent::Behaviour(RequestResponseEvent::ResponseSent { request_id, .. }) => { - if let Some(response_channel) = self.await_response_sent.remove(&request_id) { - let _ = response_channel.send(Ok(())); - } - } - SwarmEvent::NewListenAddr { listener_id, address } => { - if let Some(response_channel) = self.await_listen.remove(&listener_id) { - let _ = response_channel.send(Ok(address)); - } - } - _ => (), - } - } - - fn handle_command(&mut self, command: SwarmCommand) -> ControlFlow<()> { - match command { - SwarmCommand::SendRequest { - peer_id: peer, - request, - response_channel, - } => { - let request_id = self.swarm.behaviour_mut().send_request(&peer, request); - self.await_response.insert(request_id, response_channel); - } - SwarmCommand::SendResponse { - response, - response_channel, - cmd_response_channel, - request_id, - } => { - if self - .swarm - .behaviour_mut() - .send_response(response_channel, ResponseMessage(response)) - .is_err() - { - if let Err(err) = cmd_response_channel.send(Err(InboundFailure::ConnectionClosed)) { - log::warn!("unable to send message `{err:?}` because receiver was dropped"); - } - } else { - self.await_response_sent.insert(request_id, cmd_response_channel); - } - } - SwarmCommand::StartListening { - address, - response_channel, - } => match self.swarm.listen_on(address) { - Ok(listener_id) => { - self.await_listen.insert(listener_id, response_channel); - } - Err(err) => { - if let Err(err) = response_channel.send(Err(err)) { - log::warn!("unable to send message `{err:?}` because receiver was dropped"); - } - } - }, - SwarmCommand::AddAddresses { - peer_id: peer, - addresses, - } => { - for addr in addresses { - self.swarm.behaviour_mut().add_address(&peer, addr); - } - } - SwarmCommand::GetAddresses { response_channel } => { - if let Err(err) = response_channel.send(self.swarm.listeners().map(ToOwned::to_owned).collect()) { - log::warn!("unable to send message `{err:?}` because receiver was dropped"); - } - } - SwarmCommand::Shutdown { response_channel } => { - // On shutdown, send error messages through all open channels - // to allow those tasks to terminate gracefully. - for (listener, channel) in std::mem::take(&mut self.await_listen).into_iter() { - let _ = self.swarm.remove_listener(listener); - let err = TransportError::Other(std::io::Error::new( - std::io::ErrorKind::Interrupted, - "handler was shut down", - )); - - let _ = channel.send(Err(err)); - } - - for (_, channel) in std::mem::take(&mut self.await_response) { - let _ = channel.send(Err(OutboundFailure::ConnectionClosed)); - } - - for (_, channel) in std::mem::take(&mut self.await_response_sent) { - let _ = channel.send(Err(InboundFailure::ConnectionClosed)); - } - - if let Err(err) = response_channel.send(()) { - log::warn!("unable to send message `{err:?}` because receiver was dropped"); - } - - return ControlFlow::Break(()); - } - } - ControlFlow::Continue(()) - } -} - -/// An inbound request as received by the p2p layer. -#[derive(Debug)] -pub(crate) struct InboundRequest { - pub(crate) peer_id: PeerId, - pub(crate) endpoint: Endpoint, - pub(crate) request_mode: RequestMode, - pub(crate) input: Vec, - pub(crate) response_channel: ResponseChannel, - pub(crate) request_id: RequestId, -} - -/// A request in a DIDComm thread. -#[derive(Debug)] -pub(crate) struct ThreadRequest { - pub(crate) endpoint: Endpoint, - pub(crate) input: Vec, -} diff --git a/identity_agent/src/p2p/message.rs b/identity_agent/src/p2p/message.rs deleted file mode 100644 index 2f4f776ca2..0000000000 --- a/identity_agent/src/p2p/message.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Debug; - -use serde::Deserialize; -use serde::Serialize; - -use crate::agent::Endpoint; -use crate::agent::RequestMode; - -/// A request message containing some opaque data together with the endpoint it is inteded for and its request mode. -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct RequestMessage { - pub(crate) endpoint: Endpoint, - pub(crate) request_mode: RequestMode, - pub(crate) data: Vec, -} - -impl RequestMessage { - /// Creates a new request message from its parts. - pub(crate) fn new(endpoint: Endpoint, request_mode: RequestMode, data: Vec) -> Self { - Self { - endpoint, - request_mode, - data, - } - } - - /// Deserializes some JSON bytes into a request message. - pub(crate) fn from_bytes(bytes: &[u8]) -> std::io::Result { - serde_json::from_slice::<'_, Self>(bytes) - .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string())) - } - - /// Serializes the request message into JSON bytes. - pub(crate) fn to_bytes(&self) -> std::io::Result> { - serde_json::to_vec(self).map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string())) - } -} - -/// A response message containing some opaque data. -#[derive(Debug)] -pub(crate) struct ResponseMessage(pub(crate) Vec); diff --git a/identity_agent/src/p2p/mod.rs b/identity_agent/src/p2p/mod.rs deleted file mode 100644 index 3dc1e65abd..0000000000 --- a/identity_agent/src/p2p/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod behaviour; -mod event_loop; -mod message; -mod net_commander; - -pub(crate) use behaviour::*; -pub(crate) use event_loop::*; -pub(crate) use message::*; -pub(crate) use net_commander::*; diff --git a/identity_agent/src/p2p/net_commander.rs b/identity_agent/src/p2p/net_commander.rs deleted file mode 100644 index b516fb4962..0000000000 --- a/identity_agent/src/p2p/net_commander.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use futures::channel::mpsc; -use futures::channel::oneshot; -use futures::future::poll_fn; -use identity_core::common::OneOrMany; -use libp2p::request_response::InboundFailure; -use libp2p::request_response::OutboundFailure; -use libp2p::request_response::RequestId; -use libp2p::request_response::ResponseChannel; -use libp2p::Multiaddr; -use libp2p::PeerId; -use libp2p::TransportError; - -use crate::agent::Error; -use crate::agent::Result as AgentResult; -use crate::p2p::RequestMessage; -use crate::p2p::ResponseMessage; - -/// A thread-safe way to interact with an `EventLoop` running in the background. -#[derive(Debug, Clone)] -pub(crate) struct NetCommander { - command_sender: mpsc::Sender, -} - -impl NetCommander { - /// Create a new [`NetCommander`] from the sender half of a channel. - /// The receiver half needs to be passed to the `EventLoop`. - pub(crate) fn new(command_sender: mpsc::Sender) -> Self { - NetCommander { command_sender } - } - - /// Send the `request` to `peer_id` and returns the response. - pub(crate) async fn send_request( - &mut self, - peer_id: PeerId, - request: RequestMessage, - ) -> AgentResult { - let (sender, receiver) = oneshot::channel(); - let command = SwarmCommand::SendRequest { - peer_id, - request, - response_channel: sender, - }; - self.send_command(command).await?; - receiver - .await - .map_err(|_| Error::Shutdown)? - .map_err(Error::OutboundFailure) - } - - /// Send `data` as a response for the `request_id` using the provided `channel`. - /// The inner result signals whether sending the response was successful. - pub(crate) async fn send_response( - &mut self, - data: Vec, - channel: ResponseChannel, - request_id: RequestId, - ) -> AgentResult> { - let (sender, receiver) = oneshot::channel(); - let command = SwarmCommand::SendResponse { - response: data, - cmd_response_channel: sender, - response_channel: channel, - request_id, - }; - self.send_command(command).await?; - receiver.await.map_err(|_| Error::Shutdown) - } - - /// Start listening on the given address. - pub(crate) async fn start_listening(&mut self, address: Multiaddr) -> AgentResult { - let (sender, receiver) = oneshot::channel(); - let command = SwarmCommand::StartListening { - address, - response_channel: sender, - }; - self.send_command(command).await?; - receiver - .await - .map_err(|_| Error::Shutdown)? - .map_err(|transport_err| Error::TransportError("start listening", transport_err)) - } - - /// Add additional `addresses` to listen on. - pub(crate) async fn add_addresses(&mut self, peer_id: PeerId, addresses: OneOrMany) -> AgentResult<()> { - self - .send_command(SwarmCommand::AddAddresses { peer_id, addresses }) - .await - } - - /// Returns all addresses the event loop is listening on. - pub(crate) async fn get_addresses(&mut self) -> AgentResult> { - let (sender, receiver) = oneshot::channel(); - self - .send_command(SwarmCommand::GetAddresses { - response_channel: sender, - }) - .await?; - receiver.await.map_err(|_| Error::Shutdown) - } - - /// Shut down the event loop. This will return `Error::Shutdown` from all outstanding requests. - pub(crate) async fn shutdown(&mut self) -> AgentResult<()> { - let (sender, receiver) = oneshot::channel(); - self - .send_command(SwarmCommand::Shutdown { - response_channel: sender, - }) - .await?; - receiver.await.map_err(|_| Error::Shutdown) - } - - /// Send a command to the event loop. - async fn send_command(&mut self, command: SwarmCommand) -> AgentResult<()> { - poll_fn(|cx| self.command_sender.poll_ready(cx)) - .await - .map_err(|_| Error::Shutdown)?; - self.command_sender.start_send(command).map_err(|_| Error::Shutdown) - } -} - -/// A command to send to the `EventLoop` with (typically) a channel to return a response through. -/// -/// See the [`NetCommander`] methods for documentation. -#[derive(Debug)] -pub(crate) enum SwarmCommand { - SendRequest { - peer_id: PeerId, - request: RequestMessage, - response_channel: oneshot::Sender>, - }, - SendResponse { - response: Vec, - cmd_response_channel: oneshot::Sender>, - response_channel: ResponseChannel, - request_id: RequestId, - }, - StartListening { - address: Multiaddr, - response_channel: oneshot::Sender>>, - }, - AddAddresses { - peer_id: PeerId, - addresses: OneOrMany, - }, - GetAddresses { - response_channel: oneshot::Sender>, - }, - Shutdown { - response_channel: oneshot::Sender<()>, - }, -} diff --git a/identity_agent/src/tests/didcomm.rs b/identity_agent/src/tests/didcomm.rs deleted file mode 100644 index 9561ef83e4..0000000000 --- a/identity_agent/src/tests/didcomm.rs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::time::Duration; - -use crate::agent::AgentId; -use crate::agent::Endpoint; -use crate::agent::Error; -use crate::agent::Handler; -use crate::agent::HandlerRequest; -use crate::agent::RequestContext; -use crate::agent::Result as AgentResult; -use crate::didcomm::DidCommAgent; -use crate::didcomm::DidCommHandler; -use crate::didcomm::DidCommPlaintextMessage; -use crate::didcomm::DidCommRequest; -use crate::didcomm::ThreadId; -use crate::tests::default_listening_didcomm_agent; -use crate::tests::default_sending_didcomm_agent; -use crate::tests::presentation::presentation_holder_handler; -use crate::tests::presentation::presentation_verifier_handler; -use crate::tests::presentation::DidCommState; -use crate::tests::presentation::PresentationOffer; -use crate::tests::presentation::PresentationRequest; -use crate::tests::remote_account::IdentityList; -use crate::tests::try_init_logger; - -/// Ensure the DidCommAgent supports handlers working with `HandlerRequest`s (rather than `DidCommRequest`s). -#[tokio::test] -async fn test_didcomm_agent_supports_handler_requests() -> AgentResult<()> { - try_init_logger(); - - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - struct SyncDummy(u16); - - impl HandlerRequest for SyncDummy { - type Response = u16; - - fn endpoint() -> Endpoint { - "test/request".try_into().unwrap() - } - } - - #[derive(Debug)] - struct TestHandler; - - #[async_trait::async_trait] - impl Handler for TestHandler { - async fn handle(&self, request: RequestContext) -> u16 { - request.input.0 - } - } - - let (listening_handler, addrs, agent_id) = default_listening_didcomm_agent(|mut builder| { - builder.attach(TestHandler); - builder - }) - .await; - - let mut sending_agent = default_sending_didcomm_agent(|builder| builder).await; - sending_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - let result = sending_agent.send_request(agent_id, SyncDummy(42)).await; - - assert_eq!(result.unwrap(), 42); - - listening_handler.shutdown().await.unwrap(); - sending_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_unknown_thread_returns_error() -> AgentResult<()> { - try_init_logger(); - - let (listening_handler, addrs, agent_id) = default_listening_didcomm_agent(|builder| builder).await; - - let mut sending_agent = default_sending_didcomm_agent(|builder| builder).await; - sending_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - struct DidCommTestRequest(u16); - - impl DidCommRequest for DidCommTestRequest { - fn endpoint() -> Endpoint { - "unknown/thread".try_into().unwrap() - } - } - - // Send a message that no handling handler on the remote agent exists for - // which causes the remote agent to look for a potential thread that is waiting for this message, - // but no such thread exists either, so an error is returned. - let result = sending_agent - .send_didcomm_request(agent_id, &ThreadId::new(), DidCommTestRequest(42)) - .await; - - assert!(matches!(result.unwrap_err(), Error::UnexpectedRequest(_))); - - listening_handler.shutdown().await.unwrap(); - sending_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_didcomm_presentation_holder_initiates() -> AgentResult<()> { - try_init_logger(); - let handler: DidCommState = DidCommState::new(); - - let mut holder_agent: DidCommAgent = default_sending_didcomm_agent(|builder| builder).await; - - // Attach the DidCommState handler to the listening agent, so it can handle PresentationOffer requests. - let (verifier_agent, addrs, agent_id) = default_listening_didcomm_agent(|mut builder| { - builder.attach_didcomm::, _>(handler.clone()); - builder - }) - .await; - - holder_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - // Holder initiates the presentation protocol. - presentation_holder_handler(holder_agent.clone(), agent_id, None) - .await - .unwrap(); - - // Allow background tasks to finish. - // The test also succeeds without this, but might cause the background tasks to panic or log an error. - tokio::task::yield_now().await; - - verifier_agent.shutdown().await.unwrap(); - holder_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_didcomm_presentation_verifier_initiates() -> AgentResult<()> { - try_init_logger(); - - let handler = DidCommState::new(); - - // Attach the DidCommState handler to the listening agent, so it can handle PresentationRequest requests. - let (holder_agent, addrs, agent_id) = default_listening_didcomm_agent(|mut builder| { - builder.attach_didcomm::, _>(handler.clone()); - builder - }) - .await; - let mut verifier_agent = default_sending_didcomm_agent(|builder| builder).await; - - verifier_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - // Verifier initiates the presentation protocol. - presentation_verifier_handler(verifier_agent.clone(), agent_id, None) - .await - .unwrap(); - - holder_agent.shutdown().await.unwrap(); - verifier_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_sending_to_an_unconnected_agent_returns_error() -> AgentResult<()> { - try_init_logger(); - - let mut sending_agent = default_sending_didcomm_agent(|builder| builder).await; - - // Send a request without adding an address first. - let result = sending_agent.send_request(AgentId::random(), IdentityList).await; - - assert!(matches!(result.unwrap_err(), Error::OutboundFailure(_))); - - let result = sending_agent - .send_didcomm_request(AgentId::random(), &ThreadId::new(), PresentationOffer::default()) - .await; - - assert!(matches!(result.unwrap_err(), Error::OutboundFailure(_))); - - sending_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_await_didcomm_request_returns_timeout_error() -> AgentResult<()> { - try_init_logger(); - - #[derive(Debug, Clone)] - struct MyHandler; - - #[async_trait::async_trait] - impl DidCommHandler> for MyHandler { - async fn handle(&self, _: DidCommAgent, _: RequestContext>) {} - } - - let (listening_handler, addrs, agent_id) = default_listening_didcomm_agent(|mut builder| { - builder.attach_didcomm(MyHandler); - builder - }) - .await; - - let mut sending_agent: DidCommAgent = - default_sending_didcomm_agent(|builder| builder.timeout(Duration::from_millis(50))).await; - - sending_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - let thread_id = ThreadId::new(); - sending_agent - .send_didcomm_request(agent_id, &thread_id, PresentationOffer::default()) - .await - .unwrap(); - - // We attempt to await a message, but the remote agent never sends one, so we expect a timeout. - let result = sending_agent.await_didcomm_request::<()>(&thread_id).await; - - assert!(matches!(result.unwrap_err(), Error::AwaitTimeout(_))); - - listening_handler.shutdown().await.unwrap(); - sending_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_handler_finishes_execution_after_shutdown() -> AgentResult<()> { - try_init_logger(); - - #[derive(Debug, Clone)] - struct TestHandler { - was_called: Arc, - } - - impl TestHandler { - fn new() -> Self { - Self { - was_called: Arc::new(AtomicBool::new(false)), - } - } - } - - #[async_trait::async_trait] - impl DidCommHandler> for TestHandler { - async fn handle(&self, _: DidCommAgent, _: RequestContext>) { - tokio::time::sleep(Duration::from_millis(25)).await; - self.was_called.store(true, Ordering::SeqCst); - } - } - - let test_handler = TestHandler::new(); - - let (listening_agent, addrs, agent_id) = default_listening_didcomm_agent(|mut builder| { - builder.attach_didcomm(test_handler.clone()); - builder - }) - .await; - - let mut sending_agent: DidCommAgent = default_sending_didcomm_agent(|builder| builder).await; - sending_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - sending_agent - .send_didcomm_request(agent_id, &ThreadId::new(), PresentationOffer::default()) - .await - .unwrap(); - - // Shut down the agent that executes the handler, and wait for some time to allow the handler to finish. - // Even though we shut the agent down, we expect the task that the handler is running in to finish. - listening_agent.shutdown().await.unwrap(); - - tokio::time::sleep(Duration::from_millis(50)).await; - - sending_agent.shutdown().await.unwrap(); - - assert!(test_handler.was_called.load(Ordering::SeqCst)); - - Ok(()) -} diff --git a/identity_agent/src/tests/handler.rs b/identity_agent/src/tests/handler.rs deleted file mode 100644 index 3d7aa3c2bf..0000000000 --- a/identity_agent/src/tests/handler.rs +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::atomic::AtomicBool; -use std::sync::atomic::AtomicU32; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::task::Poll; - -use futures::pin_mut; -use identity_iota_core::IotaDID; -use libp2p::request_response::OutboundFailure; -use libp2p::Multiaddr; - -use crate::agent::Agent; -use crate::agent::AgentBuilder; -use crate::agent::Endpoint; -use crate::agent::Error; -use crate::agent::ErrorLocation; -use crate::agent::Handler; -use crate::agent::HandlerRequest; -use crate::agent::RequestContext; -use crate::agent::Result as AgentResult; -use crate::tests::default_listening_agent; -use crate::tests::default_sending_agent; -use crate::tests::remote_account::IdentityGet; -use crate::tests::remote_account::IdentityList; -use crate::tests::try_init_logger; - -#[tokio::test] -async fn test_handler_end_to_end() -> AgentResult<()> { - try_init_logger(); - - #[derive(Debug, Clone)] - struct MyHandler { - counter: Arc, - } - - // Define our request types and implement HandlerRequest for them. - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - struct Increment(u32); - - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - struct Decrement(u32); - - impl HandlerRequest for Increment { - type Response = u32; - - fn endpoint() -> Endpoint { - "counter/increment".try_into().unwrap() - } - } - - impl HandlerRequest for Decrement { - type Response = u32; - - fn endpoint() -> Endpoint { - "counter/decrement".try_into().unwrap() - } - } - - // States that MyHandler can handle messages of type `Increment`. - #[async_trait::async_trait] - impl Handler for MyHandler { - async fn handle(&self, request: RequestContext) -> u32 { - self.counter.fetch_add(request.input.0, Ordering::SeqCst); - self.counter.load(Ordering::SeqCst) - } - } - - // States that MyHandler can handle messages of type `Decrement`. - #[async_trait::async_trait] - impl Handler for MyHandler { - async fn handle(&self, request: RequestContext) -> u32 { - self.counter.fetch_sub(request.input.0, Ordering::SeqCst); - self.counter.load(Ordering::SeqCst) - } - } - - let handler = MyHandler { - counter: Arc::new(AtomicU32::new(0)), - }; - - // Create a new agent and attach the handler. - // Each attachment is for one request type, so we have to do it twice. - let mut builder = AgentBuilder::new(); - builder.attach::(handler.clone()); - builder.attach::(handler.clone()); - - // Build the listening agent and let it listen on a default address. - let mut listening_agent: Agent = builder.build().await.unwrap(); - - let _ = listening_agent - .start_listening("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .await - .unwrap(); - let addresses = listening_agent.addresses().await.unwrap(); - let agent_id = listening_agent.agent_id(); - - let mut sender_agent: Agent = AgentBuilder::new().build().await.unwrap(); - // Add on which which addresses sender_agent can reach agent_id. - sender_agent.add_agent_addresses(agent_id, addresses).await.unwrap(); - - assert_eq!(sender_agent.send_request(agent_id, Increment(3)).await.unwrap(), 3); - assert_eq!(sender_agent.send_request(agent_id, Decrement(2)).await.unwrap(), 1); - - listening_agent.shutdown().await.unwrap(); - sender_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_unknown_request_returns_error() -> AgentResult<()> { - try_init_logger(); - - let (listening_handler, addrs, agent_id) = default_listening_agent(|builder| builder).await; - - let mut sending_handler = default_sending_agent(|builder| builder).await; - sending_handler.add_agent_addresses(agent_id, addrs).await.unwrap(); - - let result = sending_handler - .send_request( - agent_id, - IdentityGet( - "did:iota:rms:0xdfda8bcfb959c3e6ef261343c3e1a8310e9c8294eeafee326a4e96d65dbeaca0" - .try_into() - .unwrap(), - ), - ) - .await; - - assert!(matches!(result.unwrap_err(), Error::UnexpectedRequest(_))); - - listening_handler.shutdown().await.unwrap(); - sending_handler.shutdown().await.unwrap(); - - Ok(()) -} - -/// Test that agent2 can send a request to agent1 if it was previously sent a request from agent1. -#[tokio::test] -async fn test_handlers_can_communicate_bidirectionally() -> AgentResult<()> { - try_init_logger(); - - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - struct Dummy(u8); - - impl HandlerRequest for Dummy { - type Response = (); - - fn endpoint() -> Endpoint { - "request/test".try_into().unwrap() - } - } - - #[derive(Debug, Clone)] - struct TestHandler(Arc); - - #[async_trait::async_trait] - impl Handler for TestHandler { - async fn handle(&self, _req: RequestContext) { - self.0.store(true, std::sync::atomic::Ordering::SeqCst); - } - } - - let handler1 = TestHandler(Arc::new(AtomicBool::new(false))); - let handler2 = TestHandler(Arc::new(AtomicBool::new(false))); - - let mut agent1_builder = AgentBuilder::new(); - agent1_builder.attach(handler1.clone()); - let mut agent1: Agent = agent1_builder.build().await.unwrap(); - - let mut agent2_builder = AgentBuilder::new(); - agent2_builder.attach(handler2.clone()); - let mut agent2: Agent = agent2_builder.build().await.unwrap(); - - agent2 - .start_listening("/ip4/0.0.0.0/tcp/0".try_into().unwrap()) - .await - .unwrap(); - - let addrs: Vec = agent2.addresses().await.unwrap(); - - agent1.add_agent_addresses(agent2.agent_id(), addrs).await.unwrap(); - - agent1.send_request(agent2.agent_id(), Dummy(42)).await.unwrap(); - - agent2.send_request(agent1.agent_id(), Dummy(43)).await.unwrap(); - - agent1.shutdown().await.unwrap(); - agent2.shutdown().await.unwrap(); - - assert!(handler1.0.load(std::sync::atomic::Ordering::SeqCst)); - assert!(handler2.0.load(std::sync::atomic::Ordering::SeqCst)); - - Ok(()) -} - -#[tokio::test] -async fn test_interacting_with_shutdown_handler_returns_error() { - try_init_logger(); - - let (listening_handler, _, _) = default_listening_agent(|builder| builder).await; - - let mut handler_clone = listening_handler.clone(); - - listening_handler.shutdown().await.unwrap(); - - assert!(matches!(handler_clone.addresses().await.unwrap_err(), Error::Shutdown)); -} - -#[tokio::test] -async fn test_shutdown_returns_errors_through_open_channels() -> AgentResult<()> { - try_init_logger(); - - #[derive(Debug)] - struct TestHandler; - - #[async_trait::async_trait] - impl Handler for TestHandler { - async fn handle(&self, _: RequestContext) -> Vec { - tokio::time::sleep(std::time::Duration::from_millis(50)).await; - vec![] - } - } - - let (listening_agent, addrs, agent_id) = default_listening_agent(|mut builder| { - builder.attach(TestHandler); - builder - }) - .await; - - let mut sending_agent: Agent = AgentBuilder::new().build().await.unwrap(); - sending_agent.add_agent_addresses(agent_id, addrs).await.unwrap(); - - let mut sender1 = sending_agent.clone(); - - // Ensure that a handler shutdown returns errors through open channels, - // such as `EventLoop::await_response`. - // We do not test all `EventLoop::await*` fields, because some are - // much harder to test than others. - // We poll the futures once to ensure that the channels are created, - // before shutting the handler down. If we would call these methods after shutdown, - // they would immediately return a shutdown error (see test_interacting_with_shutdown_handler_returns_error), - // hence the need for manual polling. - // On the next poll after shutdown, we expect the errors. - - let send_request_future = sender1.send_request(agent_id, IdentityList); - pin_mut!(send_request_future); - let result = futures::poll!(&mut send_request_future); - assert!(matches!(result, Poll::Pending)); - - sending_agent.shutdown().await.unwrap(); - - let result = send_request_future.await; - assert!(matches!( - result.unwrap_err(), - Error::OutboundFailure(OutboundFailure::ConnectionClosed) - )); - - listening_agent.shutdown().await.unwrap(); - - Ok(()) -} - -#[tokio::test] -async fn test_endpoint_type_mismatch_results_in_serialization_errors() -> AgentResult<()> { - try_init_logger(); - - // Define two types with identical serialization results, but different `Response` types. - // Sending `CustomRequest2` to an endpoint expecting `CustomRequest`, we expect a local deserialization error. - - #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] - struct CustomRequest(u8); - - #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] - struct CustomRequest2(u8); - - impl HandlerRequest for CustomRequest { - type Response = String; - - fn endpoint() -> Endpoint { - "test/request".try_into().unwrap() - } - } - - impl HandlerRequest for CustomRequest2 { - type Response = u32; - - fn endpoint() -> Endpoint { - "test/request".try_into().unwrap() - } - } - - #[derive(Debug)] - struct TestHandler; - - #[async_trait::async_trait] - impl Handler for TestHandler { - async fn handle(&self, _: RequestContext) -> u32 { - 42 - } - } - - let (listening_handler, addrs, agent_id) = default_listening_agent(|mut builder| { - builder.attach(TestHandler); - builder - }) - .await; - - let mut sending_handler: Agent = AgentBuilder::new().build().await.unwrap(); - sending_handler.add_agent_addresses(agent_id, addrs).await.unwrap(); - - let result = sending_handler.send_request(agent_id, CustomRequest(13)).await; - - assert!(matches!( - result.unwrap_err(), - Error::DeserializationFailure { - location: ErrorLocation::Local, - .. - } - )); - - // Define a third type that has a different serialization result. - // We expect a deserialization error on the remote agent. - #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] - struct CustomRequest3(String); - - impl HandlerRequest for CustomRequest3 { - type Response = String; - - fn endpoint() -> Endpoint { - "test/request".try_into().unwrap() - } - } - - let result = sending_handler - .send_request(agent_id, CustomRequest3("13".to_owned())) - .await; - - assert!(matches!( - result.unwrap_err(), - Error::DeserializationFailure { - location: ErrorLocation::Remote, - .. - } - )); - - listening_handler.shutdown().await.unwrap(); - sending_handler.shutdown().await.unwrap(); - - Ok(()) -} diff --git a/identity_agent/src/tests/mod.rs b/identity_agent/src/tests/mod.rs deleted file mode 100644 index a785874e49..0000000000 --- a/identity_agent/src/tests/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod didcomm; -mod handler; -mod presentation; -mod remote_account; - -use libp2p::identity::Keypair; -use libp2p::Multiaddr; - -use crate::agent::Agent; -use crate::agent::AgentBuilder; -use crate::agent::AgentId; -use crate::didcomm::DidCommAgent; -use crate::didcomm::DidCommAgentBuilder; -use crate::didcomm::DidCommAgentIdentity; - -fn try_init_logger() { - let _ = pretty_env_logger::try_init(); -} - -async fn default_listening_agent(f: impl FnOnce(AgentBuilder) -> AgentBuilder) -> (Agent, Vec, AgentId) { - let id_keys = Keypair::generate_ed25519(); - - let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); - let mut builder = AgentBuilder::new().keypair(id_keys); - - builder = f(builder); - - let mut listening_agent: Agent = builder.build().await.unwrap(); - - let _ = listening_agent.start_listening(addr).await.unwrap(); - let addrs = listening_agent.addresses().await.unwrap(); - - let agent_id = listening_agent.agent_id(); - - (listening_agent, addrs, agent_id) -} - -async fn default_sending_agent(f: impl FnOnce(AgentBuilder) -> AgentBuilder) -> Agent { - let mut builder = AgentBuilder::new(); - - builder = f(builder); - - builder.build().await.unwrap() -} - -async fn default_sending_didcomm_agent(f: impl FnOnce(DidCommAgentBuilder) -> DidCommAgentBuilder) -> DidCommAgent { - let mut builder = DidCommAgentBuilder::new().identity(default_identity()); - - builder = f(builder); - - builder.build().await.unwrap() -} - -async fn default_listening_didcomm_agent( - f: impl FnOnce(DidCommAgentBuilder) -> DidCommAgentBuilder, -) -> (DidCommAgent, Vec, AgentId) { - let id_keys = Keypair::generate_ed25519(); - - let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); - let mut builder = DidCommAgentBuilder::new().keypair(id_keys).identity(default_identity()); - - builder = f(builder); - - let mut listening_agent: DidCommAgent = builder.build().await.unwrap(); - - let _ = listening_agent.start_listening(addr).await.unwrap(); - let addrs = listening_agent.addresses().await.unwrap(); - - let agent_id = listening_agent.agent_id(); - - (listening_agent, addrs, agent_id) -} - -fn default_identity() -> DidCommAgentIdentity { - DidCommAgentIdentity::new() -} diff --git a/identity_agent/src/tests/presentation.rs b/identity_agent/src/tests/presentation.rs deleted file mode 100644 index 5c20064cc4..0000000000 --- a/identity_agent/src/tests/presentation.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! A conceptual implementation of the IOTA DIDComm presentation protocol. -//! It merely sends the appropriate messages back and forth, but without any actual content. -//! It exists to prove the concept for the DIDComm agent. -//! -//! See for details: https://wiki.iota.org/identity.rs/specs/didcomm/protocols/presentation. - -use serde::Deserialize; -use serde::Serialize; - -use crate::agent::AgentId; -use crate::agent::Endpoint; -use crate::agent::RequestContext; -use crate::agent::Result as AgentResult; -use crate::didcomm::DidCommAgent; -use crate::didcomm::DidCommHandler; -use crate::didcomm::DidCommPlaintextMessage; -use crate::didcomm::DidCommRequest; -use crate::didcomm::ThreadId; - -#[derive(Debug, Clone)] -pub(crate) struct DidCommState; - -impl DidCommState { - pub(crate) fn new() -> Self { - Self - } -} - -#[async_trait::async_trait] -impl DidCommHandler> for DidCommState { - async fn handle(&self, agent: DidCommAgent, request: RequestContext>) { - log::debug!("holder: received presentation request"); - - let result = presentation_holder_handler(agent, request.agent_id, Some(request.input)).await; - - if let Err(err) = result { - log::error!("presentation holder handler errored: {err:?}"); - } - } -} - -#[async_trait::async_trait] -impl DidCommHandler> for DidCommState { - async fn handle(&self, agent: DidCommAgent, request: RequestContext>) { - log::debug!("verifier: received offer from {}", request.agent_id); - - let result = presentation_verifier_handler(agent, request.agent_id, Some(request.input)).await; - - if let Err(err) = result { - log::error!("presentation verifier handler errored: {err:?}"); - } - } -} - -/// The presentation protocol for the handler. -/// -/// If `request` is `None`, the holder initiates the protocol, otherwise the verifier initiated -/// by sending a `PresentationRequest`. -pub(crate) async fn presentation_holder_handler( - mut agent: DidCommAgent, - agent_id: AgentId, - request: Option>, -) -> AgentResult<()> { - let request: DidCommPlaintextMessage = match request { - Some(request) => request, - None => { - log::debug!("holder: sending presentation offer"); - let thread_id = ThreadId::new(); - agent - .send_didcomm_request(agent_id, &thread_id, PresentationOffer::default()) - .await?; - - let req = agent.await_didcomm_request(&thread_id).await; - log::debug!("holder: received presentation request"); - - req? - } - }; - - let thread_id = request.thread_id(); - - log::debug!("holder: sending presentation"); - agent - .send_didcomm_request(agent_id, thread_id, Presentation::default()) - .await?; - - let _result: DidCommPlaintextMessage = agent.await_didcomm_request(thread_id).await?; - log::debug!("holder: received presentation result"); - - Ok(()) -} - -/// The presentation protocol for the verifier. -/// -/// If `offer` is `None`, the verifier initiates the protocol, otherwise the holder initiated -/// by sending a `PresentationOffer`. -pub(crate) async fn presentation_verifier_handler( - mut agent: DidCommAgent, - agent_id: AgentId, - offer: Option>, -) -> AgentResult<()> { - let thread_id: ThreadId = if let Some(offer) = offer { - offer.thread_id().to_owned() - } else { - ThreadId::new() - }; - - log::debug!("verifier: sending request"); - agent - .send_didcomm_request(agent_id, &thread_id, PresentationRequest::default()) - .await?; - - log::debug!("verifier: awaiting presentation"); - let presentation: DidCommPlaintextMessage = agent.await_didcomm_request(&thread_id).await?; - log::debug!("verifier: received presentation: {:?}", presentation); - - log::debug!("verifier: sending presentation result"); - agent - .send_didcomm_request(agent_id, &thread_id, PresentationResult::default()) - .await?; - Ok(()) -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default)] -pub(crate) struct PresentationRequest([u8; 2]); - -impl DidCommRequest for PresentationRequest { - fn endpoint() -> Endpoint { - "didcomm/presentation_request".try_into().unwrap() - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default)] -pub(crate) struct PresentationOffer([u8; 3]); - -impl DidCommRequest for PresentationOffer { - fn endpoint() -> Endpoint { - "didcomm/presentation_offer".try_into().unwrap() - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default)] -pub(crate) struct Presentation([u8; 4]); - -impl DidCommRequest for Presentation { - fn endpoint() -> Endpoint { - "didcomm/presentation".try_into().unwrap() - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default)] -pub(crate) struct PresentationResult([u8; 5]); - -impl DidCommRequest for PresentationResult { - fn endpoint() -> Endpoint { - "didcomm/presentation_result".try_into().unwrap() - } -} diff --git a/identity_agent/src/tests/remote_account/error.rs b/identity_agent/src/tests/remote_account/error.rs deleted file mode 100644 index 8b9c644112..0000000000 --- a/identity_agent/src/tests/remote_account/error.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -/// The error type for the [`RemoteAccount`]. -#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] -#[non_exhaustive] -pub(crate) enum RemoteAccountError { - #[error("identity not found")] - IdentityNotFound, - #[error("placeholder DIDs cannot be managed")] - PlaceholderDID, -} diff --git a/identity_agent/src/tests/remote_account/handler.rs b/identity_agent/src/tests/remote_account/handler.rs deleted file mode 100644 index aa19e85a82..0000000000 --- a/identity_agent/src/tests/remote_account/handler.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::Arc; - -use dashmap::DashMap; -use identity_iota_core::IotaDID; -use identity_iota_core::IotaDocument; - -use crate::agent::Handler; -use crate::agent::RequestContext; -use crate::tests::remote_account::IdentityCreate; -use crate::tests::remote_account::IdentityGet; -use crate::tests::remote_account::IdentityList; -use crate::tests::remote_account::RemoteAccountError; - -/// A proof-of-concept implementation of a remote account -/// which holds and manages a collection of DID documents. -#[derive(Debug, Clone)] -pub(crate) struct RemoteAccount { - documents: Arc>, -} - -#[async_trait::async_trait] -impl Handler for RemoteAccount { - async fn handle(&self, _: RequestContext) -> Vec { - self.documents.iter().map(|entry| entry.key().to_owned()).collect() - } -} - -#[async_trait::async_trait] -impl Handler for RemoteAccount { - async fn handle(&self, request: RequestContext) -> Result<(), RemoteAccountError> { - let document = request.input.0; - - if document.id().is_placeholder() { - return Err(RemoteAccountError::PlaceholderDID); - } - - self.documents.insert(document.id().to_owned(), document); - Ok(()) - } -} - -#[async_trait::async_trait] -impl Handler for RemoteAccount { - async fn handle(&self, request: RequestContext) -> Result { - self - .documents - .get(&request.input.0) - .map(|document| document.to_owned()) - .ok_or(RemoteAccountError::IdentityNotFound) - } -} - -impl RemoteAccount { - pub(crate) fn new() -> Self { - Self { - documents: Arc::new(DashMap::new()), - } - } -} diff --git a/identity_agent/src/tests/remote_account/mod.rs b/identity_agent/src/tests/remote_account/mod.rs deleted file mode 100644 index 5346f5858b..0000000000 --- a/identity_agent/src/tests/remote_account/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod error; -mod handler; -mod requests; -mod tests; - -pub(crate) use error::*; -pub(crate) use handler::*; -pub(crate) use requests::*; diff --git a/identity_agent/src/tests/remote_account/requests.rs b/identity_agent/src/tests/remote_account/requests.rs deleted file mode 100644 index c697fe9e67..0000000000 --- a/identity_agent/src/tests/remote_account/requests.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota_core::IotaDID; -use identity_iota_core::IotaDocument; -use serde::Deserialize; -use serde::Serialize; - -use crate::agent::Endpoint; -use crate::agent::HandlerRequest; -use crate::tests::remote_account::RemoteAccountError; - -/// Can be sent to a `RemoteAccount` to instruct it to add a document. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct IdentityCreate(pub(crate) IotaDocument); - -impl HandlerRequest for IdentityCreate { - type Response = Result<(), RemoteAccountError>; - - fn endpoint() -> Endpoint { - "remote_account/create".try_into().unwrap() - } -} - -/// Can be sent to a `RemoteAccount` to instruct it to return the identities it contains. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct IdentityList; - -impl HandlerRequest for IdentityList { - type Response = Vec; - - fn endpoint() -> Endpoint { - "remote_account/list".try_into().unwrap() - } -} - -/// Can be sent to a `RemoteAccount` to instruct it to return the given identities' DID document. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct IdentityGet(pub(crate) IotaDID); - -impl HandlerRequest for IdentityGet { - type Response = Result; - - fn endpoint() -> Endpoint { - "remote_account/get".try_into().unwrap() - } -} diff --git a/identity_agent/src/tests/remote_account/tests.rs b/identity_agent/src/tests/remote_account/tests.rs deleted file mode 100644 index 05122751aa..0000000000 --- a/identity_agent/src/tests/remote_account/tests.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota_core::IotaDID; -use identity_iota_core::IotaDocument; -use identity_iota_core::NetworkName; - -use crate::agent::Result as AgentResult; -use crate::tests::default_listening_agent; -use crate::tests::default_sending_agent; -use crate::tests::remote_account::IdentityCreate; -use crate::tests::remote_account::IdentityGet; -use crate::tests::remote_account::IdentityList; -use crate::tests::remote_account::RemoteAccount; -use crate::tests::try_init_logger; - -#[tokio::test] -async fn test_remote_account() -> AgentResult<()> { - try_init_logger(); - - let (receiver, receiver_addrs, receiver_agent_id) = default_listening_agent(|mut builder| { - let remote_account = RemoteAccount::new(); - builder.attach::(remote_account.clone()); - builder.attach::(remote_account.clone()); - builder.attach::(remote_account); - builder - }) - .await; - let mut sender = default_sending_agent(|builder| builder).await; - - sender - .add_agent_addresses(receiver_agent_id, receiver_addrs) - .await - .unwrap(); - - let doc = fake_document(); - - sender - .send_request(receiver_agent_id, IdentityCreate(doc.clone())) - .await? - .unwrap(); - - assert_eq!(sender.send_request(receiver_agent_id, IdentityList).await?.len(), 1); - - let doc2: IotaDocument = sender - .send_request(receiver_agent_id, IdentityGet(doc.id().clone())) - .await? - .unwrap(); - - assert_eq!(doc, doc2); - - sender.shutdown().await.unwrap(); - receiver.shutdown().await.unwrap(); - - Ok(()) -} - -fn fake_document() -> IotaDocument { - let rand_bytes: [u8; 32] = rand::random(); - let network_name = NetworkName::try_from("iota").unwrap(); - let did = IotaDID::new(&rand_bytes, &network_name); - IotaDocument::new_with_id(did) -} diff --git a/identity_comm/Cargo.toml b/identity_comm/Cargo.toml deleted file mode 100644 index cdfcc668ef..0000000000 --- a/identity_comm/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "identity_comm" -version = "0.5.0-dev.4" -authors = ["IOTA Stiftung"] -edition = "2021" -homepage = "https://www.iota.org" -keywords = ["iota", "tangle", "identity"] -license = "Apache-2.0" -readme = "../README.md" -repository = "https://github.com/iotaledger/identity.rs" -description = "An implementation of the DIDComm Messaging Specification." - -[dependencies] -identity_core = { path = "../identity_core", version = "=0.5.0-dev.4" } -identity_credential = { path = "../identity_credential", version = "=0.5.0-dev.4" } -identity_did = { path = "../identity_did", version = "=0.5.0-dev.4" } -identity_iota_client_legacy = { version = "=0.5.0", path = "../identity_iota_client_legacy", default-features = false } -# libjose = { path = "../libjose", version = "=0.1.0" } -paste = { version = "1.0" } -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } -strum = { version = "0.24.0", default-features = false, features = ["std", "derive"] } -thiserror = { version = "1.0" } -uuid = { version = "0.8", features = ["serde", "v4"], default-features = false } - -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] -uuid = { version = "*", features = ["wasm-bindgen"], default-features = false } diff --git a/identity_comm/README.md b/identity_comm/README.md deleted file mode 100644 index cf4c3b7453..0000000000 --- a/identity_comm/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Identity Communication - -This crate implements DID-based messaging utilities that partially adheres to the [DIDComm Messaging Specification](https://identity.foundation/didcomm-messaging/spec/) by the [Decentralized Identity Foundation (DIF)](https://identity.foundation/). - -:warning: **WARNING** :warning: -The IOTA Identity team is currently working on a new IOTA DIDComm specification. Once this new spec has been finalized this crate will most likely be heavily refactored, or even replaced in its entirety. One should expect many breaking changes! - -## Very brief introduction to DIDComm Messaging -**This section essentially consists of extracts from the [DIDComm spec](https://identity.foundation/didcomm-messaging/spec/).** - -The purpose of DIDComm Messaging is to provide a secure, private communication methodology built atop the decentralized design of [DIDs](https://www.w3.org/TR/did-core/). Higher-order protocols such as issuing a verifiable credential can be built in the context of the DIDComm Messaging specification. - -DIDComm messages can exist in three different formats: -1.The simplest and most fundamental of these is the [DIDComm Plaintext message](https://identity.foundation/didcomm-messaging/spec/#didcomm-plaintext-messages) which is a [JSON Web Message (JWM)](https://datatracker.ietf.org/doc/html/draft-looker-jwm-01) containing [headers](https://identity.foundation/didcomm-messaging/spec/#message-headers) and conveys application-level data inside a JSON `body`. - -2. A DIDComm plaintext message can optionally be packed in a *signed envelope* that associates a non-repudiable signature with the plaintext message inside it. A DIDComm message of this format is called a [DIDComm Signed Message](https://identity.foundation/didcomm-messaging/spec/#didcomm-signed-message). - -3. The third format for DIDComm messages is the [DIDComm encrypted message](https://identity.foundation/didcomm-messaging/spec/#didcomm-encrypted-message). In this case either a DIDComm plaintext message or a DIDComm signed message is packed in an envelope that applies encryption to the content it encloses. -## Crate details -- The envelope module provides algorithms for packing messages into envelopes used in the definitions of the various [DIDComm Message Types](https://identity.foundation/didcomm-messaging/spec/#message-types). -- The message module contains a `Message` trait that can be utilized to pack messages into envelopes. diff --git a/identity_comm/src/envelope/encrypted.rs b/identity_comm/src/envelope/encrypted.rs deleted file mode 100644 index c87486b4cc..0000000000 --- a/identity_comm/src/envelope/encrypted.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Functionality for creating [DIDComm encrypted messages](https://identity.foundation/didcomm-messaging/spec/#didcomm-encrypted-message) - -#![allow(non_camel_case_types)] - -use identity_core::convert::FromJson; -use identity_core::convert::ToJson; -use identity_core::crypto::KeyPair; -use identity_core::crypto::PrivateKey; -use identity_core::crypto::PublicKey; -use libjose::jwe::Decoder; -use libjose::jwe::Encoder; -use libjose::jwe::JweAlgorithm; -use libjose::jwe::JweEncryption; -use libjose::jwe::JweFormat; -use libjose::jwe::JweHeader; -use libjose::jwe::Token; - -use crate::envelope::EnvelopeExt; -use crate::envelope::Plaintext; -use crate::envelope::Signed; -use crate::error::Result; - -/// A DIDComm Encrypted Message -/// -/// [Reference](https://identity.foundation/didcomm-messaging/spec/#didcomm-encrypted-message) -/// -/// # Layout -/// -/// `JWE(Plaintext | Signed)` -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Encrypted(pub(crate) String); - -impl Encrypted { - pub fn pack( - message: &T, - algorithm: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result { - Plaintext::pack(message).and_then(|plaintext| Self::pack_plaintext(&plaintext, algorithm, recipients, sender)) - } - - pub fn pack_plaintext( - envelope: &Plaintext, - algorithm: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result { - Self::pack_envelope(envelope, algorithm, recipients, sender) - } - - pub fn pack_signed( - envelope: &Signed, - algorithm: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result { - Self::pack_envelope(envelope, algorithm, recipients, sender) - } - - fn pack_envelope( - envelope: &T, - algorithm: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result { - let header: JweHeader = JweHeader::new(JweAlgorithm::ECDH_1PU, algorithm.into()); - - let encoder: Encoder<'_> = Encoder::new() - .format(JweFormat::General) - .protected(&header) - .secret(sender.private()); - - recipients - .iter() - .fold(encoder, |encoder, recipient| encoder.recipient(recipient)) - .encode(envelope.as_bytes()) - .map_err(Into::into) - .map(Self) - } - - pub fn unpack( - &self, - algorithm: EncryptionAlgorithm, - recipient: &PrivateKey, - sender: &PublicKey, - ) -> Result { - let token: Token = Decoder::new(recipient) - .public(sender) - .format(JweFormat::General) - .algorithm(JweAlgorithm::ECDH_1PU) - .encryption(algorithm.into()) - .decode(self.as_bytes())?; - - T::from_json_slice(&token.1).map_err(Into::into) - } -} - -impl EnvelopeExt for Encrypted { - const FEXT: &'static str = "dcem"; - const MIME: &'static str = "application/didcomm-encrypted+json"; - - fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } -} - -// ============================================================================= -// ============================================================================= - -/// Supported content encryption algorithms -/// -/// [Reference (auth)](https://identity.foundation/didcomm-messaging/spec/#sender-authenticated-encryption) -/// [Reference (anon)](https://identity.foundation/didcomm-messaging/spec/#anonymous-encryption) -#[derive(Clone, Copy, Debug)] -pub enum EncryptionAlgorithm { - A256GCM, - XC20P, -} - -impl From for JweEncryption { - fn from(other: EncryptionAlgorithm) -> Self { - match other { - EncryptionAlgorithm::A256GCM => Self::A256GCM, - EncryptionAlgorithm::XC20P => Self::XC20P, - } - } -} diff --git a/identity_comm/src/envelope/mod.rs b/identity_comm/src/envelope/mod.rs deleted file mode 100644 index 073c5b5ee1..0000000000 --- a/identity_comm/src/envelope/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Provides DIDComm message packing utilities - -mod encrypted; -mod plaintext; -mod signed; -mod traits; - -pub use self::encrypted::*; -pub use self::plaintext::*; -pub use self::signed::*; -pub use self::traits::*; diff --git a/identity_comm/src/envelope/plaintext.rs b/identity_comm/src/envelope/plaintext.rs deleted file mode 100644 index b5c576ff54..0000000000 --- a/identity_comm/src/envelope/plaintext.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Functionality for creating [DIDComm plaintext messages](https://identity.foundation/didcomm-messaging/spec/#didcomm-plaintext-messages) - -use identity_core::convert::FromJson; -use identity_core::convert::ToJson; - -use crate::envelope::EnvelopeExt; -use crate::error::Result; - -/// A DIDComm Plaintext Message -/// -/// [Reference](https://identity.foundation/didcomm-messaging/spec/#didcomm-plaintext-messages) -/// -/// # Layout -/// -/// `JWM(Content)` -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Plaintext(pub(crate) String); - -impl Plaintext { - pub fn pack(message: &T) -> Result { - message.to_json().map_err(Into::into).map(Self) - } - - pub fn unpack(&self) -> Result { - T::from_json(&self.0).map_err(Into::into) - } -} - -impl EnvelopeExt for Plaintext { - const FEXT: &'static str = "dcpm"; - const MIME: &'static str = "application/didcomm-plain+json"; - - fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } -} diff --git a/identity_comm/src/envelope/signed.rs b/identity_comm/src/envelope/signed.rs deleted file mode 100644 index 17df1b4054..0000000000 --- a/identity_comm/src/envelope/signed.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Functionality for creating [signed DIDComm messages](https://identity.foundation/didcomm-messaging/spec/#didcomm-signed-message) - -use identity_core::convert::FromJson; -use identity_core::convert::ToJson; -use identity_core::crypto::KeyPair; -use identity_core::crypto::PublicKey; -use identity_core::utils::encode_b58; -use libjose::jose::JoseTokenType; -use libjose::jws::Decoder; -use libjose::jws::Encoder; -use libjose::jws::JwsAlgorithm; -use libjose::jws::JwsFormat; -use libjose::jws::JwsHeader; - -use crate::envelope::EnvelopeExt; -use crate::envelope::Plaintext; -use crate::error::Result; - -/// A DIDComm Signed Message -/// -/// [Reference](https://identity.foundation/didcomm-messaging/spec/#didcomm-signed-message) -/// -/// # Layout -/// -/// `JWS(Plaintext)` -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Signed(pub(crate) String); - -impl Signed { - pub fn pack_plaintext(message: &Plaintext, algorithm: SignatureAlgorithm, keypair: &KeyPair) -> Result { - let header: JwsHeader = { - let mut header: JwsHeader = JwsHeader::new(algorithm.into()); - header.set_kid(encode_b58(keypair.public())); - header.set_typ(JoseTokenType::JWM.name()); - header - }; - - Encoder::new() - .format(JwsFormat::Compact) - .recipient((keypair.private(), &header)) - .encode(message.as_bytes()) - .map_err(Into::into) - .map(Self) - } - - pub fn unpack_plaintext(&self, algorithm: SignatureAlgorithm, public: &PublicKey) -> Result { - let claims: Vec<u8> = Decoder::new(public) - .key_id(encode_b58(public)) - .format(JwsFormat::Compact) - .algorithm(algorithm.into()) - .decode(self.0.as_bytes()) - .map(|token| token.claims.to_vec())?; - - Ok(Plaintext(String::from_utf8(claims)?)) - } - - pub fn pack<T: ToJson>(message: &T, algorithm: SignatureAlgorithm, keypair: &KeyPair) -> Result<Self> { - Plaintext::pack(message).and_then(|plaintext| Self::pack_plaintext(&plaintext, algorithm, keypair)) - } - - pub fn unpack<T: FromJson>(&self, algorithm: SignatureAlgorithm, public: &PublicKey) -> Result<T> { - self - .unpack_plaintext(algorithm, public) - .and_then(|plaintext| plaintext.unpack()) - } -} - -impl EnvelopeExt for Signed { - const FEXT: &'static str = "dcsm"; - const MIME: &'static str = "application/didcomm-signed+json"; - - fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } -} - -// ============================================================================= -// ============================================================================= - -/// Supported digital signature algorithms -/// -/// [Reference](https://identity.foundation/didcomm-messaging/spec/#algorithms) -#[derive(Clone, Copy, Debug)] -pub enum SignatureAlgorithm { - EdDSA, // crv=Ed25519 - ES256, - ES256K, -} - -impl From<SignatureAlgorithm> for JwsAlgorithm { - fn from(other: SignatureAlgorithm) -> Self { - match other { - SignatureAlgorithm::EdDSA => Self::EdDSA, - SignatureAlgorithm::ES256 => Self::ES256, - SignatureAlgorithm::ES256K => Self::ES256K, - } - } -} diff --git a/identity_comm/src/envelope/traits.rs b/identity_comm/src/envelope/traits.rs deleted file mode 100644 index bd0cbf9417..0000000000 --- a/identity_comm/src/envelope/traits.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Media type and file extension constants for DIDComm messages. -pub trait EnvelopeExt { - const FEXT: &'static str; - const MIME: &'static str; - - fn as_bytes(&self) -> &[u8]; -} diff --git a/identity_comm/src/error.rs b/identity_comm/src/error.rs deleted file mode 100644 index abc3538e85..0000000000 --- a/identity_comm/src/error.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Provides a composite of errors from identity.rs -pub type Result<T, E = Error> = core::result::Result<T, E>; - -#[derive(Debug, thiserror::Error, strum::IntoStaticStr)] -pub enum Error { - #[error(transparent)] - IotaError(#[from] identity_iota_client_legacy::Error), - #[error(transparent)] - CoreError(#[from] identity_core::Error), - #[error(transparent)] - DidError(#[from] identity_did::Error), - #[error(transparent)] - JoseError(#[from] libjose::Error), - #[error(transparent)] - Utf8Error(#[from] std::string::FromUtf8Error), -} diff --git a/identity_comm/src/lib.rs b/identity_comm/src/lib.rs deleted file mode 100644 index fccd6788df..0000000000 --- a/identity_comm/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#![forbid(unsafe_code)] -#![cfg_attr(docsrs, feature(doc_cfg, extended_key_value_attributes))] -#![cfg_attr(docsrs, cfg_attr(docsrs, doc = include_str!("../README.md")))] -#![cfg_attr(not(docsrs), doc = "")] -#![allow(clippy::upper_case_acronyms)] -#![warn( - rust_2018_idioms, - // unreachable_pub, - // missing_docs, - rustdoc::missing_crate_level_docs, - rustdoc::broken_intra_doc_links, - rustdoc::private_intra_doc_links, - rustdoc::private_doc_tests, - clippy::missing_safety_doc, - // clippy::missing_errors_doc, -)] - -pub mod envelope; -pub mod error; -pub mod message; -pub mod types; diff --git a/identity_comm/src/message/mod.rs b/identity_comm/src/message/mod.rs deleted file mode 100644 index b0bd58710e..0000000000 --- a/identity_comm/src/message/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod traits; - -pub use self::traits::*; diff --git a/identity_comm/src/message/traits.rs b/identity_comm/src/message/traits.rs deleted file mode 100644 index cbd78cc683..0000000000 --- a/identity_comm/src/message/traits.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -//! Defines how to pack messages into envelopes. -use identity_core::convert::ToJson; -use identity_core::crypto::KeyPair; -use identity_core::crypto::PublicKey; - -use crate::envelope::Encrypted; -use crate::envelope::EncryptionAlgorithm; -use crate::envelope::Plaintext; -use crate::envelope::SignatureAlgorithm; -use crate::envelope::Signed; -use crate::error::Result; - -/// A general-purpose extension to pack messages into envelopes. -pub trait Message { - fn pack_plain(&self) -> Result<Plaintext>; - - fn pack_signed(&self, algorithm: SignatureAlgorithm, sender: &KeyPair) -> Result<Signed>; - - fn pack_encrypted( - &self, - algorithm: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result<Encrypted>; - - fn pack_signed_encrypted( - &self, - signature: SignatureAlgorithm, - encryption: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result<Encrypted>; -} - -impl<T: ToJson> Message for T { - fn pack_plain(&self) -> Result<Plaintext> { - Plaintext::pack(self) - } - - fn pack_signed(&self, algorithm: SignatureAlgorithm, sender: &KeyPair) -> Result<Signed> { - Signed::pack(self, algorithm, sender) - } - - fn pack_encrypted( - &self, - algorithm: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result<Encrypted> { - Encrypted::pack(self, algorithm, recipients, sender) - } - - fn pack_signed_encrypted( - &self, - signature: SignatureAlgorithm, - encryption: EncryptionAlgorithm, - recipients: &[PublicKey], - sender: &KeyPair, - ) -> Result<Encrypted> { - Self::pack_signed(self, signature, sender) - .and_then(|signed| Encrypted::pack_signed(&signed, encryption, recipients, sender)) - } -} diff --git a/identity_comm/src/types.rs b/identity_comm/src/types.rs deleted file mode 100644 index 3ed8ccdba8..0000000000 --- a/identity_comm/src/types.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[doc(inline)] -pub use ::uuid::Uuid;