p2panda_net/iroh_endpoint/api.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::sync::Arc;
4
5use iroh::protocol::ProtocolHandler;
6use ractor::{ActorRef, call, cast};
7use thiserror::Error;
8use tokio::sync::RwLock;
9
10use crate::address_book::AddressBook;
11use crate::iroh_endpoint::Builder;
12use crate::iroh_endpoint::actors::{ConnectError, IrohEndpointArgs, ToIrohEndpoint};
13use crate::{NetworkId, NodeId};
14
15/// Establish encrypted, direct connections over Internet Protocol with QUIC.
16///
17/// ## Example
18///
19/// ```rust
20/// # use std::error::Error;
21/// #
22/// # #[tokio::main]
23/// # async fn main() -> Result<(), Box<dyn Error>> {
24/// # use p2panda_net::{AddressBook, Endpoint};
25/// #
26/// # let address_book = AddressBook::builder().spawn().await?;
27/// #
28/// // Generate Ed25519 key which will be used to authenticate node.
29/// let signing_key = p2panda_core::SigningKey::generate();
30///
31/// // Use this iroh relay as a "home relay".
32/// let relay_url = "https://my.relay.org".parse().expect("valid relay url");
33///
34/// // Initialise endpoint with custom network identifier.
35/// let endpoint = Endpoint::builder(address_book)
36/// .network_id([1; 32])
37/// .signing_key(signing_key)
38/// .relay_url(relay_url)
39/// .spawn()
40/// .await?;
41///
42/// // Other nodes can use this id now to establish a direct connection.
43/// println!("my node id: {}", endpoint.node_id());
44/// #
45/// # Ok(())
46/// # }
47/// ```
48///
49/// ## iroh
50///
51/// Most of the lower-level Internet Protocol networking is made possible by the work of [iroh]
52/// utilising well-established and known standards, like QUIC for transport, (self-certified) TLS
53/// 1.3 for transport encryption, QUIC Address Discovery (QAD) for STUN, TURN servers for relayed
54/// fallbacks.
55///
56/// ## Network identifier
57///
58/// Use [`NetworkId`](crate::NetworkId) to actively partition the network. The identifier serves as
59/// a shared secret; nodes will not be able to establish connections if their identifiers differ.
60///
61/// ## Custom Protocol Handlers
62///
63/// Register your own custom protocols using the [`Endpoint::accept`] method.
64///
65/// ## Relays
66///
67/// Use [`Builder::relay_url`] to register one or more iroh relay urls which are required to aid
68/// in establishing a direct connection.
69///
70/// ## Resolving transport infos
71///
72/// To connect to any endpoint by it's node id / public key we first need to resolve it to the
73/// associated addressing information (relay url, IPv4 and IPv6 addresses) before attempting to
74/// establish a direct connection.
75///
76/// `Endpoint` takes the [`AddressBook`](crate::AddressBook) as a dependency which provides it with
77/// the resolved transport information.
78///
79/// The address book itself is populated with resolved transport information by two services:
80///
81/// 1. [`MdnsDiscovery`](crate::MdnsDiscovery): Resolve addresses of nearby devices on the
82/// local-area network.
83/// 2. [`Discovery`](crate::Discovery): Resolve addresses using random-walk strategy, exploring the
84/// network.
85///
86/// [iroh]: https://www.iroh.computer/
87#[derive(Clone, Debug)]
88pub struct Endpoint {
89 pub(super) args: IrohEndpointArgs,
90 pub(super) inner: Arc<RwLock<Inner>>,
91}
92
93#[derive(Debug)]
94pub(super) struct Inner {
95 pub(super) actor_ref: Option<ActorRef<ToIrohEndpoint>>,
96}
97
98impl Endpoint {
99 pub(crate) fn new(actor_ref: Option<ActorRef<ToIrohEndpoint>>, args: IrohEndpointArgs) -> Self {
100 Self {
101 args,
102 inner: Arc::new(RwLock::new(Inner { actor_ref })),
103 }
104 }
105
106 pub fn builder(address_book: AddressBook) -> Builder {
107 Builder::new(address_book)
108 }
109
110 /// Return the internal iroh endpoint instance.
111 pub async fn endpoint(&self) -> Result<iroh::Endpoint, EndpointError> {
112 let inner = self.inner.read().await;
113 let result = call!(
114 inner.actor_ref.as_ref().expect("actor spawned in builder"),
115 ToIrohEndpoint::Endpoint
116 )
117 .map_err(Box::new)?;
118 Ok(result)
119 }
120
121 pub fn network_id(&self) -> NetworkId {
122 self.args.0
123 }
124
125 pub fn node_id(&self) -> NodeId {
126 self.args.1.verifying_key()
127 }
128
129 /// Register protocol handler for a given ALPN (protocol identifier).
130 pub async fn accept<P: ProtocolHandler>(
131 &self,
132 protocol_id: impl AsRef<[u8]>,
133 protocol_handler: P,
134 ) -> Result<(), EndpointError> {
135 let protocol_id = protocol_id.as_ref().to_vec();
136 let inner = self.inner.read().await;
137 cast!(
138 inner.actor_ref.as_ref().expect("actor spawned in builder"),
139 ToIrohEndpoint::RegisterProtocol(protocol_id, Box::new(protocol_handler))
140 )
141 .map_err(Box::new)?;
142 Ok(())
143 }
144
145 /// Starts a connection attempt to a remote iroh endpoint and returns a future which can be
146 /// awaited for establishing the final connection.
147 ///
148 /// The ALPN byte string, or application-level protocol identifier, is also required. The
149 /// remote endpoint must support this alpn, otherwise the connection attempt will fail with an
150 /// error.
151 pub async fn connect(
152 &self,
153 node_id: NodeId,
154 protocol_id: impl AsRef<[u8]>,
155 ) -> Result<iroh::endpoint::Connection, EndpointError> {
156 let inner = self.inner.read().await;
157 let result = call!(
158 inner.actor_ref.as_ref().expect("actor spawned in builder"),
159 ToIrohEndpoint::Connect,
160 node_id,
161 protocol_id.as_ref().to_vec(),
162 None
163 )
164 .map_err(Box::new)??;
165 Ok(result)
166 }
167
168 pub async fn connect_with_config(
169 &self,
170 node_id: NodeId,
171 protocol_id: impl AsRef<[u8]>,
172 quic_transport_config: iroh::endpoint::QuicTransportConfig,
173 ) -> Result<iroh::endpoint::Connection, EndpointError> {
174 let inner = self.inner.read().await;
175 let result = call!(
176 inner.actor_ref.as_ref().expect("actor spawned in builder"),
177 ToIrohEndpoint::Connect,
178 node_id,
179 protocol_id.as_ref().to_vec(),
180 Some(quic_transport_config)
181 )
182 .map_err(Box::new)??;
183 Ok(result)
184 }
185}
186
187#[derive(Debug, Error)]
188pub enum EndpointError {
189 /// Spawning the internal actor failed.
190 #[error(transparent)]
191 ActorSpawn(#[from] ractor::SpawnErr),
192
193 /// Spawning the internal actor as a child actor of a supervisor failed.
194 #[cfg(feature = "supervisor")]
195 #[error(transparent)]
196 ActorLinkedSpawn(#[from] crate::supervisor::SupervisorError),
197
198 /// Messaging with internal actor via RPC failed.
199 #[error(transparent)]
200 ActorRpc(#[from] Box<ractor::RactorErr<ToIrohEndpoint>>),
201
202 #[error(transparent)]
203 Connect(#[from] ConnectError),
204}
205
206impl Drop for Inner {
207 fn drop(&mut self) {
208 if let Some(actor_ref) = self.actor_ref.take() {
209 actor_ref.stop(None);
210 }
211 }
212}