microsandbox_protocol/
wire.rs1use std::collections::HashSet;
4use std::io::Cursor;
5
6use ciborium::Value;
7use serde::{Deserialize, Serialize, de::DeserializeOwned};
8use thiserror::Error;
9
10use crate::codec::{MAX_FRAME_SIZE, RawFrame};
11
12#[derive(Clone, Serialize, Deserialize)]
21pub struct Envelope {
22 pub v: u8,
24 pub t: String,
26 #[serde(with = "serde_bytes")]
28 pub p: Vec<u8>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
33pub enum WireError {
34 #[error("invalid or trailing CBOR data")]
36 InvalidCbor,
37 #[error("CBOR data exceeds the frame limit")]
39 TooLarge,
40 #[error("invalid protocol record")]
42 InvalidRecord,
43 #[error("duplicate protocol record key")]
45 DuplicateKey,
46 #[error("could not encode protocol record")]
48 Encode,
49}
50
51impl Envelope {
56 pub fn new(v: u8, t: impl Into<String>, payload: &impl Serialize) -> Result<Self, WireError> {
58 Ok(Self {
59 v,
60 t: t.into(),
61 p: encode(payload)?,
62 })
63 }
64
65 pub fn encode(&self) -> Result<Vec<u8>, WireError> {
67 encode(self)
68 }
69
70 pub fn decode(bytes: &[u8]) -> Result<Self, WireError> {
72 let value = decode_value(bytes)?;
73 validate_record(&value)?;
74 let Value::Map(fields) = &value else {
75 unreachable!()
76 };
77 if !fields
80 .iter()
81 .any(|(key, value)| key.as_text() == Some("p") && matches!(value, Value::Bytes(_)))
82 {
83 return Err(WireError::InvalidRecord);
84 }
85 value.deserialized().map_err(|_| WireError::InvalidRecord)
86 }
87
88 pub fn payload<T: DeserializeOwned>(&self) -> Result<T, WireError> {
90 decode_record(&self.p)
91 }
92
93 pub fn frame(&self, id: u32, flags: u8) -> Result<RawFrame, WireError> {
95 Ok(RawFrame {
96 id,
97 flags,
98 body: self.encode()?,
99 })
100 }
101}
102
103impl std::fmt::Debug for Envelope {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("Envelope")
110 .field("generation", &self.v)
111 .field("payload_bytes", &self.p.len())
112 .finish_non_exhaustive()
113 }
114}
115
116pub fn encode(value: &impl Serialize) -> Result<Vec<u8>, WireError> {
122 let mut bytes = Vec::new();
123 ciborium::ser::into_writer(value, &mut bytes).map_err(|_| WireError::Encode)?;
124 if bytes.len() > MAX_FRAME_SIZE as usize {
125 return Err(WireError::TooLarge);
126 }
127 Ok(bytes)
128}
129
130pub fn decode_record<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, WireError> {
135 let value = decode_value(bytes)?;
136 validate_record(&value)?;
137 value.deserialized().map_err(|_| WireError::InvalidRecord)
138}
139
140pub fn decode_value(bytes: &[u8]) -> Result<Value, WireError> {
142 if bytes.len() > MAX_FRAME_SIZE as usize {
143 return Err(WireError::TooLarge);
144 }
145 let mut reader = Cursor::new(bytes);
146 let value = ciborium::de::from_reader(&mut reader).map_err(|_| WireError::InvalidCbor)?;
147 if reader.position() != bytes.len() as u64 {
148 return Err(WireError::InvalidCbor);
149 }
150 Ok(value)
151}
152
153pub fn validate_record(value: &Value) -> Result<(), WireError> {
155 let Value::Map(fields) = value else {
156 return Err(WireError::InvalidRecord);
157 };
158 let mut names = HashSet::with_capacity(fields.len());
159 for (key, _) in fields {
160 let Value::Text(name) = key else {
161 return Err(WireError::InvalidRecord);
162 };
163 if !names.insert(name.as_str()) {
164 return Err(WireError::DuplicateKey);
165 }
166 }
167 Ok(())
168}