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/// 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}