use crate::capabilities::RuntimeCapabilities;
use crate::ids::{ExecutionId, ObjectId};
use crate::runtime::RuntimeKind;
use crate::semantic::ConversionPolicy;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionLimits {
#[serde(with = "duration_millis")]
pub timeout: Duration,
pub max_output_bytes: u64,
pub max_message_bytes: u64,
pub max_memory_bytes: Option<u64>,
pub max_fuel: Option<u64>,
}
impl Default for ExecutionLimits {
fn default() -> Self {
Self {
timeout: Duration::from_secs(30),
max_output_bytes: 1_048_576,
max_message_bytes: 16_777_216,
max_memory_bytes: Some(256 * 1024 * 1024),
max_fuel: Some(10_000_000),
}
}
}
mod duration_millis {
use serde::{Deserialize, Deserializer, Serializer};
use std::time::Duration;
pub fn serialize<S>(d: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ms = u64::try_from(d.as_millis()).unwrap_or(u64::MAX);
serializer.serialize_u64(ms)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let ms = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(ms))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ProgramSource {
File {
path: String,
},
Inline {
source: String,
filename: String,
},
Bytes {
#[serde(with = "serde_bytes_b64")]
data: Vec<u8>,
filename: String,
},
CaptureBindings {
source: String,
names: Vec<String>,
filename: String,
},
}
mod serde_bytes_b64 {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::Error;
let encoded = base64_encode(bytes).map_err(S::Error::custom)?;
serializer.serialize_str(&encoded)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let s = String::deserialize(deserializer)?;
base64_decode(&s).map_err(D::Error::custom)
}
fn base64_encode(bytes: &[u8]) -> Result<String, String> {
const TABLE: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::new();
for chunk in bytes.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 n = (b0 << 16) | (b1 << 8) | b2;
out.push(TABLE[((n >> 18) & 63) as usize] as char);
out.push(TABLE[((n >> 12) & 63) as usize] as char);
out.push(if chunk.len() > 1 {
TABLE[((n >> 6) & 63) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
TABLE[(n & 63) as usize] as char
} else {
'='
});
}
Ok(out)
}
fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
fn val(c: u8) -> Result<u8, String> {
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),
_ => Err(format!("invalid base64 byte: {}", c)),
}
}
let bytes = input.as_bytes();
if bytes.len() % 4 != 0 {
return Err("invalid base64 length".into());
}
let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
for chunk in bytes.chunks(4) {
let n = ((val(chunk[0])? as u32) << 18)
| ((val(chunk[1])? as u32) << 12)
| ((if chunk[2] == b'=' {
0
} else {
val(chunk[2])? as u32
}) << 6)
| (if chunk[3] == b'=' {
0
} else {
val(chunk[3])? as u32
});
out.push(((n >> 16) & 0xff) as u8);
if chunk[2] != b'=' {
out.push(((n >> 8) & 0xff) as u8);
}
if chunk[3] != b'=' {
out.push((n & 0xff) as u8);
}
}
Ok(out)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionRequest {
pub execution_id: ExecutionId,
pub runtime: RuntimeKind,
pub program: ProgramSource,
pub limits: ExecutionLimits,
pub entry: Option<String>,
pub args: Vec<serde_json::Value>,
pub capture_state: bool,
#[serde(default)]
pub continuum: bool,
}
impl ExecutionRequest {
pub fn file(runtime: RuntimeKind, path: impl Into<String>) -> Self {
Self {
execution_id: ExecutionId::new(),
runtime,
program: ProgramSource::File { path: path.into() },
limits: ExecutionLimits::default(),
entry: None,
args: Vec::new(),
capture_state: false,
continuum: false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionResult {
pub execution_id: ExecutionId,
pub runtime: RuntimeKind,
pub success: bool,
pub value: Option<serde_json::Value>,
pub stdout: String,
pub stderr: String,
pub duration_us: u64,
pub exception: Option<ExceptionInfo>,
pub state: Option<ExecutionState>,
#[serde(default)]
pub suspended: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ues: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub safepoint: Option<serde_json::Value>,
pub stats: IndexMap<String, serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExceptionInfo {
pub type_name: String,
pub message: String,
pub stack: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionState {
pub version: u32,
pub runtime: RuntimeKind,
pub capabilities: RuntimeCapabilities,
pub program: Option<String>,
pub instruction_position: Option<String>,
pub stack_frames: Vec<StackFrame>,
pub globals_root: Option<ObjectId>,
pub locals_root: Option<ObjectId>,
pub heap: serde_json::Value,
pub pending_exception: Option<ExceptionInfo>,
pub async_state: Option<serde_json::Value>,
pub granted_capabilities: Vec<String>,
pub runtime_metadata: IndexMap<String, serde_json::Value>,
pub migration_metadata: IndexMap<String, serde_json::Value>,
pub conversion_policy: ConversionPolicy,
}
impl ExecutionState {
pub fn empty(runtime: RuntimeKind, capabilities: RuntimeCapabilities) -> Self {
Self {
version: crate::PIR_SCHEMA_VERSION,
runtime,
capabilities,
program: None,
instruction_position: None,
stack_frames: Vec::new(),
globals_root: None,
locals_root: None,
heap: serde_json::json!({}),
pending_exception: None,
async_state: None,
granted_capabilities: Vec::new(),
runtime_metadata: IndexMap::new(),
migration_metadata: IndexMap::new(),
conversion_policy: ConversionPolicy::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StackFrame {
pub function: Option<String>,
pub location: Option<String>,
pub locals: Option<ObjectId>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RestoreResult {
pub success: bool,
pub runtime: RuntimeKind,
pub warnings: Vec<String>,
pub restored_bindings: IndexMap<String, String>,
pub duration_us: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_limits_are_bounded() {
let limits = ExecutionLimits::default();
assert!(limits.timeout.as_secs() > 0);
assert!(limits.max_output_bytes > 0);
}
}