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}