microsandbox_control_client/
json_reply.rs1use std::fmt;
4
5use microsandbox_protocol::control::{Capabilities, CpuState, MemoryState};
6use microsandbox_protocol_client::{ClientError, ErrorKind};
7use zeroize::Zeroizing;
8
9use crate::{ControlClientError, ControlClientResult, ControlMode, JsonValue};
10
11pub struct JsonReply {
17 raw: Zeroizing<Vec<u8>>,
18 value: JsonValue,
19}
20
21impl JsonReply {
26 pub fn parse(raw: Vec<u8>) -> ControlClientResult<Self> {
28 let raw = Zeroizing::new(raw);
29 let text =
30 std::str::from_utf8(&raw).map_err(|_| ClientError::new(ErrorKind::InvalidData))?;
31 let value = JsonValue::parse(text.trim().as_bytes())
32 .map_err(|_| ClientError::new(ErrorKind::InvalidData))?;
33 if value.as_object().is_none() {
34 return Err(ClientError::new(ErrorKind::InvalidData).into());
35 }
36 Ok(Self { raw, value })
37 }
38
39 pub fn raw(&self) -> &[u8] {
41 &self.raw
42 }
43
44 pub fn value(&self) -> &JsonValue {
46 &self.value
47 }
48
49 pub fn discovery_mode(&self) -> ControlClientResult<ControlMode> {
52 if self.value.get("ok").and_then(JsonValue::as_bool) != Some(true)
53 || self
54 .value
55 .get("error")
56 .is_some_and(|value| !matches!(value, JsonValue::Null))
57 || self
58 .value
59 .get("capabilities")
60 .and_then(capabilities)
61 .is_none()
62 {
63 return Err(ClientError::new(ErrorKind::InvalidData).into());
64 }
65 let Some(advertisement) = self.value.get("control_protocols") else {
66 return Ok(ControlMode::Json);
67 };
68 let protocols = advertisement
69 .as_array()
70 .ok_or_else(|| ClientError::new(ErrorKind::InvalidData))?;
71 let names = protocols
72 .iter()
73 .map(JsonValue::as_str)
74 .collect::<Option<Vec<_>>>()
75 .ok_or_else(|| ClientError::new(ErrorKind::InvalidData))?;
76 if names.contains(&"cbor") {
77 Ok(ControlMode::Framed)
78 } else if names.contains(&"json") {
79 Ok(ControlMode::Json)
80 } else {
81 Err(ClientError::new(ErrorKind::UnsupportedOperation).into())
82 }
83 }
84
85 pub(crate) fn checked<T>(
86 self,
87 decode: impl FnOnce(&JsonValue) -> Option<T>,
88 ) -> ControlClientResult<T> {
89 match self.value.get("ok").and_then(JsonValue::as_bool) {
90 Some(false) => Err(ControlClientError::LegacyRemote {
91 reply: Box::new(self),
92 }),
93 Some(true) => match decode(&self.value) {
94 Some(value) => Ok(value),
95 None => Err(ControlClientError::InvalidJsonResponse {
96 reply: Box::new(self),
97 }),
98 },
99 None => Err(ControlClientError::InvalidJsonResponse {
100 reply: Box::new(self),
101 }),
102 }
103 }
104}
105
106impl fmt::Debug for JsonReply {
111 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
112 formatter
113 .debug_struct("JsonReply")
114 .field("bytes", &self.raw.len())
115 .finish_non_exhaustive()
116 }
117}
118
119pub(crate) fn capabilities(value: &JsonValue) -> Option<Capabilities> {
124 Some(Capabilities {
125 root_disk_grow: value
126 .get("root_disk_grow")
127 .map(|v| v.as_bool())
128 .unwrap_or(Some(false))?,
129 cpu_resize: value.get("cpu_resize")?.as_bool()?,
130 memory_resize: value.get("memory_resize")?.as_bool()?,
131 secrets_update: value.get("secrets_update")?.as_bool()?,
132 })
133}
134
135pub(crate) fn memory(value: &JsonValue) -> Option<MemoryState> {
136 Some(MemoryState {
137 boot_mib: value.get("boot_mib")?.as_u64()?,
138 target_mib: value.get("target_mib")?.as_u64()?,
139 current_mib: value.get("current_mib")?.as_u64()?,
140 max_mib: value.get("max_mib")?.as_u64()?,
141 })
142}
143
144pub(crate) fn cpu(value: &JsonValue) -> Option<CpuState> {
145 Some(CpuState {
146 possible: value.get("possible")?.as_u64()?.try_into().ok()?,
147 requested_online: value.get("requested_online")?.as_u64()?.try_into().ok()?,
148 actual_online: value.get("actual_online")?.as_u64()?.try_into().ok()?,
149 enforced: value.get("enforced")?.as_u64()?.try_into().ok()?,
150 })
151}