nub_host_kvm/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
17//! Fn-id-indexed registry of host callbacks the guest can invoke
18//! via the `OutBAction::CallFunction` outb port. Each entry is a
19//! `FnMut(&[u8]) -> Result<Vec<u8>>` — receives the raw payload
20//! bytes from a `nub_host_common::rpc::Request`, produces the raw
21//! response payload bytes that the host wraps in a `Response`.
22//!
23//! No more name strings, no more parameter-tuple polymorphism. If
24//! a future caller wants typed `Fn(Spec) -> Result` registration,
25//! a sugar attribute (`#[host_function(fn_id = N)]`) in
26//! `nub-host-guest-macro` can wrap the encode/decode at compile
27//! time.
28
29use tracing::{Span, instrument};
30
31use crate::HyperlightError::HostFunctionNotFound;
32use crate::Result;
33
34/// Boxed host function. Takes a payload byte slice (the inner
35/// `Request.payload`), returns response payload bytes.
36pub type HostFn = Box<dyn FnMut(&[u8]) -> Result<Vec<u8>> + Send>;
37
38/// Maximum number of registered host functions. Sized so the
39/// fixed-size array fits in a single cache line of pointer-sized
40/// slots while leaving room for a handful of future callbacks.
41pub const HOST_FN_TABLE_SIZE: usize = 64;
42
43/// Fn-id-indexed registry. Slots default to `None`; registering at
44/// index `i` puts a callback there. Dispatching at index `i`
45/// returns `HostFunctionNotFound` if the slot is empty.
46pub struct FunctionRegistry {
47 functions: [Option<HostFn>; HOST_FN_TABLE_SIZE],
48}
49
50impl Default for FunctionRegistry {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl FunctionRegistry {
57 pub const fn new() -> Self {
58 // `[None; N]` requires `Copy`; `Option<HostFn>` isn't.
59 // Build the array element-by-element.
60 Self {
61 functions: [const { None }; HOST_FN_TABLE_SIZE],
62 }
63 }
64
65 /// Register `func` under `fn_id`. Overwrites any prior entry.
66 #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
67 pub(crate) fn register_host_function(&mut self, fn_id: u32, func: HostFn) -> Result<()> {
68 let idx = fn_id as usize;
69 if idx >= HOST_FN_TABLE_SIZE {
70 return Err(crate::new_error!(
71 "register_host_function: fn_id={} exceeds HOST_FN_TABLE_SIZE={}",
72 fn_id,
73 HOST_FN_TABLE_SIZE
74 ));
75 }
76 self.functions[idx] = Some(func);
77 Ok(())
78 }
79
80 /// Dispatch a guest→host call to the registered handler. Returns
81 /// `HostFunctionNotFound` if no handler is registered for
82 /// `fn_id`. The handler's `Err` is propagated as-is.
83 #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
84 pub(crate) fn call_host_function(&mut self, fn_id: u32, payload: &[u8]) -> Result<Vec<u8>> {
85 let idx = fn_id as usize;
86 let entry = self
87 .functions
88 .get_mut(idx)
89 .and_then(|slot| slot.as_mut())
90 .ok_or_else(|| HostFunctionNotFound(format!("fn_id={fn_id}")))?;
91 crate::metrics::maybe_time_and_emit_host_call(&format!("fn_id={fn_id}"), || entry(payload))
92 }
93}