Skip to main content

hyperlight_wasm/sandbox/
proto_wasm_sandbox.rs

1/*
2Copyright 2024 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::collections::HashMap;
18
19use hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition;
20use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
21use hyperlight_host::func::{HostFunction, ParameterTuple, Registerable, SupportedReturnType};
22use hyperlight_host::sandbox::config::SandboxConfiguration;
23use hyperlight_host::{GuestBinary, Result, UninitializedSandbox, new_error};
24
25use super::metrics::{METRIC_ACTIVE_PROTO_WASM_SANDBOXES, METRIC_TOTAL_PROTO_WASM_SANDBOXES};
26use super::sandbox_builder::SandboxBuilder;
27use super::wasm_sandbox::WasmSandbox;
28use crate::build_info::BuildInfo;
29
30/// A Hyperlight Sandbox with no Wasm run time loaded and no guest module code loaded.
31/// This is used to register new host functions that can be called by guest code.
32///
33/// Once all guest functions have been loaded you can call `load_runtime to load the Wasm runtime and get a `WasmSandbox`.
34///
35/// With that `WasmSandbox` you can load a Wasm module through the `load_module` method and get a `LoadedWasmSandbox` which can then execute functions defined in the Wasm module.
36pub struct ProtoWasmSandbox {
37    pub(super) inner: Option<UninitializedSandbox>,
38    host_function_definitions: HashMap<String, HostFunctionDefinition>,
39}
40
41impl Registerable for ProtoWasmSandbox {
42    fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
43        &mut self,
44        name: &str,
45        hf: impl Into<HostFunction<Output, Args>>,
46    ) -> Result<()> {
47        self.inner
48            .as_mut()
49            .ok_or(new_error!("inner sandbox was none"))
50            .and_then(|sb| sb.register(name, hf))?;
51
52        // Track the host function definition for pushing to guest at load time.
53        // matching hyperlight-core's FunctionRegistry behavior.
54        self.host_function_definitions.insert(
55            name.to_string(),
56            HostFunctionDefinition {
57                function_name: name.to_string(),
58                parameter_types: Some(Args::TYPE.to_vec()),
59                return_type: Output::TYPE,
60            },
61        );
62        Ok(())
63    }
64}
65
66impl ProtoWasmSandbox {
67    /// Create a new sandbox complete with no Wasm runtime and no end-user
68    /// code loaded. Since there's no user code or runtime loaded, the returned
69    /// sandbox cannot execute anything.
70    ///
71    /// The returned `WasmSandbox` can be cached and later used to load a Wasm module.
72    ///
73    /// If you'd like to restrict the (wall-clock) execution time for any guest function called in the
74    /// `LoadedWasmSandbox` returned from the `load_module` method on the `WasmSandbox`, you can
75    /// use the `max_execution_time` and `max_wait_for_cancellation`
76    /// fields in the `SandboxConfiguration` struct.
77    pub(super) fn new(
78        cfg: Option<SandboxConfiguration>,
79        guest_binary: GuestBinary,
80    ) -> Result<Self> {
81        BuildInfo::log();
82        let inner = UninitializedSandbox::new(guest_binary, cfg)?;
83        metrics::gauge!(METRIC_ACTIVE_PROTO_WASM_SANDBOXES).increment(1);
84        metrics::counter!(METRIC_TOTAL_PROTO_WASM_SANDBOXES).increment(1);
85
86        let host_function_definitions = HashMap::new();
87        Ok(Self {
88            inner: Some(inner),
89            host_function_definitions,
90        })
91    }
92
93    /// Load the Wasm runtime into the sandbox and return a `WasmSandbox`
94    /// that can be cached and used to load a Wasm module resulting in a `LoadedWasmSandbox`
95    ///
96    /// The `LoadedWasmSandbox` can be reverted to a `WasmSandbox` by calling the `unload_runtime` method.
97    /// The returned `WasmSandbox` can be then be cached and used to load a different Wasm module.
98    ///
99    pub fn load_runtime(mut self) -> Result<WasmSandbox> {
100        // Serialize host function definitions to push to the guest during InitWasmRuntime
101        let host_function_definitions = HostFunctionDetails {
102            host_functions: Some(
103                std::mem::take(&mut self.host_function_definitions)
104                    .into_values()
105                    .collect(),
106            ),
107        };
108
109        let host_function_definitions_bytes: Vec<u8> = (&host_function_definitions)
110            .try_into()
111            .map_err(|e| new_error!("Failed to serialize host function details: {:?}", e))?;
112
113        let mut sandbox = match self.inner.take() {
114            Some(s) => s.evolve()?,
115            None => return Err(new_error!("No inner sandbox found.")),
116        };
117
118        // Pass host function definitions to the guest as a parameter
119        let res: i32 = sandbox.call("InitWasmRuntime", (host_function_definitions_bytes,))?;
120        if res != 0 {
121            return Err(new_error!(
122                "InitWasmRuntime Failed  with error code {:?}",
123                res
124            ));
125        }
126
127        WasmSandbox::new(sandbox)
128    }
129
130    /// Register the given host function `host_func` with `self` under
131    /// the given `name`. Return `Ok` if the registration succeeded, and a
132    /// descriptive `Err` otherwise.
133    pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
134        &mut self,
135        name: impl AsRef<str>,
136        host_func: impl Into<HostFunction<Output, Args>>,
137    ) -> Result<()> {
138        self.register_host_function(name.as_ref(), host_func)
139    }
140
141    /// Register the given host printing function `print_func` with `self`.
142    /// Return `Ok` if the registration succeeded, and a descriptive `Err` otherwise.
143    pub fn register_print(
144        &mut self,
145        print_func: impl Into<HostFunction<i32, (String,)>>,
146    ) -> Result<()> {
147        // This method only replaces the implementation, not the definition.
148        self.inner
149            .as_mut()
150            .ok_or(new_error!("inner sandbox was none"))?
151            .register_print(print_func)
152    }
153}
154
155impl std::fmt::Debug for ProtoWasmSandbox {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        f.debug_struct("ProtoWasmSandbox").finish()
158    }
159}
160
161impl Drop for ProtoWasmSandbox {
162    fn drop(&mut self) {
163        metrics::gauge!(METRIC_ACTIVE_PROTO_WASM_SANDBOXES).decrement(1);
164    }
165}
166
167/// Create a new sandbox with a default configuration with a Wasm runtime but no end-user
168/// code loaded. Since there's no user code loaded, the returned
169/// sandbox cannot execute anything.
170///
171/// It can be used to register new host functions that can be called by guest code.
172///
173/// Once all guest functions have been loaded you can call `load_runtime` to load the Wasm runtme and get a `WasmSandbox`.
174///
175/// The default configuration is as follows:
176///
177/// * The Hyperlight default Host Print implementation is used.
178/// * The Sandbox will attempt to run in a hypervisor and will fail if no Hypervisor is available.
179/// * The Sandbox will have  a stack size of 8K and a heap size of 64K
180/// * All other Hyperlight configuration values will be set to their defaults.
181///
182/// This will result in a memory footprint for the VM backing the Sandbox of approximately 434K
183///
184/// Use the `load_runtime` method to
185/// load the Wasm runtime and convert the returned `ProtoWasmSandbox`
186/// into a `WasmSandbox` that can have a Wasm module loaded returning a `LoadedWasmSandbox` that can be used to call Wasm functions in the guest.
187impl Default for ProtoWasmSandbox {
188    fn default() -> Self {
189        SandboxBuilder::new().build().unwrap()
190    }
191}