Distributed Erlang over iroh P2P + TLS, with a bot powers SDK
0

Configure Feed

Select the types of activity you want to include in your feed.

erl-iroh / src / transport / iroh.rs
3.7 kB 105 lines
1//! iroh P2P transport for Erlang distribution. 2//! 3//! Connections are QUIC with TLS 1.3 identity (ed25519 endpoint keys). The 4//! Erlang cookie handshake still runs on top for BEAM-compatible auth. 5 6use crate::error::{Error, Result}; 7use crate::stream::DistStream; 8use crate::ALPN; 9use iroh::endpoint::presets; 10use iroh::{Endpoint, EndpointAddr, EndpointId, SecretKey}; 11use std::str::FromStr; 12use std::sync::Arc; 13use tracing::{debug, info}; 14 15/// Manages an iroh [`Endpoint`] that speaks the `erl-iroh/1` ALPN. 16#[derive(Clone, Debug)] 17pub struct IrohTransport { 18 endpoint: Endpoint, 19} 20 21impl IrohTransport { 22 /// Bind a new endpoint with a random secret key (default N0 relays). 23 pub async fn bind() -> Result<Self> { 24 Self::bind_with_key(SecretKey::generate()).await 25 } 26 27 /// Bind with an explicit secret key (stable identity across restarts). 28 pub async fn bind_with_key(secret: SecretKey) -> Result<Self> { 29 let endpoint = Endpoint::builder(presets::N0) 30 .secret_key(secret) 31 .alpns(vec![ALPN.to_vec()]) 32 .bind() 33 .await 34 .map_err(|e| Error::iroh(e))?; 35 info!(id = %endpoint.id(), "iroh endpoint bound"); 36 Ok(Self { endpoint }) 37 } 38 39 /// This endpoint's public id (share this so peers can dial you). 40 pub fn endpoint_id(&self) -> EndpointId { 41 self.endpoint.id() 42 } 43 44 /// Full addressing info (id + relay / direct addrs). 45 pub fn endpoint_addr(&self) -> EndpointAddr { 46 self.endpoint.addr() 47 } 48 49 /// Underlying iroh endpoint (for advanced use). 50 pub fn endpoint(&self) -> &Endpoint { 51 &self.endpoint 52 } 53 54 /// Dial a peer by endpoint id string (base32) and open a dist stream. 55 pub async fn connect(&self, peer: &str) -> Result<DistStream> { 56 let id = EndpointId::from_str(peer).map_err(|e| Error::InvalidName(e.to_string()))?; 57 self.connect_id(id).await 58 } 59 60 /// Dial a peer by [`EndpointId`]. 61 pub async fn connect_id(&self, id: EndpointId) -> Result<DistStream> { 62 debug!(%id, "connecting over iroh"); 63 let conn = self 64 .endpoint 65 .connect(id, ALPN) 66 .await 67 .map_err(|e| Error::iroh(e))?; 68 let (send, recv) = conn.open_bi().await.map_err(|e| Error::iroh(e))?; 69 Ok(DistStream::from_split(recv, send)) 70 } 71 72 /// Dial with a full [`EndpointAddr`] (id + relay/direct hints). 73 pub async fn connect_addr(&self, addr: EndpointAddr) -> Result<DistStream> { 74 debug!(id = %addr.id, "connecting over iroh (with addr hints)"); 75 let conn = self 76 .endpoint 77 .connect(addr, ALPN) 78 .await 79 .map_err(|e| Error::iroh(e))?; 80 let (send, recv) = conn.open_bi().await.map_err(|e| Error::iroh(e))?; 81 Ok(DistStream::from_split(recv, send)) 82 } 83 84 /// Accept one inbound connection and return the dist stream + remote id. 85 pub async fn accept(&self) -> Result<(DistStream, EndpointId)> { 86 let incoming = self 87 .endpoint 88 .accept() 89 .await 90 .ok_or_else(|| Error::iroh("endpoint closed"))?; 91 let conn = incoming.await.map_err(|e| Error::iroh(e))?; 92 let remote = conn.remote_id(); 93 let (send, recv) = conn.accept_bi().await.map_err(|e| Error::iroh(e))?; 94 debug!(%remote, "accepted iroh connection"); 95 Ok((DistStream::from_split(recv, send), remote)) 96 } 97 98 /// Gracefully close the endpoint. 99 pub async fn close(self) { 100 self.endpoint.close().await; 101 } 102} 103 104/// Shared handle used by accept loops. 105pub type SharedIroh = Arc<IrohTransport>;