Skip to main content

hyperlight_host/sandbox/
host_funcs.rs

1/*
2Copyright 2025  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;
18use std::io::{IsTerminal, Write};
19
20use hyperlight_common::flatbuffer_wrappers::function_types::{
21    ParameterType, ParameterValue, ReturnType, ReturnValue,
22};
23use hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition;
24use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
25use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
26use tracing::{Span, instrument};
27
28use crate::HyperlightError::HostFunctionNotFound;
29use crate::Result;
30use crate::func::host_functions::TypeErasedHostFunction;
31
32#[derive(Default)]
33/// A Wrapper around details of functions exposed by the Host
34pub struct FunctionRegistry {
35    functions_map: HashMap<String, FunctionEntry>,
36}
37
38/// A collection of host functions that can be supplied to a sandbox
39/// constructor (e.g. [`crate::MultiUseSandbox::from_snapshot`]) to
40/// expose host-side functionality to the guest.
41///
42/// Use [`HostFunctions::default`] to start with the standard
43/// `HostPrint` function pre-registered (matches the registry that the
44/// regular `UninitializedSandbox` → `evolve()` path constructs), or
45/// [`HostFunctions::empty`] to start with an empty registry.
46///
47/// Add additional host functions via the
48/// [`crate::func::Registerable`] trait, just as you would on an
49/// `UninitializedSandbox`.
50///
51/// ```no_run
52/// # use hyperlight_host::{HostFunctions, Result};
53/// # use hyperlight_host::func::Registerable;
54/// # fn example() -> Result<()> {
55/// // Default: HostPrint already registered.
56/// let mut funcs = HostFunctions::default();
57/// funcs.register_host_function("Add", |a: i32, b: i32| Ok(a + b))?;
58/// # Ok(())
59/// # }
60/// ```
61pub struct HostFunctions(FunctionRegistry);
62
63impl HostFunctions {
64    /// Create an empty `HostFunctions` with no host functions
65    /// registered.
66    ///
67    /// Most callers want [`HostFunctions::default`] instead, which
68    /// pre-registers the standard `HostPrint` function. An empty
69    /// registry will fail snapshot validation against any snapshot
70    /// that captured `HostPrint`, and any guest code that tries to
71    /// `printf` into an empty registry will get an EIO from
72    /// `write(2)`.
73    pub fn empty() -> Self {
74        Self(FunctionRegistry::default())
75    }
76
77    /// Consume this `HostFunctions` and return the inner registry.
78    pub(crate) fn into_inner(self) -> FunctionRegistry {
79        self.0
80    }
81
82    /// Borrow the inner registry mutably.
83    pub(crate) fn inner_mut(&mut self) -> &mut FunctionRegistry {
84        &mut self.0
85    }
86
87    /// Borrow the inner registry immutably.
88    pub(crate) fn inner(&self) -> &FunctionRegistry {
89        &self.0
90    }
91}
92
93impl Default for HostFunctions {
94    /// Create a `HostFunctions` pre-populated with the standard
95    /// `HostPrint` function (writes UTF-8 strings to the host's
96    /// stdout in green).
97    ///
98    /// This matches the default registry installed by
99    /// `UninitializedSandbox::new()`, so a snapshot taken from a
100    /// regular sandbox can be loaded with
101    /// `MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)`
102    /// without registering anything else.
103    ///
104    /// Use [`HostFunctions::empty`] for an empty registry.
105    fn default() -> Self {
106        Self(FunctionRegistry::with_default_host_print())
107    }
108}
109
110impl From<&FunctionRegistry> for HostFunctionDetails {
111    fn from(registry: &FunctionRegistry) -> Self {
112        let host_functions = registry
113            .functions_map
114            .iter()
115            .map(|(name, entry)| HostFunctionDefinition {
116                function_name: name.clone(),
117                parameter_types: Some(entry.parameter_types.to_vec()),
118                return_type: entry.return_type,
119            })
120            .collect();
121
122        HostFunctionDetails {
123            host_functions: Some(host_functions),
124        }
125    }
126}
127
128pub struct FunctionEntry {
129    pub function: TypeErasedHostFunction,
130    pub parameter_types: &'static [ParameterType],
131    pub return_type: ReturnType,
132}
133
134impl FunctionRegistry {
135    /// Register a host function with the sandbox.
136    #[instrument(skip_all, parent = Span::current(), level = "Trace")]
137    pub(crate) fn register_host_function(&mut self, name: String, func: FunctionEntry) {
138        self.functions_map.insert(name, func);
139    }
140
141    /// Return the registered signature for `name`.
142    pub(crate) fn function_signature(
143        &self,
144        name: &str,
145    ) -> Option<(&'static [ParameterType], ReturnType)> {
146        self.functions_map
147            .get(name)
148            .map(|entry| (entry.parameter_types, entry.return_type))
149    }
150
151    /// Create a `FunctionRegistry` pre-populated with the default
152    /// `HostPrint` function (writes to stdout with green text).
153    pub(crate) fn with_default_host_print() -> Self {
154        use crate::func::host_functions::HostFunction;
155        use crate::func::{ParameterTuple, SupportedReturnType};
156
157        let mut registry = Self::default();
158        let hf: HostFunction<i32, (String,)> = default_writer_func.into();
159        let entry = FunctionEntry {
160            function: hf.into(),
161            parameter_types: <(String,)>::TYPE,
162            return_type: <i32 as SupportedReturnType>::TYPE,
163        };
164        registry.register_host_function("HostPrint".to_string(), entry);
165        registry
166    }
167
168    /// Assuming a host function called `"HostPrint"` exists, and takes a
169    /// single string parameter, call it with the given `msg` parameter.
170    ///
171    /// Return `Ok` if the function was found and was of the right signature,
172    /// and `Err` otherwise.
173    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
174    #[allow(dead_code)]
175    pub(super) fn host_print(&mut self, msg: String) -> Result<i32> {
176        let res = self.call_host_func_impl("HostPrint", vec![ParameterValue::String(msg)])?;
177        res.try_into()
178            .map_err(|_| HostFunctionNotFound("HostPrint".to_string()))
179    }
180    /// From the set of registered host functions, attempt to get the one
181    /// named `name`. If it exists, call it with the given arguments list
182    /// `args` and return its result.
183    ///
184    /// Return `Err` if no such function exists,
185    /// its parameter list doesn't match `args`, or there was another error
186    /// getting, configuring or calling the function.
187    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
188    pub(super) fn call_host_function(
189        &self,
190        name: &str,
191        args: Vec<ParameterValue>,
192    ) -> Result<ReturnValue> {
193        self.call_host_func_impl(name, args)
194    }
195
196    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
197    fn call_host_func_impl(&self, name: &str, args: Vec<ParameterValue>) -> Result<ReturnValue> {
198        let FunctionEntry {
199            function,
200            parameter_types: _,
201            return_type: _,
202        } = self
203            .functions_map
204            .get(name)
205            .ok_or_else(|| HostFunctionNotFound(name.to_string()))?;
206
207        // Make the host function call
208        crate::metrics::maybe_time_and_emit_host_call(name, || function.call(args))
209    }
210}
211
212/// The default writer function is to write to stdout with green text.
213#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
214fn default_writer_func(s: String) -> Result<i32> {
215    match std::io::stdout().is_terminal() {
216        false => {
217            print!("{}", s);
218            Ok(s.len() as i32)
219        }
220        true => {
221            let mut stdout = StandardStream::stdout(ColorChoice::Auto);
222            let mut color_spec = ColorSpec::new();
223            color_spec.set_fg(Some(Color::Green));
224            stdout.set_color(&color_spec)?;
225            stdout.write_all(s.as_bytes())?;
226            stdout.reset()?;
227            Ok(s.len() as i32)
228        }
229    }
230}