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