use std::error::Error;
use std::io::Write;
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use liminal::protocol::{Frame, decode, encode, encoded_len};
use super::super::{LoopbackClientEnd, LoopbackDuplex};
use crate::config::types::{LimitsConfig, ServerConfig, ServicesConfig};
use crate::server::connection::services::LiminalConnectionServices;
use crate::server::connection::supervisor::{ConnectionRuntime, ConnectionSupervisor};
use crate::server::mount::MountKind;
const PIN_RING_BYTES: usize = 64 * 1024;
const PIN_DEADLINE: Duration = Duration::from_secs(5);
fn supervisor() -> Result<ConnectionSupervisor, Box<dyn Error>> {
Ok(ConnectionSupervisor::with_services(Arc::new(
LiminalConnectionServices::empty()?,
))?)
}
fn supervisor_with_token(token: &str) -> Result<ConnectionSupervisor, Box<dyn Error>> {
Ok(ConnectionSupervisor::with_services_and_auth(
Arc::new(LiminalConnectionServices::empty()?),
Some(token.as_bytes().to_vec()),
)?)
}
fn supervisor_with_max_connections(max: usize) -> Result<ConnectionSupervisor, Box<dyn Error>> {
let config = ServerConfig {
listen_address: "127.0.0.1:0".parse()?,
health_listen_address: "127.0.0.1:1".parse()?,
drain_timeout_ms: 30_000,
channels: Vec::new(),
routing_rules: Vec::new(),
persistence_path: None,
cluster: None,
auth: None,
services: ServicesConfig::default(),
limits: LimitsConfig {
max_connections: max,
..LimitsConfig::default()
},
participant: None,
websocket: None,
};
Ok(ConnectionSupervisor::from_config(&config)?)
}
fn tcp_pair() -> Result<(TcpStream, TcpStream), Box<dyn Error>> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let address = listener.local_addr()?;
let client = TcpStream::connect(address)?;
let (server, _peer) = listener.accept()?;
Ok((client, server))
}
fn write_frame(client: &mut LoopbackClientEnd, frame: &Frame) -> Result<(), Box<dyn Error>> {
let mut bytes = vec![0_u8; encoded_len(frame)?];
let written = encode(frame, &mut bytes)?;
bytes.truncate(written);
client.write_all(&bytes)?;
Ok(())
}
fn read_frame(client: &mut LoopbackClientEnd) -> Result<Option<Frame>, Box<dyn Error>> {
let deadline = Instant::now() + PIN_DEADLINE;
let mut buffered: Vec<u8> = Vec::new();
loop {
match decode(&buffered) {
Ok((frame, _consumed)) => return Ok(Some(frame)),
Err(
liminal::protocol::ProtocolError::IncompleteHeader { .. }
| liminal::protocol::ProtocolError::TruncatedPayload { .. },
) => {}
Err(error) => return Err(Box::new(error)),
}
let remaining = deadline
.checked_duration_since(Instant::now())
.ok_or("loopback pin timed out waiting for a frame")?;
let mut chunk = [0_u8; 4096];
let read = client.read_timeout(&mut chunk, Some(remaining))?;
if read == 0 {
return Ok(None);
}
buffered.extend_from_slice(chunk.get(..read).unwrap_or(&[]));
}
}
fn wait_until(mut predicate: impl FnMut() -> bool) -> bool {
let deadline = Instant::now() + PIN_DEADLINE;
while Instant::now() < deadline {
if predicate() {
return true;
}
std::thread::sleep(Duration::from_millis(5));
}
predicate()
}
fn connect_frame(token: &[u8]) -> Frame {
Frame::Connect {
flags: 0,
min_version: liminal::protocol::ProtocolVersion::new(1, 0),
max_version: liminal::protocol::ProtocolVersion::new(1, 0),
auth_token: token.to_vec(),
}
}
#[test]
fn a_loopback_connection_consumes_an_admission_slot_from_the_one_shared_pool()
-> Result<(), Box<dyn Error>> {
let supervisor = supervisor_with_max_connections(1)?;
let (_client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let admitted = supervisor.spawn_loopback_connection(server_end)?;
assert_eq!(
supervisor.active_connection_count(),
1,
"an in-process connection is a tracked connection like any other"
);
let (_socket_client, socket_server) = tcp_pair()?;
match supervisor.spawn_connection(socket_server) {
Err(crate::ServerError::ConnectionLimitReached { limit }) => {
assert_eq!(limit, 1, "the refusal reports the configured cap");
}
other => {
return Err(format!("expected ConnectionLimitReached, got {other:?}").into());
}
}
drop(admitted);
supervisor.shutdown();
Ok(())
}
#[test]
fn a_loopback_connection_at_capacity_is_refused_exactly_like_a_socket_connect()
-> Result<(), Box<dyn Error>> {
let supervisor = supervisor_with_max_connections(1)?;
let (_socket_client, socket_server) = tcp_pair()?;
supervisor.spawn_connection(socket_server)?;
let (_client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
match supervisor.spawn_loopback_connection(server_end) {
Err(crate::ServerError::ConnectionLimitReached { limit }) => {
assert_eq!(
limit, 1,
"an in-process connect at capacity is refused with the same typed \
refusal, reporting the same cap, as a socket connect"
);
}
other => {
return Err(format!("expected ConnectionLimitReached, got {other:?}").into());
}
}
assert_eq!(
supervisor.active_connection_count(),
1,
"a refused loopback connect leaves no record and leaks no admission"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn a_loopback_connect_with_the_configured_token_is_acked_through_the_real_apply_frame_path()
-> Result<(), Box<dyn Error>> {
let supervisor = supervisor_with_token("s3cr3t")?;
let (mut client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let handle = supervisor.spawn_loopback_connection(server_end)?;
write_frame(&mut client, &connect_frame(b"s3cr3t"))?;
let answer = read_frame(&mut client)?.ok_or("loopback connect produced end of file")?;
assert!(
matches!(answer, Frame::ConnectAck { .. }),
"a correct token over the loopback is acked by the same handshake a socket \
reaches, got {answer:?}"
);
assert!(
supervisor.is_tracked(handle.pid()),
"an acked connection stays open"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn a_loopback_connect_with_a_wrong_token_is_refused_on_its_own_duplex() -> Result<(), Box<dyn Error>>
{
let supervisor = supervisor_with_token("s3cr3t")?;
let (mut client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let handle = supervisor.spawn_loopback_connection(server_end)?;
let pid = handle.pid();
write_frame(&mut client, &connect_frame(b"wrong"))?;
let answer = read_frame(&mut client)?.ok_or("loopback refusal produced end of file")?;
assert!(
matches!(answer, Frame::ConnectError { .. }),
"admission is admission: an embedded caller with the wrong token is refused \
on its own loopback, got {answer:?}"
);
assert!(
wait_until(|| !supervisor.is_tracked(pid)),
"a refused connection is torn down, not left open"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn a_parked_loopback_connection_is_woken_by_a_client_write_and_answers()
-> Result<(), Box<dyn Error>> {
let supervisor = supervisor()?;
let (mut client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let handle = supervisor.spawn_loopback_connection(server_end)?;
let pid = handle.pid();
assert!(
wait_until(|| supervisor.slice_count(pid) > 0),
"the connection must run its admission slice"
);
let parked_at = supervisor.slice_count(pid);
std::thread::sleep(Duration::from_millis(200));
assert_eq!(
supervisor.slice_count(pid),
parked_at,
"an idle loopback connection parks: it must service no slices while nothing \
has happened, and must never spin on its ring"
);
write_frame(&mut client, &Frame::Ping { flags: 0 })?;
let answer = read_frame(&mut client)?.ok_or("woken loopback produced end of file")?;
assert!(
matches!(answer, Frame::Pong { .. }),
"the parked connection was told about the write and answered it, got {answer:?}"
);
assert!(
supervisor.slice_count(pid) > parked_at,
"the wake ran a slice"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn dropping_the_client_end_tears_the_loopback_connection_down_and_deregisters_it()
-> Result<(), Box<dyn Error>> {
let supervisor = supervisor()?;
let (client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let handle = supervisor.spawn_loopback_connection(server_end)?;
let pid = handle.pid();
assert!(
wait_until(|| supervisor.slice_count(pid) > 0),
"the connection must run its admission slice before the hangup"
);
drop(client);
assert!(
wait_until(|| !supervisor.is_tracked(pid)),
"a dropped client end must drive the connection's teardown"
);
assert_eq!(
supervisor.active_connection_count(),
0,
"teardown deregisters the record and releases its admission slot"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn the_registry_record_for_a_loopback_connection_carries_no_fd_and_tears_down_without_one()
-> Result<(), Box<dyn Error>> {
let supervisor = supervisor()?;
let (client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let handle = supervisor.spawn_loopback_connection(server_end)?;
let pid = handle.pid();
assert_eq!(
supervisor.connection_has_fd_guard(pid),
Some(false),
"the fd guard keeps a descriptor alive until readiness deregistration is \
acknowledged; a loopback registers no readiness and holds no descriptor, so \
the slot is honestly empty rather than filled with a placeholder"
);
let (_socket_client, socket_server) = tcp_pair()?;
let socket_handle = supervisor.spawn_connection(socket_server)?;
assert_eq!(
supervisor.connection_has_fd_guard(socket_handle.pid()),
Some(true),
"a socket-admitted record does hold its guard"
);
drop(client);
assert!(
wait_until(|| !supervisor.is_tracked(pid)),
"teardown completes for a record that never had a guard to release"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn the_mount_on_the_record_is_the_door_that_admitted_the_connection() -> Result<(), Box<dyn Error>>
{
let supervisor = supervisor()?;
let (_client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let loopback = supervisor.spawn_loopback_connection(server_end)?;
let (_socket_client, socket_server) = tcp_pair()?;
let socket = supervisor.spawn_connection(socket_server)?;
assert_eq!(
supervisor.connection_mount(loopback.pid()),
Some(MountKind::Loopback),
"the in-process door stamps Loopback"
);
assert_eq!(
supervisor.connection_mount(socket.pid()),
Some(MountKind::Tcp),
"the socket door stamps Tcp — the two doors are told apart, so a single \
hardcoded answer cannot satisfy this pin"
);
supervisor.shutdown();
Ok(())
}
#[test]
fn a_loopback_process_stamps_its_mount_before_any_inbound_byte_is_read()
-> Result<(), Box<dyn Error>> {
let runtime = Arc::new(ConnectionRuntime::for_tests(Arc::new(
LiminalConnectionServices::empty()?,
)));
let (_client, server_end) = LoopbackDuplex::bounded(PIN_RING_BYTES);
let holder = Arc::new(Mutex::new(Some(server_end)));
let process = super::LoopbackConnectionProcess::from_loopback_holder(runtime, &holder, None);
assert_eq!(
process.mount(),
MountKind::Loopback,
"the loopback door's stamp is on the state apply_frame builds the handler \
context from, before the transport has read anything"
);
Ok(())
}