use crate::error::RpcError;
use serde::{Deserialize, Serialize};
pub const MAGIC: [u8; 2] = [0x4D, 0x52];
pub const VERSION: u8 = 1;
pub const HEADER_SIZE: usize = 9;
pub const MAX_PAYLOAD_SIZE: u32 = 16 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum MessageType {
Request = 0x01,
Response = 0x02,
Error = 0x03,
Ping = 0x04,
Pong = 0x05,
Cancel = 0x06,
}
impl MessageType {
pub fn from_u8(v: u8) -> Result<Self, RpcError> {
match v {
0x01 => Ok(MessageType::Request),
0x02 => Ok(MessageType::Response),
0x03 => Ok(MessageType::Error),
0x04 => Ok(MessageType::Ping),
0x05 => Ok(MessageType::Pong),
0x06 => Ok(MessageType::Cancel),
_ => Err(RpcError::invalid_argument(format!(
"Unknown message type: 0x{:02X}",
v
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flags(pub u8);
impl Flags {
pub const NONE: Flags = Flags(0);
pub const COMPRESSED: Flags = Flags(1 << 0);
pub const ONE_WAY: Flags = Flags(1 << 1);
pub fn is_one_way(self) -> bool {
self.0 & Self::ONE_WAY.0 != 0
}
pub fn is_compressed(self) -> bool {
self.0 & Self::COMPRESSED.0 != 0
}
}
#[derive(Debug, Clone)]
pub struct FrameHeader {
pub version: u8,
pub flags: Flags,
pub message_type: MessageType,
pub payload_len: u32,
}
impl FrameHeader {
pub fn encode(&self) -> [u8; HEADER_SIZE] {
let mut buf = [0u8; HEADER_SIZE];
buf[0] = MAGIC[0];
buf[1] = MAGIC[1];
buf[2] = self.version;
buf[3] = self.flags.0;
buf[4] = self.message_type as u8;
buf[5..9].copy_from_slice(&self.payload_len.to_le_bytes());
buf
}
pub fn decode(buf: &[u8; HEADER_SIZE]) -> Result<Self, RpcError> {
if buf[0] != MAGIC[0] || buf[1] != MAGIC[1] {
return Err(RpcError::invalid_argument(format!(
"Invalid magic: [{:#04X}, {:#04X}]",
buf[0], buf[1]
)));
}
let version = buf[2];
if version != VERSION {
return Err(RpcError::invalid_argument(format!(
"Unsupported version: {}",
version
)));
}
let flags = Flags(buf[3]);
let message_type = MessageType::from_u8(buf[4])?;
let payload_len = u32::from_le_bytes([buf[5], buf[6], buf[7], buf[8]]);
if payload_len > MAX_PAYLOAD_SIZE {
return Err(RpcError::invalid_argument(format!(
"Payload too large: {} > {}",
payload_len, MAX_PAYLOAD_SIZE
)));
}
Ok(Self {
version,
flags,
message_type,
payload_len,
})
}
}
#[derive(Debug, Clone)]
pub struct Frame {
pub header: FrameHeader,
pub payload: Vec<u8>,
}
impl Frame {
pub fn request(
request_id: u64,
service_id: u16,
method_id: u16,
args: Vec<u8>,
one_way: bool,
) -> Self {
let mut payload = Vec::with_capacity(12 + args.len());
payload.extend_from_slice(&request_id.to_le_bytes());
payload.extend_from_slice(&service_id.to_le_bytes());
payload.extend_from_slice(&method_id.to_le_bytes());
payload.extend_from_slice(&args);
let flags = if one_way { Flags::ONE_WAY } else { Flags::NONE };
Frame {
header: FrameHeader {
version: VERSION,
flags,
message_type: MessageType::Request,
payload_len: payload.len() as u32,
},
payload,
}
}
pub fn response(request_id: u64, data: Vec<u8>) -> Self {
let mut payload = Vec::with_capacity(8 + data.len());
payload.extend_from_slice(&request_id.to_le_bytes());
payload.extend_from_slice(&data);
Frame {
header: FrameHeader {
version: VERSION,
flags: Flags::NONE,
message_type: MessageType::Response,
payload_len: payload.len() as u32,
},
payload,
}
}
pub fn error(request_id: u64, error_data: Vec<u8>) -> Self {
let mut payload = Vec::with_capacity(8 + error_data.len());
payload.extend_from_slice(&request_id.to_le_bytes());
payload.extend_from_slice(&error_data);
Frame {
header: FrameHeader {
version: VERSION,
flags: Flags::NONE,
message_type: MessageType::Error,
payload_len: payload.len() as u32,
},
payload,
}
}
pub fn ping() -> Self {
Frame {
header: FrameHeader {
version: VERSION,
flags: Flags::NONE,
message_type: MessageType::Ping,
payload_len: 0,
},
payload: Vec::new(),
}
}
pub fn pong() -> Self {
Frame {
header: FrameHeader {
version: VERSION,
flags: Flags::NONE,
message_type: MessageType::Pong,
payload_len: 0,
},
payload: Vec::new(),
}
}
pub fn encode(&self) -> Vec<u8> {
let header_bytes = self.header.encode();
let mut buf = Vec::with_capacity(HEADER_SIZE + self.payload.len());
buf.extend_from_slice(&header_bytes);
buf.extend_from_slice(&self.payload);
buf
}
pub fn parse_request_payload(&self) -> Result<(u64, u16, u16, &[u8]), RpcError> {
if self.payload.len() < 12 {
return Err(RpcError::invalid_argument("Request payload too short"));
}
let request_id = u64::from_le_bytes(self.payload[0..8].try_into().unwrap());
let service_id = u16::from_le_bytes(self.payload[8..10].try_into().unwrap());
let method_id = u16::from_le_bytes(self.payload[10..12].try_into().unwrap());
let args = &self.payload[12..];
Ok((request_id, service_id, method_id, args))
}
pub fn parse_response_payload(&self) -> Result<(u64, &[u8]), RpcError> {
if self.payload.len() < 8 {
return Err(RpcError::invalid_argument("Response payload too short"));
}
let request_id = u64::from_le_bytes(self.payload[0..8].try_into().unwrap());
let data = &self.payload[8..];
Ok((request_id, data))
}
}
pub fn parse_frames(buf: &[u8]) -> Result<(Vec<Frame>, usize), RpcError> {
let mut frames = Vec::new();
let mut offset = 0;
while offset + HEADER_SIZE <= buf.len() {
let header_bytes: &[u8; HEADER_SIZE] = buf[offset..offset + HEADER_SIZE]
.try_into()
.map_err(|_| RpcError::internal("Header slice conversion failed"))?;
let header = FrameHeader::decode(header_bytes)?;
let total_frame_size = HEADER_SIZE + header.payload_len as usize;
if offset + total_frame_size > buf.len() {
break;
}
let payload = buf[offset + HEADER_SIZE..offset + total_frame_size].to_vec();
frames.push(Frame { header, payload });
offset += total_frame_size;
}
Ok((frames, offset))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_header_roundtrip() {
let header = FrameHeader {
version: VERSION,
flags: Flags::NONE,
message_type: MessageType::Request,
payload_len: 42,
};
let encoded = header.encode();
let decoded = FrameHeader::decode(&encoded).unwrap();
assert_eq!(decoded.version, VERSION);
assert_eq!(decoded.flags, Flags::NONE);
assert_eq!(decoded.message_type, MessageType::Request);
assert_eq!(decoded.payload_len, 42);
}
#[test]
fn test_request_frame_roundtrip() {
let frame = Frame::request(123, 1, 2, vec![10, 20, 30], false);
let bytes = frame.encode();
let (frames, consumed) = parse_frames(&bytes).unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(frames.len(), 1);
let (req_id, svc_id, method_id, args) = frames[0].parse_request_payload().unwrap();
assert_eq!(req_id, 123);
assert_eq!(svc_id, 1);
assert_eq!(method_id, 2);
assert_eq!(args, &[10, 20, 30]);
}
#[test]
fn test_response_frame_roundtrip() {
let frame = Frame::response(456, vec![1, 2, 3]);
let bytes = frame.encode();
let (frames, _) = parse_frames(&bytes).unwrap();
let (req_id, data) = frames[0].parse_response_payload().unwrap();
assert_eq!(req_id, 456);
assert_eq!(data, &[1, 2, 3]);
}
#[test]
fn test_multiple_frames() {
let f1 = Frame::request(1, 0, 0, vec![0xAA], false);
let f2 = Frame::response(1, vec![0xBB]);
let mut bytes = f1.encode();
bytes.extend_from_slice(&f2.encode());
let (frames, consumed) = parse_frames(&bytes).unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(frames.len(), 2);
assert_eq!(frames[0].header.message_type, MessageType::Request);
assert_eq!(frames[1].header.message_type, MessageType::Response);
}
#[test]
fn test_partial_frame() {
let frame = Frame::request(1, 0, 0, vec![0xAA; 100], false);
let bytes = frame.encode();
let partial = &bytes[..bytes.len() / 2];
let (frames, consumed) = parse_frames(partial).unwrap();
assert_eq!(frames.len(), 0);
assert_eq!(consumed, 0);
}
#[test]
fn test_ping_pong() {
let ping = Frame::ping();
let pong = Frame::pong();
assert_eq!(ping.header.message_type, MessageType::Ping);
assert_eq!(pong.header.message_type, MessageType::Pong);
assert_eq!(ping.payload.len(), 0);
assert_eq!(pong.payload.len(), 0);
}
#[test]
fn test_invalid_magic() {
let mut buf = [0u8; HEADER_SIZE];
buf[0] = 0xFF;
buf[1] = 0xFF;
assert!(FrameHeader::decode(&buf).is_err());
}
#[test]
fn test_one_way_flag() {
let frame = Frame::request(1, 0, 0, vec![], true);
assert!(frame.header.flags.is_one_way());
let frame = Frame::request(1, 0, 0, vec![], false);
assert!(!frame.header.flags.is_one_way());
}
}