Skip to main content

runmat_execution/protocol/
mod.rs

1use std::collections::BTreeSet;
2
3use minicbor::{Decoder, Encoder};
4use serde::{Deserialize, Serialize};
5
6use crate::schema::{PROTOCOL_MAJOR_V1, PROTOCOL_MINOR_V1};
7use crate::ContractError;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
10pub struct ProtocolVersion {
11    pub major: u16,
12    pub minor: u16,
13}
14
15impl ProtocolVersion {
16    pub const V1: Self = Self {
17        major: PROTOCOL_MAJOR_V1,
18        minor: PROTOCOL_MINOR_V1,
19    };
20}
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
23pub struct ProtocolLimits {
24    pub max_message_bytes: u32,
25    pub max_payload_bytes: u32,
26    pub max_collection_items: u32,
27    pub max_nesting_depth: u16,
28}
29
30impl Default for ProtocolLimits {
31    fn default() -> Self {
32        Self {
33            max_message_bytes: 16 * 1024 * 1024,
34            max_payload_bytes: 16 * 1024 * 1024 - 128,
35            max_collection_items: 1_000_000,
36            max_nesting_depth: 64,
37        }
38    }
39}
40
41#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
42pub struct ProtocolHello {
43    pub supported_majors: Vec<u16>,
44    pub maximum_minor_by_major: Vec<(u16, u16)>,
45    pub implementation: String,
46    pub capabilities: BTreeSet<String>,
47    pub limits: ProtocolLimits,
48}
49
50impl ProtocolHello {
51    pub fn v1(
52        implementation: impl Into<String>,
53        capabilities: impl IntoIterator<Item = String>,
54    ) -> Self {
55        Self {
56            supported_majors: vec![PROTOCOL_MAJOR_V1],
57            maximum_minor_by_major: vec![(PROTOCOL_MAJOR_V1, PROTOCOL_MINOR_V1)],
58            implementation: implementation.into(),
59            capabilities: capabilities.into_iter().collect(),
60            limits: ProtocolLimits::default(),
61        }
62    }
63}
64
65pub fn negotiate(
66    left: &ProtocolHello,
67    right: &ProtocolHello,
68) -> Result<ProtocolVersion, ContractError> {
69    let major = left
70        .supported_majors
71        .iter()
72        .filter(|major| right.supported_majors.contains(major))
73        .max()
74        .copied()
75        .ok_or_else(|| ContractError::invalid("protocol", "no shared protocol major"))?;
76    let left_minor = minor_for(left, major)?;
77    let right_minor = minor_for(right, major)?;
78    Ok(ProtocolVersion {
79        major,
80        minor: left_minor.min(right_minor),
81    })
82}
83
84fn minor_for(hello: &ProtocolHello, major: u16) -> Result<u16, ContractError> {
85    hello
86        .maximum_minor_by_major
87        .iter()
88        .find_map(|(candidate, minor)| (*candidate == major).then_some(*minor))
89        .ok_or_else(|| {
90            ContractError::invalid(
91                "protocol hello",
92                format!("major {major} lacks a maximum minor"),
93            )
94        })
95}
96
97#[derive(Clone, Debug, Eq, PartialEq)]
98pub struct Envelope {
99    pub version: ProtocolVersion,
100    pub message_kind: u16,
101    pub flags: u32,
102    pub sequence: u64,
103    pub payload: Vec<u8>,
104}
105
106impl Envelope {
107    pub fn encode(&self, limits: ProtocolLimits) -> Result<Vec<u8>, ContractError> {
108        if self.payload.len() > limits.max_payload_bytes as usize {
109            return Err(ContractError::Limit {
110                field: "protocol payload bytes",
111                limit: limits.max_payload_bytes.into(),
112            });
113        }
114        let mut output = Vec::with_capacity(self.payload.len() + 32);
115        let mut encoder = Encoder::new(&mut output);
116        encoder
117            .map(6)
118            .and_then(|encoder| encoder.u8(0))
119            .and_then(|encoder| encoder.u16(self.version.major))
120            .and_then(|encoder| encoder.u8(1))
121            .and_then(|encoder| encoder.u16(self.version.minor))
122            .and_then(|encoder| encoder.u8(2))
123            .and_then(|encoder| encoder.u16(self.message_kind))
124            .and_then(|encoder| encoder.u8(3))
125            .and_then(|encoder| encoder.u32(self.flags))
126            .and_then(|encoder| encoder.u8(4))
127            .and_then(|encoder| encoder.u64(self.sequence))
128            .and_then(|encoder| encoder.u8(5))
129            .and_then(|encoder| encoder.bytes(&self.payload))
130            .map_err(protocol_encode_error)?;
131        if output.len() > limits.max_message_bytes as usize {
132            return Err(ContractError::Limit {
133                field: "protocol message bytes",
134                limit: limits.max_message_bytes.into(),
135            });
136        }
137        Ok(output)
138    }
139
140    pub fn decode(bytes: &[u8], limits: ProtocolLimits) -> Result<Self, ContractError> {
141        if bytes.len() > limits.max_message_bytes as usize {
142            return Err(ContractError::Limit {
143                field: "protocol message bytes",
144                limit: limits.max_message_bytes.into(),
145            });
146        }
147        let mut decoder = Decoder::new(bytes);
148        let fields = decoder
149            .map()
150            .map_err(protocol_decode_error)?
151            .ok_or_else(|| {
152                ContractError::MalformedProtocol("indefinite maps are prohibited".into())
153            })?;
154        if fields > 64 {
155            return Err(ContractError::Limit {
156                field: "protocol envelope fields",
157                limit: 64,
158            });
159        }
160
161        let mut major = None;
162        let mut minor = None;
163        let mut message_kind = None;
164        let mut flags = None;
165        let mut sequence = None;
166        let mut payload = None;
167        let mut previous_key = None;
168
169        for _ in 0..fields {
170            let key = decoder.u16().map_err(protocol_decode_error)?;
171            if previous_key.is_some_and(|previous| previous >= key) {
172                return Err(ContractError::MalformedProtocol(
173                    "envelope keys must be unique and ascending".into(),
174                ));
175            }
176            previous_key = Some(key);
177            match key {
178                0 => major = Some(decoder.u16().map_err(protocol_decode_error)?),
179                1 => minor = Some(decoder.u16().map_err(protocol_decode_error)?),
180                2 => message_kind = Some(decoder.u16().map_err(protocol_decode_error)?),
181                3 => flags = Some(decoder.u32().map_err(protocol_decode_error)?),
182                4 => sequence = Some(decoder.u64().map_err(protocol_decode_error)?),
183                5 => {
184                    let encoded = decoder.bytes().map_err(protocol_decode_error)?;
185                    if encoded.len() > limits.max_payload_bytes as usize {
186                        return Err(ContractError::Limit {
187                            field: "protocol payload bytes",
188                            limit: limits.max_payload_bytes.into(),
189                        });
190                    }
191                    payload = Some(encoded.to_vec());
192                }
193                _ => decoder.skip().map_err(protocol_decode_error)?,
194            }
195        }
196        if decoder.position() != bytes.len() {
197            return Err(ContractError::MalformedProtocol(
198                "trailing bytes after envelope".into(),
199            ));
200        }
201        Ok(Self {
202            version: ProtocolVersion {
203                major: required("major", major)?,
204                minor: required("minor", minor)?,
205            },
206            message_kind: required("message kind", message_kind)?,
207            flags: required("flags", flags)?,
208            sequence: required("sequence", sequence)?,
209            payload: required("payload", payload)?,
210        })
211    }
212}
213
214fn required<T>(field: &'static str, value: Option<T>) -> Result<T, ContractError> {
215    value.ok_or_else(|| ContractError::MalformedProtocol(format!("missing {field}")))
216}
217
218fn protocol_encode_error<E: std::fmt::Display>(error: E) -> ContractError {
219    ContractError::MalformedProtocol(error.to_string())
220}
221
222fn protocol_decode_error(error: minicbor::decode::Error) -> ContractError {
223    ContractError::MalformedProtocol(error.to_string())
224}