use std::{fmt, sync::Arc};
use iroh::{
endpoint::{
Accepting, Connection, ConnectionError, IncomingZeroRttConnection,
OutgoingZeroRttConnection, RecvStream, SendStream, VarInt, ZeroRttStatus,
},
protocol::{AcceptError, ProtocolHandler},
};
use n0_error::Result;
use n0_future::{TryFutureExt, future::Boxed as BoxFuture};
use portable_atomic::{AtomicU64, Ordering};
use tracing::{Instrument, debug, debug_span, warn};
use crate::{
RequestError, Service,
rpc::{Handler, IncomingRemoteConnection, ReadRequestError, RemoteConnection},
};
impl RemoteConnection for Connection {
fn clone_boxed(&self) -> Box<dyn RemoteConnection> {
Box::new(self.clone())
}
fn open_bi(&self) -> BoxFuture<std::result::Result<(SendStream, RecvStream), RequestError>> {
let conn = self.clone();
Box::pin(async move {
let (send, recv) = conn.open_bi().await?;
Ok((send, recv))
})
}
fn zero_rtt_rejected(&self) -> BoxFuture<bool> {
Box::pin(async { false })
}
}
impl RemoteConnection for OutgoingZeroRttConnection {
fn clone_boxed(&self) -> Box<dyn RemoteConnection> {
Box::new(self.clone())
}
fn open_bi(&self) -> BoxFuture<std::result::Result<(SendStream, RecvStream), RequestError>> {
let conn = self.clone();
Box::pin(async move {
let (send, recv) = conn.open_bi().await?;
Ok((send, recv))
})
}
fn zero_rtt_rejected(&self) -> BoxFuture<bool> {
let conn = self.clone();
Box::pin(async move {
match conn.handshake_completed().await {
Err(_) => true,
Ok(ZeroRttStatus::Accepted(_)) => false,
Ok(ZeroRttStatus::Rejected(_)) => true,
}
})
}
}
#[derive(Debug, Clone)]
pub struct IrohLazyRemoteConnection(Arc<IrohRemoteConnectionInner>);
#[derive(Debug)]
struct IrohRemoteConnectionInner {
endpoint: iroh::Endpoint,
addr: iroh::EndpointAddr,
connection: tokio::sync::Mutex<Option<Connection>>,
alpn: Vec<u8>,
}
impl IrohLazyRemoteConnection {
pub fn new(endpoint: iroh::Endpoint, addr: iroh::EndpointAddr, alpn: Vec<u8>) -> Self {
Self(Arc::new(IrohRemoteConnectionInner {
endpoint,
addr,
connection: Default::default(),
alpn,
}))
}
}
impl RemoteConnection for IrohLazyRemoteConnection {
fn clone_boxed(&self) -> Box<dyn RemoteConnection> {
Box::new(self.clone())
}
fn open_bi(&self) -> BoxFuture<std::result::Result<(SendStream, RecvStream), RequestError>> {
let this = self.0.clone();
Box::pin(async move {
let mut guard = this.connection.lock().await;
let pair = match guard.as_mut() {
Some(conn) => {
match conn.open_bi().await {
Ok(pair) => pair,
Err(_) => {
*guard = None;
connect_and_open_bi(&this.endpoint, &this.addr, &this.alpn, guard)
.await?
}
}
}
None => connect_and_open_bi(&this.endpoint, &this.addr, &this.alpn, guard).await?,
};
Ok(pair)
})
}
fn zero_rtt_rejected(&self) -> BoxFuture<bool> {
Box::pin(async { false })
}
}
async fn connect_and_open_bi(
endpoint: &iroh::Endpoint,
addr: &iroh::EndpointAddr,
alpn: &[u8],
mut guard: tokio::sync::MutexGuard<'_, Option<Connection>>,
) -> Result<(SendStream, RecvStream), RequestError> {
let conn = endpoint.connect(addr.clone(), alpn).await?;
let (send, recv) = conn.open_bi().await?;
*guard = Some(conn);
Ok((send, recv))
}
impl From<ReadRequestError> for AcceptError {
fn from(err: ReadRequestError) -> Self {
AcceptError::from_err(err)
}
}
pub struct IrohProtocol<S> {
handler: Handler<S>,
connection_id: AtomicU64,
}
impl<T> fmt::Debug for IrohProtocol<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RpcProtocol")
}
}
impl<S: Service> IrohProtocol<S> {
pub fn new(handler: impl Into<Handler<S>>) -> Self {
Self {
handler: handler.into(),
connection_id: Default::default(),
}
}
}
impl<S: Service> ProtocolHandler for IrohProtocol<S> {
async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
let connection_id = self.connection_id.fetch_add(1, Ordering::AcqRel);
let fut = self
.handler
.handle_connection(&connection)
.map_err(AcceptError::from_err);
let span = debug_span!("rpc", conn = connection_id);
span.in_scope(
|| debug!(remote = %connection.remote_id().fmt_short(), "connection accepted"),
);
fut.instrument(span).await
}
}
pub struct Iroh0RttProtocol<S> {
handler: Handler<S>,
connection_id: AtomicU64,
}
impl<T> fmt::Debug for Iroh0RttProtocol<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RpcProtocol")
}
}
impl<S: Service> Iroh0RttProtocol<S> {
pub fn new(handler: impl Into<Handler<S>>) -> Self {
Self {
handler: handler.into(),
connection_id: Default::default(),
}
}
}
impl<S: Service> ProtocolHandler for Iroh0RttProtocol<S> {
async fn on_accepting(&self, accepting: Accepting) -> Result<Connection, AcceptError> {
let zrtt_conn = accepting.into_0rtt();
let connection_id = self.connection_id.fetch_add(1, Ordering::AcqRel);
let span = debug_span!("rpc", conn = connection_id);
span.in_scope(|| match zrtt_conn.remote_id() {
Ok(remote) => debug!(remote = %remote.fmt_short(), "connection accepted"),
Err(_) => debug!("connection accepted"),
});
self.handler
.handle_connection(&zrtt_conn)
.map_err(AcceptError::from_err)
.instrument(span)
.await?;
let conn = zrtt_conn
.handshake_completed()
.await
.map_err(AcceptError::from)?;
Ok(conn)
}
async fn accept(&self, _connection: Connection) -> Result<(), AcceptError> {
Ok(())
}
}
impl crate::sealed::Sealed for IncomingZeroRttConnection {}
impl IncomingRemoteConnection for IncomingZeroRttConnection {
async fn accept_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
self.accept_bi().await
}
fn close(&self, error_code: VarInt, reason: &[u8]) {
self.close(error_code, reason)
}
}
impl crate::sealed::Sealed for Connection {}
impl IncomingRemoteConnection for Connection {
async fn accept_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
self.accept_bi().await
}
fn close(&self, error_code: VarInt, reason: &[u8]) {
self.close(error_code, reason)
}
}
pub async fn listen<S: Service>(endpoint: iroh::Endpoint, handler: impl Into<Handler<S>>) {
let handler = handler.into();
let mut connection_id = 0u64;
let mut tasks = n0_future::task::JoinSet::new();
loop {
let incoming = tokio::select! {
Some(res) = tasks.join_next(), if !tasks.is_empty() => {
res.expect("irpc connection task panicked");
continue;
}
incoming = endpoint.accept() => {
match incoming {
None => break,
Some(incoming) => incoming
}
}
};
let handler = handler.clone();
let fut = async move {
match incoming.await {
Ok(connection) => {
debug!(remote = %connection.remote_id().fmt_short(), "connection accepted");
match handler.handle_connection(&connection).await {
Err(err) => warn!("connection closed with error: {err:?}"),
Ok(()) => debug!("connection closed"),
}
}
Err(cause) => {
warn!("failed to accept connection: {cause:?}");
}
};
};
let span = debug_span!("rpc", conn = connection_id);
tasks.spawn(fut.instrument(span));
connection_id += 1;
}
}