use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use aegis_capability::{
CapabilitySet, CapabilitySetBuilder, ClockCapability, FilesystemCapability, LoggingCapability,
NetworkCapability,
};
use aegis_core::{
AegisEngine, EngineConfig, ExecutionError, ModuleLoader, ResourceLimits, Sandbox, SandboxConfig, SharedEngine, ValidatedModule,
};
use aegis_observe::{EventDispatcher, EventSubscriber};
pub use aegis_capability;
pub use aegis_core;
pub use aegis_host;
pub use aegis_observe;
pub use aegis_resource;
pub struct Aegis;
impl Aegis {
pub fn builder() -> AegisBuilder {
AegisBuilder::new()
}
pub fn default() -> Result<AegisRuntime, AegisError> {
AegisBuilder::new().build()
}
}
pub struct AegisBuilder {
engine_config: EngineConfig,
resource_limits: ResourceLimits,
capabilities: CapabilitySetBuilder,
event_subscribers: Vec<Arc<dyn EventSubscriber>>,
}
impl AegisBuilder {
pub fn new() -> Self {
Self {
engine_config: EngineConfig::default(),
resource_limits: ResourceLimits::default(),
capabilities: CapabilitySetBuilder::new(),
event_subscribers: Vec::new(),
}
}
pub fn with_async_support(mut self, enabled: bool) -> Self {
self.engine_config.async_support = enabled;
self
}
pub fn with_component_model(mut self, enabled: bool) -> Self {
self.engine_config.component_model = enabled;
self
}
pub fn with_debug_info(mut self, enabled: bool) -> Self {
self.engine_config.debug_info = enabled;
self
}
pub fn with_memory_limit(mut self, bytes: usize) -> Self {
self.resource_limits.max_memory_bytes = bytes;
self
}
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
self.resource_limits.initial_fuel = fuel;
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.resource_limits.timeout = timeout;
self
}
pub fn with_resource_limits(mut self, limits: ResourceLimits) -> Self {
self.resource_limits = limits;
self
}
pub fn with_filesystem(mut self, config: FilesystemCapability) -> Self {
self.capabilities = self.capabilities.with(config);
self
}
pub fn with_network(mut self, config: NetworkCapability) -> Self {
self.capabilities = self.capabilities.with(config);
self
}
pub fn with_logging(mut self, config: LoggingCapability) -> Self {
self.capabilities = self.capabilities.with(config);
self
}
pub fn with_clock(mut self, config: ClockCapability) -> Self {
self.capabilities = self.capabilities.with(config);
self
}
pub fn with_capability<C: aegis_capability::Capability + 'static>(mut self, cap: C) -> Self {
self.capabilities = self.capabilities.with(cap);
self
}
pub fn with_event_subscriber(mut self, subscriber: Arc<dyn EventSubscriber>) -> Self {
self.event_subscribers.push(subscriber);
self
}
pub fn build(self) -> Result<AegisRuntime, AegisError> {
let engine = AegisEngine::new(self.engine_config).map_err(AegisError::Engine)?;
let shared_engine = Arc::new(engine);
let capabilities = self.capabilities.build().map_err(AegisError::Capability)?;
let event_dispatcher = EventDispatcher::new();
for subscriber in self.event_subscribers {
event_dispatcher.subscribe(subscriber);
}
Ok(AegisRuntime {
engine: shared_engine,
default_limits: self.resource_limits,
default_capabilities: Arc::new(capabilities),
event_dispatcher: Arc::new(event_dispatcher),
})
}
}
impl Default for AegisBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct AegisRuntime {
engine: SharedEngine,
default_limits: ResourceLimits,
default_capabilities: Arc<CapabilitySet>,
event_dispatcher: Arc<EventDispatcher>,
}
impl AegisRuntime {
pub fn engine(&self) -> &SharedEngine {
&self.engine
}
pub fn default_limits(&self) -> &ResourceLimits {
&self.default_limits
}
pub fn default_capabilities(&self) -> &Arc<CapabilitySet> {
&self.default_capabilities
}
pub fn event_dispatcher(&self) -> &Arc<EventDispatcher> {
&self.event_dispatcher
}
pub fn loader(&self) -> ModuleLoader {
ModuleLoader::new(Arc::clone(&self.engine))
}
pub fn load_bytes(&self, bytes: &[u8]) -> Result<ValidatedModule, AegisError> {
self.loader().load_bytes(bytes).map_err(AegisError::Module)
}
pub fn load_file(&self, path: impl AsRef<Path>) -> Result<ValidatedModule, AegisError> {
self.loader()
.load_file(path.as_ref())
.map_err(AegisError::Module)
}
pub fn load_wat(&self, wat: &str) -> Result<ValidatedModule, AegisError> {
self.loader().load_wat(wat).map_err(AegisError::Module)
}
pub fn sandbox(&self) -> RuntimeSandboxBuilder<'_> {
RuntimeSandboxBuilder::new(self)
}
pub fn execute<R: wasmtime::WasmResults>(
&self,
module: &ValidatedModule,
function: &str,
) -> Result<R, AegisError> {
let mut sandbox = self.sandbox().build()?;
sandbox.load_module(module).map_err(AegisError::Execution)?;
sandbox.call(function, ()).map_err(AegisError::Execution)
}
}
impl std::fmt::Debug for AegisRuntime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AegisRuntime")
.field("default_limits", &self.default_limits)
.finish()
}
}
pub struct RuntimeSandboxBuilder<'a> {
runtime: &'a AegisRuntime,
limits: Option<ResourceLimits>,
capabilities: Option<Arc<CapabilitySet>>,
}
impl<'a> RuntimeSandboxBuilder<'a> {
fn new(runtime: &'a AegisRuntime) -> Self {
Self {
runtime,
limits: None,
capabilities: None,
}
}
pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
self.limits = Some(limits);
self
}
pub fn with_memory_limit(mut self, bytes: usize) -> Self {
let mut limits = self.limits.take().unwrap_or_else(|| self.runtime.default_limits.clone());
limits.max_memory_bytes = bytes;
self.limits = Some(limits);
self
}
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
let mut limits = self.limits.take().unwrap_or_else(|| self.runtime.default_limits.clone());
limits.initial_fuel = fuel;
self.limits = Some(limits);
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
let mut limits = self.limits.take().unwrap_or_else(|| self.runtime.default_limits.clone());
limits.timeout = timeout;
self.limits = Some(limits);
self
}
pub fn with_capabilities(mut self, capabilities: Arc<CapabilitySet>) -> Self {
self.capabilities = Some(capabilities);
self
}
pub fn build(self) -> Result<Sandbox<()>, AegisError> {
let limits = self.limits.unwrap_or_else(|| self.runtime.default_limits.clone());
let config = SandboxConfig::default().with_limits(limits);
Sandbox::new(Arc::clone(&self.runtime.engine), (), config).map_err(AegisError::Execution)
}
pub fn build_with_state<S: Send + 'static>(self, state: S) -> Result<Sandbox<S>, AegisError> {
let limits = self.limits.unwrap_or_else(|| self.runtime.default_limits.clone());
let config = SandboxConfig::default().with_limits(limits);
Sandbox::new(Arc::clone(&self.runtime.engine), state, config).map_err(AegisError::Execution)
}
}
#[derive(Debug, thiserror::Error)]
pub enum AegisError {
#[error("Engine error: {0}")]
Engine(#[from] aegis_core::EngineError),
#[error("Module error: {0}")]
Module(#[from] aegis_core::ModuleError),
#[error("Execution error: {0}")]
Execution(#[from] ExecutionError),
#[error("Capability error: {0}")]
Capability(#[from] aegis_capability::CapabilityError),
}
pub mod prelude {
pub use crate::{Aegis, AegisBuilder, AegisError, AegisRuntime};
pub use aegis_core::{
AegisEngine, EngineConfig, ModuleLoader, ResourceLimits, Sandbox, SandboxBuilder,
SandboxConfig, ValidatedModule,
};
pub use aegis_capability::{
Capability, CapabilityId, CapabilitySet, ClockCapability, FilesystemCapability,
LoggingCapability, NetworkCapability, PathPermission, PermissionResult,
};
pub use aegis_resource::{EpochConfig, EpochManager, FuelConfig, FuelManager};
pub use aegis_observe::{
EventDispatcher, EventSubscriber, ExecutionOutcome, ExecutionReport, MetricsCollector,
SandboxEvent,
};
pub use std::sync::Arc;
pub use std::time::Duration;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aegis_builder() {
let runtime = Aegis::builder()
.with_memory_limit(32 * 1024 * 1024)
.with_fuel_limit(100_000)
.with_timeout(Duration::from_secs(5))
.build()
.unwrap();
assert_eq!(runtime.default_limits().max_memory_bytes, 32 * 1024 * 1024);
assert_eq!(runtime.default_limits().initial_fuel, 100_000);
}
#[test]
fn test_load_and_execute() {
let runtime = Aegis::builder().build().unwrap();
let module = runtime
.load_wat(
r#"
(module
(func (export "answer") (result i32)
i32.const 42
)
)
"#,
)
.unwrap();
let mut sandbox = runtime.sandbox().build().unwrap();
sandbox.load_module(&module).unwrap();
let result: i32 = sandbox.call("answer", ()).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_sandbox_builder_overrides() {
let runtime = Aegis::builder()
.with_fuel_limit(1_000_000)
.build()
.unwrap();
let sandbox = runtime
.sandbox()
.with_fuel_limit(500_000)
.with_memory_limit(16 * 1024 * 1024)
.build()
.unwrap();
assert_eq!(sandbox.remaining_fuel(), Some(500_000));
}
#[test]
fn test_prelude_imports() {
use crate::prelude::*;
let _runtime = Aegis::builder().build().unwrap();
}
}