use std::fmt;
use std::io::{ErrorKind, Read, Write};
use super::{RpcError, RpcResult};
mod mem;
#[cfg(feature = "rpc-tcp-debug")]
mod tcp_debug;
#[cfg(feature = "rpc-tls")]
mod tls;
pub(crate) mod unix;
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
mod vsock;
pub use mem::MemTransport;
#[cfg(feature = "rpc-tcp-debug")]
pub use tcp_debug::{insecure_warning_emitted, TcpDebugTransport};
#[cfg(feature = "rpc-tls")]
pub use tls::{TlsStream, TlsTransport};
pub use unix::UnixTransport;
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
pub use vsock::VsockTransport;
pub const MAX_FRAME_LEN: usize = 64 * 1024 * 1024;
pub trait RpcTransport: Send + Sync {
fn send_frame(&self, buf: &[u8]) -> RpcResult<()>;
fn recv_frame(&self) -> RpcResult<Vec<u8>>;
fn peer_identity(&self) -> PeerIdentity;
fn describe(&self) -> &str;
fn set_read_timeout(&self, _timeout: Option<std::time::Duration>) -> RpcResult<()> {
Ok(())
}
fn set_write_timeout(&self, _timeout: Option<std::time::Duration>) -> RpcResult<()> {
Ok(())
}
fn send_frame_with_fds(
&self,
buf: &[u8],
fds: &[std::os::fd::BorrowedFd<'_>],
) -> RpcResult<()> {
if fds.is_empty() {
self.send_frame(buf)
} else {
Err(RpcError::Protocol(
"this transport cannot pass file descriptors (UDS only)",
))
}
}
fn recv_frame_with_fds(&self) -> RpcResult<(Vec<u8>, Vec<std::os::fd::OwnedFd>)> {
Ok((self.recv_frame()?, Vec::new()))
}
fn send_raw(&self, _buf: &[u8]) -> RpcResult<()> {
Err(RpcError::Protocol("this transport has no raw byte access"))
}
fn recv_raw(&self, _buf: &mut [u8]) -> RpcResult<usize> {
Err(RpcError::Protocol("this transport has no raw byte access"))
}
fn send_raw_with_fds(&self, buf: &[u8], fds: &[std::os::fd::BorrowedFd<'_>]) -> RpcResult<()> {
if fds.is_empty() {
self.send_raw(buf)
} else {
Err(RpcError::Protocol(
"this transport cannot pass file descriptors (UDS only)",
))
}
}
fn recv_raw_with_fds(&self, buf: &mut [u8]) -> RpcResult<(usize, Vec<std::os::fd::OwnedFd>)> {
Ok((self.recv_raw(buf)?, Vec::new()))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PeerIdentity {
Local {
uid: u32,
pid: i32,
},
Vsock {
cid: u32,
},
Certificate(CertId),
Anonymous,
}
#[derive(Clone, PartialEq, Eq)]
pub struct CertId {
subject: String,
fingerprint: [u8; 32],
}
impl CertId {
pub fn new(subject: impl Into<String>, fingerprint: [u8; 32]) -> Self {
CertId {
subject: subject.into(),
fingerprint,
}
}
pub fn subject(&self) -> &str {
&self.subject
}
pub fn fingerprint(&self) -> &[u8; 32] {
&self.fingerprint
}
pub fn fingerprint_hex(&self) -> String {
self.fingerprint
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
}
impl fmt::Debug for CertId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CertId")
.field("subject", &self.subject)
.field("fingerprint", &self.fingerprint_hex())
.finish()
}
}
impl PeerIdentity {
pub fn is_local(&self) -> bool {
matches!(self, PeerIdentity::Local { .. })
}
pub fn uid(&self) -> Option<u32> {
match self {
PeerIdentity::Local { uid, .. } => Some(*uid),
_ => None,
}
}
pub fn pid(&self) -> Option<i32> {
match self {
PeerIdentity::Local { pid, .. } if *pid >= 0 => Some(*pid),
_ => None,
}
}
}
impl fmt::Display for PeerIdentity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PeerIdentity::Local { uid, pid } => write!(f, "local(uid={uid}, pid={pid})"),
PeerIdentity::Vsock { cid } => {
write!(f, "vsock(cid={cid}; routing only, NOT an ACL basis)")
}
PeerIdentity::Certificate(c) => {
write!(
f,
"cert(subject={:?}, sha256={})",
c.subject(),
c.fingerprint_hex()
)
}
PeerIdentity::Anonymous => {
write!(
f,
"anonymous(NO peer identity — access control NOT possible)"
)
}
}
}
}
pub(crate) fn write_frame<W: Write>(w: &mut W, buf: &[u8]) -> RpcResult<()> {
if buf.len() > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: buf.len(),
max: MAX_FRAME_LEN,
});
}
let mut framed = Vec::with_capacity(4 + buf.len());
framed.extend_from_slice(&(buf.len() as u32).to_le_bytes());
framed.extend_from_slice(buf);
w.write_all(&framed)?;
w.flush()?;
Ok(())
}
fn read_header<R: Read>(r: &mut R, buf: &mut [u8]) -> RpcResult<()> {
let mut filled = 0;
while filled < buf.len() {
match r.read(&mut buf[filled..]) {
Ok(0) => {
return Err(if filled == 0 {
RpcError::PeerClosed
} else {
RpcError::Truncated
});
}
Ok(n) => filled += n,
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) if is_timeout(&e) => {
return Err(if filled == 0 {
RpcError::Timeout
} else {
RpcError::Truncated
});
}
Err(e) => return Err(e.into()),
}
}
Ok(())
}
fn is_timeout(e: &std::io::Error) -> bool {
matches!(e.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut)
}
fn read_body<R: Read>(r: &mut R, buf: &mut [u8]) -> RpcResult<()> {
let mut filled = 0;
while filled < buf.len() {
match r.read(&mut buf[filled..]) {
Ok(0) => return Err(RpcError::Truncated),
Ok(n) => filled += n,
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) if is_timeout(&e) => return Err(RpcError::Truncated),
Err(e) => return Err(e.into()),
}
}
Ok(())
}
pub(crate) fn read_frame<R: Read>(r: &mut R) -> RpcResult<Vec<u8>> {
let mut len_buf = [0u8; 4];
read_header(r, &mut len_buf)?;
let len = u32::from_le_bytes(len_buf) as usize;
if len > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: len,
max: MAX_FRAME_LEN,
});
}
let mut body = vec![0u8; len];
read_body(r, &mut body)?;
Ok(body)
}
#[doc(hidden)]
pub fn __fuzz_decode_frame(input: &[u8]) -> RpcResult<Vec<u8>> {
read_frame(&mut std::io::Cursor::new(input))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_roundtrip_over_cursor() {
for size in [0usize, 1, 4, 64, 4096, 1 << 20] {
let payload: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let mut buf = Vec::new();
write_frame(&mut buf, &payload).expect("write");
let got = read_frame(&mut std::io::Cursor::new(&buf)).expect("read");
assert_eq!(got, payload, "roundtrip mismatch at size {size}");
}
}
#[test]
fn two_frames_back_to_back_preserve_order() {
let mut buf = Vec::new();
write_frame(&mut buf, b"first").unwrap();
write_frame(&mut buf, b"second").unwrap();
let mut cur = std::io::Cursor::new(&buf);
assert_eq!(read_frame(&mut cur).unwrap(), b"first");
assert_eq!(read_frame(&mut cur).unwrap(), b"second");
}
#[test]
fn hostile_frame_headers_are_rejected_safely() {
let huge = u32::MAX.to_le_bytes();
assert!(matches!(
__fuzz_decode_frame(&huge),
Err(RpcError::FrameTooLarge { .. })
));
let over = ((MAX_FRAME_LEN + 1) as u32).to_le_bytes();
assert!(matches!(
__fuzz_decode_frame(&over),
Err(RpcError::FrameTooLarge { .. })
));
assert!(matches!(
__fuzz_decode_frame(&[]),
Err(RpcError::PeerClosed)
));
assert!(matches!(
__fuzz_decode_frame(&[1, 0]),
Err(RpcError::Truncated)
));
let mut framed = 8u32.to_le_bytes().to_vec();
framed.extend_from_slice(&[1, 2, 3]);
assert!(matches!(
__fuzz_decode_frame(&framed),
Err(RpcError::Truncated)
));
let zeros = vec![0u8; 4 * 1000];
let mut cur = std::io::Cursor::new(&zeros[..]);
for _ in 0..1000 {
assert_eq!(read_frame(&mut cur).unwrap(), Vec::<u8>::new());
}
}
#[test]
fn write_frame_rejects_oversize_payload() {
struct Trap;
impl Write for Trap {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
panic!("oversize payload must be rejected before any write");
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let big = vec![0u8; MAX_FRAME_LEN + 1];
assert!(matches!(
write_frame(&mut Trap, &big),
Err(RpcError::FrameTooLarge { .. })
));
}
#[test]
fn peer_identity_display_and_accessors() {
let local = PeerIdentity::Local { uid: 1000, pid: 42 };
assert!(local.is_local());
assert_eq!(local.uid(), Some(1000));
assert_eq!(local.pid(), Some(42));
assert_eq!(format!("{local}"), "local(uid=1000, pid=42)");
let no_pid = PeerIdentity::Local { uid: 0, pid: -1 };
assert_eq!(no_pid.pid(), None, "-1 pid is reported as unavailable");
let anon = PeerIdentity::Anonymous;
assert!(!anon.is_local());
assert_eq!(anon.uid(), None);
assert!(
format!("{anon}").contains("NO peer identity"),
"Anonymous Display must make the missing-identity state loud"
);
}
}