Skip to main content

darkbio_wire/memory/
mod.rs

1// wire-rs: encrypted protocol between Ark and host
2// Copyright 2026 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! Portable in-memory streams for local connections, emulators and tests.
8//!
9//! These streams synchronize through the clock's locks, with no sockets or
10//! worker threads. Split an endpoint with [`Duplex::into_halves`] for plain I/O.
11
12use crate::transport::{Read, Stream, Write};
13use darkbio_clock::Clock;
14use darkbio_clock::sync::{Condvar, Mutex, MutexGuard};
15use std::collections::VecDeque;
16use std::io;
17use std::sync::Arc;
18use std::time::Instant;
19
20/// One endpoint of an in-memory duplex connection, ready for a
21/// [`Client`](crate::transport::Client) or [`Server`](crate::transport::Server).
22pub type Duplex = Stream<Reader, Writer>;
23
24impl Duplex {
25    /// Takes the reader and writer out of the stream without closing either half.
26    ///
27    /// Each half closes its own direction on drop. Dropping the writer lets the
28    /// peer drain accepted output before EOF; dropping the reader refuses
29    /// further peer writes. Obtain a [`crate::transport::Closer`] with
30    /// [`Self::closer`] before splitting if you need to shut down both halves
31    /// from another thread.
32    ///
33    /// The stream's write timeout is discarded, and the halves have no deadline
34    /// until one is configured.
35    ///
36    /// ```
37    /// use darkbio_wire::{clock::Clock, memory};
38    /// use darkbio_wire::transport::Client;
39    ///
40    /// let (host, bus) = memory::duplex(64 * 1024, &Clock::real());
41    /// let (reader, writer) = bus.into_halves();
42    /// let client = Client::new(host);
43    /// // Move `reader` and `writer` to the bus's input and output pumps.
44    /// ```
45    pub fn into_halves(self) -> (Reader, Writer) {
46        let (reader, writer, _, _) = self.into_parts();
47        (reader, writer)
48    }
49}
50
51/// Creates two connected streams with `capacity` bytes of buffering per
52/// direction.
53///
54/// Both measure their deadlines on `clock`, and so does any transport on them.
55/// Reads wait for data and writes wait for buffer space, bounded by the
56/// deadlines the transport installs. Partial progress never refreshes a
57/// deadline. A timeout leaves the connection reusable and preserves any bytes
58/// already accepted. Flush checks its deadline but does not wait for the peer
59/// to consume output. Reads deliver available bytes, EOF and empty reads even
60/// after their deadline; the deadline only limits waiting for input. Writes and
61/// flushes refuse expired deadlines even if buffer space is available. The
62/// transport enforces its own deadlines before calling either half.
63///
64/// Closing or dropping an endpoint wakes blocked I/O on both sides. Its unread
65/// input is discarded, while the peer can still drain the endpoint's accepted
66/// output before receiving EOF. Further nonempty writes on either side fail
67/// with [`io::ErrorKind::BrokenPipe`]. So do flushes on the closed endpoint.
68/// The peer's flush fails the same way only if the endpoint closed with some of
69/// the peer's output unread. Output the endpoint had consumed before closing
70/// flushes fine afterwards.
71///
72/// Both peers must run concurrently when exchanging data. Allow enough capacity
73/// for the handshake's initial output; 64 KiB (`64 * 1024`) is a useful
74/// starting point. Small buffers can cause handshake backpressure, just as a
75/// real stream can.
76///
77/// # Panics
78///
79/// Panics if `capacity` is zero.
80pub fn duplex(capacity: usize, clock: &Clock) -> (Duplex, Duplex) {
81    // Validate capacity before allocating either direction
82    assert!(capacity > 0, "duplex capacity must be nonzero");
83
84    // Give both endpoints the same clock and independent buffers
85    let incoming = Arc::new(Pipe::new(capacity, clock));
86    let outgoing = Arc::new(Pipe::new(capacity, clock));
87    (
88        endpoint(incoming.clone(), outgoing.clone()),
89        endpoint(outgoing, incoming),
90    )
91}
92
93/// Bundles independent I/O halves with shutdown that wakes both directions.
94fn endpoint(incoming: Arc<Pipe>, outgoing: Arc<Pipe>) -> Duplex {
95    Stream::new(
96        Reader {
97            pipe: incoming.clone(),
98            deadline: None,
99        },
100        Writer {
101            pipe: outgoing.clone(),
102            deadline: None,
103        },
104        move || {
105            incoming.close_reader();
106            outgoing.close_writer();
107        },
108    )
109}
110
111/// Receiving half of a [`Duplex`], with an independently configured read deadline.
112#[derive(Debug)]
113pub struct Reader {
114    /// Buffer this half reads from, shared with the peer's writer.
115    pipe: Arc<Pipe>,
116    /// Latest read deadline, or `None` to wait without one.
117    deadline: Option<Instant>,
118}
119
120impl io::Read for Reader {
121    /// Reads the buffered bytes, waiting for more until the read deadline.
122    ///
123    /// Buffered bytes are delivered even past the deadline. An empty `buf`, a
124    /// closed reader or a closed peer with nothing buffered returns zero.
125    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
126        // Recheck the buffer and closure state after every wake
127        let mut state = self.pipe.lock();
128        loop {
129            // Empty reads and local closure need no buffered input
130            if buf.is_empty() || !state.reader_open {
131                return Ok(0);
132            }
133
134            // Deliver ready bytes even after the installed read deadline
135            if !state.bytes.is_empty() {
136                let count = buf.len().min(state.bytes.len());
137                for (out, byte) in buf.iter_mut().zip(state.bytes.drain(..count)) {
138                    *out = byte;
139                }
140                drop(state);
141                self.pipe.changed.notify_all();
142                return Ok(count);
143            }
144
145            // End at peer closure or wait within the remaining clock budget
146            if !state.writer_open {
147                return Ok(0);
148            }
149            self.pipe.check_deadline(self.deadline)?;
150            state = self.pipe.wait(state, self.deadline);
151        }
152    }
153}
154
155impl Read for Reader {
156    fn clock(&self) -> Clock {
157        self.pipe.clock.clone()
158    }
159
160    fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
161        self.deadline = deadline;
162        Ok(())
163    }
164}
165
166impl Drop for Reader {
167    /// Closes this reading direction and discards its unread bytes.
168    fn drop(&mut self) {
169        self.pipe.close_reader();
170    }
171}
172
173#[cfg(test)]
174impl Reader {
175    /// Makes any later wait for input panic instead of blocking, so a test
176    /// expecting an immediate return fails rather than hangs.
177    pub(crate) fn forbid_waits(&self) {
178        self.pipe.lock().waits_forbidden = true;
179    }
180}
181
182/// Sending half of a [`Duplex`], sharing one deadline across writes and flushes.
183#[derive(Debug)]
184pub struct Writer {
185    /// Buffer this half writes into, shared with the peer's reader.
186    pipe: Arc<Pipe>,
187    /// Latest write deadline, or `None` before the first is set.
188    deadline: Option<Instant>,
189}
190
191impl io::Write for Writer {
192    /// Appends as many bytes as fit, waiting for space until the write
193    /// deadline.
194    ///
195    /// An expired deadline fails before accepting any bytes, even with space
196    /// free. An empty `buf` then returns zero, and a closed end fails with
197    /// [`io::ErrorKind::BrokenPipe`].
198    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
199        // Recheck capacity and the same deadline after every wake
200        let mut state = self.pipe.lock();
201        loop {
202            // Refuse expiry and closure before accepting any bytes
203            self.pipe.check_deadline(self.deadline)?;
204            if buf.is_empty() {
205                return Ok(0);
206            }
207            if !state.reader_open || !state.writer_open {
208                return Err(io::ErrorKind::BrokenPipe.into());
209            }
210
211            // Fill available space and wake the reader after releasing the lock
212            let count = buf.len().min(self.pipe.capacity - state.bytes.len());
213            if count > 0 {
214                state.bytes.extend(&buf[..count]);
215                drop(state);
216                self.pipe.changed.notify_all();
217                return Ok(count);
218            }
219
220            // Wait for space without extending the deadline
221            state = self.pipe.wait(state, self.deadline);
222        }
223    }
224
225    /// Checks the write deadline and that no output was lost, without waiting
226    /// for the peer to read.
227    ///
228    /// It fails with [`io::ErrorKind::BrokenPipe`] after local closure, or when
229    /// the reader closed with output unread.
230    fn flush(&mut self) -> io::Result<()> {
231        // Check the installed deadline while observing a consistent pipe state
232        let state = self.pipe.lock();
233        self.pipe.check_deadline(self.deadline)?;
234
235        // Reject local closure or output lost when the peer closed
236        if !state.writer_open || state.lost {
237            return Err(io::ErrorKind::BrokenPipe.into());
238        }
239        Ok(())
240    }
241}
242
243impl Write for Writer {
244    fn clock(&self) -> Clock {
245        self.pipe.clock.clone()
246    }
247
248    fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
249        self.deadline = Some(deadline);
250        Ok(())
251    }
252}
253
254impl Drop for Writer {
255    /// Closes this writing direction, so the reader gets EOF once the buffer
256    /// drains.
257    fn drop(&mut self) {
258        self.pipe.close_writer();
259    }
260}
261
262/// Shared bounded buffer for one direction.
263///
264/// Waiting always releases the mutex.
265#[derive(Debug)]
266struct Pipe {
267    /// Clock used to check the installed I/O deadlines.
268    clock: Clock,
269    /// Most bytes the buffer holds before writers wait.
270    capacity: usize,
271    /// Buffered bytes and the open state of both ends.
272    state: Mutex<State>,
273    /// Signal waking readers and writers when the buffer or either end changes.
274    changed: Condvar,
275}
276
277/// Contents of a pipe and the open state of its two ends.
278#[derive(Debug)]
279struct State {
280    /// Bytes written and not yet read.
281    bytes: VecDeque<u8>,
282    /// Whether the reading end is open.
283    reader_open: bool,
284    /// Whether the writing end is open.
285    writer_open: bool,
286    /// Whether the reader closed with accepted output still unconsumed.
287    lost: bool,
288    /// Operations parked in a wait, counted for tests to observe.
289    #[cfg(test)]
290    waiting: usize,
291    /// Whether a wait fails instead of blocking, so a test cannot hang.
292    #[cfg(test)]
293    waits_forbidden: bool,
294}
295
296impl Pipe {
297    /// Creates an open, empty pipe holding up to `capacity` bytes, waiting on
298    /// `clock`.
299    fn new(capacity: usize, clock: &Clock) -> Self {
300        Self {
301            clock: clock.clone(),
302            capacity,
303            state: Mutex::new(State {
304                bytes: VecDeque::with_capacity(capacity),
305                reader_open: true,
306                writer_open: true,
307                lost: false,
308                #[cfg(test)]
309                waiting: 0,
310                #[cfg(test)]
311                waits_forbidden: false,
312            }),
313            changed: Condvar::new(clock),
314        }
315    }
316
317    /// Locks the pipe state, recovering it from a poisoned mutex.
318    fn lock(&self) -> MutexGuard<'_, State> {
319        // Shutdown must remain usable even after a panic in an I/O operation
320        self.state.lock().unwrap_or_else(|err| err.into_inner())
321    }
322
323    /// Releases the buffer until notified or the installed deadline is reached.
324    fn wait<'a>(
325        &self,
326        state: MutexGuard<'a, State>,
327        deadline: Option<Instant>,
328    ) -> MutexGuard<'a, State> {
329        // Let tests observe the wait before it releases the mutex, or refuse it
330        #[cfg(test)]
331        let state = {
332            let mut state = state;
333            assert!(!state.waits_forbidden, "memory pipe waited");
334            state.waiting += 1;
335            self.changed.notify_all();
336            state
337        };
338
339        // Release the buffer lock until notified or the current budget runs out
340        let state = match deadline {
341            Some(deadline) => {
342                self.changed
343                    .wait_deadline(state, deadline)
344                    .unwrap_or_else(|err| err.into_inner())
345                    .0
346            }
347            None => self
348                .changed
349                .wait(state)
350                .unwrap_or_else(|err| err.into_inner()),
351        };
352
353        // Remove the test's waiter count after the buffer lock is retaken
354        #[cfg(test)]
355        let state = {
356            let mut state = state;
357            state.waiting -= 1;
358            state
359        };
360        state
361    }
362
363    /// Refuses an expired deadline using the pipe's clock on every attempt.
364    fn check_deadline(&self, deadline: Option<Instant>) -> io::Result<()> {
365        if deadline.is_some_and(|deadline| self.clock.now() >= deadline) {
366            return Err(io::ErrorKind::TimedOut.into());
367        }
368        Ok(())
369    }
370
371    /// Closes the reading end and discards unread bytes, marking them lost for
372    /// the writer's flush, then wakes both ends.
373    fn close_reader(&self) {
374        let mut state = self.lock();
375        state.reader_open = false;
376        state.lost |= !state.bytes.is_empty();
377        state.bytes.clear();
378        drop(state);
379        self.changed.notify_all();
380    }
381
382    /// Closes the writing end, so the reader gets EOF once the buffer drains,
383    /// then wakes both ends.
384    fn close_writer(&self) {
385        self.lock().writer_open = false;
386        self.changed.notify_all();
387    }
388}
389
390/// Checks the in-memory streams' byte order, deadlines, backpressure and
391/// shutdown.
392#[cfg(test)]
393#[cfg_attr(coverage_nightly, coverage(off))]
394mod tests {
395    use super::*;
396    use crate::transport::Closer;
397    use crate::transport::testing::test_clock;
398    use darkbio_clock::TestClock;
399    use std::io::{Read as _, Write as _};
400    use std::sync::mpsc;
401    use std::thread;
402    use std::time::Duration;
403
404    /// Budget of 50 ms for the timeout and reuse checks.
405    const TIMEOUT: Duration = Duration::from_millis(50);
406
407    /// Splits a stream into its halves and a closer, without closing it.
408    fn halves(stream: Duplex) -> (Reader, Writer, Closer) {
409        let closer = stream.closer();
410        let (reader, writer) = stream.into_halves();
411        (reader, writer, closer)
412    }
413
414    /// Waits until an operation has released its mutex in a condition-variable wait.
415    fn blocked(pipe: &Pipe) {
416        let mut state = pipe.lock();
417        while state.waiting == 0 {
418            state = pipe.changed.wait(state).unwrap();
419        }
420    }
421
422    /// Checks that a read blocked on an empty pipe expires when its clock
423    /// reaches the installed deadline.
424    #[test]
425    fn test_blocked_read_expires_on_clock_deadline() {
426        // Park an empty read with a deadline a day ahead of real time
427        let mut tester = TestClock::new();
428        tester.advance(Duration::from_secs(86400));
429        let clock = tester.clock();
430        let (host, _peer) = duplex(1, &clock);
431        let (mut reader, _writer) = host.into_halves();
432        let deadline = clock.now() + Duration::from_secs(5);
433        reader.set_read_deadline(Some(deadline)).unwrap();
434        let reading = thread::spawn(move || reader.read(&mut [0]));
435        tester.wait_blocked(1);
436        assert_eq!(tester.next_deadline(), Some(deadline));
437
438        // Advance exactly to expiry and observe the blocked operation returning
439        tester.advance_to(deadline);
440        assert_eq!(
441            reading.join().unwrap().unwrap_err().kind(),
442            io::ErrorKind::TimedOut
443        );
444    }
445
446    /// Checks that a write blocked on a full pipe expires when its clock
447    /// reaches the installed deadline.
448    #[test]
449    fn test_blocked_write_expires_on_clock_deadline() {
450        // Fill the pipe and park another write on the same fixed deadline
451        let mut tester = TestClock::new();
452        tester.advance(Duration::from_secs(86400));
453        let clock = tester.clock();
454        let (host, _peer) = duplex(1, &clock);
455        let (_reader, mut writer) = host.into_halves();
456        writer.write_all(b"a").unwrap();
457        let deadline = clock.now() + Duration::from_secs(5);
458        writer.set_write_deadline(deadline).unwrap();
459        let writing = thread::spawn(move || writer.write(b"b"));
460        tester.wait_blocked(1);
461        assert_eq!(tester.next_deadline(), Some(deadline));
462
463        // Advance exactly to expiry and observe the blocked operation returning
464        tester.advance_to(deadline);
465        assert_eq!(
466            writing.join().unwrap().unwrap_err().kind(),
467            io::ErrorKind::TimedOut
468        );
469    }
470
471    /// Checks that an expired read delivers available input before reporting
472    /// expiry on the empty pipe.
473    #[test]
474    fn test_read_delivers_buffered_bytes_after_deadline() {
475        // Buffer input before the installed read deadline
476        let mut tester = test_clock();
477        let clock = tester.clock();
478        let (host, peer) = duplex(1, &clock);
479        let (mut reader, _writer) = host.into_halves();
480        let (_peer_reader, mut writer) = peer.into_halves();
481        let deadline = clock.now() + Duration::from_secs(5);
482        reader.set_read_deadline(Some(deadline)).unwrap();
483        writer.write_all(b"a").unwrap();
484
485        // Read buffered bytes at expiry without entering a condvar wait
486        tester.advance_to(deadline);
487        reader.forbid_waits();
488        let mut byte = [0];
489        assert_eq!(reader.read(&mut byte).unwrap(), 1);
490        assert_eq!(&byte, b"a");
491
492        // Derive expiry from the clock once input is exhausted
493        assert_eq!(
494            reader.read(&mut byte).unwrap_err().kind(),
495            io::ErrorKind::TimedOut
496        );
497    }
498
499    /// Checks that a zero-capacity pipe is rejected at construction, before
500    /// any I/O can wait.
501    #[test]
502    #[should_panic(expected = "duplex capacity must be nonzero")]
503    fn test_zero_capacity() {
504        duplex(0, &test_clock().clock());
505    }
506
507    /// Checks that partial reads and writes keep byte order across queue
508    /// wraparound, with independent read and write deadlines.
509    ///
510    /// The deadlines stay independent for empty I/O and flush too.
511    #[test]
512    fn test_byte_stream_and_independent_deadlines() {
513        // Create both directions on one paused clock
514        let tester = test_clock();
515        let clock = tester.clock();
516        let (host, ark) = duplex(4, &clock);
517        let (mut host_read, mut host_write, _host_close) = halves(host);
518        let (mut ark_read, mut ark_write, _ark_close) = halves(ark);
519
520        // Preserve byte order across partial writes and queue wraparound
521        assert_eq!(host_read.read(&mut []).unwrap(), 0);
522        assert_eq!(host_write.write(b"abcdef").unwrap(), 4);
523        assert_eq!(host_write.write(&[]).unwrap(), 0);
524        // Flush succeeds even while the queue is full
525        host_write.flush().unwrap();
526        let mut first = [0; 2];
527        ark_read.read_exact(&mut first).unwrap();
528        assert_eq!(&first, b"ab");
529        host_write.write_all(b"ef").unwrap();
530        let mut rest = [0; 4];
531        ark_read.read_exact(&mut rest).unwrap();
532        assert_eq!(&rest, b"cdef");
533
534        // Deliver buffered input before reporting its expired deadline
535        ark_write.write_all(b"xy").unwrap();
536        host_read.set_read_deadline(Some(clock.now())).unwrap();
537        host_read.read_exact(&mut first).unwrap();
538        assert_eq!(&first, b"xy");
539        assert_eq!(
540            host_read.read(&mut first).unwrap_err().kind(),
541            io::ErrorKind::TimedOut
542        );
543        assert_eq!(host_read.read(&mut []).unwrap(), 0);
544        host_write.write_all(b"q").unwrap();
545
546        // Expire output while input remains usable
547        host_write.set_write_deadline(clock.now()).unwrap();
548        host_read.set_read_deadline(None).unwrap();
549        ark_write.write_all(b"uv").unwrap();
550        host_read.read_exact(&mut first).unwrap();
551        assert_eq!(&first, b"uv");
552        for bytes in [b"t".as_slice(), b""] {
553            assert_eq!(
554                host_write.write(bytes).unwrap_err().kind(),
555                io::ErrorKind::TimedOut
556            );
557        }
558        assert_eq!(
559            host_write.flush().unwrap_err().kind(),
560            io::ErrorKind::TimedOut
561        );
562
563        // Resume output with a fresh deadline
564        host_write
565            .set_write_deadline(clock.now() + Duration::from_secs(5))
566            .unwrap();
567        host_write.write_all(b"rs").unwrap();
568        host_write.flush().unwrap();
569        let mut accepted = [0; 3];
570        ark_read.read_exact(&mut accepted).unwrap();
571        assert_eq!(&accepted, b"qrs");
572    }
573
574    /// Checks that an idle timed read expires without EOF or buffer changes,
575    /// and clearing its deadline restores an indefinite read.
576    #[test]
577    fn test_read_timeout_and_reuse() {
578        // Park an empty read on a deadline ahead of the paused clock
579        let mut tester = test_clock();
580        let clock = tester.clock();
581        let deadline = clock.now() + TIMEOUT;
582        let (host, ark) = duplex(1, &clock);
583        let (mut reader, _host_write, _host_close) = halves(host);
584        let (_ark_read, mut writer, _ark_close) = halves(ark);
585        let pipe = reader.pipe.clone();
586        let (done, result) = mpsc::channel();
587        let timed = thread::spawn(move || {
588            reader.set_read_deadline(Some(deadline)).unwrap();
589            let mut buf = [99];
590            let error = reader.read(&mut buf).unwrap_err();
591            assert!(clock.now() >= deadline);
592            assert_eq!(buf, [99]);
593            done.send((reader, error.kind())).unwrap();
594        });
595        tester.wait_blocked(1);
596        tester.advance_to(deadline);
597        let (mut reader, kind) = result.recv().unwrap();
598        timed.join().unwrap();
599        assert_eq!(kind, io::ErrorKind::TimedOut);
600
601        // Clear expiry and wake a new indefinite read with input
602        reader.set_read_deadline(None).unwrap();
603        let (done, result) = mpsc::channel();
604        let reading = thread::spawn(move || {
605            let mut buf = [0];
606            reader.read_exact(&mut buf).unwrap();
607            done.send(buf).unwrap();
608        });
609        blocked(&pipe);
610        writer.write_all(b"x").unwrap();
611        assert_eq!(&result.recv().unwrap(), b"x");
612        reading.join().unwrap();
613    }
614
615    /// Checks that a later write follows the prefix a `write_all` accepted
616    /// before timing out, without the abandoned suffix appearing afterwards.
617    #[test]
618    fn test_write_timeout_and_reuse() {
619        // Fill the pipe and park the remaining byte until its deadline
620        let mut tester = test_clock();
621        let clock = tester.clock();
622        let deadline = clock.now() + TIMEOUT;
623        let (host, ark) = duplex(3, &clock);
624        let (_host_read, mut writer, _host_close) = halves(host);
625        let (mut reader, _ark_write, _ark_close) = halves(ark);
626        let (done, result) = mpsc::channel();
627        let writing = thread::spawn(move || {
628            writer.set_write_deadline(deadline).unwrap();
629            let error = writer.write_all(b"abcd").unwrap_err();
630            assert!(clock.now() >= deadline);
631            done.send((writer, error.kind())).unwrap();
632        });
633        tester.wait_blocked(1);
634        tester.advance_to(deadline);
635        let (mut writer, kind) = result.recv().unwrap();
636        writing.join().unwrap();
637        assert_eq!(kind, io::ErrorKind::TimedOut);
638        let mut prefix = [0; 3];
639        reader.read_exact(&mut prefix).unwrap();
640        assert_eq!(&prefix, b"abc");
641
642        // Resume output after the accepted prefix with a fresh deadline
643        writer
644            .set_write_deadline(tester.clock().now() + Duration::from_secs(5))
645            .unwrap();
646        writer.write_all(b"ef").unwrap();
647        drop(writer);
648        let mut suffix = Vec::new();
649        reader.read_to_end(&mut suffix).unwrap();
650        assert_eq!(&suffix, b"ef");
651    }
652
653    /// Checks that draining a full queue wakes its writer through several
654    /// partial writes, while bounded reads rebuild the original byte stream.
655    #[test]
656    fn test_backpressure_wakes_writer() {
657        // Fill a paused pipe and start a writer behind its queued bytes
658        let tester = test_clock();
659        let deadline = tester.clock().now() + Duration::from_secs(5);
660        let (host, ark) = duplex(3, &tester.clock());
661        let (_host_read, mut writer, _host_close) = halves(host);
662        let (mut reader, _ark_write, _ark_close) = halves(ark);
663        writer.write_all(b"abc").unwrap();
664        let pipe = writer.pipe.clone();
665        let (done, result) = mpsc::channel();
666        let writing = thread::spawn(move || {
667            writer.set_write_deadline(deadline).unwrap();
668            done.send(writer.write_all(b"defgh")).unwrap();
669        });
670
671        // Drain the pipe after the writer has parked
672        blocked(&pipe);
673        reader.set_read_deadline(Some(deadline)).unwrap();
674        let mut bytes = [0; 8];
675        reader.read_exact(&mut bytes).unwrap();
676        assert_eq!(&bytes, b"abcdefgh");
677        result.recv().unwrap().unwrap();
678        writing.join().unwrap();
679    }
680
681    /// Checks that a local shutdown and dropping the peer both release blocked
682    /// reads and writes, including ones without a deadline.
683    #[test]
684    fn test_shutdown_wakes_both_directions() {
685        for local in [false, true] {
686            // Start a blocked read and a blocked write on the same clock
687            let tester = test_clock();
688            let (host, ark) = duplex(1, &tester.clock());
689            let (mut reader, mut writer, closer) = halves(host);
690            writer.write_all(b"a").unwrap();
691            let incoming = reader.pipe.clone();
692            let outgoing = writer.pipe.clone();
693            let (read_done, read_result) = mpsc::channel();
694            let reading = thread::spawn(move || {
695                read_done.send(reader.read(&mut [0])).unwrap();
696            });
697            let (write_done, write_result) = mpsc::channel();
698            let writing = thread::spawn(move || {
699                write_done.send(writer.write(b"b")).unwrap();
700            });
701
702            // Close only after both adapter calls have parked
703            blocked(&incoming);
704            blocked(&outgoing);
705            if local {
706                closer.close();
707            } else {
708                drop(ark);
709            }
710            assert_eq!(read_result.recv().unwrap().unwrap(), 0);
711            assert_eq!(
712                write_result.recv().unwrap().unwrap_err().kind(),
713                io::ErrorKind::BrokenPipe
714            );
715            reading.join().unwrap();
716            writing.join().unwrap();
717        }
718    }
719
720    /// Checks that closing discards local input but lets the peer drain accepted
721    /// output before EOF, even while the halves are still held.
722    #[test]
723    fn test_shutdown_drains_output_and_discards_input() {
724        // Queue bytes in both directions on a paused clock
725        let tester = test_clock();
726        let clock = tester.clock();
727        let (host, ark) = duplex(3, &clock);
728        let (mut host_read, mut host_write, closer) = halves(host);
729        let (mut ark_read, mut ark_write, _ark_close) = halves(ark);
730        host_write.write_all(b"abc").unwrap();
731        ark_write.write_all(b"xy").unwrap();
732        host_read.set_read_deadline(Some(clock.now())).unwrap();
733        ark_read.set_read_deadline(Some(clock.now())).unwrap();
734
735        // Close twice and retain only the peer's accepted input
736        closer.close();
737        closer.close();
738        assert_eq!(host_read.read(&mut [0]).unwrap(), 0);
739        let mut bytes = Vec::new();
740        ark_read.read_to_end(&mut bytes).unwrap();
741        assert_eq!(&bytes, b"abc");
742        for writer in [&mut host_write, &mut ark_write] {
743            assert_eq!(
744                writer.write(b"z").unwrap_err().kind(),
745                io::ErrorKind::BrokenPipe
746            );
747            assert_eq!(
748                writer.flush().unwrap_err().kind(),
749                io::ErrorKind::BrokenPipe
750            );
751        }
752    }
753
754    /// Checks that a flush succeeds after the peer consumed every accepted byte
755    /// and closed, while new output fails.
756    #[test]
757    fn test_flush_after_peer_drained_and_closed() {
758        // Drain the accepted output before closing the peer
759        let tester = test_clock();
760        let (host, ark) = duplex(4, &tester.clock());
761        let (_host_read, mut host_write) = host.into_halves();
762        let (mut ark_read, _ark_write) = ark.into_halves();
763        host_write.write_all(b"ab").unwrap();
764        let mut bytes = [0; 2];
765        ark_read.read_exact(&mut bytes).unwrap();
766        drop(ark_read);
767
768        // Allow a flush but reject any new output
769        host_write.flush().unwrap();
770        assert_eq!(
771            host_write.write(b"c").unwrap_err().kind(),
772            io::ErrorKind::BrokenPipe
773        );
774    }
775
776    /// Checks that splitting discards the write budget without closing the
777    /// halves, and dropping one half leaves the other direction usable.
778    #[test]
779    fn test_into_halves_and_independent_drop() {
780        // Split both streams on a paused clock
781        let tester = test_clock();
782        let (host, ark) = duplex(4, &tester.clock());
783        let (mut host_read, mut host_write) = host.set_write_timeout(Duration::ZERO).into_halves();
784        let (mut ark_read, mut ark_write) = ark.into_halves();
785
786        // Drop one writer and let its peer drain to EOF
787        host_write.write_all(b"abc").unwrap();
788        drop(host_write);
789        let mut bytes = Vec::new();
790        ark_read.read_to_end(&mut bytes).unwrap();
791        assert_eq!(&bytes, b"abc");
792
793        // Keep the opposite direction usable until its own reader drops
794        ark_write.write_all(b"xy").unwrap();
795        let mut reply = [0; 2];
796        host_read.read_exact(&mut reply).unwrap();
797        assert_eq!(&reply, b"xy");
798        drop(host_read);
799        assert_eq!(
800            ark_write.write(b"z").unwrap_err().kind(),
801            io::ErrorKind::BrokenPipe
802        );
803    }
804
805    /// Checks that protocol peers exchange a message larger than the pipe both
806    /// ways, then close while their transport readers wait for input.
807    #[test]
808    fn test_protocol_round_trip() {
809        use crate::protocol::{self, Message};
810        use crate::transport::mock::self_attestation;
811        use darkbio_crypto::xdsa;
812
813        // Connect protocol peers on a paused clock
814        let tester = test_clock();
815        let signer = xdsa::SecretKey::generate();
816        let identity = signer.public_key();
817        let attestation = self_attestation(&signer);
818        let (host, ark) = duplex(64 * 1024, &tester.clock());
819        let mut server = protocol::Server::new(ark, signer, attestation);
820        let (client, _) = protocol::connect(host, &identity).unwrap();
821        let mut session = server.accept().unwrap();
822
823        // Exchange a payload larger than the bounded transport pipe
824        let payload: Vec<u8> = (0..256 * 1024).map(|n| n as u8).collect();
825        let deadline = tester.clock().now() + Duration::from_secs(5);
826        let answer = client
827            .requester()
828            .request(payload.clone(), deadline)
829            .unwrap();
830        let (message, responder) = session.recv().unwrap();
831        assert_eq!(message, Message::Develop(payload.clone()));
832        let written = responder.reply(message, deadline).unwrap();
833        assert_eq!(answer.wait::<Vec<u8>>().unwrap(), payload);
834        written.wait().unwrap();
835
836        // Release both protocol owners after delivery
837        client.close();
838        server.close();
839    }
840}