102 lines
3.2 KiB
Rust
102 lines
3.2 KiB
Rust
//! Transport framing — length-prefixed JSON over sockets.
|
|
//!
|
|
//! Port of `transport_frame.c`. 4-byte big-endian length prefix + payload.
|
|
|
|
use std::io::{self, Read, Write};
|
|
use std::os::unix::net::UnixStream;
|
|
|
|
pub const MAX_MSG_SIZE: usize = 16 * 1024 * 1024; // 16 MB
|
|
|
|
/// Send a framed JSON message: 4-byte BE length + payload.
|
|
pub fn send_framed<W: Write>(writer: &mut W, payload: &str) -> io::Result<()> {
|
|
let len = payload.len() as u32;
|
|
writer.write_all(&len.to_be_bytes())?;
|
|
writer.write_all(payload.as_bytes())?;
|
|
writer.flush()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Receive a framed JSON message: read 4-byte BE length, then payload.
|
|
pub fn recv_framed<R: Read>(reader: &mut R) -> io::Result<String> {
|
|
let mut header = [0u8; 4];
|
|
reader.read_exact(&mut header)?;
|
|
let len = u32::from_be_bytes(header) as usize;
|
|
if len == 0 || len > MAX_MSG_SIZE {
|
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid frame length"));
|
|
}
|
|
let mut buf = vec![0u8; len];
|
|
reader.read_exact(&mut buf)?;
|
|
String::from_utf8(buf).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid UTF-8"))
|
|
}
|
|
|
|
/// Connect to a Unix socket in the abstract namespace via libc.
|
|
///
|
|
/// Rust's safe `UnixStream::connect` rejects paths containing null bytes,
|
|
/// so abstract sockets (sun_path[0] = '\0') must be connected via libc.
|
|
pub fn connect_abstract_unix(name: &str) -> io::Result<UnixStream> {
|
|
use std::os::unix::io::FromRawFd;
|
|
|
|
if name.len() >= 107 {
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput, "socket name too long"));
|
|
}
|
|
|
|
let fd = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_STREAM, 0) };
|
|
if fd < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() };
|
|
addr.sun_family = libc::AF_UNIX as libc::sa_family_t;
|
|
let name_bytes = name.as_bytes();
|
|
for (i, &b) in name_bytes.iter().enumerate() {
|
|
addr.sun_path[i + 1] = b as libc::c_char;
|
|
}
|
|
|
|
let addrlen = (std::mem::size_of::<libc::sa_family_t>() + 1 + name_bytes.len()) as libc::socklen_t;
|
|
|
|
let rc = unsafe {
|
|
libc::connect(
|
|
fd,
|
|
&addr as *const libc::sockaddr_un as *const libc::sockaddr,
|
|
addrlen,
|
|
)
|
|
};
|
|
if rc != 0 {
|
|
let err = io::Error::last_os_error();
|
|
unsafe { libc::close(fd) };
|
|
return Err(err);
|
|
}
|
|
|
|
let stream = unsafe { UnixStream::from_raw_fd(fd) };
|
|
Ok(stream)
|
|
}
|
|
|
|
/// Send a framed message over a Unix socket.
|
|
pub fn send_framed_unix(stream: &UnixStream, payload: &str) -> io::Result<()> {
|
|
let mut writer = stream;
|
|
send_framed(&mut writer, payload)
|
|
}
|
|
|
|
/// Receive a framed message from a Unix socket.
|
|
pub fn recv_framed_unix(stream: &UnixStream) -> io::Result<String> {
|
|
let mut reader = stream;
|
|
recv_framed(&mut reader)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Cursor;
|
|
|
|
#[test]
|
|
fn test_framing_roundtrip() {
|
|
let mut buf = Vec::new();
|
|
let msg = r#"{"id":"1","method":"get_info","params":[]}"#;
|
|
send_framed(&mut buf, msg).unwrap();
|
|
|
|
let mut cursor = Cursor::new(buf);
|
|
let received = recv_framed(&mut cursor).unwrap();
|
|
assert_eq!(received, msg);
|
|
}
|
|
}
|