add session encryption
This commit is contained in:
6 files changed
+307
-462
No files matched your search
Generated
+185
-414
File diff suppressed because it is too large.
Load diff
+10
-7
@@ -1,24 +1,27 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "denny-jack"
|
name = "denny-jack"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.45", features = ["derive"] }
|
clap = { version = "4.5.45", features = ["derive"] }
|
||||||
config = "0.15.14"
|
config = "0.15.14"
|
||||||
poise = "0.6.1"
|
poise = "0.7.1"
|
||||||
serde = "1.0.219"
|
serde = "1.0.219"
|
||||||
songbird = { version = "0.6.0", features = ["builtin-queue", "driver"] }
|
songbird = { version = "0.6.1", features = ["builtin-queue", "driver"] }
|
||||||
thiserror = "2.0.15"
|
thiserror = "2.0.15"
|
||||||
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
error = "0.1.9"
|
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
audiopus = "0.3.0-rc.0"
|
opus= { version="0.4.0" }
|
||||||
circular-buffer = "1.1.0"
|
opusic-sys = { version = "0.7.5", features = ["bundled", "no-stack-protector", "no-hardening", "no-fortify-source"] }
|
||||||
|
circular-buffer = "2.0.1"
|
||||||
tap-interface = {version = "0.1.0", registry = "ahines"}
|
tap-interface = {version = "0.1.0", registry = "ahines"}
|
||||||
prost = "0.14.1"
|
prost = "0.14.1"
|
||||||
|
aws-lc-rs = "1.18.2"
|
||||||
|
hex = "0.4.3"
|
||||||
|
tokio-util = "0.7.20"
|
||||||
|
|
||||||
[dependencies.symphonia]
|
[dependencies.symphonia]
|
||||||
version = "0.5"
|
version = "0.5.4"
|
||||||
features = ["aac", "mp3", "isomp4", "alac"] # ...as well as any extras you need!
|
features = ["aac", "mp3", "isomp4", "alac"] # ...as well as any extras you need!
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
use circular_buffer::CircularBuffer;
|
use circular_buffer::FixedCircularBuffer;
|
||||||
use songbird::input::core::io::MediaSource;
|
use songbird::input::core::io::MediaSource;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Seek, Write},
|
io::{Read, Seek, Write},
|
||||||
sync::{Arc, Condvar, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 64 * 1024;
|
const BUFFER_SIZE: usize = 64 * 1024;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct CircularBufferSource {
|
pub struct CircularBufferSource {
|
||||||
circular_buffer: Arc<Mutex<CircularBuffer<BUFFER_SIZE, u8>>>,
|
circular_buffer: Arc<Mutex<FixedCircularBuffer<u8, BUFFER_SIZE>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for CircularBufferSource {
|
impl Read for CircularBufferSource {
|
||||||
|
|||||||
+63
-20
@@ -7,14 +7,18 @@ use crate::circular_buffer_source::CircularBufferSource;
|
|||||||
use crate::dj_config::{Args, DJConfig};
|
use crate::dj_config::{Args, DJConfig};
|
||||||
use crate::stream_task::{StreamControl, stream_task};
|
use crate::stream_task::{StreamControl, stream_task};
|
||||||
use crate::udp_source::UdpSource;
|
use crate::udp_source::UdpSource;
|
||||||
|
use aws_lc_rs::cipher::AES_256_KEY_LEN;
|
||||||
|
use aws_lc_rs::rand;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
use poise::serenity_prelude::CreateMessage;
|
||||||
use poise::{PrefixFrameworkOptions, serenity_prelude as serenity};
|
use poise::{PrefixFrameworkOptions, serenity_prelude as serenity};
|
||||||
use songbird::SerenityInit;
|
use songbird::SerenityInit;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::sync::mpsc::{Sender, channel};
|
use tokio::sync::mpsc::{Sender, channel};
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use tracing_subscriber::filter::LevelFilter;
|
use tracing_subscriber::filter::LevelFilter;
|
||||||
|
|
||||||
@@ -26,12 +30,17 @@ pub enum Error {
|
|||||||
FailedToJoin(#[from] songbird::error::JoinError),
|
FailedToJoin(#[from] songbird::error::JoinError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DennyJackControl {
|
||||||
|
pub udp_task: tokio::task::JoinHandle<()>,
|
||||||
|
pub stream_control_tx: Sender<StreamControl>,
|
||||||
|
pub cancellation_token: CancellationToken,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct Data {
|
struct Data {
|
||||||
pub config: DJConfig,
|
pub config: DJConfig,
|
||||||
pub udp_task: tokio::task::JoinHandle<()>,
|
|
||||||
pub circular_buffer_source: CircularBufferSource,
|
pub circular_buffer_source: CircularBufferSource,
|
||||||
pub stream_control_tx: Mutex<Option<Sender<StreamControl>>>,
|
pub dj_control: Mutex<Option<DennyJackControl>>,
|
||||||
}
|
}
|
||||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
@@ -39,8 +48,8 @@ const COMMAND_QUEUE_SIZE: usize = 10;
|
|||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, guild_only)]
|
#[poise::command(slash_command, prefix_command, guild_only)]
|
||||||
async fn join(ctx: Context<'_>) -> Result<(), Error> {
|
async fn join(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
let mut stream_control_tx = match ctx.data().stream_control_tx.try_lock() {
|
let mut dj_control = match ctx.data().dj_control.try_lock() {
|
||||||
Ok(stream_control_tx) => stream_control_tx,
|
Ok(dj_control) => dj_control,
|
||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
ctx.reply("Yo yo yo, looks like someone else is streaming on the net.")
|
ctx.reply("Yo yo yo, looks like someone else is streaming on the net.")
|
||||||
.await?;
|
.await?;
|
||||||
@@ -67,15 +76,43 @@ async fn join(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut key = vec![0; AES_256_KEY_LEN];
|
||||||
|
rand::fill(&mut key).expect("Failed to generate key");
|
||||||
|
|
||||||
|
ctx.author()
|
||||||
|
.direct_message(
|
||||||
|
&ctx,
|
||||||
|
CreateMessage::new().content(format!(
|
||||||
|
"Yo Yo, your session key is `{}`.\nYou send your sweet tones to me at `{}`",
|
||||||
|
hex::encode(&key),
|
||||||
|
ctx.data().config.server_addr
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let cancellation_token = CancellationToken::new();
|
||||||
|
let udp_input = UdpSocket::bind(ctx.data().config.server_addr)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut udp_source = UdpSource::new(udp_input, ctx.data().circular_buffer_source.clone(), &key);
|
||||||
|
|
||||||
|
let udp_task_token = cancellation_token.clone();
|
||||||
|
let udp_task = tokio::spawn(async move { udp_source.worker(udp_task_token).await });
|
||||||
|
|
||||||
let manager = songbird::get(ctx.serenity_context())
|
let manager = songbird::get(ctx.serenity_context())
|
||||||
.await
|
.await
|
||||||
.expect("Songbird Voice client placed in at initialisation.")
|
.expect("Songbird Voice client placed in at initialization.")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
manager.join(guild_id, connect_to).await?;
|
manager.join(guild_id, connect_to).await?;
|
||||||
|
|
||||||
let (tx, rx) = channel(COMMAND_QUEUE_SIZE);
|
let (tx, rx) = channel(COMMAND_QUEUE_SIZE);
|
||||||
*stream_control_tx = Some(tx);
|
|
||||||
|
*dj_control = Some(DennyJackControl {
|
||||||
|
udp_task,
|
||||||
|
stream_control_tx: tx,
|
||||||
|
cancellation_token,
|
||||||
|
});
|
||||||
|
|
||||||
let circular_buffer_source = ctx.data().circular_buffer_source.clone();
|
let circular_buffer_source = ctx.data().circular_buffer_source.clone();
|
||||||
tokio::spawn(async move { stream_task(circular_buffer_source, manager, guild_id, rx).await });
|
tokio::spawn(async move { stream_task(circular_buffer_source, manager, guild_id, rx).await });
|
||||||
@@ -84,16 +121,16 @@ async fn join(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn send_command(ctx: Context<'_>, command: StreamControl) -> Result<bool, Error> {
|
async fn send_command(ctx: Context<'_>, command: StreamControl) -> Result<bool, Error> {
|
||||||
let stream_control_tx = ctx.data().stream_control_tx.lock().await;
|
let dj_control = ctx.data().dj_control.lock().await;
|
||||||
|
|
||||||
Ok(match &*stream_control_tx {
|
Ok(match &*dj_control {
|
||||||
None => {
|
None => {
|
||||||
ctx.reply("I can't control what doesn't exist, deep man.")
|
ctx.reply("I can't control what doesn't exist, deep man.")
|
||||||
.await?;
|
.await?;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Some(tx) => {
|
Some(dj_control) => {
|
||||||
tx.send(command).await.unwrap();
|
dj_control.stream_control_tx.send(command).await.unwrap();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -119,18 +156,29 @@ async fn pause(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, guild_only)]
|
#[poise::command(slash_command, prefix_command, guild_only)]
|
||||||
async fn disconnect(ctx: Context<'_>) -> Result<(), Error> {
|
async fn disconnect(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
let mut stream_control_tx = ctx.data().stream_control_tx.lock().await;
|
let mut dj_control = ctx.data().dj_control.lock().await;
|
||||||
*stream_control_tx = None;
|
|
||||||
|
let dj_control = dj_control.take();
|
||||||
|
|
||||||
|
if let Some(dj_control) = dj_control {
|
||||||
|
dj_control.cancellation_token.cancel();
|
||||||
|
if let Err(err) = dj_control.udp_task.await {
|
||||||
|
error!("Failed to join UdpSource: {err}")
|
||||||
|
}
|
||||||
|
|
||||||
let manager = songbird::get(ctx.serenity_context())
|
let manager = songbird::get(ctx.serenity_context())
|
||||||
.await
|
.await
|
||||||
.expect("Songbird Voice client placed in at initialisation.")
|
.expect("Songbird Voice client placed in at initialization.")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
manager.leave(ctx.guild_id().unwrap()).await?;
|
manager.leave(ctx.guild_id().unwrap()).await?;
|
||||||
|
|
||||||
ctx.reply("Until next time, friend DJ Denny Jack OUT!")
|
ctx.reply("Until next time, friend DJ Denny Jack OUT!")
|
||||||
.await?;
|
.await?;
|
||||||
|
} else {
|
||||||
|
ctx.reply("You must be confused, I'm off the air waves for the moment")
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -158,24 +206,19 @@ async fn main() {
|
|||||||
let intents =
|
let intents =
|
||||||
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT;
|
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT;
|
||||||
|
|
||||||
let udp_input = UdpSocket::bind(cfg.server_addr).await.unwrap();
|
|
||||||
let source = CircularBufferSource::default();
|
let source = CircularBufferSource::default();
|
||||||
|
|
||||||
let mut udp_source = UdpSource::new(udp_input, source.clone());
|
|
||||||
let udp_task = tokio::spawn(async move { udp_source.worker().await });
|
|
||||||
|
|
||||||
let data = Data {
|
let data = Data {
|
||||||
config: cfg.clone(),
|
config: cfg.clone(),
|
||||||
udp_task,
|
|
||||||
circular_buffer_source: source,
|
circular_buffer_source: source,
|
||||||
stream_control_tx: Mutex::new(None),
|
dj_control: Mutex::new(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let framework = poise::Framework::builder()
|
let framework = poise::Framework::builder()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
commands: vec![join(), play(), pause(), disconnect()],
|
commands: vec![join(), play(), pause(), disconnect()],
|
||||||
prefix_options: PrefixFrameworkOptions {
|
prefix_options: PrefixFrameworkOptions {
|
||||||
prefix: Some("!".to_string()),
|
prefix: Some("!".to_string().into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ pub async fn stream_task(
|
|||||||
loop {
|
loop {
|
||||||
let msg = match receiver.recv().await {
|
let msg = match receiver.recv().await {
|
||||||
None => {
|
None => {
|
||||||
info!("Stopping Stram Task...");
|
info!("Stopping Stream Task...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Some(msg) => msg,
|
Some(msg) => msg,
|
||||||
|
|||||||
+45
-17
@@ -1,14 +1,16 @@
|
|||||||
use crate::circular_buffer_source::CircularBufferSource;
|
use crate::circular_buffer_source::CircularBufferSource;
|
||||||
use audiopus::coder::Decoder;
|
use aws_lc_rs::aead::{AES_256_GCM, Aad, NONCE_LEN, Nonce, RandomizedNonceKey};
|
||||||
use audiopus::{Channels, MutSignals, SampleRate};
|
use aws_lc_rs::error::Unspecified;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info, trace};
|
||||||
use prost::{DecodeError, Message};
|
use prost::{DecodeError, Message};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tap_interface::tap::message::TapMessage;
|
use tap_interface::tap::message::TapMessage;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
|
use tokio::select;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
pub const SAMPLE_RATE: SampleRate = SampleRate::Hz48000;
|
pub const SAMPLE_RATE: u32 = 48_000;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
@@ -16,7 +18,7 @@ pub enum Error {
|
|||||||
#[error("IO Error {0}")]
|
#[error("IO Error {0}")]
|
||||||
IOError(#[from] std::io::Error),
|
IOError(#[from] std::io::Error),
|
||||||
#[error("Failed to decode audio in packet: {0}")]
|
#[error("Failed to decode audio in packet: {0}")]
|
||||||
OpusError(#[from] audiopus::Error),
|
OpusError(#[from] opus::Error),
|
||||||
#[error("Failed to decode packet: {0}")]
|
#[error("Failed to decode packet: {0}")]
|
||||||
DecodeError(#[from] DecodeError),
|
DecodeError(#[from] DecodeError),
|
||||||
}
|
}
|
||||||
@@ -24,17 +26,19 @@ pub enum Error {
|
|||||||
pub struct UdpSource {
|
pub struct UdpSource {
|
||||||
udp: UdpSocket,
|
udp: UdpSocket,
|
||||||
source: CircularBufferSource,
|
source: CircularBufferSource,
|
||||||
decoder: Decoder,
|
decoder: opus::Decoder,
|
||||||
expected_seq: u32,
|
expected_seq: u32,
|
||||||
|
key: RandomizedNonceKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpSource {
|
impl UdpSource {
|
||||||
pub fn new(udp_socket: UdpSocket, source: CircularBufferSource) -> Self {
|
pub fn new(udp_socket: UdpSocket, source: CircularBufferSource, key: &[u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
udp: udp_socket,
|
udp: udp_socket,
|
||||||
source,
|
source,
|
||||||
decoder: Decoder::new(SAMPLE_RATE, Channels::Stereo).unwrap(),
|
decoder: opus::Decoder::new(SAMPLE_RATE, opus::Channels::Stereo).unwrap(),
|
||||||
expected_seq: 0,
|
expected_seq: 0,
|
||||||
|
key: RandomizedNonceKey::new(&AES_256_GCM, key).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,17 +47,15 @@ impl UdpSource {
|
|||||||
input_buffer: &[u8],
|
input_buffer: &[u8],
|
||||||
output_buffer: &mut [f32],
|
output_buffer: &mut [f32],
|
||||||
) -> Result<usize, Error> {
|
) -> Result<usize, Error> {
|
||||||
let packet = audiopus::packet::Packet::try_from(input_buffer)?;
|
let sample_size = self
|
||||||
|
.decoder
|
||||||
let signals = MutSignals::try_from(output_buffer)?;
|
.decode_float(input_buffer, output_buffer, false)?;
|
||||||
|
|
||||||
let sample_size = self.decoder.decode_float(Some(packet), signals, false)?;
|
|
||||||
|
|
||||||
Ok(sample_size * 2)
|
Ok(sample_size * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_audio_data_to_source(&mut self, samples: &[f32]) -> Result<(), Error> {
|
fn write_audio_data_to_source(&mut self, samples: &[f32]) -> Result<(), Error> {
|
||||||
let mut sample_bytes = Vec::with_capacity(std::mem::size_of_val(samples));
|
let mut sample_bytes = Vec::with_capacity(size_of_val(samples));
|
||||||
|
|
||||||
for sample in samples {
|
for sample in samples {
|
||||||
let bytes = sample.to_le_bytes();
|
let bytes = sample.to_le_bytes();
|
||||||
@@ -69,20 +71,46 @@ impl UdpSource {
|
|||||||
self.udp.recv(buffer).await
|
self.udp.recv(buffer).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn worker(&mut self) {
|
fn decrypt<'a>(&'a mut self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], Unspecified> {
|
||||||
|
let nonce = &buffer[0..NONCE_LEN];
|
||||||
|
let nonce = Nonce::try_assume_unique_for_key(nonce)?;
|
||||||
|
|
||||||
|
self.key
|
||||||
|
.open_in_place(nonce, Aad::empty(), &mut buffer[NONCE_LEN..])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn worker(&mut self, cancellation_token: CancellationToken) {
|
||||||
info!("Starting TAP UDP Endpoint");
|
info!("Starting TAP UDP Endpoint");
|
||||||
let mut buffer = vec![0; 1024 * 32];
|
let mut buffer = vec![0; 1024 * 32];
|
||||||
let mut decode_data = vec![0.0; tap_interface::STEREO_FRAME_SIZE];
|
let mut decode_data = vec![0.0; tap_interface::STEREO_FRAME_SIZE];
|
||||||
loop {
|
loop {
|
||||||
let msg_size = match self.recv_message(&mut buffer).await {
|
let msg_size = select! {
|
||||||
|
res = self.recv_message(&mut buffer) => {
|
||||||
|
|
||||||
|
|
||||||
|
match res {
|
||||||
Ok(msg_size) => msg_size,
|
Ok(msg_size) => msg_size,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed to recv udp packet: {err:?}");
|
error!("Failed to recv udp packet: {err:?}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = cancellation_token.cancelled() => {
|
||||||
|
info!("Closing TAP UDP Endpoint...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let tap_message = match TapMessage::decode(&buffer[..msg_size]) {
|
let decrypted_msg = match self.decrypt(&mut buffer[0..msg_size]) {
|
||||||
|
Ok(msg) => msg,
|
||||||
|
Err(_err) => {
|
||||||
|
trace!("Failed to decrypt message");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tap_message = match TapMessage::decode(&*decrypted_msg) {
|
||||||
Ok(tap_msg) => tap_msg,
|
Ok(tap_msg) => tap_msg,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed decode proto message: {err:?}");
|
error!("Failed decode proto message: {err:?}");
|
||||||
|
|||||||
Reference in new issue
Block a user