use crate::transport::{Read, Stream, Write};
use darkbio_clock::Clock;
use darkbio_clock::sync::{Condvar, Mutex, MutexGuard};
use std::collections::VecDeque;
use std::io;
use std::sync::Arc;
use std::time::Instant;
pub type Duplex = Stream<Reader, Writer>;
impl Duplex {
pub fn into_halves(self) -> (Reader, Writer) {
let (reader, writer, _, _) = self.into_parts();
(reader, writer)
}
}
pub fn duplex(capacity: usize, clock: &Clock) -> (Duplex, Duplex) {
assert!(capacity > 0, "duplex capacity must be nonzero");
let incoming = Arc::new(Pipe::new(capacity, clock));
let outgoing = Arc::new(Pipe::new(capacity, clock));
(
endpoint(incoming.clone(), outgoing.clone()),
endpoint(outgoing, incoming),
)
}
fn endpoint(incoming: Arc<Pipe>, outgoing: Arc<Pipe>) -> Duplex {
Stream::new(
Reader {
pipe: incoming.clone(),
deadline: None,
},
Writer {
pipe: outgoing.clone(),
deadline: None,
},
move || {
incoming.close_reader();
outgoing.close_writer();
},
)
}
#[derive(Debug)]
pub struct Reader {
pipe: Arc<Pipe>,
deadline: Option<Instant>,
}
impl io::Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut state = self.pipe.lock();
loop {
if buf.is_empty() || !state.reader_open {
return Ok(0);
}
if !state.bytes.is_empty() {
let count = buf.len().min(state.bytes.len());
let (front, back) = state.bytes.as_slices();
let split = count.min(front.len());
buf[..split].copy_from_slice(&front[..split]);
buf[split..count].copy_from_slice(&back[..count - split]);
state.bytes.drain(..count);
drop(state);
self.pipe.changed.notify_all();
return Ok(count);
}
if !state.writer_open {
return Ok(0);
}
self.pipe.check_deadline(self.deadline)?;
state = self.pipe.wait(state, self.deadline);
}
}
}
impl Read for Reader {
fn clock(&self) -> Clock {
self.pipe.clock.clone()
}
fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
self.deadline = deadline;
Ok(())
}
}
impl Drop for Reader {
fn drop(&mut self) {
self.pipe.close_reader();
}
}
#[cfg(test)]
impl Reader {
pub(crate) fn forbid_waits(&self) {
self.pipe.lock().waits_forbidden = true;
}
}
#[derive(Debug)]
pub struct Writer {
pipe: Arc<Pipe>,
deadline: Option<Instant>,
}
impl io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut state = self.pipe.lock();
loop {
self.pipe.check_deadline(self.deadline)?;
if buf.is_empty() {
return Ok(0);
}
if !state.reader_open || !state.writer_open {
return Err(io::ErrorKind::BrokenPipe.into());
}
let count = buf.len().min(self.pipe.capacity - state.bytes.len());
if count > 0 {
state.bytes.extend(&buf[..count]);
drop(state);
self.pipe.changed.notify_all();
return Ok(count);
}
state = self.pipe.wait(state, self.deadline);
}
}
fn flush(&mut self) -> io::Result<()> {
let state = self.pipe.lock();
self.pipe.check_deadline(self.deadline)?;
if !state.writer_open || state.lost {
return Err(io::ErrorKind::BrokenPipe.into());
}
Ok(())
}
}
impl Write for Writer {
fn clock(&self) -> Clock {
self.pipe.clock.clone()
}
fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
self.deadline = Some(deadline);
Ok(())
}
}
impl Drop for Writer {
fn drop(&mut self) {
self.pipe.close_writer();
}
}
#[derive(Debug)]
struct Pipe {
clock: Clock,
capacity: usize,
state: Mutex<State>,
changed: Condvar,
}
#[derive(Debug)]
struct State {
bytes: VecDeque<u8>,
reader_open: bool,
writer_open: bool,
lost: bool,
#[cfg(test)]
waiting: usize,
#[cfg(test)]
waits_forbidden: bool,
}
impl Pipe {
fn new(capacity: usize, clock: &Clock) -> Self {
Self {
clock: clock.clone(),
capacity,
state: Mutex::new(State {
bytes: VecDeque::with_capacity(capacity),
reader_open: true,
writer_open: true,
lost: false,
#[cfg(test)]
waiting: 0,
#[cfg(test)]
waits_forbidden: false,
}),
changed: Condvar::new(clock),
}
}
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().unwrap_or_else(|err| err.into_inner())
}
fn wait<'a>(
&self,
state: MutexGuard<'a, State>,
deadline: Option<Instant>,
) -> MutexGuard<'a, State> {
#[cfg(test)]
let state = {
let mut state = state;
assert!(!state.waits_forbidden, "memory pipe waited");
state.waiting += 1;
self.changed.notify_all();
state
};
let state = match deadline {
Some(deadline) => {
self.changed
.wait_deadline(state, deadline)
.unwrap_or_else(|err| err.into_inner())
.0
}
None => self
.changed
.wait(state)
.unwrap_or_else(|err| err.into_inner()),
};
#[cfg(test)]
let state = {
let mut state = state;
state.waiting -= 1;
state
};
state
}
fn check_deadline(&self, deadline: Option<Instant>) -> io::Result<()> {
if deadline.is_some_and(|deadline| self.clock.now() >= deadline) {
return Err(io::ErrorKind::TimedOut.into());
}
Ok(())
}
fn close_reader(&self) {
let mut state = self.lock();
state.reader_open = false;
state.lost |= !state.bytes.is_empty();
state.bytes.clear();
drop(state);
self.changed.notify_all();
}
fn close_writer(&self) {
self.lock().writer_open = false;
self.changed.notify_all();
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::transport::Closer;
use crate::transport::testing::test_clock;
use darkbio_clock::TestClock;
use std::io::{Read as _, Write as _};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
const TIMEOUT: Duration = Duration::from_millis(50);
fn halves(stream: Duplex) -> (Reader, Writer, Closer) {
let closer = stream.closer();
let (reader, writer) = stream.into_halves();
(reader, writer, closer)
}
fn blocked(pipe: &Pipe) {
let mut state = pipe.lock();
while state.waiting == 0 {
state = pipe.changed.wait(state).unwrap();
}
}
#[test]
fn test_blocked_read_expires_on_clock_deadline() {
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let clock = tester.clock();
let (host, _peer) = duplex(1, &clock);
let (mut reader, _writer) = host.into_halves();
let deadline = clock.now() + Duration::from_secs(5);
reader.set_read_deadline(Some(deadline)).unwrap();
let reading = thread::spawn(move || reader.read(&mut [0]));
tester.wait_registered(1);
assert_eq!(tester.next_deadline(), Some(deadline));
tester.advance_to(deadline);
assert_eq!(
reading.join().unwrap().unwrap_err().kind(),
io::ErrorKind::TimedOut
);
}
#[test]
fn test_blocked_write_expires_on_clock_deadline() {
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let clock = tester.clock();
let (host, _peer) = duplex(1, &clock);
let (_reader, mut writer) = host.into_halves();
writer.write_all(b"a").unwrap();
let deadline = clock.now() + Duration::from_secs(5);
writer.set_write_deadline(deadline).unwrap();
let writing = thread::spawn(move || writer.write(b"b"));
tester.wait_registered(1);
assert_eq!(tester.next_deadline(), Some(deadline));
tester.advance_to(deadline);
assert_eq!(
writing.join().unwrap().unwrap_err().kind(),
io::ErrorKind::TimedOut
);
}
#[test]
fn test_read_delivers_buffered_bytes_after_deadline() {
let mut tester = test_clock();
let clock = tester.clock();
let (host, peer) = duplex(1, &clock);
let (mut reader, _writer) = host.into_halves();
let (_peer_reader, mut writer) = peer.into_halves();
let deadline = clock.now() + Duration::from_secs(5);
reader.set_read_deadline(Some(deadline)).unwrap();
writer.write_all(b"a").unwrap();
tester.advance_to(deadline);
reader.forbid_waits();
let mut byte = [0];
assert_eq!(reader.read(&mut byte).unwrap(), 1);
assert_eq!(&byte, b"a");
assert_eq!(
reader.read(&mut byte).unwrap_err().kind(),
io::ErrorKind::TimedOut
);
}
#[test]
#[should_panic(expected = "duplex capacity must be nonzero")]
fn test_zero_capacity() {
duplex(0, &test_clock().clock());
}
#[test]
fn test_byte_stream_and_independent_deadlines() {
let tester = test_clock();
let clock = tester.clock();
let (host, ark) = duplex(4, &clock);
let (mut host_read, mut host_write, _host_close) = halves(host);
let (mut ark_read, mut ark_write, _ark_close) = halves(ark);
assert_eq!(host_read.read(&mut []).unwrap(), 0);
assert_eq!(host_write.write(b"abcdef").unwrap(), 4);
assert_eq!(host_write.write(&[]).unwrap(), 0);
host_write.flush().unwrap();
let mut first = [0; 2];
ark_read.read_exact(&mut first).unwrap();
assert_eq!(&first, b"ab");
host_write.write_all(b"ef").unwrap();
let mut rest = [0; 4];
ark_read.read_exact(&mut rest).unwrap();
assert_eq!(&rest, b"cdef");
ark_write.write_all(b"xy").unwrap();
host_read.set_read_deadline(Some(clock.now())).unwrap();
host_read.read_exact(&mut first).unwrap();
assert_eq!(&first, b"xy");
assert_eq!(
host_read.read(&mut first).unwrap_err().kind(),
io::ErrorKind::TimedOut
);
assert_eq!(host_read.read(&mut []).unwrap(), 0);
host_write.write_all(b"q").unwrap();
host_write.set_write_deadline(clock.now()).unwrap();
host_read.set_read_deadline(None).unwrap();
ark_write.write_all(b"uv").unwrap();
host_read.read_exact(&mut first).unwrap();
assert_eq!(&first, b"uv");
for bytes in [b"t".as_slice(), b""] {
assert_eq!(
host_write.write(bytes).unwrap_err().kind(),
io::ErrorKind::TimedOut
);
}
assert_eq!(
host_write.flush().unwrap_err().kind(),
io::ErrorKind::TimedOut
);
host_write
.set_write_deadline(clock.now() + Duration::from_secs(5))
.unwrap();
host_write.write_all(b"rs").unwrap();
host_write.flush().unwrap();
let mut accepted = [0; 3];
ark_read.read_exact(&mut accepted).unwrap();
assert_eq!(&accepted, b"qrs");
}
#[test]
fn test_read_across_wraparound() {
let tests: [(usize, usize, &[u8; 6], &[u8]); 5] = [
(1, 1, b"c-----", b"def"), (2, 2, b"cd----", b"ef"), (3, 3, b"cde---", b"f"), (4, 4, b"cdef--", b""), (6, 4, b"cdef--", b""), ];
for (i, (size, count, filled, rest)) in tests.into_iter().enumerate() {
let tester = test_clock();
let (host, ark) = duplex(4, &tester.clock());
let (_host_read, mut writer) = host.into_halves();
let (mut reader, _ark_write) = ark.into_halves();
writer.write_all(b"abcd").unwrap();
reader.read_exact(&mut [0; 2]).unwrap();
writer.write_all(b"ef").unwrap();
assert_eq!(
reader.pipe.lock().bytes.as_slices(),
(b"cd".as_slice(), b"ef".as_slice()),
"test {i}"
);
let mut buf = [b'-'; 6];
let copied = reader.read(&mut buf[..size]).unwrap();
assert_eq!(copied, count, "test {i}");
assert_eq!(&buf, filled, "test {i}");
drop(writer);
let mut left = Vec::new();
reader.read_to_end(&mut left).unwrap();
assert_eq!(left, rest, "test {i}");
}
}
#[test]
fn test_read_timeout_and_reuse() {
let mut tester = test_clock();
let clock = tester.clock();
let deadline = clock.now() + TIMEOUT;
let (host, ark) = duplex(1, &clock);
let (mut reader, _host_write, _host_close) = halves(host);
let (_ark_read, mut writer, _ark_close) = halves(ark);
let pipe = reader.pipe.clone();
let (done, result) = mpsc::channel();
let timed = thread::spawn(move || {
reader.set_read_deadline(Some(deadline)).unwrap();
let mut buf = [99];
let error = reader.read(&mut buf).unwrap_err();
assert!(clock.now() >= deadline);
assert_eq!(buf, [99]);
done.send((reader, error.kind())).unwrap();
});
tester.wait_registered(1);
tester.advance_to(deadline);
let (mut reader, kind) = result.recv().unwrap();
timed.join().unwrap();
assert_eq!(kind, io::ErrorKind::TimedOut);
reader.set_read_deadline(None).unwrap();
let (done, result) = mpsc::channel();
let reading = thread::spawn(move || {
let mut buf = [0];
reader.read_exact(&mut buf).unwrap();
done.send(buf).unwrap();
});
blocked(&pipe);
writer.write_all(b"x").unwrap();
assert_eq!(&result.recv().unwrap(), b"x");
reading.join().unwrap();
}
#[test]
fn test_write_timeout_and_reuse() {
let mut tester = test_clock();
let clock = tester.clock();
let deadline = clock.now() + TIMEOUT;
let (host, ark) = duplex(3, &clock);
let (_host_read, mut writer, _host_close) = halves(host);
let (mut reader, _ark_write, _ark_close) = halves(ark);
let (done, result) = mpsc::channel();
let writing = thread::spawn(move || {
writer.set_write_deadline(deadline).unwrap();
let error = writer.write_all(b"abcd").unwrap_err();
assert!(clock.now() >= deadline);
done.send((writer, error.kind())).unwrap();
});
tester.wait_registered(1);
tester.advance_to(deadline);
let (mut writer, kind) = result.recv().unwrap();
writing.join().unwrap();
assert_eq!(kind, io::ErrorKind::TimedOut);
let mut prefix = [0; 3];
reader.read_exact(&mut prefix).unwrap();
assert_eq!(&prefix, b"abc");
writer
.set_write_deadline(tester.clock().now() + Duration::from_secs(5))
.unwrap();
writer.write_all(b"ef").unwrap();
drop(writer);
let mut suffix = Vec::new();
reader.read_to_end(&mut suffix).unwrap();
assert_eq!(&suffix, b"ef");
}
#[test]
fn test_backpressure_wakes_writer() {
let tester = test_clock();
let deadline = tester.clock().now() + Duration::from_secs(5);
let (host, ark) = duplex(3, &tester.clock());
let (_host_read, mut writer, _host_close) = halves(host);
let (mut reader, _ark_write, _ark_close) = halves(ark);
writer.write_all(b"abc").unwrap();
let pipe = writer.pipe.clone();
let (done, result) = mpsc::channel();
let writing = thread::spawn(move || {
writer.set_write_deadline(deadline).unwrap();
done.send(writer.write_all(b"defgh")).unwrap();
});
blocked(&pipe);
reader.set_read_deadline(Some(deadline)).unwrap();
let mut bytes = [0; 8];
reader.read_exact(&mut bytes).unwrap();
assert_eq!(&bytes, b"abcdefgh");
result.recv().unwrap().unwrap();
writing.join().unwrap();
}
#[test]
fn test_shutdown_wakes_both_directions() {
for local in [false, true] {
let tester = test_clock();
let (host, ark) = duplex(1, &tester.clock());
let (mut reader, mut writer, closer) = halves(host);
writer.write_all(b"a").unwrap();
let incoming = reader.pipe.clone();
let outgoing = writer.pipe.clone();
let (read_done, read_result) = mpsc::channel();
let reading = thread::spawn(move || {
read_done.send(reader.read(&mut [0])).unwrap();
});
let (write_done, write_result) = mpsc::channel();
let writing = thread::spawn(move || {
write_done.send(writer.write(b"b")).unwrap();
});
blocked(&incoming);
blocked(&outgoing);
if local {
closer.close();
} else {
drop(ark);
}
assert_eq!(read_result.recv().unwrap().unwrap(), 0);
assert_eq!(
write_result.recv().unwrap().unwrap_err().kind(),
io::ErrorKind::BrokenPipe
);
reading.join().unwrap();
writing.join().unwrap();
}
}
#[test]
fn test_shutdown_drains_output_and_discards_input() {
let tester = test_clock();
let clock = tester.clock();
let (host, ark) = duplex(3, &clock);
let (mut host_read, mut host_write, closer) = halves(host);
let (mut ark_read, mut ark_write, _ark_close) = halves(ark);
host_write.write_all(b"abc").unwrap();
ark_write.write_all(b"xy").unwrap();
host_read.set_read_deadline(Some(clock.now())).unwrap();
ark_read.set_read_deadline(Some(clock.now())).unwrap();
closer.close();
closer.close();
assert_eq!(host_read.read(&mut [0]).unwrap(), 0);
let mut bytes = Vec::new();
ark_read.read_to_end(&mut bytes).unwrap();
assert_eq!(&bytes, b"abc");
for writer in [&mut host_write, &mut ark_write] {
assert_eq!(
writer.write(b"z").unwrap_err().kind(),
io::ErrorKind::BrokenPipe
);
assert_eq!(
writer.flush().unwrap_err().kind(),
io::ErrorKind::BrokenPipe
);
}
}
#[test]
fn test_flush_after_peer_drained_and_closed() {
let tester = test_clock();
let (host, ark) = duplex(4, &tester.clock());
let (_host_read, mut host_write) = host.into_halves();
let (mut ark_read, _ark_write) = ark.into_halves();
host_write.write_all(b"ab").unwrap();
let mut bytes = [0; 2];
ark_read.read_exact(&mut bytes).unwrap();
drop(ark_read);
host_write.flush().unwrap();
assert_eq!(
host_write.write(b"c").unwrap_err().kind(),
io::ErrorKind::BrokenPipe
);
}
#[test]
fn test_into_halves_and_independent_drop() {
let tester = test_clock();
let (host, ark) = duplex(4, &tester.clock());
let (mut host_read, mut host_write) = host.set_write_timeout(Duration::ZERO).into_halves();
let (mut ark_read, mut ark_write) = ark.into_halves();
host_write.write_all(b"abc").unwrap();
drop(host_write);
let mut bytes = Vec::new();
ark_read.read_to_end(&mut bytes).unwrap();
assert_eq!(&bytes, b"abc");
ark_write.write_all(b"xy").unwrap();
let mut reply = [0; 2];
host_read.read_exact(&mut reply).unwrap();
assert_eq!(&reply, b"xy");
drop(host_read);
assert_eq!(
ark_write.write(b"z").unwrap_err().kind(),
io::ErrorKind::BrokenPipe
);
}
#[test]
fn test_protocol_round_trip() {
use crate::protocol::{self, Message};
use crate::transport::mock::self_attestation;
use darkbio_crypto::xdsa;
let tester = test_clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attestation = self_attestation(&signer);
let (host, ark) = duplex(64 * 1024, &tester.clock());
let mut server = protocol::Server::new(ark, signer, attestation);
let (client, _) = protocol::connect(host, &identity).unwrap();
let mut session = server.accept().unwrap();
let payload: Vec<u8> = (0..256 * 1024).map(|n| n as u8).collect();
let deadline = tester.clock().now() + Duration::from_secs(5);
let answer = client
.requester()
.request(payload.clone(), deadline)
.unwrap();
let (message, responder) = session.recv().unwrap();
assert_eq!(message, Message::Develop(payload.clone()));
let written = responder.reply(message, deadline).unwrap();
assert_eq!(answer.wait::<Vec<u8>>().unwrap(), payload);
written.wait().unwrap();
client.close();
server.close();
}
}