use base64::Engine;
use flate2::Compression;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MessageType {
DataUpdate,
DataStream,
ChartEdit,
UserPresence,
UserActivity,
Heartbeat,
Error,
Acknowledgment,
AuthRequest,
AuthResponse,
JoinSession,
LeaveSession,
SessionUpdate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: String,
pub message_type: MessageType,
pub version: u32,
pub timestamp: u64,
pub sender_id: String,
pub recipient_id: Option<String>,
pub payload: MessagePayload,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessagePayload {
DataUpdate {
chart_id: String,
data: Vec<DataPoint>,
update_type: DataUpdateType,
},
DataStream {
stream_id: String,
data: Vec<DataPoint>,
stream_config: StreamConfig,
},
ChartEdit {
chart_id: String,
operation: ChartOperation,
element_id: Option<String>,
},
UserPresence {
user_id: String,
username: String,
status: UserStatus,
cursor_position: Option<Position>,
last_activity: u64,
},
UserActivity {
user_id: String,
activity_type: ActivityType,
target_id: Option<String>,
details: HashMap<String, String>,
},
Heartbeat {
client_time: u64,
server_time: Option<u64>,
},
Error {
code: u32,
message: String,
details: Option<String>,
recoverable: bool,
},
Acknowledgment {
original_message_id: String,
status: AcknowledgmentStatus,
timestamp: u64,
},
AuthRequest {
token: String,
session_id: Option<String>,
},
AuthResponse {
success: bool,
user_id: Option<String>,
permissions: Vec<String>,
error_message: Option<String>,
},
JoinSession {
session_id: String,
user_id: String,
permissions: Vec<String>,
},
LeaveSession {
session_id: String,
user_id: String,
reason: Option<String>,
},
SessionUpdate {
session_id: String,
update_type: SessionUpdateType,
data: HashMap<String, String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DataUpdateType {
Replace,
Append,
Modify,
Delete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamConfig {
pub frequency: u64,
pub max_batch_size: usize,
pub compression: bool,
pub filters: Vec<DataFilter>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataFilter {
pub field: String,
pub operator: FilterOperator,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum FilterOperator {
Equal,
NotEqual,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
Contains,
StartsWith,
EndsWith,
In,
NotIn,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ChartOperation {
AddElement { element: ChartElement },
RemoveElement { element_id: String },
UpdateElement {
element_id: String,
changes: ElementChanges,
},
MoveElement {
element_id: String,
position: Position,
},
ResizeElement { element_id: String, size: Size },
StyleElement {
element_id: String,
style: ElementStyle,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ChartElement {
pub id: String,
pub element_type: ElementType,
pub position: Position,
pub size: Size,
pub properties: HashMap<String, String>,
pub style: ElementStyle,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ElementType {
Point,
Line,
Bar,
Area,
Text,
Shape,
Image,
Annotation,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ElementChanges {
pub position: Option<Position>,
pub size: Option<Size>,
pub properties: HashMap<String, String>,
pub style: Option<ElementStyle>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ElementStyle {
pub fill_color: Option<String>,
pub stroke_color: Option<String>,
pub stroke_width: Option<f64>,
pub opacity: Option<f64>,
pub font_family: Option<String>,
pub font_size: Option<f64>,
pub font_weight: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Position {
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Size {
pub width: f64,
pub height: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UserStatus {
Online,
Away,
Busy,
Offline,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ActivityType {
View,
Edit,
Comment,
Share,
Export,
Import,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AcknowledgmentStatus {
Success,
Error,
Warning,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SessionUpdateType {
UserJoined,
UserLeft,
ChartUpdated,
SettingsChanged,
PermissionsChanged,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataPoint {
pub x: f64,
pub y: f64,
pub value: Option<f64>,
pub metadata: HashMap<String, String>,
}
#[derive(Error, Debug)]
pub enum MessageProtocolError {
#[error("Serialization failed: {0}")]
SerializationFailed(String),
#[error("Deserialization failed: {0}")]
DeserializationFailed(String),
#[error("Validation failed: {0}")]
ValidationFailed(String),
#[error("Compression failed: {0}")]
CompressionFailed(String),
#[error("Decompression failed: {0}")]
DecompressionFailed(String),
#[error("Invalid message type: {0}")]
InvalidMessageType(String),
#[error("Invalid protocol version: {0}")]
InvalidProtocolVersion(u32),
#[error("Message too large: {0} bytes")]
MessageTooLarge(usize),
#[error("Invalid timestamp: {0}")]
InvalidTimestamp(u64),
}
pub struct MessageProtocol {
max_message_size: usize,
compression_enabled: bool,
compression_level: Compression,
}
impl MessageProtocol {
pub fn new() -> Self {
Self {
max_message_size: 1024 * 1024, compression_enabled: true,
compression_level: Compression::default(),
}
}
pub fn with_settings(max_message_size: usize, compression_enabled: bool) -> Self {
Self {
max_message_size,
compression_enabled,
compression_level: Compression::default(),
}
}
pub fn serialize(&self, message: &Message) -> Result<String, MessageProtocolError> {
let json = serde_json::to_string(message)
.map_err(|e| MessageProtocolError::SerializationFailed(e.to_string()))?;
if json.len() > self.max_message_size {
return Err(MessageProtocolError::MessageTooLarge(json.len()));
}
if self.compression_enabled && json.len() > 1024 {
self.compress(&json)
} else {
Ok(json)
}
}
pub fn deserialize(&self, data: &str) -> Result<Message, MessageProtocolError> {
let json = if self.is_compressed(data) {
self.decompress(data)?
} else {
data.to_string()
};
let message: Message = serde_json::from_str(&json)
.map_err(|e| MessageProtocolError::DeserializationFailed(e.to_string()))?;
self.validate(&message)?;
Ok(message)
}
pub fn validate(&self, message: &Message) -> Result<(), MessageProtocolError> {
if message.version != PROTOCOL_VERSION {
return Err(MessageProtocolError::InvalidProtocolVersion(
message.version,
));
}
if message.timestamp == 0 || message.timestamp > 4102444800000 {
return Err(MessageProtocolError::InvalidTimestamp(message.timestamp));
}
if message.id.is_empty() {
return Err(MessageProtocolError::ValidationFailed(
"Message ID cannot be empty".to_string(),
));
}
if message.sender_id.is_empty() {
return Err(MessageProtocolError::ValidationFailed(
"Sender ID cannot be empty".to_string(),
));
}
Ok(())
}
fn compress(&self, data: &str) -> Result<String, MessageProtocolError> {
use flate2::write::GzEncoder;
use std::io::Write;
let mut encoder = GzEncoder::new(Vec::new(), self.compression_level);
encoder
.write_all(data.as_bytes())
.map_err(|e| MessageProtocolError::CompressionFailed(e.to_string()))?;
let compressed = encoder
.finish()
.map_err(|e| MessageProtocolError::CompressionFailed(e.to_string()))?;
Ok(base64::engine::general_purpose::STANDARD.encode(compressed))
}
fn decompress(&self, data: &str) -> Result<String, MessageProtocolError> {
use flate2::read::GzDecoder;
use std::io::Read;
let compressed = base64::engine::general_purpose::STANDARD
.decode(data)
.map_err(|e| MessageProtocolError::DecompressionFailed(e.to_string()))?;
let mut decoder = GzDecoder::new(&compressed[..]);
let mut decompressed = String::new();
decoder
.read_to_string(&mut decompressed)
.map_err(|e| MessageProtocolError::DecompressionFailed(e.to_string()))?;
Ok(decompressed)
}
fn is_compressed(&self, data: &str) -> bool {
data.starts_with("eJ") || data.starts_with("H4sI")
}
pub fn create_heartbeat(&self, sender_id: &str) -> Message {
Message {
id: uuid::Uuid::new_v4().to_string(),
message_type: MessageType::Heartbeat,
version: PROTOCOL_VERSION,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
sender_id: sender_id.to_string(),
recipient_id: None,
payload: MessagePayload::Heartbeat {
client_time: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
server_time: None,
},
metadata: HashMap::new(),
}
}
pub fn create_acknowledgment(
&self,
original_message_id: &str,
sender_id: &str,
status: AcknowledgmentStatus,
) -> Message {
Message {
id: uuid::Uuid::new_v4().to_string(),
message_type: MessageType::Acknowledgment,
version: PROTOCOL_VERSION,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
sender_id: sender_id.to_string(),
recipient_id: None,
payload: MessagePayload::Acknowledgment {
original_message_id: original_message_id.to_string(),
status,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
},
metadata: HashMap::new(),
}
}
pub fn create_error(
&self,
code: u32,
message: &str,
sender_id: &str,
recipient_id: Option<&str>,
) -> Message {
Message {
id: uuid::Uuid::new_v4().to_string(),
message_type: MessageType::Error,
version: PROTOCOL_VERSION,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
sender_id: sender_id.to_string(),
recipient_id: recipient_id.map(|s| s.to_string()),
payload: MessagePayload::Error {
code,
message: message.to_string(),
details: None,
recoverable: true,
},
metadata: HashMap::new(),
}
}
}
impl Default for MessageProtocol {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_serialization() {
let message = Message {
id: "test-id".to_string(),
message_type: MessageType::Heartbeat,
version: PROTOCOL_VERSION,
timestamp: 1234567890,
sender_id: "sender".to_string(),
recipient_id: None,
payload: MessagePayload::Heartbeat {
client_time: 1234567890,
server_time: None,
},
metadata: HashMap::new(),
};
let protocol = MessageProtocol::new();
let result = protocol.serialize(&message);
assert!(result.is_ok());
let json = result.unwrap();
assert!(!json.is_empty());
}
#[test]
fn test_message_deserialization() {
let json = r#"{
"id": "test-id",
"message_type": "Heartbeat",
"version": 1,
"timestamp": 1234567890,
"sender_id": "sender",
"recipient_id": null,
"payload": {
"Heartbeat": {
"client_time": 1234567890,
"server_time": null
}
},
"metadata": {}
}"#;
let protocol = MessageProtocol::new();
let result = protocol.deserialize(json);
assert!(result.is_ok());
let message = result.unwrap();
assert_eq!(message.id, "test-id");
assert_eq!(message.message_type, MessageType::Heartbeat);
assert_eq!(message.version, PROTOCOL_VERSION);
assert_eq!(message.timestamp, 1234567890);
assert_eq!(message.sender_id, "sender");
}
#[test]
fn test_message_validation() {
let message = Message {
id: "".to_string(), message_type: MessageType::Heartbeat,
version: PROTOCOL_VERSION,
timestamp: 1234567890,
sender_id: "sender".to_string(),
recipient_id: None,
payload: MessagePayload::Heartbeat {
client_time: 1234567890,
server_time: None,
},
metadata: HashMap::new(),
};
let protocol = MessageProtocol::new();
let result = protocol.validate(&message);
assert!(result.is_err());
assert!(matches!(
result,
Err(MessageProtocolError::ValidationFailed(_))
));
}
#[test]
fn test_message_compression() {
let mut metadata = HashMap::new();
for i in 0..1000 {
metadata.insert(format!("key_{}", i), format!("value_{}", i));
}
let message = Message {
id: "test-id".to_string(),
message_type: MessageType::DataUpdate,
version: PROTOCOL_VERSION,
timestamp: 1234567890,
sender_id: "sender".to_string(),
recipient_id: None,
payload: MessagePayload::DataUpdate {
chart_id: "chart-1".to_string(),
data: vec![],
update_type: DataUpdateType::Replace,
},
metadata,
};
let protocol = MessageProtocol::new();
let result = protocol.serialize(&message);
assert!(result.is_ok());
let compressed = result.unwrap();
assert!(!compressed.is_empty());
}
#[test]
fn test_heartbeat_message_creation() {
let protocol = MessageProtocol::new();
let message = protocol.create_heartbeat("test-sender");
assert_eq!(message.message_type, MessageType::Heartbeat);
assert_eq!(message.sender_id, "test-sender");
assert!(matches!(message.payload, MessagePayload::Heartbeat { .. }));
}
#[test]
fn test_acknowledgment_message_creation() {
let protocol = MessageProtocol::new();
let message =
protocol.create_acknowledgment("original-id", "sender", AcknowledgmentStatus::Success);
assert_eq!(message.message_type, MessageType::Acknowledgment);
assert_eq!(message.sender_id, "sender");
assert!(matches!(
message.payload,
MessagePayload::Acknowledgment { .. }
));
}
#[test]
fn test_error_message_creation() {
let protocol = MessageProtocol::new();
let message = protocol.create_error(500, "Test error", "sender", Some("recipient"));
assert_eq!(message.message_type, MessageType::Error);
assert_eq!(message.sender_id, "sender");
assert_eq!(message.recipient_id, Some("recipient".to_string()));
assert!(matches!(message.payload, MessagePayload::Error { .. }));
}
#[test]
fn test_protocol_version_validation() {
let message = Message {
id: "test-id".to_string(),
message_type: MessageType::Heartbeat,
version: 999, timestamp: 1234567890,
sender_id: "sender".to_string(),
recipient_id: None,
payload: MessagePayload::Heartbeat {
client_time: 1234567890,
server_time: None,
},
metadata: HashMap::new(),
};
let protocol = MessageProtocol::new();
let result = protocol.validate(&message);
assert!(result.is_err());
assert!(matches!(
result,
Err(MessageProtocolError::InvalidProtocolVersion(999))
));
}
#[test]
fn test_timestamp_validation() {
let message = Message {
id: "test-id".to_string(),
message_type: MessageType::Heartbeat,
version: PROTOCOL_VERSION,
timestamp: 0, sender_id: "sender".to_string(),
recipient_id: None,
payload: MessagePayload::Heartbeat {
client_time: 1234567890,
server_time: None,
},
metadata: HashMap::new(),
};
let protocol = MessageProtocol::new();
let result = protocol.validate(&message);
assert!(result.is_err());
assert!(matches!(
result,
Err(MessageProtocolError::InvalidTimestamp(0))
));
}
}