//! Connection wrapper around a Unix socket stream. //! //! [`Connection`] pairs a buffered reader with a raw writer and exposes //! `send` / `receive` for framed JSON messages. use crate::frame::{read_frame, write_frame}; use anyhow::{Context, Result}; use serde::de::DeserializeOwned; use serde::Serialize; use std::io::BufReader; use std::os::unix::net::UnixStream; use tracing; /// A framed JSON connection over a Unix socket. /// /// Wraps the raw [`UnixStream`] with a [`BufReader`] on the read side and /// direct writes (with explicit flushing) on the write side. pub struct Connection { /// Buffered reader for receiving frames. reader: BufReader, /// Unbuffered writer (flushed after every frame). writer: UnixStream, } impl Connection { /// Create a new `Connection` from an already-connected [`UnixStream`]. /// /// # Panics /// /// Panics if `UnixStream::try_clone` fails — this should never happen on /// Linux (it calls `dup(2)`). #[must_use] pub fn new(stream: UnixStream) -> Self { // Clone the stream so that reader and writer can reference separate // file-descriptor handles. `UnixStream::try_clone` is infallible on // Unix (it calls `dup(2)`). let reader = BufReader::new( stream .try_clone() .expect("UnixStream::try_clone should never fail on Linux"), ); let writer = stream; Self { reader, writer } } /// Serialise `msg` to JSON and send it as a length-prefixed frame. /// /// # Errors /// /// Delegates to [`serde_json::to_vec`] for serialisation and /// [`write_frame`] for writing. pub fn send(&mut self, msg: &T) -> Result<()> { let json = serde_json::to_vec(msg).context("failed to serialise message to JSON")?; tracing::trace!("sending frame ({} byte(s))", json.len()); write_frame(&mut self.writer, &json).context("failed to write frame to connection") } /// Read one framed JSON message and deserialise it. /// /// Returns `Ok(None)` when the remote end has closed the connection /// cleanly (EOF). Returns `Ok(Some(msg))` on a successful read. /// /// # Errors /// /// Delegates to [`read_frame`] for reading and /// [`serde_json::from_slice`] for deserialisation. pub fn receive(&mut self) -> Result> { let raw = read_frame(&mut self.reader).context("failed to read frame from connection")?; match raw { None => { tracing::trace!("connection closed (EOF)"); Ok(None) } Some(bytes) => { tracing::trace!("received frame ({} byte(s))", bytes.len()); let msg: T = serde_json::from_slice(&bytes).with_context(|| { format!("failed to deserialise frame ({} byte(s))", bytes.len()) })?; Ok(Some(msg)) } } } } // Safety: `UnixStream` is `Send` but not `Sync`. Wrapping `Connection` in // a `Mutex` (as done in `IpcClient`) provides the `Sync` guarantee. // The type itself is `Send` because both fields are `Send`. // // We explicitly assert Send here for clarity: fn _assert_send() where Connection: Send, { } #[cfg(test)] mod tests { use super::*; use crate::test_utils::Ping; /// Helper: create a pair of connected `Connection` values via a /// Unix socket pair. fn pair() -> (Connection, Connection) { let (a, b) = UnixStream::pair().expect("UnixStream::pair failed"); (Connection::new(a), Connection::new(b)) } #[test] fn round_trip() { let (mut left, mut right) = pair(); left.send(&Ping { seq: 42 }).unwrap(); let received: Ping = right.receive().unwrap().expect("expected a frame"); assert_eq!(received, Ping { seq: 42 }); } #[test] fn eof_detection() { let (left, right) = pair(); drop(right); // close remote end // Send something first so we can read past it... actually let's // just drop the peer and check that receive returns None. // Since we dropped right, left's reads should eventually get EOF. // But with a socket pair, dropping one end signals EOF on the other. drop(left); // drop left too — we'll test EOF on a fresh pair let (mut a, _) = pair(); let result: Option = a.receive().unwrap(); assert!(result.is_none()); } }