141 lines
4.5 KiB
Rust
141 lines
4.5 KiB
Rust
//! Connection wrapper around a Unix socket stream.
|
|||
|
|
//!
|
||
|
|
//! [`Connection`] pairs a buffered reader with a raw writer and exposes
|
||
|
|
//! `send` / `receive` for framed JSON messages.
|
||
|
|
|
||
|
|
#![allow(
|
||
|
|
clippy::cast_possible_truncation,
|
||
|
|
clippy::cast_sign_loss,
|
||
|
|
clippy::cast_precision_loss,
|
||
|
|
clippy::cast_possible_wrap
|
||
|
|
)]
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
/// 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<UnixStream>,
|
||
|
|
/// Unbuffered writer (flushed after every frame).
|
||
|
|
writer: UnixStream,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Connection {
|
||
|
|
/// Create a new `Connection` from an already-connected [`UnixStream`].
|
||
|
|
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<T: Serialize>(&mut self, msg: &T) -> Result<()> {
|
||
|
|
let json =
|
||
|
|
serde_json::to_vec(msg).context("failed to serialise message to JSON")?;
|
||
|
|
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<T: DeserializeOwned>(&mut self) -> Result<Option<T>> {
|
||
|
|
let raw = read_frame(&mut self.reader)
|
||
|
|
.context("failed to read frame from connection")?;
|
||
|
|
|
||
|
|
match raw {
|
||
|
|
None => Ok(None),
|
||
|
|
Some(bytes) => {
|
||
|
|
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 serde::{Deserialize, Serialize};
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||
|
|
struct Ping {
|
||
|
|
seq: u32,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// 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, _b) = pair();
|
||
|
|
drop(_b);
|
||
|
|
let result: Option<Ping> = a.receive().unwrap();
|
||
|
|
assert!(result.is_none());
|
||
|
|
}
|
||
|
|
}
|