use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const NETCODE_PROTOCOL: &str = "openrtc-netcode";
pub const NETCODE_PROTOCOL_VERSION: u8 = 1;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum EnvelopeKind {
Hello,
Peer,
User,
#[serde(rename = "state.snapshot")]
StateSnapshot,
#[serde(rename = "state.patch")]
StatePatch,
#[serde(rename = "lobby.meta")]
LobbyMeta,
#[serde(rename = "member.meta")]
MemberMeta,
Ping,
Pong,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NetcodeEnvelope<T = Value> {
pub protocol: String,
pub v: u8,
pub kind: EnvelopeKind,
#[serde(rename = "lobbyId")]
pub lobby_id: String,
pub from: String,
pub seq: u64,
#[serde(rename = "sentAt")]
pub sent_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
pub body: T,
}
impl<T> NetcodeEnvelope<T> {
pub fn new(
kind: EnvelopeKind,
lobby_id: impl Into<String>,
from: impl Into<String>,
seq: u64,
sent_at: u64,
body: T,
) -> Self {
Self {
protocol: NETCODE_PROTOCOL.to_string(),
v: NETCODE_PROTOCOL_VERSION,
kind,
lobby_id: lobby_id.into(),
from: from.into(),
seq,
sent_at,
target: None,
body,
}
}
pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
pub fn validate(&self) -> Result<(), NetcodeProtocolError> {
if self.protocol != NETCODE_PROTOCOL {
return Err(NetcodeProtocolError::ProtocolMismatch);
}
if self.v != NETCODE_PROTOCOL_VERSION {
return Err(NetcodeProtocolError::VersionMismatch(self.v));
}
if self.lobby_id.is_empty() {
return Err(NetcodeProtocolError::MissingLobbyId);
}
if self.from.is_empty() {
return Err(NetcodeProtocolError::MissingSender);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NetcodeProtocolError {
ProtocolMismatch,
VersionMismatch(u8),
MissingLobbyId,
MissingSender,
}
impl std::fmt::Display for NetcodeProtocolError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ProtocolMismatch => write!(formatter, "netcode protocol mismatch"),
Self::VersionMismatch(version) => {
write!(formatter, "unsupported netcode protocol version {version}")
}
Self::MissingLobbyId => write!(formatter, "netcode envelope missing lobby id"),
Self::MissingSender => write!(formatter, "netcode envelope missing sender"),
}
}
}
impl std::error::Error for NetcodeProtocolError {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HelloBody {
#[serde(rename = "peerId")]
pub peer_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "displayName")]
pub display_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UserMessageBody {
#[serde(rename = "type")]
pub message_type: String,
pub payload: Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StateSnapshotBody {
pub values: std::collections::BTreeMap<String, Value>,
pub owners: std::collections::BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatePatchBody {
pub key: String,
pub value: Value,
pub owner: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MetadataBody {
pub values: std::collections::BTreeMap<String, String>,
}
pub fn encode_envelope<T: Serialize>(envelope: &NetcodeEnvelope<T>) -> serde_json::Result<String> {
serde_json::to_string(envelope)
}
pub fn decode_envelope(json: &str) -> serde_json::Result<NetcodeEnvelope<Value>> {
serde_json::from_str(json)
}