Skip to main content

microsandbox_control_client/
compat_message.rs

1//! Explicit native translation and optional checked JSON normalization.
2
3use microsandbox_protocol::control::{ControlRequest, SecretChange, SecretValue, SecretsResult};
4use microsandbox_protocol_client::{
5    ClientError, EncodedMessage, ErrorKind, IntoOutboundMessage, Request, TypedMessage,
6};
7use serde::Serialize;
8use zeroize::Zeroizing;
9
10use crate::{
11    ControlClientError, ControlClientResult, ControlProtocol, GetCapabilities, GetCpuState,
12    GetMemoryState, JsonReply, JsonValue, SetCpuTarget, SetMemoryTarget, UpdateSecrets, json_reply,
13};
14
15//--------------------------------------------------------------------------------------------------
16// Types
17//--------------------------------------------------------------------------------------------------
18
19/// Named messages that can choose a real framed or legacy representation.
20pub trait IntoControlMessage: IntoOutboundMessage<ControlProtocol> {
21    /// Translate a known native request. Encoded payloads fail locally because
22    /// parsing them into a JSON substitute would discard the caller's wire form.
23    fn into_json(self) -> ControlClientResult<ControlRequest>;
24}
25
26/// Checked control requests sharing operation records across both formats.
27pub trait CheckedControlRequest: Request<ControlProtocol, Error = ControlClientError> {
28    /// Prepare the actual legacy operation, before any connection or write.
29    fn json_request(&self) -> ControlClientResult<ControlRequest>;
30    /// Normalize a real JSON response, preserving its original bytes on failure.
31    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response>;
32}
33
34//--------------------------------------------------------------------------------------------------
35// Trait Implementations
36//--------------------------------------------------------------------------------------------------
37
38impl<T: Serialize> IntoControlMessage for TypedMessage<T> {
39    fn into_json(self) -> ControlClientResult<ControlRequest> {
40        // Do not apply the framed four-MiB ceiling to a native legacy request.
41        // Parsing its actual JSON tokens also catches duplicate keys emitted by
42        // a custom Serialize implementation before a map could erase them.
43        let bytes = Zeroizing::new(
44            serde_json::to_vec(&self.payload).map_err(|_| ClientError::new(ErrorKind::Encode))?,
45        );
46        let value =
47            JsonValue::parse(&bytes).map_err(|_| ClientError::new(ErrorKind::InvalidData))?;
48        if value.as_object().is_none() {
49            return invalid();
50        }
51        Ok(match self.message_type.as_str() {
52            "control.capabilities" => ControlRequest::Capabilities,
53            "control.memory.state" => ControlRequest::MemoryState,
54            "control.cpu.state" => ControlRequest::CpuState,
55            "control.memory.target" => ControlRequest::MemoryTarget {
56                total_mib: value
57                    .get("total_mib")
58                    .and_then(JsonValue::as_u64)
59                    .ok_or_else(invalid_error)?,
60            },
61            "control.cpu.target" => ControlRequest::CpuTarget {
62                online: value
63                    .get("online")
64                    .and_then(JsonValue::as_u64)
65                    .and_then(|number| number.try_into().ok())
66                    .ok_or_else(invalid_error)?,
67            },
68            "control.secrets.update" => ControlRequest::SecretsUpdate {
69                changes: value
70                    .get("changes")
71                    .and_then(JsonValue::as_array)
72                    .ok_or_else(invalid_error)?
73                    .iter()
74                    .map(secret_change)
75                    .collect::<ControlClientResult<_>>()?,
76            },
77            _ => return Err(ControlClientError::UnsupportedMode),
78        })
79    }
80}
81
82impl IntoControlMessage for EncodedMessage {
83    fn into_json(self) -> ControlClientResult<ControlRequest> {
84        Err(ControlClientError::UnsupportedMode)
85    }
86}
87
88impl CheckedControlRequest for GetCapabilities {
89    fn json_request(&self) -> ControlClientResult<ControlRequest> {
90        Ok(ControlRequest::Capabilities)
91    }
92    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
93        reply.checked(|value| json_reply::capabilities(value.get("capabilities")?))
94    }
95}
96
97impl CheckedControlRequest for GetMemoryState {
98    fn json_request(&self) -> ControlClientResult<ControlRequest> {
99        Ok(ControlRequest::MemoryState)
100    }
101    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
102        reply.checked(|value| json_reply::memory(value.get("memory")?))
103    }
104}
105
106impl CheckedControlRequest for SetMemoryTarget {
107    fn json_request(&self) -> ControlClientResult<ControlRequest> {
108        Ok(ControlRequest::MemoryTarget {
109            total_mib: self.total_mib,
110        })
111    }
112    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
113        GetMemoryState.decode_json(reply)
114    }
115}
116
117impl CheckedControlRequest for GetCpuState {
118    fn json_request(&self) -> ControlClientResult<ControlRequest> {
119        Ok(ControlRequest::CpuState)
120    }
121    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
122        reply.checked(|value| json_reply::cpu(value.get("cpu")?))
123    }
124}
125
126impl CheckedControlRequest for SetCpuTarget {
127    fn json_request(&self) -> ControlClientResult<ControlRequest> {
128        Ok(ControlRequest::CpuTarget {
129            online: self.online,
130        })
131    }
132    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
133        GetCpuState.decode_json(reply)
134    }
135}
136
137impl CheckedControlRequest for UpdateSecrets {
138    fn json_request(&self) -> ControlClientResult<ControlRequest> {
139        Ok(ControlRequest::SecretsUpdate {
140            changes: self.changes.clone(),
141        })
142    }
143    fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
144        reply.checked(|_| {
145            Some(SecretsResult::Complete {
146                applied_count: self.changes.len().try_into().ok()?,
147            })
148        })
149    }
150}
151
152//--------------------------------------------------------------------------------------------------
153// Functions
154//--------------------------------------------------------------------------------------------------
155
156fn invalid_error() -> ControlClientError {
157    ClientError::new(ErrorKind::InvalidData).into()
158}
159
160fn invalid<T>() -> ControlClientResult<T> {
161    Err(invalid_error())
162}
163
164fn secret_change(value: &JsonValue) -> ControlClientResult<SecretChange> {
165    let name = value
166        .get("name")
167        .and_then(JsonValue::as_str)
168        .ok_or_else(invalid_error)?
169        .to_owned();
170    Ok(match value.get("change").and_then(JsonValue::as_str) {
171        Some("rotate") => SecretChange::Rotate {
172            name,
173            value: SecretValue(
174                value
175                    .get("value")
176                    .and_then(JsonValue::as_str)
177                    .ok_or_else(invalid_error)?
178                    .to_owned(),
179            ),
180        },
181        Some("remove") => SecretChange::Remove { name },
182        Some("set_allowed_hosts") => SecretChange::SetAllowedHosts {
183            name,
184            hosts: value
185                .get("hosts")
186                .and_then(JsonValue::as_array)
187                .ok_or_else(invalid_error)?
188                .iter()
189                .map(|host| host.as_str().map(str::to_owned).ok_or_else(invalid_error))
190                .collect::<ControlClientResult<_>>()?,
191        },
192        _ => return invalid(),
193    })
194}