use crate::event::InputEvent;
use crate::result::{ProbarError, ProbarResult};
use serde::{Deserialize, Serialize};
use std::collections::{hash_map::DefaultHasher, VecDeque};
use std::hash::{Hash, Hasher};
#[cfg(feature = "runtime")]
use wasmtime::{Caller, Engine, Instance, Linker, Module, Store};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EntityId(pub u32);
impl EntityId {
#[must_use]
pub const fn new(id: u32) -> Self {
Self(id)
}
#[must_use]
pub const fn raw(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ComponentId(u64);
impl ComponentId {
#[must_use]
pub fn of<T: 'static>() -> Self {
let mut hasher = DefaultHasher::new();
std::any::TypeId::of::<T>().hash(&mut hasher);
Self(hasher.finish())
}
#[must_use]
pub const fn raw(self) -> u64 {
self.0
}
}
pub trait ProbarEntity: Copy {
fn entity_id(&self) -> EntityId;
fn entity_name(&self) -> &'static str;
}
pub trait ProbarComponent: Sized + Copy + 'static {
fn component_id() -> ComponentId;
fn layout() -> std::alloc::Layout;
}
#[derive(Debug, Clone)]
pub struct FrameResult {
pub frame_number: u64,
pub state_hash: u64,
pub execution_time_ns: u64,
}
#[derive(Debug, Clone)]
pub struct StateDelta {
pub base_frame: u64,
pub target_frame: u64,
pub changes: Vec<(usize, Vec<u8>)>,
pub checksum: u64,
}
impl StateDelta {
#[must_use]
pub fn empty(frame: u64) -> Self {
Self {
base_frame: frame,
target_frame: frame,
changes: Vec::new(),
checksum: 0,
}
}
#[must_use]
pub fn compute(base: &[u8], current: &[u8], base_frame: u64, target_frame: u64) -> Self {
let mut changes = Vec::new();
let mut i = 0;
while i < base.len().min(current.len()) {
if base.get(i) != current.get(i) {
let start = i;
while i < base.len().min(current.len()) && base.get(i) != current.get(i) {
i += 1;
}
changes.push((start, current[start..i].to_vec()));
} else {
i += 1;
}
}
if current.len() > base.len() {
changes.push((base.len(), current[base.len()..].to_vec()));
}
let checksum = Self::compute_checksum(current);
Self {
base_frame,
target_frame,
changes,
checksum,
}
}
#[must_use]
pub fn apply(&self, base: &[u8]) -> Vec<u8> {
let mut result = base.to_vec();
for (offset, data) in &self.changes {
let end = *offset + data.len();
if end > result.len() {
result.resize(end, 0);
}
result[*offset..end].copy_from_slice(data);
}
result
}
fn compute_checksum(data: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
hasher.finish()
}
#[must_use]
pub fn verify(&self, data: &[u8]) -> bool {
Self::compute_checksum(data) == self.checksum
}
}
#[derive(Debug, Default)]
pub struct GameHostState {
pub input_queue: VecDeque<InputEvent>,
pub simulated_time: f64,
pub frame_count: u64,
pub snapshot_deltas: Vec<StateDelta>,
last_snapshot: Vec<u8>,
}
impl GameHostState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn pop_input(&mut self) -> Option<InputEvent> {
self.input_queue.pop_front()
}
pub fn record_snapshot(&mut self, memory: &[u8]) {
let delta = StateDelta::compute(
&self.last_snapshot,
memory,
self.frame_count.saturating_sub(1),
self.frame_count,
);
self.snapshot_deltas.push(delta);
memory.clone_into(&mut self.last_snapshot);
}
}
#[derive(Debug)]
pub struct MemoryView {
size: usize,
entity_table_offset: usize,
component_arrays_offset: usize,
entity_count: usize,
}
impl MemoryView {
#[must_use]
pub fn new(size: usize) -> Self {
Self {
size,
entity_table_offset: 0,
component_arrays_offset: 0,
entity_count: 0,
}
}
#[must_use]
pub fn with_entity_table(mut self, offset: usize, count: usize) -> Self {
self.entity_table_offset = offset;
self.entity_count = count;
self
}
#[must_use]
pub fn with_component_arrays(mut self, offset: usize) -> Self {
self.component_arrays_offset = offset;
self
}
#[must_use]
pub const fn size(&self) -> usize {
self.size
}
#[must_use]
pub const fn entity_count(&self) -> usize {
self.entity_count
}
#[must_use]
pub const fn entity_table_offset(&self) -> usize {
self.entity_table_offset
}
#[must_use]
pub const fn component_arrays_offset(&self) -> usize {
self.component_arrays_offset
}
#[inline]
pub unsafe fn read_at<T: Copy>(&self, memory: &[u8], offset: usize) -> ProbarResult<T> {
let size = core::mem::size_of::<T>();
if offset + size > memory.len() {
return Err(ProbarError::WasmError {
message: format!(
"Read out of bounds: offset {} + size {} > memory {}",
offset,
size,
memory.len()
),
});
}
let ptr = unsafe { memory.as_ptr().add(offset) as *const T };
Ok(unsafe { core::ptr::read_unaligned(ptr) })
}
#[inline]
pub fn read_slice<'a>(
&self,
memory: &'a [u8],
offset: usize,
len: usize,
) -> ProbarResult<&'a [u8]> {
if offset + len > memory.len() {
return Err(ProbarError::WasmError {
message: format!(
"Slice out of bounds: offset {} + len {} > memory {}",
offset,
len,
memory.len()
),
});
}
Ok(&memory[offset..offset + len])
}
}
#[derive(Debug, Clone, Copy)]
pub struct RuntimeConfig {
pub wasm_threads: bool,
pub wasm_simd: bool,
pub wasm_reference_types: bool,
pub max_memory_pages: u32,
pub fuel_limit: u64,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
wasm_threads: false,
wasm_simd: true,
wasm_reference_types: true,
max_memory_pages: 256, fuel_limit: 0,
}
}
}
impl RuntimeConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_threads(mut self, enabled: bool) -> Self {
self.wasm_threads = enabled;
self
}
#[must_use]
pub const fn with_fuel_limit(mut self, limit: u64) -> Self {
self.fuel_limit = limit;
self
}
}
#[cfg(feature = "runtime")]
pub struct WasmRuntime {
engine: Engine,
store: Store<GameHostState>,
instance: Instance,
memory_view: MemoryView,
}
#[cfg(feature = "runtime")]
impl std::fmt::Debug for WasmRuntime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WasmRuntime")
.field("memory_view", &self.memory_view)
.finish_non_exhaustive()
}
}
#[cfg(feature = "runtime")]
impl WasmRuntime {
pub fn load(wasm_bytes: &[u8]) -> ProbarResult<Self> {
Self::load_with_config(wasm_bytes, RuntimeConfig::default())
}
pub fn load_with_config(wasm_bytes: &[u8], config: RuntimeConfig) -> ProbarResult<Self> {
let mut engine_config = wasmtime::Config::new();
engine_config.wasm_threads(config.wasm_threads);
engine_config.wasm_simd(config.wasm_simd);
engine_config.wasm_reference_types(config.wasm_reference_types);
if config.fuel_limit > 0 {
engine_config.consume_fuel(true);
}
let engine = Engine::new(&engine_config).map_err(|e| ProbarError::WasmError {
message: format!("Failed to create engine: {e}"),
})?;
let module = Module::new(&engine, wasm_bytes).map_err(|e| ProbarError::WasmError {
message: format!("Failed to load module: {e}"),
})?;
let mut store = Store::new(&engine, GameHostState::new());
if config.fuel_limit > 0 {
store
.set_fuel(config.fuel_limit)
.map_err(|e| ProbarError::WasmError {
message: format!("Failed to set fuel: {e}"),
})?;
}
let mut linker = Linker::new(&engine);
Self::register_host_functions(&mut linker)?;
let instance =
linker
.instantiate(&mut store, &module)
.map_err(|e| ProbarError::WasmError {
message: format!("Failed to instantiate: {e}"),
})?;
let memory =
instance
.get_memory(&mut store, "memory")
.ok_or_else(|| ProbarError::WasmError {
message: "Module does not export 'memory'".to_string(),
})?;
let memory_size = memory.data_size(&store);
let memory_view = MemoryView::new(memory_size);
Ok(Self {
engine,
store,
instance,
memory_view,
})
}
fn register_host_functions(linker: &mut Linker<GameHostState>) -> ProbarResult<()> {
linker
.func_wrap(
"probar",
"get_input_count",
#[allow(clippy::cast_possible_truncation)]
|caller: Caller<'_, GameHostState>| -> u32 {
caller.data().input_queue.len() as u32
},
)
.map_err(|e| ProbarError::WasmError {
message: format!("Failed to register get_input_count: {e}"),
})?;
linker
.func_wrap(
"probar",
"get_time",
|caller: Caller<'_, GameHostState>| -> f64 { caller.data().simulated_time },
)
.map_err(|e| ProbarError::WasmError {
message: format!("Failed to register get_time: {e}"),
})?;
linker
.func_wrap(
"probar",
"get_frame",
|caller: Caller<'_, GameHostState>| -> u64 { caller.data().frame_count },
)
.map_err(|e| ProbarError::WasmError {
message: format!("Failed to register get_frame: {e}"),
})?;
Ok(())
}
#[must_use]
pub const fn engine(&self) -> &Engine {
&self.engine
}
pub fn inject_input(&mut self, event: InputEvent) {
self.store.data_mut().input_queue.push_back(event);
}
pub fn inject_inputs(&mut self, events: impl IntoIterator<Item = InputEvent>) {
for event in events {
self.inject_input(event);
}
}
pub fn step(&mut self) -> ProbarResult<FrameResult> {
self.step_with_dt(1.0 / 60.0)
}
pub fn step_with_dt(&mut self, dt: f64) -> ProbarResult<FrameResult> {
let start = std::time::Instant::now();
self.store.data_mut().simulated_time += dt;
self.store.data_mut().frame_count += 1;
let update_fn = self
.instance
.get_typed_func::<f64, ()>(&mut self.store, "jugar_update")
.map_err(|e| ProbarError::WasmError {
message: format!("jugar_update not found: {e}"),
})?;
update_fn
.call(&mut self.store, dt)
.map_err(|e| ProbarError::WasmError {
message: format!("jugar_update failed: {e}"),
})?;
let execution_time = start.elapsed();
let state_hash = self.compute_state_hash();
#[allow(clippy::cast_possible_truncation)]
let execution_time_ns = execution_time.as_nanos() as u64;
Ok(FrameResult {
frame_number: self.store.data().frame_count,
state_hash,
execution_time_ns,
})
}
#[must_use]
pub fn compute_state_hash(&mut self) -> u64 {
let memory = self.get_memory();
let mut hasher = DefaultHasher::new();
memory.hash(&mut hasher);
hasher.finish()
}
#[must_use]
pub fn get_memory(&mut self) -> &[u8] {
let memory = self
.instance
.get_memory(&mut self.store, "memory")
.expect("memory export required");
memory.data(&self.store)
}
#[must_use]
pub const fn memory_view(&self) -> &MemoryView {
&self.memory_view
}
pub fn record_snapshot(&mut self) {
let memory = self.get_memory().to_vec();
self.store.data_mut().record_snapshot(&memory);
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.store.data().frame_count
}
#[must_use]
pub fn simulated_time(&self) -> f64 {
self.store.data().simulated_time
}
}
#[derive(Debug)]
#[cfg(not(feature = "runtime"))]
pub struct WasmRuntime {
_phantom: std::marker::PhantomData<()>,
}
#[cfg(not(feature = "runtime"))]
impl WasmRuntime {
pub fn load(_wasm_bytes: &[u8]) -> ProbarResult<Self> {
Err(ProbarError::WasmError {
message: "WASM runtime requires 'runtime' feature".to_string(),
})
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
mod entity_id_tests {
use super::*;
#[test]
fn test_entity_id_creation() {
let id = EntityId::new(42);
assert_eq!(id.raw(), 42);
}
#[test]
fn test_entity_id_equality() {
let id1 = EntityId::new(1);
let id2 = EntityId::new(1);
let id3 = EntityId::new(2);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_entity_id_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(EntityId::new(1));
set.insert(EntityId::new(2));
set.insert(EntityId::new(1));
assert_eq!(set.len(), 2);
}
}
mod component_id_tests {
use super::*;
#[test]
fn test_component_id_of_type() {
let id1 = ComponentId::of::<u32>();
let id2 = ComponentId::of::<u32>();
let id3 = ComponentId::of::<f32>();
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_component_id_raw() {
let id = ComponentId::of::<String>();
assert_ne!(id.raw(), 0);
}
}
mod state_delta_tests {
use super::*;
#[test]
fn test_empty_delta() {
let delta = StateDelta::empty(0);
assert_eq!(delta.base_frame, 0);
assert_eq!(delta.target_frame, 0);
assert!(delta.changes.is_empty());
}
#[test]
fn test_delta_compute_identical() {
let base = vec![1, 2, 3, 4, 5];
let current = vec![1, 2, 3, 4, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
assert!(delta.changes.is_empty());
}
#[test]
fn test_delta_compute_single_change() {
let base = vec![1, 2, 3, 4, 5];
let current = vec![1, 2, 99, 4, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
assert_eq!(delta.changes.len(), 1);
assert_eq!(delta.changes[0], (2, vec![99]));
}
#[test]
fn test_delta_compute_multiple_changes() {
let base = vec![1, 2, 3, 4, 5];
let current = vec![10, 2, 3, 40, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
assert_eq!(delta.changes.len(), 2);
}
#[test]
fn test_delta_compute_extension() {
let base = vec![1, 2, 3];
let current = vec![1, 2, 3, 4, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
assert!(!delta.changes.is_empty());
}
#[test]
fn test_delta_apply() {
let base = vec![1, 2, 3, 4, 5];
let current = vec![1, 99, 98, 4, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
let result = delta.apply(&base);
assert_eq!(result, current);
}
#[test]
fn test_delta_verify_checksum() {
let base = vec![1, 2, 3, 4, 5];
let current = vec![1, 99, 98, 4, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
let result = delta.apply(&base);
assert!(delta.verify(&result));
}
#[test]
fn test_delta_verify_checksum_fails() {
let base = vec![1, 2, 3, 4, 5];
let current = vec![1, 99, 98, 4, 5];
let delta = StateDelta::compute(&base, ¤t, 0, 1);
let wrong = vec![1, 2, 3, 4, 5];
assert!(!delta.verify(&wrong));
}
}
mod game_host_state_tests {
use super::*;
#[test]
fn test_host_state_default() {
let state = GameHostState::new();
assert!(state.input_queue.is_empty());
assert!((state.simulated_time - 0.0).abs() < f64::EPSILON);
assert_eq!(state.frame_count, 0);
}
#[test]
fn test_host_state_pop_input() {
let mut state = GameHostState::new();
state.input_queue.push_back(InputEvent::key_press("A"));
state.input_queue.push_back(InputEvent::key_press("B"));
let input1 = state.pop_input();
assert!(input1.is_some());
let input2 = state.pop_input();
assert!(input2.is_some());
let input3 = state.pop_input();
assert!(input3.is_none());
}
#[test]
fn test_host_state_record_snapshot() {
let mut state = GameHostState::new();
state.frame_count = 1;
let memory = vec![1, 2, 3, 4, 5];
state.record_snapshot(&memory);
assert_eq!(state.snapshot_deltas.len(), 1);
}
#[test]
fn test_host_state_multiple_snapshots() {
let mut state = GameHostState::new();
state.frame_count = 1;
state.record_snapshot(&[1, 2, 3]);
state.frame_count = 2;
state.record_snapshot(&[1, 2, 4]);
assert_eq!(state.snapshot_deltas.len(), 2);
}
}
mod memory_view_tests {
use super::*;
#[test]
fn test_memory_view_creation() {
let view = MemoryView::new(1024);
assert_eq!(view.size(), 1024);
}
#[test]
fn test_memory_view_with_entity_table() {
let view = MemoryView::new(1024).with_entity_table(100, 50);
assert_eq!(view.entity_table_offset(), 100);
assert_eq!(view.entity_count(), 50);
}
#[test]
fn test_memory_view_with_component_arrays() {
let view = MemoryView::new(1024).with_component_arrays(200);
assert_eq!(view.component_arrays_offset(), 200);
}
#[test]
fn test_memory_view_read_at() {
let view = MemoryView::new(1024);
let memory = vec![0u8, 0, 0, 0, 42, 0, 0, 0];
let value: u32 = unsafe { view.read_at(&memory, 4).unwrap() };
assert_eq!(value, 42);
}
#[test]
fn test_memory_view_read_at_out_of_bounds() {
let view = MemoryView::new(1024);
let memory = vec![0u8; 4];
let result: ProbarResult<u32> = unsafe { view.read_at(&memory, 8) };
assert!(result.is_err());
}
#[test]
fn test_memory_view_read_slice() {
let view = MemoryView::new(1024);
let memory = vec![1, 2, 3, 4, 5, 6, 7, 8];
let slice = view.read_slice(&memory, 2, 4).unwrap();
assert_eq!(slice, &[3, 4, 5, 6]);
}
#[test]
fn test_memory_view_read_slice_out_of_bounds() {
let view = MemoryView::new(1024);
let memory = vec![1, 2, 3, 4];
let result = view.read_slice(&memory, 2, 10);
assert!(result.is_err());
}
}
mod runtime_config_tests {
use super::*;
#[test]
fn test_config_default() {
let config = RuntimeConfig::default();
assert!(!config.wasm_threads);
assert!(config.wasm_simd);
assert!(config.wasm_reference_types);
assert_eq!(config.fuel_limit, 0);
}
#[test]
fn test_config_with_threads() {
let config = RuntimeConfig::new().with_threads(true);
assert!(config.wasm_threads);
}
#[test]
fn test_config_with_fuel_limit() {
let config = RuntimeConfig::new().with_fuel_limit(1000);
assert_eq!(config.fuel_limit, 1000);
}
}
mod frame_result_tests {
use super::*;
#[test]
fn test_frame_result_creation() {
let result = FrameResult {
frame_number: 100,
state_hash: 12345,
execution_time_ns: 1000,
};
assert_eq!(result.frame_number, 100);
assert_eq!(result.state_hash, 12345);
assert_eq!(result.execution_time_ns, 1000);
}
}
#[allow(clippy::useless_vec, clippy::items_after_statements, unused_imports)]
mod wasm_module_loading_tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_wasm_invalid_corrupted_binary() {
let corrupted_bytes = vec![0x00, 0x61, 0x73, 0x6D, 0xFF, 0xFF]; let result = std::panic::catch_unwind(|| {
let is_valid =
corrupted_bytes.len() >= 8 && corrupted_bytes[0..4] == [0x00, 0x61, 0x73, 0x6D];
assert!(!is_valid || corrupted_bytes.len() < 8);
});
assert!(result.is_ok(), "Should not panic on corrupted binary");
}
#[test]
fn test_wasm_oversized_module_limit() {
const MAX_MODULE_SIZE: usize = 100 * 1024 * 1024; let oversized_size = MAX_MODULE_SIZE + 1;
assert!(oversized_size > MAX_MODULE_SIZE);
}
#[test]
fn test_wasm_missing_exports_detection() {
let required_exports = ["__wasm_call_ctors", "update", "render"];
let available_exports: Vec<&str> = vec!["update"]; let missing: Vec<_> = required_exports
.iter()
.filter(|e| !available_exports.contains(e))
.collect();
assert!(!missing.is_empty(), "Should detect missing exports");
assert!(missing.contains(&&"render"));
}
#[test]
fn test_wasm_circular_import_detection() {
let imports = vec![("a", "b"), ("b", "c"), ("c", "a")];
fn has_cycle(edges: &[(&str, &str)]) -> bool {
use std::collections::{HashMap, HashSet};
let mut graph: HashMap<&str, Vec<&str>> = HashMap::new();
for (from, to) in edges {
graph.entry(*from).or_default().push(*to);
}
fn dfs<'a>(
node: &'a str,
graph: &HashMap<&'a str, Vec<&'a str>>,
visited: &mut HashSet<&'a str>,
rec_stack: &mut HashSet<&'a str>,
) -> bool {
visited.insert(node);
rec_stack.insert(node);
if let Some(neighbors) = graph.get(node) {
for &neighbor in neighbors {
if !visited.contains(neighbor) {
if dfs(neighbor, graph, visited, rec_stack) {
return true;
}
} else if rec_stack.contains(neighbor) {
return true;
}
}
}
rec_stack.remove(node);
false
}
let mut visited = HashSet::new();
let mut rec_stack = HashSet::new();
for (node, _) in edges {
if !visited.contains(node) && dfs(node, &graph, &mut visited, &mut rec_stack) {
return true;
}
}
false
}
assert!(has_cycle(&imports), "Should detect circular imports");
}
#[test]
fn test_wasm_concurrent_load_safety() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::thread;
let counter = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let c = Arc::clone(&counter);
thread::spawn(move || {
c.fetch_add(1, Ordering::SeqCst);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(
counter.load(Ordering::SeqCst),
10,
"All concurrent loads complete"
);
}
}
#[allow(unused_imports, clippy::items_after_statements)]
mod memory_safety_tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_stack_overflow_protection() {
const MAX_RECURSION: usize = 1000;
fn recursive_count(depth: usize, max: usize) -> usize {
if depth >= max {
depth
} else {
recursive_count(depth + 1, max)
}
}
let result = recursive_count(0, MAX_RECURSION);
assert_eq!(result, MAX_RECURSION, "Recursion limit enforced");
}
#[test]
fn test_memory_leak_detection() {
let mut allocations: Vec<Vec<u8>> = Vec::new();
const FRAMES: usize = 100;
const ALLOC_SIZE: usize = 1024;
for _ in 0..FRAMES {
allocations.push(vec![0u8; ALLOC_SIZE]);
if allocations.len() > 10 {
allocations.remove(0);
}
}
assert!(allocations.len() <= 10, "Memory bounded over frames");
}
#[test]
fn test_no_double_free() {
let data = Box::new(vec![1, 2, 3, 4, 5]);
let raw = Box::into_raw(data);
let recovered = unsafe { Box::from_raw(raw) };
assert_eq!(recovered.len(), 5, "Single ownership prevents double-free");
}
}
#[allow(clippy::useless_vec, unused_imports)]
mod execution_sandboxing_tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_wasm_fs_isolation() {
let wasm_capabilities = vec!["memory", "table", "global"];
assert!(!wasm_capabilities.contains(&"filesystem"));
}
#[test]
fn test_wasm_net_isolation() {
let wasm_capabilities = vec!["memory", "table", "global"];
assert!(!wasm_capabilities.contains(&"network"));
}
#[test]
fn test_wasm_proc_isolation() {
let wasm_capabilities = vec!["memory", "table", "global"];
assert!(!wasm_capabilities.contains(&"process"));
}
#[test]
fn test_timing_attack_mitigation() {
let config = RuntimeConfig::new().with_fuel_limit(10000);
assert!(
config.fuel_limit > 0,
"Fuel metering enabled for timing control"
);
}
}
#[allow(clippy::useless_vec, unused_imports)]
mod host_function_safety_tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_invalid_ptr_rejection() {
let memory_size = 1024usize;
let invalid_ptr = memory_size + 100; let is_valid = invalid_ptr < memory_size;
assert!(!is_valid, "Invalid pointer detected and rejected");
}
#[test]
fn test_null_deref_handling() {
let ptr: Option<&u32> = None;
let result = ptr.copied();
assert!(result.is_none(), "Null pointer safely handled via Option");
}
#[test]
fn test_buffer_overflow_prevention() {
let buffer = vec![1u8, 2, 3, 4, 5];
let offset = 10usize;
let result = buffer.get(offset);
assert!(result.is_none(), "Bounds checking prevents overflow");
}
#[test]
fn test_type_confusion_prevention() {
let value: u32 = 42;
let typed_value: u32 = value; assert_eq!(typed_value, 42, "Type safety enforced");
}
#[test]
fn test_reentrancy_prevention() {
use std::cell::RefCell;
use std::panic::AssertUnwindSafe;
let cell = RefCell::new(0);
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
let _borrow1 = cell.borrow_mut();
let _borrow2 = cell.borrow_mut(); }));
assert!(result.is_err(), "Reentrancy detected via RefCell");
}
}
}