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    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
125    pub fn call(self, cur: &mut MultiUseGuestCallContext) -> Result<()> {
126        (self.cb)(cur)
127    }
128}
129
130impl<'a, Cur: Sandbox, F> From<F> for MultiUseContextCallback<'a, Cur, F>
131where
132    F: FnOnce(&mut MultiUseGuestCallContext) -> Result<()> + 'a,
133{
134    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
135    fn from(val: F) -> Self {
136        MultiUseContextCallback {
137            cur_ph: PhantomData,
138            fn_life_ph: PhantomData,
139            cb: val,
140        }
141    }
142}
143#[cfg(test)]
144mod tests {
145    use super::Noop;
146    use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox, Sandbox};
147    use crate::Result;
148
149    #[derive(Debug, Eq, PartialEq, Clone)]
150    struct MySandbox1 {}
151    #[derive(Debug, Eq, PartialEq, Clone)]
152    struct MySandbox2 {}
153
154    impl Sandbox for MySandbox1 {}
155    impl Sandbox for MySandbox2 {}
156
157    impl EvolvableSandbox<MySandbox1, MySandbox2, Noop<MySandbox1, MySandbox2>> for MySandbox1 {
158        fn evolve(self, _: Noop<MySandbox1, MySandbox2>) -> Result<MySandbox2> {
159            Ok(MySandbox2 {})
160        }
161    }
162
163    impl DevolvableSandbox<MySandbox2, MySandbox1, Noop<MySandbox2, MySandbox1>> for MySandbox2 {
164        fn devolve(self, _: Noop<MySandbox2, MySandbox1>) -> Result<MySandbox1> {
165            Ok(MySandbox1 {})
166        }
167    }
168
169    #[test]
170    fn test_evolve_devolve() {
171        let sbox_1_1 = MySandbox1 {};
172        let sbox_2_1 = sbox_1_1.clone().evolve(Noop::default()).unwrap();
173        let sbox_1_2 = sbox_2_1.clone().devolve(Noop::default()).unwrap();
174        let sbox_2_2 = sbox_1_2.clone().evolve(Noop::default()).unwrap();
175        assert_eq!(sbox_1_1, sbox_1_2);
176        assert_eq!(sbox_2_1, sbox_2_2);
177    }
178}