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>;
}