hyperlight_js/sandbox/
js_sandbox.rs1use std::collections::HashMap;
2use std::fmt::Debug;
3
4use hyperlight_host::sandbox::snapshot::Snapshot;
5use hyperlight_host::{new_error, MultiUseSandbox, Result};
6use tracing::{instrument, Level};
7
8use super::loaded_js_sandbox::LoadedJSSandbox;
9use crate::sandbox::metrics::SandboxMetricsGuard;
10use crate::Script;
11
12pub struct JSSandbox {
14 pub(super) inner: MultiUseSandbox,
15 handlers: HashMap<String, Script>,
16 snapshot: Snapshot,
19 _metric_guard: SandboxMetricsGuard<JSSandbox>,
21}
22
23impl JSSandbox {
24 #[instrument(err(Debug), skip(inner), level=Level::INFO)]
25 pub(super) fn new(mut inner: MultiUseSandbox) -> Result<Self> {
26 let snapshot = inner.snapshot()?;
27 Ok(Self {
28 inner,
29 handlers: HashMap::new(),
30 snapshot,
31 _metric_guard: SandboxMetricsGuard::new(),
32 })
33 }
34
35 pub(crate) fn from_loaded(mut loaded: MultiUseSandbox, snapshot: Snapshot) -> Result<Self> {
37 loaded.restore(&snapshot)?;
38 Ok(Self {
39 inner: loaded,
40 handlers: HashMap::new(),
41 snapshot,
42 _metric_guard: SandboxMetricsGuard::new(),
43 })
44 }
45
46 #[instrument(err(Debug), skip(self, script), level=Level::DEBUG)]
49 pub fn add_handler<F>(&mut self, function_name: F, script: Script) -> Result<()>
50 where
51 F: Into<String> + std::fmt::Debug,
52 {
53 let function_name = function_name.into();
54 if function_name.is_empty() {
55 return Err(new_error!("Handler name must not be empty"));
56 }
57 if self.handlers.contains_key(&function_name) {
58 return Err(new_error!(
59 "Handler already exists for function name: {}",
60 function_name
61 ));
62 }
63
64 self.handlers.insert(function_name, script);
65 Ok(())
66 }
67
68 #[instrument(err(Debug), skip(self), level=Level::DEBUG)]
70 pub fn remove_handler(&mut self, function_name: &str) -> Result<()> {
71 if function_name.is_empty() {
72 return Err(new_error!("Handler name must not be empty"));
73 }
74 match self.handlers.remove(function_name) {
75 Some(_) => Ok(()),
76 None => Err(new_error!(
77 "Handler does not exist for function name: {}",
78 function_name
79 )),
80 }
81 }
82
83 #[instrument(skip_all, level=Level::TRACE)]
85 pub fn clear_handlers(&mut self) {
86 self.handlers.clear();
87 }
88
89 pub fn poisoned(&self) -> bool {
96 self.inner.poisoned()
97 }
98
99 #[cfg(test)]
100 fn get_number_of_handlers(&self) -> usize {
101 self.handlers.len()
102 }
103
104 #[instrument(err(Debug), skip_all, level=Level::TRACE)]
106 pub fn get_loaded_sandbox(mut self) -> Result<LoadedJSSandbox> {
107 if self.handlers.is_empty() {
108 return Err(new_error!("No handlers have been added to the sandbox"));
109 }
110
111 let handlers = self.handlers.clone();
112 for (function_name, script) in handlers {
113 let content = script.content().to_owned();
114
115 let path = script
116 .base_path()
117 .map(|p| p.to_string_lossy().to_string())
118 .unwrap_or_default();
119 self.inner
120 .call::<()>("register_handler", (function_name, content, path))?;
121 }
122
123 LoadedJSSandbox::new(self.inner, self.snapshot)
124 }
125 #[cfg(feature = "crashdump")]
161 pub fn generate_crashdump(&self) -> Result<()> {
162 self.inner.generate_crashdump()
163 }
164}
165
166impl Debug for JSSandbox {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.debug_struct("JSSandbox")
169 .field("handlers", &self.handlers)
170 .finish()
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::SandboxBuilder;
178
179 #[test]
180 fn test_add_handler() {
181 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
182 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
183 sandbox.add_handler("handler1", "script1".into()).unwrap();
184 sandbox.add_handler("handler2", "script2".into()).unwrap();
185
186 assert_eq!(sandbox.get_number_of_handlers(), 2);
187 }
188
189 #[test]
190 fn test_remove_handler() {
191 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
192 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
193 sandbox.add_handler("handler1", "script1".into()).unwrap();
194 sandbox.add_handler("handler2", "script2".into()).unwrap();
195
196 sandbox.remove_handler("handler1").unwrap();
197
198 assert_eq!(sandbox.get_number_of_handlers(), 1);
199 }
200
201 #[test]
202 fn test_clear_handlers() {
203 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
204 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
205 sandbox.add_handler("handler1", "script1".into()).unwrap();
206 sandbox.add_handler("handler2", "script2".into()).unwrap();
207
208 sandbox.clear_handlers();
209
210 assert_eq!(sandbox.get_number_of_handlers(), 0);
211 }
212
213 #[test]
214 fn test_get_loaded_sandbox() {
215 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
216 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
217 sandbox
218 .add_handler(
219 "handler1",
220 Script::from_content(
221 r#"function handler(event) {
222 event.request.uri = "/redirected.html";
223 return event
224 }"#,
225 ),
226 )
227 .unwrap();
228
229 let res = sandbox.get_loaded_sandbox();
230 assert!(res.is_ok());
231 }
232}