Skip to main content

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}