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