1use std::path::Path;
67use std::sync::Arc;
68use std::time::Duration;
69
70use aegis_capability::{
71 CapabilitySet, CapabilitySetBuilder, ClockCapability, FilesystemCapability, LoggingCapability,
72 NetworkCapability,
73};
74use aegis_core::{
75 AegisEngine, EngineConfig, ExecutionError, ModuleLoader, ResourceLimits, Sandbox, SandboxConfig, SharedEngine, ValidatedModule,
76};
77use aegis_observe::{EventDispatcher, EventSubscriber};
78
79pub use aegis_capability;
81pub use aegis_core;
82pub use aegis_host;
83pub use aegis_observe;
84pub use aegis_resource;
85
86pub struct Aegis;
88
89impl Aegis {
90 pub fn builder() -> AegisBuilder {
92 AegisBuilder::new()
93 }
94
95 pub fn default() -> Result<AegisRuntime, AegisError> {
97 AegisBuilder::new().build()
98 }
99}
100
101pub struct AegisBuilder {
103 engine_config: EngineConfig,
104 resource_limits: ResourceLimits,
105 capabilities: CapabilitySetBuilder,
106 event_subscribers: Vec<Arc<dyn EventSubscriber>>,
107}
108
109impl AegisBuilder {
110 pub fn new() -> Self {
112 Self {
113 engine_config: EngineConfig::default(),
114 resource_limits: ResourceLimits::default(),
115 capabilities: CapabilitySetBuilder::new(),
116 event_subscribers: Vec::new(),
117 }
118 }
119
120 pub fn with_async_support(mut self, enabled: bool) -> Self {
124 self.engine_config.async_support = enabled;
125 self
126 }
127
128 pub fn with_component_model(mut self, enabled: bool) -> Self {
130 self.engine_config.component_model = enabled;
131 self
132 }
133
134 pub fn with_debug_info(mut self, enabled: bool) -> Self {
136 self.engine_config.debug_info = enabled;
137 self
138 }
139
140 pub fn with_memory_limit(mut self, bytes: usize) -> Self {
144 self.resource_limits.max_memory_bytes = bytes;
145 self
146 }
147
148 pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
150 self.resource_limits.initial_fuel = fuel;
151 self
152 }
153
154 pub fn with_timeout(mut self, timeout: Duration) -> Self {
156 self.resource_limits.timeout = timeout;
157 self
158 }
159
160 pub fn with_resource_limits(mut self, limits: ResourceLimits) -> Self {
162 self.resource_limits = limits;
163 self
164 }
165
166 pub fn with_filesystem(mut self, config: FilesystemCapability) -> Self {
170 self.capabilities = self.capabilities.with(config);
171 self
172 }
173
174 pub fn with_network(mut self, config: NetworkCapability) -> Self {
176 self.capabilities = self.capabilities.with(config);
177 self
178 }
179
180 pub fn with_logging(mut self, config: LoggingCapability) -> Self {
182 self.capabilities = self.capabilities.with(config);
183 self
184 }
185
186 pub fn with_clock(mut self, config: ClockCapability) -> Self {
188 self.capabilities = self.capabilities.with(config);
189 self
190 }
191
192 pub fn with_capability<C: aegis_capability::Capability + 'static>(mut self, cap: C) -> Self {
194 self.capabilities = self.capabilities.with(cap);
195 self
196 }
197
198 pub fn with_event_subscriber(mut self, subscriber: Arc<dyn EventSubscriber>) -> Self {
202 self.event_subscribers.push(subscriber);
203 self
204 }
205
206 pub fn build(self) -> Result<AegisRuntime, AegisError> {
208 let engine = AegisEngine::new(self.engine_config).map_err(AegisError::Engine)?;
209 let shared_engine = Arc::new(engine);
210
211 let capabilities = self.capabilities.build().map_err(AegisError::Capability)?;
212
213 let event_dispatcher = EventDispatcher::new();
214 for subscriber in self.event_subscribers {
215 event_dispatcher.subscribe(subscriber);
216 }
217
218 Ok(AegisRuntime {
219 engine: shared_engine,
220 default_limits: self.resource_limits,
221 default_capabilities: Arc::new(capabilities),
222 event_dispatcher: Arc::new(event_dispatcher),
223 })
224 }
225}
226
227impl Default for AegisBuilder {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233pub struct AegisRuntime {
235 engine: SharedEngine,
236 default_limits: ResourceLimits,
237 default_capabilities: Arc<CapabilitySet>,
238 event_dispatcher: Arc<EventDispatcher>,
239}
240
241impl AegisRuntime {
242 pub fn engine(&self) -> &SharedEngine {
244 &self.engine
245 }
246
247 pub fn default_limits(&self) -> &ResourceLimits {
249 &self.default_limits
250 }
251
252 pub fn default_capabilities(&self) -> &Arc<CapabilitySet> {
254 &self.default_capabilities
255 }
256
257 pub fn event_dispatcher(&self) -> &Arc<EventDispatcher> {
259 &self.event_dispatcher
260 }
261
262 pub fn loader(&self) -> ModuleLoader {
264 ModuleLoader::new(Arc::clone(&self.engine))
265 }
266
267 pub fn load_bytes(&self, bytes: &[u8]) -> Result<ValidatedModule, AegisError> {
269 self.loader().load_bytes(bytes).map_err(AegisError::Module)
270 }
271
272 pub fn load_file(&self, path: impl AsRef<Path>) -> Result<ValidatedModule, AegisError> {
274 self.loader()
275 .load_file(path.as_ref())
276 .map_err(AegisError::Module)
277 }
278
279 pub fn load_wat(&self, wat: &str) -> Result<ValidatedModule, AegisError> {
281 self.loader().load_wat(wat).map_err(AegisError::Module)
282 }
283
284 pub fn sandbox(&self) -> RuntimeSandboxBuilder<'_> {
286 RuntimeSandboxBuilder::new(self)
287 }
288
289 pub fn execute<R: wasmtime::WasmResults>(
293 &self,
294 module: &ValidatedModule,
295 function: &str,
296 ) -> Result<R, AegisError> {
297 let mut sandbox = self.sandbox().build()?;
298 sandbox.load_module(module).map_err(AegisError::Execution)?;
299 sandbox.call(function, ()).map_err(AegisError::Execution)
300 }
301}
302
303impl std::fmt::Debug for AegisRuntime {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 f.debug_struct("AegisRuntime")
306 .field("default_limits", &self.default_limits)
307 .finish()
308 }
309}
310
311pub struct RuntimeSandboxBuilder<'a> {
313 runtime: &'a AegisRuntime,
314 limits: Option<ResourceLimits>,
315 capabilities: Option<Arc<CapabilitySet>>,
316}
317
318impl<'a> RuntimeSandboxBuilder<'a> {
319 fn new(runtime: &'a AegisRuntime) -> Self {
320 Self {
321 runtime,
322 limits: None,
323 capabilities: None,
324 }
325 }
326
327 pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
329 self.limits = Some(limits);
330 self
331 }
332
333 pub fn with_memory_limit(mut self, bytes: usize) -> Self {
335 let mut limits = self.limits.take().unwrap_or_else(|| self.runtime.default_limits.clone());
336 limits.max_memory_bytes = bytes;
337 self.limits = Some(limits);
338 self
339 }
340
341 pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
343 let mut limits = self.limits.take().unwrap_or_else(|| self.runtime.default_limits.clone());
344 limits.initial_fuel = fuel;
345 self.limits = Some(limits);
346 self
347 }
348
349 pub fn with_timeout(mut self, timeout: Duration) -> Self {
351 let mut limits = self.limits.take().unwrap_or_else(|| self.runtime.default_limits.clone());
352 limits.timeout = timeout;
353 self.limits = Some(limits);
354 self
355 }
356
357 pub fn with_capabilities(mut self, capabilities: Arc<CapabilitySet>) -> Self {
359 self.capabilities = Some(capabilities);
360 self
361 }
362
363 pub fn build(self) -> Result<Sandbox<()>, AegisError> {
365 let limits = self.limits.unwrap_or_else(|| self.runtime.default_limits.clone());
366 let config = SandboxConfig::default().with_limits(limits);
367
368 Sandbox::new(Arc::clone(&self.runtime.engine), (), config).map_err(AegisError::Execution)
369 }
370
371 pub fn build_with_state<S: Send + 'static>(self, state: S) -> Result<Sandbox<S>, AegisError> {
373 let limits = self.limits.unwrap_or_else(|| self.runtime.default_limits.clone());
374 let config = SandboxConfig::default().with_limits(limits);
375
376 Sandbox::new(Arc::clone(&self.runtime.engine), state, config).map_err(AegisError::Execution)
377 }
378}
379
380#[derive(Debug, thiserror::Error)]
382pub enum AegisError {
383 #[error("Engine error: {0}")]
385 Engine(#[from] aegis_core::EngineError),
386
387 #[error("Module error: {0}")]
389 Module(#[from] aegis_core::ModuleError),
390
391 #[error("Execution error: {0}")]
393 Execution(#[from] ExecutionError),
394
395 #[error("Capability error: {0}")]
397 Capability(#[from] aegis_capability::CapabilityError),
398}
399
400pub mod prelude {
402 pub use crate::{Aegis, AegisBuilder, AegisError, AegisRuntime};
404
405 pub use aegis_core::{
407 AegisEngine, EngineConfig, ModuleLoader, ResourceLimits, Sandbox, SandboxBuilder,
408 SandboxConfig, ValidatedModule,
409 };
410
411 pub use aegis_capability::{
413 Capability, CapabilityId, CapabilitySet, ClockCapability, FilesystemCapability,
414 LoggingCapability, NetworkCapability, PathPermission, PermissionResult,
415 };
416
417 pub use aegis_resource::{EpochConfig, EpochManager, FuelConfig, FuelManager};
419
420 pub use aegis_observe::{
422 EventDispatcher, EventSubscriber, ExecutionOutcome, ExecutionReport, MetricsCollector,
423 SandboxEvent,
424 };
425
426 pub use std::sync::Arc;
428 pub use std::time::Duration;
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 #[test]
436 fn test_aegis_builder() {
437 let runtime = Aegis::builder()
438 .with_memory_limit(32 * 1024 * 1024)
439 .with_fuel_limit(100_000)
440 .with_timeout(Duration::from_secs(5))
441 .build()
442 .unwrap();
443
444 assert_eq!(runtime.default_limits().max_memory_bytes, 32 * 1024 * 1024);
445 assert_eq!(runtime.default_limits().initial_fuel, 100_000);
446 }
447
448 #[test]
449 fn test_load_and_execute() {
450 let runtime = Aegis::builder().build().unwrap();
451
452 let module = runtime
453 .load_wat(
454 r#"
455 (module
456 (func (export "answer") (result i32)
457 i32.const 42
458 )
459 )
460 "#,
461 )
462 .unwrap();
463
464 let mut sandbox = runtime.sandbox().build().unwrap();
465 sandbox.load_module(&module).unwrap();
466
467 let result: i32 = sandbox.call("answer", ()).unwrap();
468 assert_eq!(result, 42);
469 }
470
471 #[test]
472 fn test_sandbox_builder_overrides() {
473 let runtime = Aegis::builder()
474 .with_fuel_limit(1_000_000)
475 .build()
476 .unwrap();
477
478 let sandbox = runtime
479 .sandbox()
480 .with_fuel_limit(500_000)
481 .with_memory_limit(16 * 1024 * 1024)
482 .build()
483 .unwrap();
484
485 assert_eq!(sandbox.remaining_fuel(), Some(500_000));
487 }
488
489 #[test]
490 fn test_prelude_imports() {
491 use crate::prelude::*;
492
493 let _runtime = Aegis::builder().build().unwrap();
494 }
495}