Files
signer/src/transport.rs
T

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);
}
}