use std::collections::HashMap;
use bytes::{Buf, BufMut, BytesMut};
use serde::{Deserialize, Serialize};
use super::config::{NodeClass, NodeIdentity};
use crate::{Op, VersionVector};
const MAX_FRAME_SIZE: usize = 4 * 1024 * 1024;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ControlMessage {
Handshake(HandshakePayload),
Swim(Vec<u8>),
RegistrySync(Vec<Op>),
RegistrySyncRequest(VersionVector),
Ping,
Pong,
Departure(NodeIdentity),
SpawnActor(SpawnRequest),
SpawnAckOk { request_id: u64, label: String },
SpawnAckErr { request_id: u64, error: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpawnRequest {
pub request_id: u64,
pub label: String,
pub actor_type_name: String,
pub initial_state: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandshakePayload {
pub identity: NodeIdentity,
pub cookie: String,
pub type_manifest: Vec<String>,
pub protocol_version: u32,
pub node_class: NodeClass,
pub node_metadata: HashMap<String, String>,
#[serde(default)]
pub is_edge_client: bool,
}
pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamInit {
pub actor_label: String,
}
pub struct FrameCodec {
buffer: BytesMut,
expected_length: Option<usize>,
}
impl FrameCodec {
pub fn new() -> Self {
Self {
buffer: BytesMut::new(),
expected_length: None,
}
}
pub fn push_data(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
pub fn next_frame(&mut self) -> Result<Option<Vec<u8>>, std::io::Error> {
if self.expected_length.is_none() {
if self.buffer.len() < 4 {
return Ok(None);
}
let length = u32::from_le_bytes([
self.buffer[0],
self.buffer[1],
self.buffer[2],
self.buffer[3],
]) as usize;
if length > MAX_FRAME_SIZE {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("frame too large: {length} bytes (max {MAX_FRAME_SIZE})"),
));
}
self.expected_length = Some(length);
}
let expected = self.expected_length.unwrap();
if self.buffer.len() < 4 + expected {
return Ok(None);
}
self.buffer.advance(4);
let payload = self.buffer.split_to(expected).to_vec();
self.expected_length = None;
Ok(Some(payload))
}
pub fn encode_frame(data: &[u8]) -> Vec<u8> {
let mut buf = BytesMut::with_capacity(4 + data.len());
buf.put_u32_le(data.len() as u32);
buf.put_slice(data);
buf.to_vec()
}
}
impl Default for FrameCodec {
fn default() -> Self {
Self::new()
}
}
pub fn encode_message<T: Serialize>(msg: &T) -> Result<Vec<u8>, String> {
let payload = bincode::serde::encode_to_vec(msg, bincode::config::standard())
.map_err(|e| e.to_string())?;
Ok(FrameCodec::encode_frame(&payload))
}
pub fn decode_message<T: for<'de> Deserialize<'de>>(data: &[u8]) -> Result<T, String> {
let (val, _): (T, _) = bincode::serde::decode_from_slice(data, bincode::config::standard())
.map_err(|e| e.to_string())?;
Ok(val)
}
pub fn encode_invocation_frame(call_id: u64, message_type: &str, payload: &[u8]) -> Vec<u8> {
let type_bytes = message_type.as_bytes();
let body_len = 8 + 2 + type_bytes.len() + payload.len();
let mut buf = Vec::with_capacity(4 + body_len);
buf.extend_from_slice(&(body_len as u32).to_le_bytes());
buf.extend_from_slice(&call_id.to_le_bytes());
buf.extend_from_slice(&(type_bytes.len() as u16).to_le_bytes());
buf.extend_from_slice(type_bytes);
buf.extend_from_slice(payload);
buf
}
pub struct DecodedInvocation<'a> {
pub call_id: u64,
pub message_type: &'a str,
pub payload: &'a [u8],
}
pub fn decode_invocation_frame(data: &[u8]) -> Result<DecodedInvocation<'_>, String> {
if data.len() < 10 {
return Err("invocation frame too short".into());
}
let call_id = u64::from_le_bytes(data[0..8].try_into().unwrap());
let type_len = u16::from_le_bytes(data[8..10].try_into().unwrap()) as usize;
if data.len() < 10 + type_len {
return Err(format!(
"invocation frame too short for message type (need {}, have {})",
10 + type_len,
data.len()
));
}
let message_type = std::str::from_utf8(&data[10..10 + type_len])
.map_err(|e| format!("invalid message type UTF-8: {e}"))?;
let payload = &data[10 + type_len..];
Ok(DecodedInvocation {
call_id,
message_type,
payload,
})
}
pub fn encode_response_frame(call_id: u64, result: &Result<Vec<u8>, String>) -> Vec<u8> {
let (status, body): (u8, &[u8]) = match result {
Ok(payload) => (1, payload.as_slice()),
Err(error) => (0, error.as_bytes()),
};
let body_len = 8 + 1 + body.len();
let mut buf = Vec::with_capacity(4 + body_len);
buf.extend_from_slice(&(body_len as u32).to_le_bytes());
buf.extend_from_slice(&call_id.to_le_bytes());
buf.push(status);
buf.extend_from_slice(body);
buf
}
pub struct DecodedResponse<'a> {
pub call_id: u64,
pub result: Result<&'a [u8], String>,
}
pub fn decode_response_frame(data: &[u8]) -> Result<DecodedResponse<'_>, String> {
if data.len() < 9 {
return Err("response frame too short".into());
}
let call_id = u64::from_le_bytes(data[0..8].try_into().unwrap());
let status = data[8];
let body = &data[9..];
let result = if status == 1 {
Ok(body)
} else {
Err(String::from_utf8_lossy(body).into_owned())
};
Ok(DecodedResponse { call_id, result })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_codec_roundtrip() {
let original = b"hello, cluster!";
let frame = FrameCodec::encode_frame(original);
let mut codec = FrameCodec::new();
codec.push_data(&frame);
let decoded = codec.next_frame().unwrap().unwrap();
assert_eq!(decoded, original);
}
#[test]
fn test_frame_codec_incremental() {
let original = b"incremental data";
let frame = FrameCodec::encode_frame(original);
let mut codec = FrameCodec::new();
for (i, byte) in frame.iter().enumerate() {
codec.push_data(std::slice::from_ref(byte));
let result = codec.next_frame().unwrap();
if i < frame.len() - 1 {
assert!(result.is_none(), "should not yield frame at byte {i}");
} else {
assert_eq!(result.unwrap(), original);
}
}
}
#[test]
fn test_frame_codec_multiple_frames() {
let msg1 = b"first";
let msg2 = b"second";
let mut wire = FrameCodec::encode_frame(msg1);
wire.extend_from_slice(&FrameCodec::encode_frame(msg2));
let mut codec = FrameCodec::new();
codec.push_data(&wire);
assert_eq!(codec.next_frame().unwrap().unwrap(), msg1);
assert_eq!(codec.next_frame().unwrap().unwrap(), msg2);
assert!(codec.next_frame().unwrap().is_none());
}
#[test]
fn test_control_message_serde() {
let msg = ControlMessage::Ping;
let frame = encode_message(&msg).unwrap();
let payload = &frame[4..];
let decoded: ControlMessage = decode_message(payload).unwrap();
assert!(matches!(decoded, ControlMessage::Ping));
}
#[test]
fn test_frame_too_large() {
let mut codec = FrameCodec::new();
let len = (5 * 1024 * 1024u32).to_le_bytes();
codec.push_data(&len);
codec.push_data(&[0u8; 10]);
let result = codec.next_frame();
assert!(result.is_err());
}
#[test]
fn test_empty_data_returns_none() {
let mut codec = FrameCodec::new();
codec.push_data(&[]);
assert!(codec.next_frame().unwrap().is_none());
}
#[test]
fn test_truncated_length_prefix() {
let mut codec = FrameCodec::new();
codec.push_data(&[0x02]);
assert!(codec.next_frame().unwrap().is_none());
codec.push_data(&[0x00, 0x00]);
assert!(codec.next_frame().unwrap().is_none());
codec.push_data(&[0x00]);
assert!(codec.next_frame().unwrap().is_none());
codec.push_data(b"hi");
assert_eq!(codec.next_frame().unwrap().unwrap(), b"hi");
}
#[test]
fn test_partial_payload_returns_none() {
let frame = FrameCodec::encode_frame(b"hello");
let mut codec = FrameCodec::new();
codec.push_data(&frame[..7]);
assert!(codec.next_frame().unwrap().is_none());
codec.push_data(&frame[7..]);
assert_eq!(codec.next_frame().unwrap().unwrap(), b"hello");
}
#[test]
fn test_garbage_after_valid_frame() {
let valid_frame = FrameCodec::encode_frame(b"good");
let garbage: &[u8] = &[0xff, 0xfe, 0x01];
let mut codec = FrameCodec::new();
codec.push_data(&valid_frame);
codec.push_data(garbage);
assert_eq!(codec.next_frame().unwrap().unwrap(), b"good");
assert!(codec.next_frame().unwrap().is_none());
}
#[test]
fn test_frame_exactly_at_max_size() {
let mut codec = FrameCodec::new();
let len = (MAX_FRAME_SIZE as u32).to_le_bytes();
codec.push_data(&len);
assert!(codec.next_frame().unwrap().is_none());
}
#[test]
fn test_frame_one_over_max_size() {
let mut codec = FrameCodec::new();
let len = ((MAX_FRAME_SIZE + 1) as u32).to_le_bytes();
codec.push_data(&len);
assert!(codec.next_frame().is_err());
}
#[test]
fn test_zero_length_frame() {
let frame = FrameCodec::encode_frame(b"");
assert_eq!(frame, vec![0, 0, 0, 0]);
let mut codec = FrameCodec::new();
codec.push_data(&frame);
let result = codec.next_frame().unwrap().unwrap();
assert_eq!(result, Vec::<u8>::new());
}
#[test]
fn test_multiple_frames_with_partial_interleaved() {
let frame1 = FrameCodec::encode_frame(b"alpha");
let frame2 = FrameCodec::encode_frame(b"beta");
let mut codec = FrameCodec::new();
codec.push_data(&frame1);
codec.push_data(&frame2[..3]);
assert_eq!(codec.next_frame().unwrap().unwrap(), b"alpha");
assert!(codec.next_frame().unwrap().is_none());
codec.push_data(&frame2[3..]);
assert_eq!(codec.next_frame().unwrap().unwrap(), b"beta");
}
#[test]
fn test_encode_decode_control_message_departure() {
let identity = NodeIdentity::new("test-node", "127.0.0.1", 9000);
let msg = ControlMessage::Departure(identity.clone());
let frame = encode_message(&msg).unwrap();
let payload = &frame[4..];
let decoded: ControlMessage = decode_message(payload).unwrap();
match decoded {
ControlMessage::Departure(decoded_identity) => {
assert_eq!(decoded_identity.name, identity.name);
assert_eq!(decoded_identity.host, identity.host);
assert_eq!(decoded_identity.port, identity.port);
assert_eq!(decoded_identity.incarnation, identity.incarnation);
}
other => panic!("expected Departure, got {other:?}"),
}
}
#[test]
fn test_invocation_frame_roundtrip() {
let call_id = 42u64;
let message_type = "counter::Increment";
let payload = b"some-serialized-message-bytes";
let frame = encode_invocation_frame(call_id, message_type, payload);
let body = &frame[4..];
let decoded = decode_invocation_frame(body).unwrap();
assert_eq!(decoded.call_id, call_id);
assert_eq!(decoded.message_type, message_type);
assert_eq!(decoded.payload, payload);
}
#[test]
fn test_invocation_frame_empty_payload() {
let frame = encode_invocation_frame(1, "test::Empty", &[]);
let body = &frame[4..];
let decoded = decode_invocation_frame(body).unwrap();
assert_eq!(decoded.call_id, 1);
assert_eq!(decoded.message_type, "test::Empty");
assert!(decoded.payload.is_empty());
}
#[test]
fn test_invocation_frame_through_codec() {
let frame = encode_invocation_frame(99, "actor::Ping", b"hello");
let mut codec = FrameCodec::new();
codec.push_data(&frame);
let body = codec.next_frame().unwrap().unwrap();
let decoded = decode_invocation_frame(&body).unwrap();
assert_eq!(decoded.call_id, 99);
assert_eq!(decoded.message_type, "actor::Ping");
assert_eq!(decoded.payload, b"hello");
}
#[test]
fn test_invocation_frame_too_short() {
assert!(decode_invocation_frame(&[0u8; 5]).is_err());
}
#[test]
fn test_response_frame_roundtrip_ok() {
let call_id = 42u64;
let result_bytes = vec![1, 2, 3, 4, 5];
let frame = encode_response_frame(call_id, &Ok(result_bytes.clone()));
let body = &frame[4..];
let decoded = decode_response_frame(body).unwrap();
assert_eq!(decoded.call_id, call_id);
assert_eq!(decoded.result.unwrap(), &result_bytes[..]);
}
#[test]
fn test_response_frame_roundtrip_err() {
let call_id = 7u64;
let error = "actor not found".to_string();
let frame = encode_response_frame(call_id, &Err(error.clone()));
let body = &frame[4..];
let decoded = decode_response_frame(body).unwrap();
assert_eq!(decoded.call_id, call_id);
assert_eq!(decoded.result.unwrap_err(), error);
}
#[test]
fn test_response_frame_through_codec() {
let frame = encode_response_frame(55, &Ok(vec![10, 20, 30]));
let mut codec = FrameCodec::new();
codec.push_data(&frame);
let body = codec.next_frame().unwrap().unwrap();
let decoded = decode_response_frame(&body).unwrap();
assert_eq!(decoded.call_id, 55);
assert_eq!(decoded.result.unwrap(), &[10, 20, 30]);
}
#[test]
fn test_response_frame_too_short() {
assert!(decode_response_frame(&[0u8; 5]).is_err());
}
#[test]
fn test_multiple_invocation_frames_through_codec() {
let frame1 = encode_invocation_frame(1, "a::B", b"first");
let frame2 = encode_invocation_frame(2, "c::D", b"second");
let mut codec = FrameCodec::new();
codec.push_data(&frame1);
codec.push_data(&frame2);
let body1 = codec.next_frame().unwrap().unwrap();
let d1 = decode_invocation_frame(&body1).unwrap();
assert_eq!(d1.call_id, 1);
assert_eq!(d1.message_type, "a::B");
assert_eq!(d1.payload, b"first");
let body2 = codec.next_frame().unwrap().unwrap();
let d2 = decode_invocation_frame(&body2).unwrap();
assert_eq!(d2.call_id, 2);
assert_eq!(d2.message_type, "c::D");
assert_eq!(d2.payload, b"second");
}
}