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