mod config;
mod decision;
use std::{fmt, sync::Arc};
use sim_codec::{Input, decode_with_codec};
use sim_kernel::{
AbiVersion, CapabilityName, CapabilitySet, Cx, Datum, Diagnostic, Error, Event, Export, Expr,
Lib, LibManifest, LibTarget, Linker, LoadCx, Object, ReadPolicy, Ref, Result, Shape, ShapeId,
Symbol, Value, Version, diminish, read_eval_capability,
};
use sim_shape::expected_shape_diagnostic;
pub use config::{
ConfigEvalNode, HostConfigEvalOptIn, config_eval_node_symbol, config_eval_origin_tag,
parse_config_eval_node, realize_config_expr,
};
pub use decision::{ReadEvalDecision, ReadEvalOutcome, read_eval_decision_run};
#[cfg(test)]
trait GrantOutcome {
fn expect_granted(self);
}
#[cfg(test)]
impl GrantOutcome for () {
fn expect_granted(self) {}
}
#[cfg(test)]
impl GrantOutcome for Result<()> {
fn expect_granted(self) {
self.unwrap();
}
}
#[cfg(test)]
macro_rules! expect_granted {
($grant:expr) => {{
#[allow(clippy::let_unit_value)]
let grant_result = $grant;
#[allow(clippy::unit_arg)]
grant_result.expect_granted();
}};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RequestOrigin {
pub tag: Symbol,
pub detail: Option<Expr>,
}
impl RequestOrigin {
pub fn new(tag: Symbol) -> Self {
Self { tag, detail: None }
}
pub fn with_detail(tag: Symbol, detail: Expr) -> Self {
Self {
tag,
detail: Some(detail),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReadEvalSource {
Text(String),
Bytes(Vec<u8>),
Expr(Expr),
}
#[derive(Clone, PartialEq, Eq)]
pub struct SourceAuthority {
read_policy: ReadPolicy,
requires: Vec<CapabilityName>,
allow: CapabilitySet,
}
impl fmt::Debug for SourceAuthority {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SourceAuthority")
.field("read_policy", &"<redacted>")
.field("requires", &self.requires)
.field("allow", &self.allow)
.finish()
}
}
impl SourceAuthority {
pub fn new(
read_policy: ReadPolicy,
requires: Vec<CapabilityName>,
allow: CapabilitySet,
) -> Result<Self> {
read_policy.require(&read_eval_capability())?;
Ok(Self {
read_policy,
requires,
allow,
})
}
pub fn read_policy(&self) -> &ReadPolicy {
&self.read_policy
}
pub fn requires(&self) -> &[CapabilityName] {
&self.requires
}
pub fn allow(&self) -> &CapabilitySet {
&self.allow
}
pub fn decision_datum(&self) -> Datum {
Datum::Node {
tag: Symbol::qualified("source", "authority"),
fields: vec![
(
Symbol::new("requires"),
capability_names_datum(self.requires()),
),
(
Symbol::new("allow"),
capability_names_datum(self.allow().iter()),
),
(
Symbol::new("read-policy"),
Datum::Symbol(Symbol::new("redacted")),
),
],
}
}
}
pub struct ReadEvalRequest {
pub origin: RequestOrigin,
pub codec: Symbol,
pub source: ReadEvalSource,
pub authority: SourceAuthority,
pub expected_shape: Arc<dyn Shape>,
}
impl ReadEvalRequest {
pub fn new(
origin: RequestOrigin,
codec: Symbol,
source: ReadEvalSource,
authority: SourceAuthority,
expected_shape: Arc<dyn Shape>,
) -> Self {
Self {
origin,
codec,
source,
authority,
expected_shape,
}
}
}
#[derive(Clone, Default)]
pub struct ReadEvalBroker {
ledger: decision::ReadEvalLedger,
}
pub struct ReadEvalAdmission {
pub result: Result<Value>,
pub decision: ReadEvalDecision,
pub event: Event,
}
impl ReadEvalBroker {
pub fn new() -> Self {
Self::default()
}
pub fn admit(&self, cx: &mut Cx, request: ReadEvalRequest) -> Result<Value> {
self.admit_with_event(cx, request)?.result
}
pub fn admit_with_event(
&self,
cx: &mut Cx,
request: ReadEvalRequest,
) -> Result<ReadEvalAdmission> {
if let Err(err) = request
.authority
.read_policy()
.require(&read_eval_capability())
{
let outcome = match err {
Error::TrustDenied { .. } => ReadEvalOutcome::TrustDenied,
_ => ReadEvalOutcome::CapDenied,
};
return self.admission(cx, &request, &CapabilitySet::new(), outcome, Err(err));
}
if let Err(err) = cx.require_all(request.authority.requires()) {
return self.admission(
cx,
&request,
&CapabilitySet::new(),
ReadEvalOutcome::MissingPower,
Err(err),
);
}
let active = diminish(cx.capabilities(), request.authority.allow());
let expr = match cx.with_capabilities(active.clone(), |cx| {
decode_source(
cx,
&request.codec,
request.source.clone(),
request.authority.read_policy().clone(),
)
}) {
Ok(expr) => expr,
Err(err) => {
return self.admission(
cx,
&request,
&active,
ReadEvalOutcome::DecodeFailed,
Err(err),
);
}
};
let value = match cx.with_capabilities(active.clone(), |cx| cx.eval_expr(expr)) {
Ok(value) => value,
Err(err) => {
return self.admission(
cx,
&request,
&active,
ReadEvalOutcome::EvalFailed,
Err(err),
);
}
};
let matched = match request.expected_shape.check_value(cx, value.clone()) {
Ok(matched) => matched,
Err(err) => {
return self.admission(
cx,
&request,
&active,
ReadEvalOutcome::ShapeError,
Err(err),
);
}
};
if matched.accepted {
return self.admission(cx, &request, &active, ReadEvalOutcome::Admitted, Ok(value));
}
let diagnostics =
match shape_diagnostics(cx, request.expected_shape.as_ref(), matched.diagnostics) {
Ok(diagnostics) => diagnostics,
Err(err) => {
return self.admission(
cx,
&request,
&active,
ReadEvalOutcome::ShapeError,
Err(err),
);
}
};
self.admission(
cx,
&request,
&active,
ReadEvalOutcome::ShapeDenied,
Err(Error::WrongShape {
expected: request.expected_shape.id().unwrap_or(ShapeId(0)),
diagnostics,
}),
)
}
pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
self.ledger.decisions(cx)
}
pub fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
self.ledger.decisions_for_run(cx, run)
}
pub fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
self.ledger.events_for_run(run)
}
fn admission(
&self,
cx: &mut Cx,
request: &ReadEvalRequest,
active: &CapabilitySet,
outcome: ReadEvalOutcome,
result: Result<Value>,
) -> Result<ReadEvalAdmission> {
let decision = decision::decision_from_request(request, active, outcome);
let event = self.ledger.record(cx, &decision)?;
Ok(ReadEvalAdmission {
result,
decision,
event,
})
}
}
#[derive(Clone)]
pub struct DynamicSourcePolicy {
broker: ReadEvalBroker,
codec: Symbol,
origin: RequestOrigin,
}
impl DynamicSourcePolicy {
pub fn new(codec: Symbol, origin: RequestOrigin) -> Self {
Self::with_broker(ReadEvalBroker::new(), codec, origin)
}
pub fn with_broker(broker: ReadEvalBroker, codec: Symbol, origin: RequestOrigin) -> Self {
Self {
broker,
codec,
origin,
}
}
pub fn evaluate_text(
&self,
cx: &mut Cx,
text: impl Into<String>,
authority: SourceAuthority,
expected_shape: Arc<dyn Shape>,
) -> Result<Value> {
self.evaluate(
cx,
ReadEvalSource::Text(text.into()),
authority,
expected_shape,
)
}
pub fn evaluate_bytes(
&self,
cx: &mut Cx,
bytes: impl Into<Vec<u8>>,
authority: SourceAuthority,
expected_shape: Arc<dyn Shape>,
) -> Result<Value> {
self.evaluate(
cx,
ReadEvalSource::Bytes(bytes.into()),
authority,
expected_shape,
)
}
pub fn evaluate_expr(
&self,
cx: &mut Cx,
expr: Expr,
authority: SourceAuthority,
expected_shape: Arc<dyn Shape>,
) -> Result<Value> {
self.evaluate(cx, ReadEvalSource::Expr(expr), authority, expected_shape)
}
pub fn evaluate(
&self,
cx: &mut Cx,
source: ReadEvalSource,
authority: SourceAuthority,
expected_shape: Arc<dyn Shape>,
) -> Result<Value> {
self.broker.admit(
cx,
ReadEvalRequest::new(
self.origin.clone(),
self.codec.clone(),
source,
authority,
expected_shape,
),
)
}
pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
self.broker.decisions(cx)
}
pub fn decisions_for_run(&self, cx: &Cx, run: &Ref) -> Result<Vec<ReadEvalDecision>> {
self.broker.decisions_for_run(cx, run)
}
pub fn events_for_run(&self, run: &Ref) -> Result<Vec<Event>> {
self.broker.events_for_run(run)
}
}
impl Object for ReadEvalBroker {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok("#<read-eval-broker>".to_owned())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl sim_kernel::ObjectCompat for ReadEvalBroker {
fn class(&self, cx: &mut Cx) -> Result<sim_kernel::ClassRef> {
cx.factory().class_stub(
sim_kernel::ClassId(0),
Symbol::qualified("read-eval", "Broker"),
)
}
}
pub fn read_eval_broker_symbol() -> Symbol {
Symbol::qualified("read-eval", "broker")
}
pub fn read_eval_broker_lib_id() -> Symbol {
Symbol::qualified("sim", "read-eval-broker")
}
pub struct ReadEvalBrokerLib;
impl Lib for ReadEvalBrokerLib {
fn manifest(&self) -> LibManifest {
LibManifest {
id: read_eval_broker_lib_id(),
version: Version(env!("CARGO_PKG_VERSION").to_owned()),
abi: AbiVersion { major: 0, minor: 1 },
target: LibTarget::HostRegistered,
requires: Vec::new(),
capabilities: Vec::new(),
exports: vec![Export::Value {
symbol: read_eval_broker_symbol(),
}],
}
}
fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
linker.value(
read_eval_broker_symbol(),
cx.factory().opaque(Arc::new(ReadEvalBroker::new()))?,
)?;
Ok(())
}
}
pub fn install_read_eval_broker(cx: &mut Cx) -> Result<bool> {
crate::install_once(cx, &ReadEvalBrokerLib)
}
fn decode_source(
cx: &mut Cx,
codec: &Symbol,
source: ReadEvalSource,
read_policy: ReadPolicy,
) -> Result<Expr> {
match source {
ReadEvalSource::Text(text) => decode_with_codec(cx, codec, Input::Text(text), read_policy),
ReadEvalSource::Bytes(bytes) => {
decode_with_codec(cx, codec, Input::Bytes(bytes), read_policy)
}
ReadEvalSource::Expr(expr) => Ok(expr),
}
}
fn capability_names_datum<'a>(capabilities: impl IntoIterator<Item = &'a CapabilityName>) -> Datum {
Datum::Vector(
capabilities
.into_iter()
.map(|capability| Datum::String(capability.as_str().to_owned()))
.collect(),
)
}
fn shape_diagnostics(
cx: &mut Cx,
shape: &dyn Shape,
diagnostics: Vec<Diagnostic>,
) -> Result<Vec<Diagnostic>> {
if !diagnostics.is_empty() {
return Ok(diagnostics);
}
let expected = match shape.symbol() {
Some(symbol) => symbol.to_string(),
None => shape.describe(cx)?.name,
};
Ok(vec![expected_shape_diagnostic(
expected,
"read-eval result",
)])
}
#[cfg(test)]
mod config_tests;
#[cfg(test)]
mod ledger_tests;
#[cfg(test)]
mod tests;