Compare commits
7 Commits
97ef94bfa6
...
3a65ff3ffc
Author | SHA1 | Date |
---|---|---|
kozabrada123 | 3a65ff3ffc | |
kozabrada123 | a3ea5c0975 | |
kozabrada123 | b79f1a7c26 | |
kozabrada123 | 2f0b0cf3ae | |
kozabrada123 | be0ae1e7fa | |
kozabrada123 | 000a4ada8a | |
kozabrada123 | 4b7724a703 |
|
@ -53,7 +53,7 @@ sqlx = { version = "0.7.3", features = [
|
||||||
"any",
|
"any",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] }
|
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] }
|
||||||
crypto_secretbox = {version = "0.1.1", optional = true}
|
crypto_secretbox = { version = "0.1.1", optional = true }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
|
|
@ -2,6 +2,15 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// This example showcase how to properly use gateway observers.
|
||||||
|
//
|
||||||
|
// To properly run it, you will need to change the token below.
|
||||||
|
|
||||||
|
const TOKEN: &str = "";
|
||||||
|
|
||||||
|
/// Find the gateway websocket url of the server we want to connect to
|
||||||
|
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chorus::gateway::Gateway;
|
use chorus::gateway::Gateway;
|
||||||
use chorus::{
|
use chorus::{
|
||||||
|
@ -36,11 +45,10 @@ impl Observer<GatewayReady> for ExampleObserver {
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Find the gateway websocket url of the server we want to connect to
|
let gateway_websocket_url = GATEWAY_URL.to_string();
|
||||||
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
|
|
||||||
|
|
||||||
// Initiate the gateway connection
|
// Initiate the gateway connection
|
||||||
let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap();
|
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
|
||||||
|
|
||||||
// Create an instance of our observer
|
// Create an instance of our observer
|
||||||
let observer = ExampleObserver {};
|
let observer = ExampleObserver {};
|
||||||
|
@ -59,7 +67,7 @@ async fn main() {
|
||||||
.subscribe(shared_observer);
|
.subscribe(shared_observer);
|
||||||
|
|
||||||
// Authenticate so we will receive any events
|
// Authenticate so we will receive any events
|
||||||
let token = "SecretToken".to_string();
|
let token = TOKEN.to_string();
|
||||||
let mut identify = GatewayIdentifyPayload::common();
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
identify.token = token;
|
identify.token = token;
|
||||||
gateway.send_identify(identify).await;
|
gateway.send_identify(identify).await;
|
||||||
|
|
|
@ -2,6 +2,16 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// This example showcases how to initiate a gateway connection manually
|
||||||
|
// (e. g. not through ChorusUser)
|
||||||
|
//
|
||||||
|
// To properly run it, you will need to modify the token below.
|
||||||
|
|
||||||
|
const TOKEN: &str = "";
|
||||||
|
|
||||||
|
/// Find the gateway websocket url of the server we want to connect to
|
||||||
|
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chorus::gateway::Gateway;
|
use chorus::gateway::Gateway;
|
||||||
|
@ -15,16 +25,15 @@ use wasmtimer::tokio::sleep;
|
||||||
/// This example creates a simple gateway connection and a session with an Identify event
|
/// This example creates a simple gateway connection and a session with an Identify event
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Find the gateway websocket url of the server we want to connect to
|
let gateway_websocket_url = GATEWAY_URL.to_string();
|
||||||
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
|
|
||||||
|
|
||||||
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
|
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
|
||||||
let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap();
|
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
|
||||||
|
|
||||||
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
|
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
|
||||||
|
|
||||||
// Get a token for an account on the server
|
// Get a token for an account on the server
|
||||||
let token = "SecretToken".to_string();
|
let token = TOKEN.to_string();
|
||||||
|
|
||||||
// Create an identify event
|
// Create an identify event
|
||||||
// An Identify event is how the server authenticates us and gets info about our os and browser, along with our intents / capabilities
|
// An Identify event is how the server authenticates us and gets info about our os and browser, along with our intents / capabilities
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "voice_simple"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "*"
|
||||||
|
chorus = { path = "../../", features = ["rt", "client", "voice"] }
|
||||||
|
tokio = { version = "*", features = ["full"] }
|
||||||
|
simplelog = "*"
|
||||||
|
log = "*"
|
|
@ -0,0 +1,311 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// This example showcases how to use the voice udp channel.
|
||||||
|
//
|
||||||
|
// To use this to properly communicate with voice, you will need to bring your own opus bindings
|
||||||
|
// along with potentially sending some other events, like Speaking
|
||||||
|
//
|
||||||
|
// To properly run this example, you will need to change some values below,
|
||||||
|
// like the token, guild and channel ids.
|
||||||
|
|
||||||
|
const TOKEN: &str = "";
|
||||||
|
|
||||||
|
const VOICE_GUILD_ID: Option<Snowflake> = None;
|
||||||
|
const VOICE_CHANNEL_ID: Option<Snowflake> = Some(Snowflake(0_u64));
|
||||||
|
|
||||||
|
const GATEWAY_URL: &str = "wss://gateway.discord.gg";
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use simplelog::{TermLogger, Config, WriteLogger};
|
||||||
|
use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration};
|
||||||
|
|
||||||
|
use chorus::{
|
||||||
|
gateway::{Observer, Gateway},
|
||||||
|
types::{
|
||||||
|
GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking,
|
||||||
|
SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol,
|
||||||
|
VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState,
|
||||||
|
},
|
||||||
|
voice::{
|
||||||
|
gateway::{VoiceGateway, VoiceGatewayHandle},
|
||||||
|
udp::{UdpHandle, UdpHandler},
|
||||||
|
voice_data::VoiceData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use log::{info, LevelFilter};
|
||||||
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
|
extern crate chorus;
|
||||||
|
extern crate tokio;
|
||||||
|
|
||||||
|
/// Handles inbetween connections between the gateway and udp modules
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VoiceHandler {
|
||||||
|
pub voice_gateway_connection: Arc<Mutex<Option<VoiceGatewayHandle>>>,
|
||||||
|
pub voice_udp_connection: Arc<Mutex<Option<UdpHandle>>>,
|
||||||
|
pub data: Arc<RwLock<VoiceData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VoiceHandler {
|
||||||
|
/// Creates a new voicehandler, only initializing the data
|
||||||
|
pub fn new() -> VoiceHandler {
|
||||||
|
Self {
|
||||||
|
data: Arc::new(RwLock::new(VoiceData::default())),
|
||||||
|
voice_gateway_connection: Arc::new(Mutex::new(None)),
|
||||||
|
voice_udp_connection: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VoiceHandler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// On [VoiceServerUpdate] we get our starting data and url for the voice gateway server.
|
||||||
|
impl Observer<VoiceServerUpdate> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &VoiceServerUpdate) {
|
||||||
|
let mut data_lock = self.data.write().await;
|
||||||
|
|
||||||
|
data_lock.server_data = Some(data.clone());
|
||||||
|
let user_id = data_lock.user_id;
|
||||||
|
let session_id = data_lock.session_id.clone();
|
||||||
|
|
||||||
|
drop(data_lock);
|
||||||
|
|
||||||
|
// Create and connect to the voice gateway
|
||||||
|
let voice_gateway_handle = VoiceGateway::spawn(data.endpoint.clone().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let server_id: Snowflake;
|
||||||
|
|
||||||
|
if data.guild_id.is_some() {
|
||||||
|
server_id = data.guild_id.unwrap();
|
||||||
|
} else {
|
||||||
|
server_id = data.channel_id.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let voice_identify = VoiceIdentify {
|
||||||
|
server_id,
|
||||||
|
user_id,
|
||||||
|
session_id,
|
||||||
|
token: data.token.clone(),
|
||||||
|
video: Some(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
voice_gateway_handle.send_identify(voice_identify).await;
|
||||||
|
|
||||||
|
let cloned_gateway_handle = voice_gateway_handle.clone();
|
||||||
|
|
||||||
|
let mut voice_events = cloned_gateway_handle.events.lock().await;
|
||||||
|
|
||||||
|
let self_reference = Arc::new(self.clone());
|
||||||
|
|
||||||
|
// Subscribe to voice gateway events
|
||||||
|
voice_events.voice_ready.subscribe(self_reference.clone());
|
||||||
|
voice_events
|
||||||
|
.session_description
|
||||||
|
.subscribe(self_reference.clone());
|
||||||
|
voice_events.speaking.subscribe(self_reference.clone());
|
||||||
|
voice_events
|
||||||
|
.ssrc_definition
|
||||||
|
.subscribe(self_reference.clone());
|
||||||
|
|
||||||
|
*self.voice_gateway_connection.lock().await = Some(voice_gateway_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// On [VoiceReady] we get info for establishing a UDP connection, and we immedietly need said UDP
|
||||||
|
// connection for ip discovery.
|
||||||
|
impl Observer<VoiceReady> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &VoiceReady) {
|
||||||
|
let mut data_lock = self.data.write().await;
|
||||||
|
|
||||||
|
data_lock.ready_data = Some(data.clone());
|
||||||
|
|
||||||
|
drop(data_lock);
|
||||||
|
|
||||||
|
// Create a udp connection and perform ip discovery
|
||||||
|
let udp_handle = UdpHandler::spawn(
|
||||||
|
self.data.clone(),
|
||||||
|
std::net::SocketAddr::V4(SocketAddrV4::new(data.ip, data.port)),
|
||||||
|
data.ssrc,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Subscribe ourself to receiving rtp data
|
||||||
|
udp_handle
|
||||||
|
.events
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.rtp
|
||||||
|
.subscribe(Arc::new(self.clone()));
|
||||||
|
|
||||||
|
let ip_discovery = self.data.read().await.ip_discovery.clone().unwrap();
|
||||||
|
|
||||||
|
*self.voice_udp_connection.lock().await = Some(udp_handle.clone());
|
||||||
|
|
||||||
|
let string_ip_address =
|
||||||
|
String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip");
|
||||||
|
|
||||||
|
// Send a select protocol, which tells the server where we'll be receiving data and what
|
||||||
|
// mode to encrypt data in
|
||||||
|
self.voice_gateway_connection
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.send_select_protocol(SelectProtocol {
|
||||||
|
protocol: VoiceProtocol::Udp,
|
||||||
|
data: SelectProtocolData {
|
||||||
|
address: string_ip_address,
|
||||||
|
port: ip_discovery.port,
|
||||||
|
// There are several other voice encryption modes available, though not all are
|
||||||
|
// implemented in chorus
|
||||||
|
mode: VoiceEncryptionMode::Xsalsa20Poly1305,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// Session descryption gives us final info regarding codecs and our encryption key
|
||||||
|
impl Observer<SessionDescription> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &SessionDescription) {
|
||||||
|
let mut data_write = self.data.write().await;
|
||||||
|
|
||||||
|
data_write.session_description = Some(data.clone());
|
||||||
|
|
||||||
|
drop(data_write);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// Ready is used just to obtain some info, like the user id and session id
|
||||||
|
impl Observer<GatewayReady> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &GatewayReady) {
|
||||||
|
let mut lock = self.data.write().await;
|
||||||
|
lock.user_id = data.user.id;
|
||||||
|
lock.session_id = data.session_id.clone();
|
||||||
|
drop(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// This is the received voice data
|
||||||
|
impl Observer<chorus::voice::discortp::rtp::Rtp> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &chorus::voice::discortp::rtp::Rtp) {
|
||||||
|
info!(
|
||||||
|
"Received decrypted voice data! {:?} (SSRC: {})",
|
||||||
|
data.payload.clone(),
|
||||||
|
data.ssrc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// This event gives extra info about who is speaking
|
||||||
|
impl Observer<Speaking> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &Speaking) {
|
||||||
|
println!(
|
||||||
|
"Received Speaking! (SRRC: {}, flags: {:?})",
|
||||||
|
data.ssrc,
|
||||||
|
SpeakingBitflags::from_bits(data.speaking).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
// This event gives some info about which user has which ssrc
|
||||||
|
impl Observer<SsrcDefinition> for VoiceHandler {
|
||||||
|
async fn update(&self, data: &SsrcDefinition) {
|
||||||
|
println!(
|
||||||
|
"Received SSRC Definition! (User {} has audio ssrc {})",
|
||||||
|
data.user_id.unwrap(),
|
||||||
|
data.audio_ssrc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
simplelog::CombinedLogger::init(vec![
|
||||||
|
TermLogger::new(
|
||||||
|
LevelFilter::Debug,
|
||||||
|
Config::default(),
|
||||||
|
simplelog::TerminalMode::Mixed,
|
||||||
|
simplelog::ColorChoice::Auto,
|
||||||
|
),
|
||||||
|
WriteLogger::new(
|
||||||
|
LevelFilter::Trace,
|
||||||
|
Config::default(),
|
||||||
|
File::create("latest.log").unwrap(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let gateway = Gateway::spawn(GATEWAY_URL.to_string())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
|
identify.token = TOKEN.to_string();
|
||||||
|
|
||||||
|
gateway.send_identify(identify).await;
|
||||||
|
|
||||||
|
let voice_handler = Arc::new(VoiceHandler::new());
|
||||||
|
|
||||||
|
// Voice handler needs voice server update
|
||||||
|
gateway
|
||||||
|
.events
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.voice
|
||||||
|
.server_update
|
||||||
|
.subscribe(voice_handler.clone());
|
||||||
|
|
||||||
|
// It also needs a bit of the data in ready
|
||||||
|
gateway
|
||||||
|
.events
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.session
|
||||||
|
.ready
|
||||||
|
.subscribe(voice_handler.clone());
|
||||||
|
|
||||||
|
// Data which channel to update the local user to be joined into.
|
||||||
|
//
|
||||||
|
// guild_id and channel_id can be some to join guild voice channels
|
||||||
|
//
|
||||||
|
// guild_id can be none and channel id some to join dm calls
|
||||||
|
//
|
||||||
|
// both can be none to leave all voice channels
|
||||||
|
let voice_state_update = UpdateVoiceState {
|
||||||
|
guild_id: VOICE_GUILD_ID,
|
||||||
|
channel_id: VOICE_CHANNEL_ID,
|
||||||
|
self_mute: false,
|
||||||
|
self_deaf: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
gateway.send_update_voice_state(voice_state_update).await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
|
// Potentially send some data here
|
||||||
|
/*let voice_udp_option = voice_handler.voice_udp_connection.lock().await.clone();
|
||||||
|
if voice_udp_option.is_some() {
|
||||||
|
voice_udp_option.unwrap().send_opus_data(0, vec![1, 2, 3, 4, 5]).await.unwrap();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,6 +144,7 @@ custom_error! {
|
||||||
NoData = "We have not set received the necessary data to perform this operation.",
|
NoData = "We have not set received the necessary data to perform this operation.",
|
||||||
|
|
||||||
// Encryption errors
|
// Encryption errors
|
||||||
|
EncryptionModeNotImplemented{encryption_mode: String} = "Voice encryption mode {encryption_mode} is not yet implemented.",
|
||||||
NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet",
|
NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet",
|
||||||
FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
|
FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
|
||||||
FailedDecryption = "Tried to decrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
|
FailedDecryption = "Tried to decrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
|
||||||
|
|
|
@ -88,14 +88,40 @@ pub enum VoiceEncryptionMode {
|
||||||
// Officially Undocumented
|
// Officially Undocumented
|
||||||
/// Not implemented yet, we have no idea what the rtpsize nonces are.
|
/// Not implemented yet, we have no idea what the rtpsize nonces are.
|
||||||
Xsalsa20Poly1305LiteRtpsize,
|
Xsalsa20Poly1305LiteRtpsize,
|
||||||
/// Not implemented yet
|
/// Not implemented yet, we have no idea what the nonce is.
|
||||||
AeadAes256Gcm,
|
AeadAes256Gcm,
|
||||||
/// Not implemented yet
|
/// Not implemented yet, we have no idea what the rtpsize nonces are.
|
||||||
AeadAes256GcmRtpsize,
|
AeadAes256GcmRtpsize,
|
||||||
/// Not implemented yet, we have no idea what the rtpsize nonces are.
|
/// Not implemented yet, we have no idea what the rtpsize nonces are.
|
||||||
AeadXchacha20Poly1305Rtpsize,
|
AeadXchacha20Poly1305Rtpsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VoiceEncryptionMode {
|
||||||
|
/// Returns whether this encryption mode uses Xsalsa20Poly1305 encryption.
|
||||||
|
pub fn is_xsalsa20_poly1305(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
*self,
|
||||||
|
VoiceEncryptionMode::Xsalsa20Poly1305
|
||||||
|
| VoiceEncryptionMode::Xsalsa20Poly1305Lite
|
||||||
|
| VoiceEncryptionMode::Xsalsa20Poly1305Suffix
|
||||||
|
| VoiceEncryptionMode::Xsalsa20Poly1305LiteRtpsize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this encryption mode uses AeadAes256Gcm encryption.
|
||||||
|
pub fn is_aead_aes256_gcm(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
*self,
|
||||||
|
VoiceEncryptionMode::AeadAes256Gcm | VoiceEncryptionMode::AeadAes256GcmRtpsize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this encryption mode uses AeadXchacha20Poly1305 encryption.
|
||||||
|
pub fn is_aead_xchacha20_poly1305(&self) -> bool {
|
||||||
|
*self == VoiceEncryptionMode::AeadXchacha20Poly1305Rtpsize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The possible audio codecs to use
|
/// The possible audio codecs to use
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
use std::{net::SocketAddrV4, sync::Arc};
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use tokio::sync::{Mutex, RwLock};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
gateway::Observer,
|
|
||||||
types::{
|
|
||||||
GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake,
|
|
||||||
VoiceEncryptionMode, VoiceIdentify, VoiceProtocol, VoiceReady, VoiceServerUpdate,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
gateway::{VoiceGateway, VoiceGatewayHandle},
|
|
||||||
udp::UdpHandle,
|
|
||||||
udp::UdpHandler,
|
|
||||||
voice_data::VoiceData,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Handles inbetween connections between the gateway and udp modules
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct VoiceHandler {
|
|
||||||
pub voice_gateway_connection: Arc<Mutex<Option<VoiceGatewayHandle>>>,
|
|
||||||
pub voice_udp_connection: Arc<Mutex<Option<UdpHandle>>>,
|
|
||||||
pub data: Arc<RwLock<VoiceData>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VoiceHandler {
|
|
||||||
/// Creates a new voicehandler, only initializing the data
|
|
||||||
pub fn new() -> VoiceHandler {
|
|
||||||
Self {
|
|
||||||
data: Arc::new(RwLock::new(VoiceData::default())),
|
|
||||||
voice_gateway_connection: Arc::new(Mutex::new(None)),
|
|
||||||
voice_udp_connection: Arc::new(Mutex::new(None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VoiceHandler {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
// On [VoiceServerUpdate] we get our starting data and url for the voice gateway server.
|
|
||||||
impl Observer<VoiceServerUpdate> for VoiceHandler {
|
|
||||||
async fn update(&self, data: &VoiceServerUpdate) {
|
|
||||||
let mut data_lock = self.data.write().await;
|
|
||||||
data_lock.server_data = Some(data.clone());
|
|
||||||
let user_id = data_lock.user_id;
|
|
||||||
let session_id = data_lock.session_id.clone();
|
|
||||||
drop(data_lock);
|
|
||||||
|
|
||||||
let voice_gateway_handle = VoiceGateway::spawn(data.endpoint.clone().unwrap())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let server_id: Snowflake;
|
|
||||||
|
|
||||||
if data.guild_id.is_some() {
|
|
||||||
server_id = data.guild_id.unwrap();
|
|
||||||
} else {
|
|
||||||
server_id = data.channel_id.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let voice_identify = VoiceIdentify {
|
|
||||||
server_id,
|
|
||||||
user_id,
|
|
||||||
session_id,
|
|
||||||
token: data.token.clone(),
|
|
||||||
video: Some(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
voice_gateway_handle.send_identify(voice_identify).await;
|
|
||||||
|
|
||||||
let cloned_gateway_handle = voice_gateway_handle.clone();
|
|
||||||
|
|
||||||
let mut voice_events = cloned_gateway_handle.events.lock().await;
|
|
||||||
|
|
||||||
let self_reference = Arc::new(self.clone());
|
|
||||||
|
|
||||||
voice_events.voice_ready.subscribe(self_reference.clone());
|
|
||||||
voice_events
|
|
||||||
.session_description
|
|
||||||
.subscribe(self_reference.clone());
|
|
||||||
|
|
||||||
*self.voice_gateway_connection.lock().await = Some(voice_gateway_handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
// On [VoiceReady] we get info for establishing a UDP connection, and we immedietly need said UDP
|
|
||||||
// connection for ip discovery.
|
|
||||||
impl Observer<VoiceReady> for VoiceHandler {
|
|
||||||
async fn update(&self, data: &VoiceReady) {
|
|
||||||
let mut data_lock = self.data.write().await;
|
|
||||||
data_lock.ready_data = Some(data.clone());
|
|
||||||
drop(data_lock);
|
|
||||||
|
|
||||||
let udp_handle = UdpHandler::spawn(
|
|
||||||
self.data.clone(),
|
|
||||||
std::net::SocketAddr::V4(SocketAddrV4::new(data.ip, data.port)),
|
|
||||||
data.ssrc,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let ip_discovery = self.data.read().await.ip_discovery.clone().unwrap();
|
|
||||||
|
|
||||||
*self.voice_udp_connection.lock().await = Some(udp_handle.clone());
|
|
||||||
|
|
||||||
let string_ip_address =
|
|
||||||
String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip");
|
|
||||||
|
|
||||||
self.voice_gateway_connection
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.clone()
|
|
||||||
.unwrap()
|
|
||||||
.send_select_protocol(SelectProtocol {
|
|
||||||
protocol: VoiceProtocol::Udp,
|
|
||||||
data: SelectProtocolData {
|
|
||||||
address: string_ip_address,
|
|
||||||
port: ip_discovery.port,
|
|
||||||
mode: VoiceEncryptionMode::Xsalsa20Poly1305,
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
// Session descryption gives us final info regarding codecs and our encryption key
|
|
||||||
impl Observer<SessionDescription> for VoiceHandler {
|
|
||||||
async fn update(&self, data: &SessionDescription) {
|
|
||||||
let mut data_write = self.data.write().await;
|
|
||||||
|
|
||||||
data_write.session_description = Some(data.clone());
|
|
||||||
|
|
||||||
drop(data_write);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Observer<GatewayReady> for VoiceHandler {
|
|
||||||
async fn update(&self, data: &GatewayReady) {
|
|
||||||
let mut lock = self.data.write().await;
|
|
||||||
lock.user_id = data.user.id;
|
|
||||||
lock.session_id = data.session_id.clone();
|
|
||||||
drop(lock);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,8 +7,6 @@
|
||||||
mod crypto;
|
mod crypto;
|
||||||
#[cfg(feature = "voice_gateway")]
|
#[cfg(feature = "voice_gateway")]
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
#[cfg(all(feature = "voice_udp", feature = "voice_gateway"))]
|
|
||||||
pub mod handler;
|
|
||||||
#[cfg(feature = "voice_udp")]
|
#[cfg(feature = "voice_udp")]
|
||||||
pub mod udp;
|
pub mod udp;
|
||||||
#[cfg(feature = "voice_udp")]
|
#[cfg(feature = "voice_udp")]
|
||||||
|
|
|
@ -148,32 +148,61 @@ impl UdpHandle {
|
||||||
.last_udp_encryption_nonce
|
.last_udp_encryption_nonce
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.wrapping_add(1);
|
.wrapping_add(1);
|
||||||
|
|
||||||
data_lock.last_udp_encryption_nonce = Some(nonce);
|
data_lock.last_udp_encryption_nonce = Some(nonce);
|
||||||
drop(data_lock);
|
drop(data_lock);
|
||||||
// TODO: Is le correct? This is not documented anywhere
|
// TODO: Is le correct? This is not documented anywhere
|
||||||
let mut bytes = nonce.to_le_bytes().to_vec();
|
let mut bytes = nonce.to_le_bytes().to_vec();
|
||||||
// This is 4 bytes, it has to be 24, so we need to append 20
|
|
||||||
|
// This is 4 bytes, it has to be a different size, appends 0s
|
||||||
while bytes.len() < 24 {
|
while bytes.len() < 24 {
|
||||||
bytes.push(0);
|
bytes.push(0);
|
||||||
}
|
}
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// TODO: Implement aead_aes256_gcm
|
error!(
|
||||||
todo!("This voice encryption mode is not yet implemented.");
|
"This voice encryption mode ({:?}) is not yet implemented.",
|
||||||
|
session_description.encryption_mode
|
||||||
|
);
|
||||||
|
return Err(VoiceUdpError::EncryptionModeNotImplemented {
|
||||||
|
encryption_mode: format!("{:?}", session_description.encryption_mode),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonce = GenericArray::from_slice(&nonce_bytes);
|
|
||||||
|
|
||||||
let key = GenericArray::from_slice(&session_description.secret_key);
|
let key = GenericArray::from_slice(&session_description.secret_key);
|
||||||
|
|
||||||
let encryptor = XSalsa20Poly1305::new(key);
|
let encryption_result;
|
||||||
|
|
||||||
let encryption_result = encryptor.encrypt(nonce, payload);
|
if session_description.encryption_mode.is_xsalsa20_poly1305() {
|
||||||
|
let nonce = GenericArray::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
|
let encryptor = XSalsa20Poly1305::new(key);
|
||||||
|
|
||||||
|
encryption_result = encryptor.encrypt(nonce, payload);
|
||||||
|
}
|
||||||
|
// Note: currently unused because I have no idea what the AeadAes256Gcm nonce is
|
||||||
|
/*else if session_description.encryption_mode.is_aead_aes256_gcm() {
|
||||||
|
let nonce = GenericArray::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
|
let encryptor = Aes256Gcm::new(key);
|
||||||
|
|
||||||
|
encryption_result = encryptor.encrypt(nonce, payload);
|
||||||
|
|
||||||
|
}*/
|
||||||
|
else {
|
||||||
|
error!(
|
||||||
|
"This voice encryption mode ({:?}) is not yet implemented.",
|
||||||
|
session_description.encryption_mode
|
||||||
|
);
|
||||||
|
return Err(VoiceUdpError::EncryptionModeNotImplemented {
|
||||||
|
encryption_mode: format!("{:?}", session_description.encryption_mode),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if encryption_result.is_err() {
|
if encryption_result.is_err() {
|
||||||
// Safety: If encryption errors here, it's chorus' fault, and it makes no sense to
|
// Safety: If encryption fails here, it's chorus' fault, and it makes no sense to
|
||||||
// return the error to the user.
|
// return the error to the user.
|
||||||
//
|
//
|
||||||
// This is not an error the user should account for, which is why we throw it here.
|
// This is not an error the user should account for, which is why we throw it here.
|
||||||
|
|
|
@ -96,8 +96,6 @@ impl UdpHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let received_size = received_size_or_err.unwrap();
|
|
||||||
|
|
||||||
let receieved_ip_discovery = IpDiscoveryPacket::new(&buf).expect("Could not make ipdiscovery packet from received data, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new");
|
let receieved_ip_discovery = IpDiscoveryPacket::new(&buf).expect("Could not make ipdiscovery packet from received data, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new");
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -188,7 +186,8 @@ impl UdpHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!();
|
error!("VUDP: Failed to decrypt voice data: {}", err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,18 +305,45 @@ impl UdpHandler {
|
||||||
get_xsalsa20_poly1305_lite_nonce(packet_bytes)
|
get_xsalsa20_poly1305_lite_nonce(packet_bytes)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// TODO: Implement aead_aes256_gcm
|
error!(
|
||||||
todo!("This voice encryption mode is not yet implemented.");
|
"This voice encryption mode ({:?}) is not yet implemented.",
|
||||||
|
session_description.encryption_mode
|
||||||
|
);
|
||||||
|
return Err(VoiceUdpError::EncryptionModeNotImplemented {
|
||||||
|
encryption_mode: format!("{:?}", session_description.encryption_mode),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonce = GenericArray::from_slice(&nonce_bytes);
|
|
||||||
|
|
||||||
let key = GenericArray::from_slice(&session_description.secret_key);
|
let key = GenericArray::from_slice(&session_description.secret_key);
|
||||||
|
|
||||||
let decryptor = XSalsa20Poly1305::new(key);
|
let decryption_result;
|
||||||
|
|
||||||
let decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref());
|
if session_description.encryption_mode.is_xsalsa20_poly1305() {
|
||||||
|
let nonce = GenericArray::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
|
let decryptor = XSalsa20Poly1305::new(key);
|
||||||
|
|
||||||
|
decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref());
|
||||||
|
}
|
||||||
|
// Note: currently unused because I have no idea what the AeadAes256Gcm nonce is
|
||||||
|
/*else if session_description.encryption_mode.is_aead_aes256_gcm() {
|
||||||
|
let nonce = GenericArray::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
|
let decryptor = Aes256Gcm::new(key);
|
||||||
|
|
||||||
|
decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref());
|
||||||
|
|
||||||
|
}*/
|
||||||
|
else {
|
||||||
|
error!(
|
||||||
|
"This voice encryption mode ({:?}) is not yet implemented.",
|
||||||
|
session_description.encryption_mode
|
||||||
|
);
|
||||||
|
return Err(VoiceUdpError::EncryptionModeNotImplemented {
|
||||||
|
encryption_mode: format!("{:?}", session_description.encryption_mode),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Note: this may seem like we are throwing away valuable error handling data,
|
// Note: this may seem like we are throwing away valuable error handling data,
|
||||||
// but the decryption error provides no extra info.
|
// but the decryption error provides no extra info.
|
||||||
|
|
Loading…
Reference in New Issue