//! V8 isolate runtime manager backed by the embedded V8 runtime. use crate::v8_ipc::{self, BinaryFrame};use agentos_runtime::RuntimeContext;use agentos_v8_runtime::embedded_runtime::{spawn_embedded_runtime_ipc, EmbeddedRuntimeHandle};use serde_json::Value;use std::io::{self, BufReader, Read, Write};use std::os::unix::net::UnixStream;use std::sync::{Arc, Mutex}; /// Manages an embedded V8 runtime and its IPC connection.pub struct V8Runtime { runtime: EmbeddedRuntimeHandle, reader: BufReader<UnixStream>, writer: UnixStream,} impl V8Runtime { /// Spawn the embedded V8 runtime and connect over IPC. pub fn spawn(runtime_context: &RuntimeContext) -> io::Result<Self> { let (stream, runtime) = spawn_embedded_runtime_ipc(None, runtime_context.clone())?; let writer = stream.try_clone()?; let reader = BufReader::new(stream); Ok(V8Runtime { runtime, reader, writer, }) } /// Create a new V8 isolate session. pub fn create_session( &mut self, session_id: &str, heap_limit_mb: u32, cpu_time_limit_ms: u32, wall_clock_limit_ms: u32, ) -> io::Result<()> { self.send_frame(&BinaryFrame::CreateSession { session_id: session_id.to_owned(), heap_limit_mb, cpu_time_limit_ms, wall_clock_limit_ms, }) } /// Inject per-session globals (processConfig, osConfig) as CBOR payload. pub fn inject_globals(&mut self, session_id: &str, payload: Vec<u8>) -> io::Result<()> { self.send_frame(&BinaryFrame::InjectGlobals { session_id: session_id.to_owned(), payload, }) } /// Execute bridge code + user code in a session. pub fn execute( &mut self, session_id: &str, mode: u8, file_path: &str, bridge_code: &str, user_code: &str, ) -> io::Result<()> { self.send_frame(&BinaryFrame::Execute { session_id: session_id.to_owned(), mode, file_path: file_path.to_owned(), bridge_code: bridge_code.to_owned(), post_restore_script: String::new(), userland_code: String::new(), high_resolution_time: false, user_code: user_code.to_owned(), }) } /// Send a bridge response back to the V8 isolate. pub fn send_bridge_response( &mut self, session_id: &str, call_id: u64, status: u8, payload: Vec<u8>, ) -> io::Result<()> { self.send_frame(&BinaryFrame::BridgeResponse { session_id: session_id.to_owned(), call_id, status, payload, }) } /// Send a stream event to the V8 isolate (stdin data, timer, child process events). pub fn send_stream_event( &mut self, session_id: &str, event_type: &str, payload: Vec<u8>, ) -> io::Result<()> { self.send_frame(&BinaryFrame::StreamEvent { session_id: session_id.to_owned(), event_type: event_type.to_owned(), payload, }) } /// Terminate execution in a session. pub fn terminate_execution(&mut self, session_id: &str) -> io::Result<()> { self.send_frame(&BinaryFrame::TerminateExecution { session_id: session_id.to_owned(), }) } /// Destroy a session. pub fn destroy_session(&mut self, session_id: &str) -> io::Result<()> { self.send_frame(&BinaryFrame::DestroySession { session_id: session_id.to_owned(), }) } /// Read the next frame from the V8 runtime. pub fn read_frame(&mut self) -> io::Result<BinaryFrame> { let mut len_buf = [0u8; 4]; self.reader.read_exact(&mut len_buf)?; let total_len = u32::from_be_bytes(len_buf); if total_len > 64 * 1024 * 1024 { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("frame size {total_len} exceeds maximum"), )); } let mut buf = vec![0u8; total_len as usize]; self.reader.read_exact(&mut buf)?; v8_ipc::decode_frame(&buf) } fn send_frame(&mut self, frame: &BinaryFrame) -> io::Result<()> { let bytes = v8_ipc::encode_frame(frame)?; self.writer.write_all(&bytes)?; self.writer.flush() }} impl Drop for V8Runtime { fn drop(&mut self) { self.runtime.shutdown(); }} /// Thread-safe wrapper for V8Runtime that allows sending from multiple threads.pub struct SharedV8Runtime { inner: Arc<Mutex<V8Runtime>>,} impl SharedV8Runtime { pub fn new(runtime: V8Runtime) -> Self { Self { inner: Arc::new(Mutex::new(runtime)), } } pub fn lock(&self) -> std::sync::MutexGuard<'_, V8Runtime> { self.inner.lock().expect("V8 runtime lock poisoned") }} impl Clone for SharedV8Runtime { fn clone(&self) -> Self { Self { inner: self.inner.clone(), } }} /// Bridge call method name mapping from V8 polyfill names to sidecar sync RPC names./// The V8 polyfills use underscore-prefixed camelCase names while the sidecar/// uses dot-separated category.method names. The mapping lives in/// `bridge-contract.json` so bridge installation and dispatch drift together.pub fn map_bridge_method(method: &str) -> (&str, bool) { if let Some(target) = agentos_bridge::bridge_contract().dispatch.get(method) { (target.method.as_str(), target.translate_args) } else { (method, false) }} /// Deserialize a CBOR payload into a JSON array of arguments./// The V8 bridge serializes bridge call args as a CBOR array.pub fn cbor_payload_to_json_args(payload: &[u8]) -> io::Result<Vec<Value>> { if payload.is_empty() { return Ok(vec![]); } let cbor_value: ciborium::value::Value = ciborium::de::from_reader(payload).map_err(|e| { io::Error::new( io::ErrorKind::InvalidData, format!("failed to deserialize CBOR bridge call payload: {e}"), ) })?; match cbor_to_json(cbor_value) { Value::Array(arr) => Ok(arr), single => Ok(vec![single]), }} pub fn cbor_payload_raw_byte_arg(payload: &[u8], index: usize) -> io::Result<Option<Vec<u8>>> { if payload.is_empty() { return Ok(None); } let cbor_value: ciborium::value::Value = ciborium::de::from_reader(payload).map_err(|e| { io::Error::new( io::ErrorKind::InvalidData, format!("failed to deserialize CBOR bridge call payload: {e}"), ) })?; let Some(value) = cbor_array_arg(&cbor_value, index) else { return Ok(None); }; Ok(cbor_raw_bytes(value).map(ToOwned::to_owned))} /// Serialize a JSON value to CBOR bytes for bridge responses.pub fn json_to_cbor_payload(value: &Value) -> io::Result<Vec<u8>> { let cbor_value = json_to_cbor(value); let mut buf = Vec::new(); ciborium::ser::into_writer(&cbor_value, &mut buf).map_err(|e| { io::Error::new( io::ErrorKind::InvalidData, format!("failed to serialize CBOR bridge response: {e}"), ) })?; Ok(buf)} fn cbor_array_arg(value: &ciborium::value::Value, index: usize) -> Option<&ciborium::value::Value> { match value { ciborium::value::Value::Array(values) => values.get(index), value if index == 0 => Some(value), _ => None, }} fn cbor_raw_bytes(value: &ciborium::value::Value) -> Option<&[u8]> { match value { ciborium::value::Value::Bytes(bytes) => Some(bytes), ciborium::value::Value::Tag(_, inner) => cbor_raw_bytes(inner), _ => None, }} fn cbor_to_json(value: ciborium::value::Value) -> Value { use ciborium::value::Value as Cbor; match value { Cbor::Null => Value::Null, Cbor::Bool(b) => Value::Bool(b), Cbor::Integer(i) => { let n: i128 = i.into(); if let Ok(n) = i64::try_from(n) { Value::Number(n.into()) } else if let Ok(n) = u64::try_from(n) { Value::Number(n.into()) } else { Value::Number(serde_json::Number::from_f64(n as f64).unwrap_or(0.into())) } } Cbor::Float(f) => serde_json::Number::from_f64(f) .map(Value::Number) .unwrap_or(Value::Null), Cbor::Text(s) => Value::String(s), Cbor::Bytes(b) => { use serde_json::json; // Encode binary data as base64 with a type marker json!({ "__type": "Buffer", "data": base64_encode(&b) }) } Cbor::Array(arr) => Value::Array(arr.into_iter().map(cbor_to_json).collect()), Cbor::Map(map) => { let mut obj = serde_json::Map::new(); for (k, v) in map { let key = match k { Cbor::Text(s) => s, Cbor::Integer(i) => { let n: i128 = i.into(); n.to_string() } other => format!("{other:?}"), }; obj.insert(key, cbor_to_json(v)); } Value::Object(obj) } Cbor::Tag(_, inner) => cbor_to_json(*inner), _ => Value::Null, }} fn json_to_cbor(value: &Value) -> ciborium::value::Value { use ciborium::value::Value as Cbor; match value { Value::Null => Cbor::Null, Value::Bool(b) => Cbor::Bool(*b), Value::Number(n) => { if let Some(i) = n.as_i64() { Cbor::Integer(i.into()) } else if let Some(u) = n.as_u64() { Cbor::Integer(u.into()) } else if let Some(f) = n.as_f64() { Cbor::Float(f) } else { Cbor::Null } } Value::String(s) => Cbor::Text(s.clone()), Value::Array(arr) => Cbor::Array(arr.iter().map(json_to_cbor).collect()), Value::Object(map) => { // Check for Buffer type marker if map.get("__type").and_then(Value::as_str) == Some("Buffer") { if let Some(data) = map.get("data").and_then(Value::as_str) { if let Ok(bytes) = base64_decode(data) { return Cbor::Bytes(bytes); } } } Cbor::Map( map.iter() .map(|(k, v)| (Cbor::Text(k.clone()), json_to_cbor(v))) .collect(), ) } }} /// Public base64 encode for use in bridge call handlers.pub fn base64_encode_pub(data: &[u8]) -> String { base64_encode(data)} pub fn base64_decode_pub(input: &str) -> Option<Vec<u8>> { base64_decode(input).ok()} fn base64_encode(data: &[u8]) -> String { const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let mut result = String::with_capacity(data.len().div_ceil(3) * 4); for chunk in data.chunks(3) { let b0 = chunk[0] as u32; let b1 = chunk.get(1).copied().unwrap_or(0) as u32; let b2 = chunk.get(2).copied().unwrap_or(0) as u32; let triple = (b0 << 16) | (b1 << 8) | b2; result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char); result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char); if chunk.len() > 1 { result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char); } else { result.push('='); } if chunk.len() > 2 { result.push(CHARS[(triple & 0x3F) as usize] as char); } else { result.push('='); } } result} fn base64_decode(input: &str) -> Result<Vec<u8>, ()> { fn decode_char(c: u8) -> Result<u8, ()> { match c { b'A'..=b'Z' => Ok(c - b'A'), b'a'..=b'z' => Ok(c - b'a' + 26), b'0'..=b'9' => Ok(c - b'0' + 52), b'+' => Ok(62), b'/' => Ok(63), b'=' => Ok(0), _ => Err(()), } } let bytes = input.as_bytes(); let mut result = Vec::with_capacity(bytes.len() * 3 / 4); for chunk in bytes.chunks(4) { if chunk.len() < 4 { return Err(()); } let a = decode_char(chunk[0])?; let b = decode_char(chunk[1])?; let c = decode_char(chunk[2])?; let d = decode_char(chunk[3])?; let triple = ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32); result.push((triple >> 16) as u8); if chunk[2] != b'=' { result.push((triple >> 8) as u8); } if chunk[3] != b'=' { result.push(triple as u8); } } Ok(result)} #[cfg(test)]mod tests { use super::{json_to_cbor_payload, map_bridge_method}; use serde_json::json; #[test] fn cbor_byte_string_boundary_includes_five_byte_header() { let payload_limit = 256 * 1024; let raw_bytes = payload_limit - 5; let encoded = json_to_cbor_payload(&json!({ "__type": "Buffer", "data": super::base64_encode_pub(&vec![0xA5; raw_bytes]), })) .expect("encode boundary byte string"); assert_eq!(encoded.len(), payload_limit); let oversized = json_to_cbor_payload(&json!({ "__type": "Buffer", "data": super::base64_encode_pub(&vec![0xA5; raw_bytes + 1]), })) .expect("encode oversized byte string"); assert_eq!(oversized.len(), payload_limit + 1); } #[test] fn audited_bridge_methods_map_to_named_handlers() { for method in [ "_cryptoHashDigest", "_cryptoSubtle", "_networkHttp2ServerListenRaw", "_networkHttpServerRequestRaw", "_networkHttp2SessionConnectRaw", "_networkHttp2StreamRespondRaw", "_upgradeSocketWriteRaw", "_netSocketSetNoDelayRaw", "_kernelStdioWriteRaw", "_kernelPollRaw", "_kernelTtySizeRaw", "_netSocketUpgradeTlsRaw", "_tlsGetCiphersRaw", "_dgramSocketAddressRaw", "_dgramSocketSetBufferSizeRaw", ] { let (mapped, _) = map_bridge_method(method); assert_ne!(mapped, method, "missing bridge-method mapping for {method}"); } } #[test] fn http_request_bridge_shortcut_is_not_mapped() { assert_eq!( map_bridge_method("_networkHttpRequestRaw"), ("_networkHttpRequestRaw", false) ); }}
//! V8 isolate runtime manager backed by the embedded V8 runtime.
use crate::v8_ipc::{self, BinaryFrame};
use agentos_runtime::RuntimeContext;
use agentos_v8_runtime::embedded_runtime::{spawn_embedded_runtime_ipc, EmbeddedRuntimeHandle};
use serde_json::Value;
use std::io::{self, BufReader, Read, Write};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
/// Manages an embedded V8 runtime and its IPC connection.
pub struct V8Runtime {
runtime: EmbeddedRuntimeHandle,
reader: BufReader<UnixStream>,
writer: UnixStream,
}
impl V8Runtime {
/// Spawn the embedded V8 runtime and connect over IPC.
pub fn spawn(runtime_context: &RuntimeContext) -> io::Result<Self> {
let (stream, runtime) = spawn_embedded_runtime_ipc(None, runtime_context.clone())?;
let writer = stream.try_clone()?;
let reader = BufReader::new(stream);
Ok(V8Runtime {
runtime,
reader,
writer,
})
}
/// Create a new V8 isolate session.
pub fn create_session(
&mut self,
session_id: &str,
heap_limit_mb: u32,
cpu_time_limit_ms: u32,
wall_clock_limit_ms: u32,
) -> io::Result<()> {
self.send_frame(&BinaryFrame::CreateSession {
session_id: session_id.to_owned(),
heap_limit_mb,
cpu_time_limit_ms,
wall_clock_limit_ms,
})
}
/// Inject per-session globals (processConfig, osConfig) as CBOR payload.
pub fn inject_globals(&mut self, session_id: &str, payload: Vec<u8>) -> io::Result<()> {
self.send_frame(&BinaryFrame::InjectGlobals {
session_id: session_id.to_owned(),
payload,
})
}
/// Execute bridge code + user code in a session.
pub fn execute(
&mut self,
session_id: &str,
mode: u8,
file_path: &str,
bridge_code: &str,
user_code: &str,
) -> io::Result<()> {
self.send_frame(&BinaryFrame::Execute {
session_id: session_id.to_owned(),
mode,
file_path: file_path.to_owned(),
bridge_code: bridge_code.to_owned(),
post_restore_script: String::new(),
userland_code: String::new(),
high_resolution_time: false,
user_code: user_code.to_owned(),
})
}
/// Send a bridge response back to the V8 isolate.
pub fn send_bridge_response(
&mut self,
session_id: &str,
call_id: u64,
status: u8,
payload: Vec<u8>,
) -> io::Result<()> {
self.send_frame(&BinaryFrame::BridgeResponse {
session_id: session_id.to_owned(),
call_id,
status,
payload,
})
}
/// Send a stream event to the V8 isolate (stdin data, timer, child process events).
pub fn send_stream_event(
&mut self,
session_id: &str,
event_type: &str,
payload: Vec<u8>,
) -> io::Result<()> {
self.send_frame(&BinaryFrame::StreamEvent {
session_id: session_id.to_owned(),
event_type: event_type.to_owned(),
payload,
})
}
/// Terminate execution in a session.
pub fn terminate_execution(&mut self, session_id: &str) -> io::Result<()> {
self.send_frame(&BinaryFrame::TerminateExecution {
session_id: session_id.to_owned(),
})
}
/// Destroy a session.
pub fn destroy_session(&mut self, session_id: &str) -> io::Result<()> {
self.send_frame(&BinaryFrame::DestroySession {
session_id: session_id.to_owned(),
})
}
/// Read the next frame from the V8 runtime.
pub fn read_frame(&mut self) -> io::Result<BinaryFrame> {
let mut len_buf = [0u8; 4];
self.reader.read_exact(&mut len_buf)?;
let total_len = u32::from_be_bytes(len_buf);
if total_len > 64 * 1024 * 1024 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("frame size {total_len} exceeds maximum"),
));
}
let mut buf = vec![0u8; total_len as usize];
self.reader.read_exact(&mut buf)?;
v8_ipc::decode_frame(&buf)
}
fn send_frame(&mut self, frame: &BinaryFrame) -> io::Result<()> {
let bytes = v8_ipc::encode_frame(frame)?;
self.writer.write_all(&bytes)?;
self.writer.flush()
}
}
impl Drop for V8Runtime {
fn drop(&mut self) {
self.runtime.shutdown();
}
}
/// Thread-safe wrapper for V8Runtime that allows sending from multiple threads.
pub struct SharedV8Runtime {
inner: Arc<Mutex<V8Runtime>>,
}
impl SharedV8Runtime {
pub fn new(runtime: V8Runtime) -> Self {
Self {
inner: Arc::new(Mutex::new(runtime)),
}
}
pub fn lock(&self) -> std::sync::MutexGuard<'_, V8Runtime> {
self.inner.lock().expect("V8 runtime lock poisoned")
}
}
impl Clone for SharedV8Runtime {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
/// Bridge call method name mapping from V8 polyfill names to sidecar sync RPC names.
/// The V8 polyfills use underscore-prefixed camelCase names while the sidecar
/// uses dot-separated category.method names. The mapping lives in
/// `bridge-contract.json` so bridge installation and dispatch drift together.
pub fn map_bridge_method(method: &str) -> (&str, bool) {
if let Some(target) = agentos_bridge::bridge_contract().dispatch.get(method) {
(target.method.as_str(), target.translate_args)
} else {
(method, false)
}
}
/// Deserialize a CBOR payload into a JSON array of arguments.
/// The V8 bridge serializes bridge call args as a CBOR array.
pub fn cbor_payload_to_json_args(payload: &[u8]) -> io::Result<Vec<Value>> {
if payload.is_empty() {
return Ok(vec![]);
}
let cbor_value: ciborium::value::Value = ciborium::de::from_reader(payload).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to deserialize CBOR bridge call payload: {e}"),
)
})?;
match cbor_to_json(cbor_value) {
Value::Array(arr) => Ok(arr),
single => Ok(vec![single]),
}
}
pub fn cbor_payload_raw_byte_arg(payload: &[u8], index: usize) -> io::Result<Option<Vec<u8>>> {
if payload.is_empty() {
return Ok(None);
}
let cbor_value: ciborium::value::Value = ciborium::de::from_reader(payload).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to deserialize CBOR bridge call payload: {e}"),
)
})?;
let Some(value) = cbor_array_arg(&cbor_value, index) else {
return Ok(None);
};
Ok(cbor_raw_bytes(value).map(ToOwned::to_owned))
}
/// Serialize a JSON value to CBOR bytes for bridge responses.
pub fn json_to_cbor_payload(value: &Value) -> io::Result<Vec<u8>> {
let cbor_value = json_to_cbor(value);
let mut buf = Vec::new();
ciborium::ser::into_writer(&cbor_value, &mut buf).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to serialize CBOR bridge response: {e}"),
)
})?;
Ok(buf)
}
fn cbor_array_arg(value: &ciborium::value::Value, index: usize) -> Option<&ciborium::value::Value> {
match value {
ciborium::value::Value::Array(values) => values.get(index),
value if index == 0 => Some(value),
_ => None,
}
}
fn cbor_raw_bytes(value: &ciborium::value::Value) -> Option<&[u8]> {
match value {
ciborium::value::Value::Bytes(bytes) => Some(bytes),
ciborium::value::Value::Tag(_, inner) => cbor_raw_bytes(inner),
_ => None,
}
}
fn cbor_to_json(value: ciborium::value::Value) -> Value {
use ciborium::value::Value as Cbor;
match value {
Cbor::Null => Value::Null,
Cbor::Bool(b) => Value::Bool(b),
Cbor::Integer(i) => {
let n: i128 = i.into();
if let Ok(n) = i64::try_from(n) {
Value::Number(n.into())
} else if let Ok(n) = u64::try_from(n) {
Value::Number(n.into())
} else {
Value::Number(serde_json::Number::from_f64(n as f64).unwrap_or(0.into()))
}
}
Cbor::Float(f) => serde_json::Number::from_f64(f)
.map(Value::Number)
.unwrap_or(Value::Null),
Cbor::Text(s) => Value::String(s),
Cbor::Bytes(b) => {
use serde_json::json;
// Encode binary data as base64 with a type marker
json!({ "__type": "Buffer", "data": base64_encode(&b) })
}
Cbor::Array(arr) => Value::Array(arr.into_iter().map(cbor_to_json).collect()),
Cbor::Map(map) => {
let mut obj = serde_json::Map::new();
for (k, v) in map {
let key = match k {
Cbor::Text(s) => s,
Cbor::Integer(i) => {
let n: i128 = i.into();
n.to_string()
}
other => format!("{other:?}"),
};
obj.insert(key, cbor_to_json(v));
}
Value::Object(obj)
}
Cbor::Tag(_, inner) => cbor_to_json(*inner),
_ => Value::Null,
}
}
fn json_to_cbor(value: &Value) -> ciborium::value::Value {
use ciborium::value::Value as Cbor;
match value {
Value::Null => Cbor::Null,
Value::Bool(b) => Cbor::Bool(*b),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Cbor::Integer(i.into())
} else if let Some(u) = n.as_u64() {
Cbor::Integer(u.into())
} else if let Some(f) = n.as_f64() {
Cbor::Float(f)
} else {
Cbor::Null
}
}
Value::String(s) => Cbor::Text(s.clone()),
Value::Array(arr) => Cbor::Array(arr.iter().map(json_to_cbor).collect()),
Value::Object(map) => {
// Check for Buffer type marker
if map.get("__type").and_then(Value::as_str) == Some("Buffer") {
if let Some(data) = map.get("data").and_then(Value::as_str) {
if let Ok(bytes) = base64_decode(data) {
return Cbor::Bytes(bytes);
}
}
}
Cbor::Map(
map.iter()
.map(|(k, v)| (Cbor::Text(k.clone()), json_to_cbor(v)))
.collect(),
)
}
}
}
/// Public base64 encode for use in bridge call handlers.
pub fn base64_encode_pub(data: &[u8]) -> String {
base64_encode(data)
}
pub fn base64_decode_pub(input: &str) -> Option<Vec<u8>> {
base64_decode(input).ok()
}
fn base64_encode(data: &[u8]) -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = chunk.get(1).copied().unwrap_or(0) as u32;
let b2 = chunk.get(2).copied().unwrap_or(0) as u32;
let triple = (b0 << 16) | (b1 << 8) | b2;
result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
if chunk.len() > 1 {
result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(CHARS[(triple & 0x3F) as usize] as char);
} else {
result.push('=');
}
}
result
}
fn base64_decode(input: &str) -> Result<Vec<u8>, ()> {
fn decode_char(c: u8) -> Result<u8, ()> {
match c {
b'A'..=b'Z' => Ok(c - b'A'),
b'a'..=b'z' => Ok(c - b'a' + 26),
b'0'..=b'9' => Ok(c - b'0' + 52),
b'+' => Ok(62),
b'/' => Ok(63),
b'=' => Ok(0),
_ => Err(()),
}
}
let bytes = input.as_bytes();
let mut result = Vec::with_capacity(bytes.len() * 3 / 4);
for chunk in bytes.chunks(4) {
if chunk.len() < 4 {
return Err(());
}
let a = decode_char(chunk[0])?;
let b = decode_char(chunk[1])?;
let c = decode_char(chunk[2])?;
let d = decode_char(chunk[3])?;
let triple = ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32);
result.push((triple >> 16) as u8);
if chunk[2] != b'=' {
result.push((triple >> 8) as u8);
}
if chunk[3] != b'=' {
result.push(triple as u8);
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::{json_to_cbor_payload, map_bridge_method};
use serde_json::json;
#[test]
fn cbor_byte_string_boundary_includes_five_byte_header() {
let payload_limit = 256 * 1024;
let raw_bytes = payload_limit - 5;
let encoded = json_to_cbor_payload(&json!({
"__type": "Buffer",
"data": super::base64_encode_pub(&vec![0xA5; raw_bytes]),
}))
.expect("encode boundary byte string");
assert_eq!(encoded.len(), payload_limit);
let oversized = json_to_cbor_payload(&json!({
"__type": "Buffer",
"data": super::base64_encode_pub(&vec![0xA5; raw_bytes + 1]),
}))
.expect("encode oversized byte string");
assert_eq!(oversized.len(), payload_limit + 1);
}
#[test]
fn audited_bridge_methods_map_to_named_handlers() {
for method in [
"_cryptoHashDigest",
"_cryptoSubtle",
"_networkHttp2ServerListenRaw",
"_networkHttpServerRequestRaw",
"_networkHttp2SessionConnectRaw",
"_networkHttp2StreamRespondRaw",
"_upgradeSocketWriteRaw",
"_netSocketSetNoDelayRaw",
"_kernelStdioWriteRaw",
"_kernelPollRaw",
"_kernelTtySizeRaw",
"_netSocketUpgradeTlsRaw",
"_tlsGetCiphersRaw",
"_dgramSocketAddressRaw",
"_dgramSocketSetBufferSizeRaw",
] {
let (mapped, _) = map_bridge_method(method);
assert_ne!(mapped, method, "missing bridge-method mapping for {method}");
}
}
#[test]
fn http_request_bridge_shortcut_is_not_mapped() {
assert_eq!(
map_bridge_method("_networkHttpRequestRaw"),
("_networkHttpRequestRaw", false)
);
}
}