Files
tines/tines-bus/tests/daemon.rs
T

176 lines
5.3 KiB
Rust

//! Protocol-level tests driving raw Unix sockets straight at the daemon.
mod common;
use common::{spawn_daemon, unique_dir};
use std::io::{ErrorKind, Read, Write};
use std::os::unix::net::UnixStream;
use std::time::{Duration, Instant};
use tines_core::consts::MAX_FRAME;
type NodeId = [u8; 16];
fn connect(sock: &std::path::Path) -> UnixStream {
let s = UnixStream::connect(sock).unwrap();
s.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
s
}
fn send_envelope(s: &mut UnixStream, payload: &[u8]) {
let mut buf = Vec::with_capacity(4 + payload.len());
buf.extend_from_slice(&(payload.len() as u32).to_le_bytes());
buf.extend_from_slice(payload);
s.write_all(&buf).unwrap();
}
fn hello(s: &mut UnixStream, id: &NodeId) {
send_envelope(s, id);
}
fn data(s: &mut UnixStream, from: &NodeId, to: &NodeId, frame: &[u8]) {
let mut p = Vec::with_capacity(32 + frame.len());
p.extend_from_slice(from);
p.extend_from_slice(to);
p.extend_from_slice(frame);
send_envelope(s, &p);
}
/// Read one envelope's payload; `Ok(None)` means EOF.
fn read_envelope(s: &mut UnixStream) -> std::io::Result<Option<Vec<u8>>> {
let mut len_buf = [0u8; 4];
let n = s.read(&mut len_buf)?;
if n == 0 {
return Ok(None);
}
assert_eq!(n, 4, "short length header");
let len = u32::from_le_bytes(len_buf) as usize;
let mut payload = vec![0u8; len];
s.read_exact(&mut payload)?;
Ok(Some(payload))
}
fn expect_control(s: &mut UnixStream, kind: u8, id: &NodeId) {
let p = read_envelope(s)
.unwrap()
.expect("connection closed unexpectedly");
assert_eq!(p.len(), 17, "control payload is kind + 16-byte id");
assert_eq!(p[0], kind);
assert_eq!(&p[1..17], id);
}
/// Assert the socket stays silent for `window_ms`, then return.
fn expect_no_data(s: &mut UnixStream, window_ms: u64) {
s.set_read_timeout(Some(Duration::from_millis(100)))
.unwrap();
let deadline = Instant::now() + Duration::from_millis(window_ms);
let mut buf = [0u8; 1];
loop {
match s.read(&mut buf) {
Ok(0) => panic!("connection closed, expected silence"),
Ok(_) => panic!("expected no data, got some"),
Err(e) if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut => {
if Instant::now() >= deadline {
return;
}
std::thread::sleep(Duration::from_millis(5));
}
Err(e) => panic!("unexpected read error: {e}"),
}
}
}
#[test]
fn duplicate_hello_replaces() {
let dir = unique_dir("dup");
let sock = spawn_daemon(&dir);
let mut s1 = connect(&sock);
hello(&mut s1, &[1; 16]);
let mut s2 = connect(&sock);
hello(&mut s2, &[1; 16]);
// The first connection is closed by the daemon...
let mut buf = [0u8; 1];
assert_eq!(
s1.read(&mut buf).unwrap(),
0,
"first connection must see EOF after replacement"
);
// ...and the daemon stays up, serving the replacement: a third peer's
// join reaches s2 as a control message.
let mut s3 = connect(&sock);
hello(&mut s3, &[2; 16]);
expect_control(&mut s2, 0, &[2; 16]);
}
#[test]
fn mismatched_from_is_dropped() {
let dir = unique_dir("mismatch");
let sock = spawn_daemon(&dir);
let mut a = connect(&sock);
hello(&mut a, &[1; 16]);
let mut b = connect(&sock);
hello(&mut b, &[2; 16]);
// A (registered as [1;16]) claims to be [2;16]: nothing may reach B.
data(&mut a, &[2; 16], &[2; 16], &[0xAB; 10]);
expect_no_data(&mut b, 300);
// The daemon survives and A is still connected: a well-formed message
// is forwarded verbatim.
data(&mut a, &[1; 16], &[2; 16], &[0xCD; 10]);
let p = read_envelope(&mut b).unwrap().expect("connection closed");
assert_eq!(p.len(), 42);
assert_eq!(&p[0..16], &[1; 16]);
assert_eq!(&p[16..32], &[2; 16]);
assert_eq!(&p[32..42], &[0xCD; 10]);
}
#[test]
fn unknown_dest_is_dropped() {
let dir = unique_dir("unknown");
let sock = spawn_daemon(&dir);
let mut a = connect(&sock);
hello(&mut a, &[1; 16]);
// No [9;16] registered: dropped, and A stays connected.
data(&mut a, &[1; 16], &[9; 16], &[0xAB; 10]);
let mut b = connect(&sock);
hello(&mut b, &[2; 16]);
// Wait until the daemon has registered B: A sees the join control.
expect_control(&mut a, 0, &[2; 16]);
data(&mut a, &[1; 16], &[2; 16], &[0xCD; 10]);
let p = read_envelope(&mut b).unwrap().expect("connection closed");
assert_eq!(p.len(), 42);
assert_eq!(&p[32..42], &[0xCD; 10]);
}
#[test]
fn oversized_frame_closes_connection() {
let dir = unique_dir("oversize");
let sock = spawn_daemon(&dir);
let mut a = connect(&sock);
hello(&mut a, &[1; 16]);
let mut b = connect(&sock);
hello(&mut b, &[2; 16]);
// Handshake: once A has seen B join, both hellos are registered.
expect_control(&mut a, 0, &[2; 16]);
// A frame one byte over MAX_FRAME: that connection is closed...
data(&mut a, &[1; 16], &[2; 16], &vec![0u8; MAX_FRAME + 1]);
let mut buf = [0u8; 1];
assert_eq!(
a.read(&mut buf).unwrap(),
0,
"oversized frame must close the connection"
);
// ...and B learns A left; the daemon stays up for everyone else.
expect_control(&mut b, 1, &[1; 16]);
}