microsandbox_control_client/
compat_message.rs1use 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
15pub trait IntoControlMessage: IntoOutboundMessage<ControlProtocol> {
21 fn into_json(self) -> ControlClientResult<ControlRequest>;
24}
25
26pub trait CheckedControlRequest: Request<ControlProtocol, Error = ControlClientError> {
28 fn json_request(&self) -> ControlClientResult<ControlRequest>;
30 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response>;
32}
33
34pub trait CompatibleControlRequest: Request<ControlProtocol, Error = ControlClientError> {
40 fn min_generation(&self) -> u8;
42 fn compatibility_json_bytes(&self) -> ControlClientResult<Zeroizing<Vec<u8>>>;
44 fn decode_compatibility_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response>;
46}
47
48impl<T: Serialize> IntoControlMessage for TypedMessage<T> {
53 fn into_json(self) -> ControlClientResult<ControlRequest> {
54 let bytes = Zeroizing::new(
58 serde_json::to_vec(&self.payload).map_err(|_| ClientError::new(ErrorKind::Encode))?,
59 );
60 let value =
61 JsonValue::parse(&bytes).map_err(|_| ClientError::new(ErrorKind::InvalidData))?;
62 if value.as_object().is_none() {
63 return invalid();
64 }
65 Ok(match self.message_type.as_str() {
66 "control.capabilities" => ControlRequest::Capabilities,
67 "control.memory.state" => ControlRequest::MemoryState,
68 "control.cpu.state" => ControlRequest::CpuState,
69 "control.memory.target" => ControlRequest::MemoryTarget {
70 total_mib: value
71 .get("total_mib")
72 .and_then(JsonValue::as_u64)
73 .ok_or_else(invalid_error)?,
74 },
75 "control.cpu.target" => ControlRequest::CpuTarget {
76 online: value
77 .get("online")
78 .and_then(JsonValue::as_u64)
79 .and_then(|number| number.try_into().ok())
80 .ok_or_else(invalid_error)?,
81 },
82 "control.secrets.update" => ControlRequest::SecretsUpdate {
83 changes: value
84 .get("changes")
85 .and_then(JsonValue::as_array)
86 .ok_or_else(invalid_error)?
87 .iter()
88 .map(secret_change)
89 .collect::<ControlClientResult<_>>()?,
90 },
91 _ => return Err(ControlClientError::UnsupportedMode),
92 })
93 }
94}
95
96impl<T: CheckedControlRequest> CompatibleControlRequest for T {
97 fn min_generation(&self) -> u8 {
98 1
99 }
100
101 fn compatibility_json_bytes(&self) -> ControlClientResult<Zeroizing<Vec<u8>>> {
102 Ok(Zeroizing::new(
103 serde_json::to_vec(&self.json_request()?)
104 .map_err(|_| ClientError::new(ErrorKind::Encode))?,
105 ))
106 }
107
108 fn decode_compatibility_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
109 CheckedControlRequest::decode_json(self, reply)
110 }
111}
112
113impl IntoControlMessage for EncodedMessage {
114 fn into_json(self) -> ControlClientResult<ControlRequest> {
115 Err(ControlClientError::UnsupportedMode)
116 }
117}
118
119impl CheckedControlRequest for GetCapabilities {
120 fn json_request(&self) -> ControlClientResult<ControlRequest> {
121 Ok(ControlRequest::Capabilities)
122 }
123 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
124 reply.checked(|value| {
125 json_reply::capabilities(value.get("capabilities")?)
126 .map(microsandbox_protocol::control::RuntimeCapabilities::generation_one)
127 })
128 }
129}
130
131impl CheckedControlRequest for GetMemoryState {
132 fn json_request(&self) -> ControlClientResult<ControlRequest> {
133 Ok(ControlRequest::MemoryState)
134 }
135 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
136 reply.checked(|value| json_reply::memory(value.get("memory")?))
137 }
138}
139
140impl CheckedControlRequest for SetMemoryTarget {
141 fn json_request(&self) -> ControlClientResult<ControlRequest> {
142 Ok(ControlRequest::MemoryTarget {
143 total_mib: self.total_mib,
144 })
145 }
146 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
147 CheckedControlRequest::decode_json(&GetMemoryState, reply)
148 }
149}
150
151impl CheckedControlRequest for GetCpuState {
152 fn json_request(&self) -> ControlClientResult<ControlRequest> {
153 Ok(ControlRequest::CpuState)
154 }
155 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
156 reply.checked(|value| json_reply::cpu(value.get("cpu")?))
157 }
158}
159
160impl CheckedControlRequest for SetCpuTarget {
161 fn json_request(&self) -> ControlClientResult<ControlRequest> {
162 Ok(ControlRequest::CpuTarget {
163 online: self.online,
164 })
165 }
166 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
167 CheckedControlRequest::decode_json(&GetCpuState, reply)
168 }
169}
170
171impl CheckedControlRequest for UpdateSecrets {
172 fn json_request(&self) -> ControlClientResult<ControlRequest> {
173 Ok(ControlRequest::SecretsUpdate {
174 changes: self.changes.clone(),
175 })
176 }
177 fn decode_json(&self, reply: JsonReply) -> ControlClientResult<Self::Response> {
178 reply.checked(|_| {
179 Some(SecretsResult::Complete {
180 applied_count: self.changes.len().try_into().ok()?,
181 })
182 })
183 }
184}
185
186fn invalid_error() -> ControlClientError {
191 ClientError::new(ErrorKind::InvalidData).into()
192}
193
194fn invalid<T>() -> ControlClientResult<T> {
195 Err(invalid_error())
196}
197
198fn secret_change(value: &JsonValue) -> ControlClientResult<SecretChange> {
199 let name = value
200 .get("name")
201 .and_then(JsonValue::as_str)
202 .ok_or_else(invalid_error)?
203 .to_owned();
204 Ok(match value.get("change").and_then(JsonValue::as_str) {
205 Some("rotate") => SecretChange::Rotate {
206 name,
207 value: SecretValue(
208 value
209 .get("value")
210 .and_then(JsonValue::as_str)
211 .ok_or_else(invalid_error)?
212 .to_owned(),
213 ),
214 },
215 Some("remove") => SecretChange::Remove { name },
216 Some("set_allowed_hosts") => SecretChange::SetAllowedHosts {
217 name,
218 hosts: value
219 .get("hosts")
220 .and_then(JsonValue::as_array)
221 .ok_or_else(invalid_error)?
222 .iter()
223 .map(|host| host.as_str().map(str::to_owned).ok_or_else(invalid_error))
224 .collect::<ControlClientResult<_>>()?,
225 },
226 _ => return invalid(),
227 })
228}