hyperlight_js/sandbox/
proto_js_sandbox.rs1use std::collections::HashMap;
17use std::fmt::Debug;
18use std::time::SystemTime;
19
20use anyhow::Context;
21use hyperlight_host::sandbox::SandboxConfiguration;
22use hyperlight_host::{new_error, GuestBinary, Result, UninitializedSandbox};
23use serde::de::DeserializeOwned;
24use serde::Serialize;
25use tracing::{instrument, Level};
26
27use super::js_sandbox::JSSandbox;
28use super::sandbox_builder::SandboxBuilder;
29use crate::sandbox::host_fn::{Function, HostModule};
30use crate::sandbox::metrics::SandboxMetricsGuard;
31use crate::HostPrintFn;
32
33pub struct ProtoJSSandbox {
36 inner: UninitializedSandbox,
37 host_modules: HashMap<String, HostModule>,
38 _metric_guard: SandboxMetricsGuard<ProtoJSSandbox>,
40}
41
42impl ProtoJSSandbox {
43 #[instrument(err(Debug), skip_all, level=Level::INFO, fields(version= env!("CARGO_PKG_VERSION")))]
44 pub(super) fn new(
45 guest_binary: GuestBinary,
46 cfg: Option<SandboxConfiguration>,
47 host_print_writer: Option<HostPrintFn>,
48 ) -> Result<Self> {
49 let mut usbox: UninitializedSandbox = UninitializedSandbox::new(guest_binary, cfg)?;
50
51 if let Some(host_print_writer) = host_print_writer {
53 usbox.register_print(host_print_writer)?;
54 }
55
56 fn current_time_micros() -> hyperlight_host::Result<u64> {
58 Ok(SystemTime::now()
59 .duration_since(SystemTime::UNIX_EPOCH)
60 .with_context(|| "Unable to get duration since epoch")
61 .map(|d| d.as_micros() as u64)?)
62 }
63
64 usbox.register("CurrentTimeMicros", current_time_micros)?;
65
66 Ok(Self {
67 inner: usbox,
68 host_modules: HashMap::new(),
69 _metric_guard: SandboxMetricsGuard::new(),
70 })
71 }
72
73 #[instrument(err(Debug), skip_all, level=Level::INFO)]
77 pub fn set_module_loader<Fs: crate::resolver::FileSystem + Clone + 'static>(
78 mut self,
79 file_system: Fs,
80 ) -> Result<Self> {
81 use std::path::PathBuf;
82
83 use oxc_resolver::{ResolveOptions, ResolverGeneric};
84
85 let resolver = ResolverGeneric::new_with_file_system(
86 file_system.clone(),
87 ResolveOptions {
88 extensions: vec![".js".into(), ".mjs".into()],
89 condition_names: vec!["import".into(), "module".into()],
90 ..Default::default()
91 },
92 );
93
94 self.inner.register(
95 "ResolveModule",
96 move |base: String, specifier: String| -> hyperlight_host::Result<String> {
97 tracing::debug!(
98 base = %base,
99 specifier = %specifier,
100 "Resolving module"
101 );
102
103 let resolved = resolver.resolve(&base, &specifier).map_err(|e| {
104 new_error!(
105 "Failed to resolve module '{}' from '{}': {:?}",
106 specifier,
107 base,
108 e
109 )
110 })?;
111
112 Ok(resolved.path().to_string_lossy().to_string())
113 },
114 )?;
115
116 self.inner.register(
117 "LoadModule",
118 move |path: String| -> hyperlight_host::Result<String> {
119 tracing::debug!(path = %path, "Loading module");
120 let path_buf = PathBuf::from(&path);
121 let source = file_system
122 .read_to_string(&path_buf)
123 .map_err(|e| new_error!("Failed to read module '{}': {}", path, e))?;
124
125 Ok(source)
126 },
127 )?;
128
129 Ok(self)
130 }
131
132 #[instrument(err(Debug), skip(self), level=Level::INFO)]
134 pub fn load_runtime(mut self) -> Result<JSSandbox> {
135 let host_modules = self.host_modules;
136
137 let host_modules_json = serde_json::to_string(&host_modules)?;
138
139 self.inner.register(
140 "CallHostJsFunction",
141 move |module_name: String, func_name: String, args: String| -> Result<String> {
142 let module = host_modules
143 .get(&module_name)
144 .ok_or_else(|| new_error!("Host module '{}' not found", module_name))?;
145 let func = module.get(&func_name).ok_or_else(|| {
146 new_error!(
147 "Host function '{}' not found in module '{}'",
148 func_name,
149 module_name
150 )
151 })?;
152 func(args)
153 },
154 )?;
155
156 let mut multi_use_sandbox = self.inner.evolve()?;
157
158 let _: () = multi_use_sandbox.call("RegisterHostModules", host_modules_json)?;
159
160 JSSandbox::new(multi_use_sandbox)
161 }
162
163 #[instrument(skip(self), level=Level::INFO)]
196 pub fn host_module(&mut self, name: impl Into<String> + Debug) -> &mut HostModule {
197 self.host_modules.entry(name.into()).or_default()
198 }
199
200 #[instrument(err(Debug), skip(self, func), level=Level::INFO)]
206 pub fn register<Output: Serialize, Args: DeserializeOwned>(
207 &mut self,
208 module: impl Into<String> + Debug,
209 name: impl Into<String> + Debug,
210 func: impl Function<Output, Args> + Send + Sync + 'static,
211 ) -> Result<()> {
212 self.host_module(module).register(name, func);
213 Ok(())
214 }
215
216 #[instrument(err(Debug), skip(self, func), level=Level::INFO)]
227 pub fn register_raw(
228 &mut self,
229 module: impl Into<String> + Debug,
230 name: impl Into<String> + Debug,
231 func: impl Fn(String) -> Result<String> + Send + Sync + 'static,
232 ) -> Result<()> {
233 self.host_module(module).register_raw(name, func);
234 Ok(())
235 }
236}
237
238impl std::fmt::Debug for ProtoJSSandbox {
239 #[instrument(skip_all, level=Level::TRACE)]
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 f.debug_struct("ProtoJsSandbox").finish()
242 }
243}
244
245impl Default for ProtoJSSandbox {
246 #[instrument(skip_all, level=Level::INFO)]
247 fn default() -> Self {
248 #[allow(clippy::unwrap_used)]
251 SandboxBuilder::new().build().unwrap()
252 }
253}