1use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18pub use rift_core::{ChannelId, MessageId, PeerId};
19use rand::rngs::OsRng;
20use rand::RngCore;
21
22const MAGIC: &[u8; 4] = b"RFT1";
23const MAX_FRAME_LEN: usize = 64 * 1024;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
27#[repr(u8)]
28pub enum ProtocolVersion {
29 V1 = 1,
30 V2 = 2,
31}
32
33impl ProtocolVersion {
34 pub fn as_u8(self) -> u8 {
36 self as u8
37 }
38
39 pub fn from_u8(value: u8) -> Option<Self> {
41 match value {
42 1 => Some(ProtocolVersion::V1),
43 2 => Some(ProtocolVersion::V2),
44 _ => None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum StreamKind {
52 Voice,
53 Text,
54 Control,
55 Custom(u16),
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60pub enum CodecId {
61 Opus,
62 PCM16,
63 Experimental(u16),
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68pub enum FeatureFlag {
69 Voice,
70 Text,
71 Relay,
72 E2EE,
73 ScreenShare,
74 DataChannel,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub struct SessionId(pub [u8; 32]);
80
81impl SessionId {
82 pub const NONE: SessionId = SessionId([0u8; 32]);
84
85 pub fn random() -> Self {
87 let mut bytes = [0u8; 32];
88 OsRng.fill_bytes(&mut bytes);
89 SessionId(bytes)
90 }
91
92 pub fn from_channel(name: &str, password: Option<&str>) -> Self {
94 let mut hasher = blake3::Hasher::new();
95 hasher.update(b"rift-channel:");
96 hasher.update(name.as_bytes());
97 if let Some(password) = password {
98 hasher.update(b":");
99 hasher.update(password.as_bytes());
100 }
101 let hash = hasher.finalize();
102 let mut bytes = [0u8; 32];
103 bytes.copy_from_slice(hash.as_bytes());
104 SessionId(bytes)
105 }
106
107 pub fn to_hex(&self) -> String {
109 hex::encode(self.0)
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct RiftFrameHeader {
116 pub version: ProtocolVersion,
118 pub stream: StreamKind,
120 pub flags: u16,
122 pub seq: u32,
124 pub timestamp: u64,
126 pub source: PeerId,
128 pub session: SessionId,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ChatMessage {
135 pub id: MessageId,
137 pub from: PeerId,
139 pub timestamp: u64,
141 pub text: String,
143}
144
145impl ChatMessage {
146 pub fn new(from: PeerId, timestamp: u64, text: String) -> Self {
148 let id = MessageId::new(from, timestamp, &text);
149 Self {
150 id,
151 from,
152 timestamp,
153 text,
154 }
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Capabilities {
161 pub supported_versions: Vec<ProtocolVersion>,
163 pub audio_codecs: Vec<CodecId>,
165 pub features: Vec<FeatureFlag>,
167 pub max_bitrate: Option<u32>,
169 pub preferred_frame_duration_ms: Option<u16>,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
175pub enum CallState {
176 Ringing,
177 Active,
178 Ended,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183pub enum GroupMode {
184 Mesh,
185 Hybrid { forwarder: PeerId },
186}
187
188pub type StreamId = u64;
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub enum GroupControl {
193 Join { session: SessionId, peer_id: PeerId },
194 Leave { session: SessionId, peer_id: PeerId },
195 StreamPublish {
196 session: SessionId,
197 stream_id: StreamId,
198 from: PeerId,
199 codec: CodecId,
200 },
201 StreamSubscribe {
202 session: SessionId,
203 stream_id: StreamId,
204 from: PeerId,
205 },
206 Topology {
207 session: SessionId,
208 mode: GroupMode,
209 },
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub enum CallControl {
215 Invite {
216 session: SessionId,
217 from: PeerId,
218 to: PeerId,
219 display_name: Option<String>,
220 #[serde(default)]
221 rndzv_srt_uri: Option<String>,
222 },
223 Accept { session: SessionId, from: PeerId },
224 Decline {
225 session: SessionId,
226 from: PeerId,
227 reason: Option<String>,
228 },
229 Bye { session: SessionId, from: PeerId },
230 Mute {
231 session: SessionId,
232 from: PeerId,
233 muted: bool,
234 },
235 SessionInfo {
236 session: SessionId,
237 participants: Vec<PeerId>,
238 },
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
243pub enum CandidateType {
244 Host,
245 Srflx,
246 Relay,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct IceCandidate {
252 pub addr: std::net::SocketAddr,
254 pub cand_type: CandidateType,
256 pub priority: u32,
258 pub foundation: u64,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264pub enum ControlMessage {
265 Join { peer_id: PeerId, display_name: Option<String> },
266 Hello {
267 peer_id: PeerId,
268 public_key: Vec<u8>,
269 capabilities: Capabilities,
270 #[serde(default)]
271 candidates: Vec<std::net::SocketAddr>,
272 },
273 IceCandidates {
274 peer_id: PeerId,
275 session: SessionId,
276 candidates: Vec<IceCandidate>,
277 },
278 IceCheck {
279 session: SessionId,
280 tie_breaker: u64,
281 candidate: IceCandidate,
282 },
283 IceCheckAck {
284 session: SessionId,
285 candidate: IceCandidate,
286 },
287 KeyInit {
288 session: SessionId,
289 eph_pub_x25519: [u8; 32],
290 sig_ed25519: Vec<u8>,
291 },
292 KeyResp {
293 session: SessionId,
294 eph_pub_x25519: [u8; 32],
295 sig_ed25519: Vec<u8>,
296 },
297 EncryptedReady {
298 session: SessionId,
299 alg: u8,
300 },
301 Leave { peer_id: PeerId },
302 PeerState { peer_id: PeerId, relay_capable: bool },
303 Chat(ChatMessage),
304 Ping { nonce: u64, sent_at_ms: u64 },
305 Pong { nonce: u64, sent_at_ms: u64 },
306 Auth { token: Vec<u8> },
307 RouteInfo { from: PeerId, to: PeerId, relayed: bool },
308 Capabilities(Capabilities),
309 CapabilitiesUpdate(Capabilities),
310 PeerList { peers: Vec<PeerInfo> },
311 Call(CallControl),
312 Group(GroupControl),
313 E2eeInit {
314 session: SessionId,
315 from: PeerId,
316 public_key: [u8; 32],
317 signature: Vec<u8>,
318 },
319 E2eeResp {
320 session: SessionId,
321 from: PeerId,
322 public_key: [u8; 32],
323 signature: Vec<u8>,
324 },
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct EncryptedPayload {
330 pub nonce: [u8; 12],
332 pub ciphertext: Vec<u8>,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct PeerInfo {
339 pub peer_id: PeerId,
341 pub addr: std::net::SocketAddr,
343 #[serde(default)]
345 pub addrs: Vec<std::net::SocketAddr>,
346 pub relay_capable: bool,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct VoicePacket {
353 pub codec_id: CodecId,
355 pub payload: Vec<u8>,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct QosProfile {
362 pub target_latency_ms: u32,
364 pub max_latency_ms: u32,
366 pub min_bitrate: u32,
368 pub max_bitrate: u32,
370 pub packet_loss_tolerance: f32,
372}
373
374impl Default for QosProfile {
375 fn default() -> Self {
376 Self {
377 target_latency_ms: 50,
378 max_latency_ms: 200,
379 min_bitrate: 16_000,
380 max_bitrate: 96_000,
381 packet_loss_tolerance: 0.08,
382 }
383 }
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub enum RiftPayload {
389 Control(ControlMessage),
390 Voice(VoicePacket),
391 Text(ChatMessage),
392 Relay { target: PeerId, inner: Box<RiftPayload> },
393 Encrypted(EncryptedPayload),
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn group_control_roundtrip() {
402 let session = SessionId::random();
403 let msg = ControlMessage::Group(GroupControl::Topology {
404 session,
405 mode: GroupMode::Hybrid {
406 forwarder: PeerId([7u8; 32]),
407 },
408 });
409 let bytes = bincode::serialize(&msg).expect("serialize");
410 let decoded: ControlMessage = bincode::deserialize(&bytes).expect("deserialize");
411 match decoded {
412 ControlMessage::Group(GroupControl::Topology { session: s, mode }) => {
413 assert_eq!(s, session);
414 assert!(matches!(mode, GroupMode::Hybrid { .. }));
415 }
416 other => panic!("unexpected: {other:?}"),
417 }
418 }
419
420 #[test]
421 fn decode_rejects_oversize_frames() {
422 let mut bytes = Vec::new();
423 bytes.extend_from_slice(MAGIC);
424 bytes.push(ProtocolVersion::V1.as_u8());
425 let len = (MAX_FRAME_LEN + 1) as u32;
426 bytes.extend_from_slice(&len.to_le_bytes());
427 bytes.resize(10, 0);
428 let res = decode_frame(&bytes);
429 assert!(matches!(res, Err(FrameError::FrameTooLarge)));
430 }
431}
432
433#[derive(Debug, Error)]
435pub enum FrameError {
436 #[error("invalid magic")]
437 InvalidMagic,
438 #[error("unsupported version {0}")]
439 UnsupportedVersion(u8),
440 #[error("frame length mismatch")]
441 LengthMismatch,
442 #[error("frame too large")]
443 FrameTooLarge,
444 #[error("decode error: {0}")]
445 Decode(#[from] bincode::Error),
446}
447
448pub fn supported_versions() -> &'static [ProtocolVersion] {
450 &[ProtocolVersion::V2, ProtocolVersion::V1]
451}
452
453pub fn select_version(theirs: &[ProtocolVersion]) -> Option<ProtocolVersion> {
455 let mut ours = supported_versions().to_vec();
456 ours.sort();
457 let mut theirs = theirs.to_vec();
458 theirs.sort();
459 ours.into_iter()
460 .rev()
461 .find(|v| theirs.contains(v))
462}
463
464pub fn encode_frame(header: &RiftFrameHeader, payload: &RiftPayload) -> Vec<u8> {
467 let body = bincode::serialize(&(header, payload)).expect("serialize frame");
468 let mut out = Vec::with_capacity(4 + 1 + 4 + body.len());
469 out.extend_from_slice(MAGIC);
470 out.push(header.version.as_u8());
471 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
472 out.extend_from_slice(&body);
473 out
474}
475
476pub fn decode_frame(bytes: &[u8]) -> Result<(RiftFrameHeader, RiftPayload), FrameError> {
478 if bytes.len() < 9 {
479 return Err(FrameError::LengthMismatch);
480 }
481 if &bytes[..4] != MAGIC {
482 return Err(FrameError::InvalidMagic);
483 }
484 let version = ProtocolVersion::from_u8(bytes[4]).ok_or(FrameError::UnsupportedVersion(bytes[4]))?;
485 let len = u32::from_le_bytes([bytes[5], bytes[6], bytes[7], bytes[8]]) as usize;
486 if len > MAX_FRAME_LEN {
487 return Err(FrameError::FrameTooLarge);
488 }
489 if bytes.len() < 9 + len {
490 return Err(FrameError::LengthMismatch);
491 }
492 let body = &bytes[9..9 + len];
493 let (mut header, payload): (RiftFrameHeader, RiftPayload) = bincode::deserialize(body)?;
494 header.version = version;
495 Ok((header, payload))
496}