aranya_runtime/
command.rs

1use alloc::{
2    format,
3    string::{String, ToString},
4};
5
6use buggy::{Bug, BugExt};
7use serde::{Deserialize, Serialize};
8
9use crate::Prior;
10
11aranya_crypto::custom_id! {
12    /// An ID constructed as a cryptographic hash of a serialized [`Command`].
13    pub struct CommandId;
14}
15
16impl CommandId {
17    /// Derives a [`CommandId`] from some data.
18    #[cfg(feature = "testing")]
19    pub fn hash_for_testing_only(data: &[u8]) -> Self {
20        use aranya_crypto::dangerous::spideroak_crypto::{hash::Hash, rust::Sha256};
21        Sha256::hash(data).into_array().into()
22    }
23
24    pub fn short_b58(&self) -> String {
25        #![allow(clippy::arithmetic_side_effects)]
26        let b58 = self.to_string();
27        let trimmed = b58.trim_start_matches('1');
28        let len = trimmed.len();
29        if len == 0 {
30            "1".into()
31        } else if len > 8 {
32            format!("..{}", &trimmed[len - 6..])
33        } else {
34            trimmed.into()
35        }
36    }
37}
38
39/// Identify how the client will sort the associated [`Command`].
40// Note: Order of variants affects derived Ord: Merge is least and Init is greatest.
41#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
42pub enum Priority {
43    /// Indicates two branches in the parent graph have been merged at this
44    /// command. A command with this priority must have two parents,
45    /// `Parent::Merge`.
46    Merge,
47    /// Indicates a device-specific action; the runtime uses the internal u32
48    /// for ordering.
49    Basic(u32),
50    /// Indicates all preceding commands are ancestors of this command.
51    Finalize,
52    /// Indicates state is initialized; the associated command is a common
53    /// ancestor to all other commands in the graph. A command with this
54    /// priority must have no parents, `Parent::None`.
55    Init,
56}
57
58/// An action message interpreted by its associated policy to affect state.
59///
60/// A [`Command`] is opaque to the runtime engine. When the engine receives a
61/// message, it is validated and serialized by its policy. The policy
62/// returns a command implementation to update the stored graph. A
63/// policy will also emit effects once a command is verified,
64/// which are sent to the client.
65pub trait Command {
66    /// Return this command's [`Priority`], determining how this event is
67    /// ordered amongst others it does not have a causal relationship with.
68    fn priority(&self) -> Priority;
69
70    /// Uniquely identifies the serialized command.
71    fn id(&self) -> CommandId;
72
73    /// Return this command's parents, or address(s) that immediately
74    /// precede(s) this.
75    fn parent(&self) -> Prior<Address>;
76
77    /// Return this command's associated policy.
78    fn policy(&self) -> Option<&[u8]>;
79
80    /// Return this command's serialized data.
81    fn bytes(&self) -> &[u8];
82
83    /// Return this command's max cut. Max cut is the maximum distance to the init command.
84    fn max_cut(&self) -> Result<usize, Bug> {
85        match self.parent() {
86            Prior::None => Ok(0),
87            Prior::Single(l) => Ok(l.max_cut.checked_add(1).assume("must not overflow")?),
88            Prior::Merge(l, r) => Ok(l
89                .max_cut
90                .max(r.max_cut)
91                .checked_add(1)
92                .assume("must not overflow")?),
93        }
94    }
95
96    /// Return this command's address.
97    fn address(&self) -> Result<Address, Bug> {
98        Ok(Address {
99            id: self.id(),
100            max_cut: self.max_cut()?,
101        })
102    }
103}
104
105impl<C: Command> Command for &C {
106    fn priority(&self) -> Priority {
107        (*self).priority()
108    }
109
110    fn id(&self) -> CommandId {
111        (*self).id()
112    }
113
114    fn parent(&self) -> Prior<Address> {
115        (*self).parent()
116    }
117
118    fn policy(&self) -> Option<&[u8]> {
119        (*self).policy()
120    }
121
122    fn bytes(&self) -> &[u8] {
123        (*self).bytes()
124    }
125
126    fn max_cut(&self) -> Result<usize, Bug> {
127        (*self).max_cut()
128    }
129
130    fn address(&self) -> Result<Address, Bug> {
131        (*self).address()
132    }
133}
134
135#[derive(Debug, Serialize, Deserialize, Clone, Copy, Ord, PartialEq, PartialOrd, Eq, Default)]
136/// An address contains all of the information needed to find a command in
137/// another graph.
138///
139/// The command id identifies the command you're searching for and the
140/// max_cut allows that command to be found efficiently.
141pub struct Address {
142    pub id: CommandId,
143    pub max_cut: usize,
144}
145
146impl Prior<Address> {
147    /// Returns the max cut for the command that is after this prior.
148    pub fn next_max_cut(&self) -> Result<usize, Bug> {
149        Ok(match self {
150            Prior::None => 1,
151            Prior::Single(l) => l.max_cut.checked_add(1).assume("must not overflow")?,
152            Prior::Merge(l, r) => l
153                .max_cut
154                .max(r.max_cut)
155                .checked_add(1)
156                .assume("must not overflow")?,
157        })
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use super::*;
164
165    #[test]
166    fn priority_ordering() {
167        assert!(Priority::Merge < Priority::Basic(0));
168        assert!(Priority::Basic(0) < Priority::Basic(1));
169        assert!(Priority::Basic(1) < Priority::Basic(u32::MAX));
170        assert!(Priority::Basic(u32::MAX) < Priority::Finalize);
171        assert!(Priority::Finalize < Priority::Init);
172    }
173}