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