use std::{future::Future, sync::Arc};
use serde::{Deserialize, Serialize};
use smallvec::smallvec;
use snafu::Snafu;
use tokio::net::UnixStream;
use tracing::{Instrument, debug};
use crate::{
codec::{BoxReadStream, BoxWriteStream},
ipc::{
quic::{
IpcReadStream, IpcWriteStream,
connection::{IPC_ERROR_KIND, IPC_FRAME_TYPE, bridge_reader, bridge_writer},
},
transport::{FdRegistry, FdSender},
},
quic::{self, ConnectionError, DynLifecycle, GetStreamIdExt},
rpc::lifecycle::{ConnectionErrorLatch, HasLatch, LifecycleExt},
varint::VarInt,
webtransport::{self, Closed, OpenStreamError, WtLifecycleExt},
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Snafu)]
#[snafu(module(ipc_plumbing_error), visibility(pub))]
pub enum IpcPlumbingError {
#[snafu(transparent)]
Rpc { source: remoc::rtc::CallError },
#[snafu(transparent)]
Stream { source: quic::StreamError },
#[snafu(display("{message}"))]
Io { message: String },
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Snafu)]
#[snafu(module(ipc_wt_open_error), visibility(pub))]
pub enum IpcWtOpenError {
#[snafu(transparent)]
Stream { source: OpenStreamError },
#[snafu(transparent)]
Transport { source: IpcPlumbingError },
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Snafu)]
#[snafu(module(ipc_wt_accept_error), visibility(pub))]
pub enum IpcWtAcceptError {
#[snafu(display("webtransport session closed"))]
Closed,
#[snafu(transparent)]
Transport { source: IpcPlumbingError },
}
impl From<remoc::rtc::CallError> for IpcWtOpenError {
fn from(error: remoc::rtc::CallError) -> Self {
IpcPlumbingError::Rpc { source: error }.into()
}
}
impl From<remoc::rtc::CallError> for IpcWtAcceptError {
fn from(error: remoc::rtc::CallError) -> Self {
IpcPlumbingError::Rpc { source: error }.into()
}
}
impl From<Closed> for IpcWtAcceptError {
fn from(_: Closed) -> Self {
IpcWtAcceptError::Closed
}
}
#[remoc::rtc::remote]
pub trait IpcWtSession: Send + Sync {
async fn open_bi(&self) -> Result<(VarInt, VarInt), IpcWtOpenError>;
async fn open_uni(&self) -> Result<(VarInt, VarInt), IpcWtOpenError>;
async fn accept_bi(&self) -> Result<(VarInt, VarInt), IpcWtAcceptError>;
async fn accept_uni(&self) -> Result<(VarInt, VarInt), IpcWtAcceptError>;
}
fn ipc_open_io(err: impl std::fmt::Display, context: &str) -> IpcWtOpenError {
debug!(error = %err, context, "ipc wt session i/o error");
IpcPlumbingError::Io {
message: format!("{context}: {err}"),
}
.into()
}
fn ipc_accept_io(err: impl std::fmt::Display, context: &str) -> IpcWtAcceptError {
debug!(error = %err, context, "ipc wt session i/o error");
IpcPlumbingError::Io {
message: format!("{context}: {err}"),
}
.into()
}
fn ipc_connection_error(error: &IpcPlumbingError) -> ConnectionError {
ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: error.to_string().into(),
},
}
}
#[derive(Serialize, Deserialize)]
pub struct WtSessionBootstrap {
pub session_id: VarInt,
pub session: IpcWtSessionClient,
}
pub struct WtSessionAdapter {
session: Arc<webtransport::WebTransportSession>,
fd_sender: FdSender,
lifecycle: Arc<dyn DynLifecycle>,
}
impl WtSessionAdapter {
pub fn new(
session: Arc<webtransport::WebTransportSession>,
fd_sender: FdSender,
lifecycle: Arc<dyn DynLifecycle>,
) -> Self {
Self {
session,
fd_sender,
lifecycle,
}
}
}
impl IpcWtSession for WtSessionAdapter {
async fn open_bi(&self) -> Result<(VarInt, VarInt), IpcWtOpenError> {
let (mut reader, writer) = self.session.open_bi().await?;
let stream_id = GetStreamIdExt::stream_id(&mut reader)
.await
.map_err(IpcPlumbingError::from)?;
self.bridge_bi(reader, writer, stream_id)
}
async fn accept_bi(&self) -> Result<(VarInt, VarInt), IpcWtAcceptError> {
let (mut reader, writer) = self.session.accept_bi().await?;
let stream_id = GetStreamIdExt::stream_id(&mut reader)
.await
.map_err(IpcPlumbingError::from)?;
self.bridge_bi_accept(reader, writer, stream_id)
}
async fn open_uni(&self) -> Result<(VarInt, VarInt), IpcWtOpenError> {
let mut writer = self.session.open_uni().await?;
let stream_id = GetStreamIdExt::stream_id(&mut writer)
.await
.map_err(IpcPlumbingError::from)?;
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let (srv, cli) = UnixStream::pair().map_err(|e| ipc_open_io(e, "socketpair"))?;
let cli_std = cli.into_std().map_err(|e| ipc_open_io(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_std.into()])
.map_err(|e| ipc_open_io(e, "queue_fds"))?;
let pipe_r = IpcReadStream::new(stream_id, srv, lifecycle);
tokio::spawn(bridge_writer(pipe_r, writer).in_current_span());
Ok((fd_id, stream_id))
}
async fn accept_uni(&self) -> Result<(VarInt, VarInt), IpcWtAcceptError> {
let mut reader = self.session.accept_uni().await?;
let stream_id = GetStreamIdExt::stream_id(&mut reader)
.await
.map_err(IpcPlumbingError::from)?;
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let (srv, cli) = UnixStream::pair().map_err(|e| ipc_accept_io(e, "socketpair"))?;
let cli_std = cli.into_std().map_err(|e| ipc_accept_io(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_std.into()])
.map_err(|e| ipc_accept_io(e, "queue_fds"))?;
let pipe_w = IpcWriteStream::new(stream_id, srv, lifecycle);
tokio::spawn(bridge_reader(reader, pipe_w).in_current_span());
Ok((fd_id, stream_id))
}
}
impl WtSessionAdapter {
fn bridge_bi(
&self,
reader: BoxReadStream,
writer: BoxWriteStream,
stream_id: VarInt,
) -> Result<(VarInt, VarInt), IpcWtOpenError> {
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let (srv_a, cli_a) = UnixStream::pair().map_err(|e| ipc_open_io(e, "socketpair"))?;
let (srv_b, cli_b) = UnixStream::pair().map_err(|e| ipc_open_io(e, "socketpair"))?;
let cli_a_std = cli_a.into_std().map_err(|e| ipc_open_io(e, "into_std"))?;
let cli_b_std = cli_b.into_std().map_err(|e| ipc_open_io(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_a_std.into(), cli_b_std.into()])
.map_err(|e| ipc_open_io(e, "queue_fds"))?;
let pipe_w = IpcWriteStream::new(stream_id, srv_a, lifecycle.clone());
tokio::spawn(bridge_reader(reader, pipe_w).in_current_span());
let pipe_r = IpcReadStream::new(stream_id, srv_b, lifecycle);
tokio::spawn(bridge_writer(pipe_r, writer).in_current_span());
Ok((fd_id, stream_id))
}
fn bridge_bi_accept(
&self,
reader: BoxReadStream,
writer: BoxWriteStream,
stream_id: VarInt,
) -> Result<(VarInt, VarInt), IpcWtAcceptError> {
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let (srv_a, cli_a) = UnixStream::pair().map_err(|e| ipc_accept_io(e, "socketpair"))?;
let (srv_b, cli_b) = UnixStream::pair().map_err(|e| ipc_accept_io(e, "socketpair"))?;
let cli_a_std = cli_a.into_std().map_err(|e| ipc_accept_io(e, "into_std"))?;
let cli_b_std = cli_b.into_std().map_err(|e| ipc_accept_io(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_a_std.into(), cli_b_std.into()])
.map_err(|e| ipc_accept_io(e, "queue_fds"))?;
let pipe_w = IpcWriteStream::new(stream_id, srv_a, lifecycle.clone());
tokio::spawn(bridge_reader(reader, pipe_w).in_current_span());
let pipe_r = IpcReadStream::new(stream_id, srv_b, lifecycle);
tokio::spawn(bridge_writer(pipe_r, writer).in_current_span());
Ok((fd_id, stream_id))
}
}
struct IpcWtLifecycle {
parent: Arc<dyn DynLifecycle>,
latch: ConnectionErrorLatch,
}
impl HasLatch for IpcWtLifecycle {
fn latch(&self) -> &ConnectionErrorLatch {
&self.latch
}
}
impl quic::Lifecycle for IpcWtLifecycle {
fn close(&self, code: crate::error::Code, reason: std::borrow::Cow<'static, str>) {
DynLifecycle::close(self.parent.as_ref(), code, reason);
}
fn check(&self) -> Result<(), ConnectionError> {
self.check_with_probe(|| DynLifecycle::check(self.parent.as_ref()).err())
}
async fn closed(&self) -> ConnectionError {
self.resolve_closed(async { DynLifecycle::closed(self.parent.as_ref()).await })
.await
}
}
pub struct IpcWtSessionHandle {
session_id: VarInt,
rpc: IpcWtSessionClient,
fd_registry: FdRegistry,
lifecycle: Arc<IpcWtLifecycle>,
}
impl IpcWtSessionHandle {
pub fn new(
session_id: VarInt,
rpc: IpcWtSessionClient,
fd_registry: FdRegistry,
conn_lifecycle: Arc<dyn DynLifecycle>,
) -> Self {
let lifecycle = Arc::new(IpcWtLifecycle {
parent: conn_lifecycle,
latch: ConnectionErrorLatch::new(),
});
Self {
session_id,
rpc,
fd_registry,
lifecycle,
}
}
async fn guard_ipc_open<T>(
&self,
fut: impl Future<Output = Result<T, IpcWtOpenError>>,
) -> Result<T, OpenStreamError> {
self.lifecycle
.guard_open_with(fut, |e| match e {
IpcWtOpenError::Stream { source } => source,
IpcWtOpenError::Transport { source } => OpenStreamError::Open {
source: ipc_connection_error(&source),
},
})
.await
}
async fn guard_ipc_accept<T>(
&self,
fut: impl Future<Output = Result<T, IpcWtAcceptError>>,
) -> Result<T, Closed> {
self.lifecycle
.guard_accept_err(fut, |e| match e {
IpcWtAcceptError::Closed => None,
IpcWtAcceptError::Transport { source } => Some(ipc_connection_error(&source)),
})
.await
}
fn latch_open_transport(&self, err: impl std::error::Error, context: &str) -> OpenStreamError {
debug!(error = %snafu::Report::from_error(&err), context, "ipc wt session error");
let message = format!("ipc wt: {context}: {err}");
let source = self.lifecycle.latch().latch_with(|| {
let plumbing = IpcPlumbingError::Io { message };
ipc_connection_error(&plumbing)
});
OpenStreamError::Open { source }
}
fn latch_accept_transport(&self, err: impl std::error::Error, context: &str) -> Closed {
debug!(error = %snafu::Report::from_error(&err), context, "ipc wt session error");
let message = format!("ipc wt: {context}: {err}");
let _ = self.lifecycle.latch().latch_with(|| {
let plumbing = IpcPlumbingError::Io { message };
ipc_connection_error(&plumbing)
});
Closed
}
}
impl webtransport::Session for IpcWtSessionHandle {
type StreamReader = IpcReadStream;
type StreamWriter = IpcWriteStream;
fn session_id(&self) -> VarInt {
self.session_id
}
async fn open_bi(&self) -> Result<(IpcReadStream, IpcWriteStream), OpenStreamError> {
let (fd_id, stream_id) = self
.guard_ipc_open(IpcWtSession::open_bi(&self.rpc))
.await?;
self.fds_to_bi(fd_id, stream_id).await
}
async fn accept_bi(&self) -> Result<(IpcReadStream, IpcWriteStream), Closed> {
let (fd_id, stream_id) = self
.guard_ipc_accept(IpcWtSession::accept_bi(&self.rpc))
.await?;
self.fds_to_bi_accept(fd_id, stream_id).await
}
async fn open_uni(&self) -> Result<IpcWriteStream, OpenStreamError> {
let (fd_id, stream_id) = self
.guard_ipc_open(IpcWtSession::open_uni(&self.rpc))
.await?;
self.fds_to_uni_writer(fd_id, stream_id).await
}
async fn accept_uni(&self) -> Result<IpcReadStream, Closed> {
let (fd_id, stream_id) = self
.guard_ipc_accept(IpcWtSession::accept_uni(&self.rpc))
.await?;
self.fds_to_uni_reader(fd_id, stream_id).await
}
}
impl IpcWtSessionHandle {
async fn fds_to_bi(
&self,
fd_id: VarInt,
stream_id: VarInt,
) -> Result<(IpcReadStream, IpcWriteStream), OpenStreamError> {
let fds = self
.fd_registry
.wait_fds(fd_id)
.await
.map_err(|e| self.latch_open_transport(e, "wait_fds"))?;
if fds.len() != 2 {
return Err(self.latch_open_transport(
FdCountError {
expected: 2,
got: fds.len(),
},
"fd count",
));
}
let mut fds = fds.into_iter();
let fd_a = fds.next().unwrap();
let fd_b = fds.next().unwrap();
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let sock_a = UnixStream::from_std(std::os::unix::net::UnixStream::from(fd_a))
.map_err(|e| self.latch_open_transport(e, "UnixStream::from_std"))?;
let reader = IpcReadStream::new(stream_id, sock_a, lifecycle.clone());
let sock_b = UnixStream::from_std(std::os::unix::net::UnixStream::from(fd_b))
.map_err(|e| self.latch_open_transport(e, "UnixStream::from_std"))?;
let writer = IpcWriteStream::new(stream_id, sock_b, lifecycle);
Ok((reader, writer))
}
async fn fds_to_bi_accept(
&self,
fd_id: VarInt,
stream_id: VarInt,
) -> Result<(IpcReadStream, IpcWriteStream), Closed> {
let fds = self
.fd_registry
.wait_fds(fd_id)
.await
.map_err(|e| self.latch_accept_transport(e, "wait_fds"))?;
if fds.len() != 2 {
return Err(self.latch_accept_transport(
FdCountError {
expected: 2,
got: fds.len(),
},
"fd count",
));
}
let mut fds = fds.into_iter();
let fd_a = fds.next().unwrap();
let fd_b = fds.next().unwrap();
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let sock_a = UnixStream::from_std(std::os::unix::net::UnixStream::from(fd_a))
.map_err(|e| self.latch_accept_transport(e, "UnixStream::from_std"))?;
let reader = IpcReadStream::new(stream_id, sock_a, lifecycle.clone());
let sock_b = UnixStream::from_std(std::os::unix::net::UnixStream::from(fd_b))
.map_err(|e| self.latch_accept_transport(e, "UnixStream::from_std"))?;
let writer = IpcWriteStream::new(stream_id, sock_b, lifecycle);
Ok((reader, writer))
}
async fn fds_to_uni_writer(
&self,
fd_id: VarInt,
stream_id: VarInt,
) -> Result<IpcWriteStream, OpenStreamError> {
let fds = self
.fd_registry
.wait_fds(fd_id)
.await
.map_err(|e| self.latch_open_transport(e, "wait_fds"))?;
if fds.len() != 1 {
return Err(self.latch_open_transport(
FdCountError {
expected: 1,
got: fds.len(),
},
"fd count",
));
}
let fd = fds.into_iter().next().unwrap();
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let sock = UnixStream::from_std(std::os::unix::net::UnixStream::from(fd))
.map_err(|e| self.latch_open_transport(e, "UnixStream::from_std"))?;
Ok(IpcWriteStream::new(stream_id, sock, lifecycle))
}
async fn fds_to_uni_reader(
&self,
fd_id: VarInt,
stream_id: VarInt,
) -> Result<IpcReadStream, Closed> {
let fds = self
.fd_registry
.wait_fds(fd_id)
.await
.map_err(|e| self.latch_accept_transport(e, "wait_fds"))?;
if fds.len() != 1 {
return Err(self.latch_accept_transport(
FdCountError {
expected: 1,
got: fds.len(),
},
"fd count",
));
}
let fd = fds.into_iter().next().unwrap();
let lifecycle: Arc<dyn DynLifecycle> = self.lifecycle.clone();
let sock = UnixStream::from_std(std::os::unix::net::UnixStream::from(fd))
.map_err(|e| self.latch_accept_transport(e, "UnixStream::from_std"))?;
Ok(IpcReadStream::new(stream_id, sock, lifecycle))
}
}
#[derive(Debug)]
struct FdCountError {
expected: usize,
got: usize,
}
impl std::fmt::Display for FdCountError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "expected {} fds, got {}", self.expected, self.got)
}
}
impl std::error::Error for FdCountError {}
impl IpcWtSessionClient {
pub fn into_handle(
self,
session_id: VarInt,
fd_registry: FdRegistry,
conn_lifecycle: Arc<dyn DynLifecycle>,
) -> IpcWtSessionHandle {
IpcWtSessionHandle::new(session_id, self, fd_registry, conn_lifecycle)
}
}