1mod config;
4mod decision;
5
6use std::{fmt, sync::Arc};
7
8use sim_codec::{Input, decode_with_codec};
9use sim_kernel::{
10 AbiVersion, CapabilityName, CapabilitySet, Cx, Datum, Diagnostic, Error, Event, Export, Expr,
11 Lib, LibManifest, LibTarget, Linker, LoadCx, Object, ReadPolicy, Ref, Result, Shape, ShapeId,
12 Symbol, Value, Version, diminish, 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
84#[derive(Clone, PartialEq, Eq)]
90pub struct SourceAuthority {
91 read_policy: ReadPolicy,
92 requires: Vec<CapabilityName>,
93 allow: CapabilitySet,
94}
95
96impl fmt::Debug for SourceAuthority {
97 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
98 formatter
99 .debug_struct("SourceAuthority")
100 .field("read_policy", &"<redacted>")
101 .field("requires", &self.requires)
102 .field("allow", &self.allow)
103 .finish()
104 }
105}
106
107impl SourceAuthority {
108 pub fn new(
112 read_policy: ReadPolicy,
113 requires: Vec<CapabilityName>,
114 allow: CapabilitySet,
115 ) -> Result<Self> {
116 read_policy.require(&read_eval_capability())?;
117 Ok(Self {
118 read_policy,
119 requires,
120 allow,
121 })
122 }
123
124 pub fn read_policy(&self) -> &ReadPolicy {
126 &self.read_policy
127 }
128
129 pub fn requires(&self) -> &[CapabilityName] {
131 &self.requires
132 }
133
134 pub fn allow(&self) -> &CapabilitySet {
136 &self.allow
137 }
138
139 pub fn decision_datum(&self) -> Datum {
142 Datum::Node {
143 tag: Symbol::qualified("source", "authority"),
144 fields: vec![
145 (
146 Symbol::new("requires"),
147 capability_names_datum(self.requires()),
148 ),
149 (
150 Symbol::new("allow"),
151 capability_names_datum(self.allow().iter()),
152 ),
153 (
154 Symbol::new("read-policy"),
155 Datum::Symbol(Symbol::new("redacted")),
156 ),
157 ],
158 }
159 }
160}
161
162pub struct ReadEvalRequest {
164 pub origin: RequestOrigin,
166 pub codec: Symbol,
168 pub source: ReadEvalSource,
170 pub authority: SourceAuthority,
172 pub expected_shape: Arc<dyn Shape>,
174}
175
176impl ReadEvalRequest {
177 pub fn new(
179 origin: RequestOrigin,
180 codec: Symbol,
181 source: ReadEvalSource,
182 authority: SourceAuthority,
183 expected_shape: Arc<dyn Shape>,
184 ) -> Self {
185 Self {
186 origin,
187 codec,
188 source,
189 authority,
190 expected_shape,
191 }
192 }
193}
194
195#[derive(Clone, Default)]
198pub struct ReadEvalBroker {
199 ledger: decision::ReadEvalLedger,
200}
201
202pub struct ReadEvalAdmission {
204 pub result: Result<Value>,
206 pub decision: ReadEvalDecision,
208 pub event: Event,
210}
211
212impl ReadEvalBroker {
213 pub fn new() -> Self {
215 Self::default()
216 }
217
218 pub fn admit(&self, cx: &mut Cx, request: ReadEvalRequest) -> Result<Value> {
220 self.admit_with_event(cx, request)?.result
221 }
222
223 pub fn admit_with_event(
225 &self,
226 cx: &mut Cx,
227 request: ReadEvalRequest,
228 ) -> Result<ReadEvalAdmission> {
229 if let Err(err) = request
230 .authority
231 .read_policy()
232 .require(&read_eval_capability())
233 {
234 let outcome = match err {
235 Error::TrustDenied { .. } => ReadEvalOutcome::TrustDenied,
236 _ => ReadEvalOutcome::CapDenied,
237 };
238 return self.admission(cx, &request, &CapabilitySet::new(), outcome, Err(err));
239 }
240 if let Err(err) = cx.require_all(request.authority.requires()) {
241 return self.admission(
242 cx,
243 &request,
244 &CapabilitySet::new(),
245 ReadEvalOutcome::MissingPower,
246 Err(err),
247 );
248 }
249
250 let active = diminish(cx.capabilities(), request.authority.allow());
251 let expr = match cx.with_capabilities(active.clone(), |cx| {
252 decode_source(
253 cx,
254 &request.codec,
255 request.source.clone(),
256 request.authority.read_policy().clone(),
257 )
258 }) {
259 Ok(expr) => expr,
260 Err(err) => {
261 return self.admission(
262 cx,
263 &request,
264 &active,
265 ReadEvalOutcome::DecodeFailed,
266 Err(err),
267 );
268 }
269 };
270 let value = match cx.with_capabilities(active.clone(), |cx| cx.eval_expr(expr)) {
271 Ok(value) => value,
272 Err(err) => {
273 return self.admission(
274 cx,
275 &request,
276 &active,
277 ReadEvalOutcome::EvalFailed,
278 Err(err),
279 );
280 }
281 };
282
283 let matched = match request.expected_shape.check_value(cx, value.clone()) {
284 Ok(matched) => matched,
285 Err(err) => {
286 return self.admission(
287 cx,
288 &request,
289 &active,
290 ReadEvalOutcome::ShapeError,
291 Err(err),
292 );
293 }
294 };
295 if matched.accepted {
296 return self.admission(cx, &request, &active, ReadEvalOutcome::Admitted, Ok(value));
297 }
298
299 let diagnostics =
300 match shape_diagnostics(cx, request.expected_shape.as_ref(), matched.diagnostics) {
301 Ok(diagnostics) => diagnostics,
302 Err(err) => {
303 return self.admission(
304 cx,
305 &request,
306 &active,
307 ReadEvalOutcome::ShapeError,
308 Err(err),
309 );
310 }
311 };
312 self.admission(
313 cx,
314 &request,
315 &active,
316 ReadEvalOutcome::ShapeDenied,
317 Err(Error::WrongShape {
318 expected: request.expected_shape.id().unwrap_or(ShapeId(0)),
319 diagnostics,
320 }),
321 )
322 }
323
324 pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
326 self.ledger.decisions(cx)
327 }
328
329 pub fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
331 self.ledger.decisions_for_run(cx, run)
332 }
333
334 pub fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
336 self.ledger.events_for_run(run)
337 }
338
339 fn admission(
340 &self,
341 cx: &mut Cx,
342 request: &ReadEvalRequest,
343 active: &CapabilitySet,
344 outcome: ReadEvalOutcome,
345 result: Result<Value>,
346 ) -> Result<ReadEvalAdmission> {
347 let decision = decision::decision_from_request(request, active, outcome);
348 let event = self.ledger.record(cx, &decision)?;
349 Ok(ReadEvalAdmission {
350 result,
351 decision,
352 event,
353 })
354 }
355}
356
357#[derive(Clone)]
365pub struct DynamicSourcePolicy {
366 broker: ReadEvalBroker,
367 codec: Symbol,
368 origin: RequestOrigin,
369}
370
371impl DynamicSourcePolicy {
372 pub fn new(codec: Symbol, origin: RequestOrigin) -> Self {
374 Self::with_broker(ReadEvalBroker::new(), codec, origin)
375 }
376
377 pub fn with_broker(broker: ReadEvalBroker, codec: Symbol, origin: RequestOrigin) -> Self {
382 Self {
383 broker,
384 codec,
385 origin,
386 }
387 }
388
389 pub fn evaluate_text(
391 &self,
392 cx: &mut Cx,
393 text: impl Into<String>,
394 authority: SourceAuthority,
395 expected_shape: Arc<dyn Shape>,
396 ) -> Result<Value> {
397 self.evaluate(
398 cx,
399 ReadEvalSource::Text(text.into()),
400 authority,
401 expected_shape,
402 )
403 }
404
405 pub fn evaluate_bytes(
407 &self,
408 cx: &mut Cx,
409 bytes: impl Into<Vec<u8>>,
410 authority: SourceAuthority,
411 expected_shape: Arc<dyn Shape>,
412 ) -> Result<Value> {
413 self.evaluate(
414 cx,
415 ReadEvalSource::Bytes(bytes.into()),
416 authority,
417 expected_shape,
418 )
419 }
420
421 pub fn evaluate_expr(
423 &self,
424 cx: &mut Cx,
425 expr: Expr,
426 authority: SourceAuthority,
427 expected_shape: Arc<dyn Shape>,
428 ) -> Result<Value> {
429 self.evaluate(cx, ReadEvalSource::Expr(expr), authority, expected_shape)
430 }
431
432 pub fn evaluate(
434 &self,
435 cx: &mut Cx,
436 source: ReadEvalSource,
437 authority: SourceAuthority,
438 expected_shape: Arc<dyn Shape>,
439 ) -> Result<Value> {
440 self.broker.admit(
441 cx,
442 ReadEvalRequest::new(
443 self.origin.clone(),
444 self.codec.clone(),
445 source,
446 authority,
447 expected_shape,
448 ),
449 )
450 }
451
452 pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
454 self.broker.decisions(cx)
455 }
456
457 pub fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
459 self.broker.decisions_for_run(cx, run)
460 }
461
462 pub fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
464 self.broker.events_for_run(run)
465 }
466}
467
468impl Object for ReadEvalBroker {
469 fn display(&self, _cx: &mut Cx) -> Result<String> {
470 Ok("#<read-eval-broker>".to_owned())
471 }
472
473 fn as_any(&self) -> &dyn std::any::Any {
474 self
475 }
476}
477
478impl sim_kernel::ObjectCompat for ReadEvalBroker {
479 fn class(&self, cx: &mut Cx) -> Result<sim_kernel::ClassRef> {
480 cx.factory().class_stub(
481 sim_kernel::ClassId(0),
482 Symbol::qualified("read-eval", "Broker"),
483 )
484 }
485}
486
487pub fn read_eval_broker_symbol() -> Symbol {
489 Symbol::qualified("read-eval", "broker")
490}
491
492pub fn read_eval_broker_lib_id() -> Symbol {
494 Symbol::qualified("sim", "read-eval-broker")
495}
496
497pub struct ReadEvalBrokerLib;
499
500impl Lib for ReadEvalBrokerLib {
501 fn manifest(&self) -> LibManifest {
502 LibManifest {
503 id: read_eval_broker_lib_id(),
504 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
505 abi: AbiVersion { major: 0, minor: 1 },
506 target: LibTarget::HostRegistered,
507 requires: Vec::new(),
508 capabilities: Vec::new(),
509 exports: vec![Export::Value {
510 symbol: read_eval_broker_symbol(),
511 }],
512 }
513 }
514
515 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
516 linker.value(
517 read_eval_broker_symbol(),
518 cx.factory().opaque(Arc::new(ReadEvalBroker::new()))?,
519 )?;
520 Ok(())
521 }
522}
523
524pub fn install_read_eval_broker(cx: &mut Cx) -> Result<bool> {
526 crate::install_once(cx, &ReadEvalBrokerLib)
527}
528
529fn decode_source(
530 cx: &mut Cx,
531 codec: &Symbol,
532 source: ReadEvalSource,
533 read_policy: ReadPolicy,
534) -> Result<Expr> {
535 match source {
536 ReadEvalSource::Text(text) => decode_with_codec(cx, codec, Input::Text(text), read_policy),
537 ReadEvalSource::Bytes(bytes) => {
538 decode_with_codec(cx, codec, Input::Bytes(bytes), read_policy)
539 }
540 ReadEvalSource::Expr(expr) => Ok(expr),
541 }
542}
543
544fn capability_names_datum<'a>(capabilities: impl IntoIterator<Item = &'a CapabilityName>) -> Datum {
545 Datum::Vector(
546 capabilities
547 .into_iter()
548 .map(|capability| Datum::String(capability.as_str().to_owned()))
549 .collect(),
550 )
551}
552
553fn shape_diagnostics(
554 cx: &mut Cx,
555 shape: &dyn Shape,
556 diagnostics: Vec<Diagnostic>,
557) -> Result<Vec<Diagnostic>> {
558 if !diagnostics.is_empty() {
559 return Ok(diagnostics);
560 }
561 let expected = match shape.symbol() {
562 Some(symbol) => symbol.to_string(),
563 None => shape.describe(cx)?.name,
564 };
565 Ok(vec![expected_shape_diagnostic(
566 expected,
567 "read-eval result",
568 )])
569}
570
571#[cfg(test)]
572mod config_tests;
573
574#[cfg(test)]
575mod ledger_tests;
576
577#[cfg(test)]
578mod tests;