Skip to main content

darkbio_wire/protocol/
server.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//! Persistent server ownership and ordered attachment of successive sessions.
8
9use super::envelope::Side;
10use super::promise::Notifications;
11use super::session::SessionInner;
12use super::worker;
13use super::{
14    Closer, DEFAULT_AUTOREPLY_TIMEOUT, DEFAULT_MAX_INBOUND_BYTES, DEFAULT_MAX_INBOUND_REQUESTS,
15    Error, Session,
16};
17use crate::transport::{self, Attester, Read, Stream, Write};
18use darkbio_clock::Clock;
19use darkbio_clock::sync::{Condvar, Mutex};
20use darkbio_crypto::xdsa;
21use std::fmt;
22use std::sync::{Arc, Weak};
23use std::time::Duration;
24
25/// Pause before the reader retries a failed read, so an adapter that keeps
26/// failing cannot spin it.
27const READ_RETRY_DELAY: Duration = Duration::from_millis(100);
28
29/// Owner of a persistent server stream, accepting successive sessions.
30///
31/// Closing or dropping the server ends its active session and shuts down the
32/// physical stream. Closing an individual [`Session`] keeps this owner and
33/// its stream available for another handshake.
34///
35/// A failed read keeps the server and its current session, and reading resumes
36/// after a 100 ms pause on the stream's clock. The end of the stream ends the
37/// server.
38pub struct Server {
39    /// Server state retained independently of any accepted session owner.
40    pub(super) inner: Arc<ServerInner>,
41}
42
43impl Server {
44    /// Takes ownership of a stream and constructs its transport internally.
45    ///
46    /// The signer is the server's identity key, which must match the key embedded
47    /// in the attestation. The attester supplies the current device attestation
48    /// for each handshake. The persistent reader starts immediately. Failure to
49    /// start a required worker or an escaping worker panic aborts the process.
50    pub fn new<R, W, A>(stream: Stream<R, W>, signer: xdsa::SecretKey, attester: A) -> Self
51    where
52        R: Read + Send + 'static,
53        W: Write + Send + 'static,
54        A: Attester + Send + 'static,
55    {
56        // Retain the stream clock and shutdown handle for every accepted session
57        let stream_closer = stream.closer();
58        let server = Self {
59            inner: Arc::new(ServerInner {
60                clock: stream.clock(),
61                state: Mutex::new(State::Open {
62                    max_inbound_requests: DEFAULT_MAX_INBOUND_REQUESTS,
63                    max_inbound_bytes: DEFAULT_MAX_INBOUND_BYTES,
64                    autoreply_timeout: DEFAULT_AUTOREPLY_TIMEOUT,
65                    session: Weak::new(),
66                    ready: None,
67                    #[cfg(any(test, feature = "fuzz"))]
68                    wait_hook: None,
69                    #[cfg(any(test, feature = "fuzz"))]
70                    retry_hook: None,
71                }),
72                changed: Condvar::new(&stream.clock()),
73                stream_closer: Some(stream_closer),
74                #[cfg(any(test, feature = "fuzz"))]
75                workers: Arc::new(worker::Tracker::default()),
76            }),
77        };
78
79        // Keep accepting transport handshakes until this owner closes
80        let server_ref = Arc::downgrade(&server.inner);
81        worker::spawn(
82            "wire-server-reader",
83            #[cfg(any(test, feature = "fuzz"))]
84            &server.inner.workers,
85            move || {
86                let transport = transport::Server::new(stream, signer, attester);
87                run_reader(transport, server_ref);
88            },
89        );
90        server
91    }
92
93    /// Sets the timeout for automatic `UNANSWERED` and `UNKNOWN` replies in the
94    /// current and future sessions.
95    ///
96    /// Defaults to [`DEFAULT_AUTOREPLY_TIMEOUT`]. Applies even before
97    /// [`Self::accept`]. Replies already queued keep their deadlines.
98    ///
99    /// See [`Session::set_autoreply_timeout`] for when the timeout starts and
100    /// expires. Changing a session's timeout leaves the server's default unchanged.
101    /// This method also replaces a timeout set directly on the current session.
102    pub fn set_autoreply_timeout(self, timeout: Duration) -> Self {
103        self.inner.set_autoreply_timeout(timeout);
104        self
105    }
106
107    /// Sets both per-session inbound limits, initially
108    /// [`DEFAULT_MAX_INBOUND_REQUESTS`] and [`DEFAULT_MAX_INBOUND_BYTES`].
109    ///
110    /// Applies to the current session, even before [`Self::accept`], and future
111    /// sessions. Lowering either limit below usage closes that session. The
112    /// server stays open.
113    ///
114    /// See [`Session::set_inbound_limits`] for what each limit counts. Changing a
115    /// session's limits leaves the server's defaults unchanged. This method also
116    /// replaces limits set directly on the current session.
117    pub fn set_inbound_limits(self, requests: usize, bytes: usize) -> Self {
118        self.inner.set_inbound_limits(requests, bytes);
119        self
120    }
121
122    /// Blocks until a session is established or the server ends.
123    ///
124    /// Recoverable handshake failures leave the stream available for another
125    /// attempt. A replacement session closes the previous one, and old handles
126    /// still refer to it.
127    ///
128    /// The reader runs before acceptance. A returned session may already have
129    /// queued requests or be closed, including from exceeding an inbound limit.
130    /// If several sessions arrive before acceptance, only the newest is returned.
131    pub fn accept(&mut self) -> Result<Session, Error> {
132        let mut state = self.inner.state.lock().expect("server state not poisoned");
133        loop {
134            match &mut *state {
135                State::Closed { reason, .. } => return Err(reason.clone()),
136                State::Open {
137                    ready,
138                    #[cfg(any(test, feature = "fuzz"))]
139                    wait_hook,
140                    ..
141                } => {
142                    if let Some(session) = ready.take() {
143                        return Ok(session);
144                    }
145                    #[cfg(any(test, feature = "fuzz"))]
146                    if let Some(wait_hook) = wait_hook.take() {
147                        let _ = wait_hook.send(());
148                    }
149                    state = self
150                        .inner
151                        .changed
152                        .wait(state)
153                        .expect("server state not poisoned");
154                }
155            }
156        }
157    }
158
159    /// Returns a clonable handle for closing this server from another thread,
160    /// including while its owner is blocked in [`Self::accept`].
161    pub fn closer(&self) -> Closer {
162        Closer::server(Arc::downgrade(&self.inner))
163    }
164
165    /// Permanently closes this server and its active session.
166    ///
167    /// It wakes blocked acceptance and receive calls and fails unresolved
168    /// operations. It also closes the stream, waiting for adapter calls in
169    /// progress to return. Repeated calls have no further effect. It does not
170    /// join application jobs or guarantee the peer has observed closure.
171    pub fn close(&self) {
172        self.inner.close(Error::Closed);
173    }
174}
175
176/// Receives transport events across successive server sessions.
177///
178/// Weak references let closed sessions be freed while this reader waits for
179/// another handshake.
180fn run_reader<R: Read, W: Write + Send + 'static, A: Attester>(
181    mut transport: transport::Server<R, W, A>,
182    server_ref: Weak<ServerInner>,
183) {
184    let mut current: Weak<SessionInner> = Weak::new();
185    loop {
186        // Hold no server state across the blocking read. Dropping Server closes
187        // its stream and wakes this call.
188        let result = transport.recv();
189        let Some(server) = server_ref.upgrade() else {
190            break;
191        };
192        match result {
193            // A successful handshake gets its own session and workers
194            Ok(transport::Event::Connected(sender)) => {
195                let session = Session::start(
196                    Side::Server,
197                    server.clock.clone(),
198                    sender,
199                    None,
200                    #[cfg(any(test, feature = "fuzz"))]
201                    server.workers.clone(),
202                );
203                current = Arc::downgrade(&session.inner);
204                if server.attach(session).is_err() {
205                    break;
206                }
207            }
208            // Disconnecting closes the session while keeping the server stream
209            Ok(transport::Event::Disconnected) => {
210                if let Some(session) = current.upgrade() {
211                    session.close(transport::Error::SessionReset.into());
212                }
213                current = Weak::new();
214            }
215            // A message goes to the current session, which closes if handling fails
216            Ok(transport::Event::Message(bytes)) => {
217                if let Some(session) = current.upgrade()
218                    && let Err(error) = session.handle_message(bytes)
219                {
220                    session.close(error);
221                }
222            }
223            // Keep the session, whose binding and partial frame survive the
224            // failed read, and pause so a failing adapter cannot spin the reader
225            Err(transport::Error::RecvFailed(error)) => {
226                tracing::warn!("wire receive failed, retrying: {}", error);
227                if !server.pause_reader() {
228                    break;
229                }
230            }
231            // A handshake that failed to write leaves the reader available for
232            // the next reset
233            Err(transport::Error::SendFailed(error)) => {
234                tracing::debug!("wire handshake output failed: {}", error);
235            }
236            // The end of the stream, or any other failure, ends the server
237            Err(error) => {
238                server.close(error.into());
239                break;
240            }
241        }
242    }
243}
244
245impl Drop for Server {
246    /// Ends the server and its attached session even when handles remain.
247    fn drop(&mut self) {
248        self.close();
249    }
250}
251
252impl fmt::Debug for Server {
253    /// Shows whether the server still accepts sessions.
254    ///
255    /// A state lock held elsewhere leaves the state out.
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        let mut server = f.debug_struct("Server");
258        if let Ok(state) = self.inner.state.try_lock() {
259            server.field("open", &matches!(*state, State::Open { .. }));
260        }
261        server.finish_non_exhaustive()
262    }
263}
264
265/// Shared server state, holding at most one session waiting for acceptance.
266///
267/// The reader attaches replacements in transport order. Accepted sessions own
268/// themselves, and the server retains only a weak reference for server shutdown.
269pub(super) struct ServerInner {
270    /// Clock inherited by every session accepted on this stream.
271    clock: Clock,
272    /// Attached session, pending acceptance and server closure, under one lock.
273    state: Mutex<State>,
274    /// Signal waking [`Server::accept`] when a session is attached or the
275    /// server closes.
276    changed: Condvar,
277    /// Closer of the server's stream, empty in tests that supply sessions directly.
278    stream_closer: Option<transport::Closer>,
279    /// Tracker letting tests wait for the reader and all session workers to exit.
280    #[cfg(any(test, feature = "fuzz"))]
281    pub(super) workers: Arc<worker::Tracker>,
282}
283
284/// Server policy and the session waiting for acceptance, or the error that
285/// closed the server.
286enum State {
287    /// Open server, tracking the current session and keeping its owner until
288    /// [`Server::accept`] takes it.
289    Open {
290        /// Request ceiling applied to the attached session and future sessions.
291        max_inbound_requests: usize,
292        /// Encoded-byte ceiling applied independently to each session.
293        max_inbound_bytes: usize,
294        /// Automatic reply timeout applied to the current and future sessions.
295        autoreply_timeout: Duration,
296        /// Current session, which server closure closes even after
297        /// [`Server::accept`] returns it.
298        session: Weak<SessionInner>,
299        /// Session waiting for [`Server::accept`], replaced by a newer handshake.
300        ready: Option<Session>,
301        /// One-shot test notification sent under the server lock before waiting.
302        #[cfg(any(test, feature = "fuzz"))]
303        wait_hook: Option<std::sync::mpsc::Sender<()>>,
304        /// Test notification of each pause the reader takes after a failed
305        /// read, carrying the deadline it waits for, sent under the server lock.
306        #[cfg(any(test, feature = "fuzz"))]
307        retry_hook: Option<std::sync::mpsc::Sender<std::time::Instant>>,
308    },
309    /// Closed server, with its closing error and the session attached at the time.
310    ///
311    /// Repeated [`ServerInner::close`] calls can finish closing that session if
312    /// the first closer is still doing so.
313    Closed {
314        /// First reason the server ended; later closes cannot replace it.
315        reason: Error,
316        /// Session that was attached when the server closed.
317        session: Weak<SessionInner>,
318    },
319}
320
321impl ServerInner {
322    /// Updates the current session's timeout and the default under the
323    /// attachment lock.
324    ///
325    /// Lock order is server then session, as with inbound limit updates.
326    fn set_autoreply_timeout(&self, timeout: Duration) {
327        let mut state = self.state.lock().expect("server state not poisoned");
328        if let State::Open {
329            autoreply_timeout,
330            session,
331            ..
332        } = &mut *state
333        {
334            *autoreply_timeout = timeout;
335            if let Some(session) = session.upgrade() {
336                session.set_autoreply_timeout(timeout);
337            }
338        }
339    }
340
341    /// Updates the current session's limits and the defaults under the
342    /// attachment lock.
343    ///
344    /// Lock order is server then session, and session methods never acquire
345    /// the server lock. Server sessions have no stream closer, so applying
346    /// their limits cannot wait for stream I/O.
347    fn set_inbound_limits(&self, requests: usize, bytes: usize) {
348        // Defer session callbacks until the server's policy lock is released too
349        let mut notifications = Notifications::default();
350        let mut state = self.state.lock().expect("server state not poisoned");
351        if let State::Open {
352            max_inbound_requests,
353            max_inbound_bytes,
354            session,
355            ..
356        } = &mut *state
357        {
358            *max_inbound_requests = requests;
359            *max_inbound_bytes = bytes;
360            if let Some(session) = session.upgrade() {
361                session.set_inbound_limits(requests, bytes, &mut notifications);
362            }
363        }
364    }
365
366    /// Closes the server, refusing attachment and acceptance before closing the
367    /// attached session.
368    ///
369    /// The server lock is released before closing or dropping a [`Session`],
370    /// since those operations take the session's own lock.
371    pub(super) fn close(&self, error: Error) {
372        // Stop `attach` and `accept` by switching to `Closed`. Save the attached
373        // session so repeated `close` calls can finish closing it too.
374        let (session, reason, ready) = {
375            let mut state = self.state.lock().expect("server state not poisoned");
376            match &mut *state {
377                State::Closed { reason, session } => (session.upgrade(), reason.clone(), None),
378                State::Open { session, ready, .. } => {
379                    let session = session.clone();
380                    let ready = ready.take();
381                    *state = State::Closed {
382                        reason: error.clone(),
383                        session: session.clone(),
384                    };
385                    match &error {
386                        Error::Closed => tracing::info!("wire server closed locally"),
387                        _ if error.orderly() => {
388                            tracing::info!("wire server closed: {}", error.reason());
389                        }
390                        _ => tracing::warn!("wire server failed: {}", error.reason()),
391                    }
392                    (session.upgrade(), error, ready)
393                }
394            }
395        };
396
397        // Release the server lock before taking the session's lock. Wake local
398        // callers before closing the stream, which waits for active I/O to return.
399        if let Some(session) = session {
400            session.close(reason);
401        }
402        self.changed.notify_all();
403        drop(ready);
404        if let Some(stream_closer) = &self.stream_closer {
405            stream_closer.close();
406        }
407    }
408
409    /// Waits [`READ_RETRY_DELAY`] on the stream clock before the reader retries
410    /// a failed read, returning false if the server closes first.
411    fn pause_reader(&self) -> bool {
412        // Fix the deadline first, so a wait that starts late still ends on time
413        let deadline = self.clock.now() + READ_RETRY_DELAY;
414        let mut state = self.state.lock().expect("server state not poisoned");
415
416        // Let a test move time on once the pause has its deadline
417        #[cfg(any(test, feature = "fuzz"))]
418        if let State::Open {
419            retry_hook: Some(hook),
420            ..
421        } = &*state
422        {
423            let _ = hook.send(deadline);
424        }
425
426        // Wait out the delay, ending early once a close switches the state
427        loop {
428            if matches!(*state, State::Closed { .. }) {
429                return false;
430            }
431            let (guard, result) = self
432                .changed
433                .wait_deadline(state, deadline)
434                .expect("server state not poisoned");
435            state = guard;
436            if result.timed_out() {
437                return matches!(*state, State::Open { .. });
438            }
439        }
440    }
441
442    /// Closes the previous session and makes this one available to
443    /// [`Server::accept`].
444    ///
445    /// Only the reader, or the test fixture replacing it, calls this method.
446    fn attach(&self, session: Session) -> Result<(), Error> {
447        // Take an Arc to the previous session, then release the server lock
448        // before closing that session
449        let previous = {
450            let state = self.state.lock().expect("server state not poisoned");
451            match &*state {
452                State::Closed { reason, .. } => return Err(reason.clone()),
453                State::Open { session, .. } => session.upgrade(),
454            }
455        };
456        if let Some(previous) = previous {
457            tracing::info!(
458                "replacing wire session {} with session {}",
459                previous.log_id,
460                session.inner.log_id
461            );
462            previous.close(transport::Error::SessionReset.into());
463        }
464
465        // Another thread may have closed the server while we closed the old
466        // session. Check again under the lock before installing the new one.
467        let mut notifications = Notifications::default();
468        let previous = {
469            let mut state = self.state.lock().expect("server state not poisoned");
470            match &mut *state {
471                State::Closed { reason, .. } => return Err(reason.clone()),
472                State::Open {
473                    session: attached,
474                    max_inbound_requests,
475                    max_inbound_bytes,
476                    autoreply_timeout,
477                    ready,
478                    ..
479                } => {
480                    // Apply the current policy before exposing this session or
481                    // letting the reader deliver its first message
482                    session.inner.set_inbound_limits(
483                        *max_inbound_requests,
484                        *max_inbound_bytes,
485                        &mut notifications,
486                    );
487                    session.inner.set_autoreply_timeout(*autoreply_timeout);
488                    *attached = Arc::downgrade(&session.inner);
489                    ready.replace(session)
490                }
491            }
492        };
493
494        // Wake a blocked accept, then drop the owner of a previous session that
495        // accept never took
496        self.changed.notify_all();
497        drop(previous);
498        Ok(())
499    }
500}
501
502/// Fixture supplying sessions in tests in place of the server's transport reader.
503#[cfg(any(test, feature = "fuzz"))]
504pub(super) struct SessionSource {
505    /// Server that receives sessions created by [`SessionSource::open`].
506    server_ref: Weak<ServerInner>,
507}
508
509#[cfg(any(test, feature = "fuzz"))]
510impl Server {
511    /// Creates a server and a fixture that attaches sessions without a stream.
512    pub(super) fn fixture(clock: Clock) -> (Self, SessionSource) {
513        let inner = Arc::new(ServerInner {
514            clock: clock.clone(),
515            state: Mutex::new(State::Open {
516                max_inbound_requests: DEFAULT_MAX_INBOUND_REQUESTS,
517                max_inbound_bytes: DEFAULT_MAX_INBOUND_BYTES,
518                autoreply_timeout: DEFAULT_AUTOREPLY_TIMEOUT,
519                session: Weak::new(),
520                ready: None,
521                wait_hook: None,
522                retry_hook: None,
523            }),
524            changed: Condvar::new(&clock),
525            stream_closer: None,
526            workers: Arc::new(worker::Tracker::default()),
527        });
528        let source = SessionSource {
529            server_ref: Arc::downgrade(&inner),
530        };
531        (Self { inner }, source)
532    }
533}
534
535#[cfg(any(test, feature = "fuzz"))]
536impl SessionSource {
537    /// Creates a session and passes it to [`ServerInner::attach`], just as the
538    /// reader does after a handshake.
539    ///
540    /// Returns its weak reference so tests can deliver messages to it even
541    /// after another session connects.
542    pub(super) fn open(&mut self) -> Result<Weak<SessionInner>, Error> {
543        let server = self.server_ref.upgrade().ok_or(Error::Closed)?;
544        let session = Session::fixture_with_clock(Side::Server, server.clock.clone());
545        let session_ref = Arc::downgrade(&session.inner);
546        server.attach(session)?;
547        Ok(session_ref)
548    }
549}
550
551#[cfg(any(test, feature = "fuzz"))]
552impl Drop for SessionSource {
553    /// Models loss of the transport reader by permanently ending its server.
554    fn drop(&mut self) {
555        if let Some(server) = self.server_ref.upgrade() {
556            server.close(crate::transport::Error::Terminated.into());
557        }
558    }
559}
560
561#[cfg(any(test, feature = "fuzz"))]
562impl ServerInner {
563    /// Arms a one-shot notification for [`Server::accept`] waiting without a
564    /// ready session.
565    ///
566    /// It is sent while holding `state`, just before acceptance waits on
567    /// `changed`. Tests can then attach a session or close the server without
568    /// using sleeps.
569    ///
570    /// # Panics
571    ///
572    /// Panics if the fixture is closed or already has a session waiting for
573    /// acceptance.
574    pub(super) fn watch_accept_wait(&self) -> std::sync::mpsc::Receiver<()> {
575        let (sender, receiver) = std::sync::mpsc::channel();
576        let mut state = self.state.lock().expect("server state not poisoned");
577        let State::Open {
578            ready, wait_hook, ..
579        } = &mut *state
580        else {
581            panic!("only watch an open server accept");
582        };
583        assert!(ready.is_none());
584        *wait_hook = Some(sender);
585        receiver
586    }
587
588    /// Arms a notification for every pause the reader takes after a failed
589    /// read, carrying the deadline the pause waits for.
590    ///
591    /// It is sent while holding `state`, once the pause has fixed its
592    /// deadline, so advancing the clock to it cannot race the wait.
593    ///
594    /// # Panics
595    ///
596    /// Panics if the server is closed.
597    pub(super) fn watch_read_retries(&self) -> std::sync::mpsc::Receiver<std::time::Instant> {
598        let (sender, receiver) = std::sync::mpsc::channel();
599        let mut state = self.state.lock().expect("server state not poisoned");
600        let State::Open { retry_hook, .. } = &mut *state else {
601            panic!("only watch an open server read");
602        };
603        *retry_hook = Some(sender);
604        receiver
605    }
606}
607
608/// Checks server policy callbacks and ownership bounds, and compiles server
609/// construction and acceptance.
610#[cfg(test)]
611#[cfg_attr(coverage_nightly, coverage(off))]
612mod tests {
613    use super::Side;
614    use crate::memory;
615    use crate::protocol::{self, Error, Message, Server, Session};
616    use crate::transport::mock::self_attestation;
617    use crate::transport::testing::test_clock;
618    use crate::transport::{Attester, Read, Stream, Write};
619    use darkbio_clock::Clock;
620    use darkbio_crypto::xdsa;
621    use std::collections::VecDeque;
622    use std::fmt::Debug;
623    use std::io::{self, Write as _};
624    use std::sync::{Arc, Mutex, mpsc};
625    use std::thread;
626    use std::time::{Duration, Instant};
627
628    /// Pause the server's docs promise between a failed read and its retry.
629    const PAUSE: Duration = Duration::from_millis(100);
630
631    /// Failure a [`Faults`] reader injects.
632    enum Fault {
633        /// The read itself fails.
634        Read,
635        /// Installing the read's deadline fails with a timeout, as a broken
636        /// adapter's setter can.
637        Setter,
638        /// The read fails and loses the rest of the frame it was inside.
639        Loss,
640    }
641
642    /// Ark-side reader that hands out one byte per read and injects the
643    /// failures its test queues, recording when each happened.
644    struct Faults {
645        /// Duplex half carrying the host's bytes.
646        inner: memory::Reader,
647        /// Failures the test queued, shared with it.
648        plan: Arc<Mutex<Plan>>,
649    }
650
651    /// Failures a [`Faults`] reader still owes, and those it injected.
652    #[derive(Default)]
653    struct Plan {
654        /// Bytes to hand out before the first queued failure.
655        after: usize,
656        /// Failures to inject in order, one per read attempt.
657        queued: VecDeque<Fault>,
658        /// Clock times of the injected failures.
659        failed: Vec<Instant>,
660        /// Whether bytes are being dropped up to the next frame delimiter.
661        losing: bool,
662    }
663
664    impl io::Read for Faults {
665        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
666            // Fail this read if a read or loss failure is due, recording when
667            {
668                let mut plan = self.plan.lock().unwrap();
669                if plan.after == 0 && matches!(plan.queued.front(), Some(Fault::Read | Fault::Loss))
670                {
671                    let fault = plan.queued.pop_front().unwrap();
672                    plan.losing = matches!(fault, Fault::Loss);
673                    plan.failed.push(self.inner.clock().now());
674                    return Err(io::Error::other("injected read failure"));
675                }
676            }
677
678            // Hand out one byte, so a failure can land inside a frame, dropping
679            // the bytes a loss took up to the frame's delimiter
680            loop {
681                let len = buf.len().min(1);
682                let read = self.inner.read(&mut buf[..len])?;
683                let mut plan = self.plan.lock().unwrap();
684                if plan.losing && read == 1 && buf[0] != 0 {
685                    continue;
686                }
687                plan.losing = false;
688                plan.after = plan.after.saturating_sub(read);
689                return Ok(read);
690            }
691        }
692    }
693
694    impl Read for Faults {
695        fn clock(&self) -> Clock {
696            self.inner.clock()
697        }
698
699        fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
700            // Fail installing the deadline if a setter failure is due,
701            // recording when
702            {
703                let mut plan = self.plan.lock().unwrap();
704                if plan.after == 0 && matches!(plan.queued.front(), Some(Fault::Setter)) {
705                    plan.queued.pop_front();
706                    plan.failed.push(self.inner.clock().now());
707                    return Err(io::ErrorKind::TimedOut.into());
708                }
709            }
710            self.inner.set_read_deadline(deadline)
711        }
712    }
713
714    /// Starts a server whose reader fails on request.
715    ///
716    /// Returns the server, the host's end of the link, the failure plan, the
717    /// deadlines of the reader's pauses and the server's identity.
718    fn faulty_server(
719        clock: &Clock,
720    ) -> (
721        Server,
722        memory::Duplex,
723        Arc<Mutex<Plan>>,
724        mpsc::Receiver<Instant>,
725        xdsa::PublicKey,
726    ) {
727        // Wrap the Ark's reader, so the test decides which reads fail
728        let signer = xdsa::SecretKey::generate();
729        let identity = signer.public_key();
730        let attestation = self_attestation(&signer);
731        let (host, ark) = memory::duplex(64 * 1024, clock);
732        let (reader, writer, closer, _) = ark.into_parts();
733        let plan = Arc::new(Mutex::new(Plan::default()));
734        let faults = Faults {
735            inner: reader,
736            plan: plan.clone(),
737        };
738        let stream = Stream::new(faults, writer, move || closer.close());
739
740        // Start the server and watch its reader pause after each failure
741        let server = Server::new(stream, signer, attestation);
742        let retries = server.inner.watch_read_retries();
743        (server, host, plan, retries, identity)
744    }
745
746    /// Checks that a failed read and a failing deadline setter keep the server,
747    /// each retry waiting out the documented pause, and that the handshake
748    /// then completes.
749    #[test]
750    fn test_server_retries_failed_reads() {
751        // Fail a read and then a deadline setter behind a client's first byte
752        let mut tester = test_clock();
753        let (mut server, host, plan, retries, identity) = faulty_server(&tester.clock());
754        {
755            let mut plan = plan.lock().unwrap();
756            plan.after = 1;
757            plan.queued.extend([Fault::Read, Fault::Setter]);
758        }
759        let connecting =
760            thread::spawn(move || protocol::connect(host, &identity).map(|(client, _)| client));
761
762        // Require each pause to last the documented time and the retry to wait
763        // it out
764        for failure in 0..2 {
765            let deadline = retries.recv().unwrap();
766            let failed = plan.lock().unwrap().failed[failure];
767            assert_eq!(deadline - failed, PAUSE, "{failure}");
768            tester.advance_to(deadline);
769        }
770        let failed = plan.lock().unwrap().failed.clone();
771        assert_eq!(failed[1] - failed[0], PAUSE);
772
773        // Require the handshake to complete once reads succeed again, keeping
774        // the client open until the server accepts
775        let _client = connecting.join().unwrap().unwrap();
776        server.accept().unwrap();
777    }
778
779    /// Checks that a read failing inside a frame keeps the session, whose
780    /// deadlines run during the pause, and which gets the frame once the
781    /// retry reads the rest.
782    #[test]
783    fn test_server_keeps_session_across_failed_read() {
784        // Connect, and leave an Ark request that expires during the pause
785        let mut tester = test_clock();
786        let (mut server, host, plan, retries, identity) = faulty_server(&tester.clock());
787        let (client, _) = protocol::connect(host, &identity).unwrap();
788        let mut session = server.accept().unwrap();
789        let expiry = tester.clock().now() + PAUSE / 2;
790        let expiring = session
791            .requester()
792            .request(b"pong".to_vec(), expiry)
793            .unwrap();
794
795        // Send a request whose frame a failed read splits after its first byte
796        {
797            let mut plan = plan.lock().unwrap();
798            plan.after = 1;
799            plan.queued.push_back(Fault::Read);
800        }
801        let deadline = tester.clock().now() + Duration::from_secs(5);
802        let answer = client
803            .requester()
804            .request(b"ping".to_vec(), deadline)
805            .unwrap();
806
807        // Require the deadline worker to expire the Ark request while the
808        // reader pauses, then let the reader retry
809        let resume = retries.recv().unwrap();
810        tester.advance_to(expiry);
811        assert!(matches!(expiring.wait_worker_result(), Err(Error::Timeout)));
812        tester.advance_to(resume);
813
814        // Require the same session to receive the split request and answer it
815        let (message, responder) = session.recv().unwrap();
816        assert_eq!(message, Message::Develop(b"ping".to_vec()));
817        let written = responder.reply(message, deadline).unwrap();
818        assert_eq!(answer.wait::<Vec<u8>>().unwrap(), b"ping");
819        written.wait().unwrap();
820    }
821
822    /// Checks that a read failure losing the rest of a frame ends the session
823    /// instead of letting it read past the gap.
824    #[test]
825    fn test_server_ends_session_after_lost_frame() {
826        // Connect, then lose all but the first byte of the next frame
827        let mut tester = test_clock();
828        let (mut server, host, plan, retries, identity) = faulty_server(&tester.clock());
829        let (client, _) = protocol::connect(host, &identity).unwrap();
830        let mut session = server.accept().unwrap();
831        {
832            let mut plan = plan.lock().unwrap();
833            plan.after = 1;
834            plan.queued.push_back(Fault::Loss);
835        }
836
837        // Send a request into the loss and another behind it
838        let deadline = tester.clock().now() + Duration::from_secs(5);
839        let _lost = client
840            .requester()
841            .request(b"lost".to_vec(), deadline)
842            .unwrap();
843        let _after = client
844            .requester()
845            .request(b"after".to_vec(), deadline)
846            .unwrap();
847        tester.advance_to(retries.recv().unwrap());
848
849        // Require the session to end on the broken frame without either request
850        assert!(session.recv().is_err());
851    }
852
853    /// Checks that closing the server ends the reader's pause with the clock
854    /// stopped.
855    #[test]
856    fn test_server_close_interrupts_read_retry() {
857        // Fail the read after a junk byte, leaving the reader waiting in its pause
858        let tester = test_clock();
859        let (server, host, plan, retries, _) = faulty_server(&tester.clock());
860        {
861            let mut plan = plan.lock().unwrap();
862            plan.after = 1;
863            plan.queued.push_back(Fault::Read);
864        }
865        let (_reader, mut writer, _closer, _) = host.into_parts();
866        writer.write_all(&[1]).unwrap();
867        retries.recv().unwrap();
868
869        // Close the server and require its reader to exit without the clock
870        let workers = server.inner.workers.clone();
871        server.close();
872        workers.wait_stopped();
873    }
874
875    /// Checks that a server policy change closing its session runs promise
876    /// callbacks outside the server lock.
877    #[test]
878    fn test_limit_callback_releases_server_lock() {
879        use darkbio_clock::TestClock;
880        use std::sync::mpsc;
881        use std::time::Duration;
882
883        // Attach a clock-controlled session and retain a peer request against its limit
884        let tester = TestClock::new();
885        let (server, _source) = Server::fixture(tester.clock());
886        let session = Session::fixture_with_clock(Side::Server, tester.clock());
887        let inner = session.inner.clone();
888        server.inner.attach(session).unwrap();
889        inner.inject_request(1, vec![1].into()).unwrap();
890
891        // Register a callback on another pending operation in that session
892        let mut promise = inner
893            .request(
894                vec![2].into(),
895                tester.clock().now() + Duration::from_secs(5),
896            )
897            .unwrap();
898        let state = server.inner.clone();
899        let notification = promise.notification_unlocked();
900        let (observed, receiver) = mpsc::channel();
901        promise.notify(move || {
902            let _ = observed.send((state.state.try_lock().is_ok(), notification()));
903        });
904
905        // Lower the server policy and require notification outside both owning locks
906        let server = server.set_inbound_limits(0, 1024);
907        assert_eq!(receiver.try_recv(), Ok((true, true)));
908        assert!(matches!(
909            promise.wait::<Vec<u8>>(),
910            Err(Error::InboundRequestLimitExceeded(0))
911        ));
912        drop(server);
913    }
914
915    /// Compiles server construction from a caller-owned stream, signer and attester.
916    #[allow(dead_code)]
917    fn server<R, W, A>(stream: Stream<R, W>, signer: xdsa::SecretKey, attester: A) -> Server
918    where
919        R: Read + Send + 'static,
920        W: Write + Send + 'static,
921        A: Attester + Send + 'static,
922    {
923        Server::new(stream, signer, attester)
924    }
925
926    /// Checks that server acceptance returns the common concrete session type.
927    #[allow(dead_code)]
928    fn accept(server: &mut Server) -> Result<Session, Error> {
929        server.accept()
930    }
931
932    /// Checks the bounds required to move the server to an application thread
933    /// and to print it.
934    #[test]
935    fn test_thread_capabilities() {
936        /// Requires an owned value to be printable and transferable to a
937        /// background thread.
938        fn movable<T: Debug + Send + 'static>() {}
939        movable::<Server>();
940    }
941}