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}