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}