1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4pub const NETCODE_PROTOCOL: &str = "openrtc-netcode";
5pub const NETCODE_PROTOCOL_VERSION: u8 = 1;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "kebab-case")]
9pub enum EnvelopeKind {
10 Hello,
11 Peer,
12 User,
13 #[serde(rename = "state.snapshot")]
14 StateSnapshot,
15 #[serde(rename = "state.patch")]
16 StatePatch,
17 #[serde(rename = "lobby.meta")]
18 LobbyMeta,
19 #[serde(rename = "member.meta")]
20 MemberMeta,
21 Ping,
22 Pong,
23}
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct NetcodeEnvelope<T = Value> {
27 pub protocol: String,
28 pub v: u8,
29 pub kind: EnvelopeKind,
30 #[serde(rename = "lobbyId")]
31 pub lobby_id: String,
32 pub from: String,
33 pub seq: u64,
34 #[serde(rename = "sentAt")]
35 pub sent_at: u64,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub target: Option<String>,
38 pub body: T,
39}
40
41impl<T> NetcodeEnvelope<T> {
42 pub fn new(
43 kind: EnvelopeKind,
44 lobby_id: impl Into<String>,
45 from: impl Into<String>,
46 seq: u64,
47 sent_at: u64,
48 body: T,
49 ) -> Self {
50 Self {
51 protocol: NETCODE_PROTOCOL.to_string(),
52 v: NETCODE_PROTOCOL_VERSION,
53 kind,
54 lobby_id: lobby_id.into(),
55 from: from.into(),
56 seq,
57 sent_at,
58 target: None,
59 body,
60 }
61 }
62
63 pub fn with_target(mut self, target: impl Into<String>) -> Self {
64 self.target = Some(target.into());
65 self
66 }
67
68 pub fn validate(&self) -> Result<(), NetcodeProtocolError> {
69 if self.protocol != NETCODE_PROTOCOL {
70 return Err(NetcodeProtocolError::ProtocolMismatch);
71 }
72 if self.v != NETCODE_PROTOCOL_VERSION {
73 return Err(NetcodeProtocolError::VersionMismatch(self.v));
74 }
75 if self.lobby_id.is_empty() {
76 return Err(NetcodeProtocolError::MissingLobbyId);
77 }
78 if self.from.is_empty() {
79 return Err(NetcodeProtocolError::MissingSender);
80 }
81 Ok(())
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum NetcodeProtocolError {
87 ProtocolMismatch,
88 VersionMismatch(u8),
89 MissingLobbyId,
90 MissingSender,
91}
92
93impl std::fmt::Display for NetcodeProtocolError {
94 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 Self::ProtocolMismatch => write!(formatter, "netcode protocol mismatch"),
97 Self::VersionMismatch(version) => {
98 write!(formatter, "unsupported netcode protocol version {version}")
99 }
100 Self::MissingLobbyId => write!(formatter, "netcode envelope missing lobby id"),
101 Self::MissingSender => write!(formatter, "netcode envelope missing sender"),
102 }
103 }
104}
105
106impl std::error::Error for NetcodeProtocolError {}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct HelloBody {
110 #[serde(rename = "peerId")]
111 pub peer_id: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 #[serde(rename = "displayName")]
114 pub display_name: Option<String>,
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct UserMessageBody {
119 #[serde(rename = "type")]
120 pub message_type: String,
121 pub payload: Value,
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct StateSnapshotBody {
126 pub values: std::collections::BTreeMap<String, Value>,
127 pub owners: std::collections::BTreeMap<String, String>,
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub struct StatePatchBody {
132 pub key: String,
133 pub value: Value,
134 pub owner: String,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct MetadataBody {
139 pub values: std::collections::BTreeMap<String, String>,
140}
141
142pub fn encode_envelope<T: Serialize>(envelope: &NetcodeEnvelope<T>) -> serde_json::Result<String> {
143 serde_json::to_string(envelope)
144}
145
146pub fn decode_envelope(json: &str) -> serde_json::Result<NetcodeEnvelope<Value>> {
147 serde_json::from_str(json)
148}