Skip to main content

microsandbox_control_client/
protocol.rs

1//! Framed control setup. This path never probes or falls back to JSON.
2
3use std::sync::Arc;
4
5use microsandbox_protocol::{
6    codec::{self, RawFrame},
7    control::{
8        CONTROL_HANDSHAKE_GENERATION, ControlError, ControlHello, ControlWelcome,
9        DEFAULT_MAX_IN_FLIGHT, DEFAULT_REQUEST_TIMEOUT, DEFAULT_SETUP_TIMEOUT,
10        MAX_HANDSHAKE_FRAME_SIZE, control_message_min_generation,
11    },
12    wire::Envelope,
13};
14use microsandbox_protocol_client::{
15    BoxFuture, BoxTransport, CborEnvelopeCodec, Client, ClientError, ClientResult, ConnectOptions,
16    ErrorKind, Established, IdRange, Protocol, SendMetadata,
17};
18use tokio::io::AsyncReadExt;
19
20//--------------------------------------------------------------------------------------------------
21// Types
22//--------------------------------------------------------------------------------------------------
23
24/// Always-framed host control, including the full generic low-level surface.
25pub type ControlClient = Client<ControlProtocol>;
26
27/// Generation-one hello/welcome and operation metadata.
28pub struct ControlProtocol;
29
30/// Negotiated limits and the exact original welcome envelope.
31#[derive(Debug, Clone)]
32pub struct ControlReady {
33    /// Peer-selected generation and application ceilings.
34    pub welcome: ControlWelcome,
35    /// Original frame, including unknown envelope fields.
36    pub frame: RawFrame,
37}
38
39//--------------------------------------------------------------------------------------------------
40// Trait Implementations
41//--------------------------------------------------------------------------------------------------
42
43impl Protocol for ControlProtocol {
44    type Ready = ControlReady;
45
46    fn establish(
47        mut stream: BoxTransport,
48        options: ConnectOptions,
49    ) -> BoxFuture<'static, ClientResult<Established<Self::Ready>>> {
50        Box::pin(async move {
51            let hello = ControlHello {
52                max_frame_size: options.limits.max_frame_size,
53                max_in_flight: options
54                    .limits
55                    .max_in_flight
56                    .min(DEFAULT_MAX_IN_FLIGHT as usize) as u32,
57                ..Default::default()
58            };
59            hello
60                .validate()
61                .map_err(|_| ClientError::new(ErrorKind::InvalidOptions))?;
62            let opening = Envelope::new(CONTROL_HANDSHAKE_GENERATION, "control.hello", &hello)?
63                .frame(0, 0)?;
64            codec::write_raw_frame(&mut stream, &opening)
65                .await
66                .map_err(|error| match error {
67                    microsandbox_protocol::ProtocolError::Io(error) => ClientError::from(error),
68                    _ => ClientError::new(ErrorKind::InvalidData),
69                })?;
70            // Setup is already inside the engine's one total deadline. Bound
71            // allocation from the opening prefix before reading any body.
72            let length = stream.read_u32().await?;
73            if !(5..=MAX_HANDSHAKE_FRAME_SIZE).contains(&length) {
74                return Err(ClientError::new(ErrorKind::InvalidData));
75            }
76            let id = stream.read_u32().await?;
77            let flags = stream.read_u8().await?;
78            let mut body = vec![0; length as usize - 5];
79            stream.read_exact(&mut body).await?;
80            let frame = RawFrame { id, flags, body };
81            let envelope = Envelope::decode(&frame.body)?;
82            if frame.id != 0 || frame.flags != 1 || envelope.v != CONTROL_HANDSHAKE_GENERATION {
83                return Err(ClientError::new(ErrorKind::InvalidData));
84            }
85            if envelope.t == "control.error" {
86                let refusal: ControlError = envelope.payload()?;
87                let kind = if refusal.code == "unsupported_generation" {
88                    ErrorKind::UnsupportedOperation
89                } else {
90                    ErrorKind::InvalidData
91                };
92                return Err(ClientError::new(kind));
93            }
94            if envelope.t != "control.welcome" {
95                return Err(ClientError::new(ErrorKind::InvalidData));
96            }
97            let welcome: ControlWelcome = envelope.payload()?;
98            welcome
99                .validate_for(&hello)
100                .map_err(|_| ClientError::new(ErrorKind::InvalidData))?;
101            let mut limits = options.limits;
102            limits.max_frame_size = welcome.max_frame_size;
103            limits.max_in_flight = welcome.max_in_flight as usize;
104            limits
105                .incomplete_frame_timeout
106                .get_or_insert(DEFAULT_SETUP_TIMEOUT);
107            limits
108                .request_timeout
109                .get_or_insert(DEFAULT_REQUEST_TIMEOUT);
110            Ok(Established {
111                transport: stream,
112                codec: Arc::new(CborEnvelopeCodec),
113                ids: IdRange {
114                    start: 1,
115                    end_exclusive: 1u64 << 32,
116                },
117                ready: ControlReady { welcome, frame },
118                limits,
119            })
120        })
121    }
122
123    fn prepare(ready: &Self::Ready, wire_name: &str) -> ClientResult<SendMetadata> {
124        // Setup messages cannot be sent as ordinary named operations. Unknown
125        // extensions remain possible; raw callers can inspect any wire shape.
126        if matches!(wire_name, "control.hello" | "control.welcome") {
127            return Err(ClientError::new(ErrorKind::UnsupportedOperation));
128        }
129        if control_message_min_generation(wire_name)
130            .is_some_and(|minimum| minimum > ready.welcome.generation)
131        {
132            return Err(ClientError::new(ErrorKind::UnsupportedOperation));
133        }
134        Ok(SendMetadata {
135            generation: ready.welcome.generation,
136            flags: 0,
137        })
138    }
139}