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}