aranya_runtime/vm_policy/
protocol.rs

1extern crate alloc;
2
3use alloc::{borrow::Cow, collections::BTreeMap};
4
5use aranya_crypto::DeviceId;
6use aranya_policy_vm::{
7    Struct, Value,
8    ast::{Identifier, ident},
9};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13    Address, Prior,
14    command::{CmdId, Command, Priority},
15};
16
17/// The data inside a [VmProtocol]. It gets serialized and deserialized over the wire.
18#[derive(Debug, Serialize, Deserialize)]
19pub enum VmProtocolData<'a> {
20    Init {
21        policy: [u8; 8],
22        author_id: DeviceId,
23        kind: Identifier,
24        #[serde(borrow)]
25        serialized_fields: &'a [u8],
26        #[serde(borrow)]
27        signature: &'a [u8],
28    },
29    Merge {
30        left: Address,
31        right: Address,
32    },
33    Basic {
34        parent: Address,
35        author_id: DeviceId,
36        kind: Identifier,
37        #[serde(borrow)]
38        serialized_fields: &'a [u8],
39        #[serde(borrow)]
40        signature: &'a [u8],
41    },
42}
43
44/// The Command implementation as used by the VM. It deserializes the interior data into a
45/// [VmProtocolData] struct, and it keeps the original serialized copy around for quick
46/// access to that.
47#[derive(Debug)]
48pub struct VmProtocol<'a> {
49    /// Reference to the serialized data underlying the command
50    data: &'a [u8],
51    /// The ID of the command
52    id: CmdId,
53    /// The deserialized data
54    unpacked: VmProtocolData<'a>,
55    /// The command's priority.
56    priority: Priority,
57}
58
59impl<'a> VmProtocol<'a> {
60    pub fn new(
61        data: &'a [u8],
62        id: CmdId,
63        unpacked: VmProtocolData<'a>,
64        priority: Priority,
65    ) -> VmProtocol<'a> {
66        VmProtocol {
67            data,
68            id,
69            unpacked,
70            priority,
71        }
72    }
73}
74
75impl Command for VmProtocol<'_> {
76    fn priority(&self) -> Priority {
77        self.priority.clone()
78    }
79
80    fn id(&self) -> CmdId {
81        self.id
82    }
83
84    fn parent(&self) -> Prior<Address> {
85        match self.unpacked {
86            VmProtocolData::Init { .. } => Prior::None,
87            VmProtocolData::Merge { left, right, .. } => Prior::Merge(left, right),
88            VmProtocolData::Basic { parent, .. } => Prior::Single(parent),
89        }
90    }
91
92    fn policy(&self) -> Option<&[u8]> {
93        match self.unpacked {
94            VmProtocolData::Init { ref policy, .. } => Some(policy),
95            _ => None,
96        }
97    }
98
99    fn bytes(&self) -> &[u8] {
100        self.data
101    }
102}
103
104#[derive(Clone, Debug)]
105pub struct Envelope<'a> {
106    pub parent_id: CmdId,
107    pub author_id: DeviceId,
108    pub command_id: CmdId,
109    pub payload: Cow<'a, [u8]>,
110    pub signature: Cow<'a, [u8]>,
111}
112
113impl From<Envelope<'_>> for Struct {
114    fn from(e: Envelope<'_>) -> Self {
115        Self::new(
116            ident!("Envelope"),
117            [
118                (ident!("parent_id"), e.parent_id.into()),
119                (ident!("author_id"), e.author_id.into()),
120                (ident!("command_id"), e.command_id.into()),
121                (ident!("payload"), e.payload.into_owned().into()),
122                (ident!("signature"), e.signature.into_owned().into()),
123            ],
124        )
125    }
126}
127
128impl TryFrom<Struct> for Envelope<'_> {
129    type Error = EnvelopeError;
130
131    fn try_from(
132        Struct {
133            name,
134            ref mut fields,
135        }: Struct,
136    ) -> Result<Self, Self::Error> {
137        if name != "Envelope" {
138            return Err(EnvelopeError::InvalidName(name));
139        }
140
141        Ok(Self {
142            parent_id: get::<aranya_crypto::Id>(fields, "parent_id")?.into(),
143            author_id: get::<aranya_crypto::Id>(fields, "author_id")?.into(),
144            command_id: get::<aranya_crypto::Id>(fields, "command_id")?.into(),
145            payload: Cow::Owned(get(fields, "payload")?),
146            signature: Cow::Owned(get(fields, "signature")?),
147        })
148    }
149}
150
151fn get<T: TryFrom<Value>>(
152    fields: &mut BTreeMap<Identifier, Value>,
153    key: &'static str,
154) -> Result<T, EnvelopeError> {
155    fields
156        .remove(key)
157        .ok_or(EnvelopeError::MissingField(key))?
158        .try_into()
159        .map_err(|_| EnvelopeError::InvalidType(key))
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
163pub enum EnvelopeError {
164    #[error("invalid struct name {0:?}")]
165    InvalidName(Identifier),
166    #[error("missing field {0:?}")]
167    MissingField(&'static str),
168    #[error("invalid type for field {0:?}")]
169    InvalidType(&'static str),
170}