From 6ecbda1550cc84a0a6eb48aa473e8e19e264786d Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Sun, 15 Oct 2023 11:51:59 +0200 Subject: [PATCH] Update to v7 --- src/types/events/voice.rs | 4 +- src/types/events/webrtc/client_connect.rs | 34 +++++++ src/types/events/webrtc/client_disconnect.rs | 14 +++ src/types/events/webrtc/hello.rs | 7 ++ src/types/events/webrtc/identify.rs | 8 +- src/types/events/webrtc/mod.rs | 40 +++++++- src/types/events/webrtc/ready.rs | 10 +- src/types/events/webrtc/select_protocol.rs | 45 +++++++-- .../events/webrtc/session_description.rs | 31 +++++- src/types/events/webrtc/speaking.rs | 14 ++- .../events/webrtc/voice_backend_version.rs | 17 ++++ src/voice.rs | 96 ++++++++++++++++++- 12 files changed, 289 insertions(+), 31 deletions(-) create mode 100644 src/types/events/webrtc/client_connect.rs create mode 100644 src/types/events/webrtc/client_disconnect.rs create mode 100644 src/types/events/webrtc/voice_backend_version.rs diff --git a/src/types/events/voice.rs b/src/types/events/voice.rs index cb258fc..ac7df7a 100644 --- a/src/types/events/voice.rs +++ b/src/types/events/voice.rs @@ -34,8 +34,10 @@ impl WebSocketEvent for VoiceStateUpdate {} /// Received to indicate which voice endpoint, token and guild_id to use; pub struct VoiceServerUpdate { pub token: String, - /// Can be None in dm calls + /// The guild this voice server update is for pub guild_id: Option, + /// The private channel this voice server update is for + pub channel_id: Option, pub endpoint: Option, } diff --git a/src/types/events/webrtc/client_connect.rs b/src/types/events/webrtc/client_connect.rs new file mode 100644 index 0000000..d367ff3 --- /dev/null +++ b/src/types/events/webrtc/client_connect.rs @@ -0,0 +1,34 @@ +use crate::types::{Snowflake, WebSocketEvent}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Sent when another user connects to the voice server. +/// +/// Contains the user id and "flags". +/// +/// Not documented anywhere, if you know what this is, please reach out +/// +/// {"op":18,"d":{"user_id":"1234567890","flags":2}} +pub struct VoiceClientConnectFlags { + pub user_id: Snowflake, + // Likely some sort of bitflags + pub flags: u8, +} + +impl WebSocketEvent for VoiceClientConnectFlags {} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Sent when another user connects to the voice server. +/// +/// Contains the user id and "platform". +/// +/// Not documented anywhere, if you know what this is, please reach out +/// +/// {"op":20,"d":{"user_id":"1234567890","platform":0}} +pub struct VoiceClientConnectPlatform { + pub user_id: Snowflake, + // Likely an enum + pub platform: u8, +} + +impl WebSocketEvent for VoiceClientConnectPlatform {} diff --git a/src/types/events/webrtc/client_disconnect.rs b/src/types/events/webrtc/client_disconnect.rs new file mode 100644 index 0000000..6e30716 --- /dev/null +++ b/src/types/events/webrtc/client_disconnect.rs @@ -0,0 +1,14 @@ +use crate::types::{Snowflake, WebSocketEvent}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Sent when another user disconnects from the voice server. +/// +/// When received, the SSRC of the user should be discarded. +/// +/// See +pub struct VoiceClientDisconnection { + pub user_id: Snowflake, +} + +impl WebSocketEvent for VoiceClientDisconnection {} diff --git a/src/types/events/webrtc/hello.rs b/src/types/events/webrtc/hello.rs index bf73d84..3d39fb8 100644 --- a/src/types/events/webrtc/hello.rs +++ b/src/types/events/webrtc/hello.rs @@ -5,7 +5,14 @@ use serde::{Deserialize, Serialize}; /// Contains info on how often the client should send heartbeats to the server; /// /// Differs from the normal hello data in that discord sends heartbeat interval as a float. +/// +/// See pub struct VoiceHelloData { + /// The voice gateway version. + /// + /// Note: no idea why this is sent, we already specify the version when establishing a connection. + #[serde(rename = "v")] + pub version: u8, /// How often a client should send heartbeats, in milliseconds pub heartbeat_interval: f64, } diff --git a/src/types/events/webrtc/identify.rs b/src/types/events/webrtc/identify.rs index a83822c..4021c28 100644 --- a/src/types/events/webrtc/identify.rs +++ b/src/types/events/webrtc/identify.rs @@ -6,16 +6,16 @@ use serde::{Deserialize, Serialize}; /// /// Contains info to begin a webrtc connection; /// -/// See +/// See pub struct VoiceIdentify { - /// Not needed when in a dm call - pub server_id: Option, + /// The ID of the guild or the private channel being connected to + pub server_id: Snowflake, pub user_id: Snowflake, pub session_id: String, pub token: String, #[serde(skip_serializing_if = "Option::is_none")] - /// Undocumented field, but is also in discord client comms pub video: Option, + // TODO: Add video streams } impl WebSocketEvent for VoiceIdentify {} diff --git a/src/types/events/webrtc/mod.rs b/src/types/events/webrtc/mod.rs index ff475e2..3c87aa0 100644 --- a/src/types/events/webrtc/mod.rs +++ b/src/types/events/webrtc/mod.rs @@ -2,19 +2,25 @@ use super::WebSocketEvent; use serde::{Deserialize, Serialize}; use serde_json::{value::RawValue, Value}; +pub use client_connect::*; +pub use client_disconnect::*; pub use hello::*; pub use identify::*; pub use ready::*; pub use select_protocol::*; pub use session_description::*; pub use speaking::*; +pub use voice_backend_version::*; +mod client_connect; +mod client_disconnect; mod hello; mod identify; mod ready; mod select_protocol; mod session_description; mod speaking; +mod voice_backend_version; #[derive(Debug, Default, Serialize, Clone)] /// The payload used for sending events to the webrtc gateway. @@ -48,22 +54,41 @@ pub struct VoiceGatewayReceivePayload<'a> { impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {} /// The modes of encryption available in webrtc connections; -/// See +/// +/// See and #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "snake_case")] -pub enum WebrtcEncryptionMode { +pub enum VoiceEncryptionMode { #[default] - // Documented + // Officially Documented Xsalsa20Poly1305, Xsalsa20Poly1305Suffix, Xsalsa20Poly1305Lite, - // Undocumented + // Officially Undocumented Xsalsa20Poly1305LiteRtpsize, AeadAes256Gcm, AeadAes256GcmRtpsize, AeadXchacha20Poly1305Rtpsize, } +/// The possible audio codecs to use +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum AudioCodec { + #[default] + Opus, +} + +/// The possible video codecs to use +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "UPPERCASE")] +pub enum VideoCodec { + #[default] + VP8, + VP9, + H264, +} + // The various voice opcodes pub const VOICE_IDENTIFY: u8 = 0; pub const VOICE_SELECT_PROTOCOL: u8 = 1; @@ -77,7 +102,12 @@ pub const VOICE_HELLO: u8 = 8; pub const VOICE_RESUMED: u8 = 9; /// See pub const VOICE_VIDEO: u8 = 12; -pub const VOICE_CLIENT_DISCONENCT: u8 = 13; +pub const VOICE_CLIENT_DISCONNECT: u8 = 13; +pub const VOICE_SESSION_UPDATE: u8 = 14; /// See /// Sent with empty data from the client, the server responds with the voice backend version; pub const VOICE_BACKEND_VERSION: u8 = 16; + +// These two get simultaenously fired when a user joins, one has flags and one has a platform +pub const VOICE_CLIENT_CONNECT_FLAGS: u8 = 18; +pub const VOICE_CLIENT_CONNECT_PLATFORM: u8 = 20; diff --git a/src/types/events/webrtc/ready.rs b/src/types/events/webrtc/ready.rs index 8b46e83..1a39750 100644 --- a/src/types/events/webrtc/ready.rs +++ b/src/types/events/webrtc/ready.rs @@ -3,21 +3,24 @@ use std::net::Ipv4Addr; use crate::types::WebSocketEvent; use serde::{Deserialize, Serialize}; -use super::WebrtcEncryptionMode; +use super::VoiceEncryptionMode; #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] /// The ready event for the webrtc stream; /// /// Used to give info after the identify event; /// -/// See +/// See pub struct VoiceReady { /// See pub ssrc: i32, pub ip: Ipv4Addr, pub port: u32, /// The available encryption modes for the webrtc connection - pub modes: Vec, + pub modes: Vec, + #[serde(default)] + pub experiments: Vec, + // TODO: Add video streams // Heartbeat interval is also sent, but is "an erroneous field and should be ignored. The correct heartbeat_interval value comes from the Hello payload." } @@ -28,6 +31,7 @@ impl Default for VoiceReady { ip: Ipv4Addr::UNSPECIFIED, port: 0, modes: Vec::new(), + experiments: Vec::new(), } } } diff --git a/src/types/events/webrtc/select_protocol.rs b/src/types/events/webrtc/select_protocol.rs index 731c0d8..3cda068 100644 --- a/src/types/events/webrtc/select_protocol.rs +++ b/src/types/events/webrtc/select_protocol.rs @@ -2,27 +2,58 @@ use std::net::Ipv4Addr; use serde::{Deserialize, Serialize}; -use super::WebrtcEncryptionMode; +use super::VoiceEncryptionMode; -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Default)] /// An event sent by the client to the webrtc server, detailing what protocol, address and encryption to use; /// -/// See +/// See pub struct SelectProtocol { - /// The protocol to use. The only option detailed in discord docs is "udp" - pub protocol: String, + /// The protocol to use. The only option chorus supports is [VoiceProtocol::Udp]. + pub protocol: VoiceProtocol, pub data: SelectProtocolData, + /// The UUID4 RTC connection ID, used for analytics. + /// + /// Note: Not recommended to set this + pub rtc_connection_id: Option, + // TODO: Add codecs, what is a codec object + /// The possible experiments we want to enable + #[serde(rename = "experiments")] + pub enabled_experiments: Vec, +} + +/// The possible protocol for sending a receiving voice data. +/// +/// See +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum VoiceProtocol { + #[default] + /// Sending data via UDP, documented and the only protocol chorus supports. + Udp, + // Possible value, yet NOT RECOMMENED, AS CHORUS DOES NOT SUPPORT WEBRTC + //Webrtc, } #[derive(Debug, Deserialize, Serialize, Clone)] /// The data field of the SelectProtocol Event /// -/// See +/// See pub struct SelectProtocolData { /// Our external ip pub address: Ipv4Addr, /// Our external udp port pub port: u32, /// The mode of encryption to use - pub mode: WebrtcEncryptionMode, + pub mode: VoiceEncryptionMode, +} + +impl Default for SelectProtocolData { + fn default() -> Self { + SelectProtocolData { + address: Ipv4Addr::UNSPECIFIED, + port: 0, + mode: Default::default(), + } + } } diff --git a/src/types/events/webrtc/session_description.rs b/src/types/events/webrtc/session_description.rs index dfef1eb..9c1b3d4 100644 --- a/src/types/events/webrtc/session_description.rs +++ b/src/types/events/webrtc/session_description.rs @@ -1,14 +1,39 @@ -use super::WebrtcEncryptionMode; +use super::{AudioCodec, VideoCodec, VoiceEncryptionMode}; use crate::types::WebSocketEvent; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone, Default)] /// Event that describes our encryption mode and secret key for encryption +/// +/// See pub struct SessionDescription { - /// The encryption mode we're using in webrtc - pub mode: WebrtcEncryptionMode, + pub audio_codec: AudioCodec, + pub video_codec: VideoCodec, + pub media_session_id: String, + /// The encryption mode to use + #[serde(rename = "mode")] + pub encryption_mode: VoiceEncryptionMode, /// The secret key we'll use for encryption pub secret_key: [u8; 32], + /// The keyframe interval in milliseconds + pub keyframe_interval: Option, } impl WebSocketEvent for SessionDescription {} + +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +/// Event that might be sent to update session parameters +/// +/// See +pub struct SessionUpdate { + #[serde(rename = "audio_codec")] + pub new_audio_codec: Option, + + #[serde(rename = "video_codec")] + pub new_video_codec: Option, + + #[serde(rename = "media_session_id")] + pub new_media_session_id: Option, +} + +impl WebSocketEvent for SessionUpdate {} diff --git a/src/types/events/webrtc/speaking.rs b/src/types/events/webrtc/speaking.rs index 5b48efa..b854876 100644 --- a/src/types/events/webrtc/speaking.rs +++ b/src/types/events/webrtc/speaking.rs @@ -1,20 +1,28 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; +use crate::types::{Snowflake, WebSocketEvent}; + /// Event that tells the server we are speaking; /// /// Essentially, what allows us to send udp data and lights up the green circle around your avatar. /// -/// See +/// See #[derive(Debug, Deserialize, Serialize, Clone, Default)] pub struct Speaking { /// Data about the audio we're transmitting, its type pub speaking: SpeakingBitflags, - /// Assuming delay in milliseconds for the audio, should be 0 most of the time - pub delay: u64, pub ssrc: i32, + /// The user id of the speaking user, only sent by the server + #[serde(skip_serializing)] + pub user_id: Option, + /// Delay in milliseconds, not sent by the server + #[serde(default)] + pub delay: u64, } +impl WebSocketEvent for Speaking {} + bitflags! { /// Bitflags of speaking types; /// diff --git a/src/types/events/webrtc/voice_backend_version.rs b/src/types/events/webrtc/voice_backend_version.rs new file mode 100644 index 0000000..6b788ea --- /dev/null +++ b/src/types/events/webrtc/voice_backend_version.rs @@ -0,0 +1,17 @@ +use crate::types::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] +/// Received from the server to describe the backend version. +/// +/// See +pub struct VoiceBackendVersion { + /// The voice backend's version + #[serde(rename = "voice")] + pub voice_version: String, + /// The WebRTC worker's version + #[serde(rename = "rtc_worker")] + pub rtc_worker_version: String, +} + +impl WebSocketEvent for VoiceBackendVersion {} diff --git a/src/voice.rs b/src/voice.rs index a39a6d8..edca815 100644 --- a/src/voice.rs +++ b/src/voice.rs @@ -19,9 +19,10 @@ use crate::errors::VoiceGatewayError; use crate::gateway::{GatewayEvent, HEARTBEAT_ACK_TIMEOUT}; use crate::types::{ self, SelectProtocol, Speaking, VoiceGatewayReceivePayload, VoiceGatewaySendPayload, - VoiceIdentify, WebSocketEvent, VOICE_BACKEND_VERSION, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK, + VoiceIdentify, WebSocketEvent, VOICE_BACKEND_VERSION, VOICE_CLIENT_CONNECT_FLAGS, + VOICE_CLIENT_CONNECT_PLATFORM, VOICE_CLIENT_DISCONNECT, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK, VOICE_HELLO, VOICE_IDENTIFY, VOICE_READY, VOICE_RESUME, VOICE_SELECT_PROTOCOL, - VOICE_SESSION_DESCRIPTION, VOICE_SPEAKING, + VOICE_SESSION_DESCRIPTION, VOICE_SESSION_UPDATE, VOICE_SPEAKING, }; use self::voice_events::VoiceEvents; @@ -199,7 +200,7 @@ impl VoiceGateway { #[allow(clippy::new_ret_no_self)] pub async fn new(websocket_url: String) -> Result { // Append the needed things to the websocket url - let processed_url = format!("wss://{}/?v=4", websocket_url); + let processed_url = format!("wss://{}/?v=7", websocket_url); trace!("Created voice socket url: {}", processed_url.clone()); let (websocket_stream, _) = match connect_async_tls_with_config( @@ -357,6 +358,19 @@ impl VoiceGateway { return; } } + VOICE_BACKEND_VERSION => { + trace!("VGW: Received Backend Version"); + + let event = &mut self.events.lock().await.backend_version; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_BACKEND_VERSION ({})", + result.err().unwrap() + ); + return; + } + } VOICE_SESSION_DESCRIPTION => { trace!("VGW: Received Session Description"); @@ -364,12 +378,75 @@ impl VoiceGateway { let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; if result.is_err() { warn!( - "Failed to parse VOICE_SELECT_PROTOCOL ({})", + "Failed to parse VOICE_SESSION_DESCRIPTION ({})", result.err().unwrap() ); return; } } + VOICE_SESSION_UPDATE => { + trace!("VGW: Received Session Update"); + + let event = &mut self.events.lock().await.session_update; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SESSION_UPDATE ({})", + result.err().unwrap() + ); + return; + } + } + VOICE_SPEAKING => { + trace!("VGW: Received Speaking"); + + let event = &mut self.events.lock().await.speaking; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!("Failed to parse VOICE_SPEAKING ({})", result.err().unwrap()); + return; + } + } + VOICE_CLIENT_DISCONNECT => { + trace!("VGW: Received Client Disconnect"); + + let event = &mut self.events.lock().await.client_disconnect; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_DISCONNECT ({})", + result.err().unwrap() + ); + return; + } + } + VOICE_CLIENT_CONNECT_FLAGS => { + trace!("VGW: Received Client Connect Flags"); + + let event = &mut self.events.lock().await.client_connect_flags; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_CONNECT_FLAGS ({})", + result.err().unwrap() + ); + return; + } + } + VOICE_CLIENT_CONNECT_PLATFORM => { + trace!("VGW: Received Client Connect Platform"); + + let event = &mut self.events.lock().await.client_connect_platform; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_CONNECT_PLATFORM ({})", + result.err().unwrap() + ); + return; + } + } + // We received a heartbeat from the server // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." VOICE_HEARTBEAT => { @@ -563,14 +640,23 @@ struct VoiceHeartbeatThreadCommunication { } pub mod voice_events { - use crate::types::{SessionDescription, VoiceReady}; + use crate::types::{ + SessionDescription, SessionUpdate, VoiceBackendVersion, VoiceClientConnectFlags, + VoiceClientConnectPlatform, VoiceClientDisconnection, VoiceReady, + }; use super::*; #[derive(Default, Debug)] pub struct VoiceEvents { pub voice_ready: GatewayEvent, + pub backend_version: GatewayEvent, pub session_description: GatewayEvent, + pub session_update: GatewayEvent, + pub speaking: GatewayEvent, + pub client_disconnect: GatewayEvent, + pub client_connect_flags: GatewayEvent, + pub client_connect_platform: GatewayEvent, pub error: GatewayEvent, } }