protobuf migration: replace JSON with prost-generated wire format
- Expanded protocol/lnex.proto with full message set for all packet types - Added prost + prost-build to gateway build pipeline - Refactored all handlers and framing to use prost encode/decode - Removed serde payload structs (now generated from .proto) - Server starts and admin endpoint responds
This commit is contained in:
parent
5534cd01f7
commit
9821814240
31 changed files with 719 additions and 661 deletions
145
Cargo.lock
generated
145
Cargo.lock
generated
|
|
@ -564,6 +564,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fiat-crypto"
|
name = "fiat-crypto"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
|
@ -576,6 +582,12 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
|
@ -991,6 +1003,15 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -1041,6 +1062,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
@ -1110,6 +1137,12 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multimap"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
|
|
@ -1211,6 +1244,16 @@ version = "2.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
@ -1268,6 +1311,16 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
|
|
@ -1277,6 +1330,58 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"prost-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-build"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"itertools",
|
||||||
|
"log",
|
||||||
|
"multimap",
|
||||||
|
"once_cell",
|
||||||
|
"petgraph",
|
||||||
|
"prettyplease",
|
||||||
|
"prost",
|
||||||
|
"prost-types",
|
||||||
|
"regex",
|
||||||
|
"syn",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-derive"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"itertools",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-types"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
|
||||||
|
dependencies = [
|
||||||
|
"prost",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.46"
|
version = "1.0.46"
|
||||||
|
|
@ -1396,6 +1501,18 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
|
@ -1436,6 +1553,19 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.41"
|
version = "0.23.41"
|
||||||
|
|
@ -1918,6 +2048,19 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.4.3",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.18"
|
||||||
|
|
@ -2305,6 +2448,8 @@ dependencies = [
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"hex",
|
"hex",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
|
"prost",
|
||||||
|
"prost-build",
|
||||||
"rand 0.8.6",
|
"rand 0.8.6",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,13 @@ 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"
|
||||||
|
prost = "0.13"
|
||||||
|
|
||||||
# TLS 1.3 support
|
# TLS 1.3 support
|
||||||
rustls-pki-types = "1"
|
rustls-pki-types = "1"
|
||||||
rustls = "0.23"
|
rustls = "0.23"
|
||||||
tokio-rustls = "0.26"
|
tokio-rustls = "0.26"
|
||||||
rcgen = "0.13"
|
rcgen = "0.13"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
prost-build = "0.13"
|
||||||
|
|
|
||||||
3
gateway/build.rs
Normal file
3
gateway/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
prost_build::compile_protos(&["../protocol/lnex.proto"], &["../protocol/"]).unwrap();
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ pub async fn handle_channel_create(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: ChannelCreatePayload = serde_json::from_slice(payload)?;
|
let req = ChannelCreatePayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
@ -178,7 +179,7 @@ pub async fn handle_channel_delete(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: ChannelDeletePayload = serde_json::from_slice(payload)?;
|
let req = ChannelDeletePayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -8,7 +9,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_message_edit(session_id: &str, payload: &[u8], state: &State) -> Result<()> {
|
pub async fn handle_message_edit(session_id: &str, payload: &[u8], state: &State) -> Result<()> {
|
||||||
let edit: crate::proto::MessageEditPayload = serde_json::from_slice(payload)?;
|
let edit = crate::proto::MessageEditPayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
@ -60,7 +61,7 @@ pub async fn handle_message_edit(session_id: &str, payload: &[u8], state: &State
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_message_delete(session_id: &str, payload: &[u8], state: &State) -> Result<()> {
|
pub async fn handle_message_delete(session_id: &str, payload: &[u8], state: &State) -> Result<()> {
|
||||||
let delete: MessageDeletePayload = serde_json::from_slice(payload)?;
|
let delete = MessageDeletePayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -21,7 +22,7 @@ pub async fn handle_presence_update(
|
||||||
_crypto: &SessionCrypto,
|
_crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: PresenceUpdatePayload = serde_json::from_slice(payload)?;
|
let req = PresenceUpdatePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
net::state::{BroadcastMsg, State},
|
net::state::{BroadcastMsg, State},
|
||||||
proto::{PacketId, ReadReceiptPayload, encode_packet, to_payload},
|
proto::{PacketId, ReadReceiptBroadcastPayload, ReadReceiptPayload, encode_packet, to_payload},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_read_receipt(
|
pub async fn handle_read_receipt(
|
||||||
|
|
@ -14,7 +15,7 @@ pub async fn handle_read_receipt(
|
||||||
_crypto: &crate::proto::SessionCrypto,
|
_crypto: &crate::proto::SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: ReadReceiptPayload = serde_json::from_slice(payload)?;
|
let req = ReadReceiptPayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
@ -25,11 +26,11 @@ pub async fn handle_read_receipt(
|
||||||
.update_read_receipt(&req.channel_id, &sess.user_id, &req.last_read_message_id)
|
.update_read_receipt(&req.channel_id, &sess.user_id, &req.last_read_message_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let broadcast_data = serde_json::json!({
|
let broadcast_data = ReadReceiptBroadcastPayload {
|
||||||
"channel_id": req.channel_id,
|
channel_id: req.channel_id.clone(),
|
||||||
"user_id": sess.user_id,
|
user_id: sess.user_id.clone(),
|
||||||
"last_read_message_id": req.last_read_message_id,
|
last_read_message_id: req.last_read_message_id,
|
||||||
});
|
};
|
||||||
|
|
||||||
let _ = state.broadcast.send(BroadcastMsg {
|
let _ = state.broadcast.send(BroadcastMsg {
|
||||||
channel_id: Some(req.channel_id),
|
channel_id: Some(req.channel_id),
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
net::state::{BroadcastMsg, State},
|
net::state::{BroadcastMsg, State},
|
||||||
proto::{PacketId, TypingStartPayload, encode_packet, to_payload},
|
proto::{PacketId, TypingBroadcastPayload, TypingStartPayload, encode_packet, to_payload},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_typing_start(session_id: &str, payload: &[u8], state: &State) -> Result<()> {
|
pub async fn handle_typing_start(session_id: &str, payload: &[u8], state: &State) -> Result<()> {
|
||||||
let req: TypingStartPayload = serde_json::from_slice(payload)?;
|
let req = TypingStartPayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = serde_json::json!({
|
let data = TypingBroadcastPayload {
|
||||||
"user_id": sess.user_id,
|
user_id: sess.user_id.clone(),
|
||||||
"nickname": sess.nickname,
|
nickname: sess.nickname.clone(),
|
||||||
"channel_id": req.channel_id,
|
channel_id: req.channel_id.clone(),
|
||||||
});
|
};
|
||||||
|
|
||||||
let _ = state.broadcast.send(BroadcastMsg {
|
let _ = state.broadcast.send(BroadcastMsg {
|
||||||
channel_id: Some(req.channel_id),
|
channel_id: Some(req.channel_id),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -15,7 +16,7 @@ pub async fn handle_dm_history(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: DmHistoryPayload = serde_json::from_slice(payload)?;
|
let req = DmHistoryPayload::decode(payload)?;
|
||||||
|
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ pub async fn handle_dm_message(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let msg: DmMessagePayload = serde_json::from_slice(payload)?;
|
let msg = DmMessagePayload::decode(payload)?;
|
||||||
|
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -15,7 +16,7 @@ pub async fn handle_dm_start(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: DmStartPayload = serde_json::from_slice(payload)?;
|
let req = DmStartPayload::decode(payload)?;
|
||||||
|
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
|
|
@ -95,10 +96,7 @@ pub async fn handle_dm_read_ack(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: serde_json::Value = serde_json::from_slice(payload)?;
|
let req = crate::proto::DmReadAckPayload::decode(payload)?;
|
||||||
let dm_id = req["dm_id"]
|
|
||||||
.as_str()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("missing dm_id"))?;
|
|
||||||
|
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
|
|
@ -106,7 +104,7 @@ pub async fn handle_dm_read_ack(
|
||||||
let my_id = sess.user_id.clone();
|
let my_id = sess.user_id.clone();
|
||||||
drop(sess);
|
drop(sess);
|
||||||
|
|
||||||
state.storage.reset_dm_unread(dm_id, &my_id).await?;
|
state.storage.reset_dm_unread(&req.dm_id, &my_id).await?;
|
||||||
|
|
||||||
io::send_encrypted(stream, PacketId::DmReadAck, seq, b"{}", crypto).await?;
|
io::send_encrypted(stream, PacketId::DmReadAck, seq, b"{}", crypto).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ pub async fn dispatch<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match pid {
|
match pid {
|
||||||
PacketId::Ping => {
|
PacketId::Ping => {
|
||||||
let ping: PingPayload = serde_json::from_slice(payload)?;
|
let ping = PingPayload::decode(payload)?;
|
||||||
io::send_encrypted(
|
io::send_encrypted(
|
||||||
ctx.stream,
|
ctx.stream,
|
||||||
PacketId::Pong,
|
PacketId::Pong,
|
||||||
|
|
@ -34,12 +35,12 @@ pub async fn dispatch<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::JoinChannel => {
|
PacketId::JoinChannel => {
|
||||||
let m: JoinChannelPayload = serde_json::from_slice(payload)?;
|
let m = JoinChannelPayload::decode(payload)?;
|
||||||
channel::join(ctx.stream, ctx.seq, session_id, &m.channel_id, ctx.crypto, ctx.state)
|
channel::join(ctx.stream, ctx.seq, session_id, &m.channel_id, ctx.crypto, ctx.state)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::LeaveChannel => {
|
PacketId::LeaveChannel => {
|
||||||
let m: LeaveChannelPayload = serde_json::from_slice(payload)?;
|
let m = LeaveChannelPayload::decode(payload)?;
|
||||||
channel::leave(ctx.stream, ctx.seq, session_id, &m.channel_id, ctx.crypto, ctx.state)
|
channel::leave(ctx.stream, ctx.seq, session_id, &m.channel_id, ctx.crypto, ctx.state)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +71,7 @@ pub async fn dispatch<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::ChatMessage => {
|
PacketId::ChatMessage => {
|
||||||
let m: ChatMessagePayload = serde_json::from_slice(payload)?;
|
let m = ChatMessagePayload::decode(payload)?;
|
||||||
content::chat::handle(session_id, m, ctx.state).await?;
|
content::chat::handle(session_id, m, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::DmStart => {
|
PacketId::DmStart => {
|
||||||
|
|
@ -382,11 +383,11 @@ pub async fn dispatch<S: AsyncRead + AsyncWrite + Unpin>(
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
PacketId::MessageReactionAdd => {
|
PacketId::MessageReactionAdd => {
|
||||||
let m: ReactionPayload = serde_json::from_slice(payload)?;
|
let m = ReactionPayload::decode(payload)?;
|
||||||
content::reaction::handle_reaction_add(session_id, m, ctx.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::decode(payload)?;
|
||||||
content::reaction::handle_reaction_remove(session_id, m, ctx.state).await?;
|
content::reaction::handle_reaction_remove(session_id, m, ctx.state).await?;
|
||||||
}
|
}
|
||||||
PacketId::MessageEdit => {
|
PacketId::MessageEdit => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
net::io,
|
net::io,
|
||||||
proto::{FriendDeclinePayload, PacketId, SessionCrypto, to_payload},
|
proto::{FriendDeclinePayload, PacketId, SessionCrypto, SimpleResponsePayload, to_payload},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_friend_decline(
|
pub async fn handle_friend_decline(
|
||||||
|
|
@ -15,7 +16,7 @@ pub async fn handle_friend_decline(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &crate::net::state::State,
|
state: &crate::net::state::State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: FriendDeclinePayload = serde_json::from_slice(payload)?;
|
let req = FriendDeclinePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -29,7 +30,7 @@ pub async fn handle_friend_decline(
|
||||||
stream,
|
stream,
|
||||||
PacketId::FriendDecline,
|
PacketId::FriendDecline,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"status": "DECLINED"})),
|
&to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -6,7 +7,7 @@ use crate::{
|
||||||
net::{io, state::State},
|
net::{io, state::State},
|
||||||
proto::{
|
proto::{
|
||||||
BlockListPayload, BlockUserPayload, FriendRemovePayload, PacketId, SessionCrypto,
|
BlockListPayload, BlockUserPayload, FriendRemovePayload, PacketId, SessionCrypto,
|
||||||
UnblockUserPayload, to_payload,
|
SimpleResponsePayload, UnblockUserPayload, to_payload,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ pub async fn handle_friend_remove(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: FriendRemovePayload = serde_json::from_slice(payload)?;
|
let req = FriendRemovePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -32,7 +33,7 @@ pub async fn handle_friend_remove(
|
||||||
stream,
|
stream,
|
||||||
PacketId::FriendRemove,
|
PacketId::FriendRemove,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"removed": true})),
|
&to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -47,7 +48,7 @@ pub async fn handle_block_user(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: BlockUserPayload = serde_json::from_slice(payload)?;
|
let req = BlockUserPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -61,7 +62,7 @@ pub async fn handle_block_user(
|
||||||
stream,
|
stream,
|
||||||
PacketId::BlockUser,
|
PacketId::BlockUser,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"blocked": true})),
|
&to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -76,7 +77,7 @@ pub async fn handle_unblock_user(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: UnblockUserPayload = serde_json::from_slice(payload)?;
|
let req = UnblockUserPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -90,7 +91,7 @@ pub async fn handle_unblock_user(
|
||||||
stream,
|
stream,
|
||||||
PacketId::UnblockUser,
|
PacketId::UnblockUser,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"unblocked": true})),
|
&to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -23,7 +24,7 @@ pub async fn handle_friend_request(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: FriendRequestPayload = serde_json::from_slice(payload)?;
|
let req = FriendRequestPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -92,7 +93,9 @@ pub async fn handle_friend_request(
|
||||||
stream,
|
stream,
|
||||||
PacketId::FriendRequest,
|
PacketId::FriendRequest,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"status": "PENDING", "to_user_id": req.to_user_id})),
|
&to_payload(&FriendRequestPayload {
|
||||||
|
to_user_id: req.to_user_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -107,7 +110,7 @@ pub async fn handle_friend_accept(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: FriendAcceptPayload = serde_json::from_slice(payload)?;
|
let req = FriendAcceptPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -144,7 +147,9 @@ pub async fn handle_friend_accept(
|
||||||
stream,
|
stream,
|
||||||
PacketId::FriendAccept,
|
PacketId::FriendAccept,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"status": "ACCEPTED", "from_user_id": req.from_user_id})),
|
&to_payload(&FriendAcceptPayload {
|
||||||
|
from_user_id: req.from_user_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -19,7 +20,7 @@ pub async fn handle_audit_log_fetch(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildAuditLogFetchPayload = serde_json::from_slice(payload)?;
|
let req = GuildAuditLogFetchPayload::decode(payload)?;
|
||||||
let sess = match crate::domain::session::get(&state.sessions, session_id).await {
|
let sess = match crate::domain::session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ pub async fn handle_guild_create(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildCreatePayload = serde_json::from_slice(payload)?;
|
let req = GuildCreatePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -74,11 +75,7 @@ pub async fn handle_guild_delete(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
#[derive(serde::Deserialize)]
|
let req = crate::proto::GuildDeletePayload::decode(payload)?;
|
||||||
struct Req {
|
|
||||||
guild_id: String,
|
|
||||||
}
|
|
||||||
let req: Req = serde_json::from_slice(payload)?;
|
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -112,11 +109,14 @@ pub async fn handle_guild_delete(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
io::send_encrypted(
|
io::send_encrypted(
|
||||||
stream,
|
stream,
|
||||||
PacketId::GuildDelete,
|
PacketId::GuildDelete,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"guild_id": req.guild_id})),
|
&to_payload(&crate::proto::GuildDeletePayload {
|
||||||
|
guild_id: req.guild_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -20,7 +21,7 @@ pub async fn handle_invite_create(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: InviteCreatePayload = serde_json::from_slice(payload)?;
|
let req = InviteCreatePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -96,7 +97,7 @@ pub async fn handle_invite_accept(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: InviteAcceptPayload = serde_json::from_slice(payload)?;
|
let req = InviteAcceptPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -156,7 +157,11 @@ pub async fn handle_invite_accept(
|
||||||
stream,
|
stream,
|
||||||
PacketId::InviteAccept,
|
PacketId::InviteAccept,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"guild_id": inv.guild_id, "guild_name": inv.guild_name})),
|
&to_payload(&InviteAcceptPayload {
|
||||||
|
code: "".into(),
|
||||||
|
guild_id: inv.guild_id.clone(),
|
||||||
|
guild_name: inv.guild_name.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -171,7 +176,7 @@ pub async fn handle_invite_delete(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: InviteDeletePayload = serde_json::from_slice(payload)?;
|
let req = InviteDeletePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -211,7 +216,10 @@ pub async fn handle_invite_delete(
|
||||||
stream,
|
stream,
|
||||||
PacketId::InviteDelete,
|
PacketId::InviteDelete,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"invite_id": req.invite_id})),
|
&to_payload(&InviteDeletePayload {
|
||||||
|
guild_id: req.guild_id.clone(),
|
||||||
|
invite_id: req.invite_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::session,
|
domain::session,
|
||||||
net::{io, state::State},
|
net::{io, state::State},
|
||||||
proto::{GuildInfo, GuildListPayload, PacketId, SessionCrypto, to_payload},
|
proto::{GuildInfo, GuildListPayload, PacketId, SessionCrypto, UserRoleUpdatePayload, to_payload},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_guild_list(
|
pub async fn handle_guild_list(
|
||||||
|
|
@ -41,9 +41,11 @@ pub async fn handle_guild_list(
|
||||||
stream,
|
stream,
|
||||||
PacketId::UserRoleUpdate,
|
PacketId::UserRoleUpdate,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(
|
&to_payload(&UserRoleUpdatePayload {
|
||||||
&serde_json::json!({"user_id": sess.user_id, "guild_id": g.id, "color": color}),
|
user_id: sess.user_id.clone(),
|
||||||
),
|
guild_id: g.id.clone(),
|
||||||
|
color,
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -20,7 +21,7 @@ pub async fn handle_guild_member_join(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildMemberJoinPayload = serde_json::from_slice(payload)?;
|
let req = GuildMemberJoinPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -44,7 +45,9 @@ pub async fn handle_guild_member_join(
|
||||||
stream,
|
stream,
|
||||||
PacketId::GuildMemberJoin,
|
PacketId::GuildMemberJoin,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"guild_id": req.guild_id, "user_id": sess.user_id})),
|
&to_payload(&crate::proto::GuildMemberJoinPayload {
|
||||||
|
guild_id: req.guild_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -53,9 +56,11 @@ pub async fn handle_guild_member_join(
|
||||||
stream,
|
stream,
|
||||||
PacketId::UserRoleUpdate,
|
PacketId::UserRoleUpdate,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(
|
&to_payload(&crate::proto::UserRoleUpdatePayload {
|
||||||
&serde_json::json!({"user_id": sess.user_id, "guild_id": req.guild_id, "color": color}),
|
user_id: sess.user_id.clone(),
|
||||||
),
|
guild_id: req.guild_id.clone(),
|
||||||
|
color,
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -70,25 +75,28 @@ pub async fn handle_guild_member_leave(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildMemberLeavePayload = serde_json::from_slice(payload)?;
|
let req = GuildMemberLeavePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
||||||
let target = if req.user_id.is_empty() {
|
let target = if req.user_id.is_empty() {
|
||||||
&sess.user_id
|
sess.user_id.clone()
|
||||||
} else {
|
} else {
|
||||||
&req.user_id
|
req.user_id.clone()
|
||||||
};
|
};
|
||||||
state
|
state
|
||||||
.storage
|
.storage
|
||||||
.remove_guild_member(&req.guild_id, target)
|
.remove_guild_member(&req.guild_id, target.as_str())
|
||||||
.await?;
|
.await?;
|
||||||
io::send_encrypted(
|
io::send_encrypted(
|
||||||
stream,
|
stream,
|
||||||
PacketId::GuildMemberLeave,
|
PacketId::GuildMemberLeave,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"guild_id": req.guild_id, "user_id": target})),
|
&to_payload(&GuildMemberLeavePayload {
|
||||||
|
guild_id: req.guild_id.clone(),
|
||||||
|
user_id: target,
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -103,7 +111,7 @@ pub async fn handle_guild_member_kick(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildMemberKickPayload = serde_json::from_slice(payload)?;
|
let req = GuildMemberKickPayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -146,7 +154,10 @@ pub async fn handle_guild_member_kick(
|
||||||
stream,
|
stream,
|
||||||
PacketId::GuildMemberKick,
|
PacketId::GuildMemberKick,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"guild_id": req.guild_id, "user_id": req.user_id})),
|
&to_payload(&GuildMemberKickPayload {
|
||||||
|
guild_id: req.guild_id.clone(),
|
||||||
|
user_id: req.user_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -22,7 +23,7 @@ pub async fn handle_member_list_fetch(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildMemberListFetchPayload = serde_json::from_slice(payload)?;
|
let req = GuildMemberListFetchPayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
@ -72,7 +73,7 @@ pub async fn handle_role_assign(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: RoleAssignPayload = serde_json::from_slice(payload)?;
|
let req = RoleAssignPayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
@ -118,9 +119,11 @@ pub async fn handle_role_assign(
|
||||||
stream,
|
stream,
|
||||||
PacketId::GuildRoleAssign,
|
PacketId::GuildRoleAssign,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(
|
&to_payload(&RoleAssignPayload {
|
||||||
&serde_json::json!({"ok": true, "user_id": req.user_id, "role_id": req.role_id}),
|
guild_id: req.guild_id.clone(),
|
||||||
),
|
user_id: req.user_id.clone(),
|
||||||
|
role_id: req.role_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -136,7 +139,7 @@ pub async fn handle_role_unassign(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: RoleAssignPayload = serde_json::from_slice(payload)?;
|
let req = RoleAssignPayload::decode(payload)?;
|
||||||
let sess = match session::get(&state.sessions, session_id).await {
|
let sess = match session::get(&state.sessions, session_id).await {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
|
@ -181,9 +184,11 @@ pub async fn handle_role_unassign(
|
||||||
stream,
|
stream,
|
||||||
PacketId::GuildRoleUnassign,
|
PacketId::GuildRoleUnassign,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(
|
&to_payload(&RoleAssignPayload {
|
||||||
&serde_json::json!({"ok": true, "user_id": req.user_id, "role_id": req.role_id}),
|
guild_id: req.guild_id.clone(),
|
||||||
),
|
user_id: req.user_id.clone(),
|
||||||
|
role_id: req.role_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -199,7 +204,7 @@ pub async fn handle_role_list_fetch(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: GuildRoleListFetchPayload = serde_json::from_slice(payload)?;
|
let req = GuildRoleListFetchPayload::decode(payload)?;
|
||||||
let _sess = session::get(&state.sessions, session_id).await;
|
let _sess = session::get(&state.sessions, session_id).await;
|
||||||
|
|
||||||
let rows = state.storage.list_guild_roles(&req.guild_id).await?;
|
let rows = state.storage.list_guild_roles(&req.guild_id).await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -17,7 +18,7 @@ pub async fn handle_role_create(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: RoleCreatePayload = serde_json::from_slice(payload)?;
|
let req = RoleCreatePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -63,9 +64,13 @@ pub async fn handle_role_create(
|
||||||
stream,
|
stream,
|
||||||
PacketId::RoleCreate,
|
PacketId::RoleCreate,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(
|
&to_payload(&RoleCreatePayload {
|
||||||
&serde_json::json!({"id": role_id, "guild_id": req.guild_id, "name": req.name}),
|
guild_id: req.guild_id.clone(),
|
||||||
),
|
name: req.name.clone(),
|
||||||
|
color: req.color.clone(),
|
||||||
|
permissions: req.permissions,
|
||||||
|
id: role_id,
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -80,7 +85,7 @@ pub async fn handle_role_delete(
|
||||||
crypto: &SessionCrypto,
|
crypto: &SessionCrypto,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let req: RoleDeletePayload = serde_json::from_slice(payload)?;
|
let req = RoleDeletePayload::decode(payload)?;
|
||||||
let sess = session::get(&state.sessions, session_id)
|
let sess = session::get(&state.sessions, session_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
||||||
|
|
@ -120,7 +125,10 @@ pub async fn handle_role_delete(
|
||||||
stream,
|
stream,
|
||||||
PacketId::RoleDelete,
|
PacketId::RoleDelete,
|
||||||
seq,
|
seq,
|
||||||
&to_payload(&serde_json::json!({"role_id": req.role_id})),
|
&to_payload(&RoleDeletePayload {
|
||||||
|
guild_id: req.guild_id.clone(),
|
||||||
|
role_id: req.role_id.clone(),
|
||||||
|
}),
|
||||||
crypto,
|
crypto,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use prost::Message;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
|
|
@ -23,17 +24,17 @@ pub async fn run(
|
||||||
) -> Result<(session::Session, SessionCrypto)> {
|
) -> Result<(session::Session, SessionCrypto)> {
|
||||||
// Generate ephemeral X25519 keypair for forward secrecy
|
// Generate ephemeral X25519 keypair for forward secrecy
|
||||||
let (eph_sk, eph_pk) = SessionCrypto::new_ephemeral();
|
let (eph_sk, eph_pk) = SessionCrypto::new_ephemeral();
|
||||||
let eph_pk_hex = hex::encode(eph_pk.as_bytes());
|
let eph_pk_raw = eph_pk.as_bytes();
|
||||||
|
|
||||||
// HELLO — include server's ephemeral public key + privacy mode
|
// HELLO — include server's ephemeral public key + privacy mode
|
||||||
let challenge = auth::new_challenge();
|
let challenge = auth::new_challenge();
|
||||||
let private_mode = state.config.is_private();
|
let private_mode = state.config.is_private();
|
||||||
let hello = HelloPayload {
|
let hello = HelloPayload {
|
||||||
lnex_version: LNEX_VERSION.into(),
|
lnex_version: LNEX_VERSION.into(),
|
||||||
server_pubkey: state.server_identity.pubkey_hex(),
|
server_pubkey: hex::decode(state.server_identity.pubkey_hex())?,
|
||||||
challenge_nonce: hex::encode(challenge),
|
challenge_nonce: challenge.to_vec(),
|
||||||
node_name: state.config.node.name.clone(),
|
node_name: state.config.node.name.clone(),
|
||||||
server_eph_pubkey: eph_pk_hex,
|
server_eph_pubkey: eph_pk_raw.to_vec(),
|
||||||
private_mode,
|
private_mode,
|
||||||
};
|
};
|
||||||
io::send_packet(stream, PacketId::Hello, seq, &to_payload(&hello)).await?;
|
io::send_packet(stream, PacketId::Hello, seq, &to_payload(&hello)).await?;
|
||||||
|
|
@ -52,7 +53,7 @@ pub async fn run(
|
||||||
return Err(anyhow::anyhow!("expected AUTH"));
|
return Err(anyhow::anyhow!("expected AUTH"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg: AuthPayload = serde_json::from_slice(&payload)?;
|
let msg = AuthPayload::decode(payload.as_slice())?;
|
||||||
debug!("{addr} → AUTH nick={}", msg.nickname);
|
debug!("{addr} → AUTH nick={}", msg.nickname);
|
||||||
|
|
||||||
if msg.lnex_version != LNEX_VERSION {
|
if msg.lnex_version != LNEX_VERSION {
|
||||||
|
|
@ -66,9 +67,8 @@ pub async fn run(
|
||||||
return Err(anyhow::anyhow!("version mismatch"));
|
return Err(anyhow::anyhow!("version mismatch"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pubkey =
|
let pubkey: [u8; 32] = msg.client_pubkey.as_slice().try_into()?;
|
||||||
hex_to_32(&msg.client_pubkey).ok_or_else(|| anyhow::anyhow!("bad client pubkey"))?;
|
let sig: [u8; 64] = msg.signature.as_slice().try_into()?;
|
||||||
let sig = hex_to_64(&msg.signature).ok_or_else(|| anyhow::anyhow!("bad signature"))?;
|
|
||||||
|
|
||||||
if let Err(e) = auth::verify_auth(&challenge, &pubkey, &sig) {
|
if let Err(e) = auth::verify_auth(&challenge, &pubkey, &sig) {
|
||||||
warn!("{addr} auth failed: {e}");
|
warn!("{addr} auth failed: {e}");
|
||||||
|
|
@ -83,7 +83,7 @@ pub async fn run(
|
||||||
return Err(anyhow::anyhow!("auth failed"));
|
return Err(anyhow::anyhow!("auth failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.storage.is_banned(&msg.client_pubkey).await? {
|
if state.storage.is_banned(&hex::encode(&msg.client_pubkey)).await? {
|
||||||
state.metrics.inc(&state.metrics.auth_failures);
|
state.metrics.inc(&state.metrics.auth_failures);
|
||||||
io::send_error(stream, seq, proto::ErrorCode::AuthFailed, "banned").await?;
|
io::send_error(stream, seq, proto::ErrorCode::AuthFailed, "banned").await?;
|
||||||
return Err(anyhow::anyhow!("banned"));
|
return Err(anyhow::anyhow!("banned"));
|
||||||
|
|
@ -91,19 +91,18 @@ pub async fn run(
|
||||||
|
|
||||||
state
|
state
|
||||||
.storage
|
.storage
|
||||||
.upsert_user(&msg.client_pubkey, &msg.nickname)
|
.upsert_user(&hex::encode(&msg.client_pubkey), &msg.nickname)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// ECDH: compute shared secret from server's ephemeral sk + client's ephemeral pk
|
// ECDH: compute shared secret from server's ephemeral sk + client's ephemeral pk
|
||||||
let client_eph_raw = hex_to_32(&msg.client_eph_pubkey)
|
let client_eph_raw: [u8; 32] = msg.client_eph_pubkey.as_slice().try_into()?;
|
||||||
.ok_or_else(|| anyhow::anyhow!("bad client eph pubkey"))?;
|
|
||||||
let client_eph_pk = x25519_dalek::PublicKey::from(client_eph_raw);
|
let client_eph_pk = x25519_dalek::PublicKey::from(client_eph_raw);
|
||||||
let shared_secret = SessionCrypto::ecdh(eph_sk, &client_eph_pk);
|
let shared_secret = SessionCrypto::ecdh(eph_sk, &client_eph_pk);
|
||||||
|
|
||||||
// SESSION
|
// SESSION
|
||||||
let sess = session::create(
|
let sess = session::create(
|
||||||
&state.sessions,
|
&state.sessions,
|
||||||
msg.client_pubkey.clone(),
|
hex::encode(&msg.client_pubkey),
|
||||||
msg.nickname.clone(),
|
msg.nickname.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -131,10 +130,3 @@ pub async fn run(
|
||||||
|
|
||||||
Ok((sess, crypto))
|
Ok((sess, crypto))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hex_to_32(s: &str) -> Option<[u8; 32]> {
|
|
||||||
hex::decode(s).ok()?.try_into().ok()
|
|
||||||
}
|
|
||||||
fn hex_to_64(s: &str) -> Option<[u8; 64]> {
|
|
||||||
hex::decode(s).ok()?.try_into().ok()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use chacha20poly1305::{
|
use chacha20poly1305::{
|
||||||
ChaCha20Poly1305, Key, KeyInit, Nonce,
|
|
||||||
aead::{Aead, Payload},
|
aead::{Aead, Payload},
|
||||||
|
ChaCha20Poly1305, Key, KeyInit, Nonce,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn make_nonce(cid: &[u8; 8], seq: u64) -> [u8; 12] {
|
pub(super) fn make_nonce(cid: &[u8; 8], seq: u64) -> [u8; 12] {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::packet::{PacketHeader, PacketId};
|
use super::packet::{PacketHeader, PacketId};
|
||||||
use serde::Serialize;
|
use prost::Message;
|
||||||
|
|
||||||
/// Encode a packet: header + JSON payload bytes.
|
/// Encode a packet: header + protobuf payload bytes.
|
||||||
pub fn encode_packet(id: PacketId, seq: u32, payload: &[u8]) -> Vec<u8> {
|
pub fn encode_packet(id: PacketId, seq: u32, payload: &[u8]) -> Vec<u8> {
|
||||||
let header = PacketHeader::new(id, seq, payload.len() as u32);
|
let header = PacketHeader::new(id, seq, payload.len() as u32);
|
||||||
let mut out = Vec::with_capacity(PacketHeader::SIZE + payload.len());
|
let mut out = Vec::with_capacity(PacketHeader::SIZE + payload.len());
|
||||||
|
|
@ -10,7 +10,7 @@ pub fn encode_packet(id: PacketId, seq: u32, payload: &[u8]) -> Vec<u8> {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize a payload struct to JSON bytes.
|
/// Encode a protobuf payload struct to bytes.
|
||||||
pub fn to_payload<T: Serialize>(v: &T) -> Vec<u8> {
|
pub fn to_payload(msg: &impl Message) -> Vec<u8> {
|
||||||
serde_json::to_vec(v).expect("payload serialization is infallible")
|
msg.encode_to_vec()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,104 @@
|
||||||
mod crypto;
|
mod crypto;
|
||||||
mod framing;
|
mod framing;
|
||||||
mod packet;
|
mod packet;
|
||||||
mod payloads;
|
|
||||||
|
pub mod lnex {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/lnex.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use lnex::*;
|
||||||
|
|
||||||
pub use crypto::SessionCrypto;
|
pub use crypto::SessionCrypto;
|
||||||
|
|
||||||
// Re-export everything so `crate::proto::X` works as before
|
// Re-export everything so `crate::proto::X` works as before
|
||||||
pub use framing::{encode_packet, to_payload};
|
pub use framing::{encode_packet, to_payload};
|
||||||
pub use packet::{ErrorCode, PacketHeader, PacketId, flags};
|
pub use packet::{flags, ErrorCode, PacketHeader, PacketId};
|
||||||
pub use payloads::*;
|
|
||||||
|
// ─── Backward-compat type aliases ────────────────────────────────
|
||||||
|
// Handshake
|
||||||
|
pub type HelloPayload = Hello;
|
||||||
|
pub type AuthPayload = Auth;
|
||||||
|
pub type SessionPayload = Session;
|
||||||
|
|
||||||
|
// Keepalive
|
||||||
|
pub type PingPayload = Ping;
|
||||||
|
pub type PongPayload = Pong;
|
||||||
|
|
||||||
|
// Error / Disconnect
|
||||||
|
pub type ErrorPayload = Error;
|
||||||
|
pub type DisconnectPayload = Disconnect;
|
||||||
|
|
||||||
|
// Channels
|
||||||
|
pub type JoinChannelPayload = JoinChannel;
|
||||||
|
pub type LeaveChannelPayload = LeaveChannel;
|
||||||
|
pub type ChannelStatePayload = ChannelState;
|
||||||
|
pub type ChannelCreatePayload = ChannelCreate;
|
||||||
|
pub type ChannelDeletePayload = ChannelDelete;
|
||||||
|
pub type ChannelListPayload = ChannelList;
|
||||||
|
pub type UserJoinPayload = UserJoin;
|
||||||
|
pub type UserLeavePayload = UserLeave;
|
||||||
|
|
||||||
|
// Chat
|
||||||
|
pub type ChatMessagePayload = ChatMessage;
|
||||||
|
pub type ChatHistoryPayload = ChatHistory;
|
||||||
|
pub type MessageEditPayload = MessageEdit;
|
||||||
|
pub type MessageDeletePayload = MessageDelete;
|
||||||
|
|
||||||
|
// Voice
|
||||||
|
pub type VoiceStatePayload = VoiceState;
|
||||||
|
|
||||||
|
// Guilds
|
||||||
|
pub type GuildCreatePayload = GuildCreate;
|
||||||
|
pub type GuildDeletePayload = GuildDelete;
|
||||||
|
pub type GuildListPayload = GuildList;
|
||||||
|
pub type GuildMemberJoinPayload = GuildMemberJoin;
|
||||||
|
pub type GuildMemberLeavePayload = GuildMemberLeave;
|
||||||
|
pub type GuildMemberKickPayload = GuildMemberKick;
|
||||||
|
pub type RoleCreatePayload = RoleCreate;
|
||||||
|
pub type RoleDeletePayload = RoleDelete;
|
||||||
|
pub type InviteCreatePayload = InviteCreate;
|
||||||
|
pub type InviteAcceptPayload = InviteAccept;
|
||||||
|
pub type InviteDeletePayload = InviteDelete;
|
||||||
|
pub type GuildAuditLogFetchPayload = GuildAuditLogFetch;
|
||||||
|
pub type AuditLogEntryPayload = AuditLogEntry;
|
||||||
|
pub type GuildAuditLogPayload = GuildAuditLog;
|
||||||
|
pub type GuildMemberListFetchPayload = GuildMemberListFetch;
|
||||||
|
pub type GuildMemberInfoPayload = GuildMemberInfo;
|
||||||
|
pub type GuildMemberListPayload = GuildMemberList;
|
||||||
|
pub type RoleAssignPayload = RoleAssign;
|
||||||
|
pub type GuildRoleListFetchPayload = GuildRoleListFetch;
|
||||||
|
pub type GuildRoleInfoPayload = GuildRoleInfo;
|
||||||
|
pub type GuildRoleListPayload = GuildRoleList;
|
||||||
|
|
||||||
|
// DMs
|
||||||
|
pub type DmStartPayload = DmStart;
|
||||||
|
pub type DmStartResponsePayload = DmStartResponse;
|
||||||
|
pub type DmMessagePayload = DmMessage;
|
||||||
|
pub type DmHistoryPayload = DmHistory;
|
||||||
|
pub type DmReadAckPayload = DmReadAck;
|
||||||
|
|
||||||
|
// Friends
|
||||||
|
pub type FriendRequestPayload = FriendRequest;
|
||||||
|
pub type FriendAcceptPayload = FriendAccept;
|
||||||
|
pub type FriendDeclinePayload = FriendDecline;
|
||||||
|
pub type FriendRemovePayload = FriendRemove;
|
||||||
|
pub type FriendListPayload = FriendList;
|
||||||
|
pub type BlockUserPayload = BlockUser;
|
||||||
|
pub type UnblockUserPayload = UnblockUser;
|
||||||
|
pub type BlockListPayload = BlockList;
|
||||||
|
pub type FriendEventPayload = FriendEvent;
|
||||||
|
|
||||||
|
// Presence
|
||||||
|
pub type PresenceUpdatePayload = PresenceUpdate;
|
||||||
|
pub type PresenceSyncPayload = PresenceSync;
|
||||||
|
pub type PresenceEventPayload = PresenceEvent;
|
||||||
|
|
||||||
|
// Read/typing
|
||||||
|
pub type ReadReceiptPayload = ReadReceipt;
|
||||||
|
pub type TypingStartPayload = TypingStart;
|
||||||
|
|
||||||
|
// Response types
|
||||||
|
pub type ReadReceiptBroadcastPayload = ReadReceiptBroadcast;
|
||||||
|
pub type UserRoleUpdatePayload = UserRoleUpdate;
|
||||||
|
pub type TypingBroadcastPayload = TypingBroadcast;
|
||||||
|
pub type SimpleResponsePayload = SimpleResponse;
|
||||||
|
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct HelloPayload {
|
|
||||||
pub lnex_version: String,
|
|
||||||
pub server_pubkey: String,
|
|
||||||
pub challenge_nonce: String,
|
|
||||||
pub node_name: String,
|
|
||||||
pub server_eph_pubkey: String,
|
|
||||||
pub private_mode: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct AuthPayload {
|
|
||||||
pub client_pubkey: String,
|
|
||||||
pub nickname: String,
|
|
||||||
pub lnex_version: String,
|
|
||||||
pub signature: String,
|
|
||||||
pub client_eph_pubkey: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct SessionPayload {
|
|
||||||
pub session_id: String,
|
|
||||||
pub token: String,
|
|
||||||
pub expires_at: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct PingPayload {
|
|
||||||
pub timestamp: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct PongPayload {
|
|
||||||
pub timestamp: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct JoinChannelPayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct LeaveChannelPayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ChannelStatePayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub channel_name: String,
|
|
||||||
pub kind: String,
|
|
||||||
pub members: Vec<MemberInfo>,
|
|
||||||
pub voice_endpoint: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ChannelCreatePayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub channel_name: String,
|
|
||||||
/// "text" or "voice".
|
|
||||||
pub kind: String,
|
|
||||||
/// Optional guild_id this channel belongs to (Phase 1.x — unused, future).
|
|
||||||
#[serde(default)]
|
|
||||||
pub guild_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ChannelDeletePayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ChannelListPayload {
|
|
||||||
pub channels: Vec<ChannelListItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ChannelListItem {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub channel_name: String,
|
|
||||||
pub kind: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MemberInfo {
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: String,
|
|
||||||
pub in_voice: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UserJoinPayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UserLeavePayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ChatMessagePayload {
|
|
||||||
pub message_id: String,
|
|
||||||
pub channel_id: String,
|
|
||||||
pub sender_id: String,
|
|
||||||
pub content: String,
|
|
||||||
pub timestamp: i64,
|
|
||||||
#[serde(default)]
|
|
||||||
pub edited: bool,
|
|
||||||
/// Optional message_id this message is replying to.
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub reply_to: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ReactionPayload {
|
|
||||||
pub message_id: String,
|
|
||||||
pub channel_id: String,
|
|
||||||
pub emoji: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct MessageEditPayload {
|
|
||||||
pub message_id: String,
|
|
||||||
pub channel_id: String,
|
|
||||||
pub content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct MessageDeletePayload {
|
|
||||||
pub message_id: String,
|
|
||||||
pub channel_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ChatHistoryPayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub messages: Vec<ChatMessagePayload>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ErrorPayload {
|
|
||||||
pub code: u32,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct DisconnectPayload {
|
|
||||||
pub reason: String,
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildCreatePayload {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct GuildInfo {
|
|
||||||
pub id: String,
|
|
||||||
pub owner_id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub member_count: i64,
|
|
||||||
pub created_at: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildListPayload {
|
|
||||||
pub guilds: Vec<GuildInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildMemberJoinPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildMemberLeavePayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildMemberKickPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct RoleCreatePayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub color: Option<String>,
|
|
||||||
pub permissions: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct RoleDeletePayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub role_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct InviteCreatePayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub max_uses: Option<i64>,
|
|
||||||
pub expires_in_seconds: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct InviteInfo {
|
|
||||||
pub id: String,
|
|
||||||
pub guild_id: String,
|
|
||||||
pub guild_name: String,
|
|
||||||
pub code: String,
|
|
||||||
pub creator_id: String,
|
|
||||||
pub max_uses: Option<i64>,
|
|
||||||
pub uses: i64,
|
|
||||||
pub expires_at: Option<i64>,
|
|
||||||
pub created_at: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct InviteAcceptPayload {
|
|
||||||
pub code: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct InviteDeletePayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub invite_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildAuditLogFetchPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
#[serde(default = "default_audit_limit")]
|
|
||||||
pub limit: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_audit_limit() -> i64 {
|
|
||||||
50
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct AuditLogEntryPayload {
|
|
||||||
pub id: String,
|
|
||||||
pub guild_id: String,
|
|
||||||
pub actor_id: String,
|
|
||||||
pub action: String,
|
|
||||||
pub target_id: Option<String>,
|
|
||||||
pub target_type: Option<String>,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
pub created_at: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildAuditLogPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub entries: Vec<AuditLogEntryPayload>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildMemberListFetchPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct GuildMemberInfoPayload {
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: String,
|
|
||||||
pub joined_at: i64,
|
|
||||||
pub role_color: String,
|
|
||||||
pub role_name: String,
|
|
||||||
/// True if this user is the guild owner.
|
|
||||||
pub is_owner: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildMemberListPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub members: Vec<GuildMemberInfoPayload>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct RoleAssignPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub user_id: String,
|
|
||||||
pub role_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildRoleListFetchPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct GuildRoleInfoPayload {
|
|
||||||
pub id: String,
|
|
||||||
pub guild_id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub color: String,
|
|
||||||
pub permissions: u64,
|
|
||||||
pub position: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GuildRoleListPayload {
|
|
||||||
pub guild_id: String,
|
|
||||||
pub roles: Vec<GuildRoleInfoPayload>,
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mod channel;
|
|
||||||
mod guild;
|
|
||||||
mod social;
|
|
||||||
|
|
||||||
pub use channel::*;
|
|
||||||
pub use guild::*;
|
|
||||||
pub use social::*;
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct DmStartPayload {
|
|
||||||
pub target_user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct DmStartResponsePayload {
|
|
||||||
pub dm_id: String,
|
|
||||||
pub other_user_id: String,
|
|
||||||
pub other_nickname: String,
|
|
||||||
pub messages: Vec<DmMessagePayload>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub unread_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct DmMessagePayload {
|
|
||||||
pub dm_id: String,
|
|
||||||
pub sender_id: String,
|
|
||||||
pub content: String,
|
|
||||||
pub timestamp: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct DmHistoryPayload {
|
|
||||||
pub dm_id: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub messages: Vec<DmMessagePayload>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub search_query: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub limit: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct FriendRequestPayload {
|
|
||||||
pub to_user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct FriendAcceptPayload {
|
|
||||||
pub from_user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct FriendDeclinePayload {
|
|
||||||
pub from_user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct FriendRemovePayload {
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct FriendInfo {
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: String,
|
|
||||||
pub status: String,
|
|
||||||
pub since: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct FriendListPayload {
|
|
||||||
pub friends: Vec<FriendInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct BlockUserPayload {
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UnblockUserPayload {
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct BlockListPayload {
|
|
||||||
pub blocked: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct FriendEventPayload {
|
|
||||||
pub event: String,
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct PresenceUpdatePayload {
|
|
||||||
pub status: String,
|
|
||||||
pub activity_type: Option<String>,
|
|
||||||
pub activity_text: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct PresenceInfo {
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: String,
|
|
||||||
pub status: String,
|
|
||||||
pub activity_type: Option<String>,
|
|
||||||
pub activity_text: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct PresenceSyncPayload {
|
|
||||||
pub presences: Vec<PresenceInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct PresenceEventPayload {
|
|
||||||
pub user_id: String,
|
|
||||||
pub nickname: String,
|
|
||||||
pub status: String,
|
|
||||||
pub activity_type: Option<String>,
|
|
||||||
pub activity_text: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ReadReceiptPayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
pub last_read_message_id: String,
|
|
||||||
pub user_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct TypingStartPayload {
|
|
||||||
pub channel_id: String,
|
|
||||||
}
|
|
||||||
|
|
@ -3,129 +3,359 @@ package lnex;
|
||||||
|
|
||||||
// ─── HANDSHAKE ──────────────────────────────────────────────────────────────
|
// ─── HANDSHAKE ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Server → Client on TCP connect (packet_id = 0x0001)
|
|
||||||
message Hello {
|
message Hello {
|
||||||
string lnex_version = 1; // "v1"
|
string lnex_version = 1;
|
||||||
bytes server_pubkey = 2; // 32-byte Ed25519 public key
|
bytes server_pubkey = 2;
|
||||||
bytes challenge_nonce = 3; // 32-byte random challenge
|
bytes challenge_nonce = 3;
|
||||||
string node_name = 4;
|
string node_name = 4;
|
||||||
|
bytes server_eph_pubkey = 5;
|
||||||
|
bool private_mode = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client → Server (packet_id = 0x0002)
|
|
||||||
message Auth {
|
message Auth {
|
||||||
bytes client_pubkey = 1; // 32-byte Ed25519 public key
|
bytes client_pubkey = 1;
|
||||||
string nickname = 2;
|
string nickname = 2;
|
||||||
string lnex_version = 3;
|
string lnex_version = 3;
|
||||||
bytes signature = 4; // Ed25519 sig over challenge_nonce
|
bytes signature = 4;
|
||||||
|
bytes client_eph_pubkey = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server → Client on success (packet_id = 0x0003)
|
|
||||||
message Session {
|
message Session {
|
||||||
string session_id = 1;
|
string session_id = 1;
|
||||||
string token = 2;
|
string token = 2;
|
||||||
int64 expires_at = 3; // Unix ms, 0 = no expiry
|
int64 expires_at = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── KEEPALIVE ──────────────────────────────────────────────────────────────
|
// ─── KEEPALIVE ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Either direction (0x0004 / 0x0005)
|
message Ping { int64 timestamp = 1; }
|
||||||
message Ping {
|
message Pong { int64 timestamp = 1; }
|
||||||
int64 timestamp = 1; // Unix ms
|
|
||||||
}
|
|
||||||
|
|
||||||
message Pong {
|
// ─── ERROR / DISCONNECT ─────────────────────────────────────────────────────
|
||||||
int64 timestamp = 1; // Echo of Ping.timestamp
|
|
||||||
}
|
message Error { uint32 code = 1; string message = 2; }
|
||||||
|
message Disconnect { string reason = 1; }
|
||||||
|
|
||||||
// ─── CHANNELS ───────────────────────────────────────────────────────────────
|
// ─── CHANNELS ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Client → Gateway (0x0030)
|
message JoinChannel { string channel_id = 1; }
|
||||||
message JoinChannel {
|
message LeaveChannel { string channel_id = 1; }
|
||||||
string channel_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client → Gateway (0x0031)
|
|
||||||
message LeaveChannel {
|
|
||||||
string channel_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gateway → Client (0x0032) — full channel snapshot on join
|
|
||||||
message ChannelState {
|
message ChannelState {
|
||||||
string channel_id = 1;
|
string channel_id = 1;
|
||||||
string channel_name = 2;
|
string channel_name = 2;
|
||||||
ChannelKind kind = 3;
|
string kind = 3;
|
||||||
repeated Member members = 4;
|
repeated MemberInfo members = 4;
|
||||||
string voice_endpoint = 5; // host:port of voice-node UDP
|
string voice_endpoint = 5;
|
||||||
|
}
|
||||||
|
|
||||||
enum ChannelKind {
|
message MemberInfo {
|
||||||
TEXT = 0;
|
|
||||||
VOICE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Member {
|
|
||||||
string user_id = 1;
|
string user_id = 1;
|
||||||
string nickname = 2;
|
string nickname = 2;
|
||||||
bool in_voice = 3;
|
bool in_voice = 3;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast → all channel members (0x0040 / 0x0041)
|
message ChannelCreate {
|
||||||
message UserJoin {
|
|
||||||
string channel_id = 1;
|
string channel_id = 1;
|
||||||
string user_id = 2;
|
string channel_name = 2;
|
||||||
string nickname = 3;
|
string kind = 3;
|
||||||
|
optional string guild_id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserLeave {
|
message ChannelDelete { string channel_id = 1; }
|
||||||
|
|
||||||
|
message ChannelList { repeated ChannelListItem channels = 1; }
|
||||||
|
|
||||||
|
message ChannelListItem {
|
||||||
string channel_id = 1;
|
string channel_id = 1;
|
||||||
string user_id = 2;
|
string channel_name = 2;
|
||||||
|
string kind = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── CHAT ───────────────────────────────────────────────────────────────────
|
message UserJoin { string channel_id = 1; string user_id = 2; string nickname = 3; }
|
||||||
|
message UserLeave { string channel_id = 1; string user_id = 2; }
|
||||||
|
|
||||||
|
// ─── CHAT ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Client → Gateway, broadcast to channel (0x0020)
|
|
||||||
message ChatMessage {
|
message ChatMessage {
|
||||||
string message_id = 1; // UUID v4, generated by client
|
string message_id = 1;
|
||||||
string channel_id = 2;
|
string channel_id = 2;
|
||||||
string sender_id = 3; // sender pubkey short form
|
string sender_id = 3;
|
||||||
string content = 4;
|
string content = 4;
|
||||||
int64 timestamp = 5; // Unix ms
|
int64 timestamp = 5;
|
||||||
|
bool edited = 6;
|
||||||
|
optional string reply_to = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gateway → Client on join (0x0021) — recent history
|
|
||||||
message ChatHistory {
|
message ChatHistory {
|
||||||
string channel_id = 1;
|
string channel_id = 1;
|
||||||
repeated ChatMessage messages = 2;
|
repeated ChatMessage messages = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── VOICE STATE ────────────────────────────────────────────────────────────
|
message ReactionPayload {
|
||||||
|
string message_id = 1;
|
||||||
|
string channel_id = 2;
|
||||||
|
string emoji = 3;
|
||||||
|
string user_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MessageEdit {
|
||||||
|
string message_id = 1;
|
||||||
|
string channel_id = 2;
|
||||||
|
string content = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MessageDelete {
|
||||||
|
string message_id = 1;
|
||||||
|
string channel_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── VOICE ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Broadcast on voice state change (0x0011)
|
|
||||||
message VoiceState {
|
message VoiceState {
|
||||||
string user_id = 1;
|
string user_id = 1;
|
||||||
string channel_id = 2;
|
string channel_id = 2;
|
||||||
|
string state = 3;
|
||||||
enum State {
|
|
||||||
SILENT = 0;
|
|
||||||
SPEAKING = 1;
|
|
||||||
MUTED = 2;
|
|
||||||
DEAFENED = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
State state = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── ERRORS ─────────────────────────────────────────────────────────────────
|
// ─── GUILDS ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Any direction (0x00F0)
|
message GuildCreate { string name = 1; }
|
||||||
message Error {
|
|
||||||
uint32 code = 1; // ErrorCode enum value
|
message GuildInfo {
|
||||||
string message = 2;
|
string id = 1;
|
||||||
|
string owner_id = 2;
|
||||||
|
string name = 3;
|
||||||
|
int64 member_count = 4;
|
||||||
|
int64 created_at = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── DISCONNECT ─────────────────────────────────────────────────────────────
|
message GuildDelete { string guild_id = 1; }
|
||||||
|
message GuildList { repeated GuildInfo guilds = 1; }
|
||||||
|
|
||||||
// Any direction (0x00FF) — graceful close
|
message GuildMemberJoin { string guild_id = 1; }
|
||||||
message Disconnect {
|
message GuildMemberLeave { string guild_id = 1; string user_id = 2; }
|
||||||
string reason = 1; // optional human-readable reason
|
message GuildMemberKick { string guild_id = 1; string user_id = 2; }
|
||||||
|
|
||||||
|
message RoleCreate {
|
||||||
|
string guild_id = 1;
|
||||||
|
string name = 2;
|
||||||
|
optional string color = 3;
|
||||||
|
optional uint64 permissions = 4;
|
||||||
|
string id = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoleDelete {
|
||||||
|
string guild_id = 1;
|
||||||
|
string role_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InviteCreate {
|
||||||
|
string guild_id = 1;
|
||||||
|
optional int64 max_uses = 2;
|
||||||
|
optional int64 expires_in_seconds = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InviteInfo {
|
||||||
|
string id = 1;
|
||||||
|
string guild_id = 2;
|
||||||
|
string guild_name = 3;
|
||||||
|
string code = 4;
|
||||||
|
string creator_id = 5;
|
||||||
|
optional int64 max_uses = 6;
|
||||||
|
int64 uses = 7;
|
||||||
|
optional int64 expires_at = 8;
|
||||||
|
int64 created_at = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InviteAccept {
|
||||||
|
string code = 1;
|
||||||
|
string guild_id = 2;
|
||||||
|
string guild_name = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InviteDelete {
|
||||||
|
string guild_id = 1;
|
||||||
|
string invite_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GuildAuditLogFetch {
|
||||||
|
string guild_id = 1;
|
||||||
|
int64 limit = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuditLogEntry {
|
||||||
|
string id = 1;
|
||||||
|
string guild_id = 2;
|
||||||
|
string actor_id = 3;
|
||||||
|
string action = 4;
|
||||||
|
optional string target_id = 5;
|
||||||
|
optional string target_type = 6;
|
||||||
|
optional string reason = 7;
|
||||||
|
int64 created_at = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GuildAuditLog {
|
||||||
|
string guild_id = 1;
|
||||||
|
repeated AuditLogEntry entries = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GuildMemberListFetch { string guild_id = 1; }
|
||||||
|
|
||||||
|
message GuildMemberInfo {
|
||||||
|
string user_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
int64 joined_at = 3;
|
||||||
|
string role_color = 4;
|
||||||
|
string role_name = 5;
|
||||||
|
bool is_owner = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GuildMemberList {
|
||||||
|
string guild_id = 1;
|
||||||
|
repeated GuildMemberInfo members = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoleAssign {
|
||||||
|
string guild_id = 1;
|
||||||
|
string user_id = 2;
|
||||||
|
string role_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GuildRoleListFetch { string guild_id = 1; }
|
||||||
|
|
||||||
|
message GuildRoleInfo {
|
||||||
|
string id = 1;
|
||||||
|
string guild_id = 2;
|
||||||
|
string name = 3;
|
||||||
|
string color = 4;
|
||||||
|
uint64 permissions = 5;
|
||||||
|
int32 position = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GuildRoleList {
|
||||||
|
string guild_id = 1;
|
||||||
|
repeated GuildRoleInfo roles = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── DIRECT MESSAGES ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
message DmStart {
|
||||||
|
string target_user_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DmStartResponse {
|
||||||
|
string dm_id = 1;
|
||||||
|
string other_user_id = 2;
|
||||||
|
string other_nickname = 3;
|
||||||
|
repeated DmMessage messages = 4;
|
||||||
|
uint32 unread_count = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DmMessage {
|
||||||
|
string dm_id = 1;
|
||||||
|
string sender_id = 2;
|
||||||
|
string content = 3;
|
||||||
|
int64 timestamp = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DmHistory {
|
||||||
|
string dm_id = 1;
|
||||||
|
repeated DmMessage messages = 2;
|
||||||
|
optional string search_query = 3;
|
||||||
|
optional int64 limit = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DmReadAck {
|
||||||
|
string dm_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── FRIENDS ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
message FriendRequest { string to_user_id = 1; }
|
||||||
|
message FriendAccept { string from_user_id = 1; }
|
||||||
|
message FriendDecline { string from_user_id = 1; }
|
||||||
|
message FriendRemove { string user_id = 1; }
|
||||||
|
|
||||||
|
message FriendInfo {
|
||||||
|
string user_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
string status = 3;
|
||||||
|
int64 since = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FriendList { repeated FriendInfo friends = 1; }
|
||||||
|
|
||||||
|
message BlockUser { string user_id = 1; }
|
||||||
|
message UnblockUser { string user_id = 1; }
|
||||||
|
message BlockList { repeated string blocked = 1; }
|
||||||
|
|
||||||
|
message FriendEvent {
|
||||||
|
string event = 1;
|
||||||
|
string user_id = 2;
|
||||||
|
optional string nickname = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── PRESENCE ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
message PresenceUpdate {
|
||||||
|
string status = 1;
|
||||||
|
optional string activity_type = 2;
|
||||||
|
optional string activity_text = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PresenceInfo {
|
||||||
|
string user_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
string status = 3;
|
||||||
|
optional string activity_type = 4;
|
||||||
|
optional string activity_text = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PresenceSync { repeated PresenceInfo presences = 1; }
|
||||||
|
|
||||||
|
message PresenceEvent {
|
||||||
|
string user_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
string status = 3;
|
||||||
|
optional string activity_type = 4;
|
||||||
|
optional string activity_text = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── READ RECEIPT & TYPING ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
message ReadReceipt {
|
||||||
|
string channel_id = 1;
|
||||||
|
string last_read_message_id = 2;
|
||||||
|
string user_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TypingStart {
|
||||||
|
string channel_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── AD-HOC RESPONSE / BROADCAST TYPES ──────────────────────────────────────
|
||||||
|
|
||||||
|
message ReadReceiptBroadcast {
|
||||||
|
string channel_id = 1;
|
||||||
|
string user_id = 2;
|
||||||
|
string last_read_message_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserRoleUpdate {
|
||||||
|
string user_id = 1;
|
||||||
|
string guild_id = 2;
|
||||||
|
string color = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TypingBroadcast {
|
||||||
|
string user_id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
string channel_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SimpleResponse {
|
||||||
|
bool ok = 1;
|
||||||
|
optional string user_id = 2;
|
||||||
|
optional string role_id = 3;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue