Compare commits
10 Commits
5d320a394b
...
e8c134664b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8c134664b | ||
|
|
8956352f13 | ||
|
|
64868e7213 | ||
|
|
5ba5170c91 | ||
|
|
ff7bd4a9ee | ||
|
|
84accb0e3a | ||
|
|
e09843ded7 | ||
|
|
d0b7fbb911 | ||
|
|
f05b262dfe | ||
|
|
47cf0b27eb |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -107,7 +107,7 @@ jobs:
|
|||||||
sudo apt-get install -y libopus-dev
|
sudo apt-get install -y libopus-dev
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
env:
|
env:
|
||||||
RUSTDOCFLAGS: -D broken_intra_doc_links
|
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links
|
||||||
run: |
|
run: |
|
||||||
cargo doc --no-deps --features full-doc
|
cargo doc --no-deps --features full-doc
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
env:
|
env:
|
||||||
RUSTDOCFLAGS: -D broken_intra_doc_links
|
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links
|
||||||
run: |
|
run: |
|
||||||
cargo doc --no-deps --features full-doc
|
cargo doc --no-deps --features full-doc
|
||||||
|
|
||||||
|
|||||||
73
CHANGELOG.md
73
CHANGELOG.md
@@ -1,5 +1,75 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [v0.5.0] — 2025-02-21 — **Starling**
|
||||||
|
|
||||||
|
Starlings are social birds, known for chittering loudly (often sounding like human speech!) and flocking en masse. Iridescent and speckled like the night sky after twilight, their murmurations make for a beautiful evening sight.
|
||||||
|
|
||||||
|
This is a smaller breaking release which adds support for the latest version of [*twilight*](https://twilight.rs/) (v0.16), with a few main breaking changes:
|
||||||
|
- `TrackHandle`s now store an arbitrary `Arc<dyn Any>`, rather than a `TypeMap`. You can set this field to any desired type during track creation using the `Track::new_with_data` API.
|
||||||
|
- Legacy encryption modes `"xsalsa20_poly1305"`, `"xsalsa20_poly1305_suffix"`, and `"xsalsa20_poly1305_lite"` have been removed. If you were explicitly specifying these then please change over to one of the new modes. Discord have ceased serving voice connections with these parameters.
|
||||||
|
- `simd-json` support has been removed from the library.
|
||||||
|
|
||||||
|
Thanks to the following for their contributions:
|
||||||
|
|
||||||
|
- [@decahedron1]
|
||||||
|
- [@DPlayer234]
|
||||||
|
- [@Erk-]
|
||||||
|
- [@FelixMcFelix]
|
||||||
|
- [@GnomedDev]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Driver: Support `tokio-websockets` ([@decahedron1]) [c:c4331c4]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Chore: quick respin on twilight-example. ([@FelixMcFelix]) [c:e09843d]
|
||||||
|
- Chore: clippy appeasement ([@FelixMcFelix]) [c:47cf0b2]
|
||||||
|
- Chore: Bump rubato->0.16 ([@FelixMcFelix]) [c:5d320a3]
|
||||||
|
- Chore: Bump rand->0.9, tokio-tungstenite->0.26 ([@FelixMcFelix]) [c:b39ab98]
|
||||||
|
- Support for Twilight 0.16 ([@Erk-]) [c:b46a568]
|
||||||
|
- Update `tokio-tungstenite` dependency to 0.24 ([@DPlayer234]) [c:1c52e6e]
|
||||||
|
- Update to DashMap 6 ([@GnomedDev]) [c:9a244ba]
|
||||||
|
- Bump dependencies ([@GnomedDev]) [c:e9b2243]
|
||||||
|
- Chore: Update `tokio-websockets` to v0.7 ([@decahedron1]) [c:3bc132f]
|
||||||
|
- Tracks: Replace `RwLock<TypeMap>` data store with `Arc<dyn Any>` ([@GnomedDev]) [c:0d6a226]
|
||||||
|
- Allow borrowed strings for YoutubeDl ([@GnomedDev]) [c:2bcc522]
|
||||||
|
- Deps: Replace OnceCell with std::sync::OnceLock ([@GnomedDev]) [c:743a1d2]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Docs: remove unused link. ([@FelixMcFelix]) [c:d0b7fbb]
|
||||||
|
- Remove unnecessary simd-json feature gate ([@GnomedDev]) [c:b435e16]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Driver: Remove legacy encryption algorithms. ([@FelixMcFelix]) [c:f05b262]
|
||||||
|
- Remove Simd-json ([@Erk-]) [c:c81f2a9]
|
||||||
|
|
||||||
|
<!-- COMPARISONS -->
|
||||||
|
[v0.5.0]: https://github.com/serenity-rs/songbird/compare/v0.4.6...v0.5.0
|
||||||
|
|
||||||
|
<!-- COMMITS -->
|
||||||
|
[c:e09843d]: https://github.com/serenity-rs/songbird/commit/e09843ded7d8a492159ad62854a88c17d013703a
|
||||||
|
[c:d0b7fbb]: https://github.com/serenity-rs/songbird/commit/d0b7fbb911931b9ad9bc9988bef48d79650e11d6
|
||||||
|
[c:f05b262]: https://github.com/serenity-rs/songbird/commit/f05b262dfe25c35c9ac010c53cce54b6ca464581
|
||||||
|
[c:47cf0b2]: https://github.com/serenity-rs/songbird/commit/47cf0b27ebe5587b23d7fe8b8e125ae0ccde304b
|
||||||
|
[c:5d320a3]: https://github.com/serenity-rs/songbird/commit/5d320a394b2b00cb4dd04636c0c98aabdbbb7923
|
||||||
|
[c:b39ab98]: https://github.com/serenity-rs/songbird/commit/b39ab982238a50eecfefbfd25a07ffc1be44a516
|
||||||
|
[c:b46a568]: https://github.com/serenity-rs/songbird/commit/b46a568fb51b4ae92841485ee626728d43ad0731
|
||||||
|
[c:1c52e6e]: https://github.com/serenity-rs/songbird/commit/1c52e6ebc047d21a64551704cc02ef42d29f5f86
|
||||||
|
[c:b435e16]: https://github.com/serenity-rs/songbird/commit/b435e167d2760cd4a353fa422f33a0039f4151b5
|
||||||
|
[c:9a244ba]: https://github.com/serenity-rs/songbird/commit/9a244ba4c28377458a0ebb039f20093b09969399
|
||||||
|
[c:e9b2243]: https://github.com/serenity-rs/songbird/commit/e9b2243b83e61c874b0026e8d223ecbb7b879bba
|
||||||
|
[c:3bc132f]: https://github.com/serenity-rs/songbird/commit/3bc132faaff77bafaae85f93ed6a0a82083b8ec1
|
||||||
|
[c:c81f2a9]: https://github.com/serenity-rs/songbird/commit/c81f2a95780b0e32b878a664c8e69f714650e47a
|
||||||
|
[c:c4331c4]: https://github.com/serenity-rs/songbird/commit/c4331c451fc233ac97c8363cea32e6c5b30c5df5
|
||||||
|
[c:0d6a226]: https://github.com/serenity-rs/songbird/commit/0d6a226910943a56c5d53e3651958f480c5ee7c4
|
||||||
|
[c:2bcc522]: https://github.com/serenity-rs/songbird/commit/2bcc5223feb1e631cd623c71628010a56aff97bb
|
||||||
|
[c:743a1d2]: https://github.com/serenity-rs/songbird/commit/743a1d262e6bf9e9f3767b6d9646c680ef4172c4
|
||||||
|
|
||||||
|
<!-- generated by git-cliff -->
|
||||||
|
|
||||||
## [v0.4.6] — 2024-12-04
|
## [v0.4.6] — 2024-12-04
|
||||||
|
|
||||||
This patch release adds the ability to pass custom arguments into `yt-dlp` instances, and fixes early event handling needed for voice receive as well as inner track handle state after `set_track` is called.
|
This patch release adds the ability to pass custom arguments into `yt-dlp` instances, and fixes early event handling needed for voice receive as well as inner track handle state after `set_track` is called.
|
||||||
@@ -23,7 +93,6 @@ Thanks to the following for their contributions:
|
|||||||
<!-- COMPARISONS -->
|
<!-- COMPARISONS -->
|
||||||
[v0.4.6]: https://github.com/serenity-rs/songbird/compare/v0.4.5...v0.4.6
|
[v0.4.6]: https://github.com/serenity-rs/songbird/compare/v0.4.5...v0.4.6
|
||||||
|
|
||||||
|
|
||||||
<!-- COMMITS -->
|
<!-- COMMITS -->
|
||||||
[c:651d037]: https://github.com/serenity-rs/songbird/commit/651d037a54ce37f5b69f456ccdfdb03ba9de8a91
|
[c:651d037]: https://github.com/serenity-rs/songbird/commit/651d037a54ce37f5b69f456ccdfdb03ba9de8a91
|
||||||
[c:17993bc]: https://github.com/serenity-rs/songbird/commit/17993bc0d0ae7a4e43e86bbb6439c30538b67832
|
[c:17993bc]: https://github.com/serenity-rs/songbird/commit/17993bc0d0ae7a4e43e86bbb6439c30538b67832
|
||||||
@@ -789,7 +858,9 @@ We'd also like to thank all users who have contributed to this module in the pas
|
|||||||
[@clarity0]: https://github.com/clarity0
|
[@clarity0]: https://github.com/clarity0
|
||||||
[@cycle-five]: https://github.com/cycle-five
|
[@cycle-five]: https://github.com/cycle-five
|
||||||
[@DasEtwas]: https://github.com/DasEtwas
|
[@DasEtwas]: https://github.com/DasEtwas
|
||||||
|
[@decahedron1]: https://github.com/decahedron1
|
||||||
[@DoumanAsh]: https://github.com/DoumanAsh
|
[@DoumanAsh]: https://github.com/DoumanAsh
|
||||||
|
[@DPlayer234]: https://github.com/DPlayer234
|
||||||
[@Elinvynia]: https://github.com/Elinvynia
|
[@Elinvynia]: https://github.com/Elinvynia
|
||||||
[@Erk-]: https://github.com/Erk-
|
[@Erk-]: https://github.com/Erk-
|
||||||
[@fee1-dead]: https://github.com/fee1-dead
|
[@fee1-dead]: https://github.com/fee1-dead
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ license = "ISC"
|
|||||||
name = "songbird"
|
name = "songbird"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/serenity-rs/songbird.git"
|
repository = "https://github.com/serenity-rs/songbird.git"
|
||||||
rust-version = "1.74"
|
rust-version = "1.74.1"
|
||||||
version = "0.4.6"
|
version = "0.5.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aead = { optional = true, version = "0.5.2" }
|
aead = { optional = true, version = "0.5.2" }
|
||||||
@@ -21,7 +21,7 @@ audiopus = { optional = true, version = "0.3.0-rc.0" }
|
|||||||
byteorder = { optional = true, version = "1" }
|
byteorder = { optional = true, version = "1" }
|
||||||
bytes = { optional = true, version = "1" }
|
bytes = { optional = true, version = "1" }
|
||||||
chacha20poly1305 = { optional = true, version = "0.10.1" }
|
chacha20poly1305 = { optional = true, version = "0.10.1" }
|
||||||
crypto_secretbox = { optional = true, features = ["std"], version = "0.1" }
|
crypto-common = { optional = true, features = ["std"], version = "0.1" }
|
||||||
dashmap = { optional = true, version = "6.1.0" }
|
dashmap = { optional = true, version = "6.1.0" }
|
||||||
derivative = "2"
|
derivative = "2"
|
||||||
discortp = { default-features = false, features = [
|
discortp = { default-features = false, features = [
|
||||||
@@ -99,7 +99,7 @@ driver = [
|
|||||||
"dep:byteorder",
|
"dep:byteorder",
|
||||||
"dep:bytes",
|
"dep:bytes",
|
||||||
"dep:chacha20poly1305",
|
"dep:chacha20poly1305",
|
||||||
"dep:crypto_secretbox",
|
"dep:crypto-common",
|
||||||
"dep:discortp",
|
"dep:discortp",
|
||||||
"dep:flume",
|
"dep:flume",
|
||||||
"dep:nohash-hasher",
|
"dep:nohash-hasher",
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ project you will need to depend on Symphonia as well.
|
|||||||
```toml
|
```toml
|
||||||
# Including songbird alone gives you support for Opus via the DCA file format.
|
# Including songbird alone gives you support for Opus via the DCA file format.
|
||||||
[dependencies.songbird]
|
[dependencies.songbird]
|
||||||
version = "0.4"
|
version = "0.5"
|
||||||
features = ["builtin-queue"]
|
features = ["builtin-queue"]
|
||||||
|
|
||||||
# To get additional codecs, you *must* add Symphonia yourself.
|
# To get additional codecs, you *must* add Symphonia yourself.
|
||||||
# This includes the default formats (MKV/WebM, Ogg, Wave) and codecs (FLAC, PCM, Vorbis)...
|
# This includes the default formats (MKV/WebM, Ogg, Wave) and codecs (FLAC, PCM, Vorbis)...
|
||||||
[dependencies.symphonia]
|
[dependencies.symphonia]
|
||||||
version = "0.5.2"
|
version = "0.5"
|
||||||
features = ["aac", "mp3", "isomp4", "alac"] # ...as well as any extras you need!
|
features = ["aac", "mp3", "isomp4", "alac"] # ...as well as any extras you need!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ resolver = "2"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
serenity = { features = ["cache", "framework", "standard_framework", "voice", "http", "rustls_backend"], version = "0.12" }
|
serenity = { features = ["cache", "framework", "standard_framework", "voice", "http", "rustls_backend"], version = "0.12" }
|
||||||
songbird = { path = "../", version = "0.4", default-features = false }
|
songbird = { path = "../", version = "0.5", default-features = false }
|
||||||
symphonia = { features = ["aac", "mp3", "isomp4", "alac"], version = "0.5.2" }
|
symphonia = { features = ["aac", "mp3", "isomp4", "alac"], version = "0.5.2" }
|
||||||
tokio = { features = ["macros", "rt-multi-thread", "signal", "sync"], version = "1" }
|
tokio = { features = ["macros", "rt-multi-thread", "signal", "sync"], version = "1" }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ authors = ["my name <my@email.address>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dashmap = "5"
|
dashmap = "6"
|
||||||
serenity = { workspace = true }
|
serenity = { workspace = true }
|
||||||
songbird = { workspace = true, default-features = true, features = ["receive"] }
|
songbird = { workspace = true, default-features = true, features = ["receive"] }
|
||||||
symphonia = { workspace = true }
|
symphonia = { workspace = true }
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ use serenity::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use songbird::{
|
use songbird::{
|
||||||
driver::DecodeMode,
|
driver::{DecodeConfig, DecodeMode},
|
||||||
model::{
|
model::{
|
||||||
id::UserId,
|
id::UserId,
|
||||||
payload::{ClientDisconnect, Speaking},
|
payload::{ClientDisconnect, Speaking},
|
||||||
@@ -217,7 +217,8 @@ async fn main() {
|
|||||||
// Here, we need to configure Songbird to decode all incoming voice packets.
|
// Here, we need to configure Songbird to decode all incoming voice packets.
|
||||||
// If you want, you can do this on a per-call basis---here, we need it to
|
// If you want, you can do this on a per-call basis---here, we need it to
|
||||||
// read the audio data that other people are sending us!
|
// read the audio data that other people are sending us!
|
||||||
let songbird_config = Config::default().decode_mode(DecodeMode::Decode);
|
let songbird_config =
|
||||||
|
Config::default().decode_mode(DecodeMode::Decode(DecodeConfig::default()));
|
||||||
|
|
||||||
let mut client = Client::builder(&token, intents)
|
let mut client = Client::builder(&token, intents)
|
||||||
.event_handler(Handler)
|
.event_handler(Handler)
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ edition = "2021"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
# In an actual twilight project, use the "tws" feature as below.
|
# In an actual twilight project, use the "tws" feature as below.
|
||||||
|
# Tungstenite is used here due to workspace feature unification.
|
||||||
# songbird = { workspace = true, features = ["driver", "gateway", "twilight", "rustls", "tws"] }
|
# songbird = { workspace = true, features = ["driver", "gateway", "twilight", "rustls", "tws"] }
|
||||||
songbird = { workspace = true, features = ["driver", "gateway", "twilight", "rustls"] }
|
songbird = { workspace = true, features = ["driver", "gateway", "twilight", "rustls", "tungstenite"] }
|
||||||
symphonia = { workspace = true }
|
symphonia = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true, default-features = true }
|
tracing-subscriber = { workspace = true, default-features = true }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
use crate::driver::{Channels, DecodeMode, SampleRate};
|
use crate::driver::DecodeMode;
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
use crate::{
|
use crate::{
|
||||||
driver::{
|
driver::{
|
||||||
@@ -61,18 +61,6 @@ pub struct Config {
|
|||||||
/// [User speaking state]: crate::events::CoreEvent::VoiceTick
|
/// [User speaking state]: crate::events::CoreEvent::VoiceTick
|
||||||
pub decode_mode: DecodeMode,
|
pub decode_mode: DecodeMode,
|
||||||
|
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
|
||||||
/// Configures the channel layout for output audio when using [`DecodeMode::Decode`].
|
|
||||||
///
|
|
||||||
/// Defaults to [`Channels::Stereo`].
|
|
||||||
pub decode_channels: Channels,
|
|
||||||
|
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
|
||||||
/// Configures the sample rate for output audio when using [`DecodeMode::Decode`].
|
|
||||||
///
|
|
||||||
/// Defaults to [`SampleRate::Hz48000`].
|
|
||||||
pub decode_sample_rate: SampleRate,
|
|
||||||
|
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
/// Configures the amount of time after a user/SSRC is inactive before their decoder state
|
/// Configures the amount of time after a user/SSRC is inactive before their decoder state
|
||||||
/// should be removed.
|
/// should be removed.
|
||||||
@@ -223,10 +211,6 @@ impl Default for Config {
|
|||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
decode_mode: DecodeMode::Decrypt,
|
decode_mode: DecodeMode::Decrypt,
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
decode_channels: Channels::Stereo,
|
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
|
||||||
decode_sample_rate: SampleRate::Hz48000,
|
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
|
||||||
decode_state_timeout: Duration::from_secs(60),
|
decode_state_timeout: Duration::from_secs(60),
|
||||||
#[cfg(all(feature = "driver", feature = "receive"))]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
playout_buffer_length: NonZeroUsize::new(5).unwrap(),
|
playout_buffer_length: NonZeroUsize::new(5).unwrap(),
|
||||||
@@ -279,22 +263,6 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "receive")]
|
|
||||||
/// Sets this `Config`'s channel layout for output audio when using [`DecodeMode::Decode`]
|
|
||||||
#[must_use]
|
|
||||||
pub fn decode_channels(mut self, decode_channels: Channels) -> Self {
|
|
||||||
self.decode_channels = decode_channels;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "receive")]
|
|
||||||
/// Sets this `Config`'s sample rate for output audio when using [`DecodeMode::Decode`]
|
|
||||||
#[must_use]
|
|
||||||
pub fn decode_sample_rate(mut self, decode_sample_rate: SampleRate) -> Self {
|
|
||||||
self.decode_sample_rate = decode_sample_rate;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "receive")]
|
#[cfg(feature = "receive")]
|
||||||
/// Sets this `Config`'s received packet decoder cleanup timer.
|
/// Sets this `Config`'s received packet decoder cleanup timer.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
driver::tasks::{error::Recipient, message::*},
|
driver::tasks::{error::Recipient, message::*},
|
||||||
ws::Error as WsError,
|
ws::Error as WsError,
|
||||||
};
|
};
|
||||||
use crypto_secretbox::Error as CryptoError;
|
use aes_gcm::Error as CryptoError;
|
||||||
use flume::SendError;
|
use flume::SendError;
|
||||||
use serde_json::Error as JsonError;
|
use serde_json::Error as JsonError;
|
||||||
use std::{error::Error as StdError, fmt, io::Error as IoError};
|
use std::{error::Error as StdError, fmt, io::Error as IoError};
|
||||||
@@ -118,6 +118,7 @@ impl StdError for Error {
|
|||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Error::AttemptDiscarded
|
Error::AttemptDiscarded
|
||||||
|
| Error::Crypto(_)
|
||||||
| Error::CryptoInvalidLength
|
| Error::CryptoInvalidLength
|
||||||
| Error::CryptoModeInvalid
|
| Error::CryptoModeInvalid
|
||||||
| Error::CryptoModeUnavailable
|
| Error::CryptoModeUnavailable
|
||||||
@@ -127,7 +128,6 @@ impl StdError for Error {
|
|||||||
| Error::InterconnectFailure(_)
|
| Error::InterconnectFailure(_)
|
||||||
| Error::Ws(_)
|
| Error::Ws(_)
|
||||||
| Error::TimedOut => None,
|
| Error::TimedOut => None,
|
||||||
Error::Crypto(e) => e.source(),
|
|
||||||
Error::Io(e) => e.source(),
|
Error::Io(e) => e.source(),
|
||||||
Error::Json(e) => e.source(),
|
Error::Json(e) => e.source(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
//! Encryption schemes supported by Discord's secure RTP negotiation.
|
//! Encryption schemes supported by Discord's secure RTP negotiation.
|
||||||
|
#[cfg(any(feature = "receive", test))]
|
||||||
use super::tasks::error::Error as InternalError;
|
use super::tasks::error::Error as InternalError;
|
||||||
use aead::AeadCore;
|
use aead::AeadCore;
|
||||||
use aes_gcm::{AeadInPlace, Aes256Gcm, KeyInit};
|
use aes_gcm::{AeadInPlace, Aes256Gcm, Error as CryptoError};
|
||||||
use byteorder::{NetworkEndian, WriteBytesExt};
|
use byteorder::{NetworkEndian, WriteBytesExt};
|
||||||
use chacha20poly1305::XChaCha20Poly1305;
|
use chacha20poly1305::XChaCha20Poly1305;
|
||||||
use crypto_secretbox::{cipher::InvalidLength, Error as CryptoError, XSalsa20Poly1305};
|
use crypto_common::{InvalidLength, KeyInit};
|
||||||
#[cfg(feature = "receive")]
|
#[cfg(feature = "receive")]
|
||||||
use discortp::rtcp::MutableRtcpPacket;
|
use discortp::rtcp::MutableRtcpPacket;
|
||||||
use discortp::{rtp::RtpPacket, MutablePacket};
|
use discortp::MutablePacket;
|
||||||
#[cfg(any(feature = "receive", test))]
|
#[cfg(any(feature = "receive", test))]
|
||||||
use discortp::{
|
use discortp::{
|
||||||
rtp::{MutableRtpPacket, RtpExtensionPacket},
|
rtp::{MutableRtpPacket, RtpExtensionPacket},
|
||||||
Packet,
|
Packet,
|
||||||
};
|
};
|
||||||
use rand::Rng;
|
|
||||||
use std::{num::Wrapping, str::FromStr};
|
use std::{num::Wrapping, str::FromStr};
|
||||||
use typenum::Unsigned;
|
use typenum::Unsigned;
|
||||||
|
|
||||||
use crate::error::ConnectionError;
|
use crate::error::ConnectionError;
|
||||||
|
|
||||||
/// Variants of the `XSalsa20Poly1305` encryption scheme.
|
/// Encryption schemes supportd by Discord.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CryptoMode {
|
pub enum CryptoMode {
|
||||||
@@ -46,45 +46,11 @@ pub enum CryptoMode {
|
|||||||
///
|
///
|
||||||
/// Nonce width of 4B (32b), at an extra 4B per packet (~0.2 kB/s).
|
/// Nonce width of 4B (32b), at an extra 4B per packet (~0.2 kB/s).
|
||||||
XChaCha20Poly1305,
|
XChaCha20Poly1305,
|
||||||
#[deprecated(
|
|
||||||
since = "0.4.4",
|
|
||||||
note = "This voice encryption mode will no longer be accepted by Discord\
|
|
||||||
as of 2024-11-18. This variant will be removed in `v0.5`."
|
|
||||||
)]
|
|
||||||
/// The RTP header is used as the source of nonce bytes for the packet.
|
|
||||||
///
|
|
||||||
/// Equivalent to a nonce of at most 48b (6B) at no extra packet overhead:
|
|
||||||
/// the RTP sequence number and timestamp are the varying quantities.
|
|
||||||
Normal,
|
|
||||||
#[deprecated(
|
|
||||||
since = "0.4.4",
|
|
||||||
note = "This voice encryption mode will no longer be accepted by Discord\
|
|
||||||
as of 2024-11-18. This variant will be removed in `v0.5`."
|
|
||||||
)]
|
|
||||||
/// An additional random 24B suffix is used as the source of nonce bytes for the packet.
|
|
||||||
/// This is regenerated randomly for each packet.
|
|
||||||
///
|
|
||||||
/// Full nonce width of 24B (192b), at an extra 24B per packet (~1.2 kB/s).
|
|
||||||
Suffix,
|
|
||||||
#[deprecated(
|
|
||||||
since = "0.4.4",
|
|
||||||
note = "This voice encryption mode will no longer be accepted by Discord\
|
|
||||||
as of 2024-11-18. This variant will be removed in `v0.5`."
|
|
||||||
)]
|
|
||||||
/// An additional random 4B suffix is used as the source of nonce bytes for the packet.
|
|
||||||
/// This nonce value increments by `1` with each packet.
|
|
||||||
///
|
|
||||||
/// Nonce width of 4B (32b), at an extra 4B per packet (~0.2 kB/s).
|
|
||||||
Lite,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
impl From<CryptoState> for CryptoMode {
|
impl From<CryptoState> for CryptoMode {
|
||||||
fn from(val: CryptoState) -> Self {
|
fn from(val: CryptoState) -> Self {
|
||||||
match val {
|
match val {
|
||||||
CryptoState::Normal => Self::Normal,
|
|
||||||
CryptoState::Suffix => Self::Suffix,
|
|
||||||
CryptoState::Lite(_) => Self::Lite,
|
|
||||||
CryptoState::Aes256Gcm(_) => Self::Aes256Gcm,
|
CryptoState::Aes256Gcm(_) => Self::Aes256Gcm,
|
||||||
CryptoState::XChaCha20Poly1305(_) => Self::XChaCha20Poly1305,
|
CryptoState::XChaCha20Poly1305(_) => Self::XChaCha20Poly1305,
|
||||||
}
|
}
|
||||||
@@ -98,20 +64,15 @@ pub struct UnrecognisedCryptoMode;
|
|||||||
impl FromStr for CryptoMode {
|
impl FromStr for CryptoMode {
|
||||||
type Err = UnrecognisedCryptoMode;
|
type Err = UnrecognisedCryptoMode;
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"aead_aes256_gcm_rtpsize" => Ok(Self::Aes256Gcm),
|
"aead_aes256_gcm_rtpsize" => Ok(Self::Aes256Gcm),
|
||||||
"aead_xchacha20_poly1305_rtpsize" => Ok(Self::XChaCha20Poly1305),
|
"aead_xchacha20_poly1305_rtpsize" => Ok(Self::XChaCha20Poly1305),
|
||||||
"xsalsa20_poly1305" => Ok(Self::Normal),
|
|
||||||
"xsalsa20_poly1305_suffix" => Ok(Self::Suffix),
|
|
||||||
"xsalsa20_poly1305_lite" => Ok(Self::Lite),
|
|
||||||
_ => Err(UnrecognisedCryptoMode),
|
_ => Err(UnrecognisedCryptoMode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
impl CryptoMode {
|
impl CryptoMode {
|
||||||
/// Returns the underlying crypto algorithm used by a given [`CryptoMode`].
|
/// Returns the underlying crypto algorithm used by a given [`CryptoMode`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -119,22 +80,6 @@ impl CryptoMode {
|
|||||||
match self {
|
match self {
|
||||||
CryptoMode::Aes256Gcm => EncryptionAlgorithm::Aes256Gcm,
|
CryptoMode::Aes256Gcm => EncryptionAlgorithm::Aes256Gcm,
|
||||||
CryptoMode::XChaCha20Poly1305 => EncryptionAlgorithm::XChaCha20Poly1305,
|
CryptoMode::XChaCha20Poly1305 => EncryptionAlgorithm::XChaCha20Poly1305,
|
||||||
CryptoMode::Normal | CryptoMode::Suffix | CryptoMode::Lite =>
|
|
||||||
EncryptionAlgorithm::XSalsa20Poly1305,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this [`CryptoMode`] dynamically sizes the ciphertext region
|
|
||||||
/// to begin in the middle of RTP extensions.
|
|
||||||
///
|
|
||||||
/// Compliant SRTP would leave all extensions in cleartext, hence 'more' SRTP
|
|
||||||
/// compliant.
|
|
||||||
#[must_use]
|
|
||||||
#[cfg(any(feature = "receive", test))]
|
|
||||||
pub(crate) const fn is_more_srtp_compliant(self) -> bool {
|
|
||||||
match self {
|
|
||||||
CryptoMode::Aes256Gcm | CryptoMode::XChaCha20Poly1305 => true,
|
|
||||||
CryptoMode::Normal | CryptoMode::Suffix | CryptoMode::Lite => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +93,6 @@ impl CryptoMode {
|
|||||||
.map(Cipher::Aes256Gcm),
|
.map(Cipher::Aes256Gcm),
|
||||||
EncryptionAlgorithm::XChaCha20Poly1305 =>
|
EncryptionAlgorithm::XChaCha20Poly1305 =>
|
||||||
XChaCha20Poly1305::new_from_slice(key).map(Cipher::XChaCha20Poly1305),
|
XChaCha20Poly1305::new_from_slice(key).map(Cipher::XChaCha20Poly1305),
|
||||||
EncryptionAlgorithm::XSalsa20Poly1305 =>
|
|
||||||
XSalsa20Poly1305::new_from_slice(key).map(|v| Cipher::XSalsa20Poly1305(v, self)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +102,8 @@ impl CryptoMode {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn priority(self) -> u64 {
|
pub(crate) fn priority(self) -> u64 {
|
||||||
match self {
|
match self {
|
||||||
CryptoMode::Aes256Gcm => 4,
|
CryptoMode::Aes256Gcm => 1,
|
||||||
CryptoMode::XChaCha20Poly1305 => 3,
|
CryptoMode::XChaCha20Poly1305 => 0,
|
||||||
CryptoMode::Normal => 2,
|
|
||||||
CryptoMode::Suffix => 1,
|
|
||||||
CryptoMode::Lite => 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,9 +152,6 @@ impl CryptoMode {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn to_request_str(self) -> &'static str {
|
pub const fn to_request_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Normal => "xsalsa20_poly1305",
|
|
||||||
Self::Suffix => "xsalsa20_poly1305_suffix",
|
|
||||||
Self::Lite => "xsalsa20_poly1305_lite",
|
|
||||||
Self::Aes256Gcm => "aead_aes256_gcm_rtpsize",
|
Self::Aes256Gcm => "aead_aes256_gcm_rtpsize",
|
||||||
Self::XChaCha20Poly1305 => "aead_xchacha20_poly1305_rtpsize",
|
Self::XChaCha20Poly1305 => "aead_xchacha20_poly1305_rtpsize",
|
||||||
}
|
}
|
||||||
@@ -225,7 +162,6 @@ impl CryptoMode {
|
|||||||
pub const fn algorithm_nonce_size(self) -> usize {
|
pub const fn algorithm_nonce_size(self) -> usize {
|
||||||
use typenum::Unsigned as _;
|
use typenum::Unsigned as _;
|
||||||
match self {
|
match self {
|
||||||
Self::Lite | Self::Normal | Self::Suffix => XSalsa20Poly1305::NONCE_SIZE,
|
|
||||||
Self::XChaCha20Poly1305 => <XChaCha20Poly1305 as AeadCore>::NonceSize::USIZE, // => 24
|
Self::XChaCha20Poly1305 => <XChaCha20Poly1305 as AeadCore>::NonceSize::USIZE, // => 24
|
||||||
Self::Aes256Gcm => <Aes256Gcm as AeadCore>::NonceSize::USIZE, // => 12
|
Self::Aes256Gcm => <Aes256Gcm as AeadCore>::NonceSize::USIZE, // => 12
|
||||||
}
|
}
|
||||||
@@ -236,35 +172,18 @@ impl CryptoMode {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn nonce_size(self) -> usize {
|
pub const fn nonce_size(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Aes256Gcm | Self::XChaCha20Poly1305 | Self::Lite => 4,
|
Self::Aes256Gcm | Self::XChaCha20Poly1305 => 4,
|
||||||
Self::Normal => RtpPacket::minimum_packet_size(),
|
|
||||||
Self::Suffix => XSalsa20Poly1305::NONCE_SIZE,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of bytes occupied by the XSalsa20Poly1305
|
|
||||||
/// encryption schemes which fall before the payload.
|
|
||||||
#[must_use]
|
|
||||||
#[deprecated(
|
|
||||||
since = "0.4.4",
|
|
||||||
note = "This method returns the fixed payload prefix for older encryption modes,\
|
|
||||||
which will no longer be accepted by Discord as of 2024-11-18. It is an\
|
|
||||||
implementation detail and will be removed in `v0.5`."
|
|
||||||
)]
|
|
||||||
pub fn payload_prefix_len() -> usize {
|
|
||||||
XSalsa20Poly1305::TAG_SIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of bytes occupied by the encryption scheme
|
/// Returns the number of bytes occupied by the encryption scheme
|
||||||
/// which fall before the payload.
|
/// which fall before the payload.
|
||||||
///
|
///
|
||||||
/// Method name duplicated until v0.5, to prevent breaking change.
|
/// Method name duplicated until v0.5, to prevent breaking change.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn payload_prefix_len2(self) -> usize {
|
pub(crate) const fn payload_prefix_len(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
CryptoMode::Aes256Gcm | CryptoMode::XChaCha20Poly1305 => 0,
|
CryptoMode::Aes256Gcm | CryptoMode::XChaCha20Poly1305 => 0,
|
||||||
CryptoMode::Normal | CryptoMode::Suffix | CryptoMode::Lite =>
|
|
||||||
XSalsa20Poly1305::TAG_SIZE,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,41 +197,32 @@ impl CryptoMode {
|
|||||||
/// which fall after the payload.
|
/// which fall after the payload.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn payload_suffix_len(self) -> usize {
|
pub const fn payload_suffix_len(self) -> usize {
|
||||||
match self {
|
self.nonce_size() + self.encryption_tag_len()
|
||||||
Self::Normal => 0,
|
|
||||||
Self::Suffix | Self::Lite => self.nonce_size(),
|
|
||||||
Self::Aes256Gcm | Self::XChaCha20Poly1305 =>
|
|
||||||
self.nonce_size() + self.encryption_tag_len(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of bytes occupied by an encryption scheme's tag which
|
/// Returns the number of bytes occupied by an encryption scheme's tag which
|
||||||
/// fall *after* the payload.
|
/// fall *after* the payload.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn tag_suffix_len(self) -> usize {
|
pub const fn tag_suffix_len(self) -> usize {
|
||||||
match self {
|
self.encryption_tag_len()
|
||||||
Self::Normal | Self::Suffix | Self::Lite => 0,
|
|
||||||
Self::Aes256Gcm | Self::XChaCha20Poly1305 => self.encryption_tag_len(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the number of additional bytes required compared
|
/// Calculates the number of additional bytes required compared
|
||||||
/// to an unencrypted payload.
|
/// to an unencrypted payload.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn payload_overhead(self) -> usize {
|
pub const fn payload_overhead(self) -> usize {
|
||||||
self.payload_prefix_len2() + self.payload_suffix_len()
|
self.payload_prefix_len() + self.payload_suffix_len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the byte slice in a packet used as the nonce, and the remaining mutable
|
/// Extracts the byte slice in a packet used as the nonce, and the remaining mutable
|
||||||
/// portion of the packet.
|
/// portion of the packet.
|
||||||
fn nonce_slice<'a>(
|
fn nonce_slice<'a>(
|
||||||
self,
|
self,
|
||||||
header: &'a [u8],
|
_header: &'a [u8],
|
||||||
body: &'a mut [u8],
|
body: &'a mut [u8],
|
||||||
) -> Result<(&'a [u8], &'a mut [u8]), CryptoError> {
|
) -> Result<(&'a [u8], &'a mut [u8]), CryptoError> {
|
||||||
match self {
|
match self {
|
||||||
Self::Normal => Ok((header, body)),
|
Self::Aes256Gcm | Self::XChaCha20Poly1305 => {
|
||||||
Self::Suffix | Self::Lite | Self::Aes256Gcm | Self::XChaCha20Poly1305 => {
|
|
||||||
let len = body.len();
|
let len = body.len();
|
||||||
if len < self.payload_suffix_len() {
|
if len < self.payload_suffix_len() {
|
||||||
Err(CryptoError)
|
Err(CryptoError)
|
||||||
@@ -323,47 +233,6 @@ impl CryptoMode {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypts a Discord RT(C)P packet using the given XSalsa20Poly1305 cipher.
|
|
||||||
///
|
|
||||||
/// Use of this requires that the input packet has had a nonce generated in the correct location,
|
|
||||||
/// and `payload_len` specifies the number of bytes after the header including this nonce.
|
|
||||||
#[deprecated(
|
|
||||||
since = "0.4.4",
|
|
||||||
note = "This method performs encryption for older encryption modes,\
|
|
||||||
which will no longer be accepted by Discord as of 2024-11-18. It is an\
|
|
||||||
implementation detail and will be removed in `v0.5`."
|
|
||||||
)]
|
|
||||||
#[inline]
|
|
||||||
pub fn encrypt_in_place(
|
|
||||||
self,
|
|
||||||
packet: &mut impl MutablePacket,
|
|
||||||
cipher: &XSalsa20Poly1305,
|
|
||||||
payload_len: usize,
|
|
||||||
) -> Result<(), CryptoError> {
|
|
||||||
let header_len = packet.packet().len() - packet.payload().len();
|
|
||||||
let (header, body) = packet.packet_mut().split_at_mut(header_len);
|
|
||||||
let (slice_to_use, body_remaining) = self.nonce_slice(header, &mut body[..payload_len])?;
|
|
||||||
|
|
||||||
let nonce_size = self.nonce_size();
|
|
||||||
let tag_size = self.encryption_tag_len();
|
|
||||||
|
|
||||||
let mut nonce = crypto_secretbox::Nonce::default();
|
|
||||||
let nonce_slice = if slice_to_use.len() == nonce_size {
|
|
||||||
crypto_secretbox::Nonce::from_slice(&slice_to_use[..nonce_size])
|
|
||||||
} else {
|
|
||||||
nonce[..slice_to_use.len()].copy_from_slice(slice_to_use);
|
|
||||||
&nonce
|
|
||||||
};
|
|
||||||
|
|
||||||
// body_remaining is now correctly truncated by this point.
|
|
||||||
// the true_payload to encrypt follows after the first TAG_LEN bytes.
|
|
||||||
let tag =
|
|
||||||
cipher.encrypt_in_place_detached(nonce_slice, b"", &mut body_remaining[tag_size..])?;
|
|
||||||
body_remaining[..tag_size].copy_from_slice(&tag[..]);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State used in nonce generation for the encryption variants in [`CryptoMode`].
|
/// State used in nonce generation for the encryption variants in [`CryptoMode`].
|
||||||
@@ -380,29 +249,11 @@ pub enum CryptoState {
|
|||||||
///
|
///
|
||||||
/// The last used nonce is stored.
|
/// The last used nonce is stored.
|
||||||
XChaCha20Poly1305(Wrapping<u32>),
|
XChaCha20Poly1305(Wrapping<u32>),
|
||||||
/// The RTP header is used as the source of nonce bytes for the packet.
|
|
||||||
///
|
|
||||||
/// No state is required.
|
|
||||||
Normal,
|
|
||||||
/// An additional random 24B suffix is used as the source of nonce bytes for the packet.
|
|
||||||
/// This is regenerated randomly for each packet.
|
|
||||||
///
|
|
||||||
/// No state is required.
|
|
||||||
Suffix,
|
|
||||||
/// An additional random 4B suffix is used as the source of nonce bytes for the packet.
|
|
||||||
/// This nonce value increments by `1` with each packet.
|
|
||||||
///
|
|
||||||
/// The last used nonce is stored.
|
|
||||||
Lite(Wrapping<u32>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
impl From<CryptoMode> for CryptoState {
|
impl From<CryptoMode> for CryptoState {
|
||||||
fn from(val: CryptoMode) -> Self {
|
fn from(val: CryptoMode) -> Self {
|
||||||
match val {
|
match val {
|
||||||
CryptoMode::Normal => CryptoState::Normal,
|
|
||||||
CryptoMode::Suffix => CryptoState::Suffix,
|
|
||||||
CryptoMode::Lite => CryptoState::Lite(Wrapping(rand::random::<u32>())),
|
|
||||||
CryptoMode::Aes256Gcm => CryptoState::Aes256Gcm(Wrapping(rand::random::<u32>())),
|
CryptoMode::Aes256Gcm => CryptoState::Aes256Gcm(Wrapping(rand::random::<u32>())),
|
||||||
CryptoMode::XChaCha20Poly1305 =>
|
CryptoMode::XChaCha20Poly1305 =>
|
||||||
CryptoState::XChaCha20Poly1305(Wrapping(rand::random::<u32>())),
|
CryptoState::XChaCha20Poly1305(Wrapping(rand::random::<u32>())),
|
||||||
@@ -422,12 +273,7 @@ impl CryptoState {
|
|||||||
let startpoint = endpoint - mode.nonce_size();
|
let startpoint = endpoint - mode.nonce_size();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Suffix => {
|
Self::Aes256Gcm(ref mut i) | Self::XChaCha20Poly1305(ref mut i) => {
|
||||||
rand::rng().fill(&mut packet.payload_mut()[startpoint..endpoint]);
|
|
||||||
},
|
|
||||||
Self::Lite(ref mut i)
|
|
||||||
| Self::Aes256Gcm(ref mut i)
|
|
||||||
| Self::XChaCha20Poly1305(ref mut i) => {
|
|
||||||
(&mut packet.payload_mut()[startpoint..endpoint])
|
(&mut packet.payload_mut()[startpoint..endpoint])
|
||||||
.write_u32::<NetworkEndian>(i.0)
|
.write_u32::<NetworkEndian>(i.0)
|
||||||
.expect(
|
.expect(
|
||||||
@@ -435,7 +281,6 @@ impl CryptoState {
|
|||||||
);
|
);
|
||||||
*i += Wrapping(1);
|
*i += Wrapping(1);
|
||||||
},
|
},
|
||||||
Self::Normal => {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint
|
endpoint
|
||||||
@@ -452,7 +297,6 @@ impl CryptoState {
|
|||||||
pub(crate) enum EncryptionAlgorithm {
|
pub(crate) enum EncryptionAlgorithm {
|
||||||
Aes256Gcm,
|
Aes256Gcm,
|
||||||
XChaCha20Poly1305,
|
XChaCha20Poly1305,
|
||||||
XSalsa20Poly1305,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncryptionAlgorithm {
|
impl EncryptionAlgorithm {
|
||||||
@@ -461,7 +305,6 @@ impl EncryptionAlgorithm {
|
|||||||
match self {
|
match self {
|
||||||
Self::Aes256Gcm => <Aes256Gcm as AeadCore>::TagSize::USIZE, // 16
|
Self::Aes256Gcm => <Aes256Gcm as AeadCore>::TagSize::USIZE, // 16
|
||||||
Self::XChaCha20Poly1305 => <XChaCha20Poly1305 as AeadCore>::TagSize::USIZE, // 16
|
Self::XChaCha20Poly1305 => <XChaCha20Poly1305 as AeadCore>::TagSize::USIZE, // 16
|
||||||
Self::XSalsa20Poly1305 => XSalsa20Poly1305::TAG_SIZE, // 16
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +312,6 @@ impl EncryptionAlgorithm {
|
|||||||
impl From<&Cipher> for EncryptionAlgorithm {
|
impl From<&Cipher> for EncryptionAlgorithm {
|
||||||
fn from(value: &Cipher) -> Self {
|
fn from(value: &Cipher) -> Self {
|
||||||
match value {
|
match value {
|
||||||
Cipher::XSalsa20Poly1305(..) => EncryptionAlgorithm::XSalsa20Poly1305,
|
|
||||||
Cipher::XChaCha20Poly1305(_) => EncryptionAlgorithm::XChaCha20Poly1305,
|
Cipher::XChaCha20Poly1305(_) => EncryptionAlgorithm::XChaCha20Poly1305,
|
||||||
Cipher::Aes256Gcm(_) => EncryptionAlgorithm::Aes256Gcm,
|
Cipher::Aes256Gcm(_) => EncryptionAlgorithm::Aes256Gcm,
|
||||||
}
|
}
|
||||||
@@ -478,7 +320,6 @@ impl From<&Cipher> for EncryptionAlgorithm {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Cipher {
|
pub enum Cipher {
|
||||||
XSalsa20Poly1305(XSalsa20Poly1305, CryptoMode),
|
|
||||||
XChaCha20Poly1305(XChaCha20Poly1305),
|
XChaCha20Poly1305(XChaCha20Poly1305),
|
||||||
Aes256Gcm(Box<Aes256Gcm>),
|
Aes256Gcm(Box<Aes256Gcm>),
|
||||||
}
|
}
|
||||||
@@ -487,7 +328,6 @@ impl Cipher {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn mode(&self) -> CryptoMode {
|
pub(crate) fn mode(&self) -> CryptoMode {
|
||||||
match self {
|
match self {
|
||||||
Cipher::XSalsa20Poly1305(_, mode) => *mode,
|
|
||||||
Cipher::XChaCha20Poly1305(_) => CryptoMode::XChaCha20Poly1305,
|
Cipher::XChaCha20Poly1305(_) => CryptoMode::XChaCha20Poly1305,
|
||||||
Cipher::Aes256Gcm(_) => CryptoMode::Aes256Gcm,
|
Cipher::Aes256Gcm(_) => CryptoMode::Aes256Gcm,
|
||||||
}
|
}
|
||||||
@@ -518,23 +358,13 @@ impl Cipher {
|
|||||||
|
|
||||||
// body_remaining is now correctly truncated to exclude the nonce by this point.
|
// body_remaining is now correctly truncated to exclude the nonce by this point.
|
||||||
// the true_payload to encrypt is within the buf[prefix:-suffix].
|
// the true_payload to encrypt is within the buf[prefix:-suffix].
|
||||||
let (pre_payload, body_remaining) = body_remaining.split_at_mut(mode.payload_prefix_len2());
|
let (_, body_remaining) = body_remaining.split_at_mut(mode.payload_prefix_len());
|
||||||
let (body, post_payload) =
|
let (body, post_payload) =
|
||||||
body_remaining.split_at_mut(body_remaining.len() - mode.tag_suffix_len());
|
body_remaining.split_at_mut(body_remaining.len() - mode.tag_suffix_len());
|
||||||
|
|
||||||
// All these Nonce types are distinct at the type level
|
// All these Nonce types are distinct at the type level
|
||||||
// (96b for AES, 192b for XSalsa/XChaCha).
|
// (96b for AES, 192b for XChaCha).
|
||||||
match self {
|
match self {
|
||||||
// Older modes place the tag before the payload and do not authenticate
|
|
||||||
// cleartext.
|
|
||||||
Self::XSalsa20Poly1305(secret_box, _) => {
|
|
||||||
let mut nonce = crypto_secretbox::Nonce::default();
|
|
||||||
nonce[..mode.nonce_size()].copy_from_slice(slice_to_use);
|
|
||||||
|
|
||||||
let tag = secret_box.encrypt_in_place_detached(&nonce, b"", body)?;
|
|
||||||
pre_payload[..tag_size].copy_from_slice(&tag[..]);
|
|
||||||
},
|
|
||||||
|
|
||||||
// The below variants follow part of the SRTP spec (RFC3711, sec 3.1)
|
// The below variants follow part of the SRTP spec (RFC3711, sec 3.1)
|
||||||
// by requiring that we include the cleartext header portion as
|
// by requiring that we include the cleartext header portion as
|
||||||
// authenticated data.
|
// authenticated data.
|
||||||
@@ -562,14 +392,13 @@ impl Cipher {
|
|||||||
&self,
|
&self,
|
||||||
packet: &mut MutableRtpPacket<'_>,
|
packet: &mut MutableRtpPacket<'_>,
|
||||||
) -> Result<(usize, usize), InternalError> {
|
) -> Result<(usize, usize), InternalError> {
|
||||||
let mode = self.mode();
|
|
||||||
// An exciting difference from the SRTP spec: Discord begins encryption
|
// An exciting difference from the SRTP spec: Discord begins encryption
|
||||||
// after the RTP extension *header*, encrypting the extensions themselves,
|
// after the RTP extension *header*, encrypting the extensions themselves,
|
||||||
// whereas the spec leaves all extensions in the clear.
|
// whereas the spec leaves all extensions in the clear.
|
||||||
// This header is described as the 'extension preamble'.
|
// This header is described as the 'extension preamble'.
|
||||||
let has_extension = packet.get_extension() != 0;
|
let has_extension = packet.get_extension() != 0;
|
||||||
|
|
||||||
let plain_bytes = if mode.is_more_srtp_compliant() && has_extension {
|
let plain_bytes = if has_extension {
|
||||||
// CSRCs and extension bytes will be in the plaintext segment.
|
// CSRCs and extension bytes will be in the plaintext segment.
|
||||||
// We will need these demarcated to select the right bytes to
|
// We will need these demarcated to select the right bytes to
|
||||||
// decrypt, and to use as auth data.
|
// decrypt, and to use as auth data.
|
||||||
@@ -578,19 +407,19 @@ impl Cipher {
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut start_estimate, end) = self.decrypt_pkt_in_place(packet, plain_bytes)?;
|
let (_, end) = self.decrypt_pkt_in_place(packet, plain_bytes)?;
|
||||||
|
|
||||||
// Update the start estimate to account for bytes occupied by extension headers.
|
// Update the start estimate to account for bytes occupied by extension headers.
|
||||||
if has_extension {
|
let payload_offset = if has_extension {
|
||||||
let packet = packet.packet();
|
let payload = packet.payload();
|
||||||
if let Some((_, exts_and_opus)) = split_at_checked(packet, start_estimate) {
|
let extension =
|
||||||
let extension = RtpExtensionPacket::new(exts_and_opus)
|
RtpExtensionPacket::new(payload).ok_or(InternalError::IllegalVoicePacket)?;
|
||||||
.ok_or(InternalError::IllegalVoicePacket)?;
|
extension.packet().len() - extension.payload().len()
|
||||||
start_estimate += extension.packet().len() - extension.payload().len();
|
} else {
|
||||||
}
|
0
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok((start_estimate, end))
|
Ok((payload_offset, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "receive")]
|
#[cfg(feature = "receive")]
|
||||||
@@ -623,7 +452,7 @@ impl Cipher {
|
|||||||
let (slice_to_use, body_remaining) = mode.nonce_slice(plaintext, ciphertext)?;
|
let (slice_to_use, body_remaining) = mode.nonce_slice(plaintext, ciphertext)?;
|
||||||
|
|
||||||
let (pre_payload, body_remaining) =
|
let (pre_payload, body_remaining) =
|
||||||
split_at_mut_checked(body_remaining, mode.payload_prefix_len2()).ok_or(CryptoError)?;
|
split_at_mut_checked(body_remaining, mode.payload_prefix_len()).ok_or(CryptoError)?;
|
||||||
|
|
||||||
let suffix_split_point = body_remaining
|
let suffix_split_point = body_remaining
|
||||||
.len()
|
.len()
|
||||||
@@ -636,16 +465,6 @@ impl Cipher {
|
|||||||
let tag_size = self.encryption_tag_len();
|
let tag_size = self.encryption_tag_len();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
// Older modes place the tag before the payload and do not authenticate
|
|
||||||
// cleartext.
|
|
||||||
Self::XSalsa20Poly1305(secret_box, _) => {
|
|
||||||
let mut nonce = crypto_secretbox::Nonce::default();
|
|
||||||
nonce[..mode.nonce_size().min(slice_to_use.len())].copy_from_slice(slice_to_use);
|
|
||||||
|
|
||||||
let tag = crypto_secretbox::Tag::from_slice(&pre_payload[..tag_size]);
|
|
||||||
secret_box.decrypt_in_place_detached(&nonce, b"", body, tag)?;
|
|
||||||
},
|
|
||||||
|
|
||||||
// The below variants follow part of the SRTP spec (RFC3711, sec 3.1)
|
// The below variants follow part of the SRTP spec (RFC3711, sec 3.1)
|
||||||
// by requiring that we include the cleartext header portion as
|
// by requiring that we include the cleartext header portion as
|
||||||
// authenticated data.
|
// authenticated data.
|
||||||
@@ -665,23 +484,15 @@ impl Cipher {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((plaintext_end + pre_payload.len(), post_payload.len()))
|
Ok((
|
||||||
|
plaintext_end + pre_payload.len(),
|
||||||
|
post_payload.len() + slice_to_use.len(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary functions -- MSRV is ostensibly 1.74, slice::split_at(_mut)_checked is 1.80+.
|
// Temporary functions -- MSRV is ostensibly 1.74, slice::split_at(_mut)_checked is 1.80+.
|
||||||
// TODO: Remove in v0.5+ with MSRV bump to 1.81+.
|
// TODO: Remove in v0.5+ with MSRV bump to 1.81+.
|
||||||
#[cfg(any(feature = "receive", test))]
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
const fn split_at_checked(els: &[u8], mid: usize) -> Option<(&[u8], &[u8])> {
|
|
||||||
if mid <= els.len() {
|
|
||||||
Some(els.split_at(mid))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "receive", test))]
|
#[cfg(any(feature = "receive", test))]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -699,16 +510,10 @@ mod test {
|
|||||||
use discortp::rtp::MutableRtpPacket;
|
use discortp::rtp::MutableRtpPacket;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(deprecated)]
|
|
||||||
fn small_packet_decrypts_error() {
|
fn small_packet_decrypts_error() {
|
||||||
let mut buf = [0u8; MutableRtpPacket::minimum_packet_size()];
|
let mut buf = [0u8; MutableRtpPacket::minimum_packet_size()];
|
||||||
let modes = [
|
let modes = [CryptoMode::Aes256Gcm, CryptoMode::XChaCha20Poly1305];
|
||||||
CryptoMode::Normal,
|
|
||||||
CryptoMode::Suffix,
|
|
||||||
CryptoMode::Lite,
|
|
||||||
CryptoMode::Aes256Gcm,
|
|
||||||
CryptoMode::XChaCha20Poly1305,
|
|
||||||
];
|
|
||||||
let mut pkt = MutableRtpPacket::new(&mut buf[..]).unwrap();
|
let mut pkt = MutableRtpPacket::new(&mut buf[..]).unwrap();
|
||||||
|
|
||||||
for mode in modes {
|
for mode in modes {
|
||||||
@@ -719,41 +524,6 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
fn symmetric_encrypt_decrypt_xsalsa20() {
|
|
||||||
const TRUE_PAYLOAD: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
||||||
let mut buf = [0u8; MutableRtpPacket::minimum_packet_size()
|
|
||||||
+ TRUE_PAYLOAD.len()
|
|
||||||
+ XSalsa20Poly1305::TAG_SIZE
|
|
||||||
+ XSalsa20Poly1305::NONCE_SIZE];
|
|
||||||
let modes = [CryptoMode::Normal, CryptoMode::Lite, CryptoMode::Suffix];
|
|
||||||
|
|
||||||
for mode in modes {
|
|
||||||
buf.fill(0);
|
|
||||||
let cipher = mode
|
|
||||||
.cipher_from_key(&[7u8; XSalsa20Poly1305::KEY_SIZE])
|
|
||||||
.unwrap();
|
|
||||||
let mut pkt = MutableRtpPacket::new(&mut buf[..]).unwrap();
|
|
||||||
let mut crypto_state = CryptoState::from(mode);
|
|
||||||
let payload = pkt.payload_mut();
|
|
||||||
payload[XSalsa20Poly1305::TAG_SIZE..XSalsa20Poly1305::TAG_SIZE + TRUE_PAYLOAD.len()]
|
|
||||||
.copy_from_slice(&TRUE_PAYLOAD[..]);
|
|
||||||
|
|
||||||
let final_payload_size = crypto_state
|
|
||||||
.write_packet_nonce(&mut pkt, XSalsa20Poly1305::TAG_SIZE + TRUE_PAYLOAD.len());
|
|
||||||
|
|
||||||
let enc_succ = cipher.encrypt_pkt_in_place(&mut pkt, final_payload_size);
|
|
||||||
|
|
||||||
assert!(enc_succ.is_ok());
|
|
||||||
|
|
||||||
let final_pkt_len = MutableRtpPacket::minimum_packet_size() + final_payload_size;
|
|
||||||
let mut pkt = MutableRtpPacket::new(&mut buf[..final_pkt_len]).unwrap();
|
|
||||||
|
|
||||||
assert!(cipher.decrypt_rtp_in_place(&mut pkt).is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn symmetric_encrypt_decrypt_tag_after_data() {
|
fn symmetric_encrypt_decrypt_tag_after_data() {
|
||||||
const TRUE_PAYLOAD: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
|
const TRUE_PAYLOAD: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||||
@@ -771,7 +541,7 @@ mod test {
|
|||||||
let mut pkt = MutableRtpPacket::new(&mut buf[..]).unwrap();
|
let mut pkt = MutableRtpPacket::new(&mut buf[..]).unwrap();
|
||||||
let mut crypto_state = CryptoState::from(mode);
|
let mut crypto_state = CryptoState::from(mode);
|
||||||
let payload = pkt.payload_mut();
|
let payload = pkt.payload_mut();
|
||||||
payload[mode.payload_prefix_len2()..TRUE_PAYLOAD.len()].copy_from_slice(&TRUE_PAYLOAD);
|
payload[mode.payload_prefix_len()..TRUE_PAYLOAD.len()].copy_from_slice(&TRUE_PAYLOAD);
|
||||||
|
|
||||||
let final_payload_size = crypto_state.write_packet_nonce(&mut pkt, TRUE_PAYLOAD.len());
|
let final_payload_size = crypto_state.write_packet_nonce(&mut pkt, TRUE_PAYLOAD.len());
|
||||||
|
|
||||||
@@ -787,33 +557,31 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(deprecated)]
|
|
||||||
fn negotiate_cryptomode() {
|
fn negotiate_cryptomode() {
|
||||||
// If we have no preference (or our preference is missing), choose the highest available in the set.
|
// If we have no preference (or our preference is missing), choose the highest available in the set.
|
||||||
let test_set = [
|
let test_set =
|
||||||
CryptoMode::Suffix,
|
[CryptoMode::XChaCha20Poly1305, CryptoMode::Aes256Gcm].map(CryptoMode::to_request_str);
|
||||||
CryptoMode::XChaCha20Poly1305,
|
|
||||||
CryptoMode::Lite,
|
|
||||||
]
|
|
||||||
.map(CryptoMode::to_request_str);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
CryptoMode::negotiate(test_set, None).unwrap(),
|
CryptoMode::negotiate(test_set, None).unwrap(),
|
||||||
CryptoMode::XChaCha20Poly1305
|
CryptoMode::Aes256Gcm
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let test_set_missing = [CryptoMode::XChaCha20Poly1305].map(CryptoMode::to_request_str);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
CryptoMode::negotiate(test_set, Some(CryptoMode::Aes256Gcm)).unwrap(),
|
CryptoMode::negotiate(test_set_missing, Some(CryptoMode::Aes256Gcm)).unwrap(),
|
||||||
CryptoMode::XChaCha20Poly1305
|
CryptoMode::XChaCha20Poly1305
|
||||||
);
|
);
|
||||||
|
|
||||||
// Preference wins in spite of the defined `priority` value.
|
// Preference wins in spite of the defined `priority` value.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
CryptoMode::negotiate(test_set, Some(CryptoMode::Suffix)).unwrap(),
|
CryptoMode::negotiate(test_set, Some(CryptoMode::XChaCha20Poly1305)).unwrap(),
|
||||||
CryptoMode::Suffix
|
CryptoMode::XChaCha20Poly1305
|
||||||
);
|
);
|
||||||
|
|
||||||
// If there is no mutual intelligibility, return an error.
|
// If there is no mutual intelligibility, return an error.
|
||||||
let bad_modes = ["not_real", "des", "rc5"];
|
let bad_modes = ["not_real", "des", "rc5"];
|
||||||
assert!(CryptoMode::negotiate(&bad_modes, None).is_err());
|
assert!(CryptoMode::negotiate(bad_modes, None).is_err());
|
||||||
assert!(CryptoMode::negotiate(&bad_modes, Some(CryptoMode::Aes256Gcm)).is_err());
|
assert!(CryptoMode::negotiate(bad_modes, Some(CryptoMode::Aes256Gcm)).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,16 @@ pub enum DecodeMode {
|
|||||||
/// Decrypts and decodes each received packet, correctly accounting for losses.
|
/// Decrypts and decodes each received packet, correctly accounting for losses.
|
||||||
///
|
///
|
||||||
/// Larger per-packet CPU use.
|
/// Larger per-packet CPU use.
|
||||||
Decode,
|
Decode(DecodeConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecodeMode {
|
impl DecodeMode {
|
||||||
|
/// Returns whether this mode will decrypt and decode received packets.
|
||||||
|
#[must_use]
|
||||||
|
pub fn should_decode(self) -> bool {
|
||||||
|
matches!(self, DecodeMode::Decode(..))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether this mode will decrypt received packets.
|
/// Returns whether this mode will decrypt received packets.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn should_decrypt(self) -> bool {
|
pub fn should_decrypt(self) -> bool {
|
||||||
@@ -27,6 +33,32 @@ impl DecodeMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for [`DecodeMode::Decode`]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
|
||||||
|
pub struct DecodeConfig {
|
||||||
|
/// Configures the channel layout for output audio.
|
||||||
|
///
|
||||||
|
/// Defaults to [`Channels::Stereo`].
|
||||||
|
pub channels: Channels,
|
||||||
|
|
||||||
|
/// Configures the sample rate for output audio.
|
||||||
|
///
|
||||||
|
/// Defaults to [`SampleRate::Hz48000`].
|
||||||
|
pub sample_rate: SampleRate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeConfig {
|
||||||
|
/// Creates a new [`DecodeConfig`] with the specified channels and sample rate.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(channels: Channels, sample_rate: SampleRate) -> Self {
|
||||||
|
Self {
|
||||||
|
channels,
|
||||||
|
sample_rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The channel layout of output audio when using [`DecodeMode::Decode`].
|
/// The channel layout of output audio when using [`DecodeMode::Decode`].
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ impl Live {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::used_underscore_items)]
|
||||||
self._march_deadline();
|
self._march_deadline();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::message::*;
|
use super::message::*;
|
||||||
use crate::ws::Error as WsError;
|
use crate::ws::Error as WsError;
|
||||||
|
use aes_gcm::Error as CryptoError;
|
||||||
use audiopus::Error as OpusError;
|
use audiopus::Error as OpusError;
|
||||||
use crypto_secretbox::aead::Error as CryptoError;
|
|
||||||
use flume::SendError;
|
use flume::SendError;
|
||||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use super::Interconnect;
|
use super::Interconnect;
|
||||||
use crate::driver::Config;
|
use crate::driver::DecodeConfig;
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::{DashMap, DashSet};
|
||||||
use serenity_voice_model::id::UserId;
|
use serenity_voice_model::id::UserId;
|
||||||
|
|
||||||
pub enum UdpRxMessage {
|
pub enum UdpRxMessage {
|
||||||
SetConfig(Config),
|
SetConfig(DecodeConfig),
|
||||||
ReplaceInterconnect(Interconnect),
|
ReplaceInterconnect(Interconnect),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ pub fn mix_symph_indiv(
|
|||||||
resample_in_progress = true;
|
resample_in_progress = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let samples_marched = mix_resampled(rs_out_buf, symph_mix, samples_written, volume);
|
let samples_marched = mix_resampled(rs_out_buf, symph_mix, samples_written, volume);
|
||||||
|
|
||||||
|
|||||||
@@ -329,10 +329,11 @@ impl Mixer {
|
|||||||
|
|
||||||
#[cfg(feature = "receive")]
|
#[cfg(feature = "receive")]
|
||||||
if let Some(conn) = &self.conn_active {
|
if let Some(conn) = &self.conn_active {
|
||||||
conn_failure |= conn
|
if let crate::driver::DecodeMode::Decode(decode_config) = new_config.decode_mode
|
||||||
.udp_rx
|
{
|
||||||
.send(UdpRxMessage::SetConfig(new_config))
|
let msg = UdpRxMessage::SetConfig(decode_config);
|
||||||
.is_err();
|
conn_failure |= conn.udp_rx.send(msg).is_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -554,7 +555,7 @@ impl Mixer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let payload = rtp.payload_mut();
|
let payload = rtp.payload_mut();
|
||||||
let pre_len = self.crypto_mode().payload_prefix_len2();
|
let pre_len = self.crypto_mode().payload_prefix_len();
|
||||||
|
|
||||||
payload[pre_len..pre_len + SILENT_FRAME.len()].copy_from_slice(&SILENT_FRAME[..]);
|
payload[pre_len..pre_len + SILENT_FRAME.len()].copy_from_slice(&SILENT_FRAME[..]);
|
||||||
|
|
||||||
@@ -591,7 +592,7 @@ impl Mixer {
|
|||||||
);
|
);
|
||||||
let payload = rtp.payload();
|
let payload = rtp.payload();
|
||||||
let opus_frame =
|
let opus_frame =
|
||||||
(payload[self.crypto_mode().payload_prefix_len2()..][..len]).to_vec();
|
(payload[self.crypto_mode().payload_prefix_len()..][..len]).to_vec();
|
||||||
|
|
||||||
OutputMessage::Passthrough(opus_frame)
|
OutputMessage::Passthrough(opus_frame)
|
||||||
},
|
},
|
||||||
@@ -637,7 +638,7 @@ impl Mixer {
|
|||||||
|
|
||||||
let payload = rtp.payload_mut();
|
let payload = rtp.payload_mut();
|
||||||
let crypto_mode = conn.crypto_state.kind();
|
let crypto_mode = conn.crypto_state.kind();
|
||||||
let first_payload_byte = crypto_mode.payload_prefix_len2();
|
let first_payload_byte = crypto_mode.payload_prefix_len();
|
||||||
|
|
||||||
// If passthrough, Opus payload in place already.
|
// If passthrough, Opus payload in place already.
|
||||||
// Else encode into buffer with space for AEAD encryption headers.
|
// Else encode into buffer with space for AEAD encryption headers.
|
||||||
@@ -679,11 +680,11 @@ impl Mixer {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self._send_packet(packet)
|
self.send_packet_(packet)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
let send_status = self._send_packet(packet);
|
let send_status = self.send_packet_(packet);
|
||||||
|
|
||||||
send_status.or_else(Error::disarm_would_block)?;
|
send_status.or_else(Error::disarm_would_block)?;
|
||||||
|
|
||||||
@@ -691,7 +692,7 @@ impl Mixer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn _send_packet(&self, packet: &[u8]) -> Result<()> {
|
fn send_packet_(&self, packet: &[u8]) -> Result<()> {
|
||||||
let conn = self
|
let conn = self
|
||||||
.conn_active
|
.conn_active
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -755,7 +756,7 @@ impl Mixer {
|
|||||||
(Blame: VOICE_PACKET_MAX?)",
|
(Blame: VOICE_PACKET_MAX?)",
|
||||||
);
|
);
|
||||||
let payload = rtp.payload_mut();
|
let payload = rtp.payload_mut();
|
||||||
let opus_frame = &mut payload[self.crypto_mode().payload_prefix_len2()..];
|
let opus_frame = &mut payload[self.crypto_mode().payload_prefix_len()..];
|
||||||
|
|
||||||
// Opus frame passthrough.
|
// Opus frame passthrough.
|
||||||
// This requires that we have only one PLAYING track, who has volume 1.0, and an
|
// This requires that we have only one PLAYING track, who has volume 1.0, and an
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ mod ssrc_state;
|
|||||||
use self::{decode_sizes::*, playout_buffer::*, ssrc_state::*};
|
use self::{decode_sizes::*, playout_buffer::*, ssrc_state::*};
|
||||||
|
|
||||||
use super::message::*;
|
use super::message::*;
|
||||||
use crate::driver::CryptoMode;
|
use crate::driver::{CryptoMode, DecodeMode};
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::*,
|
constants::*,
|
||||||
driver::crypto::Cipher,
|
driver::crypto::Cipher,
|
||||||
@@ -65,13 +65,12 @@ impl UdpRx {
|
|||||||
Ok(UdpRxMessage::ReplaceInterconnect(i)) => {
|
Ok(UdpRxMessage::ReplaceInterconnect(i)) => {
|
||||||
*interconnect = i;
|
*interconnect = i;
|
||||||
},
|
},
|
||||||
Ok(UdpRxMessage::SetConfig(c)) => {
|
Ok(UdpRxMessage::SetConfig(new_config)) => {
|
||||||
let old_coder = (self.config.decode_channels, self.config.decode_sample_rate);
|
if let DecodeMode::Decode(old_config) = &mut self.config.decode_mode {
|
||||||
let new_coder = (c.decode_channels, c.decode_sample_rate);
|
if *old_config != new_config {
|
||||||
self.config = c;
|
*old_config = new_config;
|
||||||
|
self.decoder_map.values_mut().for_each(|v| v.reconfigure_decoder(new_config));
|
||||||
if old_coder != new_coder {
|
}
|
||||||
self.decoder_map.values_mut().for_each(|v| v.reconfigure_decoder(&self.config));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(flume::RecvError::Disconnected) => break,
|
Err(flume::RecvError::Disconnected) => break,
|
||||||
@@ -177,7 +176,7 @@ impl UdpRx {
|
|||||||
let rtp = rtp.to_immutable();
|
let rtp = rtp.to_immutable();
|
||||||
let (rtp_body_start, rtp_body_tail, decrypted) = packet_data.unwrap_or_else(|| {
|
let (rtp_body_start, rtp_body_tail, decrypted) = packet_data.unwrap_or_else(|| {
|
||||||
(
|
(
|
||||||
crypto_mode.payload_prefix_len2(),
|
crypto_mode.payload_prefix_len(),
|
||||||
crypto_mode.payload_suffix_len(),
|
crypto_mode.payload_suffix_len(),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -222,7 +221,7 @@ impl UdpRx {
|
|||||||
|
|
||||||
let (start, tail) = packet_data.unwrap_or_else(|| {
|
let (start, tail) = packet_data.unwrap_or_else(|| {
|
||||||
(
|
(
|
||||||
crypto_mode.payload_prefix_len2(),
|
crypto_mode.payload_prefix_len(),
|
||||||
crypto_mode.payload_suffix_len(),
|
crypto_mode.payload_suffix_len(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::{
|
|||||||
driver::{
|
driver::{
|
||||||
tasks::error::{Error, Result},
|
tasks::error::{Error, Result},
|
||||||
Channels,
|
Channels,
|
||||||
|
DecodeConfig,
|
||||||
DecodeMode,
|
DecodeMode,
|
||||||
},
|
},
|
||||||
events::context_data::{RtpData, VoiceData},
|
events::context_data::{RtpData, VoiceData},
|
||||||
@@ -29,29 +30,28 @@ pub struct SsrcState {
|
|||||||
impl SsrcState {
|
impl SsrcState {
|
||||||
pub fn new(pkt: &RtpPacket<'_>, crypto_mode: CryptoMode, config: &Config) -> Self {
|
pub fn new(pkt: &RtpPacket<'_>, crypto_mode: CryptoMode, config: &Config) -> Self {
|
||||||
let playout_capacity = config.playout_buffer_length.get() + config.playout_spike_length;
|
let playout_capacity = config.playout_buffer_length.get() + config.playout_spike_length;
|
||||||
|
let (sample_rate, channels) = match config.decode_mode {
|
||||||
|
DecodeMode::Decode(config) => (config.sample_rate, config.channels),
|
||||||
|
DecodeMode::Decrypt | DecodeMode::Pass => Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
playout_buffer: PlayoutBuffer::new(playout_capacity, pkt.get_sequence().0),
|
playout_buffer: PlayoutBuffer::new(playout_capacity, pkt.get_sequence().0),
|
||||||
crypto_mode,
|
crypto_mode,
|
||||||
decoder: OpusDecoder::new(
|
decoder: OpusDecoder::new(sample_rate.into(), channels.into())
|
||||||
config.decode_sample_rate.into(),
|
.expect("Failed to create new Opus decoder for source."),
|
||||||
config.decode_channels.into(),
|
|
||||||
)
|
|
||||||
.expect("Failed to create new Opus decoder for source."),
|
|
||||||
decode_size: PacketDecodeSize::TwentyMillis,
|
decode_size: PacketDecodeSize::TwentyMillis,
|
||||||
prune_time: Instant::now() + config.decode_state_timeout,
|
prune_time: Instant::now() + config.decode_state_timeout,
|
||||||
disconnected: false,
|
disconnected: false,
|
||||||
channels: config.decode_channels,
|
channels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reconfigure_decoder(&mut self, config: &Config) {
|
pub fn reconfigure_decoder(&mut self, config: DecodeConfig) {
|
||||||
self.decoder = OpusDecoder::new(
|
self.decoder = OpusDecoder::new(config.sample_rate.into(), config.channels.into())
|
||||||
config.decode_sample_rate.into(),
|
.expect("Failed to create new Opus decoder for source.");
|
||||||
config.decode_channels.into(),
|
|
||||||
)
|
self.channels = config.channels;
|
||||||
.expect("Failed to create new Opus decoder for source.");
|
|
||||||
self.channels = config.decode_channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_packet(&mut self, packet: StoredPacket, config: &Config) {
|
pub fn store_packet(&mut self, packet: StoredPacket, config: &Config) {
|
||||||
@@ -80,14 +80,13 @@ impl SsrcState {
|
|||||||
decoded_voice: None,
|
decoded_voice: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let should_decode = config.decode_mode == DecodeMode::Decode;
|
let should_decode = config.decode_mode.should_decode();
|
||||||
|
|
||||||
if let Some((packet, decrypted)) = pkt {
|
if let Some((packet, decrypted)) = pkt {
|
||||||
let rtp = RtpPacket::new(&packet).unwrap();
|
let rtp = RtpPacket::new(&packet).unwrap();
|
||||||
let extensions = rtp.get_extension() != 0;
|
let extensions = rtp.get_extension() != 0;
|
||||||
|
|
||||||
let payload = rtp.payload();
|
let payload = rtp.payload();
|
||||||
let payload_offset = self.crypto_mode.payload_prefix_len2();
|
let payload_offset = self.crypto_mode.payload_prefix_len();
|
||||||
let payload_end_pad = payload.len() - self.crypto_mode.payload_suffix_len();
|
let payload_end_pad = payload.len() - self.crypto_mode.payload_suffix_len();
|
||||||
|
|
||||||
// We still need to compute missed packets here in case of long loss chains or similar.
|
// We still need to compute missed packets here in case of long loss chains or similar.
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use crate::{
|
|||||||
test_utils,
|
test_utils,
|
||||||
tracks::LoopState,
|
tracks::LoopState,
|
||||||
};
|
};
|
||||||
use crypto_secretbox::XSalsa20Poly1305;
|
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use std::{io::Cursor, net::UdpSocket, sync::Arc};
|
use std::{io::Cursor, net::UdpSocket, sync::Arc};
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
@@ -63,11 +62,8 @@ impl Mixer {
|
|||||||
.connect("127.0.0.1:5316")
|
.connect("127.0.0.1:5316")
|
||||||
.expect("Failed to connect to local dest port.");
|
.expect("Failed to connect to local dest port.");
|
||||||
|
|
||||||
#[allow(deprecated)]
|
let mode = CryptoMode::Aes256Gcm;
|
||||||
let mode = CryptoMode::Normal;
|
let cipher = mode.cipher_from_key(&[0u8; 32]).unwrap();
|
||||||
let cipher = mode
|
|
||||||
.cipher_from_key(&[0u8; XSalsa20Poly1305::KEY_SIZE])
|
|
||||||
.unwrap();
|
|
||||||
let crypto_state = mode.into();
|
let crypto_state = mode.into();
|
||||||
|
|
||||||
#[cfg(feature = "receive")]
|
#[cfg(feature = "receive")]
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ pub struct VoiceData {
|
|||||||
///
|
///
|
||||||
/// Valid audio data (`Some(audio)` where `audio.len >= 0`) typically contains 20ms of 16-bit PCM audio
|
/// Valid audio data (`Some(audio)` where `audio.len >= 0`) typically contains 20ms of 16-bit PCM audio
|
||||||
/// using native endianness. This defaults to stereo audio at 48kHz, and can be configured via
|
/// using native endianness. This defaults to stereo audio at 48kHz, and can be configured via
|
||||||
/// [`Config::decode_channels`] and [`Config::decode_sample_rate`] -- channels are interleaved
|
/// [`DecodeConfig::sample_rate`] and [`DecodeConfig::sample_rate`] -- channels are interleaved
|
||||||
/// (i.e., `L, R, L, R, ...`) if stereo.
|
/// (i.e., `L, R, L, R, ...`) if stereo.
|
||||||
///
|
///
|
||||||
/// This value will be `None` if Songbird is not configured to decode audio.
|
/// This value will be `None` if Songbird is not configured to decode audio.
|
||||||
///
|
///
|
||||||
/// [`Config::decode_channels`]: crate::Config::decode_channels
|
/// [`DecodeConfig::decode_channels`]: crate::driver::DecodeConfig::channels
|
||||||
/// [`Config::decode_sample_rate`]: crate::Config::decode_sample_rate
|
/// [`DecodeConfig::sample_rate`]: crate::driver::DecodeConfig::sample_rate
|
||||||
pub decoded_voice: Option<Vec<i16>>,
|
pub decoded_voice: Option<Vec<i16>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ impl EventStore {
|
|||||||
|
|
||||||
/// Processes all events due up to and including `now`.
|
/// Processes all events due up to and including `now`.
|
||||||
pub(crate) fn timed_event_ready(&self, now: Duration) -> bool {
|
pub(crate) fn timed_event_ready(&self, now: Duration) -> bool {
|
||||||
self.timed.peek().map_or(false, |evt| {
|
self.timed.peek().is_some_and(|evt| {
|
||||||
evt.fire_time
|
evt.fire_time
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Timed event must have a fire_time.")
|
.expect("Timed event must have a fire_time.")
|
||||||
|
|||||||
@@ -219,11 +219,11 @@ impl Call {
|
|||||||
where
|
where
|
||||||
C: Into<ChannelId> + Debug,
|
C: Into<ChannelId> + Debug,
|
||||||
{
|
{
|
||||||
self._join(channel_id.into()).await
|
self.join_inner(channel_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
async fn _join(&mut self, channel_id: ChannelId) -> JoinResult<Join> {
|
async fn join_inner(&mut self, channel_id: ChannelId) -> JoinResult<Join> {
|
||||||
let (tx, rx) = flume::unbounded();
|
let (tx, rx) = flume::unbounded();
|
||||||
let (gw_tx, gw_rx) = flume::unbounded();
|
let (gw_tx, gw_rx) = flume::unbounded();
|
||||||
|
|
||||||
@@ -280,10 +280,10 @@ impl Call {
|
|||||||
where
|
where
|
||||||
C: Into<ChannelId> + Debug,
|
C: Into<ChannelId> + Debug,
|
||||||
{
|
{
|
||||||
self._join_gateway(channel_id.into()).await
|
self.join_gateway_inner(channel_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _join_gateway(&mut self, channel_id: ChannelId) -> JoinResult<JoinGateway> {
|
async fn join_gateway_inner(&mut self, channel_id: ChannelId) -> JoinResult<JoinGateway> {
|
||||||
let (tx, rx) = flume::unbounded();
|
let (tx, rx) = flume::unbounded();
|
||||||
|
|
||||||
let do_conn = self
|
let do_conn = self
|
||||||
@@ -414,10 +414,10 @@ impl Call {
|
|||||||
where
|
where
|
||||||
C: Into<ChannelId> + Debug,
|
C: Into<ChannelId> + Debug,
|
||||||
{
|
{
|
||||||
self._update_state(session_id, channel_id.map(Into::into));
|
self.update_state_inner(session_id, channel_id.map(Into::into));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _update_state(&mut self, session_id: String, channel_id: Option<ChannelId>) {
|
fn update_state_inner(&mut self, session_id: String, channel_id: Option<ChannelId>) {
|
||||||
if let Some(channel_id) = channel_id {
|
if let Some(channel_id) = channel_id {
|
||||||
let try_conn = if let Some((ref mut progress, _)) = self.connection.as_mut() {
|
let try_conn = if let Some((ref mut progress, _)) = self.connection.as_mut() {
|
||||||
progress.apply_state_update(session_id, channel_id)
|
progress.apply_state_update(session_id, channel_id)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ struct AsyncAdapterSink {
|
|||||||
|
|
||||||
impl AsyncAdapterSink {
|
impl AsyncAdapterSink {
|
||||||
async fn launch(mut self) {
|
async fn launch(mut self) {
|
||||||
let mut inner_buf = [0u8; 32 * 1024];
|
let mut inner_buf = vec![0u8; 32 * 1024].into_boxed_slice();
|
||||||
let mut read_region = 0..0;
|
let mut read_region = 0..0;
|
||||||
let mut hit_end = false;
|
let mut hit_end = false;
|
||||||
let mut blocked = false;
|
let mut blocked = false;
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ where
|
|||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Read error {:?} {:?} {:?}.", e, out, raw_len);
|
debug!("Read error {:?} {:?} {:?}.", e, out, raw_len);
|
||||||
out = Some(Err(IoError::new(IoErrorKind::Other, e)));
|
out = Some(Err(IoError::other(e)));
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -472,7 +472,7 @@ where
|
|||||||
|
|
||||||
// NOTE: use of raw_len here preserves true sample length even if
|
// NOTE: use of raw_len here preserves true sample length even if
|
||||||
// stream is extended to 20ms boundary.
|
// stream is extended to 20ms boundary.
|
||||||
out.unwrap_or_else(|| Err(IoError::new(IoErrorKind::Other, "Unclear.")))
|
out.unwrap_or_else(|| Err(IoError::other("Unclear.")))
|
||||||
.map(|compressed_sz| {
|
.map(|compressed_sz| {
|
||||||
self.audio_bytes
|
self.audio_bytes
|
||||||
.fetch_add(raw_len * mem::size_of::<f32>(), Ordering::Release);
|
.fetch_add(raw_len * mem::size_of::<f32>(), Ordering::Release);
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
//! [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
|
//! [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
|
||||||
//! [`Compressed`]: cached::Compressed
|
//! [`Compressed`]: cached::Compressed
|
||||||
//! [DCA1]: https://github.com/bwmarrin/dca
|
//! [DCA1]: https://github.com/bwmarrin/dca
|
||||||
//! [`registry::*`]: registry
|
|
||||||
//! [`cached::*`]: cached
|
//! [`cached::*`]: cached
|
||||||
//! [`OpusDecoder`]: codecs::OpusDecoder
|
//! [`OpusDecoder`]: codecs::OpusDecoder
|
||||||
//! [`DcaReader`]: codecs::DcaReader
|
//! [`DcaReader`]: codecs::DcaReader
|
||||||
|
|||||||
@@ -132,8 +132,7 @@ impl HttpRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let stream = Box::new(StreamReader::new(
|
let stream = Box::new(StreamReader::new(
|
||||||
resp.bytes_stream()
|
resp.bytes_stream().map_err(IoError::other),
|
||||||
.map_err(|e| IoError::new(IoErrorKind::Other, e)),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
let input = HttpStream {
|
let input = HttpStream {
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ impl From<YoutubeDl<'static>> for Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<'a> Compose for YoutubeDl<'a> {
|
impl Compose for YoutubeDl<'_> {
|
||||||
fn create(&mut self) -> Result<AudioStream<Box<dyn MediaSource>>, AudioStreamError> {
|
fn create(&mut self) -> Result<AudioStream<Box<dyn MediaSource>>, AudioStreamError> {
|
||||||
Err(AudioStreamError::Unsupported)
|
Err(AudioStreamError::Unsupported)
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/lib.rs
18
src/lib.rs
@@ -10,16 +10,15 @@
|
|||||||
//! Songbird is an async, cross-library compatible voice system for Discord, written in Rust.
|
//! Songbird is an async, cross-library compatible voice system for Discord, written in Rust.
|
||||||
//! The library offers:
|
//! The library offers:
|
||||||
//! * A standalone gateway frontend compatible with [serenity] and [twilight] using the
|
//! * A standalone gateway frontend compatible with [serenity] and [twilight] using the
|
||||||
//! `"gateway"` and `"[serenity/twilight]"` plus `"[rustls/native]"` features. You can even run
|
//! `"gateway"` and `"[serenity/twilight]"` plus `"[rustls/native]"` features. You can even run
|
||||||
//! driverless, to help manage your [lavalink] sessions.
|
//! driverless, to help manage your [lavalink] sessions.
|
||||||
//! * A standalone driver for voice calls, via the `"driver"` feature. If you can create
|
//! * A standalone driver for voice calls, via the `"driver"` feature. If you can create
|
||||||
//! a `ConnectionInfo` using any other gateway, or language for your bot, then you
|
//! a `ConnectionInfo` using any other gateway, or language for your bot, then you
|
||||||
//! can run the songbird voice driver.
|
//! can run the songbird voice driver.
|
||||||
//! * Voice receive and RT(C)P packet handling via the `"receive"` feature.
|
//! * Voice receive and RT(C)P packet handling via the `"receive"` feature.
|
||||||
//! * SIMD-accelerated JSON decoding via the `"simd-json"` feature.
|
|
||||||
//! * And, by default, a fully featured voice system featuring events, queues,
|
//! * And, by default, a fully featured voice system featuring events, queues,
|
||||||
//! seeking on compatible streams, shared multithreaded audio stream caches,
|
//! seeking on compatible streams, shared multithreaded audio stream caches,
|
||||||
//! and direct Opus data passthrough from DCA files.
|
//! and direct Opus data passthrough from DCA files.
|
||||||
//!
|
//!
|
||||||
//! ## Intents
|
//! ## Intents
|
||||||
//! Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
//! Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
//! ```toml
|
//! ```toml
|
||||||
//! # Including songbird alone gives you support for Opus via the DCA file format.
|
//! # Including songbird alone gives you support for Opus via the DCA file format.
|
||||||
//! [dependencies.songbird]
|
//! [dependencies.songbird]
|
||||||
//! version = "0.4"
|
//! version = "0.5"
|
||||||
//! features = ["builtin-queue"]
|
//! features = ["builtin-queue"]
|
||||||
//!
|
//!
|
||||||
//! # To get additional codecs, you *must* add Symphonia yourself.
|
//! # To get additional codecs, you *must* add Symphonia yourself.
|
||||||
@@ -78,6 +77,9 @@
|
|||||||
clippy::missing_panics_doc,
|
clippy::missing_panics_doc,
|
||||||
clippy::doc_link_with_quotes,
|
clippy::doc_link_with_quotes,
|
||||||
clippy::doc_markdown,
|
clippy::doc_markdown,
|
||||||
|
// Allowed as they cannot be fixed without breaking
|
||||||
|
clippy::result_large_err,
|
||||||
|
clippy::large_enum_variant,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|||||||
@@ -154,36 +154,34 @@ impl Songbird {
|
|||||||
where
|
where
|
||||||
G: Into<GuildId>,
|
G: Into<GuildId>,
|
||||||
{
|
{
|
||||||
self._get_or_insert(guild_id.into())
|
self.get_or_insert_inner(guild_id.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _get_or_insert(&self, guild_id: GuildId) -> Arc<Mutex<Call>> {
|
fn get_or_insert_inner(&self, guild_id: GuildId) -> Arc<Mutex<Call>> {
|
||||||
self.get(guild_id).unwrap_or_else(|| {
|
self.calls
|
||||||
self.calls
|
.entry(guild_id)
|
||||||
.entry(guild_id)
|
.or_insert_with(|| {
|
||||||
.or_insert_with(|| {
|
let info = self
|
||||||
let info = self
|
.client_data
|
||||||
.client_data
|
.get()
|
||||||
.get()
|
.expect("Manager has not been initialised");
|
||||||
.expect("Manager has not been initialised");
|
|
||||||
|
|
||||||
let shard = shard_id(guild_id.0.get(), info.shard_count);
|
let shard = shard_id(guild_id.0.get(), info.shard_count);
|
||||||
let shard_handle = self
|
let shard_handle = self
|
||||||
.sharder
|
.sharder
|
||||||
.get_shard(shard)
|
.get_shard(shard)
|
||||||
.expect("Failed to get shard handle: shard_count incorrect?");
|
.expect("Failed to get shard handle: shard_count incorrect?");
|
||||||
|
|
||||||
let call = Call::from_config(
|
let call = Call::from_config(
|
||||||
guild_id,
|
guild_id,
|
||||||
shard_handle,
|
shard_handle,
|
||||||
info.user_id,
|
info.user_id,
|
||||||
self.config.read().clone(),
|
self.config.read().clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Arc::new(Mutex::new(call))
|
Arc::new(Mutex::new(call))
|
||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an iterator for all [`Call`]s currently managed.
|
/// Creates an iterator for all [`Call`]s currently managed.
|
||||||
@@ -235,11 +233,11 @@ impl Songbird {
|
|||||||
C: Into<ChannelId>,
|
C: Into<ChannelId>,
|
||||||
G: Into<GuildId>,
|
G: Into<GuildId>,
|
||||||
{
|
{
|
||||||
self._join(guild_id.into(), channel_id.into()).await
|
self.join_inner(guild_id.into(), channel_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
async fn _join(
|
async fn join_inner(
|
||||||
&self,
|
&self,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
@@ -277,10 +275,11 @@ impl Songbird {
|
|||||||
C: Into<ChannelId>,
|
C: Into<ChannelId>,
|
||||||
G: Into<GuildId>,
|
G: Into<GuildId>,
|
||||||
{
|
{
|
||||||
self._join_gateway(guild_id.into(), channel_id.into()).await
|
self.join_gateway_inner(guild_id.into(), channel_id.into())
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _join_gateway(
|
async fn join_gateway_inner(
|
||||||
&self,
|
&self,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
@@ -318,10 +317,10 @@ impl Songbird {
|
|||||||
/// [`remove`]: Songbird::remove
|
/// [`remove`]: Songbird::remove
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn leave<G: Into<GuildId>>(&self, guild_id: G) -> JoinResult<()> {
|
pub async fn leave<G: Into<GuildId>>(&self, guild_id: G) -> JoinResult<()> {
|
||||||
self._leave(guild_id.into()).await
|
self.leave_inner(guild_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _leave(&self, guild_id: GuildId) -> JoinResult<()> {
|
async fn leave_inner(&self, guild_id: GuildId) -> JoinResult<()> {
|
||||||
if let Some(call) = self.get(guild_id) {
|
if let Some(call) = self.get(guild_id) {
|
||||||
let mut handler = call.lock().await;
|
let mut handler = call.lock().await;
|
||||||
handler.leave().await
|
handler.leave().await
|
||||||
@@ -341,10 +340,10 @@ impl Songbird {
|
|||||||
/// [`Call`]: Call
|
/// [`Call`]: Call
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn remove<G: Into<GuildId>>(&self, guild_id: G) -> JoinResult<()> {
|
pub async fn remove<G: Into<GuildId>>(&self, guild_id: G) -> JoinResult<()> {
|
||||||
self._remove(guild_id.into()).await
|
self.remove_inner(guild_id.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _remove(&self, guild_id: GuildId) -> JoinResult<()> {
|
async fn remove_inner(&self, guild_id: GuildId) -> JoinResult<()> {
|
||||||
self.leave(guild_id).await?;
|
self.leave(guild_id).await?;
|
||||||
self.calls.remove(&guild_id);
|
self.calls.remove(&guild_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -476,7 +475,7 @@ pub struct Iter<'a> {
|
|||||||
inner: InnerIter<'a>,
|
inner: InnerIter<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Iter<'a> {
|
impl Iterator for Iter<'_> {
|
||||||
type Item = (GuildId, Arc<Mutex<Call>>);
|
type Item = (GuildId, Arc<Mutex<Call>>);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
|||||||
Reference in New Issue
Block a user