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 private_key = p2panda_core::PrivateKey::new();
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/// .private_key(private_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)]
88pub struct Endpoint {
89 pub(super) args: IrohEndpointArgs,
90 pub(super) inner: Arc<RwLock<Inner>>,
91}
92
93pub(super) struct Inner {
94 pub(super) actor_ref: Option<ActorRef<ToIrohEndpoint>>,
95}
96
97impl Endpoint {
98 pub(crate) fn new(actor_ref: Option<ActorRef<ToIrohEndpoint>>, args: IrohEndpointArgs) -> Self {
99 Self {
100 args,
101 inner: Arc::new(RwLock::new(Inner { actor_ref })),
102 }
103 }
104
105 pub fn builder(address_book: AddressBook) -> Builder {
106 Builder::new(address_book)
107 }
108
109 /// Return the internal iroh endpoint instance.
110 pub async fn endpoint(&self) -> Result<iroh::Endpoint, EndpointError> {
111 let inner = self.inner.read().await;
112 let result = call!(
113 inner.actor_ref.as_ref().expect("actor spawned in builder"),
114 ToIrohEndpoint::Endpoint
115 )
116 .map_err(Box::new)?;
117 Ok(result)
118 }
119
120 pub fn network_id(&self) -> NetworkId {
121 self.args.0
122 }
123
124 pub fn node_id(&self) -> NodeId {
125 self.args.1.public_key()
126 }
127
128 /// Register protocol handler for a given ALPN (protocol identifier).
129 pub async fn accept<P: ProtocolHandler>(
130 &self,
131 protocol_id: impl AsRef<[u8]>,
132 protocol_handler: P,
133 ) -> Result<(), EndpointError> {
134 let protocol_id = protocol_id.as_ref().to_vec();
135 let inner = self.inner.read().await;
136 cast!(
137 inner.actor_ref.as_ref().expect("actor spawned in builder"),
138 ToIrohEndpoint::RegisterProtocol(protocol_id, Box::new(protocol_handler))
139 )
140 .map_err(Box::new)?;
141 Ok(())
142 }
143
144 /// Starts a connection attempt to a remote iroh endpoint and returns a future which can be
145 /// awaited for establishing the final connection.
146 ///
147 /// The ALPN byte string, or application-level protocol identifier, is also required. The
148 /// remote endpoint must support this alpn, otherwise the connection attempt will fail with an
149 /// error.
150 pub async fn connect(
151 &self,
152 node_id: NodeId,
153 protocol_id: impl AsRef<[u8]>,
154 ) -> Result<iroh::endpoint::Connection, EndpointError> {
155 let inner = self.inner.read().await;
156 let result = call!(
157 inner.actor_ref.as_ref().expect("actor spawned in builder"),
158 ToIrohEndpoint::Connect,
159 node_id,
160 protocol_id.as_ref().to_vec(),
161 None
162 )
163 .map_err(Box::new)??;
164 Ok(result)
165 }
166
167 pub async fn connect_with_config(
168 &self,
169 node_id: NodeId,
170 protocol_id: impl AsRef<[u8]>,
171 transport_config: Arc<iroh::endpoint::TransportConfig>,
172 ) -> Result<iroh::endpoint::Connection, EndpointError> {
173 let inner = self.inner.read().await;
174 let result = call!(
175 inner.actor_ref.as_ref().expect("actor spawned in builder"),
176 ToIrohEndpoint::Connect,
177 node_id,
178 protocol_id.as_ref().to_vec(),
179 Some(transport_config)
180 )
181 .map_err(Box::new)??;
182 Ok(result)
183 }
184}
185
186#[derive(Debug, Error)]
187pub enum EndpointError {
188 /// Spawning the internal actor failed.
189 #[error(transparent)]
190 ActorSpawn(#[from] ractor::SpawnErr),
191
192 /// Spawning the internal actor as a child actor of a supervisor failed.
193 #[cfg(feature = "supervisor")]
194 #[error(transparent)]
195 ActorLinkedSpawn(#[from] crate::supervisor::SupervisorError),
196
197 /// Messaging with internal actor via RPC failed.
198 #[error(transparent)]
199 ActorRpc(#[from] Box<ractor::RactorErr<ToIrohEndpoint>>),
200
201 #[error(transparent)]
202 Connect(#[from] ConnectError),
203}
204
205impl Drop for Inner {
206 fn drop(&mut self) {
207 if let Some(actor_ref) = self.actor_ref.take() {
208 actor_ref.stop(None);
209 }
210 }
211}