1use std::sync::Arc;
2
3use sim_citizen::value_from_expr;
4use sim_kernel::{
5 CapabilitySet, Cx, DefaultFactory, Env, Error, Expr, Factory, Registry, Result, Symbol, Value,
6};
7use sim_value::capability_names_from_expr;
8
9use crate::{EvalSite, ServerAddress, ServerFrame};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum ShareMode {
15 Share,
17 Child,
19 Isolate,
21 Import(Symbol),
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct IsolationPolicy {
29 pub env: ShareMode,
31 pub libs: ShareMode,
33 pub factory: ShareMode,
35 pub registry: ShareMode,
37 pub capabilities: ShareMode,
39}
40
41impl Default for IsolationPolicy {
42 fn default() -> Self {
43 Self {
44 env: ShareMode::Share,
45 libs: ShareMode::Share,
46 factory: ShareMode::Share,
47 registry: ShareMode::Share,
48 capabilities: ShareMode::Share,
49 }
50 }
51}
52
53impl IsolationPolicy {
54 pub fn from_expr(expr: &Expr) -> Result<Self> {
59 match expr {
60 Expr::Nil => Ok(Self::default()),
61 Expr::Symbol(symbol) if symbol.name.as_ref() == "all-shared" => Ok(Self::default()),
62 Expr::List(items) | Expr::Vector(items) => {
63 let mut policy = Self::default();
64 if !items.len().is_multiple_of(2) {
65 return Err(Error::Eval(
66 "isolation policy must use key/value pairs".to_owned(),
67 ));
68 }
69 for pair in items.chunks(2) {
70 let Expr::Symbol(symbol) = &pair[0] else {
71 return Err(Error::TypeMismatch {
72 expected: "keyword symbol",
73 found: "non-symbol",
74 });
75 };
76 let key = symbol
77 .name
78 .strip_prefix(':')
79 .unwrap_or(symbol.name.as_ref());
80 let mode = parse_share_mode(&pair[1])?;
81 match key {
82 "env" => policy.env = mode,
83 "libs" => policy.libs = mode,
84 "factory" => policy.factory = mode,
85 "registry" => policy.registry = mode,
86 "capabilities" => policy.capabilities = mode,
87 other => {
88 return Err(Error::Eval(format!(
89 "unknown isolation policy axis :{other}"
90 )));
91 }
92 }
93 }
94 Ok(policy)
95 }
96 _ => Err(Error::TypeMismatch {
97 expected: "isolation policy expression",
98 found: "non-policy",
99 }),
100 }
101 }
102
103 pub fn as_value(&self, cx: &mut Cx) -> Result<Value> {
105 let env = share_mode_value(cx, &self.env)?;
106 let libs = share_mode_value(cx, &self.libs)?;
107 let factory = share_mode_value(cx, &self.factory)?;
108 let registry = share_mode_value(cx, &self.registry)?;
109 let capabilities = share_mode_value(cx, &self.capabilities)?;
110 cx.factory().table(vec![
111 (Symbol::new("env"), env),
112 (Symbol::new("libs"), libs),
113 (Symbol::new("factory"), factory),
114 (Symbol::new("registry"), registry),
115 (Symbol::new("capabilities"), capabilities),
116 ])
117 }
118
119 pub fn apply<T>(&self, cx: &mut Cx, f: impl FnOnce(&mut Cx) -> Result<T>) -> Result<T> {
124 let env = derive_env(cx, &self.env)?;
125 let capabilities = derive_capabilities(cx, &self.capabilities)?;
126 let factory = derive_factory(cx, &self.factory);
127 let registry = derive_registry(cx, &self.registry, &self.libs)?;
128 cx.with_registry(registry, |cx| {
129 cx.with_factory(factory, |cx| {
130 cx.with_capabilities(capabilities, |cx| cx.with_env(env, f))
131 })
132 })
133 }
134}
135
136fn parse_share_mode(expr: &Expr) -> Result<ShareMode> {
137 match expr {
138 Expr::Symbol(symbol) => match symbol.name.as_ref() {
139 "share" => Ok(ShareMode::Share),
140 "child" => Ok(ShareMode::Child),
141 "isolate" => Ok(ShareMode::Isolate),
142 other => Err(Error::Eval(format!("unsupported share mode {other}"))),
143 },
144 Expr::List(items) | Expr::Vector(items) => match items.as_slice() {
145 [Expr::Symbol(head), value] if head.name.as_ref() == "import" => match value {
146 Expr::Symbol(symbol) => Ok(ShareMode::Import(symbol.clone())),
147 _ => Err(Error::TypeMismatch {
148 expected: "import symbol",
149 found: "non-symbol",
150 }),
151 },
152 _ => Err(Error::Eval("unsupported share mode form".to_owned())),
153 },
154 _ => Err(Error::TypeMismatch {
155 expected: "share mode",
156 found: "non-share-mode",
157 }),
158 }
159}
160
161fn share_mode_value(cx: &mut Cx, mode: &ShareMode) -> Result<Value> {
162 match mode {
163 ShareMode::Share => cx.factory().symbol(Symbol::new("share")),
164 ShareMode::Child => cx.factory().symbol(Symbol::new("child")),
165 ShareMode::Isolate => cx.factory().symbol(Symbol::new("isolate")),
166 ShareMode::Import(symbol) => cx.factory().table(vec![
167 (
168 Symbol::new("kind"),
169 cx.factory().symbol(Symbol::new("import"))?,
170 ),
171 (Symbol::new("value"), cx.factory().symbol(symbol.clone())?),
172 ]),
173 }
174}
175
176fn derive_env(cx: &mut Cx, mode: &ShareMode) -> Result<Env> {
177 match mode {
178 ShareMode::Share => Ok(cx.env().clone()),
179 ShareMode::Child => Ok(Env::child(Arc::new(cx.env().clone()))),
180 ShareMode::Isolate => Ok(Env::default()),
181 ShareMode::Import(symbol) => env_from_snapshot_expr(&snapshot_expr(cx, symbol)?, cx),
182 }
183}
184
185fn derive_capabilities(cx: &mut Cx, mode: &ShareMode) -> Result<CapabilitySet> {
186 match mode {
187 ShareMode::Share | ShareMode::Child => Ok(cx.capabilities().clone()),
188 ShareMode::Isolate => Ok(CapabilitySet::default()),
189 ShareMode::Import(symbol) => capabilities_from_snapshot_expr(&snapshot_expr(cx, symbol)?),
190 }
191}
192
193fn derive_factory(cx: &Cx, mode: &ShareMode) -> Arc<dyn Factory> {
194 match mode {
195 ShareMode::Share | ShareMode::Child => cx.factory_ref(),
196 ShareMode::Isolate | ShareMode::Import(_) => Arc::new(DefaultFactory),
197 }
198}
199
200fn derive_registry(
201 cx: &mut Cx,
202 registry_mode: &ShareMode,
203 libs_mode: &ShareMode,
204) -> Result<Registry> {
205 let base = match registry_mode {
206 ShareMode::Share | ShareMode::Child => cx.registry().clone(),
207 ShareMode::Isolate => Registry::default(),
208 ShareMode::Import(symbol) => registry_from_snapshot_expr(&snapshot_expr(cx, symbol)?, cx)?,
209 };
210 apply_libs_mode(cx, base, libs_mode)
211}
212
213fn apply_libs_mode(cx: &mut Cx, registry: Registry, mode: &ShareMode) -> Result<Registry> {
214 match mode {
215 ShareMode::Share | ShareMode::Child => Ok(registry),
216 ShareMode::Isolate => Ok(Registry::default()),
217 ShareMode::Import(symbol) => registry_from_snapshot_expr(&snapshot_expr(cx, symbol)?, cx),
218 }
219}
220
221fn snapshot_expr(cx: &mut Cx, symbol: &Symbol) -> Result<Expr> {
222 let value = cx
223 .registry()
224 .value_by_symbol(symbol)
225 .cloned()
226 .ok_or_else(|| Error::UnknownSymbol {
227 symbol: symbol.clone(),
228 })?;
229 value.object().as_expr(cx)
230}
231
232fn env_from_snapshot_expr(expr: &Expr, cx: &mut Cx) -> Result<Env> {
233 let Expr::Map(entries) = expr else {
234 return Err(Error::TypeMismatch {
235 expected: "env snapshot table",
236 found: "non-table",
237 });
238 };
239 let mut env = Env::default();
240 for (key, value) in entries {
241 let Expr::Symbol(symbol) = key else {
242 return Err(Error::TypeMismatch {
243 expected: "symbol table key",
244 found: "non-symbol",
245 });
246 };
247 env.define(symbol.clone(), value_from_expr(cx, value)?);
248 }
249 Ok(env)
250}
251
252fn capabilities_from_snapshot_expr(expr: &Expr) -> Result<CapabilitySet> {
253 let mut capabilities = CapabilitySet::new();
254 for capability in capability_names_from_expr(expr)? {
255 capabilities.insert(capability);
256 }
257 Ok(capabilities)
258}
259
260fn registry_from_snapshot_expr(expr: &Expr, cx: &mut Cx) -> Result<Registry> {
261 let libs = match expr {
262 Expr::Nil => Vec::new(),
263 Expr::Symbol(symbol) => vec![symbol.clone()],
264 Expr::List(items) | Expr::Vector(items) => items
265 .iter()
266 .map(|item| match item {
267 Expr::Symbol(symbol) => Ok(symbol.clone()),
268 _ => Err(Error::TypeMismatch {
269 expected: "library symbol",
270 found: "non-symbol",
271 }),
272 })
273 .collect::<Result<Vec<_>>>()?,
274 _ => {
275 return Err(Error::TypeMismatch {
276 expected: "registry snapshot symbol list",
277 found: "non-list",
278 });
279 }
280 };
281 Ok(cx.registry().subset_for_libs(&libs))
282}
283
284#[derive(Clone)]
285pub(crate) struct IsolatedEvalSite {
286 inner: Arc<dyn EvalSite>,
287 policy: IsolationPolicy,
288}
289
290impl IsolatedEvalSite {
291 pub(crate) fn wrap(inner: Arc<dyn EvalSite>, policy: IsolationPolicy) -> Arc<dyn EvalSite> {
292 Arc::new(Self { inner, policy })
293 }
294
295 pub(crate) fn inner(&self) -> &Arc<dyn EvalSite> {
296 &self.inner
297 }
298}
299
300impl EvalSite for IsolatedEvalSite {
301 fn site_kind(&self) -> &'static str {
302 self.inner.site_kind()
303 }
304
305 fn address(&self) -> &ServerAddress {
306 self.inner.address()
307 }
308
309 fn codecs(&self) -> &[Symbol] {
310 self.inner.codecs()
311 }
312
313 fn answer(&self, cx: &mut Cx, frame: ServerFrame) -> Result<ServerFrame> {
314 self.policy.apply(cx, |cx| self.inner.answer(cx, frame))
315 }
316
317 fn stream(
318 &self,
319 cx: &mut Cx,
320 frame: ServerFrame,
321 sink: &mut dyn crate::StreamSink,
322 ) -> Result<()> {
323 self.policy
324 .apply(cx, |cx| self.inner.stream(cx, frame, sink))
325 }
326
327 fn as_eval_fabric(&self) -> Option<&dyn sim_kernel::EvalFabric> {
328 self.inner.as_eval_fabric()
329 }
330
331 fn as_any(&self) -> &dyn std::any::Any {
332 self
333 }
334}