use std::{collections::BTreeMap, sync::Arc};
use serde::{Deserialize, Serialize};
use crate::{
error::InterpreterError,
state::{InterpreterState, estimate_value_size},
value::{ClassValue, Value},
};
pub const STATE_FORMAT_VERSION: u32 = 11;
const VERSION_PREFIX_SIZE: usize = 4;
#[derive(Serialize, Deserialize)]
struct SerializedState {
variables: BTreeMap<String, Value>,
#[serde(default)]
classes: BTreeMap<String, ClassValue>,
}
const MAX_IMPORT_SIZE: usize = 16 * 1024 * 1024;
pub fn export_state(state: &InterpreterState) -> Result<Vec<u8>, InterpreterError> {
let mut variables = BTreeMap::new();
for (key, value) in &state.variables {
if key.starts_with('_') {
continue;
}
if matches!(value, Value::LazyProxy(_)) {
continue;
}
variables.insert(key.clone(), value.clone());
}
let classes = state.classes.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let serialized = SerializedState { variables, classes };
let body = serde_json::to_vec(&serialized)
.map_err(|e| InterpreterError::Runtime(format!("failed to serialize state: {e}")))?;
let mut out = Vec::with_capacity(VERSION_PREFIX_SIZE + body.len());
out.extend_from_slice(&STATE_FORMAT_VERSION.to_le_bytes());
out.extend_from_slice(&body);
debug_assert!(out.len() >= VERSION_PREFIX_SIZE, "exported state always carries version prefix");
debug_assert!(
out.len() == VERSION_PREFIX_SIZE + body.len(),
"exported state is version prefix + json body, nothing else"
);
Ok(out)
}
pub fn import_state(state: &mut InterpreterState, data: &[u8]) -> Result<(), InterpreterError> {
if data.len() > MAX_IMPORT_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"serialized state ({} bytes) exceeds import limit ({MAX_IMPORT_SIZE} bytes)",
data.len()
)));
}
let Some(version_bytes) = data.get(..VERSION_PREFIX_SIZE) else {
return Err(InterpreterError::StateFormatSuperseded {
found: 0,
expected: STATE_FORMAT_VERSION,
});
};
let Ok(version_array) = <[u8; VERSION_PREFIX_SIZE]>::try_from(version_bytes) else {
return Err(InterpreterError::StateFormatSuperseded {
found: 0,
expected: STATE_FORMAT_VERSION,
});
};
let found_version = u32::from_le_bytes(version_array);
if found_version != STATE_FORMAT_VERSION {
return Err(InterpreterError::StateFormatSuperseded {
found: found_version,
expected: STATE_FORMAT_VERSION,
});
}
let body = &data[VERSION_PREFIX_SIZE..];
let serialized: SerializedState = serde_json::from_slice(body)
.map_err(|e| InterpreterError::Runtime(format!("failed to deserialize state: {e}")))?;
for key in serialized.variables.keys() {
if !crate::security::validator::is_name_allowed(key) {
return Err(InterpreterError::Security(format!(
"imported state contains dangerous variable name '{key}'"
)));
}
}
let mut restored_bodies: Vec<(String, Vec<rustpython_parser::ast::Stmt>)> = Vec::new();
for (name, value) in &serialized.variables {
let Value::Function(func) = value else {
continue;
};
let stmts = crate::parser::parse(&func.source).map_err(|e| {
InterpreterError::Runtime(format!("failed to re-parse function '{name}': {e}"))
})?;
if stmts.len() != 1 {
return Err(InterpreterError::Security(format!(
"imported function '{name}' source contains {} statements (expected 1)",
stmts.len()
)));
}
match stmts.into_iter().next() {
Some(rustpython_parser::ast::Stmt::FunctionDef(func_node)) => {
if func_node.name.as_str() != name {
return Err(InterpreterError::Security(format!(
"imported function source declares '{}' but key is '{name}'",
func_node.name
)));
}
if func.name != *name {
return Err(InterpreterError::Security(format!(
"imported function '{name}' carries a mismatched inner name '{}'",
func.name
)));
}
restored_bodies.push((name.clone(), func_node.body));
}
_ => {
return Err(InterpreterError::Security(format!(
"imported function '{name}' source is not a function definition"
)));
}
}
}
let mut restored_method_bodies: Vec<(String, Vec<rustpython_parser::ast::Stmt>)> = Vec::new();
for class in serialized.classes.values() {
for method in class.methods.values() {
let stmts = crate::parser::parse(&method.source).map_err(|e| {
InterpreterError::Runtime(format!(
"failed to re-parse method '{}': {e}",
method.name
))
})?;
match stmts.into_iter().next() {
Some(rustpython_parser::ast::Stmt::FunctionDef(func_node)) => {
restored_method_bodies.push((method.name.clone(), func_node.body));
}
_ => {
return Err(InterpreterError::Security(format!(
"imported method '{}' source is not a function definition",
method.name
)));
}
}
}
}
state.variables = serialized.variables.into_iter().collect();
state.classes = serialized.classes.into_iter().collect();
state.function_bodies.clear();
for (name, body) in restored_bodies {
state.function_bodies.insert(name, Arc::new(body));
}
for (name, body) in restored_method_bodies {
state.function_bodies.insert(name, Arc::new(body));
}
state.lambda_bodies.clear();
let lambda_imports: Vec<(String, String)> = state
.variables
.values()
.filter_map(|v| match v {
Value::Lambda(def) => Some((def.lambda_id.clone(), def.source.clone())),
_ => None,
})
.collect();
for (lambda_id, source) in lambda_imports {
let parsed = crate::parser::parse(&source).map_err(|e| {
InterpreterError::Runtime(format!("failed to re-parse lambda '{lambda_id}': {e}"))
})?;
let Some(rustpython_parser::ast::Stmt::Expr(expr_stmt)) = parsed.into_iter().next() else {
return Err(InterpreterError::Security(format!(
"imported lambda '{lambda_id}' source is not an expression statement"
)));
};
let rustpython_parser::ast::Expr::Lambda(lambda_expr) = *expr_stmt.value else {
return Err(InterpreterError::Security(format!(
"imported lambda '{lambda_id}' source does not parse as a lambda"
)));
};
state.lambda_bodies.insert(lambda_id, Arc::new(*lambda_expr.body));
}
state.memory_used_bytes = 0;
for value in state.variables.values() {
state.memory_used_bytes =
state.memory_used_bytes.saturating_add(estimate_value_size(value));
}
state.check_memory()?;
Ok(())
}