microsandbox_protocol/control/handshake.rs
1//! Control generation and handshake limits, separate from the agent protocol.
2
3use serde::{Deserialize, Serialize};
4
5use super::ControlError;
6use crate::codec::MAX_FRAME_SIZE;
7
8//--------------------------------------------------------------------------------------------------
9// Constants
10//--------------------------------------------------------------------------------------------------
11
12/// Current framed host-control generation.
13pub const CONTROL_GENERATION: u8 = 1;
14/// Stable protocol discriminator; it does not authenticate a peer.
15pub const CONTROL_PROTOCOL: &str = "msb.control";
16/// The opening frame stays small and zero-prefixed across future generations.
17pub const MAX_HANDSHAKE_FRAME_SIZE: u32 = 4096;
18/// Maximum outstanding control IDs on a default connection.
19pub const DEFAULT_MAX_IN_FLIGHT: u32 = 64;
20/// Default deadline for the complete automatic connection setup.
21pub const DEFAULT_SETUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
22/// Default local request wait, which never cancels or retries a mutation.
23pub const DEFAULT_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
24/// Bound for JSON capability responses consumed during discovery.
25pub const MAX_DISCOVERY_RESPONSE_SIZE: usize = 64 * 1024;
26
27//--------------------------------------------------------------------------------------------------
28// Types
29//--------------------------------------------------------------------------------------------------
30
31/// Opening framed-control offer. The envelope generation is always one.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct ControlHello {
34 /// Must equal [`CONTROL_PROTOCOL`].
35 pub protocol: String,
36 /// Oldest application generation understood by this client.
37 pub min_generation: u8,
38 /// Newest application generation understood by this client.
39 pub max_generation: u8,
40 /// Largest application frame this client can accept.
41 pub max_frame_size: u32,
42 /// Maximum outstanding IDs this client will use.
43 pub max_in_flight: u32,
44}
45
46/// Selected generation and limits. The welcome envelope is always generation one.
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct ControlWelcome {
49 /// Must equal [`CONTROL_PROTOCOL`].
50 pub protocol: String,
51 /// Application generation selected from the offer.
52 pub generation: u8,
53 /// Negotiated application frame ceiling, excluding the length prefix.
54 pub max_frame_size: u32,
55 /// Negotiated number of outstanding IDs.
56 pub max_in_flight: u32,
57}
58
59//--------------------------------------------------------------------------------------------------
60// Methods
61//--------------------------------------------------------------------------------------------------
62
63impl ControlHello {
64 /// Validate the offer without accepting any application operation.
65 pub fn validate(&self) -> Result<(), ControlError> {
66 if self.protocol != CONTROL_PROTOCOL
67 || self.min_generation == 0
68 || self.min_generation > self.max_generation
69 || !(MAX_HANDSHAKE_FRAME_SIZE..=MAX_FRAME_SIZE).contains(&self.max_frame_size)
70 || self.max_in_flight == 0
71 {
72 return Err(ControlError::rejected(
73 "invalid_handshake",
74 "invalid control handshake",
75 ));
76 }
77 Ok(())
78 }
79}
80
81impl ControlWelcome {
82 /// Select generation one and the smaller limits for the initial server.
83 pub fn negotiate(hello: &ControlHello, max_in_flight: u32) -> Result<Self, ControlError> {
84 hello.validate()?;
85 if max_in_flight == 0 {
86 return Err(ControlError::rejected(
87 "internal",
88 "invalid server admission limit",
89 ));
90 }
91 if hello.min_generation > CONTROL_GENERATION {
92 return Err(ControlError::rejected(
93 "unsupported_generation",
94 "no shared control generation",
95 ));
96 }
97 Ok(Self {
98 protocol: CONTROL_PROTOCOL.into(),
99 generation: CONTROL_GENERATION,
100 max_frame_size: hello.max_frame_size.min(MAX_FRAME_SIZE),
101 max_in_flight: hello.max_in_flight.min(max_in_flight),
102 })
103 }
104
105 /// Verify the response before admitting the client's first operation.
106 pub fn validate_for(&self, hello: &ControlHello) -> Result<(), ControlError> {
107 hello.validate()?;
108 if self.protocol != CONTROL_PROTOCOL
109 || !(hello.min_generation..=hello.max_generation).contains(&self.generation)
110 || !(MAX_HANDSHAKE_FRAME_SIZE..=hello.max_frame_size).contains(&self.max_frame_size)
111 || self.max_in_flight == 0
112 || self.max_in_flight > hello.max_in_flight
113 {
114 return Err(ControlError::rejected(
115 "invalid_handshake",
116 "invalid control welcome",
117 ));
118 }
119 Ok(())
120 }
121}
122
123//--------------------------------------------------------------------------------------------------
124// Trait Implementations
125//--------------------------------------------------------------------------------------------------
126
127impl Default for ControlHello {
128 fn default() -> Self {
129 Self {
130 protocol: CONTROL_PROTOCOL.into(),
131 min_generation: CONTROL_GENERATION,
132 max_generation: CONTROL_GENERATION,
133 max_frame_size: MAX_FRAME_SIZE,
134 max_in_flight: DEFAULT_MAX_IN_FLIGHT,
135 }
136 }
137}