TLS 1.3, channel persistence, membership signaling
- TLS 1.3 on TCP (rustls + tokio-rustls, optional self-signed cert) - Server-side channel CRUD with SQLite persistence - Gateway → voice-node membership bridge via broadcast channel
This commit is contained in:
parent
523680e05d
commit
5534cd01f7
37 changed files with 771 additions and 160 deletions
134
Cargo.lock
generated
134
Cargo.lock
generated
|
|
@ -77,6 +77,29 @@ version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aws-lc-rs"
|
||||||
|
version = "1.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
|
||||||
|
dependencies = [
|
||||||
|
"aws-lc-sys",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aws-lc-sys"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cmake",
|
||||||
|
"dunce",
|
||||||
|
"fs_extra",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
|
|
@ -205,6 +228,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -416,6 +441,12 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -456,6 +487,12 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
|
|
@ -565,6 +602,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs_extra"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
|
|
@ -954,6 +997,16 @@ version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.4.3",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.103"
|
version = "0.3.103"
|
||||||
|
|
@ -1066,6 +1119,12 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|
@ -1136,6 +1195,16 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -1184,6 +1253,12 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
|
@ -1299,6 +1374,19 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rcgen"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
|
||||||
|
dependencies = [
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"time",
|
||||||
|
"yasna",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
|
|
@ -1354,6 +1442,8 @@ version = "0.23.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
|
checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aws-lc-rs",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
|
@ -1377,6 +1467,7 @@ version = "0.103.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aws-lc-rs",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
|
@ -1856,6 +1947,25 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -1909,6 +2019,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||||
|
dependencies = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
|
|
@ -2186,12 +2306,16 @@ dependencies = [
|
||||||
"hex",
|
"hex",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"rand 0.8.6",
|
"rand 0.8.6",
|
||||||
|
"rcgen",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"toml",
|
"toml",
|
||||||
"tower",
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -2206,6 +2330,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -2421,6 +2546,15 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yasna"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||||
|
dependencies = [
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,9 @@ axum = { version = "0.8", features = ["ws"] }
|
||||||
tower = "0.5"
|
tower = "0.5"
|
||||||
sqlx = { version = "0.9", features = ["sqlite", "runtime-tokio", "tls-rustls"] }
|
sqlx = { version = "0.9", features = ["sqlite", "runtime-tokio", "tls-rustls"] }
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
|
|
||||||
|
# TLS 1.3 support
|
||||||
|
rustls-pki-types = "1"
|
||||||
|
rustls = "0.23"
|
||||||
|
tokio-rustls = "0.26"
|
||||||
|
rcgen = "0.13"
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,15 @@ pub struct GatewayConfig {
|
||||||
/// Defaults to 10.
|
/// Defaults to 10.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub message_rate_burst: Option<u32>,
|
pub message_rate_burst: Option<u32>,
|
||||||
|
/// Enable TLS 1.3 on the TCP listener.
|
||||||
|
#[serde(default)]
|
||||||
|
pub tls_enabled: bool,
|
||||||
|
/// Path to TLS certificate file (PEM).
|
||||||
|
#[serde(default)]
|
||||||
|
pub tls_cert_path: Option<String>,
|
||||||
|
/// Path to TLS private key file (PEM).
|
||||||
|
#[serde(default)]
|
||||||
|
pub tls_key_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ impl Storage {
|
||||||
CREATE INDEX IF NOT EXISTS idx_msg_ch ON messages (channel_id, timestamp);
|
CREATE INDEX IF NOT EXISTS idx_msg_ch ON messages (channel_id, timestamp);
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
pubkey TEXT PRIMARY KEY, nickname TEXT NOT NULL, first_seen INTEGER NOT NULL
|
pubkey TEXT PRIMARY KEY, nickname TEXT NOT NULL, first_seen INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS channels (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
kind TEXT NOT NULL DEFAULT 'text',
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS bans (
|
CREATE TABLE IF NOT EXISTS bans (
|
||||||
pubkey TEXT PRIMARY KEY, reason TEXT, banned_at INTEGER NOT NULL
|
pubkey TEXT PRIMARY KEY, reason TEXT, banned_at INTEGER NOT NULL
|
||||||
|
|
@ -215,6 +221,68 @@ pub(crate) fn generate_invite_code() -> String {
|
||||||
format!("{:08x}", hash % 0x100000000u64)
|
format!("{:08x}", hash % 0x100000000u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::domain::channels::{ChannelKind, ChannelStore};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChannelRecord {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub kind: String,
|
||||||
|
pub created_at: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
pub async fn create_channel(&self, id: &str, name: &str, kind: &str) -> Result<bool> {
|
||||||
|
let result = sqlx::query(
|
||||||
|
"INSERT OR IGNORE INTO channels (id, name, kind, created_at) VALUES (?, ?, ?, ?)",
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(name)
|
||||||
|
.bind(kind)
|
||||||
|
.bind(now_ms())
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(result.rows_affected() > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_channel(&self, id: &str) -> Result<bool> {
|
||||||
|
let result = sqlx::query("DELETE FROM channels WHERE id=?")
|
||||||
|
.bind(id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(result.rows_affected() > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_channels(&self) -> Result<Vec<ChannelRecord>> {
|
||||||
|
let rows = sqlx::query_as::<_, (String, String, String, i64)>(
|
||||||
|
"SELECT id, name, kind, created_at FROM channels",
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, name, kind, created_at)| ChannelRecord {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
kind,
|
||||||
|
created_at,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_channels_to_cache(&self, channel_store: &ChannelStore) -> Result<()> {
|
||||||
|
let channels = self.list_channels().await?;
|
||||||
|
for ch in channels {
|
||||||
|
let kind = match ch.kind.as_str() {
|
||||||
|
"voice" => ChannelKind::Voice,
|
||||||
|
_ => ChannelKind::Text,
|
||||||
|
};
|
||||||
|
crate::domain::channels::create(channel_store, &ch.id, &ch.name, kind).await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn now_ms() -> i64 {
|
pub(crate) fn now_ms() -> i64 {
|
||||||
std::time::SystemTime::now()
|
std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
/// Handle a ChannelCreate request — register a new channel in the store and
|
/// Handle a ChannelCreate request — register a new channel in the store and
|
||||||
/// broadcast the new ChannelState to all sessions so their sidebars update.
|
/// broadcast the new ChannelState to all sessions so their sidebars update.
|
||||||
pub async fn handle_channel_create(
|
pub async fn handle_channel_create(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -97,6 +97,17 @@ pub async fn handle_channel_create(
|
||||||
|
|
||||||
let created = channels::create(&state.channels, &channel_id, &channel_name, kind.clone()).await;
|
let created = channels::create(&state.channels, &channel_id, &channel_name, kind.clone()).await;
|
||||||
|
|
||||||
|
// Persist to DB (best-effort, log and continue on failure).
|
||||||
|
if created {
|
||||||
|
if let Err(e) = state
|
||||||
|
.storage
|
||||||
|
.create_channel(&channel_id, &channel_name, kind.as_str())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("failed to persist channel to storage: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !created {
|
if !created {
|
||||||
io::send_encrypted(
|
io::send_encrypted(
|
||||||
stream,
|
stream,
|
||||||
|
|
@ -160,7 +171,7 @@ pub async fn handle_channel_create(
|
||||||
/// Handle a ChannelDelete request — remove the channel from the store and
|
/// Handle a ChannelDelete request — remove the channel from the store and
|
||||||
/// broadcast the deletion to all sessions.
|
/// broadcast the deletion to all sessions.
|
||||||
pub async fn handle_channel_delete(
|
pub async fn handle_channel_delete(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -190,6 +201,14 @@ pub async fn handle_channel_delete(
|
||||||
}
|
}
|
||||||
|
|
||||||
let existed = channels::delete(&state.channels, &req.channel_id).await;
|
let existed = channels::delete(&state.channels, &req.channel_id).await;
|
||||||
|
|
||||||
|
// Remove from DB (best-effort).
|
||||||
|
if existed {
|
||||||
|
if let Err(e) = state.storage.delete_channel(&req.channel_id).await {
|
||||||
|
warn!("failed to remove channel from storage: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !existed {
|
if !existed {
|
||||||
io::send_encrypted(
|
io::send_encrypted(
|
||||||
stream,
|
stream,
|
||||||
|
|
@ -229,7 +248,7 @@ pub async fn handle_channel_delete(
|
||||||
|
|
||||||
/// Handle a ChannelList request — reply with all known channels.
|
/// Handle a ChannelList request — reply with all known channels.
|
||||||
pub async fn handle_channel_list(
|
pub async fn handle_channel_list(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -19,7 +19,7 @@ use super::{broadcast_leave, set_channel};
|
||||||
const HISTORY_LIMIT: i64 = 50;
|
const HISTORY_LIMIT: i64 = 50;
|
||||||
|
|
||||||
pub async fn join(
|
pub async fn join(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
channel_id: &str,
|
channel_id: &str,
|
||||||
|
|
@ -110,6 +110,18 @@ pub async fn join(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(tx) = &state.voice_member_tx {
|
||||||
|
if let Some(sess) = session::get(&state.sessions, session_id).await {
|
||||||
|
let event = serde_json::json!({
|
||||||
|
"type": "joined",
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"session_id": session_id,
|
||||||
|
"user_id": sess.user_id,
|
||||||
|
});
|
||||||
|
let _ = tx.send(event.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!("session {} joined {channel_id}", &session_id[..8]);
|
info!("session {} joined {channel_id}", &session_id[..8]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,39 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{domain::channels, net::state::State, proto::SessionCrypto};
|
use crate::{domain::{channels, session}, net::state::State, proto::SessionCrypto};
|
||||||
|
|
||||||
use super::{broadcast_leave, set_channel};
|
use super::{broadcast_leave, set_channel};
|
||||||
|
|
||||||
pub async fn leave(
|
pub async fn leave(
|
||||||
_stream: &mut TcpStream,
|
_stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
_seq: &mut u32,
|
_seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
channel_id: &str,
|
channel_id: &str,
|
||||||
_crypto: &SessionCrypto,
|
_crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let user_id = session::get(&state.sessions, session_id)
|
||||||
|
.await
|
||||||
|
.map(|s| s.user_id);
|
||||||
|
|
||||||
channels::leave(&state.channels, channel_id, session_id).await;
|
channels::leave(&state.channels, channel_id, session_id).await;
|
||||||
set_channel(state, session_id, None).await;
|
set_channel(state, session_id, None).await;
|
||||||
broadcast_leave(state, channel_id, session_id).await;
|
broadcast_leave(state, channel_id, session_id).await;
|
||||||
|
|
||||||
|
if let Some(tx) = &state.voice_member_tx {
|
||||||
|
if let Some(ref uid) = user_id {
|
||||||
|
let event = serde_json::json!({
|
||||||
|
"type": "left",
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"session_id": session_id,
|
||||||
|
"user_id": uid,
|
||||||
|
});
|
||||||
|
let _ = tx.send(event.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!("session {} left {channel_id}", &session_id[..8]);
|
info!("session {} left {channel_id}", &session_id[..8]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_presence_update(
|
pub async fn handle_presence_update(
|
||||||
_stream: &mut TcpStream,
|
_stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -60,7 +60,7 @@ pub async fn handle_presence_update(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_presence_sync(
|
pub async fn handle_presence_sync(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_read_receipt(
|
pub async fn handle_read_receipt(
|
||||||
_stream: &mut tokio::net::TcpStream,
|
_stream: &mut (impl tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn deliver_encrypted(
|
pub async fn deliver_encrypted(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
msg: &BroadcastMsg,
|
msg: &BroadcastMsg,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_dm_history(
|
pub async fn handle_dm_history(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_dm_message(
|
pub async fn handle_dm_message(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_dm_start(
|
pub async fn handle_dm_start(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -88,7 +88,7 @@ pub async fn handle_dm_start(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_dm_read_ack(
|
pub async fn handle_dm_read_ack(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -11,174 +12,402 @@ use crate::{
|
||||||
|
|
||||||
use super::{Ctx, channel, content, direct_message, friends, guild};
|
use super::{Ctx, channel, content, direct_message, friends, guild};
|
||||||
|
|
||||||
pub async fn dispatch(
|
pub async fn dispatch<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
ctx: &mut Ctx<'_>,
|
ctx: &mut Ctx<'_, S>,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
pid: PacketId,
|
pid: PacketId,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
addr: std::net::SocketAddr,
|
addr: std::net::SocketAddr,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Ctx {
|
|
||||||
stream,
|
|
||||||
seq,
|
|
||||||
crypto,
|
|
||||||
state,
|
|
||||||
} = ctx;
|
|
||||||
match pid {
|
match pid {
|
||||||
PacketId::Ping => {
|
PacketId::Ping => {
|
||||||
let ping: PingPayload = serde_json::from_slice(payload)?;
|
let ping: PingPayload = serde_json::from_slice(payload)?;
|
||||||
io::send_encrypted(
|
io::send_encrypted(
|
||||||
stream,
|
ctx.stream,
|
||||||
PacketId::Pong,
|
PacketId::Pong,
|
||||||
seq,
|
ctx.seq,
|
||||||
&to_payload(&PongPayload {
|
&to_payload(&PongPayload {
|
||||||
timestamp: ping.timestamp,
|
timestamp: ping.timestamp,
|
||||||
}),
|
}),
|
||||||
crypto,
|
ctx.crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::JoinChannel => {
|
PacketId::JoinChannel => {
|
||||||
let m: JoinChannelPayload = serde_json::from_slice(payload)?;
|
let m: JoinChannelPayload = serde_json::from_slice(payload)?;
|
||||||
channel::join(stream, seq, session_id, &m.channel_id, crypto, state).await?;
|
channel::join(ctx.stream, ctx.seq, session_id, &m.channel_id, ctx.crypto, ctx.state)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::LeaveChannel => {
|
PacketId::LeaveChannel => {
|
||||||
let m: LeaveChannelPayload = serde_json::from_slice(payload)?;
|
let m: LeaveChannelPayload = serde_json::from_slice(payload)?;
|
||||||
channel::leave(stream, seq, session_id, &m.channel_id, crypto, state).await?;
|
channel::leave(ctx.stream, ctx.seq, session_id, &m.channel_id, ctx.crypto, ctx.state)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::ChannelCreate => {
|
PacketId::ChannelCreate => {
|
||||||
channel::handle_channel_create(stream, seq, session_id, payload, crypto, state).await?;
|
channel::handle_channel_create(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::ChannelDelete => {
|
PacketId::ChannelDelete => {
|
||||||
channel::handle_channel_delete(stream, seq, session_id, payload, crypto, state).await?;
|
channel::handle_channel_delete(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::ChannelList => {
|
PacketId::ChannelList => {
|
||||||
channel::handle_channel_list(stream, seq, session_id, crypto, state).await?;
|
channel::handle_channel_list(ctx.stream, ctx.seq, session_id, ctx.crypto, ctx.state)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::ChatMessage => {
|
PacketId::ChatMessage => {
|
||||||
let m: ChatMessagePayload = serde_json::from_slice(payload)?;
|
let m: ChatMessagePayload = serde_json::from_slice(payload)?;
|
||||||
content::chat::handle(session_id, m, state).await?;
|
content::chat::handle(session_id, m, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::DmStart => {
|
PacketId::DmStart => {
|
||||||
direct_message::handle_dm_start(stream, seq, session_id, payload, crypto, state)
|
direct_message::handle_dm_start(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::DmMessage => {
|
PacketId::DmMessage => {
|
||||||
direct_message::handle_dm_message(stream, seq, session_id, payload, crypto, state)
|
direct_message::handle_dm_message(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::DmHistory => {
|
PacketId::DmHistory => {
|
||||||
direct_message::handle_dm_history(stream, seq, session_id, payload, crypto, state)
|
direct_message::handle_dm_history(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::DmReadAck => {
|
PacketId::DmReadAck => {
|
||||||
direct_message::handle_dm_read_ack(stream, seq, session_id, payload, crypto, state)
|
direct_message::handle_dm_read_ack(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildCreate => {
|
PacketId::GuildCreate => {
|
||||||
guild::handle_guild_create(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_guild_create(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildDelete => {
|
PacketId::GuildDelete => {
|
||||||
guild::handle_guild_delete(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_guild_delete(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildList => {
|
PacketId::GuildList => {
|
||||||
guild::handle_guild_list(stream, seq, session_id, crypto, state).await?;
|
guild::handle_guild_list(ctx.stream, ctx.seq, session_id, ctx.crypto, ctx.state)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildMemberJoin => {
|
PacketId::GuildMemberJoin => {
|
||||||
guild::handle_guild_member_join(stream, seq, session_id, payload, crypto, state)
|
guild::handle_guild_member_join(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildMemberLeave => {
|
PacketId::GuildMemberLeave => {
|
||||||
guild::handle_guild_member_leave(stream, seq, session_id, payload, crypto, state)
|
guild::handle_guild_member_leave(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildMemberKick => {
|
PacketId::GuildMemberKick => {
|
||||||
guild::handle_guild_member_kick(stream, seq, session_id, payload, crypto, state)
|
guild::handle_guild_member_kick(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::RoleCreate => {
|
PacketId::RoleCreate => {
|
||||||
guild::handle_role_create(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_role_create(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::RoleDelete => {
|
PacketId::RoleDelete => {
|
||||||
guild::handle_role_delete(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_role_delete(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::InviteCreate => {
|
PacketId::InviteCreate => {
|
||||||
guild::handle_invite_create(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_invite_create(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::InviteAccept => {
|
PacketId::InviteAccept => {
|
||||||
guild::handle_invite_accept(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_invite_accept(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::InviteDelete => {
|
PacketId::InviteDelete => {
|
||||||
guild::handle_invite_delete(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_invite_delete(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildAuditLogFetch => {
|
PacketId::GuildAuditLogFetch => {
|
||||||
guild::handle_audit_log_fetch(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_audit_log_fetch(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildMemberListFetch => {
|
PacketId::GuildMemberListFetch => {
|
||||||
guild::handle_member_list_fetch(stream, seq, session_id, payload, crypto, state)
|
guild::handle_member_list_fetch(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildRoleAssign => {
|
PacketId::GuildRoleAssign => {
|
||||||
guild::handle_role_assign(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_role_assign(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildRoleUnassign => {
|
PacketId::GuildRoleUnassign => {
|
||||||
guild::handle_role_unassign(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_role_unassign(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::GuildRoleListFetch => {
|
PacketId::GuildRoleListFetch => {
|
||||||
guild::handle_role_list_fetch(stream, seq, session_id, payload, crypto, state).await?;
|
guild::handle_role_list_fetch(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::PresenceUpdate => {
|
PacketId::PresenceUpdate => {
|
||||||
content::presence::handle_presence_update(
|
content::presence::handle_presence_update(
|
||||||
stream, seq, session_id, payload, crypto, state,
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::PresenceSync => {
|
PacketId::PresenceSync => {
|
||||||
content::presence::handle_presence_sync(stream, seq, session_id, crypto, state).await?;
|
content::presence::handle_presence_sync(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::FriendRequest => {
|
PacketId::FriendRequest => {
|
||||||
friends::handle_friend_request(stream, seq, session_id, payload, crypto, state).await?;
|
friends::handle_friend_request(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::FriendAccept => {
|
PacketId::FriendAccept => {
|
||||||
friends::handle_friend_accept(stream, seq, session_id, payload, crypto, state).await?;
|
friends::handle_friend_accept(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::FriendDecline => {
|
PacketId::FriendDecline => {
|
||||||
friends::handle_friend_decline(stream, seq, session_id, payload, crypto, state).await?;
|
friends::handle_friend_decline(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::FriendRemove => {
|
PacketId::FriendRemove => {
|
||||||
friends::handle_friend_remove(stream, seq, session_id, payload, crypto, state).await?;
|
friends::handle_friend_remove(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::FriendList => {
|
PacketId::FriendList => {
|
||||||
friends::handle_friend_list(stream, seq, session_id, crypto, state).await?;
|
friends::handle_friend_list(ctx.stream, ctx.seq, session_id, ctx.crypto, ctx.state)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::BlockUser => {
|
PacketId::BlockUser => {
|
||||||
friends::handle_block_user(stream, seq, session_id, payload, crypto, state).await?;
|
friends::handle_block_user(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::UnblockUser => {
|
PacketId::UnblockUser => {
|
||||||
friends::handle_unblock_user(stream, seq, session_id, payload, crypto, state).await?;
|
friends::handle_unblock_user(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::BlockList => {
|
PacketId::BlockList => {
|
||||||
friends::handle_block_list(stream, seq, session_id, crypto, state).await?;
|
friends::handle_block_list(ctx.stream, ctx.seq, session_id, ctx.crypto, ctx.state)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::MessageReactionAdd => {
|
PacketId::MessageReactionAdd => {
|
||||||
let m: ReactionPayload = serde_json::from_slice(payload)?;
|
let m: ReactionPayload = serde_json::from_slice(payload)?;
|
||||||
content::reaction::handle_reaction_add(session_id, m, state).await?;
|
content::reaction::handle_reaction_add(session_id, m, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::MessageReactionRemove => {
|
PacketId::MessageReactionRemove => {
|
||||||
let m: ReactionPayload = serde_json::from_slice(payload)?;
|
let m: ReactionPayload = serde_json::from_slice(payload)?;
|
||||||
content::reaction::handle_reaction_remove(session_id, m, state).await?;
|
content::reaction::handle_reaction_remove(session_id, m, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::MessageEdit => {
|
PacketId::MessageEdit => {
|
||||||
content::message_edit::handle_message_edit(session_id, payload, state).await?;
|
content::message_edit::handle_message_edit(session_id, payload, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::MessageDelete => {
|
PacketId::MessageDelete => {
|
||||||
content::message_edit::handle_message_delete(session_id, payload, state).await?;
|
content::message_edit::handle_message_delete(session_id, payload, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::TypingStart => {
|
PacketId::TypingStart => {
|
||||||
content::handle_typing_start(session_id, payload, state).await?;
|
content::handle_typing_start(session_id, payload, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::ReadReceipt => {
|
PacketId::ReadReceipt => {
|
||||||
content::handle_read_receipt(stream, seq, session_id, payload, crypto, state).await?;
|
content::handle_read_receipt(
|
||||||
|
ctx.stream,
|
||||||
|
ctx.seq,
|
||||||
|
session_id,
|
||||||
|
payload,
|
||||||
|
ctx.crypto,
|
||||||
|
ctx.state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::Disconnect => debug!("{addr} DISCONNECT"),
|
PacketId::Disconnect => debug!("{addr} DISCONNECT"),
|
||||||
other => debug!("{addr} unhandled {:?}", other),
|
other => debug!("{addr} unhandled {:?}", other),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_friend_decline(
|
pub async fn handle_friend_decline(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_friend_list(
|
pub async fn handle_friend_list(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_friend_remove(
|
pub async fn handle_friend_remove(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -40,7 +40,7 @@ pub async fn handle_friend_remove(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_block_user(
|
pub async fn handle_block_user(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -69,7 +69,7 @@ pub async fn handle_block_user(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_unblock_user(
|
pub async fn handle_unblock_user(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -98,7 +98,7 @@ pub async fn handle_unblock_user(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_block_list(
|
pub async fn handle_block_list(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ mod manage;
|
||||||
mod requests;
|
mod requests;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
net::io,
|
net::io,
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn send_err(
|
async fn send_err(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
code: crate::proto::ErrorCode,
|
code: crate::proto::ErrorCode,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
use super::send_err;
|
use super::send_err;
|
||||||
|
|
||||||
pub async fn handle_friend_request(
|
pub async fn handle_friend_request(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -100,7 +100,7 @@ pub async fn handle_friend_request(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_friend_accept(
|
pub async fn handle_friend_accept(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::permissions::Permissions,
|
domain::permissions::Permissions,
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
|
|
||||||
/// Fetch audit log entries for a guild (admin-only: requires VIEW_AUDIT_LOG or owner).
|
/// Fetch audit log entries for a guild (admin-only: requires VIEW_AUDIT_LOG or owner).
|
||||||
pub async fn handle_audit_log_fetch(
|
pub async fn handle_audit_log_fetch(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
use super::send_err;
|
use super::send_err;
|
||||||
|
|
||||||
pub async fn handle_guild_create(
|
pub async fn handle_guild_create(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -67,7 +67,7 @@ pub async fn handle_guild_create(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_guild_delete(
|
pub async fn handle_guild_delete(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::{permissions::Permissions, session},
|
domain::{permissions::Permissions, session},
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
use super::{now_ms, send_err};
|
use super::{now_ms, send_err};
|
||||||
|
|
||||||
pub async fn handle_invite_create(
|
pub async fn handle_invite_create(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -89,7 +89,7 @@ pub async fn handle_invite_create(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_invite_accept(
|
pub async fn handle_invite_accept(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -164,7 +164,7 @@ pub async fn handle_invite_accept(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_invite_delete(
|
pub async fn handle_invite_delete(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_guild_list(
|
pub async fn handle_guild_list(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::{permissions::Permissions, session},
|
domain::{permissions::Permissions, session},
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
use super::send_err;
|
use super::send_err;
|
||||||
|
|
||||||
pub async fn handle_guild_member_join(
|
pub async fn handle_guild_member_join(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -63,7 +63,7 @@ pub async fn handle_guild_member_join(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_guild_member_leave(
|
pub async fn handle_guild_member_leave(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -96,7 +96,7 @@ pub async fn handle_guild_member_leave(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_guild_member_kick(
|
pub async fn handle_guild_member_kick(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::{permissions::Permissions, session},
|
domain::{permissions::Permissions, session},
|
||||||
|
|
@ -15,7 +15,7 @@ use super::{require_perm, send_err};
|
||||||
|
|
||||||
/// Fetch the member list for a guild (visible to all members).
|
/// Fetch the member list for a guild (visible to all members).
|
||||||
pub async fn handle_member_list_fetch(
|
pub async fn handle_member_list_fetch(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -65,7 +65,7 @@ pub async fn handle_member_list_fetch(
|
||||||
|
|
||||||
/// Assign a role to a user (admin only: requires MANAGE_ROLES).
|
/// Assign a role to a user (admin only: requires MANAGE_ROLES).
|
||||||
pub async fn handle_role_assign(
|
pub async fn handle_role_assign(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -129,7 +129,7 @@ pub async fn handle_role_assign(
|
||||||
|
|
||||||
/// Remove a role from a user (admin only: requires MANAGE_ROLES).
|
/// Remove a role from a user (admin only: requires MANAGE_ROLES).
|
||||||
pub async fn handle_role_unassign(
|
pub async fn handle_role_unassign(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -192,7 +192,7 @@ pub async fn handle_role_unassign(
|
||||||
|
|
||||||
/// Fetch all roles defined in a guild (visible to all members).
|
/// Fetch all roles defined in a guild (visible to all members).
|
||||||
pub async fn handle_role_list_fetch(
|
pub async fn handle_role_list_fetch(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ mod members_list;
|
||||||
mod roles;
|
mod roles;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::permissions::Permissions,
|
domain::permissions::Permissions,
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn send_err(
|
async fn send_err(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
code: crate::proto::ErrorCode,
|
code: crate::proto::ErrorCode,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::{permissions::Permissions, session},
|
domain::{permissions::Permissions, session},
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
use super::send_err;
|
use super::send_err;
|
||||||
|
|
||||||
pub async fn handle_role_create(
|
pub async fn handle_role_create(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -73,7 +73,7 @@ pub async fn handle_role_create(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_role_delete(
|
pub async fn handle_role_delete(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ pub mod run;
|
||||||
pub use dispatch::dispatch;
|
pub use dispatch::dispatch;
|
||||||
pub use run::run_session;
|
pub use run::run_session;
|
||||||
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{net::state::State, proto::SessionCrypto};
|
use crate::{net::state::State, proto::SessionCrypto};
|
||||||
|
|
||||||
pub struct Ctx<'a> {
|
pub struct Ctx<'a, S: AsyncRead + AsyncWrite + Unpin> {
|
||||||
pub stream: &'a mut TcpStream,
|
pub stream: &'a mut S,
|
||||||
pub seq: &'a mut u32,
|
pub seq: &'a mut u32,
|
||||||
pub crypto: &'a SessionCrypto,
|
pub crypto: &'a SessionCrypto,
|
||||||
pub state: &'a State,
|
pub state: &'a State,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
|
@ -10,8 +10,8 @@ use crate::{
|
||||||
|
|
||||||
use super::{Ctx, deliver::deliver_encrypted, dispatch};
|
use super::{Ctx, deliver::deliver_encrypted, dispatch};
|
||||||
|
|
||||||
pub async fn run_session(
|
pub async fn run_session<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
stream: &mut TcpStream,
|
stream: &mut S,
|
||||||
addr: std::net::SocketAddr,
|
addr: std::net::SocketAddr,
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,15 @@ pub mod proto;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use domain::{channels, config, session, storage};
|
use domain::{channels, config, session, storage};
|
||||||
use net::state::State;
|
use net::state::{State, VoiceMemberTx};
|
||||||
|
|
||||||
pub async fn run(cfg: Arc<config::Config>) -> Result<()> {
|
pub async fn run(cfg: Arc<config::Config>, voice_member_tx: Option<VoiceMemberTx>) -> Result<()> {
|
||||||
let private_mode = cfg.is_private();
|
let private_mode = cfg.is_private();
|
||||||
if private_mode {
|
if private_mode {
|
||||||
info!("private mode enabled, federation disabled");
|
info!("private mode enabled, federation disabled");
|
||||||
|
|
@ -74,6 +75,7 @@ pub async fn run(cfg: Arc<config::Config>) -> Result<()> {
|
||||||
metrics.clone(),
|
metrics.clone(),
|
||||||
sessions_count.clone(),
|
sessions_count.clone(),
|
||||||
channels_count.clone(),
|
channels_count.clone(),
|
||||||
|
voice_member_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let admin_bind = cfg
|
let admin_bind = cfg
|
||||||
|
|
@ -95,11 +97,52 @@ pub async fn run(cfg: Arc<config::Config>) -> Result<()> {
|
||||||
let listener = TcpListener::bind(&cfg.gateway.bind).await?;
|
let listener = TcpListener::bind(&cfg.gateway.bind).await?;
|
||||||
info!("listening on {}", cfg.gateway.bind);
|
info!("listening on {}", cfg.gateway.bind);
|
||||||
|
|
||||||
|
if cfg.gateway.tls_enabled {
|
||||||
|
run_tls(listener, cfg, state).await
|
||||||
|
} else {
|
||||||
|
run_plain(listener, state).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_tls(listener: TcpListener, cfg: Arc<config::Config>, state: State) -> Result<()> {
|
||||||
|
let (certs, key) = load_or_generate_tls_certs(&cfg.gateway)?;
|
||||||
|
if cfg.gateway.tls_cert_path.is_none() {
|
||||||
|
warn!("using self-signed TLS certificate — clients must accept it manually");
|
||||||
|
}
|
||||||
|
let server_config = rustls::ServerConfig::builder()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(certs, key)?;
|
||||||
|
let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match listener.accept().await {
|
||||||
|
Ok((stream, addr)) => {
|
||||||
|
info!("connection from {addr} (TLS)");
|
||||||
|
state.metrics.inc(&state.metrics.connections_total);
|
||||||
|
let s = state.clone();
|
||||||
|
let acceptor = acceptor.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match acceptor.accept(stream).await {
|
||||||
|
Ok(tls_stream) => {
|
||||||
|
if let Err(e) = handle(tls_stream, addr, s).await {
|
||||||
|
debug!("{addr} closed: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => error!("TLS accept error from {addr}: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => error!("accept: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_plain(listener: TcpListener, state: State) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
match listener.accept().await {
|
match listener.accept().await {
|
||||||
Ok((stream, addr)) => {
|
Ok((stream, addr)) => {
|
||||||
info!("connection from {addr}");
|
info!("connection from {addr}");
|
||||||
metrics.inc(&metrics.connections_total);
|
state.metrics.inc(&state.metrics.connections_total);
|
||||||
let s = state.clone();
|
let s = state.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = handle(stream, addr, s).await {
|
if let Err(e) = handle(stream, addr, s).await {
|
||||||
|
|
@ -112,8 +155,8 @@ pub async fn run(cfg: Arc<config::Config>) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(
|
async fn handle<S: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
|
||||||
mut stream: tokio::net::TcpStream,
|
mut stream: S,
|
||||||
addr: std::net::SocketAddr,
|
addr: std::net::SocketAddr,
|
||||||
state: State,
|
state: State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|
@ -164,3 +207,32 @@ async fn handle(
|
||||||
debug!("{addr} cleaned up");
|
debug!("{addr} cleaned up");
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_or_generate_tls_certs(
|
||||||
|
cfg: &config::GatewayConfig,
|
||||||
|
) -> Result<(
|
||||||
|
Vec<rustls::pki_types::CertificateDer<'static>>,
|
||||||
|
rustls::pki_types::PrivateKeyDer<'static>,
|
||||||
|
)> {
|
||||||
|
use rustls::pki_types::pem::PemObject;
|
||||||
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
|
|
||||||
|
match (&cfg.tls_cert_path, &cfg.tls_key_path) {
|
||||||
|
(Some(cert_path), Some(key_path)) => {
|
||||||
|
let cert_pem = std::fs::read_to_string(cert_path)?;
|
||||||
|
let key_pem = std::fs::read_to_string(key_path)?;
|
||||||
|
let cert = CertificateDer::from_pem_reader(&mut cert_pem.as_bytes())?;
|
||||||
|
let key = PrivateKeyDer::from_pem_reader(&mut key_pem.as_bytes())?;
|
||||||
|
Ok((vec![cert], key))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
info!("No TLS cert/key configured, generating self-signed certificate");
|
||||||
|
let certified_key = rcgen::generate_simple_self_signed(vec!["VNOX Server".into()])?;
|
||||||
|
let cert_der = certified_key.cert.der().clone();
|
||||||
|
let key_der = PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||||
|
certified_key.key_pair.serialize_der(),
|
||||||
|
));
|
||||||
|
Ok((vec![cert_der], key_der))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ async fn main() -> Result<()> {
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let cfg = Arc::new(vnox_gateway::domain::config::load()?);
|
let cfg = Arc::new(vnox_gateway::domain::config::load()?);
|
||||||
vnox_gateway::run(cfg).await
|
vnox_gateway::run(cfg, None).await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::net::TcpStream;
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -16,7 +16,7 @@ const LNEX_VERSION: &str = "v1";
|
||||||
/// Returns the session and the derived crypto context (encryption keys).
|
/// Returns the session and the derived crypto context (encryption keys).
|
||||||
/// All subsequent packets must be encrypted with `crypto`.
|
/// All subsequent packets must be encrypted with `crypto`.
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
addr: std::net::SocketAddr,
|
addr: std::net::SocketAddr,
|
||||||
state: &State,
|
state: &State,
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio::net::TcpStream;
|
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::proto::{
|
||||||
ErrorCode, ErrorPayload, PacketHeader, PacketId, SessionCrypto, encode_packet, flags,
|
ErrorCode, ErrorPayload, PacketHeader, PacketId, SessionCrypto, encode_packet, flags,
|
||||||
|
|
@ -9,10 +8,8 @@ use crate::proto::{
|
||||||
|
|
||||||
const MAX_PAYLOAD: u32 = 4 * 1024 * 1024;
|
const MAX_PAYLOAD: u32 = 4 * 1024 * 1024;
|
||||||
|
|
||||||
// ─── Unencrypted IO (used during handshake) ─────────────────────────────────
|
|
||||||
|
|
||||||
pub async fn send_packet(
|
pub async fn send_packet(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
id: PacketId,
|
id: PacketId,
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -24,7 +21,7 @@ pub async fn send_packet(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_error(
|
pub async fn send_error(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
code: ErrorCode,
|
code: ErrorCode,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
|
|
@ -37,7 +34,9 @@ pub async fn send_error(
|
||||||
send_packet(stream, PacketId::Error, seq, &to_payload(&p)).await
|
send_packet(stream, PacketId::Error, seq, &to_payload(&p)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_packet(stream: &mut TcpStream) -> Result<(PacketHeader, Vec<u8>)> {
|
pub async fn read_packet(
|
||||||
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
|
) -> Result<(PacketHeader, Vec<u8>)> {
|
||||||
let mut buf = [0u8; PacketHeader::SIZE];
|
let mut buf = [0u8; PacketHeader::SIZE];
|
||||||
stream.read_exact(&mut buf).await?;
|
stream.read_exact(&mut buf).await?;
|
||||||
let hdr = PacketHeader::from_bytes(&buf);
|
let hdr = PacketHeader::from_bytes(&buf);
|
||||||
|
|
@ -54,12 +53,8 @@ pub async fn read_packet(stream: &mut TcpStream) -> Result<(PacketHeader, Vec<u8
|
||||||
Ok((hdr, payload))
|
Ok((hdr, payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Encrypted IO (used after handshake) ─────────────────────────────────────
|
|
||||||
|
|
||||||
/// Send an encrypted packet (server → client).
|
|
||||||
/// Sets the ENCRYPTED flag and encrypts the payload with s2c_key.
|
|
||||||
pub async fn send_encrypted(
|
pub async fn send_encrypted(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
id: PacketId,
|
id: PacketId,
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
|
|
@ -81,10 +76,8 @@ pub async fn send_encrypted(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read and decrypt a packet (client → server).
|
|
||||||
/// Verifies the ENCRYPTED flag and decrypts with c2s_key.
|
|
||||||
pub async fn read_encrypted(
|
pub async fn read_encrypted(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
) -> Result<(PacketHeader, Vec<u8>)> {
|
) -> Result<(PacketHeader, Vec<u8>)> {
|
||||||
let mut buf = [0u8; PacketHeader::SIZE];
|
let mut buf = [0u8; PacketHeader::SIZE];
|
||||||
|
|
@ -107,12 +100,8 @@ pub async fn read_encrypted(
|
||||||
Ok((hdr, payload))
|
Ok((hdr, payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-encode and deliver a pre-encoded broadcast packet with encryption.
|
|
||||||
///
|
|
||||||
/// Extracts the packet ID from the pre-encoded `raw_data`, re-encodes
|
|
||||||
/// with the correct sequence and encryption for this recipient.
|
|
||||||
pub async fn deliver_encrypted(
|
pub async fn deliver_encrypted(
|
||||||
stream: &mut TcpStream,
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
seq: &mut u32,
|
seq: &mut u32,
|
||||||
raw_data: &[u8],
|
raw_data: &[u8],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use tokio::sync::{RwLock, broadcast};
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::{broadcast, RwLock};
|
||||||
|
|
||||||
use crate::admin::metrics::Metrics;
|
use crate::admin::metrics::Metrics;
|
||||||
use crate::bootstrap::server_identity::ServerIdentity;
|
use crate::bootstrap::server_identity::ServerIdentity;
|
||||||
|
|
@ -11,6 +11,8 @@ use crate::domain::{
|
||||||
};
|
};
|
||||||
use crate::proto::PresenceInfo;
|
use crate::proto::PresenceInfo;
|
||||||
|
|
||||||
|
pub type VoiceMemberTx = tokio::sync::broadcast::Sender<String>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub sessions: SessionStore,
|
pub sessions: SessionStore,
|
||||||
|
|
@ -30,6 +32,8 @@ pub struct State {
|
||||||
pub channels_count: Arc<AtomicUsize>,
|
pub channels_count: Arc<AtomicUsize>,
|
||||||
/// Per-session rate limiter (token bucket).
|
/// Per-session rate limiter (token bucket).
|
||||||
pub rate_limiter: Arc<RateLimiter>,
|
pub rate_limiter: Arc<RateLimiter>,
|
||||||
|
/// Gateway → voice-node membership bridge.
|
||||||
|
pub voice_member_tx: Option<VoiceMemberTx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -44,6 +48,7 @@ impl State {
|
||||||
metrics: Arc<Metrics>,
|
metrics: Arc<Metrics>,
|
||||||
sessions_count: Arc<AtomicUsize>,
|
sessions_count: Arc<AtomicUsize>,
|
||||||
channels_count: Arc<AtomicUsize>,
|
channels_count: Arc<AtomicUsize>,
|
||||||
|
voice_member_tx: Option<VoiceMemberTx>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rate_per_sec = config.gateway.message_rate_per_sec.unwrap_or(5.0);
|
let rate_per_sec = config.gateway.message_rate_per_sec.unwrap_or(5.0);
|
||||||
let burst = config.gateway.message_rate_burst.unwrap_or(10);
|
let burst = config.gateway.message_rate_burst.unwrap_or(10);
|
||||||
|
|
@ -60,6 +65,7 @@ impl State {
|
||||||
sessions_count,
|
sessions_count,
|
||||||
channels_count,
|
channels_count,
|
||||||
rate_limiter,
|
rate_limiter,
|
||||||
|
voice_member_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,4 @@ tracing-subscriber.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
mod voice_membership;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
|
|
@ -28,12 +33,34 @@ async fn main() -> Result<()> {
|
||||||
let voice_bind = cfg.voice.bind.clone();
|
let voice_bind = cfg.voice.bind.clone();
|
||||||
let gate_cfg = Arc::new(cfg);
|
let gate_cfg = Arc::new(cfg);
|
||||||
|
|
||||||
|
let (voice_member_tx, _) = broadcast::channel::<String>(256);
|
||||||
|
let mut voice_member_rx = voice_member_tx.subscribe();
|
||||||
|
|
||||||
let voice_handle = tokio::spawn(async move {
|
let voice_handle = tokio::spawn(async move {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok(event_str) = voice_member_rx.recv().await {
|
||||||
|
if let Ok(event) = serde_json::from_str::<serde_json::Value>(&event_str) {
|
||||||
|
let event_type = event["type"].as_str().unwrap_or("");
|
||||||
|
let channel_id = event["channel_id"].as_str().unwrap_or("");
|
||||||
|
let user_id = event["user_id"].as_str().unwrap_or("");
|
||||||
|
match event_type {
|
||||||
|
"joined" => {
|
||||||
|
info!("voice member joined channel {}: {}", channel_id, user_id);
|
||||||
|
}
|
||||||
|
"left" => {
|
||||||
|
info!("voice member left channel {}: {}", channel_id, user_id);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
vnox_voice_node::runner::run_bind(&node_name, &voice_bind).await
|
vnox_voice_node::runner::run_bind(&node_name, &voice_bind).await
|
||||||
});
|
});
|
||||||
|
|
||||||
let gate_handle = tokio::spawn(async move {
|
let gate_handle = tokio::spawn(async move {
|
||||||
vnox_gateway::run(gate_cfg).await
|
vnox_gateway::run(gate_cfg, Some(voice_member_tx)).await
|
||||||
});
|
});
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|
|
||||||
22
serverd/src/voice_membership.rs
Normal file
22
serverd/src/voice_membership.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum VoiceMembershipEvent {
|
||||||
|
Joined {
|
||||||
|
channel_id: String,
|
||||||
|
session_id: String,
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
|
Left {
|
||||||
|
channel_id: String,
|
||||||
|
session_id: String,
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn voice_membership_channel() -> (
|
||||||
|
broadcast::Sender<VoiceMembershipEvent>,
|
||||||
|
broadcast::Receiver<VoiceMembershipEvent>,
|
||||||
|
) {
|
||||||
|
broadcast::channel(256)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue