use super::*;
use crate::distributed::wire::write_channel_magic;
use std::io::{Read, Write};
use std::net::TcpListener;
fn start_test_mux() -> (PortMux, MuxAccept, u16) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let abort = Arc::new(AtomicBool::new(false));
let (mux, accept) = PortMux::start(listener, abort).unwrap();
let port = mux.port();
(mux, accept, port)
}
fn dial(port: u16, magic: u32, payload: &[u8]) -> TcpStream {
let mut s = TcpStream::connect(("127.0.0.1", port)).unwrap();
s.set_nodelay(true).unwrap();
write_channel_magic(&mut s, magic).unwrap();
s.write_all(payload).unwrap();
s
}
fn recv_and_check(rx: &Receiver<TcpStream>, magic: u32, payload: &[u8]) {
let mut stream = rx
.recv_timeout(Duration::from_secs(5))
.expect("stream routed to channel");
let mut buf = vec![0u8; 4 + payload.len()];
stream
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
stream.read_exact(&mut buf).unwrap();
assert_eq!(&buf[0..4], magic.to_le_bytes());
assert_eq!(&buf[4..], payload);
}
#[test]
fn routes_each_channel_by_magic_over_one_port() {
let (_mux, accept, port) = start_test_mux();
let s1 = dial(port, CHANNEL_MAGIC_RENDEZVOUS, b"rdv");
let s2 = dial(port, CHANNEL_MAGIC_DATA, b"data");
let s3 = dial(port, CHANNEL_MAGIC_CONTROL, b"ctrl");
let s4 = dial(port, CHANNEL_MAGIC_JOIN, b"join");
recv_and_check(&accept.rendezvous, CHANNEL_MAGIC_RENDEZVOUS, b"rdv");
recv_and_check(&accept.data, CHANNEL_MAGIC_DATA, b"data");
recv_and_check(&accept.control, CHANNEL_MAGIC_CONTROL, b"ctrl");
recv_and_check(&accept.join, CHANNEL_MAGIC_JOIN, b"join");
drop((s1, s2, s3, s4));
}
#[test]
fn http_get_routes_to_status_leg_with_request_intact() {
let (_mux, accept, port) = start_test_mux();
let request = b"GET /state.json HTTP/1.1\r\nHost: t\r\n\r\n";
let mut client = TcpStream::connect(("127.0.0.1", port)).unwrap();
client.set_nodelay(true).unwrap();
client.write_all(request).unwrap();
let mut routed = accept
.status
.recv_timeout(Duration::from_secs(5))
.expect("HTTP GET routed to status leg");
routed
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let mut buf = vec![0u8; request.len()];
routed.read_exact(&mut buf).unwrap();
assert_eq!(&buf, request);
drop(client);
}
#[test]
fn unknown_magic_dropped_and_dispatcher_continues() {
let (_mux, accept, port) = start_test_mux();
let mut rogue = dial(port, 0xDEAD_BEEF, b"junk");
rogue
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let mut buf = [0u8; 1];
match rogue.read(&mut buf) {
Ok(0) | Err(_) => {}
Ok(n) => panic!("rogue connection should be dropped, read {n} bytes"),
}
let honest = dial(port, CHANNEL_MAGIC_DATA, b"ok");
recv_and_check(&accept.data, CHANNEL_MAGIC_DATA, b"ok");
drop(honest);
}
#[test]
fn eof_before_magic_dropped_and_dispatcher_continues() {
let (_mux, accept, port) = start_test_mux();
let early_eof = TcpStream::connect(("127.0.0.1", port)).unwrap();
drop(early_eof);
let honest = dial(port, CHANNEL_MAGIC_CONTROL, b"ok");
recv_and_check(&accept.control, CHANNEL_MAGIC_CONTROL, b"ok");
drop(honest);
}
#[test]
fn closed_channel_resets_dialer_and_dispatcher_continues() {
let (_mux, accept, port) = start_test_mux();
drop(accept.rendezvous);
let mut stray = dial(port, CHANNEL_MAGIC_RENDEZVOUS, b"?");
stray
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let mut buf = [0u8; 1];
match stray.read(&mut buf) {
Ok(0) | Err(_) => {}
Ok(n) => panic!("stray dialer should be reset, read {n} bytes"),
}
let honest = dial(port, CHANNEL_MAGIC_DATA, b"ok");
recv_and_check(&accept.data, CHANNEL_MAGIC_DATA, b"ok");
drop(honest);
}
#[test]
fn abort_flag_stops_dispatcher() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let abort = Arc::new(AtomicBool::new(false));
let (mux, accept) = PortMux::start(listener, Arc::clone(&abort)).unwrap();
abort.store(true, Ordering::SeqCst);
let deadline = Instant::now() + Duration::from_secs(5);
loop {
match accept.data.recv_timeout(Duration::from_millis(50)) {
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
assert!(
Instant::now() < deadline,
"dispatcher did not exit on abort"
);
}
Ok(_) => panic!("no connection was made"),
}
}
drop(mux); }
#[test]
fn stream_source_listener_polls_and_accepts() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let source = StreamSource::from_listener(listener, "test").unwrap();
assert!(source.try_accept("test").unwrap().is_none());
let mut client = TcpStream::connect(("127.0.0.1", port)).unwrap();
client.write_all(b"x").unwrap();
let deadline = Instant::now() + Duration::from_secs(5);
let mut accepted = loop {
if let Some(s) = source.try_accept("test").unwrap() {
break s;
}
assert!(Instant::now() < deadline, "accept never surfaced");
thread::sleep(Duration::from_millis(10));
};
accepted
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let mut buf = [0u8; 1];
accepted.read_exact(&mut buf).unwrap();
assert_eq!(&buf, b"x");
}