Skip to main content

microsandbox_protocol_client/
options.rs

1//! Inert configuration, separate from connection and request execution.
2
3use std::time::Duration;
4
5use microsandbox_protocol::codec::MAX_FRAME_SIZE;
6use tokio::time::Instant;
7
8use crate::{ClientError, ClientResult, ErrorKind};
9
10//--------------------------------------------------------------------------------------------------
11// Types
12//--------------------------------------------------------------------------------------------------
13
14/// Hard resource bounds for one framed connection.
15#[derive(Debug, Clone)]
16pub struct ClientLimits {
17    /// Maximum length after the four-byte prefix, including ID and flags.
18    pub max_frame_size: u32,
19    /// Active and draining IDs both consume this capacity.
20    pub max_in_flight: usize,
21    /// Writer item capacity, in addition to the shared byte budget.
22    pub queued_writes: usize,
23    /// Per-subscription response capacity, in addition to the byte budget.
24    pub queued_responses: usize,
25    /// Combined queued/in-progress read and write bytes.
26    pub buffered_bytes: u32,
27    /// Completion deadline once the first frame byte arrives; idle is separate.
28    pub incomplete_frame_timeout: Option<Duration>,
29    /// Default local request wait; no remote cancellation is implied.
30    pub request_timeout: Option<Duration>,
31}
32
33/// Connection configuration. Builders have no I/O or background side effects.
34#[derive(Debug, Clone)]
35pub struct ConnectOptions {
36    /// Total time allowed for dial and protocol establishment.
37    pub setup_timeout: Duration,
38    /// Requested local resource limits; a protocol may reduce these.
39    pub limits: ClientLimits,
40}
41
42/// Options for one request or stream-opening attempt.
43#[derive(Debug, Clone, Default)]
44pub struct RequestOptions {
45    /// Overrides the connection's default local wait when set.
46    pub request_timeout: Option<Duration>,
47}
48
49//--------------------------------------------------------------------------------------------------
50// Methods
51//--------------------------------------------------------------------------------------------------
52
53impl ConnectOptions {
54    /// Set the deadline for the complete setup attempt.
55    pub fn setup_timeout(mut self, timeout: Duration) -> Self {
56        self.setup_timeout = timeout;
57        self
58    }
59
60    /// Configure resource bounds before connecting.
61    pub fn limits(mut self, configure: impl FnOnce(ClientLimits) -> ClientLimits) -> Self {
62        self.limits = configure(self.limits);
63        self
64    }
65}
66
67impl RequestOptions {
68    /// Stop waiting after this duration; the peer may still execute the work.
69    pub fn request_timeout(mut self, timeout: Duration) -> Self {
70        self.request_timeout = Some(timeout);
71        self
72    }
73}
74
75impl ClientLimits {
76    /// Reject limits that cannot buffer even one maximum-sized frame.
77    pub fn validate(&self) -> ClientResult<()> {
78        for timeout in [self.incomplete_frame_timeout, self.request_timeout]
79            .into_iter()
80            .flatten()
81        {
82            checked_deadline(timeout)?;
83        }
84        if self.max_frame_size < 5
85            || self.max_frame_size > MAX_FRAME_SIZE
86            || self.max_in_flight == 0
87            || self.queued_writes == 0
88            || self.queued_responses == 0
89            || self.queued_writes > tokio::sync::Semaphore::MAX_PERMITS
90            || self.queued_responses > tokio::sync::Semaphore::MAX_PERMITS
91            || u64::from(self.buffered_bytes) > tokio::sync::Semaphore::MAX_PERMITS as u64
92            || self.buffered_bytes < self.max_frame_size + 4
93        {
94            return Err(ClientError::new(ErrorKind::InvalidOptions));
95        }
96        Ok(())
97    }
98}
99
100//--------------------------------------------------------------------------------------------------
101// Trait Implementations
102//--------------------------------------------------------------------------------------------------
103
104impl Default for ConnectOptions {
105    fn default() -> Self {
106        Self {
107            setup_timeout: Duration::from_secs(10),
108            limits: ClientLimits::default(),
109        }
110    }
111}
112
113impl Default for ClientLimits {
114    fn default() -> Self {
115        Self {
116            max_frame_size: MAX_FRAME_SIZE,
117            max_in_flight: 1024,
118            queued_writes: 256,
119            queued_responses: 1024,
120            buffered_bytes: 8 * 1024 * 1024,
121            incomplete_frame_timeout: None,
122            request_timeout: None,
123        }
124    }
125}
126
127//--------------------------------------------------------------------------------------------------
128// Functions
129//--------------------------------------------------------------------------------------------------
130
131pub(crate) fn checked_deadline(timeout: Duration) -> ClientResult<Instant> {
132    Instant::now()
133        .checked_add(timeout)
134        .ok_or_else(|| ClientError::new(ErrorKind::InvalidOptions))
135}