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