use bytes::Bytes;
use std::io::{self, Read, Write};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::time::Instant;
use crate::error::{Result, TransportError};
const MAX_FRAME_SIZE: usize = 16 * 1024 * 1024;
pub struct BlockingVsockTransport {
stream: UnixStream,
}
impl BlockingVsockTransport {
pub unsafe fn from_raw_fd(fd: RawFd) -> io::Result<Self> {
let stream = unsafe { UnixStream::from_raw_fd(fd) };
stream.set_nonblocking(true)?;
Ok(Self { stream })
}
pub fn send(&mut self, data: &[u8], deadline: Instant) -> Result<()> {
let mut offset = 0;
while offset < data.len() {
self.poll_ready(libc::POLLOUT, deadline)?;
match self.stream.write(&data[offset..]) {
Ok(0) => {
return Err(TransportError::io(io::Error::new(
io::ErrorKind::WriteZero,
"write returned 0",
)));
}
Ok(n) => offset += n,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(TransportError::io(e)),
}
}
Ok(())
}
pub fn recv(&mut self, deadline: Instant) -> Result<Bytes> {
let mut len_buf = [0u8; 4];
self.read_exact(&mut len_buf, deadline)?;
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})"
)));
}
let mut buf = vec![0u8; 4 + len];
buf[..4].copy_from_slice(&len_buf);
self.read_exact(&mut buf[4..], deadline)?;
Ok(Bytes::from(buf))
}
fn read_exact(&mut self, buf: &mut [u8], deadline: Instant) -> Result<()> {
let mut offset = 0;
while offset < buf.len() {
self.poll_ready(libc::POLLIN, deadline)?;
match self.stream.read(&mut buf[offset..]) {
Ok(0) => {
return Err(TransportError::io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"early eof",
)));
}
Ok(n) => offset += n,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(TransportError::io(e)),
}
}
Ok(())
}
fn poll_ready(&self, events: i16, deadline: Instant) -> Result<()> {
let now = Instant::now();
if now >= deadline {
return Err(TransportError::io(io::Error::new(
io::ErrorKind::TimedOut,
"transport deadline exceeded",
)));
}
let timeout_ms = (deadline - now).as_millis().min(i32::MAX as u128) as i32;
let mut pfd = libc::pollfd {
fd: self.stream.as_raw_fd(),
events,
revents: 0,
};
let ret = unsafe { libc::poll(&raw mut pfd, 1, timeout_ms) };
if ret < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
return Ok(());
}
return Err(TransportError::io(err));
}
if ret == 0 {
return Err(TransportError::io(io::Error::new(
io::ErrorKind::TimedOut,
"transport deadline exceeded",
)));
}
if pfd.revents & libc::POLLNVAL != 0 {
return Err(TransportError::io(io::Error::new(
io::ErrorKind::InvalidInput,
"poll: POLLNVAL -- file descriptor is not valid",
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::unix::io::FromRawFd;
use std::time::Duration;
#[test]
fn test_blocking_roundtrip() {
let mut fds: [libc::c_int; 2] = [0; 2];
let ret =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(ret, 0, "socketpair failed: {}", io::Error::last_os_error());
let mut client = unsafe { BlockingVsockTransport::from_raw_fd(fds[0]).unwrap() };
let mut server = unsafe { BlockingVsockTransport::from_raw_fd(fds[1]).unwrap() };
let deadline = Instant::now() + Duration::from_secs(2);
let payload = b"hello";
let len = (payload.len() as u32).to_be_bytes();
let mut frame = Vec::new();
frame.extend_from_slice(&len);
frame.extend_from_slice(payload);
client.send(&frame, deadline).unwrap();
let received = server.recv(deadline).unwrap();
assert_eq!(&received[4..], b"hello");
}
#[test]
fn test_blocking_timeout() {
let mut fds: [libc::c_int; 2] = [0; 2];
let ret =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(ret, 0, "socketpair failed: {}", io::Error::last_os_error());
let mut client = unsafe { BlockingVsockTransport::from_raw_fd(fds[0]).unwrap() };
let _server_fd = unsafe { UnixStream::from_raw_fd(fds[1]) };
let deadline = Instant::now() + Duration::from_millis(50);
let result = client.recv(deadline);
assert!(result.is_err());
}
#[test]
fn test_blocking_peer_close_eof() {
let mut fds: [libc::c_int; 2] = [0; 2];
let ret =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(ret, 0, "socketpair failed: {}", io::Error::last_os_error());
let mut client = unsafe { BlockingVsockTransport::from_raw_fd(fds[0]).unwrap() };
unsafe { libc::close(fds[1]) };
let deadline = Instant::now() + Duration::from_secs(2);
let result = client.recv(deadline);
assert!(result.is_err());
}
}