use super::addr::{DEFAULT_AGENT_PORT, VsockAddr};
use super::stream::VsockStream;
use crate::Transport;
use crate::error::{Result, TransportError};
use async_trait::async_trait;
use bytes::Bytes;
use std::io;
use std::os::fd::RawFd;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
const MAX_FRAME_SIZE: usize = 16 * 1024 * 1024;
async fn read_framed<R: AsyncRead + Unpin>(reader: &mut R) -> Result<Bytes> {
let mut len_buf = [0u8; 4];
reader
.read_exact(&mut len_buf)
.await
.map_err(TransportError::io)?;
let len = u32::from_be_bytes(len_buf) as usize;
if len > MAX_FRAME_SIZE {
return Err(TransportError::Protocol(format!(
"frame too large: {len} bytes (max {MAX_FRAME_SIZE})"
)));
}
tracing::debug!("vsock read_framed: message length={}", len);
let mut buf = vec![0u8; 4 + len];
buf[..4].copy_from_slice(&len_buf);
reader
.read_exact(&mut buf[4..])
.await
.map_err(TransportError::io)?;
Ok(Bytes::from(buf))
}
pub struct VsockSender {
writer: WriteHalf<VsockStream>,
}
impl VsockSender {
pub async fn send(&mut self, data: Bytes) -> Result<()> {
self.writer
.write_all(&data)
.await
.map_err(TransportError::io)
}
}
pub struct VsockReceiver {
reader: ReadHalf<VsockStream>,
}
impl VsockReceiver {
pub async fn recv(&mut self) -> Result<Bytes> {
read_framed(&mut self.reader).await
}
}
pub struct VsockTransport {
addr: VsockAddr,
stream: Option<VsockStream>,
}
impl VsockTransport {
#[must_use]
pub const fn new(addr: VsockAddr) -> Self {
Self { addr, stream: None }
}
#[must_use]
pub const fn to_host() -> Self {
Self::new(VsockAddr::host(DEFAULT_AGENT_PORT))
}
#[must_use]
pub const fn addr(&self) -> VsockAddr {
self.addr
}
#[must_use]
pub fn from_stream(stream: VsockStream, addr: VsockAddr) -> Self {
Self {
addr,
stream: Some(stream),
}
}
#[cfg(target_os = "macos")]
pub fn from_raw_fd(fd: RawFd, addr: VsockAddr) -> Result<Self> {
if fd < 0 {
return Err(TransportError::io(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid raw vsock file descriptor",
)));
}
use std::os::unix::io::FromRawFd as _;
let std_stream = unsafe { std::os::unix::net::UnixStream::from_raw_fd(fd) };
std_stream
.set_nonblocking(true)
.map_err(TransportError::io)?;
let tokio_stream =
tokio::net::UnixStream::from_std(std_stream).map_err(TransportError::io)?;
let stream = VsockStream::from_unix_stream(tokio_stream);
Ok(Self {
addr,
stream: Some(stream),
})
}
pub fn into_split(self) -> Result<(VsockSender, VsockReceiver)> {
let stream = self.stream.ok_or(TransportError::NotConnected)?;
let (reader, writer) = tokio::io::split(stream);
Ok((VsockSender { writer }, VsockReceiver { reader }))
}
}
#[async_trait]
impl Transport for VsockTransport {
async fn connect(&mut self) -> Result<()> {
if self.stream.is_some() {
return Err(TransportError::AlreadyConnected);
}
#[cfg(target_os = "linux")]
{
let stream = super::linux::connect_vsock(self.addr)?;
self.stream = Some(stream);
}
#[cfg(target_os = "macos")]
{
let stream = super::darwin::connect_vsock(self.addr)?;
self.stream = Some(stream);
}
Ok(())
}
async fn disconnect(&mut self) -> Result<()> {
self.stream.take();
Ok(())
}
async fn send(&mut self, data: Bytes) -> Result<()> {
let stream = self.stream.as_mut().ok_or(TransportError::NotConnected)?;
stream.write_all(&data).await.map_err(TransportError::io)
}
async fn recv(&mut self) -> Result<Bytes> {
let stream = self.stream.as_mut().ok_or(TransportError::NotConnected)?;
read_framed(stream).await
}
fn is_connected(&self) -> bool {
self.stream.is_some()
}
}