1use serde::{Deserialize, Serialize};
14
15use crate::mesh::{MAX_MESSAGE_SIZE, MeshError, MeshStream};
16
17#[non_exhaustive]
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[repr(u8)]
29#[serde(rename_all = "snake_case")]
30pub enum FrameType {
31 Handshake = 0x01,
33 IpcMessage = 0x02,
35 ChainSync = 0x03,
37 TreeSync = 0x04,
39 ServiceAdvert = 0x05,
41 ProcessAdvert = 0x06,
43 Heartbeat = 0x07,
45 JoinRequest = 0x08,
47 JoinResponse = 0x09,
49 SyncDigest = 0x0A,
51 ArtifactRequest = 0x0B,
54 ArtifactResponse = 0x0C,
57 LogAggregation = 0x0D,
59 AssessmentSync = 0x0E,
61 SensorObservation = 0x0F,
65 InferRequest = 0x10,
69 InferResponse = 0x11,
72}
73
74pub type MsgType = FrameType;
76
77pub type Frame = MeshFrame;
79
80impl FrameType {
81 pub const ALL: &'static [FrameType] = &[
83 Self::Handshake,
84 Self::IpcMessage,
85 Self::ChainSync,
86 Self::TreeSync,
87 Self::ServiceAdvert,
88 Self::ProcessAdvert,
89 Self::Heartbeat,
90 Self::JoinRequest,
91 Self::JoinResponse,
92 Self::SyncDigest,
93 Self::ArtifactRequest,
94 Self::ArtifactResponse,
95 Self::LogAggregation,
96 Self::AssessmentSync,
97 Self::SensorObservation,
98 Self::InferRequest,
99 Self::InferResponse,
100 ];
101
102 pub fn from_byte(b: u8) -> Option<Self> {
105 match b {
106 0x01 => Some(Self::Handshake),
107 0x02 => Some(Self::IpcMessage),
108 0x03 => Some(Self::ChainSync),
109 0x04 => Some(Self::TreeSync),
110 0x05 => Some(Self::ServiceAdvert),
111 0x06 => Some(Self::ProcessAdvert),
112 0x07 => Some(Self::Heartbeat),
113 0x08 => Some(Self::JoinRequest),
114 0x09 => Some(Self::JoinResponse),
115 0x0A => Some(Self::SyncDigest),
116 0x0B => Some(Self::ArtifactRequest),
117 0x0C => Some(Self::ArtifactResponse),
118 0x0D => Some(Self::LogAggregation),
119 0x0E => Some(Self::AssessmentSync),
120 0x0F => Some(Self::SensorObservation),
121 0x10 => Some(Self::InferRequest),
122 0x11 => Some(Self::InferResponse),
123 _ => None,
124 }
125 }
126
127 pub const fn as_byte(self) -> u8 {
129 self as u8
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct MeshFrame {
136 pub frame_type: FrameType,
138 pub payload: Vec<u8>,
140}
141
142impl MeshFrame {
143 pub fn encode(&self) -> Result<Vec<u8>, MeshError> {
148 let payload_len = self.payload.len();
149 let total = 1 + payload_len; if total > MAX_MESSAGE_SIZE {
151 return Err(MeshError::MessageTooLarge {
152 size: total,
153 max: MAX_MESSAGE_SIZE,
154 });
155 }
156 let mut buf = Vec::with_capacity(4 + total);
157 buf.extend_from_slice(&(total as u32).to_be_bytes());
158 buf.push(self.frame_type as u8);
159 buf.extend_from_slice(&self.payload);
160 Ok(buf)
161 }
162
163 pub fn decode(data: &[u8]) -> Result<Self, MeshError> {
166 if data.is_empty() {
167 return Err(MeshError::Transport("empty frame".into()));
168 }
169 let frame_type = FrameType::from_byte(data[0]).ok_or_else(|| {
170 MeshError::Transport(format!("unknown frame type: 0x{:02x}", data[0]))
171 })?;
172 let payload = data[1..].to_vec();
173 Ok(Self {
174 frame_type,
175 payload,
176 })
177 }
178}
179
180pub async fn read_frame(stream: &mut dyn MeshStream) -> Result<MeshFrame, MeshError> {
186 let data = stream.recv().await?;
187 if data.len() > MAX_MESSAGE_SIZE + 4 {
188 return Err(MeshError::MessageTooLarge {
189 size: data.len(),
190 max: MAX_MESSAGE_SIZE,
191 });
192 }
193 if data.len() < 5 {
194 return Err(MeshError::Transport("short frame".into()));
195 }
196 let declared = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
197 if declared > MAX_MESSAGE_SIZE {
198 return Err(MeshError::MessageTooLarge {
199 size: declared,
200 max: MAX_MESSAGE_SIZE,
201 });
202 }
203 if declared != data.len() - 4 {
204 return Err(MeshError::Transport("frame length mismatch".into()));
205 }
206 MeshFrame::decode(&data[4..])
207}
208
209pub async fn write_frame(stream: &mut dyn MeshStream, frame: &MeshFrame) -> Result<(), MeshError> {
211 let encoded = frame.encode()?;
212 stream.send(&encoded).await
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn frame_type_from_byte_all_variants() {
221 let expected = [
222 (0x01, FrameType::Handshake),
223 (0x02, FrameType::IpcMessage),
224 (0x03, FrameType::ChainSync),
225 (0x04, FrameType::TreeSync),
226 (0x05, FrameType::ServiceAdvert),
227 (0x06, FrameType::ProcessAdvert),
228 (0x07, FrameType::Heartbeat),
229 (0x08, FrameType::JoinRequest),
230 (0x09, FrameType::JoinResponse),
231 (0x0A, FrameType::SyncDigest),
232 (0x0B, FrameType::ArtifactRequest),
233 (0x0C, FrameType::ArtifactResponse),
234 (0x0D, FrameType::LogAggregation),
235 (0x0E, FrameType::AssessmentSync),
236 (0x0F, FrameType::SensorObservation),
237 (0x10, FrameType::InferRequest),
238 (0x11, FrameType::InferResponse),
239 ];
240 for (byte, variant) in expected {
241 assert_eq!(FrameType::from_byte(byte), Some(variant));
242 }
243 }
244
245 #[tokio::test]
246 async fn write_then_read_frame_round_trips_and_checks_the_length() {
247 use crate::mesh_test_support::connected_pair;
248 let (mut a, mut b) = connected_pair().await.unwrap();
249 let f = MeshFrame {
250 frame_type: FrameType::InferRequest,
251 payload: vec![9, 8, 7],
252 };
253 write_frame(&mut a, &f).await.unwrap();
254 let g = read_frame(&mut b).await.unwrap();
255 assert_eq!(g.frame_type, FrameType::InferRequest);
256 assert_eq!(g.payload, vec![9, 8, 7]);
257 let mut bad = f.encode().unwrap();
259 bad[3] = bad[3].wrapping_add(1);
260 a.send(&bad).await.unwrap();
261 assert!(read_frame(&mut b).await.is_err());
262 a.send(&[0, 0, 0]).await.unwrap();
263 assert!(read_frame(&mut b).await.is_err());
264 let mut huge = vec![0u8; 8];
265 huge[..4].copy_from_slice(&(MAX_MESSAGE_SIZE as u32 + 1).to_be_bytes());
266 a.send(&huge).await.unwrap();
267 assert!(matches!(
268 read_frame(&mut b).await,
269 Err(MeshError::MessageTooLarge { .. })
270 ));
271 }
272
273 #[test]
274 fn frame_type_from_byte_unknown() {
275 assert!(FrameType::from_byte(0x00).is_none());
276 assert!(FrameType::from_byte(0x12).is_none());
277 assert!(FrameType::from_byte(0xFF).is_none());
278 }
279
280 #[test]
281 fn encode_decode_roundtrip() {
282 let frame = MeshFrame {
283 frame_type: FrameType::Heartbeat,
284 payload: vec![1, 2, 3, 4],
285 };
286 let encoded = frame.encode().unwrap();
287
288 let len = u32::from_be_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]);
290 assert_eq!(len, 5);
291
292 let decoded = MeshFrame::decode(&encoded[4..]).unwrap();
294 assert_eq!(decoded.frame_type, FrameType::Heartbeat);
295 assert_eq!(decoded.payload, vec![1, 2, 3, 4]);
296 }
297
298 #[test]
299 fn encode_decode_empty_payload() {
300 let frame = MeshFrame {
301 frame_type: FrameType::Handshake,
302 payload: vec![],
303 };
304 let encoded = frame.encode().unwrap();
305 let decoded = MeshFrame::decode(&encoded[4..]).unwrap();
306 assert_eq!(decoded.frame_type, FrameType::Handshake);
307 assert!(decoded.payload.is_empty());
308 }
309
310 #[test]
311 fn decode_empty_data_fails() {
312 let result = MeshFrame::decode(&[]);
313 assert!(result.is_err());
314 let err = result.unwrap_err();
315 assert!(err.to_string().contains("empty frame"));
316 }
317
318 #[test]
319 fn decode_unknown_type_fails() {
320 let result = MeshFrame::decode(&[0xFF, 0x01, 0x02]);
321 assert!(result.is_err());
322 let err = result.unwrap_err();
323 assert!(err.to_string().contains("unknown frame type: 0xff"));
324 }
325
326 #[test]
327 fn encode_oversized_frame_fails() {
328 let frame = MeshFrame {
329 frame_type: FrameType::IpcMessage,
330 payload: vec![0u8; MAX_MESSAGE_SIZE], };
332 let result = frame.encode();
333 assert!(result.is_err());
334 match result.unwrap_err() {
335 MeshError::MessageTooLarge { size, max } => {
336 assert_eq!(size, MAX_MESSAGE_SIZE + 1);
337 assert_eq!(max, MAX_MESSAGE_SIZE);
338 }
339 other => panic!("expected MessageTooLarge, got: {other:?}"),
340 }
341 }
342
343 #[test]
344 fn all_frame_types_encode_decode() {
345 for ft in FrameType::ALL {
346 let frame = MeshFrame {
347 frame_type: *ft,
348 payload: vec![0xCA, 0xFE],
349 };
350 let encoded = frame.encode().unwrap();
351 let decoded = MeshFrame::decode(&encoded[4..]).unwrap();
352 assert_eq!(decoded.frame_type, *ft);
353 assert_eq!(decoded.payload, vec![0xCA, 0xFE]);
354 }
355 }
356
357 #[test]
358 fn frame_type_msg_type_alias_and_serde() {
359 let t: MsgType = FrameType::ChainSync;
361 assert_eq!(t.as_byte(), 0x03);
362 let json = serde_json::to_string(&t).unwrap();
363 let restored: FrameType = serde_json::from_str(&json).unwrap();
364 assert_eq!(restored, FrameType::ChainSync);
365 }
366
367 #[test]
368 fn mesh_frame_serde_roundtrip() {
369 let frame = MeshFrame {
370 frame_type: FrameType::IpcMessage,
371 payload: vec![1, 2, 3],
372 };
373 let json = serde_json::to_string(&frame).unwrap();
374 let restored: Frame = serde_json::from_str(&json).unwrap();
375 assert_eq!(restored.frame_type, FrameType::IpcMessage);
376 assert_eq!(restored.payload, vec![1, 2, 3]);
377 }
378
379 #[test]
380 fn msg_type_all_variants_serde_roundtrip() {
381 for ft in FrameType::ALL {
382 let json = serde_json::to_string(ft).unwrap();
383 let restored: MsgType = serde_json::from_str(&json).unwrap();
384 assert_eq!(restored, *ft);
385 }
386 }
387}