hyperlight_host/sandbox_state/transition.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::marker::PhantomData;
18
19use tracing::{instrument, Span};
20
21use super::sandbox::Sandbox;
22use crate::func::call_ctx::MultiUseGuestCallContext;
23use crate::Result;
24
25/// Metadata about an evolution or devolution. Any `Sandbox` implementation
26/// that also implements `EvolvableSandbox` or `DevolvableSandbox`
27/// can decide the following things in a type-safe way:
28///
29/// 1. That transition is possible
30/// 2. That transition requires a specific kind of metadata
31///
32/// For example, if you have the following structs:
33///
34/// ```ignore
35/// struct MySandbox1 {}
36/// struct MySandbox2 {}
37///
38/// impl Sandbox for MySandbox1 {...}
39/// impl Sandbox for MySandbox2 {...}
40/// ```
41///
42/// ...then you can define a metadata-free evolve transition between
43/// `MySandbox1` and `MySandbox2`, and a devolve transition that requires
44/// a callback between `MySandbox2` and `MySandbox` as follows:
45///
46/// ```ignore
47/// impl EvolvableSandbox<
48/// MySandbox1,
49/// MySandbox2,
50/// Noop<MySandbox1, MySandbox2>
51/// > for MySandbox1 {
52/// fn evolve(
53/// self,
54/// _: Noop<MySandbox1, MySandbox2>
55/// ) -> Result<MySandbox2> {
56/// Ok(MySandbox2{})
57/// }
58/// }
59///
60/// ```
61///
62/// Most transitions will likely involve `Noop`, but some may involve
63/// implementing their own.
64pub trait TransitionMetadata<Cur: Sandbox, Next: Sandbox> {}
65
66/// Transition metadata that contains and does nothing. `Noop` is a
67/// placeholder when you want to implement an `EvolvableSandbox`
68/// or `DevolvableSandbox` that needs no additional metadata to succeed.
69///
70/// Construct one of these by using the `default()` method.
71pub struct Noop<Cur: Sandbox, Next: Sandbox> {
72 cur_ph: PhantomData<Cur>,
73 next_ph: PhantomData<Next>,
74}
75
76impl<Cur: Sandbox, Next: Sandbox> Default for Noop<Cur, Next> {
77 fn default() -> Self {
78 Self {
79 cur_ph: PhantomData,
80 next_ph: PhantomData,
81 }
82 }
83}
84
85impl<Cur: Sandbox, Next: Sandbox> TransitionMetadata<Cur, Next> for Noop<Cur, Next> {}
86
87/// A `TransitionMetadata` that calls a callback. The callback function takes
88/// a mutable reference to a `MultiUseGuestCallContext` and returns a `Result<()>`
89/// to signify success or failure of the function.
90///
91/// The function use the context to call guest functions.
92///
93/// Construct one of these by passing your callback to
94/// `MultiUseContextCallback::from`, as in the following code (assuming `MySandbox`
95/// is a `Sandbox` implementation):
96///
97/// ```ignore
98/// let my_cb_fn: dyn FnOnce(&mut MultiUseGuestCallContext) -> Result<()> = |_sbox| {
99/// println!("hello world!");
100/// };
101/// let mutating_cb = MultiUseContextCallback::from(my_cb_fn);
102/// ```
103
104pub struct MultiUseContextCallback<'func, Cur: Sandbox, F>
105where
106 F: FnOnce(&mut MultiUseGuestCallContext) -> Result<()> + 'func,
107{
108 cur_ph: PhantomData<Cur>,
109 fn_life_ph: PhantomData<&'func ()>,
110 cb: F,
111}
112
113impl<'a, Cur: Sandbox, Next: Sandbox, F> TransitionMetadata<Cur, Next>
114 for MultiUseContextCallback<'a, Cur, F>
115where
116 F: FnOnce(&mut MultiUseGuestCallContext) -> Result<()>,
117{
118}
119
120impl<'a, Cur: Sandbox, F> MultiUseContextCallback<'a, Cur, F>
121where
122 F: FnOnce(&mut MultiUseGuestCallContext) -> Result<()>,
123{
124 /// Invokes the callback on the provided guest context
125 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
126 pub fn call(self, cur: &mut MultiUseGuestCallContext) -> Result<()> {
127 (self.cb)(cur)
128 }
129}
130
131impl<'a, Cur: Sandbox, F> From<F> for MultiUseContextCallback<'a, Cur, F>
132where
133 F: FnOnce(&mut MultiUseGuestCallContext) -> Result<()> + 'a,
134{
135 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
136 fn from(val: F) -> Self {
137 MultiUseContextCallback {
138 cur_ph: PhantomData,
139 fn_life_ph: PhantomData,
140 cb: val,
141 }
142 }
143}
144#[cfg(test)]
145mod tests {
146 use super::Noop;
147 use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox, Sandbox};
148 use crate::Result;
149
150 #[derive(Debug, Eq, PartialEq, Clone)]
151 struct MySandbox1 {}
152 #[derive(Debug, Eq, PartialEq, Clone)]
153 struct MySandbox2 {}
154
155 impl Sandbox for MySandbox1 {}
156 impl Sandbox for MySandbox2 {}
157
158 impl EvolvableSandbox<MySandbox1, MySandbox2, Noop<MySandbox1, MySandbox2>> for MySandbox1 {
159 fn evolve(self, _: Noop<MySandbox1, MySandbox2>) -> Result<MySandbox2> {
160 Ok(MySandbox2 {})
161 }
162 }
163
164 impl DevolvableSandbox<MySandbox2, MySandbox1, Noop<MySandbox2, MySandbox1>> for MySandbox2 {
165 fn devolve(self, _: Noop<MySandbox2, MySandbox1>) -> Result<MySandbox1> {
166 Ok(MySandbox1 {})
167 }
168 }
169
170 #[test]
171 fn test_evolve_devolve() {
172 let sbox_1_1 = MySandbox1 {};
173 let sbox_2_1 = sbox_1_1.clone().evolve(Noop::default()).unwrap();
174 let sbox_1_2 = sbox_2_1.clone().devolve(Noop::default()).unwrap();
175 let sbox_2_2 = sbox_1_2.clone().evolve(Noop::default()).unwrap();
176 assert_eq!(sbox_1_1, sbox_1_2);
177 assert_eq!(sbox_2_1, sbox_2_2);
178 }
179}