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#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct RequestOrigin {
25 pub tag: Symbol,
27 pub detail: Option<Expr>,
29}
30
31impl RequestOrigin {
32 pub fn new(tag: Symbol) -> Self {
34 Self { tag, detail: None }
35 }
36
37 pub fn with_detail(tag: Symbol, detail: Expr) -> Self {
39 Self {
40 tag,
41 detail: Some(detail),
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum ReadEvalSource {
49 Text(String),
51 Bytes(Vec<u8>),
53 Expr(Expr),
55}
56
57pub struct ReadEvalRequest {
59 pub origin: RequestOrigin,
61 pub codec: Symbol,
63 pub source: ReadEvalSource,
65 pub read_policy: ReadPolicy,
67 pub requires: Vec<CapabilityName>,
69 pub allow: CapabilitySet,
71 pub expected_shape: Arc<dyn Shape>,
73}
74
75#[derive(Clone, Default)]
78pub struct ReadEvalBroker {
79 ledger: decision::ReadEvalLedger,
80}
81
82impl ReadEvalBroker {
83 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn admit(&self, cx: &mut Cx, request: ReadEvalRequest) -> Result<Value> {
90 if let Err(err) = request.read_policy.require(&read_eval_capability()) {
91 let outcome = match err {
92 Error::TrustDenied { .. } => ReadEvalOutcome::TrustDenied,
93 _ => ReadEvalOutcome::CapDenied,
94 };
95 self.record(cx, &request, &CapabilitySet::new(), outcome)?;
96 return Err(err);
97 }
98 if let Err(err) = cx.require_all(&request.requires) {
99 self.record(
100 cx,
101 &request,
102 &CapabilitySet::new(),
103 ReadEvalOutcome::MissingPower,
104 )?;
105 return Err(err);
106 }
107
108 let active = diminish_capabilities(cx.capabilities(), &request.allow);
109 let value = cx.with_capabilities(active.clone(), |cx| {
110 let expr = decode_source(
111 cx,
112 &request.codec,
113 request.source.clone(),
114 request.read_policy.clone(),
115 )?;
116 cx.eval_expr(expr)
117 })?;
118
119 let matched = request.expected_shape.check_value(cx, value.clone())?;
120 if matched.accepted {
121 self.record(cx, &request, &active, ReadEvalOutcome::Admitted)?;
122 return Ok(value);
123 }
124
125 self.record(cx, &request, &active, ReadEvalOutcome::ShapeDenied)?;
126 Err(Error::WrongShape {
127 expected: request.expected_shape.id().unwrap_or(ShapeId(0)),
128 diagnostics: shape_diagnostics(
129 cx,
130 request.expected_shape.as_ref(),
131 matched.diagnostics,
132 )?,
133 })
134 }
135
136 pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
138 self.ledger.decisions(cx)
139 }
140
141 pub fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
143 self.ledger.decisions_for_run(cx, run)
144 }
145
146 pub fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
148 self.ledger.events_for_run(run)
149 }
150
151 fn record(
152 &self,
153 cx: &mut Cx,
154 request: &ReadEvalRequest,
155 active: &CapabilitySet,
156 outcome: ReadEvalOutcome,
157 ) -> Result<Event> {
158 let decision = decision::decision_from_request(request, active, outcome);
159 self.ledger.record(cx, &decision)
160 }
161}
162
163impl Object for ReadEvalBroker {
164 fn display(&self, _cx: &mut Cx) -> Result<String> {
165 Ok("#<read-eval-broker>".to_owned())
166 }
167
168 fn as_any(&self) -> &dyn std::any::Any {
169 self
170 }
171}
172
173impl sim_kernel::ObjectCompat for ReadEvalBroker {
174 fn class(&self, cx: &mut Cx) -> Result<sim_kernel::ClassRef> {
175 cx.factory().class_stub(
176 sim_kernel::ClassId(0),
177 Symbol::qualified("read-eval", "Broker"),
178 )
179 }
180}
181
182pub fn read_eval_broker_symbol() -> Symbol {
184 Symbol::qualified("read-eval", "broker")
185}
186
187pub fn read_eval_broker_lib_id() -> Symbol {
189 Symbol::qualified("sim", "read-eval-broker")
190}
191
192pub struct ReadEvalBrokerLib;
194
195impl Lib for ReadEvalBrokerLib {
196 fn manifest(&self) -> LibManifest {
197 LibManifest {
198 id: read_eval_broker_lib_id(),
199 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
200 abi: AbiVersion { major: 0, minor: 1 },
201 target: LibTarget::HostRegistered,
202 requires: Vec::new(),
203 capabilities: Vec::new(),
204 exports: vec![Export::Value {
205 symbol: read_eval_broker_symbol(),
206 }],
207 }
208 }
209
210 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
211 linker.value(
212 read_eval_broker_symbol(),
213 cx.factory().opaque(Arc::new(ReadEvalBroker::new()))?,
214 )?;
215 Ok(())
216 }
217}
218
219pub fn install_read_eval_broker(cx: &mut Cx) -> Result<bool> {
221 crate::install_once(cx, &ReadEvalBrokerLib)
222}
223
224fn decode_source(
225 cx: &mut Cx,
226 codec: &Symbol,
227 source: ReadEvalSource,
228 read_policy: ReadPolicy,
229) -> Result<Expr> {
230 match source {
231 ReadEvalSource::Text(text) => decode_with_codec(cx, codec, Input::Text(text), read_policy),
232 ReadEvalSource::Bytes(bytes) => {
233 decode_with_codec(cx, codec, Input::Bytes(bytes), read_policy)
234 }
235 ReadEvalSource::Expr(expr) => Ok(expr),
236 }
237}
238
239fn diminish_capabilities(current: &CapabilitySet, allowed: &CapabilitySet) -> CapabilitySet {
240 current
241 .iter()
242 .filter(|capability| allowed.contains(capability))
243 .cloned()
244 .fold(CapabilitySet::new(), CapabilitySet::grant)
245}
246
247fn shape_diagnostics(
248 cx: &mut Cx,
249 shape: &dyn Shape,
250 diagnostics: Vec<Diagnostic>,
251) -> Result<Vec<Diagnostic>> {
252 if !diagnostics.is_empty() {
253 return Ok(diagnostics);
254 }
255 let expected = match shape.symbol() {
256 Some(symbol) => symbol.to_string(),
257 None => shape.describe(cx)?.name,
258 };
259 Ok(vec![expected_shape_diagnostic(
260 expected,
261 "read-eval result",
262 )])
263}
264
265#[cfg(test)]
266mod config_tests;
267
268#[cfg(test)]
269mod ledger_tests;
270
271#[cfg(test)]
272mod tests;