confium_sandbox_process/
protocol.rs1use serde::Deserialize;
25use serde::Serialize;
26use serde_json::Value as JsonValue;
27use snafu::GenerateImplicitData;
28
29use crate::Error;
30use crate::Result;
31use crate::sandbox::Value;
32
33pub(crate) const LEN_PREFIX_BYTES: usize = 4;
35
36pub(crate) const MAX_FRAME_BYTES: usize = 64 * 1024 * 1024;
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct Request {
43 pub method: String,
44 pub args: Vec<JsonValue>,
45}
46
47impl Request {
48 pub fn new(method: impl Into<String>, args: Vec<JsonValue>) -> Self {
49 Self {
50 method: method.into(),
51 args,
52 }
53 }
54
55 pub fn to_frame(&self) -> Result<Vec<u8>> {
57 let json = serde_json::to_vec(self).map_err(|e| Error::Protocol {
58 reason: format!("failed to serialize request: {e}"),
59 backtrace: snafu::Backtrace::generate(),
60 })?;
61 encode_frame(&json)
62 }
63}
64
65#[derive(Debug, Clone, Deserialize)]
67pub struct Response {
68 #[serde(default)]
70 pub result: Option<Vec<JsonValue>>,
71 #[serde(default)]
73 pub error: Option<ResponseError>,
74}
75
76#[derive(Debug, Clone, Deserialize)]
78pub struct ResponseError {
79 pub message: String,
80}
81
82impl Response {
83 pub fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
85 serde_json::from_slice::<Response>(bytes).map_err(|e| Error::Protocol {
86 reason: format!("failed to parse response: {e}"),
87 backtrace: snafu::Backtrace::generate(),
88 })
89 }
90
91 pub fn into_result(self, method: &str) -> Result<Vec<Value>> {
93 if let Some(err) = self.error {
94 return Err(Error::PluginError {
95 method: method.to_string(),
96 message: err.message,
97 backtrace: snafu::Backtrace::generate(),
98 });
99 }
100 let raw = self.result.unwrap_or_default();
101 raw.into_iter()
102 .map(value_from_json)
103 .collect::<Result<Vec<_>>>()
104 }
105}
106
107pub(crate) fn encode_frame(payload: &[u8]) -> Result<Vec<u8>> {
109 let len = payload.len();
110 if len > MAX_FRAME_BYTES {
111 return Err(Error::Protocol {
112 reason: format!("frame too large: {len} > {MAX_FRAME_BYTES} bytes"),
113 backtrace: snafu::Backtrace::generate(),
114 });
115 }
116 let mut out = Vec::with_capacity(LEN_PREFIX_BYTES + len);
117 out.extend_from_slice(&(len as u32).to_be_bytes());
118 out.extend_from_slice(payload);
119 Ok(out)
120}
121
122pub fn value_to_json(v: &Value) -> JsonValue {
128 match v {
129 Value::I32(x) => JsonValue::from(*x),
130 Value::I64(x) => JsonValue::from(*x),
131 Value::F32(x) => serde_json::Number::from_f64(f64::from(*x))
132 .map(JsonValue::Number)
133 .unwrap_or(JsonValue::Null),
134 Value::F64(x) => serde_json::Number::from_f64(*x)
135 .map(JsonValue::Number)
136 .unwrap_or(JsonValue::Null),
137 Value::Bytes(b) => JsonValue::Array(
138 b.iter()
139 .map(|&byte| JsonValue::from(u32::from(byte)))
140 .collect(),
141 ),
142 }
143}
144
145pub fn value_from_json(v: JsonValue) -> Result<Value> {
147 Ok(match v {
148 JsonValue::Bool(_) | JsonValue::Null => {
149 return Err(Error::Protocol {
150 reason: "null/bool are not valid sandbox values".into(),
151 backtrace: snafu::Backtrace::generate(),
152 });
153 }
154 JsonValue::String(_) => {
155 return Err(Error::Protocol {
156 reason: "string is not a valid sandbox value".into(),
157 backtrace: snafu::Backtrace::generate(),
158 });
159 }
160 JsonValue::Number(n) => {
161 if let Some(i) = n.as_i64() {
162 if let Ok(small) = i32::try_from(i) {
163 Value::I32(small)
164 } else {
165 Value::I64(i)
166 }
167 } else if let Some(u) = n.as_u64() {
168 if let Ok(i) = i64::try_from(u) {
170 Value::I64(i)
171 } else {
172 return Err(Error::Protocol {
173 reason: format!("integer {u} overflows i64"),
174 backtrace: snafu::Backtrace::generate(),
175 });
176 }
177 } else if let Some(f) = n.as_f64() {
178 Value::F64(f)
179 } else {
180 return Err(Error::Protocol {
181 reason: format!("unsupported number {n}"),
182 backtrace: snafu::Backtrace::generate(),
183 });
184 }
185 }
186 JsonValue::Array(arr) => {
187 let mut bytes = Vec::with_capacity(arr.len());
190 for el in arr {
191 let n = el.as_u64().ok_or_else(|| Error::Protocol {
192 reason: "byte array element is not a non-negative integer".into(),
193 backtrace: snafu::Backtrace::generate(),
194 })?;
195 let byte = u8::try_from(n).map_err(|_| Error::Protocol {
196 reason: format!("byte array element {n} out of u8 range"),
197 backtrace: snafu::Backtrace::generate(),
198 })?;
199 bytes.push(byte);
200 }
201 Value::Bytes(bytes)
202 }
203 JsonValue::Object(_) => {
204 return Err(Error::Protocol {
205 reason: "object is not a valid sandbox value".into(),
206 backtrace: snafu::Backtrace::generate(),
207 });
208 }
209 })
210}
211
212pub(crate) fn parse_len(buf: &[u8]) -> Result<usize> {
214 if buf.len() < LEN_PREFIX_BYTES {
215 return Err(Error::Protocol {
216 reason: format!(
217 "length header truncated: got {} bytes, need {LEN_PREFIX_BYTES}",
218 buf.len()
219 ),
220 backtrace: snafu::Backtrace::generate(),
221 });
222 }
223 let mut arr = [0u8; LEN_PREFIX_BYTES];
224 arr.copy_from_slice(&buf[..LEN_PREFIX_BYTES]);
225 let len = u32::from_be_bytes(arr) as usize;
226 if len > MAX_FRAME_BYTES {
227 return Err(Error::Protocol {
228 reason: format!("frame too large: {len} > {MAX_FRAME_BYTES} bytes"),
229 backtrace: snafu::Backtrace::generate(),
230 });
231 }
232 Ok(len)
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn request_frame_round_trips() {
241 let req = Request::new("add", vec![JsonValue::from(2), JsonValue::from(3)]);
242 let frame = req.to_frame().expect("frame encodes");
243 assert!(frame.len() > LEN_PREFIX_BYTES);
245 let len = parse_len(&frame[..LEN_PREFIX_BYTES]).expect("len parses");
246 let body = &frame[LEN_PREFIX_BYTES..LEN_PREFIX_BYTES + len];
247 let parsed: Request = serde_json::from_slice(body).expect("request parses back");
248 assert_eq!(parsed.method, "add");
249 assert_eq!(parsed.args.len(), 2);
250 }
251
252 #[test]
253 fn encode_frame_writes_big_endian_length() {
254 let payload = b"hello";
255 let frame = encode_frame(payload).expect("encode");
256 assert_eq!(&frame[..4], &[0, 0, 0, 5]);
257 assert_eq!(&frame[4..], payload);
258 }
259
260 #[test]
261 fn parse_len_rejects_truncated_header() {
262 let err = parse_len(&[0, 0]).expect_err("must fail");
263 assert_eq!(err.code(), 0x2104);
265 }
266
267 #[test]
268 fn value_to_json_round_trip_integers() {
269 let v = Value::I32(42);
270 let j = value_to_json(&v);
271 let back = value_from_json(j).expect("round trips");
272 assert_eq!(back, Value::I32(42));
273
274 let v = Value::I64(5_000_000_000);
275 let j = value_to_json(&v);
276 let back = value_from_json(j).expect("round trips");
277 assert_eq!(back, Value::I64(5_000_000_000));
278 }
279
280 #[test]
281 fn value_to_json_round_trip_bytes() {
282 let v = Value::Bytes(vec![0, 127, 255, 1]);
283 let j = value_to_json(&v);
284 let back = value_from_json(j).expect("round trips");
285 assert_eq!(back, v);
286 }
287
288 #[test]
289 fn value_to_json_round_trip_floats() {
290 let v = Value::F64(2.5);
291 let j = value_to_json(&v);
292 let back = value_from_json(j).expect("round trips");
293 match back {
294 Value::F64(x) => assert_eq!(x, 2.5),
295 other => panic!("expected F64, got {:?}", other),
296 }
297 }
298
299 #[test]
300 fn response_success_parses() {
301 let raw = br#"{"result":[7]}"#;
302 let resp = Response::from_json_bytes(raw).expect("parses");
303 let out = resp.into_result("add").expect("ok");
304 assert_eq!(out, vec![Value::I32(7)]);
305 }
306
307 #[test]
308 fn response_error_becomes_plugin_error() {
309 let raw = br#"{"error":{"message":"no such function"}}"#;
310 let resp = Response::from_json_bytes(raw).expect("parses");
311 let err = resp.into_result("foo").expect_err("must fail");
312 match err {
313 Error::PluginError {
314 method, message, ..
315 } => {
316 assert_eq!(method, "foo");
317 assert_eq!(message, "no such function");
318 }
319 other => panic!("expected PluginError, got {:?}", other),
320 }
321 }
322
323 #[test]
324 fn response_with_empty_result_is_ok_empty() {
325 let raw = br#"{"result":[]}"#;
326 let resp = Response::from_json_bytes(raw).expect("parses");
327 let out = resp.into_result("void").expect("ok");
328 assert!(out.is_empty());
329 }
330}