1mod config;
4mod decision;
5
6use std::sync::Arc;
7
8use sim_codec::{Input, decode_with_codec};
9use sim_kernel::{
10 AbiVersion, CapabilityName, CapabilitySet, Cx, Diagnostic, Error, Event, Export, Expr, Lib,
11 LibManifest, LibTarget, Linker, LoadCx, Object, ReadPolicy, Ref, Result, Shape, ShapeId,
12 Symbol, Value, Version, read_eval_capability,
13};
14use sim_shape::expected_shape_diagnostic;
15
16pub use config::{
17 ConfigEvalNode, HostConfigEvalOptIn, config_eval_node_symbol, config_eval_origin_tag,
18 parse_config_eval_node, realize_config_expr,
19};
20pub use decision::{ReadEvalDecision, ReadEvalOutcome, read_eval_decision_run};
21
22#[cfg(test)]
23trait GrantOutcome {
24 fn expect_granted(self);
25}
26
27#[cfg(test)]
28impl GrantOutcome for () {
29 fn expect_granted(self) {}
30}
31
32#[cfg(test)]
33impl GrantOutcome for Result<()> {
34 fn expect_granted(self) {
35 self.unwrap();
36 }
37}
38
39#[cfg(test)]
40macro_rules! expect_granted {
41 ($grant:expr) => {{
42 #[allow(clippy::let_unit_value)]
43 let grant_result = $grant;
44 #[allow(clippy::unit_arg)]
45 grant_result.expect_granted();
46 }};
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct RequestOrigin {
52 pub tag: Symbol,
54 pub detail: Option<Expr>,
56}
57
58impl RequestOrigin {
59 pub fn new(tag: Symbol) -> Self {
61 Self { tag, detail: None }
62 }
63
64 pub fn with_detail(tag: Symbol, detail: Expr) -> Self {
66 Self {
67 tag,
68 detail: Some(detail),
69 }
70 }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub enum ReadEvalSource {
76 Text(String),
78 Bytes(Vec<u8>),
80 Expr(Expr),
82}
83
84pub struct ReadEvalRequest {
86 pub origin: RequestOrigin,
88 pub codec: Symbol,
90 pub source: ReadEvalSource,
92 pub read_policy: ReadPolicy,
94 pub requires: Vec<CapabilityName>,
96 pub allow: CapabilitySet,
98 pub expected_shape: Arc<dyn Shape>,
100}
101
102#[derive(Clone, Default)]
105pub struct ReadEvalBroker {
106 ledger: decision::ReadEvalLedger,
107}
108
109impl ReadEvalBroker {
110 pub fn new() -> Self {
112 Self::default()
113 }
114
115 pub fn admit(&self, cx: &mut Cx, request: ReadEvalRequest) -> Result<Value> {
117 if let Err(err) = request.read_policy.require(&read_eval_capability()) {
118 let outcome = match err {
119 Error::TrustDenied { .. } => ReadEvalOutcome::TrustDenied,
120 _ => ReadEvalOutcome::CapDenied,
121 };
122 self.record(cx, &request, &CapabilitySet::new(), outcome)?;
123 return Err(err);
124 }
125 if let Err(err) = cx.require_all(&request.requires) {
126 self.record(
127 cx,
128 &request,
129 &CapabilitySet::new(),
130 ReadEvalOutcome::MissingPower,
131 )?;
132 return Err(err);
133 }
134
135 let active = diminish_capabilities(cx.capabilities(), &request.allow);
136 let expr = match cx.with_capabilities(active.clone(), |cx| {
137 decode_source(
138 cx,
139 &request.codec,
140 request.source.clone(),
141 request.read_policy.clone(),
142 )
143 }) {
144 Ok(expr) => expr,
145 Err(err) => {
146 self.record(cx, &request, &active, ReadEvalOutcome::DecodeFailed)?;
147 return Err(err);
148 }
149 };
150 let value = match cx.with_capabilities(active.clone(), |cx| cx.eval_expr(expr)) {
151 Ok(value) => value,
152 Err(err) => {
153 self.record(cx, &request, &active, ReadEvalOutcome::EvalFailed)?;
154 return Err(err);
155 }
156 };
157
158 let matched = match request.expected_shape.check_value(cx, value.clone()) {
159 Ok(matched) => matched,
160 Err(err) => {
161 self.record(cx, &request, &active, ReadEvalOutcome::ShapeError)?;
162 return Err(err);
163 }
164 };
165 if matched.accepted {
166 self.record(cx, &request, &active, ReadEvalOutcome::Admitted)?;
167 return Ok(value);
168 }
169
170 let diagnostics =
171 match shape_diagnostics(cx, request.expected_shape.as_ref(), matched.diagnostics) {
172 Ok(diagnostics) => diagnostics,
173 Err(err) => {
174 self.record(cx, &request, &active, ReadEvalOutcome::ShapeError)?;
175 return Err(err);
176 }
177 };
178 self.record(cx, &request, &active, ReadEvalOutcome::ShapeDenied)?;
179 Err(Error::WrongShape {
180 expected: request.expected_shape.id().unwrap_or(ShapeId(0)),
181 diagnostics,
182 })
183 }
184
185 pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
187 self.ledger.decisions(cx)
188 }
189
190 pub fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
192 self.ledger.decisions_for_run(cx, run)
193 }
194
195 pub fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
197 self.ledger.events_for_run(run)
198 }
199
200 fn record(
201 &self,
202 cx: &mut Cx,
203 request: &ReadEvalRequest,
204 active: &CapabilitySet,
205 outcome: ReadEvalOutcome,
206 ) -> Result<Event> {
207 let decision = decision::decision_from_request(request, active, outcome);
208 self.ledger.record(cx, &decision)
209 }
210}
211
212impl Object for ReadEvalBroker {
213 fn display(&self, _cx: &mut Cx) -> Result<String> {
214 Ok("#<read-eval-broker>".to_owned())
215 }
216
217 fn as_any(&self) -> &dyn std::any::Any {
218 self
219 }
220}
221
222impl sim_kernel::ObjectCompat for ReadEvalBroker {
223 fn class(&self, cx: &mut Cx) -> Result<sim_kernel::ClassRef> {
224 cx.factory().class_stub(
225 sim_kernel::ClassId(0),
226 Symbol::qualified("read-eval", "Broker"),
227 )
228 }
229}
230
231pub fn read_eval_broker_symbol() -> Symbol {
233 Symbol::qualified("read-eval", "broker")
234}
235
236pub fn read_eval_broker_lib_id() -> Symbol {
238 Symbol::qualified("sim", "read-eval-broker")
239}
240
241pub struct ReadEvalBrokerLib;
243
244impl Lib for ReadEvalBrokerLib {
245 fn manifest(&self) -> LibManifest {
246 LibManifest {
247 id: read_eval_broker_lib_id(),
248 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
249 abi: AbiVersion { major: 0, minor: 1 },
250 target: LibTarget::HostRegistered,
251 requires: Vec::new(),
252 capabilities: Vec::new(),
253 exports: vec![Export::Value {
254 symbol: read_eval_broker_symbol(),
255 }],
256 }
257 }
258
259 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
260 linker.value(
261 read_eval_broker_symbol(),
262 cx.factory().opaque(Arc::new(ReadEvalBroker::new()))?,
263 )?;
264 Ok(())
265 }
266}
267
268pub fn install_read_eval_broker(cx: &mut Cx) -> Result<bool> {
270 crate::install_once(cx, &ReadEvalBrokerLib)
271}
272
273fn decode_source(
274 cx: &mut Cx,
275 codec: &Symbol,
276 source: ReadEvalSource,
277 read_policy: ReadPolicy,
278) -> Result<Expr> {
279 match source {
280 ReadEvalSource::Text(text) => decode_with_codec(cx, codec, Input::Text(text), read_policy),
281 ReadEvalSource::Bytes(bytes) => {
282 decode_with_codec(cx, codec, Input::Bytes(bytes), read_policy)
283 }
284 ReadEvalSource::Expr(expr) => Ok(expr),
285 }
286}
287
288fn diminish_capabilities(current: &CapabilitySet, allowed: &CapabilitySet) -> CapabilitySet {
289 current
290 .iter()
291 .filter(|capability| allowed.contains(capability))
292 .cloned()
293 .fold(CapabilitySet::new(), CapabilitySet::grant)
294}
295
296fn shape_diagnostics(
297 cx: &mut Cx,
298 shape: &dyn Shape,
299 diagnostics: Vec<Diagnostic>,
300) -> Result<Vec<Diagnostic>> {
301 if !diagnostics.is_empty() {
302 return Ok(diagnostics);
303 }
304 let expected = match shape.symbol() {
305 Some(symbol) => symbol.to_string(),
306 None => shape.describe(cx)?.name,
307 };
308 Ok(vec![expected_shape_diagnostic(
309 expected,
310 "read-eval result",
311 )])
312}
313
314#[cfg(test)]
315mod config_tests;
316
317#[cfg(test)]
318mod ledger_tests;
319
320#[cfg(test)]
321mod tests;