use std::{borrow::Cow, io, sync::Arc};
use futures::{SinkExt, StreamExt};
use remoc::prelude::ServerShared;
use serde::{Deserialize, Serialize};
use smallvec::smallvec;
use tokio::net::UnixStream;
use tracing::{Instrument, debug};
use crate::{
error::Code,
ipc::{
error::{IpcAcceptError, IpcOpenError, IpcPlumbingError},
quic::stream::{IpcBiHandle, IpcReadStream, IpcUniHandle, IpcWriteStream},
transport::{FdRegistry, FdSender},
},
quic::{
self, ConnectionError, DynLifecycle, GetStreamIdExt, ManageStream, ReadStream, StreamError,
WriteStream,
},
rpc::{
lifecycle::{ConnectionErrorLatch, HasLatch, LifecycleExt},
quic::{
CachedLocalAgent, CachedRemoteAgent, LocalAgentClient, LocalAgentServerShared,
RemoteAgentClient, RemoteAgentServerShared,
},
},
util::deferred::Resolved,
varint::VarInt,
};
#[remoc::rtc::remote]
pub trait IpcConnection: Send + Sync {
async fn open_bi(&self) -> Result<Resolved<IpcBiHandle, StreamError>, IpcOpenError>;
async fn accept_bi(&self) -> Result<Resolved<IpcBiHandle, StreamError>, IpcAcceptError>;
async fn open_uni(&self) -> Result<Resolved<IpcUniHandle, StreamError>, IpcOpenError>;
async fn accept_uni(&self) -> Result<Resolved<IpcUniHandle, StreamError>, IpcAcceptError>;
async fn local_agent(&self) -> Result<Option<LocalAgentClient>, ConnectionError>;
async fn remote_agent(&self) -> Result<Option<RemoteAgentClient>, ConnectionError>;
async fn close(&self, code: Code, reason: Cow<'static, str>) -> Result<(), ConnectionError>;
async fn closed(&self) -> Result<ConnectionError, ConnectionError>;
}
#[derive(Serialize, Deserialize)]
pub struct ConnectionBootstrap {
pub connection: IpcConnectionClient,
}
pub async fn bridge_reader(
mut quic_reader: impl ReadStream + Unpin,
mut pipe_writer: IpcWriteStream,
) {
while let Some(Ok(chunk)) = quic_reader.next().await {
if pipe_writer.send(chunk).await.is_err() {
break;
}
}
let _ = pipe_writer.close().await;
}
pub async fn bridge_writer(
mut pipe_reader: IpcReadStream,
mut quic_writer: impl WriteStream + Unpin,
) {
while let Some(Ok(chunk)) = pipe_reader.next().await {
if quic_writer.send(chunk).await.is_err() {
break;
}
}
let _ = quic_writer.close().await;
}
pub struct ConnectionAdapter<M> {
inner: Arc<M>,
fd_sender: FdSender,
}
impl<M> ConnectionAdapter<M> {
pub fn new(inner: Arc<M>, fd_sender: FdSender) -> Self {
Self { inner, fd_sender }
}
}
impl<M> IpcConnection for ConnectionAdapter<M>
where
M: ManageStream
+ quic::Lifecycle
+ quic::WithLocalAgent
+ quic::WithRemoteAgent
+ Send
+ Sync
+ 'static,
M::StreamReader: Unpin + 'static,
M::StreamWriter: Unpin + 'static,
M::LocalAgent: Send + Sync,
M::RemoteAgent: Send + Sync,
{
async fn open_bi(&self) -> Result<Resolved<IpcBiHandle, StreamError>, IpcOpenError> {
self.open_bi_impl().await
}
async fn accept_bi(&self) -> Result<Resolved<IpcBiHandle, StreamError>, IpcAcceptError> {
self.accept_bi_impl().await
}
async fn open_uni(&self) -> Result<Resolved<IpcUniHandle, StreamError>, IpcOpenError> {
self.open_uni_impl().await
}
async fn accept_uni(&self) -> Result<Resolved<IpcUniHandle, StreamError>, IpcAcceptError> {
self.accept_uni_impl().await
}
async fn local_agent(&self) -> Result<Option<LocalAgentClient>, ConnectionError> {
match quic::WithLocalAgent::local_agent(self.inner.as_ref()).await? {
Some(agent) => {
let (server, client) = LocalAgentServerShared::new(Arc::new(agent), 1);
tokio::spawn(
(async move {
let _ = server.serve(true).await;
})
.in_current_span(),
);
Ok(Some(client))
}
None => Ok(None),
}
}
async fn remote_agent(&self) -> Result<Option<RemoteAgentClient>, ConnectionError> {
match quic::WithRemoteAgent::remote_agent(self.inner.as_ref()).await? {
Some(agent) => {
let (server, client) = RemoteAgentServerShared::new(Arc::new(agent), 1);
tokio::spawn(
(async move {
let _ = server.serve(true).await;
})
.in_current_span(),
);
Ok(Some(client))
}
None => Ok(None),
}
}
async fn close(&self, code: Code, reason: Cow<'static, str>) -> Result<(), ConnectionError> {
quic::Lifecycle::close(self.inner.as_ref(), code, reason);
Ok(())
}
async fn closed(&self) -> Result<ConnectionError, ConnectionError> {
Ok(quic::Lifecycle::closed(self.inner.as_ref()).await)
}
}
impl<M> ConnectionAdapter<M>
where
M: ManageStream
+ quic::Lifecycle
+ quic::WithLocalAgent
+ quic::WithRemoteAgent
+ Send
+ Sync
+ 'static,
M::StreamReader: Unpin + 'static,
M::StreamWriter: Unpin + 'static,
M::LocalAgent: Send + Sync,
M::RemoteAgent: Send + Sync,
{
async fn open_bi_impl(&self) -> Result<Resolved<IpcBiHandle, StreamError>, IpcOpenError> {
let (mut reader, writer) = ManageStream::open_bi(self.inner.as_ref())
.await
.map_err(|e| IpcOpenError::Connection { source: e })?;
let stream_id = match reader.stream_id().await {
Ok(id) => id,
Err(stream_err) => return Ok(Resolved::err(stream_err)),
};
self.bridge_bi(reader, writer, stream_id)
.map(Resolved::ok)
.map_err(IpcOpenError::from)
}
async fn accept_bi_impl(&self) -> Result<Resolved<IpcBiHandle, StreamError>, IpcAcceptError> {
let (mut reader, writer) = ManageStream::accept_bi(self.inner.as_ref())
.await
.map_err(|e| IpcAcceptError::Connection { source: e })?;
let stream_id = match reader.stream_id().await {
Ok(id) => id,
Err(stream_err) => return Ok(Resolved::err(stream_err)),
};
self.bridge_bi(reader, writer, stream_id)
.map(Resolved::ok)
.map_err(IpcAcceptError::from)
}
fn bridge_bi(
&self,
reader: M::StreamReader,
writer: M::StreamWriter,
stream_id: VarInt,
) -> Result<IpcBiHandle, IpcPlumbingError> {
let lifecycle: Arc<dyn DynLifecycle> = self.inner.clone();
let (srv_a, cli_a) = UnixStream::pair().map_err(|e| ipc_io_plumbing(e, "socketpair"))?;
let (srv_b, cli_b) = UnixStream::pair().map_err(|e| ipc_io_plumbing(e, "socketpair"))?;
let cli_a_std = cli_a
.into_std()
.map_err(|e| ipc_io_plumbing(e, "into_std"))?;
let cli_b_std = cli_b
.into_std()
.map_err(|e| ipc_io_plumbing(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_a_std.into(), cli_b_std.into()])
.map_err(|e| ipc_io_plumbing(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(IpcBiHandle { fd_id, stream_id })
}
async fn open_uni_impl(&self) -> Result<Resolved<IpcUniHandle, StreamError>, IpcOpenError> {
let mut writer = ManageStream::open_uni(self.inner.as_ref())
.await
.map_err(|e| IpcOpenError::Connection { source: e })?;
let stream_id = match writer.stream_id().await {
Ok(id) => id,
Err(stream_err) => return Ok(Resolved::err(stream_err)),
};
let lifecycle: Arc<dyn DynLifecycle> = self.inner.clone();
let (srv, cli) = UnixStream::pair().map_err(|e| ipc_io_plumbing(e, "socketpair"))?;
let cli_std = cli.into_std().map_err(|e| ipc_io_plumbing(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_std.into()])
.map_err(|e| ipc_io_plumbing(e, "queue_fds"))?;
let pipe_r = IpcReadStream::new(stream_id, srv, lifecycle);
tokio::spawn(bridge_writer(pipe_r, writer).in_current_span());
Ok(Resolved::ok(IpcUniHandle { fd_id, stream_id }))
}
async fn accept_uni_impl(&self) -> Result<Resolved<IpcUniHandle, StreamError>, IpcAcceptError> {
let mut reader = ManageStream::accept_uni(self.inner.as_ref())
.await
.map_err(|e| IpcAcceptError::Connection { source: e })?;
let stream_id = match reader.stream_id().await {
Ok(id) => id,
Err(stream_err) => return Ok(Resolved::err(stream_err)),
};
let lifecycle: Arc<dyn DynLifecycle> = self.inner.clone();
let (srv, cli) = UnixStream::pair().map_err(|e| ipc_io_plumbing(e, "socketpair"))?;
let cli_std = cli.into_std().map_err(|e| ipc_io_plumbing(e, "into_std"))?;
let fd_id = self
.fd_sender
.queue_fds(smallvec![cli_std.into()])
.map_err(|e| ipc_io_plumbing(e, "queue_fds"))?;
let pipe_w = IpcWriteStream::new(stream_id, srv, lifecycle);
tokio::spawn(bridge_reader(reader, pipe_w).in_current_span());
Ok(Resolved::ok(IpcUniHandle { fd_id, stream_id }))
}
}
pub struct IpcConnectionHandle {
rpc: IpcConnectionClient,
fd_registry: FdRegistry,
_conn_fd_sender: FdSender,
lifecycle: Arc<IpcLifecycle>,
}
struct IpcLifecycle {
connection: IpcConnectionClient,
latch: ConnectionErrorLatch,
}
impl HasLatch for IpcLifecycle {
fn latch(&self) -> &ConnectionErrorLatch {
&self.latch
}
}
impl quic::Lifecycle for IpcLifecycle {
fn close(&self, code: Code, reason: Cow<'static, str>) {
let rpc = self.connection.clone();
tokio::spawn(
async move {
let _ = IpcConnection::close(&rpc, code, reason).await;
}
.in_current_span(),
);
}
fn check(&self) -> Result<(), ConnectionError> {
self.check_with_probe(|| {
remoc::rtc::Client::is_closed(&self.connection)
.then(IpcConnectionHandle::ipc_channel_error)
})
}
async fn closed(&self) -> ConnectionError {
self.resolve_closed(async {
IpcConnection::closed(&self.connection)
.await
.unwrap_or_else(|_| IpcConnectionHandle::ipc_channel_error())
})
.await
}
}
impl IpcConnectionHandle {
pub fn new(
rpc: IpcConnectionClient,
fd_registry: FdRegistry,
conn_fd_sender: FdSender,
) -> Self {
let lifecycle = Arc::new(IpcLifecycle {
connection: rpc.clone(),
latch: ConnectionErrorLatch::new(),
});
Self {
rpc,
fd_registry,
_conn_fd_sender: conn_fd_sender,
lifecycle,
}
}
fn ipc_channel_error() -> ConnectionError {
quic::ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_CHANNEL_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: "ipc connection channel closed".into(),
},
}
}
}
impl quic::ManageStream for IpcConnectionHandle {
type StreamReader = Resolved<IpcReadStream, StreamError>;
type StreamWriter = Resolved<IpcWriteStream, StreamError>;
async fn open_bi(&self) -> Result<(Self::StreamReader, Self::StreamWriter), ConnectionError> {
let resolved = self
.lifecycle
.guard_with(IpcConnection::open_bi(&self.rpc), map_open_err)
.await?;
match resolved {
Resolved::Value { value: handle } => {
let (r, w) = self.fds_to_bi(handle).await?;
Ok((Resolved::ok(r), Resolved::ok(w)))
}
Resolved::Error { error } => Ok((Resolved::err(error.clone()), Resolved::err(error))),
}
}
async fn accept_bi(&self) -> Result<(Self::StreamReader, Self::StreamWriter), ConnectionError> {
let resolved = self
.lifecycle
.guard_with(IpcConnection::accept_bi(&self.rpc), map_accept_err)
.await?;
match resolved {
Resolved::Value { value: handle } => {
let (r, w) = self.fds_to_bi(handle).await?;
Ok((Resolved::ok(r), Resolved::ok(w)))
}
Resolved::Error { error } => Ok((Resolved::err(error.clone()), Resolved::err(error))),
}
}
async fn open_uni(&self) -> Result<Self::StreamWriter, ConnectionError> {
let resolved = self
.lifecycle
.guard_with(IpcConnection::open_uni(&self.rpc), map_open_err)
.await?;
match resolved {
Resolved::Value { value: handle } => {
let w = self.fds_to_uni_writer(handle).await?;
Ok(Resolved::ok(w))
}
Resolved::Error { error } => Ok(Resolved::err(error)),
}
}
async fn accept_uni(&self) -> Result<Self::StreamReader, ConnectionError> {
let resolved = self
.lifecycle
.guard_with(IpcConnection::accept_uni(&self.rpc), map_accept_err)
.await?;
match resolved {
Resolved::Value { value: handle } => {
let r = self.fds_to_uni_reader(handle).await?;
Ok(Resolved::ok(r))
}
Resolved::Error { error } => Ok(Resolved::err(error)),
}
}
}
impl IpcConnectionHandle {
async fn fds_to_bi(
&self,
handle: IpcBiHandle,
) -> Result<(IpcReadStream, IpcWriteStream), ConnectionError> {
let IpcBiHandle { fd_id, stream_id } = handle;
let fds = self
.lifecycle
.guard_with(self.fd_registry.wait_fds(fd_id), |e| {
ipc_transport_error(e, "wait_fds")
})
.await?;
self.lifecycle.guard_sync(|| {
if fds.len() != 2 {
return Err(ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: format!("expected 2 fds for bidi stream, got {}", fds.len()).into(),
},
});
}
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| ipc_io_error(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| ipc_io_error(e, "UnixStream::from_std"))?;
let writer = IpcWriteStream::new(stream_id, sock_b, lifecycle);
Ok((reader, writer))
})
}
async fn fds_to_uni_writer(
&self,
handle: IpcUniHandle,
) -> Result<IpcWriteStream, ConnectionError> {
let IpcUniHandle { fd_id, stream_id } = handle;
let fds = self
.lifecycle
.guard_with(self.fd_registry.wait_fds(fd_id), |e| {
ipc_transport_error(e, "wait_fds")
})
.await?;
self.lifecycle.guard_sync(|| {
if fds.len() != 1 {
return Err(ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: format!("expected 1 fd for uni stream, got {}", fds.len()).into(),
},
});
}
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| ipc_io_error(e, "UnixStream::from_std"))?;
Ok(IpcWriteStream::new(stream_id, sock, lifecycle))
})
}
async fn fds_to_uni_reader(
&self,
handle: IpcUniHandle,
) -> Result<IpcReadStream, ConnectionError> {
let IpcUniHandle { fd_id, stream_id } = handle;
let fds = self
.lifecycle
.guard_with(self.fd_registry.wait_fds(fd_id), |e| {
ipc_transport_error(e, "wait_fds")
})
.await?;
self.lifecycle.guard_sync(|| {
if fds.len() != 1 {
return Err(ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: format!("expected 1 fd for uni stream, got {}", fds.len()).into(),
},
});
}
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| ipc_io_error(e, "UnixStream::from_std"))?;
Ok(IpcReadStream::new(stream_id, sock, lifecycle))
})
}
}
impl quic::WithLocalAgent for IpcConnectionHandle {
type LocalAgent = CachedLocalAgent;
async fn local_agent(&self) -> Result<Option<CachedLocalAgent>, ConnectionError> {
match self
.lifecycle
.guard(IpcConnection::local_agent(&self.rpc))
.await?
{
Some(client) => Ok(Some(
self.lifecycle
.guard(CachedLocalAgent::from_client(client))
.await?,
)),
None => Ok(None),
}
}
}
impl quic::WithRemoteAgent for IpcConnectionHandle {
type RemoteAgent = CachedRemoteAgent;
async fn remote_agent(&self) -> Result<Option<CachedRemoteAgent>, ConnectionError> {
match self
.lifecycle
.guard(IpcConnection::remote_agent(&self.rpc))
.await?
{
Some(client) => Ok(Some(
self.lifecycle
.guard(CachedRemoteAgent::from_client(client))
.await?,
)),
None => Ok(None),
}
}
}
impl quic::Lifecycle for IpcConnectionHandle {
fn close(&self, code: Code, reason: Cow<'static, str>) {
quic::Lifecycle::close(self.lifecycle.as_ref(), code, reason);
}
fn check(&self) -> Result<(), ConnectionError> {
quic::Lifecycle::check(self.lifecycle.as_ref())
}
async fn closed(&self) -> ConnectionError {
quic::Lifecycle::closed(self.lifecycle.as_ref()).await
}
}
pub(crate) const IPC_ERROR_KIND: VarInt = VarInt::from_u32(0x0a);
const IPC_CHANNEL_ERROR_KIND: VarInt = VarInt::from_u32(0x01);
pub(crate) const IPC_FRAME_TYPE: VarInt = VarInt::from_u32(0x00);
fn ipc_io_error(err: io::Error, context: &str) -> ConnectionError {
debug!(error = %snafu::Report::from_error(err), context, "ipc i/o error");
ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: format!("ipc: {context}").into(),
},
}
}
fn ipc_transport_error(err: impl std::error::Error, context: &str) -> ConnectionError {
debug!(error = %snafu::Report::from_error(&err), context, "ipc transport error");
ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: format!("ipc: {context}").into(),
},
}
}
fn ipc_io_plumbing(err: impl std::fmt::Display, context: &str) -> IpcPlumbingError {
debug!(error = %err, context, "ipc plumbing i/o error");
IpcPlumbingError::Io {
message: format!("{context}: {err}"),
}
}
fn plumbing_to_conn(err: &IpcPlumbingError) -> ConnectionError {
ConnectionError::Transport {
source: quic::TransportError {
kind: IPC_ERROR_KIND,
frame_type: IPC_FRAME_TYPE,
reason: err.to_string().into(),
},
}
}
fn map_open_err(error: IpcOpenError) -> ConnectionError {
match error {
IpcOpenError::Connection { source } => source,
IpcOpenError::Plumbing { source } => plumbing_to_conn(&source),
}
}
fn map_accept_err(error: IpcAcceptError) -> ConnectionError {
match error {
IpcAcceptError::Connection { source } => source,
IpcAcceptError::Plumbing { source } => plumbing_to_conn(&source),
}
}
impl IpcConnectionClient {
pub fn into_handle(
self,
fd_registry: FdRegistry,
conn_fd_sender: FdSender,
) -> IpcConnectionHandle {
IpcConnectionHandle::new(self, fd_registry, conn_fd_sender)
}
}