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