nomoreide_remote_protocol/
envelope.rs1use super::device_bound::DeviceBound;
24use super::errors::{ErrorCode, ProtocolError};
25use super::limits;
26use super::platform_bound::PlatformBound;
27use super::version::{PROTOCOL_VERSION, SUPPORTED_VERSIONS};
28use chrono::{DateTime, Duration as ChronoDuration, Utc};
29use serde::{Deserialize, Serialize};
30
31pub const MAX_IDENTIFIER_BYTES: usize = 128;
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct Envelope<T> {
41 pub v: u32,
42 pub id: String,
45 pub device_id: String,
46 pub sent_at: DateTime<Utc>,
47 pub reply_to: Option<String>,
50 pub body: T,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase", deny_unknown_fields)]
59struct RawEnvelope {
60 v: u32,
61 id: String,
62 #[serde(rename = "type")]
63 kind: String,
64 device_id: String,
65 sent_at: DateTime<Utc>,
66 #[serde(default)]
67 reply_to: Option<String>,
68 payload: serde_json::Value,
69}
70
71impl<T> Envelope<T> {
72 pub fn new(
79 id: impl Into<String>,
80 device_id: impl Into<String>,
81 sent_at: DateTime<Utc>,
82 body: T,
83 ) -> Self {
84 Self::at_version(PROTOCOL_VERSION, id, device_id, sent_at, body)
85 }
86
87 pub fn at_version(
95 version: u32,
96 id: impl Into<String>,
97 device_id: impl Into<String>,
98 sent_at: DateTime<Utc>,
99 body: T,
100 ) -> Self {
101 Self {
102 v: version,
103 id: id.into(),
104 device_id: device_id.into(),
105 sent_at,
106 reply_to: None,
107 body,
108 }
109 }
110
111 pub fn in_reply_to(mut self, request_id: impl Into<String>) -> Self {
112 self.reply_to = Some(request_id.into());
113 self
114 }
115}
116
117macro_rules! encoder {
123 ($body:ty, $name:ident) => {
124 pub fn $name(frame: &Envelope<$body>) -> serde_json::Value {
127 serde_json::json!({
128 "v": frame.v,
129 "id": frame.id,
130 "type": frame.body.kind(),
131 "deviceId": frame.device_id,
132 "sentAt": frame.sent_at.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
133 "replyTo": frame.reply_to,
134 "payload": frame.body.payload(),
135 })
136 }
137 };
138}
139
140encoder!(DeviceBound, encode_device_bound);
141encoder!(PlatformBound, encode_platform_bound);
142
143pub fn parse_device_bound(
145 raw: &[u8],
146 now: DateTime<Utc>,
147) -> Result<Envelope<DeviceBound>, ProtocolError> {
148 let envelope = parse_header(raw)?;
149 let body = DeviceBound::parse(&envelope.kind, envelope.payload.clone())?;
150 if envelope.reply_to.is_some() {
153 return Err(ProtocolError::new(
154 ErrorCode::MalformedFrame,
155 "A command must not name a request it replies to.",
156 ));
157 }
158 finish(envelope, body, now)
159}
160
161pub fn parse_platform_bound(
163 raw: &[u8],
164 now: DateTime<Utc>,
165) -> Result<Envelope<PlatformBound>, ProtocolError> {
166 let envelope = parse_header(raw)?;
167 let body = PlatformBound::parse(&envelope.kind, envelope.payload.clone())?;
168 if body.requires_reply_to() != envelope.reply_to.is_some() {
169 return Err(ProtocolError::new(
170 ErrorCode::MalformedFrame,
171 "This event's `replyTo` does not match what its type requires.",
172 )
173 .with_detail(body.kind()));
174 }
175 finish(envelope, body, now)
176}
177
178fn parse_header(raw: &[u8]) -> Result<RawEnvelope, ProtocolError> {
180 if raw.len() > limits::MAX_FRAME_BYTES {
181 return Err(ProtocolError::new(
182 ErrorCode::FrameTooLarge,
183 "Frame exceeds the protocol's maximum size.",
184 )
185 .with_detail(raw.len().to_string()));
186 }
187 let envelope: RawEnvelope = serde_json::from_slice(raw).map_err(|error| {
188 ProtocolError::new(ErrorCode::MalformedFrame, "Frame is not a valid envelope.")
189 .with_detail(error.to_string())
190 })?;
191 if !SUPPORTED_VERSIONS.contains(&envelope.v) {
201 return Err(ProtocolError::new(
202 ErrorCode::UnsupportedProtocolVersion,
203 "This build does not speak that protocol version.",
204 )
205 .with_detail(envelope.v.to_string()));
206 }
207 check_identifier("id", &envelope.id)?;
208 check_identifier("deviceId", &envelope.device_id)?;
209 if let Some(reply_to) = &envelope.reply_to {
210 check_identifier("replyTo", reply_to)?;
211 }
212 Ok(envelope)
213}
214
215fn finish<T>(
217 envelope: RawEnvelope,
218 body: T,
219 now: DateTime<Utc>,
220) -> Result<Envelope<T>, ProtocolError> {
221 let age = now.signed_duration_since(envelope.sent_at);
222 let max_age = ChronoDuration::from_std(limits::MAX_REQUEST_AGE).expect("in range");
223 let max_ahead = ChronoDuration::from_std(limits::MAX_CLOCK_SKEW_AHEAD).expect("in range");
224 if age > max_age || age < -max_ahead {
225 return Err(ProtocolError::new(
226 ErrorCode::StaleRequest,
227 "Frame is outside the accepted time window.",
228 )
229 .with_detail(envelope.sent_at.to_rfc3339()));
230 }
231 Ok(Envelope {
232 v: envelope.v,
233 id: envelope.id,
234 device_id: envelope.device_id,
235 sent_at: envelope.sent_at,
236 reply_to: envelope.reply_to,
237 body,
238 })
239}
240
241fn check_identifier(field: &str, value: &str) -> Result<(), ProtocolError> {
244 if value.is_empty() {
245 return Err(
246 ProtocolError::new(ErrorCode::MalformedFrame, "Identifier is empty.")
247 .with_detail(field.to_string()),
248 );
249 }
250 if value.len() > MAX_IDENTIFIER_BYTES {
251 return Err(
252 ProtocolError::new(ErrorCode::MalformedFrame, "Identifier is too long.")
253 .with_detail(field.to_string()),
254 );
255 }
256 if !value
257 .bytes()
258 .all(|byte| byte.is_ascii_graphic() && byte != b'"')
259 {
260 return Err(ProtocolError::new(
261 ErrorCode::MalformedFrame,
262 "Identifier contains characters an identifier may not.",
263 )
264 .with_detail(field.to_string()));
265 }
266 Ok(())
267}