aranya_runtime/
engine.rs

1//! Interfaces for an application to begin a runtime.
2//!
3//! An [`Engine`] stores policies for an application. A [`Policy`] is required
4//! to process [`Command`]s and defines how the runtime's graph is constructed.
5
6use buggy::Bug;
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    Address,
11    command::{CmdId, Command},
12    storage::{FactPerspective, Perspective},
13};
14
15/// An error returned by the runtime engine.
16#[derive(Debug, PartialEq, Eq, thiserror::Error)]
17pub enum EngineError {
18    #[error("read error")]
19    Read,
20    #[error("write error")]
21    Write,
22    #[error("check error")]
23    Check,
24    #[error("panic")]
25    Panic,
26    #[error("internal error")]
27    InternalError,
28    #[error(transparent)]
29    Bug(#[from] Bug),
30}
31
32impl From<core::convert::Infallible> for EngineError {
33    fn from(error: core::convert::Infallible) -> Self {
34        match error {}
35    }
36}
37
38#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
39pub struct PolicyId(usize);
40
41impl PolicyId {
42    pub fn new(id: usize) -> Self {
43        PolicyId(id)
44    }
45}
46
47/// The [`Engine`] manages storing and retrieving [`Policy`].
48pub trait Engine {
49    type Policy: Policy<Effect = Self::Effect>;
50
51    type Effect;
52
53    /// Add a policy to this runtime.
54    ///
55    /// # Arguments
56    ///
57    /// * `policy` - Byte slice that holds a policy.
58    fn add_policy(&mut self, policy: &[u8]) -> Result<PolicyId, EngineError>;
59
60    /// Get a policy from this runtime.
61    ///
62    /// # Arguments
63    ///
64    /// * `policy` - Byte slice representing a [`PolicyId`].
65    fn get_policy(&self, id: PolicyId) -> Result<&Self::Policy, EngineError>;
66}
67
68/// The [`Sink`] transactionally consumes effects from evaluating [`Policy`].
69pub trait Sink<E> {
70    fn begin(&mut self);
71    fn consume(&mut self, effect: E);
72    fn rollback(&mut self);
73    fn commit(&mut self);
74}
75
76pub struct NullSink;
77
78impl<E> Sink<E> for NullSink {
79    fn begin(&mut self) {}
80
81    fn consume(&mut self, _effect: E) {}
82
83    fn rollback(&mut self) {}
84
85    fn commit(&mut self) {}
86}
87
88/// The IDs to a merge command in sorted order.
89pub struct MergeIds {
90    // left < right
91    left: Address,
92    right: Address,
93}
94
95impl MergeIds {
96    /// Create [`MergeIds`] by ordering two [`Address`]s and ensuring they are different.
97    pub fn new(a: Address, b: Address) -> Option<Self> {
98        use core::cmp::Ordering;
99        match a.id.cmp(&b.id) {
100            Ordering::Less => Some(Self { left: a, right: b }),
101            Ordering::Equal => None,
102            Ordering::Greater => Some(Self { left: b, right: a }),
103        }
104    }
105}
106
107impl From<MergeIds> for (CmdId, CmdId) {
108    /// Convert [`MergeIds`] into an ordered pair of [`CmdId`]s.
109    fn from(value: MergeIds) -> Self {
110        (value.left.id, value.right.id)
111    }
112}
113
114impl From<MergeIds> for (Address, Address) {
115    /// Convert [`MergeIds`] into an ordered pair of [`Address`]s.
116    fn from(value: MergeIds) -> Self {
117        (value.left, value.right)
118    }
119}
120
121/// Whether to execute a command's recall block on command failure
122pub enum CommandRecall {
123    /// Don't recall command
124    None,
125    /// Recall if the command fails with a [`aranya_policy_vm::ExitReason::Check`]
126    OnCheck,
127}
128
129/// [`Policy`] evaluates actions and [`Command`]s on the graph, emitting effects
130/// as a result.
131pub trait Policy {
132    type Action<'a>;
133    type Effect;
134    type Command<'a>: Command;
135
136    /// Policies have a serial number which can be used to order them.
137    /// This is used to support inband policy upgrades.
138    fn serial(&self) -> u32;
139
140    /// Evaluate a command at the given perspective. If the command is accepted, effects may
141    /// be emitted to the sink and facts may be written to the perspective. Returns an error
142    /// for a rejected command.
143    fn call_rule(
144        &self,
145        command: &impl Command,
146        facts: &mut impl FactPerspective,
147        sink: &mut impl Sink<Self::Effect>,
148        recall: CommandRecall,
149    ) -> Result<(), EngineError>;
150
151    /// Process an action checking each published command against the policy and emitting
152    /// effects to the sink. All published commands are handled transactionally where if any
153    /// published command is rejected no commands are added to the storage.
154    fn call_action(
155        &self,
156        action: Self::Action<'_>,
157        facts: &mut impl Perspective,
158        sink: &mut impl Sink<Self::Effect>,
159    ) -> Result<(), EngineError>;
160
161    /// Produces a merge message serialized to target. The `struct` representing the
162    /// Command is returned.
163    fn merge<'a>(
164        &self,
165        target: &'a mut [u8],
166        ids: MergeIds,
167    ) -> Result<Self::Command<'a>, EngineError>;
168}