Skip to main content

hyperlight_host/sandbox/
host_funcs.rs

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