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