use super::*;
use std::net::TcpListener;
use std::time::Instant;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
const EAGAIN: i32 = 35;
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "freebsd")))]
const EAGAIN: i32 = 11;
const STALL_PAST_THE_WINDOW: Duration = Duration::from_millis(5_500);
const FAKE_WINDOW: Duration = Duration::from_millis(20);
struct StallingStream {
stalls: usize,
ready: Vec<u8>,
endless: bool,
}
impl StallingStream {
fn answering(stalls: usize, ready: Vec<u8>) -> Self {
Self {
stalls,
ready,
endless: false,
}
}
const fn silent() -> Self {
Self {
stalls: 0,
ready: Vec::new(),
endless: true,
}
}
}
impl FrameStream for StallingStream {
fn read_bytes(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.endless || self.stalls > 0 {
self.stalls = self.stalls.saturating_sub(1);
std::thread::sleep(FAKE_WINDOW);
return Err(io::Error::from_raw_os_error(EAGAIN));
}
let take = self.ready.len().min(buf.len());
let Some(target) = buf.get_mut(..take) else {
return Ok(0);
};
target.copy_from_slice(self.ready.drain(..take).as_slice());
Ok(take)
}
fn write_all_bytes(&mut self, _bytes: &[u8]) -> io::Result<()> {
Ok(())
}
fn flush_bytes(&mut self) -> io::Result<()> {
Ok(())
}
fn set_read_deadline(&mut self, _timeout: Duration) -> io::Result<()> {
Ok(())
}
}
fn fixture_failure(detail: &str) -> SdkError {
SdkError::Protocol {
description: detail.to_string(),
}
}
const fn connect_ack() -> Frame {
Frame::ConnectAck {
flags: 0,
selected_version: CLIENT_MAX_VERSION,
capabilities: 0,
}
}
fn frame_bytes(frame: &Frame) -> Result<Vec<u8>, SdkError> {
let len = encoded_len(frame).map_err(|error| protocol_error(&error))?;
let mut bytes = vec![0_u8; len];
let written = encode(frame, &mut bytes).map_err(|error| protocol_error(&error))?;
bytes.truncate(written);
Ok(bytes)
}
fn bind_fake_server() -> Result<(TcpListener, String), SdkError> {
let listener = TcpListener::bind("127.0.0.1:0").map_err(|source| SdkError::Connection {
description: format!("failed to bind fake server: {source}"),
})?;
let address = listener
.local_addr()
.map_err(|source| SdkError::Connection {
description: format!("failed to read fake server address: {source}"),
})?
.to_string();
Ok((listener, address))
}
fn read_and_discard_one(socket: &mut TcpStream, buffer: &mut Vec<u8>) -> Result<(), SdkError> {
loop {
match decode(buffer) {
Ok((_, consumed)) => {
buffer.drain(..consumed);
return Ok(());
}
Err(
ProtocolError::IncompleteHeader { .. } | ProtocolError::TruncatedPayload { .. },
) => {
let mut chunk = [0_u8; 512];
let read = socket
.read(&mut chunk)
.map_err(|source| SdkError::Connection {
description: format!("fake server read failed: {source}"),
})?;
if read == 0 {
return Err(SdkError::Connection {
description: "fake server: client closed before a full frame".to_string(),
});
}
buffer.extend_from_slice(chunk.get(..read).unwrap_or(&[]));
}
Err(error) => return Err(protocol_error(&error)),
}
}
}
#[test]
fn a_closed_receive_window_is_a_wait_not_a_lost_connection() -> Result<(), SdkError> {
let stream = StallingStream::answering(3, frame_bytes(&connect_ack())?);
let connection = Connection::established(stream, &[])?;
drop(connection);
Ok(())
}
#[test]
fn a_real_socket_survives_a_reply_slower_than_its_receive_window() -> Result<(), SdkError> {
let (listener, address) = bind_fake_server()?;
let ack = frame_bytes(&connect_ack())?;
let split = ack.len() / 2;
let server = std::thread::spawn(move || -> Result<(), SdkError> {
let (mut socket, _peer) = listener.accept().map_err(|source| SdkError::Connection {
description: format!("fake server accept failed: {source}"),
})?;
let mut buffer = Vec::new();
read_and_discard_one(&mut socket, &mut buffer)?;
std::thread::sleep(STALL_PAST_THE_WINDOW);
socket
.write_all(&ack)
.map_err(|source| SdkError::Connection {
description: format!("fake server write failed: {source}"),
})?;
read_and_discard_one(&mut socket, &mut buffer)?;
socket
.write_all(ack.get(..split).unwrap_or(&[]))
.map_err(|source| SdkError::Connection {
description: format!("fake server head write failed: {source}"),
})?;
socket.flush().map_err(|source| SdkError::Connection {
description: format!("fake server flush failed: {source}"),
})?;
std::thread::sleep(STALL_PAST_THE_WINDOW);
socket
.write_all(ack.get(split..).unwrap_or(&[]))
.map_err(|source| SdkError::Connection {
description: format!("fake server tail write failed: {source}"),
})?;
let mut scratch = [0_u8; 512];
while socket.read(&mut scratch).unwrap_or(0) > 0 {}
Ok(())
});
let mut connection = Connection::connect_with_auth(&address, &[])?;
let replied = connection.round_trip(&connect_ack())?;
assert!(
matches!(replied, Frame::ConnectAck { .. }),
"expected the mid-frame-stalled reply to complete, received {replied:?}"
);
drop(connection);
server.join().ok();
Ok(())
}
#[test]
fn an_expired_response_deadline_names_the_timeout_and_never_the_errno() -> Result<(), SdkError> {
const BUDGET: Duration = Duration::from_millis(120);
let mut connection = Connection {
stream: StallingStream::silent(),
buffer: Vec::new(),
open_conversations: BTreeSet::new(),
};
let started = Instant::now();
let outcome = connection.receive_within(BUDGET);
let waited = started.elapsed();
let error = match outcome {
Ok(frame) => {
return Err(fixture_failure(&format!(
"a silent peer must not be waited on forever; a frame decoded: {frame:?}"
)));
}
Err(error) => error,
};
assert!(
waited >= BUDGET,
"the deadline ended the wait early: waited {waited:?} against a {BUDGET:?} budget"
);
let SdkError::Connection { description } = &error else {
return Err(fixture_failure(&format!(
"expected a Connection error naming the timeout, received {error:?}"
)));
};
assert!(
description.contains("timed out") && description.contains("waiting for a server response"),
"the deadline error does not name a timeout: {description}"
);
assert!(
!description.contains("os error")
&& !description
.to_lowercase()
.contains("temporarily unavailable"),
"the deadline error leaked the raw receive-window errno: {description}"
);
Ok(())
}
#[test]
fn the_response_deadline_outlives_a_single_receive_window() {
assert!(
RESPONSE_DEADLINE > IO_TIMEOUT,
"the response deadline ({RESPONSE_DEADLINE:?}) must span more than one \
receive window ({IO_TIMEOUT:?})"
);
}
const STALL_PAST_A_PUMP_WINDOW: Duration = Duration::from_secs(8);
#[test]
fn a_quiet_pump_read_reports_silence_within_the_callers_budget() -> Result<(), SdkError> {
const BUDGET: Duration = Duration::from_millis(120);
let mut connection = Connection {
stream: StallingStream::silent(),
buffer: Vec::new(),
open_conversations: BTreeSet::new(),
};
let started = Instant::now();
let outcome = connection.receive_optional_within(BUDGET)?;
let waited = started.elapsed();
if let Some(frame) = outcome {
return Err(fixture_failure(&format!(
"a silent peer produced a frame: {frame:?}"
)));
}
assert!(
waited >= BUDGET,
"the pump read reported silence before its budget was spent: waited {waited:?} \
against a {BUDGET:?} budget"
);
assert!(
waited < RESPONSE_DEADLINE / 10,
"the pump read waited {waited:?} for a {BUDGET:?} budget — it is inheriting the \
reply-owed response deadline ({RESPONSE_DEADLINE:?}) instead of its own bound"
);
Ok(())
}
#[test]
fn a_zero_budget_polls_without_arming_a_read() -> Result<(), SdkError> {
let (listener, address) = bind_fake_server()?;
let ack = frame_bytes(&connect_ack())?;
let server = std::thread::spawn(move || -> Result<(), SdkError> {
let (mut socket, _peer) = listener.accept().map_err(|source| SdkError::Connection {
description: format!("fake server accept failed: {source}"),
})?;
let mut buffer = Vec::new();
read_and_discard_one(&mut socket, &mut buffer)?;
socket
.write_all(&ack)
.map_err(|source| SdkError::Connection {
description: format!("fake server handshake write failed: {source}"),
})?;
read_and_discard_one(&mut socket, &mut buffer)?;
socket
.write_all(&ack)
.map_err(|source| SdkError::Connection {
description: format!("fake server second write failed: {source}"),
})?;
let mut scratch = [0_u8; 512];
while socket.read(&mut scratch).unwrap_or(0) > 0 {}
Ok(())
});
let mut connection = Connection::connect_with_auth(&address, &[])?;
let polled = connection.receive_optional_within(Duration::ZERO)?;
assert!(
polled.is_none(),
"a zero-budget poll of an empty buffer produced a frame: {polled:?}"
);
connection.send(&connect_ack())?;
let replied = connection.receive_optional_within(Duration::from_secs(10))?;
assert!(
matches!(replied, Some(Frame::ConnectAck { .. })),
"the connection did not survive a zero-budget poll: {replied:?}"
);
drop(connection);
server.join().ok();
Ok(())
}
#[test]
fn a_pump_read_spends_the_callers_budget_not_one_receive_window() -> Result<(), SdkError> {
const CALLER_BUDGET: Duration = Duration::from_secs(30);
assert!(
STALL_PAST_A_PUMP_WINDOW > IO_TIMEOUT
&& STALL_PAST_A_PUMP_WINDOW > crate::PARTICIPANT_PUMP_WINDOW,
"the fixture no longer crosses a closed window, so it would pass against a read \
that gave up on the first one"
);
let (listener, address) = bind_fake_server()?;
let ack = frame_bytes(&connect_ack())?;
let server = std::thread::spawn(move || -> Result<(), SdkError> {
let (mut socket, _peer) = listener.accept().map_err(|source| SdkError::Connection {
description: format!("fake server accept failed: {source}"),
})?;
let mut buffer = Vec::new();
read_and_discard_one(&mut socket, &mut buffer)?;
socket
.write_all(&ack)
.map_err(|source| SdkError::Connection {
description: format!("fake server handshake write failed: {source}"),
})?;
read_and_discard_one(&mut socket, &mut buffer)?;
std::thread::sleep(STALL_PAST_A_PUMP_WINDOW);
socket
.write_all(&ack)
.map_err(|source| SdkError::Connection {
description: format!("fake server slow write failed: {source}"),
})?;
let mut scratch = [0_u8; 512];
while socket.read(&mut scratch).unwrap_or(0) > 0 {}
Ok(())
});
let mut connection = Connection::connect_with_auth(&address, &[])?;
connection.send(&connect_ack())?;
let started = Instant::now();
let replied = connection.receive_optional_within(CALLER_BUDGET)?;
let waited = started.elapsed();
assert!(
matches!(replied, Some(Frame::ConnectAck { .. })),
"a reply slower than one receive window was reported as silence after {waited:?}: \
{replied:?} — the bounded read is ending on the window instead of the budget"
);
assert!(
waited >= IO_TIMEOUT,
"the fixture did not actually cross a closed receive window: waited {waited:?}"
);
drop(connection);
server.join().ok();
Ok(())
}
#[test]
fn the_pump_window_is_shorter_than_the_reply_owed_deadline() {
assert!(
crate::PARTICIPANT_PUMP_WINDOW < RESPONSE_DEADLINE,
"the pump window ({:?}) must be shorter than the reply-owed response deadline \
({RESPONSE_DEADLINE:?})",
crate::PARTICIPANT_PUMP_WINDOW
);
}