use super::flare::core::commands::{
command::Type as CommandType,
notification_command::Type as NotificationType,
payload_command::Type as PayloadType,
system_command::{SerializationFormat, Type as SystemType},
};
use super::flare::core::{
Frame, Reliability,
commands::{Command, CustomCommand, NotificationCommand, PayloadCommand, SystemCommand},
};
use crate::common::platform;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn generate_message_id() -> String {
let timestamp = platform::wall_clock_ms();
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{}-{:016x}", timestamp, counter)
}
pub fn current_timestamp() -> u64 {
platform::wall_clock_ms()
}
fn create_base_system_command(r#type: SystemType, format: SerializationFormat) -> SystemCommand {
SystemCommand {
r#type: r#type as i32,
format: format as i32,
message: String::new(),
metadata: HashMap::new(),
data: Vec::new(),
compression: String::new(),
encryption: String::new(),
}
}
fn create_system_command_with_message(
r#type: SystemType,
format: SerializationFormat,
message: impl Into<String>,
metadata: Option<HashMap<String, Vec<u8>>>,
) -> SystemCommand {
SystemCommand {
r#type: r#type as i32,
format: format as i32,
message: message.into(),
metadata: metadata.unwrap_or_default(),
data: Vec::new(),
compression: String::new(),
encryption: String::new(),
}
}
fn create_system_command_with_data(
r#type: SystemType,
format: SerializationFormat,
message: impl Into<String>,
metadata: Option<HashMap<String, Vec<u8>>>,
data: Option<Vec<u8>>,
) -> SystemCommand {
SystemCommand {
r#type: r#type as i32,
format: format as i32,
message: message.into(),
metadata: metadata.unwrap_or_default(),
data: data.unwrap_or_default(),
compression: String::new(),
encryption: String::new(),
}
}
pub struct FrameBuilder {
command: Option<Command>,
message_id: Option<String>,
reliability: Reliability,
timestamp: Option<u64>,
metadata: HashMap<String, Vec<u8>>,
}
impl FrameBuilder {
pub fn new() -> Self {
Self {
command: None,
message_id: None,
reliability: Reliability::BestEffort,
timestamp: None,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn with_command(mut self, command: Command) -> Self {
self.command = Some(command);
self
}
#[must_use]
pub fn with_message_id(mut self, message_id: String) -> Self {
self.message_id = Some(message_id);
self
}
#[must_use]
pub fn with_reliability(mut self, reliability: Reliability) -> Self {
self.reliability = reliability;
self
}
#[must_use]
pub fn with_timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = Some(timestamp);
self
}
#[must_use]
pub fn with_metadata(mut self, key: String, value: Vec<u8>) -> Self {
self.metadata.insert(key, value);
self
}
#[must_use]
pub fn with_metadata_str(mut self, key: String, value: String) -> Self {
self.metadata.insert(key, value.into_bytes());
self
}
pub fn build(self) -> Frame {
Frame {
command: self.command,
message_id: self.message_id.unwrap_or_else(generate_message_id),
reliability: self.reliability as i32,
timestamp: self.timestamp.unwrap_or_else(current_timestamp),
metadata: self.metadata,
}
}
}
impl Default for FrameBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn ping() -> SystemCommand {
create_base_system_command(SystemType::Ping, SerializationFormat::Protobuf)
}
pub fn pong() -> SystemCommand {
create_base_system_command(SystemType::Pong, SerializationFormat::Protobuf)
}
pub fn connect(format: SerializationFormat, metadata: HashMap<String, Vec<u8>>) -> SystemCommand {
SystemCommand {
r#type: SystemType::Connect as i32,
format: format as i32,
message: String::new(),
metadata,
data: Vec::new(),
compression: String::new(),
encryption: String::new(),
}
}
pub fn connect_ack(
format: SerializationFormat,
compression: Option<&str>,
encryption: Option<&str>,
metadata: HashMap<String, Vec<u8>>,
) -> SystemCommand {
SystemCommand {
r#type: SystemType::ConnectAck as i32,
format: format as i32,
message: String::new(),
metadata,
data: Vec::new(),
compression: compression.unwrap_or("none").to_string(),
encryption: encryption.unwrap_or("none").to_string(),
}
}
pub fn close(message: Option<String>, metadata: Option<HashMap<String, Vec<u8>>>) -> SystemCommand {
create_system_command_with_message(
SystemType::Close,
SerializationFormat::Protobuf,
message.unwrap_or_default(),
metadata,
)
}
pub fn error(message: String, metadata: Option<HashMap<String, Vec<u8>>>) -> SystemCommand {
create_system_command_with_message(
SystemType::Error,
SerializationFormat::Protobuf,
message,
metadata,
)
}
pub fn event(
message: String,
metadata: Option<HashMap<String, Vec<u8>>>,
data: Option<Vec<u8>>,
) -> SystemCommand {
create_system_command_with_data(
SystemType::Event,
SerializationFormat::Protobuf,
message,
metadata,
data,
)
}
pub fn auth(metadata: HashMap<String, Vec<u8>>, data: Option<Vec<u8>>) -> SystemCommand {
SystemCommand {
r#type: SystemType::Auth as i32,
format: SerializationFormat::Protobuf as i32,
message: String::new(),
metadata,
data: data.unwrap_or_default(),
compression: String::new(),
encryption: String::new(),
}
}
pub fn auth_ack(
message: Option<String>,
metadata: Option<HashMap<String, Vec<u8>>>,
) -> SystemCommand {
create_system_command_with_message(
SystemType::AuthAck,
SerializationFormat::Protobuf,
message.unwrap_or_default(),
metadata,
)
}
pub fn kicked(
reason: impl Into<String>,
metadata: Option<HashMap<String, Vec<u8>>>,
) -> SystemCommand {
create_system_command_with_message(
SystemType::Kicked,
SerializationFormat::Protobuf,
reason,
metadata,
)
}
fn create_payload_command(
r#type: PayloadType,
message_id: String,
payload: Vec<u8>,
metadata: Option<HashMap<String, Vec<u8>>>,
seq: Option<u64>,
) -> PayloadCommand {
PayloadCommand {
r#type: r#type as i32,
message_id,
payload,
metadata: metadata.unwrap_or_default(),
seq: seq.unwrap_or(0),
}
}
pub fn send_message(
message_id: String,
payload: Vec<u8>,
metadata: Option<HashMap<String, Vec<u8>>>,
seq: Option<u64>,
) -> PayloadCommand {
create_payload_command(PayloadType::Message, message_id, payload, metadata, seq)
}
pub fn event_message(
message_id: String,
payload: Vec<u8>,
metadata: Option<HashMap<String, Vec<u8>>>,
seq: Option<u64>,
) -> PayloadCommand {
create_payload_command(PayloadType::Event, message_id, payload, metadata, seq)
}
pub fn ack_message(
message_id: String,
metadata: Option<HashMap<String, Vec<u8>>>,
) -> PayloadCommand {
PayloadCommand {
r#type: PayloadType::Ack as i32,
message_id,
payload: Vec::new(),
metadata: metadata.unwrap_or_default(),
seq: 0,
}
}
pub fn data_message(
message_id: String,
payload: Vec<u8>,
metadata: Option<HashMap<String, Vec<u8>>>,
seq: Option<u64>,
) -> PayloadCommand {
create_payload_command(PayloadType::Data, message_id, payload, metadata, seq)
}
pub fn notification(
notification_type: NotificationType,
title: String,
content: Vec<u8>,
metadata: Option<HashMap<String, Vec<u8>>>,
) -> NotificationCommand {
NotificationCommand {
r#type: notification_type as i32,
title,
content,
metadata: metadata.unwrap_or_default(),
}
}
pub fn custom_command(
name: String,
data: Vec<u8>,
metadata: Option<HashMap<String, Vec<u8>>>,
) -> CustomCommand {
CustomCommand {
name,
data,
metadata: metadata.unwrap_or_default(),
}
}
fn create_frame_with_command(command_type: CommandType, reliability: Reliability) -> Frame {
FrameBuilder::new()
.with_command(Command {
r#type: Some(command_type),
})
.with_reliability(reliability)
.build()
}
pub fn frame_with_system_command(system_command: SystemCommand, reliability: Reliability) -> Frame {
create_frame_with_command(CommandType::System(system_command), reliability)
}
pub fn frame_with_payload_command(
mut payload_command: PayloadCommand,
reliability: Reliability,
) -> Frame {
if payload_command.message_id.is_empty() {
payload_command.message_id = generate_message_id();
}
let message_id = payload_command.message_id.clone();
FrameBuilder::new()
.with_command(Command {
r#type: Some(CommandType::Payload(payload_command)),
})
.with_message_id(message_id)
.with_reliability(reliability)
.build()
}
pub fn frame_with_message_command(
payload_command: PayloadCommand,
reliability: Reliability,
) -> Frame {
frame_with_payload_command(payload_command, reliability)
}
pub fn frame_with_notification_command(
notification_command: NotificationCommand,
reliability: Reliability,
) -> Frame {
create_frame_with_command(CommandType::Notification(notification_command), reliability)
}
pub fn frame_with_custom_command(custom_command: CustomCommand, reliability: Reliability) -> Frame {
create_frame_with_command(CommandType::Custom(custom_command), reliability)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ping_pong() {
let ping_cmd = ping();
assert_eq!(ping_cmd.r#type, SystemType::Ping as i32);
let pong_cmd = pong();
assert_eq!(pong_cmd.r#type, SystemType::Pong as i32);
}
#[test]
fn test_generate_message_id() {
let id1 = generate_message_id();
let id2 = generate_message_id();
assert_ne!(id1, id2);
}
#[test]
fn test_frame_builder() {
let frame = FrameBuilder::new()
.with_command(Command {
r#type: Some(CommandType::System(ping())),
})
.with_reliability(Reliability::AtLeastOnce)
.build();
assert!(!frame.message_id.is_empty());
assert!(frame.timestamp > 0);
}
}