1use std::io;
12
13const MAX_FRAME_SIZE: u32 = 64 * 1024 * 1024;
15
16const MSG_AUTHENTICATE: u8 = 0x01;
18const MSG_CREATE_SESSION: u8 = 0x02;
19const MSG_DESTROY_SESSION: u8 = 0x03;
20const MSG_INJECT_GLOBALS: u8 = 0x04;
21const MSG_EXECUTE: u8 = 0x05;
22const MSG_BRIDGE_RESPONSE: u8 = 0x06;
23const MSG_STREAM_EVENT: u8 = 0x07;
24const MSG_TERMINATE_EXECUTION: u8 = 0x08;
25
26const MSG_BRIDGE_CALL: u8 = 0x81;
28const MSG_EXECUTION_RESULT: u8 = 0x82;
29const MSG_LOG: u8 = 0x83;
30const MSG_STREAM_CALLBACK: u8 = 0x84;
31
32const FLAG_HAS_EXPORTS: u8 = 0x01;
34const FLAG_HAS_ERROR: u8 = 0x02;
35
36#[derive(Debug, Clone, PartialEq)]
38pub enum BinaryFrame {
39 Authenticate {
41 token: String,
42 },
43 CreateSession {
44 session_id: String,
45 heap_limit_mb: u32,
46 cpu_time_limit_ms: u32,
47 wall_clock_limit_ms: u32,
48 },
49 DestroySession {
50 session_id: String,
51 },
52 InjectGlobals {
53 session_id: String,
54 payload: Vec<u8>,
55 },
56 Execute {
57 session_id: String,
58 mode: u8, file_path: String,
60 bridge_code: String,
61 post_restore_script: String,
62 userland_code: String,
66 high_resolution_time: bool,
67 user_code: String,
68 },
69 BridgeResponse {
70 session_id: String,
71 call_id: u64,
72 status: u8, payload: Vec<u8>,
74 },
75 StreamEvent {
76 session_id: String,
77 event_type: String,
78 payload: Vec<u8>,
79 },
80 TerminateExecution {
81 session_id: String,
82 },
83
84 BridgeCall {
86 session_id: String,
87 call_id: u64,
88 method: String,
89 payload: Vec<u8>,
90 },
91 ExecutionResult {
92 session_id: String,
93 exit_code: i32,
94 exports: Option<Vec<u8>>,
95 error: Option<ExecutionErrorBin>,
96 },
97 Log {
98 session_id: String,
99 channel: u8, message: String,
101 },
102 StreamCallback {
103 session_id: String,
104 callback_type: String,
105 payload: Vec<u8>,
106 },
107}
108
109#[derive(Debug, Clone, PartialEq)]
111pub struct ExecutionErrorBin {
112 pub error_type: String,
113 pub message: String,
114 pub stack: String,
115 pub code: String,
116}
117
118pub fn encode_frame(frame: &BinaryFrame) -> io::Result<Vec<u8>> {
120 let mut buf = Vec::new();
121 buf.extend_from_slice(&[0, 0, 0, 0]);
123 encode_body(&mut buf, frame)?;
124 let total_len = buf.len() - 4;
125 if total_len > MAX_FRAME_SIZE as usize {
126 return Err(io::Error::new(
127 io::ErrorKind::InvalidData,
128 format!("frame size {total_len} exceeds maximum {MAX_FRAME_SIZE}"),
129 ));
130 }
131 buf[..4].copy_from_slice(&(total_len as u32).to_be_bytes());
132 Ok(buf)
133}
134
135pub fn decode_frame(buf: &[u8]) -> io::Result<BinaryFrame> {
137 if buf.is_empty() {
138 return Err(io::Error::new(io::ErrorKind::InvalidData, "empty frame"));
139 }
140 if buf.len() > MAX_FRAME_SIZE as usize {
141 return Err(io::Error::new(
142 io::ErrorKind::InvalidData,
143 format!("frame size {} exceeds maximum {MAX_FRAME_SIZE}", buf.len()),
144 ));
145 }
146
147 let msg_type = buf[0];
148 let mut pos = 1;
149
150 let sid_len = read_u8(buf, &mut pos)? as usize;
151 let session_id = read_utf8(buf, &mut pos, sid_len)?;
152
153 match msg_type {
154 MSG_AUTHENTICATE => {
155 let remaining = buf.len() - pos;
156 let token = read_utf8(buf, &mut pos, remaining)?;
157 Ok(BinaryFrame::Authenticate { token })
158 }
159 MSG_CREATE_SESSION => {
160 let heap_limit_mb = read_u32(buf, &mut pos)?;
161 let cpu_time_limit_ms = read_u32(buf, &mut pos)?;
162 let wall_clock_limit_ms = read_u32(buf, &mut pos)?;
163 Ok(BinaryFrame::CreateSession {
164 session_id,
165 heap_limit_mb,
166 cpu_time_limit_ms,
167 wall_clock_limit_ms,
168 })
169 }
170 MSG_DESTROY_SESSION => Ok(BinaryFrame::DestroySession { session_id }),
171 MSG_INJECT_GLOBALS => {
172 let payload = buf[pos..].to_vec();
173 Ok(BinaryFrame::InjectGlobals {
174 session_id,
175 payload,
176 })
177 }
178 MSG_EXECUTE => {
179 let mode = read_u8(buf, &mut pos)?;
180 let fp_len = read_u16(buf, &mut pos)? as usize;
181 let file_path = read_utf8(buf, &mut pos, fp_len)?;
182 let bc_len = read_u32(buf, &mut pos)? as usize;
183 let bridge_code = read_utf8(buf, &mut pos, bc_len)?;
184 let prs_len = read_u32(buf, &mut pos)? as usize;
185 let post_restore_script = read_utf8(buf, &mut pos, prs_len)?;
186 let ul_len = read_u32(buf, &mut pos)? as usize;
187 let userland_code = read_utf8(buf, &mut pos, ul_len)?;
188 let high_resolution_time = read_u8(buf, &mut pos)? != 0;
189 let remaining = buf.len() - pos;
190 let user_code = read_utf8(buf, &mut pos, remaining)?;
191 Ok(BinaryFrame::Execute {
192 session_id,
193 mode,
194 file_path,
195 bridge_code,
196 post_restore_script,
197 userland_code,
198 high_resolution_time,
199 user_code,
200 })
201 }
202 MSG_BRIDGE_RESPONSE => {
203 let call_id = read_u64(buf, &mut pos)?;
204 let status = read_u8(buf, &mut pos)?;
205 let payload = buf[pos..].to_vec();
206 Ok(BinaryFrame::BridgeResponse {
207 session_id,
208 call_id,
209 status,
210 payload,
211 })
212 }
213 MSG_STREAM_EVENT => {
214 let et_len = read_u16(buf, &mut pos)? as usize;
215 let event_type = read_utf8(buf, &mut pos, et_len)?;
216 let payload = buf[pos..].to_vec();
217 Ok(BinaryFrame::StreamEvent {
218 session_id,
219 event_type,
220 payload,
221 })
222 }
223 MSG_TERMINATE_EXECUTION => Ok(BinaryFrame::TerminateExecution { session_id }),
224 MSG_BRIDGE_CALL => {
225 let call_id = read_u64(buf, &mut pos)?;
226 let m_len = read_u16(buf, &mut pos)? as usize;
227 let method = read_utf8(buf, &mut pos, m_len)?;
228 let payload = buf[pos..].to_vec();
229 Ok(BinaryFrame::BridgeCall {
230 session_id,
231 call_id,
232 method,
233 payload,
234 })
235 }
236 MSG_EXECUTION_RESULT => {
237 let exit_code = read_i32(buf, &mut pos)?;
238 let flags = read_u8(buf, &mut pos)?;
239 let exports = if flags & FLAG_HAS_EXPORTS != 0 {
240 let exp_len = read_u32(buf, &mut pos)? as usize;
241 let data = read_bytes(buf, &mut pos, exp_len)?;
242 Some(data)
243 } else {
244 None
245 };
246 let error = if flags & FLAG_HAS_ERROR != 0 {
247 let error_type = read_len_prefixed_u16(buf, &mut pos)?;
248 let message = read_len_prefixed_u16(buf, &mut pos)?;
249 let stack = read_len_prefixed_u16(buf, &mut pos)?;
250 let code = read_len_prefixed_u16(buf, &mut pos)?;
251 Some(ExecutionErrorBin {
252 error_type,
253 message,
254 stack,
255 code,
256 })
257 } else {
258 None
259 };
260 Ok(BinaryFrame::ExecutionResult {
261 session_id,
262 exit_code,
263 exports,
264 error,
265 })
266 }
267 MSG_LOG => {
268 let channel = read_u8(buf, &mut pos)?;
269 let remaining = buf.len() - pos;
270 let message = read_utf8(buf, &mut pos, remaining)?;
271 Ok(BinaryFrame::Log {
272 session_id,
273 channel,
274 message,
275 })
276 }
277 MSG_STREAM_CALLBACK => {
278 let ct_len = read_u16(buf, &mut pos)? as usize;
279 let callback_type = read_utf8(buf, &mut pos, ct_len)?;
280 let payload = buf[pos..].to_vec();
281 Ok(BinaryFrame::StreamCallback {
282 session_id,
283 callback_type,
284 payload,
285 })
286 }
287 _ => Err(io::Error::new(
288 io::ErrorKind::InvalidData,
289 format!("unknown message type: 0x{msg_type:02x}"),
290 )),
291 }
292}
293
294fn encode_body(buf: &mut Vec<u8>, frame: &BinaryFrame) -> io::Result<()> {
297 match frame {
298 BinaryFrame::Authenticate { token } => {
299 buf.push(MSG_AUTHENTICATE);
300 buf.push(0); buf.extend_from_slice(token.as_bytes());
302 }
303 BinaryFrame::CreateSession {
304 session_id,
305 heap_limit_mb,
306 cpu_time_limit_ms,
307 wall_clock_limit_ms,
308 } => {
309 buf.push(MSG_CREATE_SESSION);
310 write_session_id(buf, session_id)?;
311 buf.extend_from_slice(&heap_limit_mb.to_be_bytes());
312 buf.extend_from_slice(&cpu_time_limit_ms.to_be_bytes());
313 buf.extend_from_slice(&wall_clock_limit_ms.to_be_bytes());
314 }
315 BinaryFrame::DestroySession { session_id } => {
316 buf.push(MSG_DESTROY_SESSION);
317 write_session_id(buf, session_id)?;
318 }
319 BinaryFrame::InjectGlobals {
320 session_id,
321 payload,
322 } => {
323 buf.push(MSG_INJECT_GLOBALS);
324 write_session_id(buf, session_id)?;
325 buf.extend_from_slice(payload);
326 }
327 BinaryFrame::Execute {
328 session_id,
329 mode,
330 file_path,
331 bridge_code,
332 post_restore_script,
333 userland_code,
334 high_resolution_time,
335 user_code,
336 } => {
337 buf.push(MSG_EXECUTE);
338 write_session_id(buf, session_id)?;
339 buf.push(*mode);
340 write_len_prefixed_u16(buf, file_path)?;
341 let bc_bytes = bridge_code.as_bytes();
342 buf.extend_from_slice(&(bc_bytes.len() as u32).to_be_bytes());
343 buf.extend_from_slice(bc_bytes);
344 let prs_bytes = post_restore_script.as_bytes();
345 buf.extend_from_slice(&(prs_bytes.len() as u32).to_be_bytes());
346 buf.extend_from_slice(prs_bytes);
347 let ul_bytes = userland_code.as_bytes();
348 buf.extend_from_slice(&(ul_bytes.len() as u32).to_be_bytes());
349 buf.extend_from_slice(ul_bytes);
350 buf.push(u8::from(*high_resolution_time));
351 buf.extend_from_slice(user_code.as_bytes());
352 }
353 BinaryFrame::BridgeResponse {
354 session_id,
355 call_id,
356 status,
357 payload,
358 } => {
359 buf.push(MSG_BRIDGE_RESPONSE);
360 write_session_id(buf, session_id)?;
361 buf.extend_from_slice(&call_id.to_be_bytes());
362 buf.push(*status);
363 buf.extend_from_slice(payload);
364 }
365 BinaryFrame::StreamEvent {
366 session_id,
367 event_type,
368 payload,
369 } => {
370 buf.push(MSG_STREAM_EVENT);
371 write_session_id(buf, session_id)?;
372 write_len_prefixed_u16(buf, event_type)?;
373 buf.extend_from_slice(payload);
374 }
375 BinaryFrame::TerminateExecution { session_id } => {
376 buf.push(MSG_TERMINATE_EXECUTION);
377 write_session_id(buf, session_id)?;
378 }
379 BinaryFrame::BridgeCall {
381 session_id,
382 call_id,
383 method,
384 payload,
385 } => {
386 buf.push(MSG_BRIDGE_CALL);
387 write_session_id(buf, session_id)?;
388 buf.extend_from_slice(&call_id.to_be_bytes());
389 write_len_prefixed_u16(buf, method)?;
390 buf.extend_from_slice(payload);
391 }
392 BinaryFrame::ExecutionResult {
393 session_id,
394 exit_code,
395 exports,
396 error,
397 } => {
398 buf.push(MSG_EXECUTION_RESULT);
399 write_session_id(buf, session_id)?;
400 buf.extend_from_slice(&exit_code.to_be_bytes());
401 let mut flags: u8 = 0;
402 if exports.is_some() {
403 flags |= FLAG_HAS_EXPORTS;
404 }
405 if error.is_some() {
406 flags |= FLAG_HAS_ERROR;
407 }
408 buf.push(flags);
409 if let Some(exp) = exports {
410 buf.extend_from_slice(&(exp.len() as u32).to_be_bytes());
411 buf.extend_from_slice(exp);
412 }
413 if let Some(err) = error {
414 write_len_prefixed_u16(buf, &err.error_type)?;
415 write_len_prefixed_u16(buf, &err.message)?;
416 write_len_prefixed_u16(buf, &err.stack)?;
417 write_len_prefixed_u16(buf, &err.code)?;
418 }
419 }
420 BinaryFrame::Log {
421 session_id,
422 channel,
423 message,
424 } => {
425 buf.push(MSG_LOG);
426 write_session_id(buf, session_id)?;
427 buf.push(*channel);
428 buf.extend_from_slice(message.as_bytes());
429 }
430 BinaryFrame::StreamCallback {
431 session_id,
432 callback_type,
433 payload,
434 } => {
435 buf.push(MSG_STREAM_CALLBACK);
436 write_session_id(buf, session_id)?;
437 write_len_prefixed_u16(buf, callback_type)?;
438 buf.extend_from_slice(payload);
439 }
440 }
441 Ok(())
442}
443
444fn write_session_id(buf: &mut Vec<u8>, sid: &str) -> io::Result<()> {
447 let bytes = sid.as_bytes();
448 if bytes.len() > 255 {
449 return Err(io::Error::new(
450 io::ErrorKind::InvalidInput,
451 format!("session ID byte length {} exceeds u8 max 255", bytes.len()),
452 ));
453 }
454 buf.push(bytes.len() as u8);
455 buf.extend_from_slice(bytes);
456 Ok(())
457}
458
459fn write_len_prefixed_u16(buf: &mut Vec<u8>, s: &str) -> io::Result<()> {
460 let bytes = s.as_bytes();
461 if bytes.len() > 0xFFFF {
462 return Err(io::Error::new(
463 io::ErrorKind::InvalidInput,
464 format!("string byte length {} exceeds u16 max 65535", bytes.len()),
465 ));
466 }
467 buf.extend_from_slice(&(bytes.len() as u16).to_be_bytes());
468 buf.extend_from_slice(bytes);
469 Ok(())
470}
471
472fn read_u8(buf: &[u8], pos: &mut usize) -> io::Result<u8> {
473 if *pos >= buf.len() {
474 return Err(eof());
475 }
476 let v = buf[*pos];
477 *pos += 1;
478 Ok(v)
479}
480
481fn read_u16(buf: &[u8], pos: &mut usize) -> io::Result<u16> {
482 if *pos + 2 > buf.len() {
483 return Err(eof());
484 }
485 let v = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]);
486 *pos += 2;
487 Ok(v)
488}
489
490fn read_u32(buf: &[u8], pos: &mut usize) -> io::Result<u32> {
491 if *pos + 4 > buf.len() {
492 return Err(eof());
493 }
494 let v = u32::from_be_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
495 *pos += 4;
496 Ok(v)
497}
498
499fn read_i32(buf: &[u8], pos: &mut usize) -> io::Result<i32> {
500 if *pos + 4 > buf.len() {
501 return Err(eof());
502 }
503 let v = i32::from_be_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
504 *pos += 4;
505 Ok(v)
506}
507
508fn read_u64(buf: &[u8], pos: &mut usize) -> io::Result<u64> {
509 if *pos + 8 > buf.len() {
510 return Err(eof());
511 }
512 let mut bytes = [0u8; 8];
513 bytes.copy_from_slice(&buf[*pos..*pos + 8]);
514 *pos += 8;
515 Ok(u64::from_be_bytes(bytes))
516}
517
518fn read_utf8(buf: &[u8], pos: &mut usize, len: usize) -> io::Result<String> {
519 if *pos + len > buf.len() {
520 return Err(eof());
521 }
522 let s = std::str::from_utf8(&buf[*pos..*pos + len])
523 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
524 *pos += len;
525 Ok(s.to_owned())
526}
527
528fn read_bytes(buf: &[u8], pos: &mut usize, len: usize) -> io::Result<Vec<u8>> {
529 if *pos + len > buf.len() {
530 return Err(eof());
531 }
532 let data = buf[*pos..*pos + len].to_vec();
533 *pos += len;
534 Ok(data)
535}
536
537fn read_len_prefixed_u16(buf: &[u8], pos: &mut usize) -> io::Result<String> {
538 let len = read_u16(buf, pos)? as usize;
539 read_utf8(buf, pos, len)
540}
541
542fn eof() -> io::Error {
543 io::Error::new(io::ErrorKind::UnexpectedEof, "unexpected end of frame")
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn roundtrip_authenticate() {
552 let frame = BinaryFrame::Authenticate {
553 token: "secret123".into(),
554 };
555 let bytes = encode_frame(&frame).unwrap();
556 let decoded = decode_frame(&bytes[4..]).unwrap();
557 assert_eq!(frame, decoded);
558 }
559
560 #[test]
561 fn roundtrip_create_session() {
562 let frame = BinaryFrame::CreateSession {
563 session_id: "sess-1".into(),
564 heap_limit_mb: 256,
565 cpu_time_limit_ms: 30000,
566 wall_clock_limit_ms: 45000,
567 };
568 let bytes = encode_frame(&frame).unwrap();
569 let decoded = decode_frame(&bytes[4..]).unwrap();
570 assert_eq!(frame, decoded);
571 }
572
573 #[test]
574 fn roundtrip_bridge_call() {
575 let frame = BinaryFrame::BridgeCall {
576 session_id: "sess-1".into(),
577 call_id: 42,
578 method: "_fsReadFile".into(),
579 payload: vec![1, 2, 3],
580 };
581 let bytes = encode_frame(&frame).unwrap();
582 let decoded = decode_frame(&bytes[4..]).unwrap();
583 assert_eq!(frame, decoded);
584 }
585
586 #[test]
587 fn roundtrip_execution_result_with_error() {
588 let frame = BinaryFrame::ExecutionResult {
589 session_id: "sess-1".into(),
590 exit_code: 1,
591 exports: None,
592 error: Some(ExecutionErrorBin {
593 error_type: "Error".into(),
594 message: "something failed".into(),
595 stack: "at foo:1:1".into(),
596 code: "ERR_TEST".into(),
597 }),
598 };
599 let bytes = encode_frame(&frame).unwrap();
600 let decoded = decode_frame(&bytes[4..]).unwrap();
601 assert_eq!(frame, decoded);
602 }
603
604 #[test]
605 fn decode_frame_rejects_oversized_body() {
606 let oversized = vec![0u8; MAX_FRAME_SIZE as usize + 1];
607 let result = decode_frame(&oversized);
608 assert!(result.is_err());
609 let err = result.unwrap_err();
610 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
611 assert!(err.to_string().contains("exceeds maximum"));
612 }
613}