1use std::collections::HashMap;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7use parking_lot::Mutex;
8
9use crate::capabilities::Capabilities;
10use crate::error::{Error, Result};
11use crate::limits::{LimitTracker, Limits};
12use crate::sandbox::{Sandbox, SandboxConfig};
13use crate::value::Value;
14
15#[derive(Debug, Clone)]
17pub struct EngineConfig {
18 pub limits: Limits,
20 pub capabilities: Capabilities,
22 pub sandbox: SandboxConfig,
24 pub debug: bool,
26 pub metadata: HashMap<String, String>,
28}
29
30impl Default for EngineConfig {
31 fn default() -> Self {
32 Self {
33 limits: Limits::default(),
34 capabilities: Capabilities::safe_defaults(),
35 sandbox: SandboxConfig::default(),
36 debug: false,
37 metadata: HashMap::new(),
38 }
39 }
40}
41
42impl EngineConfig {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn with_limits(mut self, limits: Limits) -> Self {
50 self.limits = limits;
51 self
52 }
53
54 pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
56 self.capabilities = capabilities;
57 self
58 }
59
60 pub fn with_sandbox(mut self, sandbox: SandboxConfig) -> Self {
62 self.sandbox = sandbox;
63 self
64 }
65
66 pub fn with_debug(mut self, debug: bool) -> Self {
68 self.debug = debug;
69 self
70 }
71
72 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
74 self.metadata.insert(key.into(), value.into());
75 self
76 }
77
78 pub fn strict() -> Self {
80 Self {
81 limits: Limits::strict(),
82 capabilities: Capabilities::none(),
83 sandbox: SandboxConfig::locked(),
84 debug: false,
85 metadata: HashMap::new(),
86 }
87 }
88
89 pub fn permissive() -> Self {
91 Self {
92 limits: Limits::unlimited(),
93 capabilities: Capabilities::all(),
94 sandbox: SandboxConfig::permissive(),
95 debug: false,
96 metadata: HashMap::new(),
97 }
98 }
99}
100
101pub type HostFn = Arc<dyn Fn(&[Value], &ExecutionContext) -> Result<Value> + Send + Sync>;
103
104#[derive(Default, Clone)]
106pub struct HostRegistry {
107 functions: HashMap<String, HostFn>,
108 modules: HashMap<String, HashMap<String, HostFn>>,
109}
110
111impl HostRegistry {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn register<S, F>(&mut self, name: S, f: F)
119 where
120 S: Into<String>,
121 F: Fn(&[Value], &ExecutionContext) -> Result<Value> + Send + Sync + 'static,
122 {
123 self.functions.insert(name.into(), Arc::new(f));
124 }
125
126 pub fn register_module<M, N, F>(&mut self, module: M, name: N, f: F)
128 where
129 M: Into<String>,
130 N: Into<String>,
131 F: Fn(&[Value], &ExecutionContext) -> Result<Value> + Send + Sync + 'static,
132 {
133 self.modules
134 .entry(module.into())
135 .or_default()
136 .insert(name.into(), Arc::new(f));
137 }
138
139 pub fn get(&self, name: &str) -> Option<&HostFn> {
141 self.functions.get(name)
142 }
143
144 pub fn get_module(&self, module: &str, name: &str) -> Option<&HostFn> {
146 self.modules.get(module).and_then(|m| m.get(name))
147 }
148
149 pub fn function_names(&self) -> impl Iterator<Item = &String> {
151 self.functions.keys()
152 }
153
154 pub fn module_names(&self) -> impl Iterator<Item = &String> {
156 self.modules.keys()
157 }
158
159 pub fn merge(&mut self, other: HostRegistry) {
161 self.functions.extend(other.functions);
162 for (module, funcs) in other.modules {
163 self.modules.entry(module).or_default().extend(funcs);
164 }
165 }
166}
167
168impl std::fmt::Debug for HostRegistry {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 f.debug_struct("HostRegistry")
171 .field("functions", &self.functions.keys().collect::<Vec<_>>())
172 .field("modules", &self.modules.keys().collect::<Vec<_>>())
173 .finish()
174 }
175}
176
177#[derive(Debug)]
179pub struct ExecutionContext {
180 pub engine_id: u64,
182 pub capabilities: Capabilities,
184 limit_tracker: Mutex<LimitTracker>,
186 sandbox: Sandbox,
188 custom: Mutex<HashMap<String, Value>>,
190 start_time: Instant,
192 cancelled: std::sync::atomic::AtomicBool,
194}
195
196impl ExecutionContext {
197 pub fn new(
199 engine_id: u64,
200 capabilities: Capabilities,
201 limits: Limits,
202 sandbox: Sandbox,
203 ) -> Self {
204 Self {
205 engine_id,
206 capabilities,
207 limit_tracker: Mutex::new(LimitTracker::new(limits)),
208 sandbox,
209 custom: Mutex::new(HashMap::new()),
210 start_time: Instant::now(),
211 cancelled: std::sync::atomic::AtomicBool::new(false),
212 }
213 }
214
215 pub fn has_capability(&self, cap: crate::Capability) -> bool {
217 self.capabilities.has(cap)
218 }
219
220 pub fn require_capability(&self, cap: crate::Capability) -> Result<()> {
222 self.capabilities.require(cap)
223 }
224
225 pub fn sandbox(&self) -> &Sandbox {
227 &self.sandbox
228 }
229
230 pub fn record_instructions(&self, count: u64) -> Result<()> {
232 self.limit_tracker.lock().record_instructions(count)?;
233 Ok(())
234 }
235
236 pub fn record_memory(&self, bytes: usize) -> Result<()> {
238 self.limit_tracker.lock().record_memory(bytes)?;
239 Ok(())
240 }
241
242 pub fn record_output(&self, bytes: usize) -> Result<()> {
244 self.limit_tracker.lock().record_output(bytes)?;
245 Ok(())
246 }
247
248 pub fn record_fs_op(&self) -> Result<()> {
250 self.limit_tracker.lock().record_fs_op()?;
251 Ok(())
252 }
253
254 pub fn record_net_op(&self) -> Result<()> {
256 self.limit_tracker.lock().record_net_op()?;
257 Ok(())
258 }
259
260 pub fn check_timeout(&self) -> Result<()> {
262 self.limit_tracker.lock().check_timeout()?;
263 Ok(())
264 }
265
266 pub fn elapsed(&self) -> Duration {
268 self.start_time.elapsed()
269 }
270
271 pub fn is_cancelled(&self) -> bool {
273 self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
274 }
275
276 pub fn cancel(&self) {
278 self.cancelled
279 .store(true, std::sync::atomic::Ordering::Relaxed);
280 }
281
282 pub fn set_custom(&self, key: impl Into<String>, value: Value) {
284 self.custom.lock().insert(key.into(), value);
285 }
286
287 pub fn get_custom(&self, key: &str) -> Option<Value> {
289 self.custom.lock().get(key).cloned()
290 }
291
292 pub fn reset(&self, limits: Limits) {
294 *self.limit_tracker.lock() = LimitTracker::new(limits);
295 self.cancelled
296 .store(false, std::sync::atomic::Ordering::Relaxed);
297 self.custom.lock().clear();
298 }
299}
300
301pub struct Engine {
306 id: u64,
307 config: EngineConfig,
308 registry: HostRegistry,
309 context: ExecutionContext,
310 bytecode_cache: Mutex<HashMap<String, Vec<u8>>>,
312}
313
314impl Engine {
315 pub fn new(config: EngineConfig) -> Result<Self> {
317 static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
318 let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
319
320 let sandbox = Sandbox::new(config.sandbox.clone())?;
321 let context = ExecutionContext::new(
322 id,
323 config.capabilities.clone(),
324 config.limits.clone(),
325 sandbox,
326 );
327
328 Ok(Self {
329 id,
330 config,
331 registry: HostRegistry::new(),
332 context,
333 bytecode_cache: Mutex::new(HashMap::new()),
334 })
335 }
336
337 pub fn id(&self) -> u64 {
339 self.id
340 }
341
342 pub fn config(&self) -> &EngineConfig {
344 &self.config
345 }
346
347 pub fn registry_mut(&mut self) -> &mut HostRegistry {
349 &mut self.registry
350 }
351
352 pub fn registry(&self) -> &HostRegistry {
354 &self.registry
355 }
356
357 pub fn context(&self) -> &ExecutionContext {
359 &self.context
360 }
361
362 pub fn execute(&self, source: &str) -> Result<Value> {
364 if self.context.is_cancelled() {
366 return Err(Error::Cancelled);
367 }
368
369 self.context.reset(self.config.limits.clone());
370
371 self.simulate_execution(source)
374 }
375
376 pub fn execute_bytecode(&self, bytecode: &[u8]) -> Result<Value> {
378 if self.context.is_cancelled() {
380 return Err(Error::Cancelled);
381 }
382
383 self.context.reset(self.config.limits.clone());
384
385 if bytecode.len() < 8 || &bytecode[0..4] != b"FZB\x00" {
387 return Err(Error::invalid_bytecode("invalid bytecode header"));
388 }
389
390 self.simulate_bytecode_execution(bytecode)
392 }
393
394 pub fn cancel(&self) {
396 self.context.cancel();
397 }
398
399 pub fn is_healthy(&self) -> bool {
401 !self.context.is_cancelled()
402 }
403
404 fn simulate_execution(&self, source: &str) -> Result<Value> {
407 self.context.check_timeout()?;
409
410 self.context.record_instructions(source.len() as u64 * 10)?;
412
413 let trimmed = source.trim();
415
416 if let Ok(n) = trimmed.parse::<i64>() {
418 return Ok(Value::Int(n));
419 }
420
421 if let Ok(f) = trimmed.parse::<f64>() {
422 return Ok(Value::Float(f));
423 }
424
425 if trimmed.starts_with('"') && trimmed.ends_with('"') && trimmed.len() > 1 {
427 return Ok(Value::String(trimmed[1..trimmed.len() - 1].to_string()));
428 }
429
430 if let Some(pos) = trimmed.find('+') {
432 let left = trimmed[..pos].trim();
433 let right = trimmed[pos + 1..].trim();
434 if let (Ok(l), Ok(r)) = (left.parse::<i64>(), right.parse::<i64>()) {
435 return Ok(Value::Int(l + r));
436 }
437 }
438
439 match trimmed {
441 "true" => return Ok(Value::Bool(true)),
442 "false" => return Ok(Value::Bool(false)),
443 "null" | "nil" => return Ok(Value::Null),
444 _ => {}
445 }
446
447 Ok(Value::Null)
449 }
450
451 fn simulate_bytecode_execution(&self, _bytecode: &[u8]) -> Result<Value> {
452 self.context.check_timeout()?;
453 self.context.record_instructions(100)?;
454 Ok(Value::Null)
455 }
456}
457
458impl std::fmt::Debug for Engine {
459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460 f.debug_struct("Engine")
461 .field("id", &self.id)
462 .field("config", &self.config)
463 .field("registry", &self.registry)
464 .finish()
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
473 fn test_engine_creation() {
474 let engine = Engine::new(EngineConfig::default()).unwrap();
475 assert!(engine.id() > 0);
476 assert!(engine.is_healthy());
477 }
478
479 #[test]
480 fn test_engine_execute_numbers() {
481 let engine = Engine::new(EngineConfig::default()).unwrap();
482
483 let result = engine.execute("42").unwrap();
484 assert_eq!(result, Value::Int(42));
485
486 let result = engine.execute("3.14").unwrap();
487 assert_eq!(result, Value::Float(3.14));
488 }
489
490 #[test]
491 fn test_engine_execute_addition() {
492 let engine = Engine::new(EngineConfig::default()).unwrap();
493
494 let result = engine.execute("1 + 2").unwrap();
495 assert_eq!(result, Value::Int(3));
496 }
497
498 #[test]
499 fn test_engine_execute_string() {
500 let engine = Engine::new(EngineConfig::default()).unwrap();
501
502 let result = engine.execute("\"hello\"").unwrap();
503 assert_eq!(result, Value::String("hello".into()));
504 }
505
506 #[test]
507 fn test_engine_execute_booleans() {
508 let engine = Engine::new(EngineConfig::default()).unwrap();
509
510 assert_eq!(engine.execute("true").unwrap(), Value::Bool(true));
511 assert_eq!(engine.execute("false").unwrap(), Value::Bool(false));
512 assert_eq!(engine.execute("null").unwrap(), Value::Null);
513 }
514
515 #[test]
516 fn test_engine_cancel() {
517 let engine = Engine::new(EngineConfig::default()).unwrap();
518 engine.cancel();
519
520 let result = engine.execute("42");
521 assert!(matches!(result, Err(Error::Cancelled)));
522 }
523
524 #[test]
525 fn test_host_registry() {
526 let mut registry = HostRegistry::new();
527
528 registry.register("test_fn", |args, _ctx| {
529 if args.is_empty() {
530 Ok(Value::Null)
531 } else {
532 Ok(args[0].clone())
533 }
534 });
535
536 registry.register_module("math", "add", |args, _ctx| {
537 let a = args.get(0).and_then(|v| v.as_int()).unwrap_or(0);
538 let b = args.get(1).and_then(|v| v.as_int()).unwrap_or(0);
539 Ok(Value::Int(a + b))
540 });
541
542 assert!(registry.get("test_fn").is_some());
543 assert!(registry.get_module("math", "add").is_some());
544 assert!(registry.get("nonexistent").is_none());
545 }
546
547 #[test]
548 fn test_execution_context_capabilities() {
549 use crate::Capability;
550
551 let caps = Capabilities::safe_defaults();
552 let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
553 let ctx = ExecutionContext::new(1, caps, Limits::default(), sandbox);
554
555 assert!(ctx.has_capability(Capability::TimeRead));
556 assert!(!ctx.has_capability(Capability::FsWrite));
557 }
558
559 #[test]
560 fn test_execution_context_custom_data() {
561 let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
562 let ctx = ExecutionContext::new(
563 1,
564 Capabilities::none(),
565 Limits::default(),
566 sandbox,
567 );
568
569 ctx.set_custom("key", Value::Int(42));
570 assert_eq!(ctx.get_custom("key"), Some(Value::Int(42)));
571 assert_eq!(ctx.get_custom("nonexistent"), None);
572 }
573
574 #[test]
575 fn test_engine_config_builder() {
576 let config = EngineConfig::new()
577 .with_limits(Limits::strict())
578 .with_capabilities(Capabilities::none())
579 .with_debug(true)
580 .with_metadata("name", "test-engine");
581
582 assert!(config.debug);
583 assert_eq!(config.metadata.get("name"), Some(&"test-engine".to_string()));
584 }
585}