use super::self_attestation;
use crate::transport::{Client, Closer, Error, Event, Read, Sender, Server, Stream, Write};
use darkbio_crypto::xdsa;
use std::collections::VecDeque;
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, mpsc};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
const PATIENCE: Duration = Duration::from_secs(8);
const FAULT_TIMEOUT: Duration = Duration::from_millis(250);
const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(2);
const HANDSHAKE_CAPACITY: usize = 64 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
pub enum Scenario {
FailedPrelude { read: bool },
HandshakeFailure {
ack: bool,
flush: bool,
timeout: bool,
},
RepeatedAttempts(u8),
AbandonedHello,
SilentHandshake { ack: bool },
HandshakeNoise { server: bool },
Reconnect { both_directions: bool },
Backlog(u8),
ServerTimeout { flush: bool },
Shutdown { handshake: bool, server: bool },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Operation {
Read,
Write,
Flush,
}
impl Operation {
fn index(self) -> usize {
match self {
Self::Read => 0,
Self::Write => 1,
Self::Flush => 2,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum FaultKind {
Error(io::ErrorKind),
Timeout,
}
#[derive(Clone, Copy, Debug)]
struct Fault {
operation: Operation,
after_flushes: usize,
kind: FaultKind,
}
#[derive(Debug, Default)]
struct State {
bytes: VecDeque<u8>,
closed: bool,
paused: [bool; 3],
waiting: [usize; 3],
flushes: usize,
delimiters: usize,
read_deadline: Option<Instant>, read_error: Option<io::ErrorKind>, faults: VecDeque<Fault>,
}
#[derive(Debug)]
pub(crate) struct Pipe {
capacity: usize,
state: Mutex<State>,
changed: Condvar,
}
impl Pipe {
pub(crate) fn new(capacity: usize) -> Arc<Self> {
Arc::new(Self {
capacity,
state: Mutex::new(State::default()),
changed: Condvar::new(),
})
}
pub(crate) fn close(&self) {
self.state.lock().unwrap().closed = true;
self.changed.notify_all();
}
pub(crate) fn pause(&self, operation: Operation, paused: bool) {
self.state.lock().unwrap().paused[operation.index()] = paused;
self.changed.notify_all();
}
pub(crate) fn fail_read_deadline(&self, error: io::ErrorKind) {
self.state.lock().unwrap().read_error = Some(error);
}
pub(crate) fn fault(&self, operation: Operation, after_flushes: usize, kind: FaultKind) {
self.state.lock().unwrap().faults.push_back(Fault {
operation,
after_flushes,
kind,
});
self.changed.notify_all();
}
pub(crate) fn wait_blocked(&self, operation: Operation) {
let deadline = Instant::now() + PATIENCE;
let mut state = self.state.lock().unwrap();
while state.waiting[operation.index()] == 0 {
if Instant::now() >= deadline {
drop(state);
panic!("{operation:?} never blocked");
}
state = self
.changed
.wait_timeout(state, deadline.saturating_duration_since(Instant::now()))
.unwrap()
.0;
}
}
fn wait<'a>(
&self,
mut state: MutexGuard<'a, State>,
operation: Operation,
deadline: Option<Instant>,
) -> io::Result<MutexGuard<'a, State>> {
state.waiting[operation.index()] += 1;
self.changed.notify_all();
let (mut state, timed_out) = match deadline {
Some(deadline) => {
let (state, timeout) = self
.changed
.wait_timeout(state, deadline.saturating_duration_since(Instant::now()))
.unwrap();
(state, timeout.timed_out())
}
None => (self.changed.wait(state).unwrap(), false),
};
state.waiting[operation.index()] -= 1;
self.changed.notify_all();
if timed_out && !state.closed {
Err(io::ErrorKind::TimedOut.into())
} else {
Ok(state)
}
}
fn take_fault(state: &mut State, operation: Operation) -> Option<FaultKind> {
let index = state.faults.iter().position(|fault| {
fault.operation == operation && fault.after_flushes <= state.flushes
})?;
Some(state.faults.remove(index).unwrap().kind)
}
fn fail(
&self,
mut state: MutexGuard<'_, State>,
operation: Operation,
kind: FaultKind,
deadline: Option<Instant>,
) -> io::Result<usize> {
match kind {
FaultKind::Error(kind) => Err(kind.into()),
FaultKind::Timeout => loop {
if state.closed {
return Err(io::ErrorKind::BrokenPipe.into());
}
if deadline.is_some_and(|deadline| Instant::now() >= deadline) {
return Err(io::ErrorKind::TimedOut.into());
}
state = self.wait(state, operation, deadline)?;
},
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct Adapter {
pipe: Arc<Pipe>,
read_deadline: Option<Instant>,
write_deadline: Instant,
}
impl Adapter {
pub(crate) fn new(pipe: Arc<Pipe>) -> Self {
let now = Instant::now();
Self {
pipe,
read_deadline: None,
write_deadline: now,
}
}
}
impl Read for Adapter {
fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
self.read_deadline = deadline;
let mut state = self.pipe.state.lock().unwrap();
state.read_deadline = deadline;
if let Some(error) = state.read_error.take() {
return Err(error.into());
}
Ok(())
}
}
impl io::Read for Adapter {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let deadline = self.read_deadline;
let mut state = self.pipe.state.lock().unwrap();
loop {
if deadline.is_some_and(|deadline| Instant::now() >= deadline) {
return Err(io::ErrorKind::TimedOut.into());
}
if state.closed {
return Ok(0);
}
if let Some(fault) = Pipe::take_fault(&mut state, Operation::Read) {
return self.pipe.fail(state, Operation::Read, fault, deadline);
}
if !state.paused[Operation::Read.index()] && !state.bytes.is_empty() {
let len = buf.len().min(state.bytes.len());
for byte in &mut buf[..len] {
*byte = state.bytes.pop_front().unwrap();
}
self.pipe.changed.notify_all();
return Ok(len);
}
state = self.pipe.wait(state, Operation::Read, deadline)?;
}
}
}
impl Write for Adapter {
fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
self.write_deadline = deadline;
Ok(())
}
}
impl io::Write for Adapter {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
let deadline = self.write_deadline;
let mut state = self.pipe.state.lock().unwrap();
loop {
if Instant::now() >= deadline {
return Err(io::ErrorKind::TimedOut.into());
}
if state.closed {
return Err(io::ErrorKind::BrokenPipe.into());
}
if let Some(fault) = Pipe::take_fault(&mut state, Operation::Write) {
return self
.pipe
.fail(state, Operation::Write, fault, Some(deadline));
}
if !state.paused[Operation::Write.index()] && state.bytes.len() < self.pipe.capacity {
let len = bytes.len().min(self.pipe.capacity - state.bytes.len());
state.bytes.extend(&bytes[..len]);
state.delimiters += bytes[..len].iter().filter(|byte| **byte == 0).count();
self.pipe.changed.notify_all();
return Ok(len);
}
state = self.pipe.wait(state, Operation::Write, Some(deadline))?;
}
}
fn flush(&mut self) -> io::Result<()> {
let deadline = self.write_deadline;
let mut state = self.pipe.state.lock().unwrap();
loop {
if Instant::now() >= deadline {
return Err(io::ErrorKind::TimedOut.into());
}
if state.closed {
return Err(io::ErrorKind::BrokenPipe.into());
}
if let Some(fault) = Pipe::take_fault(&mut state, Operation::Flush) {
return self
.pipe
.fail(state, Operation::Flush, fault, Some(deadline))
.map(|_| ());
}
if !state.paused[Operation::Flush.index()] {
state.flushes += 1;
return Ok(());
}
state = self.pipe.wait(state, Operation::Flush, Some(deadline))?;
}
}
}
struct Peers {
client: Client<Adapter, Adapter>,
identity: xdsa::PublicKey,
incoming: Arc<Pipe>,
outgoing: Arc<Pipe>,
events: mpsc::Receiver<Result<Event<Adapter>, Error>>,
closer: Closer,
server_closer: Closer,
server: Option<JoinHandle<()>>,
senders: Vec<JoinHandle<()>>,
watchdog: Option<JoinHandle<()>>,
stop_watchdog: Option<mpsc::Sender<()>>,
expired: Arc<AtomicBool>,
}
impl Peers {
fn new(outgoing_capacity: usize, incoming_capacity: usize, write_timeout: Duration) -> Self {
let incoming = Pipe::new(incoming_capacity);
let outgoing = Pipe::new(outgoing_capacity);
let stream = |reader: &Arc<Pipe>, writer: &Arc<Pipe>| {
Stream::new(
Adapter::new(reader.clone()),
Adapter::new(writer.clone()),
{
let incoming = incoming.clone();
let outgoing = outgoing.clone();
move || {
incoming.close();
outgoing.close();
}
},
)
.set_write_timeout(write_timeout)
};
let client_stream = stream(&incoming, &outgoing);
let server_stream = stream(&outgoing, &incoming);
let closer = client_stream.closer();
let server_closer = server_stream.closer();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attestation = self_attestation(&signer);
let (sent, events) = mpsc::channel();
let server = thread::spawn(move || {
let mut server = Server::new(server_stream, signer, attestation)
.set_handshake_timeout(HANDSHAKE_TIMEOUT);
loop {
let result = server.recv();
if matches!(result, Err(Error::Terminated)) {
break;
}
if sent.send(result).is_err() {
break;
}
}
});
let expired = Arc::new(AtomicBool::new(false));
let (stop_watchdog, stopped) = mpsc::channel();
let watchdog = thread::spawn({
let expired = expired.clone();
let closer = closer.clone();
let server_closer = server_closer.clone();
move || {
if stopped.recv_timeout(PATIENCE).is_err() {
expired.store(true, Ordering::Release);
closer.close();
server_closer.close();
}
}
});
Self {
client: Client::new(client_stream).set_handshake_timeout(HANDSHAKE_TIMEOUT),
identity,
incoming,
outgoing,
events,
closer,
server_closer,
server: Some(server),
senders: Vec::new(),
watchdog: Some(watchdog),
stop_watchdog: Some(stop_watchdog),
expired,
}
}
fn connect(&mut self) -> (Sender<Adapter>, Sender<Adapter>) {
let (client, _) = self.client.connect(&self.identity).unwrap();
loop {
match self.event() {
Event::Connected(server) => return (client, server),
Event::Disconnected | Event::Message(_) => {}
}
}
}
fn recover(&mut self, old: &[Sender<Adapter>]) {
let (client, _) = self.client.connect(&self.identity).unwrap();
for sender in old {
assert!(matches!(
sender.send(b"old"),
Err(Error::EncryptionFailed(_))
));
}
client.send(b"fresh ping").unwrap();
let mut server = None;
loop {
match self.events.recv_timeout(PATIENCE).unwrap() {
Ok(Event::Connected(sender)) => server = Some(sender),
Ok(Event::Message(bytes)) => {
assert_eq!(bytes, b"fresh ping");
break;
}
Ok(Event::Disconnected) | Err(Error::RecvFailed(_) | Error::SendFailed(_)) => {}
event => panic!("unexpected server event: {event:?}"),
}
}
let server = server.expect("fresh connection event before fresh message");
self.round_trip(&client, &server);
}
fn event(&self) -> Event<Adapter> {
self.events.recv_timeout(PATIENCE).unwrap().unwrap()
}
fn send(
&mut self,
sender: Sender<Adapter>,
bytes: Vec<u8>,
release_input: bool,
) -> mpsc::Receiver<Result<(), Error>> {
let (sent, result) = mpsc::channel();
let outgoing = self.outgoing.clone();
self.senders.push(thread::spawn(move || {
let result = sender.send(&bytes);
if release_input {
outgoing.pause(Operation::Read, false);
}
let _ = sent.send(result);
}));
result
}
fn round_trip(&mut self, client: &Sender<Adapter>, server: &Sender<Adapter>) {
client.send(b"ping").unwrap();
assert!(matches!(self.event(), Event::Message(bytes) if bytes == b"ping"));
server.send(b"pong").unwrap();
assert_eq!(self.client.recv().unwrap(), b"pong");
assert!(!self.incoming.state.lock().unwrap().closed);
assert!(!self.outgoing.state.lock().unwrap().closed);
assert!(
!self.expired.load(Ordering::Acquire),
"scenario watchdog expired"
);
}
fn fail_prelude(&mut self, read: bool) {
let pipe = if read {
self.incoming.clone()
} else {
self.outgoing.clone()
};
let operation = if read {
Operation::Read
} else {
Operation::Write
};
pipe.pause(operation, true);
let client = &mut self.client;
let identity = &self.identity;
let result = thread::scope(|scope| {
let connecting = scope.spawn(|| client.connect(identity));
pipe.wait_blocked(operation);
pipe.fault(operation, 0, FaultKind::Error(io::ErrorKind::Other));
pipe.pause(operation, false);
connecting.join().unwrap()
});
match result {
Err(Error::RecvFailed(err)) if read => assert_eq!(err.kind(), io::ErrorKind::Other),
Err(Error::SendFailed(err)) if !read => assert_eq!(err.kind(), io::ErrorKind::Other),
result => panic!("unexpected prelude failure: {:?}", result.map(|_| ())),
}
assert!(!self.incoming.state.lock().unwrap().closed);
assert!(!self.outgoing.state.lock().unwrap().closed);
}
}
impl Drop for Peers {
fn drop(&mut self) {
self.closer.close();
self.server_closer.close();
for sender in self.senders.drain(..) {
let result = sender.join();
if !thread::panicking() {
result.unwrap();
}
}
let result = self.server.take().unwrap().join();
if !thread::panicking() {
result.unwrap();
}
let _ = self.stop_watchdog.take().unwrap().send(());
self.watchdog.take().unwrap().join().unwrap();
}
}
pub fn run(scenario: Scenario) {
#[cfg(feature = "fuzz")]
super::seed::seed(super::seed::TRANSPORT_DUPLEX, &[scenario]);
match scenario {
Scenario::FailedPrelude { read } => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, 64, FAULT_TIMEOUT);
peers.fail_prelude(read);
let (client, server) = peers.connect();
peers.round_trip(&client, &server);
}
Scenario::HandshakeFailure {
ack,
flush,
timeout,
} => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, HANDSHAKE_CAPACITY, FAULT_TIMEOUT);
let (client, server) = peers.connect();
let mut old = vec![client, server];
let operation = if flush {
Operation::Flush
} else {
Operation::Write
};
let pipe = if ack {
&peers.outgoing
} else {
&peers.incoming
};
let after_flushes = pipe.state.lock().unwrap().flushes + if ack { 2 } else { 0 };
let kind = if timeout {
FaultKind::Timeout
} else {
FaultKind::Error(io::ErrorKind::Other)
};
pipe.fault(operation, after_flushes, kind);
if timeout && !ack {
peers.incoming.pause(Operation::Read, true);
}
let first = peers.client.connect(&peers.identity);
if ack {
let expected = if timeout {
io::ErrorKind::TimedOut
} else {
io::ErrorKind::Other
};
assert!(matches!(first, Err(Error::SendFailed(ref err)) if err.kind() == expected));
} else if timeout || !flush {
assert!(
matches!(first, Err(Error::RecvFailed(ref err)) if err.kind() == io::ErrorKind::TimedOut)
);
} else {
assert!(
first.is_ok()
|| matches!(first, Err(Error::RecvFailed(ref err)) if err.kind() == io::ErrorKind::TimedOut)
);
}
if let Ok((sender, _)) = first {
old.push(sender);
}
assert!(matches!(peers.event(), Event::Disconnected));
if !ack {
let expected = if timeout {
io::ErrorKind::TimedOut
} else {
io::ErrorKind::Other
};
assert!(matches!(
peers.events.recv_timeout(PATIENCE).unwrap(),
Err(Error::SendFailed(err)) if err.kind() == expected
));
}
peers.incoming.pause(Operation::Read, false);
peers.recover(&old);
}
Scenario::RepeatedAttempts(count) => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, 64, FAULT_TIMEOUT);
for attempt in 0..2 + usize::from(count % 3) {
peers.fail_prelude(attempt % 2 == 0);
let (client, server) = peers.connect();
peers.round_trip(&client, &server);
}
}
Scenario::AbandonedHello => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, 64, WRITE_TIMEOUT);
peers.incoming.pause(Operation::Read, true);
let client = &mut peers.client;
let identity = &peers.identity;
let incoming = &peers.incoming;
thread::scope(|scope| {
let connecting = scope.spawn(|| client.connect(identity));
incoming.wait_blocked(Operation::Write);
incoming.fault(Operation::Read, 0, FaultKind::Error(io::ErrorKind::Other));
incoming.pause(Operation::Read, false);
assert!(matches!(
connecting.join().unwrap(),
Err(Error::RecvFailed(err)) if err.kind() == io::ErrorKind::Other
));
});
let (client, server) = peers.connect();
assert_eq!(peers.incoming.state.lock().unwrap().flushes, 2);
peers.round_trip(&client, &server);
}
Scenario::SilentHandshake { ack } => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, HANDSHAKE_CAPACITY, FAULT_TIMEOUT);
let (client, server) = peers.connect();
let started = Instant::now();
if ack {
let after_flushes = peers.outgoing.state.lock().unwrap().flushes + 2;
peers.outgoing.fault(
Operation::Write,
after_flushes,
FaultKind::Error(io::ErrorKind::Other),
);
assert!(matches!(
peers.client.connect(&peers.identity),
Err(Error::SendFailed(_))
));
} else {
peers.client.send_frame_blob(&[]).unwrap();
}
assert!(matches!(peers.event(), Event::Disconnected));
assert!(
matches!(peers.events.recv_timeout(PATIENCE).unwrap(), Err(Error::RecvFailed(ref err)) if err.kind() == io::ErrorKind::TimedOut)
);
assert!(
(HANDSHAKE_TIMEOUT..HANDSHAKE_TIMEOUT + Duration::from_secs(2))
.contains(&started.elapsed()),
"server did not use its configured handshake budget"
);
peers.recover(&[client, server]);
}
Scenario::HandshakeNoise { server } => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, HANDSHAKE_CAPACITY, FAULT_TIMEOUT);
let (old_client, old_server) = peers.connect();
let stopped = AtomicBool::new(false);
let started = Instant::now();
if server {
peers.client.send_frame_blob(&[]).unwrap();
assert!(matches!(peers.event(), Event::Disconnected));
let client = &mut peers.client;
let events = &peers.events;
thread::scope(|scope| {
let sending = scope.spawn(|| {
while !stopped.load(Ordering::Relaxed) {
client.send_frame_blob(&[]).unwrap();
thread::sleep(Duration::from_millis(1));
}
});
let result = events.recv_timeout(PATIENCE);
stopped.store(true, Ordering::Relaxed);
sending.join().unwrap();
assert!(
matches!(result.unwrap(), Err(Error::RecvFailed(ref err)) if err.kind() == io::ErrorKind::TimedOut)
);
});
} else {
peers.outgoing.pause(Operation::Read, true);
let mut junk = Adapter::new(peers.incoming.clone());
thread::scope(|scope| {
let sending = scope.spawn(|| {
while !stopped.load(Ordering::Relaxed) {
junk.set_write_deadline(Instant::now() + FAULT_TIMEOUT)
.unwrap();
io::Write::write_all(&mut junk, &[0, 1, 0, 2, 42, 0]).unwrap();
thread::sleep(Duration::from_millis(1));
}
});
let result = peers.client.connect(&peers.identity);
stopped.store(true, Ordering::Relaxed);
sending.join().unwrap();
assert!(
matches!(result, Err(Error::RecvFailed(ref err)) if err.kind() == io::ErrorKind::TimedOut)
);
});
peers.outgoing.pause(Operation::Read, false);
}
assert!(
(HANDSHAKE_TIMEOUT..HANDSHAKE_TIMEOUT + Duration::from_secs(2))
.contains(&started.elapsed()),
"noise changed the configured handshake budget"
);
peers.recover(&[old_client, old_server]);
}
Scenario::Reconnect { both_directions } => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, HANDSHAKE_CAPACITY, FAULT_TIMEOUT);
let (old_client, old_server) = peers.connect();
let client_send = if both_directions {
peers.outgoing.pause(Operation::Read, true);
let sent = peers.send(old_client.clone(), vec![1; 3 * HANDSHAKE_CAPACITY], false);
peers.outgoing.wait_blocked(Operation::Write);
Some(sent)
} else {
None
};
let server_send = peers.send(
old_server.clone(),
vec![2; 3 * HANDSHAKE_CAPACITY],
both_directions,
);
peers.incoming.wait_blocked(Operation::Write);
let (client, server) = peers.connect();
let sent = server_send.recv_timeout(PATIENCE).unwrap();
if both_directions {
assert!(
matches!(sent, Err(Error::SendFailed(err)) if err.kind() == io::ErrorKind::TimedOut)
);
} else {
sent.unwrap();
}
if let Some(sent) = client_send {
assert!(
matches!(sent.recv_timeout(PATIENCE).unwrap(), Err(Error::SendFailed(err)) if err.kind() == io::ErrorKind::TimedOut)
);
}
assert!(matches!(
old_client.send(b"old"),
Err(Error::EncryptionFailed(_))
));
assert!(matches!(
old_server.send(b"old"),
Err(Error::EncryptionFailed(_))
));
peers.round_trip(&client, &server);
}
Scenario::Backlog(extra) => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, HANDSHAKE_CAPACITY, WRITE_TIMEOUT);
let (old_client, old_server) = peers.connect();
for id in 0..33 + usize::from(extra) {
old_server.send(&[id as u8]).unwrap();
}
let (client, server) = peers.connect();
assert!(matches!(
old_client.send(b"old"),
Err(Error::EncryptionFailed(_))
));
assert!(matches!(
old_server.send(b"old"),
Err(Error::EncryptionFailed(_))
));
peers.round_trip(&client, &server);
}
Scenario::ServerTimeout { flush } => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, 64, FAULT_TIMEOUT);
let (_, old_server) = peers.connect();
let before = peers.incoming.state.lock().unwrap().delimiters;
if flush {
peers
.incoming
.fault(Operation::Flush, 0, FaultKind::Timeout);
}
let sent = peers.send(
old_server.clone(),
vec![3; if flush { 8 } else { 4096 }],
false,
);
peers.incoming.wait_blocked(if flush {
Operation::Flush
} else {
Operation::Write
});
assert!(matches!(
sent.recv_timeout(PATIENCE).unwrap(),
Err(Error::SendFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
assert_eq!(
peers.incoming.state.lock().unwrap().delimiters,
before + usize::from(flush)
);
assert!(matches!(
old_server.send(b"old"),
Err(Error::EncryptionFailed(_))
));
let (client, server) = peers.connect();
peers.round_trip(&client, &server);
}
Scenario::Shutdown { handshake, server } => {
let mut peers = Peers::new(HANDSHAKE_CAPACITY, HANDSHAKE_CAPACITY, WRITE_TIMEOUT);
let (old_client, old_server) = peers.connect();
if handshake {
peers.outgoing.pause(Operation::Read, true);
}
let closer = if server {
peers.server_closer.clone()
} else {
peers.closer.clone()
};
let client = &mut peers.client;
let identity = &peers.identity;
let incoming = &peers.incoming;
let outgoing = &peers.outgoing;
thread::scope(|scope| {
let (sent, received) = mpsc::channel();
scope.spawn(move || {
let result = if handshake {
client.connect(identity).map(|_| ())
} else {
client.recv().map(|_| ())
};
sent.send(result).unwrap();
});
incoming.wait_blocked(Operation::Read);
outgoing.wait_blocked(Operation::Read);
assert_eq!(
incoming.state.lock().unwrap().read_deadline.is_some(),
handshake
);
assert_eq!(outgoing.state.lock().unwrap().read_deadline, None);
let (closed, closure) = mpsc::channel();
scope.spawn(move || {
closer.close();
closed.send(()).unwrap();
});
closure.recv_timeout(Duration::from_secs(1)).unwrap();
assert!(matches!(
received.recv_timeout(Duration::from_secs(1)).unwrap(),
Err(Error::Terminated)
));
});
assert!(matches!(
peers.events.recv_timeout(Duration::from_secs(1)),
Err(mpsc::RecvTimeoutError::Disconnected)
));
assert!(old_client.send(b"old").is_err());
assert!(old_server.send(b"old").is_err());
assert!(!peers.expired.load(Ordering::Acquire));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handshake_output_failure_retry() {
for ack in [false, true] {
for flush in [false, true] {
for timeout in [false, true] {
run(Scenario::HandshakeFailure {
ack,
flush,
timeout,
});
}
}
}
}
#[test]
fn test_server_deadline_before_hello() {
run(Scenario::SilentHandshake { ack: false });
}
#[test]
fn test_server_deadline_before_ack() {
run(Scenario::SilentHandshake { ack: true });
}
#[test]
fn test_reset_stream_keeps_one_deadline() {
run(Scenario::HandshakeNoise { server: true });
}
#[test]
fn test_client_junk_keeps_one_deadline() {
run(Scenario::HandshakeNoise { server: false });
}
#[test]
fn test_shutdown_releases_peer_reads() {
for handshake in [false, true] {
for server in [false, true] {
run(Scenario::Shutdown { handshake, server });
}
}
}
#[test]
fn test_reconnect_drains_bounded_output() {
for both_directions in [false, true] {
run(Scenario::Reconnect { both_directions });
}
}
#[test]
fn test_reconnect_drains_stale_backlog() {
for extra in [0, 63] {
run(Scenario::Backlog(extra));
}
}
#[test]
fn test_server_timeout_preserves_stream() {
for flush in [false, true] {
run(Scenario::ServerTimeout { flush });
}
}
#[test]
fn test_prelude_failure_preserves_stream() {
for read in [false, true] {
run(Scenario::FailedPrelude { read });
}
}
#[test]
fn test_repeated_attempts_preserve_stream() {
run(Scenario::RepeatedAttempts(2));
}
#[test]
fn test_reconnect_drains_abandoned_hello() {
run(Scenario::AbandonedHello);
}
}