use crate::mesh::{MeshError, MeshStream, MAX_MESSAGE_SIZE};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FrameType {
Handshake = 0x01,
IpcMessage = 0x02,
ChainSync = 0x03,
TreeSync = 0x04,
ServiceAdvert = 0x05,
ProcessAdvert = 0x06,
Heartbeat = 0x07,
JoinRequest = 0x08,
JoinResponse = 0x09,
SyncDigest = 0x0A,
ArtifactRequest = 0x0B,
ArtifactResponse = 0x0C,
LogAggregation = 0x0D,
AssessmentSync = 0x0E,
}
impl FrameType {
pub fn from_byte(b: u8) -> Option<Self> {
match b {
0x01 => Some(Self::Handshake),
0x02 => Some(Self::IpcMessage),
0x03 => Some(Self::ChainSync),
0x04 => Some(Self::TreeSync),
0x05 => Some(Self::ServiceAdvert),
0x06 => Some(Self::ProcessAdvert),
0x07 => Some(Self::Heartbeat),
0x08 => Some(Self::JoinRequest),
0x09 => Some(Self::JoinResponse),
0x0A => Some(Self::SyncDigest),
0x0B => Some(Self::ArtifactRequest),
0x0C => Some(Self::ArtifactResponse),
0x0D => Some(Self::LogAggregation),
0x0E => Some(Self::AssessmentSync),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct MeshFrame {
pub frame_type: FrameType,
pub payload: Vec<u8>,
}
impl MeshFrame {
pub fn encode(&self) -> Result<Vec<u8>, MeshError> {
let payload_len = self.payload.len();
let total = 1 + payload_len; if total > MAX_MESSAGE_SIZE {
return Err(MeshError::MessageTooLarge {
size: total,
max: MAX_MESSAGE_SIZE,
});
}
let mut buf = Vec::with_capacity(4 + total);
buf.extend_from_slice(&(total as u32).to_be_bytes());
buf.push(self.frame_type as u8);
buf.extend_from_slice(&self.payload);
Ok(buf)
}
pub fn decode(data: &[u8]) -> Result<Self, MeshError> {
if data.is_empty() {
return Err(MeshError::Transport("empty frame".into()));
}
let frame_type = FrameType::from_byte(data[0]).ok_or_else(|| {
MeshError::Transport(format!("unknown frame type: 0x{:02x}", data[0]))
})?;
let payload = data[1..].to_vec();
Ok(Self {
frame_type,
payload,
})
}
}
pub async fn read_frame(stream: &mut dyn MeshStream) -> Result<MeshFrame, MeshError> {
let data = stream.recv().await?;
if data.len() > MAX_MESSAGE_SIZE {
return Err(MeshError::MessageTooLarge {
size: data.len(),
max: MAX_MESSAGE_SIZE,
});
}
MeshFrame::decode(&data)
}
pub async fn write_frame(stream: &mut dyn MeshStream, frame: &MeshFrame) -> Result<(), MeshError> {
let encoded = frame.encode()?;
stream.send(&encoded).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_type_from_byte_all_variants() {
let expected = [
(0x01, FrameType::Handshake),
(0x02, FrameType::IpcMessage),
(0x03, FrameType::ChainSync),
(0x04, FrameType::TreeSync),
(0x05, FrameType::ServiceAdvert),
(0x06, FrameType::ProcessAdvert),
(0x07, FrameType::Heartbeat),
(0x08, FrameType::JoinRequest),
(0x09, FrameType::JoinResponse),
(0x0A, FrameType::SyncDigest),
(0x0B, FrameType::ArtifactRequest),
(0x0C, FrameType::ArtifactResponse),
(0x0D, FrameType::LogAggregation),
(0x0E, FrameType::AssessmentSync),
];
for (byte, variant) in expected {
assert_eq!(FrameType::from_byte(byte), Some(variant));
}
}
#[test]
fn frame_type_from_byte_unknown() {
assert!(FrameType::from_byte(0x00).is_none());
assert!(FrameType::from_byte(0x0F).is_none());
assert!(FrameType::from_byte(0xFF).is_none());
}
#[test]
fn encode_decode_roundtrip() {
let frame = MeshFrame {
frame_type: FrameType::Heartbeat,
payload: vec![1, 2, 3, 4],
};
let encoded = frame.encode().unwrap();
let len = u32::from_be_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]);
assert_eq!(len, 5);
let decoded = MeshFrame::decode(&encoded[4..]).unwrap();
assert_eq!(decoded.frame_type, FrameType::Heartbeat);
assert_eq!(decoded.payload, vec![1, 2, 3, 4]);
}
#[test]
fn encode_decode_empty_payload() {
let frame = MeshFrame {
frame_type: FrameType::Handshake,
payload: vec![],
};
let encoded = frame.encode().unwrap();
let decoded = MeshFrame::decode(&encoded[4..]).unwrap();
assert_eq!(decoded.frame_type, FrameType::Handshake);
assert!(decoded.payload.is_empty());
}
#[test]
fn decode_empty_data_fails() {
let result = MeshFrame::decode(&[]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("empty frame"));
}
#[test]
fn decode_unknown_type_fails() {
let result = MeshFrame::decode(&[0xFF, 0x01, 0x02]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("unknown frame type: 0xff"));
}
#[test]
fn encode_oversized_frame_fails() {
let frame = MeshFrame {
frame_type: FrameType::IpcMessage,
payload: vec![0u8; MAX_MESSAGE_SIZE], };
let result = frame.encode();
assert!(result.is_err());
match result.unwrap_err() {
MeshError::MessageTooLarge { size, max } => {
assert_eq!(size, MAX_MESSAGE_SIZE + 1);
assert_eq!(max, MAX_MESSAGE_SIZE);
}
other => panic!("expected MessageTooLarge, got: {other:?}"),
}
}
#[test]
fn all_frame_types_encode_decode() {
let types = [
FrameType::Handshake,
FrameType::IpcMessage,
FrameType::ChainSync,
FrameType::TreeSync,
FrameType::ServiceAdvert,
FrameType::ProcessAdvert,
FrameType::Heartbeat,
FrameType::JoinRequest,
FrameType::JoinResponse,
FrameType::SyncDigest,
FrameType::ArtifactRequest,
FrameType::ArtifactResponse,
FrameType::LogAggregation,
FrameType::AssessmentSync,
];
for ft in types {
let frame = MeshFrame {
frame_type: ft,
payload: vec![0xCA, 0xFE],
};
let encoded = frame.encode().unwrap();
let decoded = MeshFrame::decode(&encoded[4..]).unwrap();
assert_eq!(decoded.frame_type, ft);
assert_eq!(decoded.payload, vec![0xCA, 0xFE]);
}
}
}