Skip to main content

darkbio_wire/protocol/
requester.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//! Sending requests through a shared handle to a session.
8
9use super::session::SessionInner;
10use super::{Error, Message, Promise};
11use darkbio_clock::Clock;
12use std::sync::Weak;
13use std::time::Instant;
14
15/// Clonable handle for sending requests through the session that created it.
16///
17/// It does not keep the session open or follow a replacement session. Dropping
18/// a requester does not close the session or cancel operations it already
19/// submitted.
20#[derive(Clone, Debug)]
21pub struct Requester {
22    /// Clock of the session, kept here so it outlives the session.
23    clock: Clock,
24    /// Session that created this requester, even after a replacement connects.
25    session: Weak<SessionInner>,
26}
27
28impl Requester {
29    /// Returns the clock of this requester's session, which request deadlines are
30    /// measured on.
31    ///
32    /// It stays available after the session is gone.
33    pub fn clock(&self) -> Clock {
34        self.clock.clone()
35    }
36
37    /// Queues a request and returns its promise without waiting for the writer or
38    /// a reply.
39    ///
40    /// A closed session returns an error immediately, and errors after queueing
41    /// are returned through the promise. The outgoing queue has no capacity limit.
42    /// A message invalid for this session's direction fails the promise with
43    /// [`Error::WrongDirection`].
44    ///
45    /// The deadline covers time in the queue, sending and accepting the response.
46    /// Waiting on the promise does not start or refresh it. Dropping the promise
47    /// does not cancel the request. The peer may keep working after a timeout.
48    /// Decoding the response in [`Promise::wait`] is outside this deadline.
49    /// The expected response type is selected when waiting on [`Promise<Message>`].
50    pub fn request(
51        &self,
52        request: impl Into<Message>,
53        deadline: Instant,
54    ) -> Result<Promise<Message>, Error> {
55        self.session
56            .upgrade()
57            .ok_or(Error::Closed)?
58            .request(request.into(), deadline)
59    }
60
61    /// Creates a requester bound to one session, keeping its clock for after
62    /// the session is gone.
63    pub(super) fn new(session: Weak<SessionInner>, clock: &Clock) -> Self {
64        Self {
65            session,
66            clock: clock.clone(),
67        }
68    }
69}
70
71/// Checks requester sharing and compiles pipelined request submission.
72#[cfg(test)]
73#[cfg_attr(coverage_nightly, coverage(off))]
74mod tests {
75    use crate::protocol::schema::{DeviceInfoRequest, DeviceInfoResponse};
76    use crate::protocol::{Error, Message, Promise, Requester, Session};
77    use std::fmt::Debug;
78    use std::time::Instant;
79
80    /// Compiles sending several requests before waiting, dropping promises, and
81    /// choosing the expected response type at `wait()`.
82    #[allow(dead_code)]
83    fn pipeline(session: &Session, deadline: Instant) -> Result<(), Error> {
84        // Send two requests before waiting on either
85        let requester: Requester = session.requester();
86        let first: Promise<Message> = requester.request(DeviceInfoRequest {}, deadline)?;
87        let second = requester.request(DeviceInfoRequest {}, deadline)?;
88
89        // Drop a promise without selecting a response type
90        drop(requester.request(DeviceInfoRequest {}, deadline)?);
91
92        // Select the response type by annotation or by explicit generic argument
93        let _: DeviceInfoResponse = second.wait()?;
94        let _ = first.wait::<DeviceInfoResponse>()?;
95
96        // Take the message enum to match on it directly
97        let _: Message = requester.request(DeviceInfoRequest {}, deadline)?.wait()?;
98        Ok(())
99    }
100
101    /// Checks that `Requester` implements `Clone`, `Debug`, `Send`, and `Sync`.
102    #[test]
103    fn test_thread_capabilities() {
104        /// Requires a handle to be clonable, printable and usable by multiple threads.
105        fn shared<T: Clone + Debug + Send + Sync + 'static>() {}
106        shared::<Requester>();
107    }
108}