Skip to main content

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}