Skip to main content

darkbio_wire/transport/
stream.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//! The byte stream and its shutdown operation, owned together by the transport.
8
9use super::io::check_deadline;
10use super::{DEFAULT_WRITE_TIMEOUT, Read, Write};
11use darkbio_clock::Clock;
12use darkbio_clock::sync::{Condvar, Mutex};
13use std::fmt;
14use std::io;
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17use tracing::debug;
18
19/// A duplex byte stream with a shutdown operation for both directions.
20///
21/// Shutdown must release blocked reads, writes and flushes, including reads
22/// with no deadline. Socket adapters can shut down the underlying socket.
23/// The shutdown operation must return promptly, must not panic, and must not
24/// acquire a lock held by a blocked I/O operation. It runs at most once.
25/// Neither shutdown nor an I/O operation may call this stream's closer. Closing
26/// would wait for the calling operation itself to return.
27///
28/// Closing refuses further adapter I/O. Admitted operations return their normal
29/// results and may succeed while shutdown is in progress. A failed frame send
30/// may have moved any prefix of its bytes. A successful write and flush means
31/// the adapter took the bytes, not that the peer received or processed them.
32/// Data already buffered by the transport may still be received after closing.
33///
34/// Dropping the stream closes it. Passing it to a client or server transfers
35/// that responsibility to the transport owner. Closer handles do not keep the
36/// reader or writer alive, and dropping a handle does not close the stream.
37pub struct Stream<R: Read, W: Write> {
38    clock: Clock,       // Shared by both adapters and the transport on them
39    io: Option<(R, W)>, // Taken when ownership passes to the transport
40    closer: Closer,
41    timeout: Duration, // One budget for the frame's writes and flush
42}
43
44impl<R: Read, W: Write> Stream<R, W> {
45    /// Bundles the two I/O directions with their shutdown operation.
46    ///
47    /// # Panics
48    ///
49    /// Panics if the reader and writer report different clocks.
50    pub fn new(reader: R, writer: W, shutdown: impl FnOnce() + Send + 'static) -> Self {
51        // Reject mixed clocks before creating the stream's shutdown state
52        let clock = reader.clock();
53        assert_eq!(
54            clock,
55            writer.clock(),
56            "stream halves must use the same clock"
57        );
58
59        // Retain the shared clock alongside the adapters
60        Self {
61            closer: Closer::new(&clock, shutdown),
62            clock,
63            io: Some((reader, writer)),
64            timeout: DEFAULT_WRITE_TIMEOUT,
65        }
66    }
67
68    /// Returns the clock shared by this stream's adapters.
69    pub fn clock(&self) -> Clock {
70        self.clock.clone()
71    }
72
73    /// Sets the budget for writing and flushing one complete frame, including
74    /// any delimiter needed after failed output. Progress does not restart it.
75    /// The budget begins after acquiring the writer and includes frame encoding.
76    /// Waiting for locks, encryption and peer replies is outside this budget.
77    /// Handshake frames also share the overall handshake deadline, which can
78    /// shorten this write budget.
79    ///
80    /// Zero refuses output immediately. A duration too large to add to an
81    /// [`Instant`] panics when an outgoing frame's deadline is constructed.
82    pub fn set_write_timeout(mut self, timeout: Duration) -> Self {
83        self.timeout = timeout;
84        self
85    }
86
87    /// A handle that can close the stream from another thread.
88    pub fn closer(&self) -> Closer {
89        self.closer.clone()
90    }
91
92    /// Permanently closes the stream and waits for shutdown and admitted adapter
93    /// operations to finish. Concurrent close calls wait for the same completion.
94    pub fn close(&self) {
95        self.closer.close();
96    }
97
98    /// Transfers ownership to a transport without closing the stream.
99    pub(crate) fn into_parts(mut self) -> (R, W, Closer, Duration) {
100        let (reader, writer) = self.io.take().expect("stream consumed once");
101        (reader, writer, self.closer.clone(), self.timeout)
102    }
103}
104
105impl<R: Read, W: Write> Drop for Stream<R, W> {
106    fn drop(&mut self) {
107        if self.io.is_some() {
108            self.close();
109        }
110    }
111}
112
113impl<R: Read, W: Write> fmt::Debug for Stream<R, W> {
114    /// Shows the write budget and the shutdown state, never the adapters.
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        f.debug_struct("Stream")
117            .field("timeout", &self.timeout)
118            .field("closer", &self.closer)
119            .finish_non_exhaustive()
120    }
121}
122
123/// A cloneable handle that permanently closes a byte stream.
124#[derive(Clone)]
125pub struct Closer(Arc<Shutdown>);
126
127/// Shared shutdown coordination. The state lock orders I/O admission and closure.
128/// Adapter operations and the shutdown callback run without this lock. The
129/// condition variable wakes waiting closers as those operations finish.
130struct Shutdown {
131    state: Mutex<State>,
132    changed: Condvar,
133}
134
135/// Lifecycle of a byte stream. Closing refuses new adapter operations. Closed
136/// additionally guarantees that shutdown and all admitted operations have finished.
137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
138enum Phase {
139    /// Adapter I/O may be admitted and shutdown has not been requested.
140    Open,
141    /// One closer runs shutdown. Other closers wait and new I/O is refused.
142    Closing,
143    /// The callback and every admitted adapter call have returned.
144    Closed,
145}
146
147/// Stream lifecycle and active adapter operations under one lock. The first
148/// closer takes the shutdown action. Each admitted operation increments the
149/// active count and decrements it on completion. Closed requires a zero count.
150struct State {
151    phase: Phase,
152    active: usize, // Admitted adapter operations that shutdown must wait for
153    action: Option<Box<dyn FnOnce() + Send>>,
154}
155
156impl Closer {
157    /// Creates the shutdown coordinator before either I/O half can be used.
158    pub(super) fn new(clock: &Clock, shutdown: impl FnOnce() + Send + 'static) -> Self {
159        Self(Arc::new(Shutdown {
160            state: Mutex::new(State {
161                phase: Phase::Open,
162                active: 0,
163                action: Some(Box::new(shutdown)),
164            }),
165            changed: Condvar::new(clock),
166        }))
167    }
168
169    /// Permanently closes the stream. Every caller waits until the shutdown
170    /// callback and all admitted adapter calls have returned. This includes
171    /// deadline setters, reads, writes and flushes.
172    /// New I/O is refused as soon as closing begins. This does not join the
173    /// threads using the transport or wait for application handlers.
174    ///
175    /// The callback runs once, without holding a state or I/O lock. It must return
176    /// promptly and release blocked I/O, including reads with no deadline.
177    /// Calling close from adapter I/O or the callback would wait on itself.
178    pub fn close(&self) {
179        // Wait for another closer to finish or take responsibility for shutdown
180        let action = {
181            let mut state = self.0.state.lock().expect("stream state not poisoned");
182            loop {
183                match state.phase {
184                    // Stream already closed, return early
185                    Phase::Closed => return,
186
187                    // Another closer is running shutdown. Wait for it to finish.
188                    Phase::Closing => {
189                        state = self
190                            .0
191                            .changed
192                            .wait(state)
193                            .expect("stream state not poisoned");
194                    }
195
196                    // Stream open, mark it closing and begin teardown
197                    Phase::Open => {
198                        debug!("closing wire stream");
199                        state.phase = Phase::Closing;
200                        break state.action.take().expect("shutdown called once");
201                    }
202                }
203            }
204        };
205
206        // The first closer runs shutdown without holding the state lock.
207        action();
208
209        // Wait until all admitted adapter operations return
210        let mut state = self.0.state.lock().expect("stream state not poisoned");
211        while state.active != 0 {
212            state = self
213                .0
214                .changed
215                .wait(state)
216                .expect("stream state not poisoned");
217        }
218
219        // Mark the stream closed and wake any threads blocked on close
220        state.phase = Phase::Closed;
221        self.0.changed.notify_all();
222    }
223
224    /// Admits one adapter call atomically with the decision to start closing.
225    fn enter(&self) -> Option<Activity<'_>> {
226        let mut state = self.0.state.lock().expect("stream state not poisoned");
227        if state.phase != Phase::Open {
228            return None;
229        }
230        state.active += 1;
231        Some(Activity(self))
232    }
233}
234
235impl fmt::Debug for Closer {
236    /// Shows the lifecycle phase and the admitted adapter operations. A state
237    /// lock held elsewhere is reported instead of waited for.
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        let mut closer = f.debug_struct("Closer");
240        match self.0.state.try_lock() {
241            Ok(state) => closer
242                .field("phase", &state.phase)
243                .field("active", &state.active),
244            Err(_) => closer.field("state", &format_args!("<locked>")),
245        };
246        closer.finish()
247    }
248}
249
250/// Tracks one admitted adapter operation until return or unwind. Dropping it
251/// decrements the active count without acquiring an I/O lock.
252struct Activity<'a>(&'a Closer);
253
254impl Drop for Activity<'_> {
255    fn drop(&mut self) {
256        let mut state = self.0.0.state.lock().expect("stream state not poisoned");
257        state.active -= 1;
258        if state.active == 0 {
259            self.0.0.changed.notify_all();
260        }
261    }
262}
263
264/// Reader admitting each adapter call under the shutdown coordinator's lock.
265pub(super) struct ReadHalf<R> {
266    pub(super) inner: R,
267    pub(super) closer: Closer,
268}
269
270impl<R: Read> ReadHalf<R> {
271    /// Reads with the optional handshake deadline. Ordinary reads wait for data
272    /// or adapter shutdown. Timeouts and interruptions retry within the deadline.
273    /// A successful read reports its bytes even if it finishes late, so the framer
274    /// can retain them before surfacing expiry. Closure refuses new I/O with EOF.
275    pub(super) fn read(&mut self, buf: &mut [u8], deadline: Option<Instant>) -> io::Result<usize> {
276        loop {
277            // Refuse an operation whose deadline has expired
278            if let Some(deadline) = deadline {
279                check_deadline(&self.inner.clock(), deadline)?;
280            }
281            // Keep the setter and read accounted for until both have returned.
282            let result = {
283                let Some(_active) = self.closer.enter() else {
284                    return Ok(0);
285                };
286                self.inner.set_read_deadline(deadline)?;
287                self.inner.read(buf)
288            };
289            // Retry an idle timeout or interrupted read. Other errors return.
290            match result {
291                Err(err)
292                    if matches!(
293                        err.kind(),
294                        io::ErrorKind::TimedOut | io::ErrorKind::Interrupted
295                    ) =>
296                {
297                    continue;
298                }
299                result => return result,
300            }
301        }
302    }
303}
304
305/// Writer admitting each partial write and flush under the shutdown lock.
306pub(super) struct WriteHalf<W> {
307    pub(super) inner: W,
308    pub(super) closer: Closer,
309}
310
311impl<W: Write> WriteHalf<W> {
312    /// Writes all bytes and flushes them under one absolute deadline. Installs
313    /// the deadline once before I/O. Each partial write and flush checks
314    /// expiration and closure before calling the adapter.
315    /// Interrupted writes are retried. Zero progress fails with `WriteZero`.
316    /// Setter and flush errors are not retried.
317    ///
318    /// An admitted call may finish after closure begins. Failure
319    /// can leave a written prefix. The framer checks the deadline again after
320    /// this operation returns, so a late flush fails the complete frame.
321    pub(super) fn write(&mut self, mut bytes: &[u8], deadline: Instant) -> io::Result<()> {
322        // Refuse an operation whose deadline has expired
323        check_deadline(&self.inner.clock(), deadline)?;
324
325        // Account for the deadline setter so shutdown waits for it too.
326        {
327            let _active = self
328                .closer
329                .enter()
330                .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "stream closed"))?;
331            self.inner.set_write_deadline(deadline)?;
332        }
333        // Keep writing while bytes remain; flush only after the complete write
334        while !bytes.is_empty() {
335            // Recheck the deadline before each partial write.
336            check_deadline(&self.inner.clock(), deadline)?;
337
338            // Attempt to write as much data as possible
339            let result = {
340                let _active = self
341                    .closer
342                    .enter()
343                    .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "stream closed"))?;
344                self.inner.write(bytes)
345            };
346            match result {
347                Ok(0) => return Err(io::ErrorKind::WriteZero.into()),
348                Ok(n) => bytes = &bytes[n..],
349                Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
350                Err(err) => return Err(err),
351            }
352        }
353        // Flush is part of the same operation and gets its own admission
354        check_deadline(&self.inner.clock(), deadline)?;
355
356        let _active = self
357            .closer
358            .enter()
359            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "stream closed"))?;
360        self.inner.flush()
361    }
362}
363
364#[cfg(test)]
365#[cfg_attr(coverage_nightly, coverage(off))]
366mod tests {
367    use super::*;
368    use crate::testing;
369    use crate::transport::Client;
370    use crate::transport::testing::{Memory, test_clock};
371    use std::sync::atomic::{AtomicUsize, Ordering};
372    use std::sync::mpsc;
373    use std::thread;
374    use std::time::Duration;
375
376    // Concurrent closers park on the stream clock until admitted I/O is released.
377    #[test]
378    fn test_concurrent_closers_wait_on_stream_clock() {
379        // Keep an admitted operation alive while two threads close the stream
380        let tester = darkbio_clock::TestClock::new();
381        let (stream, _peer) = crate::memory::duplex(1, &tester.clock());
382        let closer = stream.closer();
383        let activity = closer.enter().unwrap();
384        thread::scope(|scope| {
385            let first = scope.spawn(|| closer.close());
386            let second = scope.spawn(|| closer.close());
387
388            // Require both shutdown waits to be registered on this clock
389            tester.wait_blocked(2);
390            assert!(closer.enter().is_none());
391
392            // Complete the admitted operation and let both closers finish
393            drop(activity);
394            first.join().unwrap();
395            second.join().unwrap();
396            assert!(closer.enter().is_none());
397        });
398    }
399
400    /// Checks that the transport's owners and handles print without printable
401    /// adapters, which hosts may box as trait objects.
402    #[test]
403    fn test_debug_capabilities() {
404        use crate::transport::{Attestation, Event, Roots, Sender, Server};
405
406        /// Requires a value to be printable.
407        fn printable<T: fmt::Debug>() {}
408        printable::<Stream<Box<dyn Read>, Box<dyn Write>>>();
409        printable::<Closer>();
410        printable::<Client<Box<dyn Read>, Box<dyn Write>>>();
411        printable::<Server<Box<dyn Read>, Box<dyn Write>, Attestation>>();
412        printable::<Sender<Box<dyn Write>>>();
413        printable::<Event<Box<dyn Write>>>();
414        printable::<Attestation>();
415        printable::<Roots<'static>>();
416    }
417
418    // A memory reader can deliver ready bytes after its own deadline, but the
419    // transport must reject an expired attempt before consuming those bytes.
420    #[test]
421    fn test_memory_reader_keeps_transport_deadline() {
422        // Queue bytes on a clock a day ahead of real time
423        let tester = test_clock();
424        let clock = tester.clock();
425        let (host, ark) = crate::memory::duplex(4, &clock);
426        let (_ark_read, mut ark_write) = ark.into_halves();
427        std::io::Write::write_all(&mut ark_write, b"abc").unwrap();
428        let (reader, _writer, closer, _) = host.into_parts();
429        let mut reader = ReadHalf {
430            inner: reader,
431            closer,
432        };
433        // Refuse an expired read without consuming the available bytes
434        let mut bytes = [0; 3];
435        assert_eq!(
436            reader
437                .read(&mut bytes, Some(clock.now()))
438                .unwrap_err()
439                .kind(),
440            io::ErrorKind::TimedOut
441        );
442        assert_eq!(bytes, [0; 3]);
443        assert_eq!(reader.read(&mut bytes, None).unwrap(), 3);
444        assert_eq!(&bytes, b"abc");
445    }
446
447    /// Adapter holding an admitted call until shutdown has been requested,
448    /// then returning the result selected by the test.
449    struct Adapter {
450        /// Clock shared by the adapter and shutdown gate.
451        clock: Clock,
452        entered: mpsc::Sender<()>,
453        released: testing::Gate,
454        fails: bool,
455        deadline: Option<Instant>,
456    }
457
458    impl Adapter {
459        /// Waits for the test's release without exceeding this adapter call's deadline.
460        fn wait(&self, deadline: Option<Instant>) -> io::Result<()> {
461            self.entered.send(()).unwrap();
462            self.released.wait(deadline)?;
463            if self.fails {
464                Err(io::Error::other("adapter failure"))
465            } else {
466                Ok(())
467            }
468        }
469    }
470
471    impl Read for Adapter {
472        fn clock(&self) -> Clock {
473            self.clock.clone()
474        }
475
476        fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
477            self.deadline = deadline;
478            Ok(())
479        }
480    }
481
482    impl io::Read for Adapter {
483        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
484            self.wait(self.deadline)?;
485            buf[0] = 0x5a;
486            Ok(1)
487        }
488    }
489
490    impl Write for Adapter {
491        fn clock(&self) -> Clock {
492            self.clock.clone()
493        }
494
495        fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
496            self.deadline = Some(deadline);
497            Ok(())
498        }
499    }
500
501    impl io::Write for Adapter {
502        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
503            self.wait(Some(self.deadline.expect("write deadline installed")))?;
504            Ok(buf.len())
505        }
506
507        fn flush(&mut self) -> io::Result<()> {
508            self.wait(Some(self.deadline.expect("write deadline installed")))
509        }
510    }
511
512    /// Runs an adapter call across shutdown and checks that its result survives.
513    fn during_shutdown(
514        fails: bool,
515        operation: impl FnOnce(Adapter, Closer) -> io::Result<()> + Send + 'static,
516    ) {
517        // Start an admitted adapter call on a paused clock
518        let tester = test_clock();
519        let clock = tester.clock();
520        let (entered, entries) = mpsc::channel();
521        let released = testing::Gate::new(&clock);
522        let release = released.clone();
523        let closer = Closer::new(&clock, move || release.open());
524        let io = thread::spawn({
525            let closer = closer.clone();
526            move || {
527                operation(
528                    Adapter {
529                        clock,
530                        entered,
531                        released,
532                        fails,
533                        deadline: None,
534                    },
535                    closer,
536                )
537            }
538        });
539        // Close only after the adapter has parked and preserve its result
540        entries.recv().unwrap();
541        tester.wait_blocked(1);
542        closer.close();
543        let result = io.join().unwrap();
544        if fails {
545            let err = result.unwrap_err();
546            assert_eq!(err.kind(), io::ErrorKind::Other);
547            assert_eq!(err.to_string(), "adapter failure");
548        } else {
549            result.unwrap();
550        }
551    }
552
553    // Tests that shutdown preserves admitted read and final flush results,
554    // including original errors. An admitted write can accept its bytes after
555    // closing starts, but the full operation then refuses the subsequent flush.
556    #[test]
557    fn test_admitted_io_preserves_results_during_shutdown() {
558        for fails in [false, true] {
559            // Preserve the admitted read's result across shutdown
560            during_shutdown(fails, |inner, closer| {
561                let mut buf = [0];
562                assert_eq!(ReadHalf { inner, closer }.read(&mut buf, None)?, 1);
563                assert_eq!(buf, [0x5a]);
564                Ok(())
565            });
566            during_shutdown(fails, move |inner, closer| {
567                let deadline = inner.clock.now() + Duration::from_secs(5);
568                let result = WriteHalf { inner, closer }.write(&[1, 2, 3], deadline);
569                if fails {
570                    result
571                } else {
572                    assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotConnected);
573                    Ok(())
574                }
575            });
576            during_shutdown(fails, |inner, closer| {
577                let deadline = inner.clock.now() + Duration::from_secs(5);
578                WriteHalf { inner, closer }.write(&[], deadline)
579            });
580        }
581    }
582
583    // Tests that handing a stream to a client transfers shutdown responsibility
584    // without closing it. Dropping either owner invokes shutdown exactly once,
585    // and repeated closes do nothing.
586    #[test]
587    fn test_ownership_and_repeated_close() {
588        // Transfer a paused stream to its client without closing it
589        let tester = test_clock();
590        let clock = tester.clock();
591        let calls = Arc::new(AtomicUsize::new(0));
592        let stream = Stream::new(
593            Memory::new(io::empty(), &clock),
594            Memory::new(io::sink(), &clock),
595            {
596                let calls = calls.clone();
597                move || {
598                    calls.fetch_add(1, Ordering::SeqCst);
599                }
600            },
601        );
602        let closer = stream.closer();
603        let client = Client::new(stream);
604        assert_eq!(
605            calls.load(Ordering::SeqCst),
606            0,
607            "handoff must keep the stream open"
608        );
609        // Close once when its owner drops and ignore repeated closes
610        drop(client);
611        assert_eq!(calls.load(Ordering::SeqCst), 1);
612        closer.close();
613        drop(closer.clone());
614        assert_eq!(calls.load(Ordering::SeqCst), 1);
615
616        // Close an unclaimed stream when it drops too
617        let stream = Stream::new(
618            Memory::new(io::empty(), &clock),
619            Memory::new(io::sink(), &clock),
620            {
621                let calls = calls.clone();
622                move || {
623                    calls.fetch_add(1, Ordering::SeqCst);
624                }
625            },
626        );
627        drop(stream);
628        assert_eq!(
629            calls.load(Ordering::SeqCst),
630            2,
631            "unclaimed streams also close on drop"
632        );
633    }
634
635    // Tests that concurrent close calls and owner drop all wait for shutdown.
636    // Hold the callback until every caller has started, then check that none
637    // returns before the callback is released.
638    #[test]
639    fn test_every_closer_waits_for_the_shutdown_callback() {
640        // Park the shutdown callback on the stream's paused clock
641        let tester = test_clock();
642        let clock = tester.clock();
643        let (entered, callback) = mpsc::channel();
644        let release = testing::Gate::new(&clock);
645        let released = release.clone();
646        let stream = Stream::new(
647            Memory::new(io::empty(), &clock),
648            Memory::new(io::sink(), &clock),
649            move || {
650                entered.send(()).unwrap();
651                released.wait(None).unwrap();
652            },
653        );
654        let closer = stream.closer();
655        let (finished, finishes) = mpsc::channel();
656        let first = thread::spawn({
657            let closer = closer.clone();
658            let finished = finished.clone();
659            move || {
660                closer.close();
661                finished.send(()).unwrap();
662            }
663        });
664        callback.recv().unwrap();
665
666        // Start another close and owner drop while the first callback is parked
667        let (started, starts) = mpsc::channel();
668        let second = thread::spawn({
669            let closer = closer.clone();
670            let started = started.clone();
671            let finished = finished.clone();
672            move || {
673                started.send(()).unwrap();
674                closer.close();
675                finished.send(()).unwrap();
676            }
677        });
678        let owner = thread::spawn(move || {
679            started.send(()).unwrap();
680            drop(stream);
681            finished.send(()).unwrap();
682        });
683        starts.recv().unwrap();
684        starts.recv().unwrap();
685        tester.wait_blocked(3);
686        assert!(finishes.try_recv().is_err());
687
688        // Release the callback and await all three closers
689        release.open();
690        finishes.recv().unwrap();
691        finishes.recv().unwrap();
692        finishes.recv().unwrap();
693        first.join().unwrap();
694        second.join().unwrap();
695        owner.join().unwrap();
696    }
697
698    /// Adapter operation held in flight while the test requests shutdown.
699    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
700    enum BlockAt {
701        /// Hold a raw read until the test releases it.
702        Read,
703        /// Hold a raw write until the test releases it.
704        Write,
705        /// Hold a flush until the test releases it.
706        Flush,
707    }
708
709    /// Holds one adapter operation until the test releases it. This keeps I/O in
710    /// progress long enough to observe shutdown.
711    struct Gate {
712        /// Clock shared with both gated adapters.
713        clock: Clock,
714        at: BlockAt,
715        entered: mpsc::Sender<()>,
716        released: testing::Gate,
717        calls: AtomicUsize,
718    }
719
720    impl Gate {
721        /// Holds the selected operation until released or its supplied deadline expires.
722        fn call(&self, at: BlockAt, deadline: Option<Instant>) -> io::Result<()> {
723            self.calls.fetch_add(1, Ordering::SeqCst);
724            if at == self.at {
725                self.entered.send(()).unwrap();
726                self.released.wait(deadline)?;
727            }
728            Ok(())
729        }
730
731        /// Allows the admitted operation to finish.
732        fn release(&self) {
733            self.released.open();
734        }
735    }
736
737    /// Adapter half sharing a gate with the test driver.
738    struct GatedAdapter {
739        gate: Arc<Gate>,
740        deadline: Option<Instant>,
741    }
742
743    impl Read for GatedAdapter {
744        fn clock(&self) -> Clock {
745            self.gate.clock.clone()
746        }
747
748        fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
749            self.deadline = deadline;
750            Ok(())
751        }
752    }
753
754    impl io::Read for GatedAdapter {
755        fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
756            self.gate.call(BlockAt::Read, self.deadline)?;
757            Ok(0)
758        }
759    }
760
761    impl Write for GatedAdapter {
762        fn clock(&self) -> Clock {
763            self.gate.clock.clone()
764        }
765
766        fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
767            self.deadline = Some(deadline);
768            Ok(())
769        }
770    }
771
772    impl io::Write for GatedAdapter {
773        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
774            self.gate.call(
775                BlockAt::Write,
776                Some(self.deadline.expect("write deadline installed")),
777            )?;
778            Ok(bytes.len())
779        }
780
781        fn flush(&mut self) -> io::Result<()> {
782            self.gate.call(
783                BlockAt::Flush,
784                Some(self.deadline.expect("write deadline installed")),
785            )
786        }
787    }
788
789    // Tests that close waits for an admitted read, write or flush even after
790    // the shutdown callback returns. A gate holds each operation independently;
791    // subsequent I/O must never reach the closed adapter.
792    #[test]
793    fn test_every_closer_waits_for_active_io_and_refuses_new_io() {
794        for at in [BlockAt::Read, BlockAt::Write, BlockAt::Flush] {
795            // Hold one admitted adapter operation on the stream's clock
796            let tester = test_clock();
797            let clock = tester.clock();
798            let (entered, entries) = mpsc::channel();
799            let gate = Arc::new(Gate {
800                clock: clock.clone(),
801                at,
802                entered,
803                released: testing::Gate::new(&clock),
804                calls: AtomicUsize::new(0),
805            });
806            let (requested, requests) = mpsc::channel();
807            let stream = Stream::new(
808                GatedAdapter {
809                    gate: gate.clone(),
810                    deadline: None,
811                },
812                GatedAdapter {
813                    gate: gate.clone(),
814                    deadline: None,
815                },
816                move || {
817                    requested.send(()).unwrap();
818                },
819            );
820            let (reader, writer, closer, _) = stream.into_parts();
821            let io_closer = closer.clone();
822            let deadline = clock.now() + Duration::from_secs(5);
823            let io = thread::spawn(move || {
824                let mut reader = ReadHalf {
825                    inner: reader,
826                    closer: io_closer.clone(),
827                };
828                let mut writer = WriteHalf {
829                    inner: writer,
830                    closer: io_closer,
831                };
832                match at {
833                    BlockAt::Read => assert_eq!(reader.read(&mut [0], None).unwrap(), 0),
834                    BlockAt::Write => assert_eq!(
835                        writer.write(&[1], deadline).unwrap_err().kind(),
836                        io::ErrorKind::NotConnected
837                    ),
838                    BlockAt::Flush => writer.write(&[], deadline).unwrap(),
839                }
840                (reader, writer)
841            });
842            entries.recv().unwrap();
843
844            // Require both I/O and shutdown to park before checking completion
845            let (finished, finishes) = mpsc::channel();
846            let closing = thread::spawn({
847                let closer = closer.clone();
848                move || {
849                    closer.close();
850                    finished.send(()).unwrap();
851                }
852            });
853            requests.recv().unwrap();
854            tester.wait_blocked(2);
855            assert!(finishes.try_recv().is_err());
856
857            // Release the admitted operation and refuse every new adapter call
858            gate.release();
859            finishes.recv().unwrap();
860            closing.join().unwrap();
861            let (mut reader, mut writer) = io.join().unwrap();
862            let calls = gate.calls.load(Ordering::SeqCst);
863            assert_eq!(reader.read(&mut [0], None).unwrap(), 0);
864            assert!(matches!(
865                writer.write(&[1], deadline),
866                Err(err) if err.kind() == io::ErrorKind::NotConnected
867            ));
868            assert_eq!(gate.calls.load(Ordering::SeqCst), calls);
869            closer.close();
870            assert!(requests.try_recv().is_err(), "shutdown called twice");
871        }
872    }
873
874    /// Accepts one byte per write and can hold flush until its supplied deadline.
875    /// Recorded deadlines reveal whether partial progress restarts the budget.
876    struct BudgetWriter {
877        /// Clock governing writes and the deliberately stalled flush.
878        clock: Clock,
879        bytes: Vec<u8>,
880        deadlines: Vec<Instant>,
881        stall_flush: bool,
882        deadline: Option<Instant>,
883        settings: usize,
884    }
885
886    impl Write for BudgetWriter {
887        fn clock(&self) -> Clock {
888            self.clock.clone()
889        }
890
891        fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
892            self.deadline = Some(deadline);
893            self.settings += 1;
894            Ok(())
895        }
896    }
897
898    impl io::Write for BudgetWriter {
899        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
900            let deadline = self.deadline.expect("write deadline installed");
901            check_deadline(&self.clock(), deadline)?;
902            self.deadlines.push(deadline);
903            self.bytes.push(bytes[0]);
904            Ok(1)
905        }
906
907        fn flush(&mut self) -> io::Result<()> {
908            let deadline = self.deadline.expect("write deadline installed");
909            self.deadlines.push(deadline);
910            if self.stall_flush {
911                self.clock.sleep_until(deadline);
912                return Err(io::ErrorKind::TimedOut.into());
913            }
914            check_deadline(&self.clock(), deadline)
915        }
916    }
917
918    // Tests that partial writes and a blocked flush share one absolute deadline,
919    // a timeout leaves the stream reusable, and a zero budget never calls I/O.
920    #[test]
921    fn test_output_deadline_and_reuse() {
922        // Start a partial write whose flush parks until its clock deadline
923        let mut tester = test_clock();
924        let clock = tester.clock();
925        let timeout = Duration::from_millis(40);
926        let stream = Stream::new(
927            Memory::new(io::empty(), &clock),
928            BudgetWriter {
929                clock: clock.clone(),
930                bytes: Vec::new(),
931                deadlines: Vec::new(),
932                stall_flush: true,
933                deadline: None,
934                settings: 0,
935            },
936            || {},
937        )
938        .set_write_timeout(timeout);
939        let (_, inner, closer, configured) = stream.into_parts();
940        assert_eq!(configured, timeout);
941        let mut writer = WriteHalf { inner, closer };
942        let deadline = clock.now() + configured;
943        let result = thread::scope(|scope| {
944            let writing = scope.spawn(|| writer.write(b"abc", deadline));
945            tester.wait_blocked(1);
946            assert_eq!(tester.next_deadline(), Some(deadline));
947            tester.advance_to(deadline);
948            writing.join().unwrap()
949        });
950        assert_eq!(result.unwrap_err().kind(), io::ErrorKind::TimedOut);
951        assert_eq!(writer.inner.deadlines, vec![deadline; 4]);
952        assert_eq!(writer.inner.settings, 1);
953
954        // Resume writes with a new deadline on the same stream
955        writer.inner.stall_flush = false;
956        let deadline = clock.now() + Duration::from_secs(5);
957        writer.write(b"d", deadline).unwrap();
958        assert_eq!(writer.inner.bytes, b"abcd");
959        assert_eq!(writer.inner.settings, 2);
960
961        // Reject an already expired budget before configuring adapter I/O
962        let calls = writer.inner.deadlines.len();
963        let deadline = clock.now();
964        assert!(matches!(
965            writer.write(b"e", deadline),
966            Err(err) if err.kind() == io::ErrorKind::TimedOut
967        ));
968        assert_eq!(writer.inner.deadlines.len(), calls);
969        assert_eq!(writer.inner.settings, 2);
970        writer.closer.close();
971    }
972
973    // Tests the complete write operation with interruption and partial progress:
974    // retries retain the unsent suffix, zero progress fails without flushing,
975    // and an interrupted flush is returned directly rather than retried.
976    #[test]
977    fn test_partial_write_retries_and_failures() {
978        /// Scripts adapter results and records offered and accepted byte sequences.
979        struct Script {
980            /// Clock governing every scripted partial write.
981            clock: Clock,
982            results: std::collections::VecDeque<io::Result<usize>>,
983            offered: Vec<Vec<u8>>,
984            accepted: Vec<u8>,
985            interrupted_flush: bool,
986            flushes: usize,
987            settings: usize,
988            deadline: Option<Instant>,
989        }
990
991        impl Write for Script {
992            fn clock(&self) -> Clock {
993                self.clock.clone()
994            }
995
996            fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
997                self.settings += 1;
998                self.deadline = Some(deadline);
999                Ok(())
1000            }
1001        }
1002
1003        impl io::Write for Script {
1004            fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1005                check_deadline(
1006                    &self.clock(),
1007                    self.deadline.expect("write deadline installed"),
1008                )?;
1009                self.offered.push(bytes.to_vec());
1010                let result = self.results.pop_front().expect("unexpected write");
1011                if let Ok(size) = &result {
1012                    self.accepted.extend_from_slice(&bytes[..*size]);
1013                }
1014                result
1015            }
1016
1017            fn flush(&mut self) -> io::Result<()> {
1018                check_deadline(
1019                    &self.clock(),
1020                    self.deadline.expect("write deadline installed"),
1021                )?;
1022                self.flushes += 1;
1023                assert_eq!(self.flushes, 1, "flush retried");
1024                if self.interrupted_flush {
1025                    Err(io::ErrorKind::Interrupted.into())
1026                } else {
1027                    Ok(())
1028                }
1029            }
1030        }
1031
1032        for (stalls, interrupted_flush) in [(false, false), (true, false), (false, true)] {
1033            // Run an interrupted write followed by partial progress on a paused clock
1034            let tester = test_clock();
1035            let clock = tester.clock();
1036            let mut writer = WriteHalf {
1037                inner: Script {
1038                    clock: clock.clone(),
1039                    results: [
1040                        Err(io::ErrorKind::Interrupted.into()),
1041                        Ok(1),
1042                        Ok(usize::from(!stalls)),
1043                    ]
1044                    .into(),
1045                    offered: Vec::new(),
1046                    accepted: Vec::new(),
1047                    interrupted_flush,
1048                    flushes: 0,
1049                    settings: 0,
1050                    deadline: None,
1051                },
1052                closer: Closer::new(&clock, || {}),
1053            };
1054            let result = writer.write(b"ab", clock.now() + Duration::from_secs(5));
1055            if stalls {
1056                assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero);
1057            } else if interrupted_flush {
1058                assert_eq!(result.unwrap_err().kind(), io::ErrorKind::Interrupted);
1059            } else {
1060                result.unwrap();
1061            }
1062            // Retain the unsent suffix across retries and configure the deadline once
1063            assert_eq!(
1064                writer.inner.offered,
1065                [b"ab".to_vec(), b"ab".to_vec(), b"b".to_vec()]
1066            );
1067            assert_eq!(
1068                writer.inner.accepted,
1069                if stalls { &b"a"[..] } else { &b"ab"[..] }
1070            );
1071            assert_eq!(writer.inner.flushes, usize::from(!stalls));
1072            assert_eq!(writer.inner.settings, 1);
1073            writer.closer.close();
1074        }
1075    }
1076
1077    // Tests that a failed deadline setter prevents byte I/O and preserves its
1078    // error, including Interrupted and TimedOut. Only retryable errors from an
1079    // actual read may start another attempt.
1080    #[test]
1081    fn test_deadline_setter_failure_prevents_io() {
1082        /// Rejects deadline installation and panics if byte I/O is attempted.
1083        struct Refused {
1084            /// Clock shared by the test's adapters and closer.
1085            clock: Clock,
1086            settings: usize,
1087            kind: io::ErrorKind,
1088        }
1089
1090        impl Refused {
1091            /// Fails once so an incorrect retry fails the test promptly.
1092            fn reject(&mut self) -> io::Result<()> {
1093                self.settings += 1;
1094                assert_eq!(self.settings, 1, "deadline setter failure retried");
1095                Err(io::Error::new(self.kind, "deadline refused"))
1096            }
1097        }
1098
1099        impl Read for Refused {
1100            fn clock(&self) -> Clock {
1101                self.clock.clone()
1102            }
1103
1104            fn set_read_deadline(&mut self, _: Option<Instant>) -> io::Result<()> {
1105                self.reject()
1106            }
1107        }
1108
1109        impl io::Read for Refused {
1110            fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
1111                panic!("read after deadline setter failed")
1112            }
1113        }
1114
1115        impl Write for Refused {
1116            fn clock(&self) -> Clock {
1117                self.clock.clone()
1118            }
1119
1120            fn set_write_deadline(&mut self, _: Instant) -> io::Result<()> {
1121                self.reject()
1122            }
1123        }
1124
1125        impl io::Write for Refused {
1126            fn write(&mut self, _: &[u8]) -> io::Result<usize> {
1127                panic!("write after deadline setter failed")
1128            }
1129
1130            fn flush(&mut self) -> io::Result<()> {
1131                panic!("flush after deadline setter failed")
1132            }
1133        }
1134
1135        for kind in [io::ErrorKind::Interrupted, io::ErrorKind::TimedOut] {
1136            // Fail each direction's deadline installation before byte I/O
1137            let tester = test_clock();
1138            let clock = tester.clock();
1139            let closer = Closer::new(&clock, || {});
1140            let mut reader = ReadHalf {
1141                inner: Refused {
1142                    clock: clock.clone(),
1143                    settings: 0,
1144                    kind,
1145                },
1146                closer: closer.clone(),
1147            };
1148            let err = reader.read(&mut [0], None).unwrap_err();
1149            assert_eq!(err.kind(), kind);
1150            assert_eq!(err.to_string(), "deadline refused");
1151            let mut writer = WriteHalf {
1152                inner: Refused {
1153                    clock: clock.clone(),
1154                    settings: 0,
1155                    kind,
1156                },
1157                closer: closer.clone(),
1158            };
1159            assert!(matches!(
1160                writer.write(&[1], clock.now() + Duration::from_secs(5)),
1161                Err(err) if err.kind() == kind && err.to_string() == "deadline refused"
1162            ));
1163
1164            // Refuse all new I/O after shutdown without another deadline setter call
1165            closer.close();
1166            assert_eq!(reader.read(&mut [0], None).unwrap(), 0);
1167            assert!(matches!(
1168                writer.write(&[1], clock.now() + Duration::from_secs(5)),
1169                Err(err) if err.kind() == io::ErrorKind::NotConnected
1170            ));
1171            assert_eq!(reader.inner.settings, 1);
1172            assert_eq!(writer.inner.settings, 1);
1173        }
1174    }
1175
1176    // Tests that a partial write returning after its deadline leaves its
1177    // accepted byte intact but fails the complete operation. Depending on the
1178    // input length, either the remaining write or flush is refused before I/O.
1179    #[test]
1180    fn test_late_write_preserves_progress() {
1181        /// Accepts one byte but delays returning until its installed deadline.
1182        struct LateWriter {
1183            /// Clock governing the late partial write.
1184            clock: Clock,
1185            deadline: Option<Instant>,
1186            bytes: Vec<u8>,
1187        }
1188
1189        impl Write for LateWriter {
1190            fn clock(&self) -> Clock {
1191                self.clock.clone()
1192            }
1193
1194            fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
1195                self.deadline = Some(deadline);
1196                Ok(())
1197            }
1198        }
1199
1200        impl io::Write for LateWriter {
1201            fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1202                self.bytes.push(bytes[0]);
1203                self.clock
1204                    .sleep_until(self.deadline.expect("write deadline installed"));
1205                Ok(1)
1206            }
1207
1208            fn flush(&mut self) -> io::Result<()> {
1209                panic!("flush admitted after deadline expired")
1210            }
1211        }
1212
1213        for bytes in [&b"a"[..], &b"ab"[..]] {
1214            // Park a successful partial write until its deadline
1215            let mut tester = test_clock();
1216            let clock = tester.clock();
1217            let mut writer = WriteHalf {
1218                inner: LateWriter {
1219                    clock: clock.clone(),
1220                    deadline: None,
1221                    bytes: Vec::new(),
1222                },
1223                closer: Closer::new(&clock, || {}),
1224            };
1225            let deadline = clock.now() + Duration::from_millis(40);
1226            let result = thread::scope(|scope| {
1227                let writing = scope.spawn(|| writer.write(bytes, deadline));
1228                tester.wait_blocked(1);
1229                tester.advance_to(deadline);
1230                writing.join().unwrap()
1231            });
1232
1233            // Retain only the accepted byte and reject all subsequent adapter I/O
1234            assert_eq!(result.unwrap_err().kind(), io::ErrorKind::TimedOut);
1235            assert_eq!(writer.inner.bytes, b"a");
1236            writer.closer.close();
1237        }
1238    }
1239}