diff --git a/Cargo.lock b/Cargo.lock index 9587b4b..eaff923 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -564,6 +564,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -576,6 +582,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flume" version = "0.12.0" @@ -991,6 +1003,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1041,6 +1062,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1110,6 +1137,12 @@ dependencies = [ "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]] name = "nu-ansi-term" version = "0.50.3" @@ -1211,6 +1244,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "pin-project-lite" version = "0.2.17" @@ -1268,6 +1311,16 @@ dependencies = [ "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]] name = "proc-macro2" version = "1.0.106" @@ -1277,6 +1330,58 @@ dependencies = [ "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]] name = "quote" version = "1.0.46" @@ -1396,6 +1501,18 @@ dependencies = [ "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]] name = "regex-automata" version = "0.4.14" @@ -1436,6 +1553,19 @@ dependencies = [ "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]] name = "rustls" version = "0.23.41" @@ -1918,6 +2048,19 @@ dependencies = [ "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]] name = "thiserror" version = "2.0.18" @@ -2305,6 +2448,8 @@ dependencies = [ "ed25519-dalek", "hex", "hkdf", + "prost", + "prost-build", "rand 0.8.6", "rcgen", "rustls", diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index bd464d0..f9f54a4 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -38,9 +38,13 @@ axum = { version = "0.8", features = ["ws"] } tower = "0.5" sqlx = { version = "0.9", features = ["sqlite", "runtime-tokio", "tls-rustls"] } bitflags = "2" +prost = "0.13" # TLS 1.3 support rustls-pki-types = "1" rustls = "0.23" tokio-rustls = "0.26" rcgen = "0.13" + +[build-dependencies] +prost-build = "0.13" diff --git a/gateway/build.rs b/gateway/build.rs new file mode 100644 index 0000000..9bbf13b --- /dev/null +++ b/gateway/build.rs @@ -0,0 +1,3 @@ +fn main() { + prost_build::compile_protos(&["../protocol/lnex.proto"], &["../protocol/"]).unwrap(); +} diff --git a/gateway/src/handler/channel/create.rs b/gateway/src/handler/channel/create.rs index d21ac2a..755f1a9 100644 --- a/gateway/src/handler/channel/create.rs +++ b/gateway/src/handler/channel/create.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::{info, warn}; @@ -24,7 +25,7 @@ pub async fn handle_channel_create( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: ChannelCreatePayload = serde_json::from_slice(payload)?; + let req = ChannelCreatePayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), @@ -178,7 +179,7 @@ pub async fn handle_channel_delete( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: ChannelDeletePayload = serde_json::from_slice(payload)?; + let req = ChannelDeletePayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), diff --git a/gateway/src/handler/content/message_edit.rs b/gateway/src/handler/content/message_edit.rs index 59f293a..462adbd 100644 --- a/gateway/src/handler/content/message_edit.rs +++ b/gateway/src/handler/content/message_edit.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tracing::debug; use crate::{ @@ -8,7 +9,7 @@ use crate::{ }; 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 { Some(s) => s, 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<()> { - let delete: MessageDeletePayload = serde_json::from_slice(payload)?; + let delete = MessageDeletePayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), diff --git a/gateway/src/handler/content/presence.rs b/gateway/src/handler/content/presence.rs index 02a8f1c..4af5c5b 100644 --- a/gateway/src/handler/content/presence.rs +++ b/gateway/src/handler/content/presence.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -21,7 +22,7 @@ pub async fn handle_presence_update( _crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: PresenceUpdatePayload = serde_json::from_slice(payload)?; + let req = PresenceUpdatePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; diff --git a/gateway/src/handler/content/read_receipt.rs b/gateway/src/handler/content/read_receipt.rs index ce59137..69c2210 100644 --- a/gateway/src/handler/content/read_receipt.rs +++ b/gateway/src/handler/content/read_receipt.rs @@ -1,9 +1,10 @@ use anyhow::Result; +use prost::Message; use crate::{ domain::session, 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( @@ -14,7 +15,7 @@ pub async fn handle_read_receipt( _crypto: &crate::proto::SessionCrypto, state: &State, ) -> Result<()> { - let req: ReadReceiptPayload = serde_json::from_slice(payload)?; + let req = ReadReceiptPayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, 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) .await?; - let broadcast_data = serde_json::json!({ - "channel_id": req.channel_id, - "user_id": sess.user_id, - "last_read_message_id": req.last_read_message_id, - }); + let broadcast_data = ReadReceiptBroadcastPayload { + channel_id: req.channel_id.clone(), + user_id: sess.user_id.clone(), + last_read_message_id: req.last_read_message_id, + }; let _ = state.broadcast.send(BroadcastMsg { channel_id: Some(req.channel_id), diff --git a/gateway/src/handler/content/typing.rs b/gateway/src/handler/content/typing.rs index 22d4647..32f3f2f 100644 --- a/gateway/src/handler/content/typing.rs +++ b/gateway/src/handler/content/typing.rs @@ -1,23 +1,24 @@ use anyhow::Result; +use prost::Message; use crate::{ domain::session, 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<()> { - let req: TypingStartPayload = serde_json::from_slice(payload)?; + let req = TypingStartPayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), }; - let data = serde_json::json!({ - "user_id": sess.user_id, - "nickname": sess.nickname, - "channel_id": req.channel_id, - }); + let data = TypingBroadcastPayload { + user_id: sess.user_id.clone(), + nickname: sess.nickname.clone(), + channel_id: req.channel_id.clone(), + }; let _ = state.broadcast.send(BroadcastMsg { channel_id: Some(req.channel_id), diff --git a/gateway/src/handler/direct_message/history.rs b/gateway/src/handler/direct_message/history.rs index cb58191..e34ff50 100644 --- a/gateway/src/handler/direct_message/history.rs +++ b/gateway/src/handler/direct_message/history.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -15,7 +16,7 @@ pub async fn handle_dm_history( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: DmHistoryPayload = serde_json::from_slice(payload)?; + let req = DmHistoryPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await diff --git a/gateway/src/handler/direct_message/send.rs b/gateway/src/handler/direct_message/send.rs index 7f53d2b..2de0bd3 100644 --- a/gateway/src/handler/direct_message/send.rs +++ b/gateway/src/handler/direct_message/send.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::warn; @@ -21,7 +22,7 @@ pub async fn handle_dm_message( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let msg: DmMessagePayload = serde_json::from_slice(payload)?; + let msg = DmMessagePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await diff --git a/gateway/src/handler/direct_message/start.rs b/gateway/src/handler/direct_message/start.rs index 6d54df3..c624b12 100644 --- a/gateway/src/handler/direct_message/start.rs +++ b/gateway/src/handler/direct_message/start.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -15,7 +16,7 @@ pub async fn handle_dm_start( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: DmStartPayload = serde_json::from_slice(payload)?; + let req = DmStartPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await @@ -95,10 +96,7 @@ pub async fn handle_dm_read_ack( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: serde_json::Value = serde_json::from_slice(payload)?; - let dm_id = req["dm_id"] - .as_str() - .ok_or_else(|| anyhow::anyhow!("missing dm_id"))?; + let req = crate::proto::DmReadAckPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await @@ -106,7 +104,7 @@ pub async fn handle_dm_read_ack( let my_id = sess.user_id.clone(); 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?; Ok(()) diff --git a/gateway/src/handler/dispatch.rs b/gateway/src/handler/dispatch.rs index cf5e28e..6ed6100 100644 --- a/gateway/src/handler/dispatch.rs +++ b/gateway/src/handler/dispatch.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::debug; @@ -21,7 +22,7 @@ pub async fn dispatch( ) -> Result<()> { match pid { PacketId::Ping => { - let ping: PingPayload = serde_json::from_slice(payload)?; + let ping = PingPayload::decode(payload)?; io::send_encrypted( ctx.stream, PacketId::Pong, @@ -34,12 +35,12 @@ pub async fn dispatch( .await?; } 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) .await?; } 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) .await?; } @@ -70,7 +71,7 @@ pub async fn dispatch( .await?; } PacketId::ChatMessage => { - let m: ChatMessagePayload = serde_json::from_slice(payload)?; + let m = ChatMessagePayload::decode(payload)?; content::chat::handle(session_id, m, ctx.state).await?; } PacketId::DmStart => { @@ -382,11 +383,11 @@ pub async fn dispatch( .await?; } 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?; } 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?; } PacketId::MessageEdit => { diff --git a/gateway/src/handler/friends/decline.rs b/gateway/src/handler/friends/decline.rs index da10800..b16356e 100644 --- a/gateway/src/handler/friends/decline.rs +++ b/gateway/src/handler/friends/decline.rs @@ -1,10 +1,11 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ domain::session, net::io, - proto::{FriendDeclinePayload, PacketId, SessionCrypto, to_payload}, + proto::{FriendDeclinePayload, PacketId, SessionCrypto, SimpleResponsePayload, to_payload}, }; pub async fn handle_friend_decline( @@ -15,7 +16,7 @@ pub async fn handle_friend_decline( crypto: &SessionCrypto, state: &crate::net::state::State, ) -> Result<()> { - let req: FriendDeclinePayload = serde_json::from_slice(payload)?; + let req = FriendDeclinePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -29,7 +30,7 @@ pub async fn handle_friend_decline( stream, PacketId::FriendDecline, seq, - &to_payload(&serde_json::json!({"status": "DECLINED"})), + &to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }), crypto, ) .await?; diff --git a/gateway/src/handler/friends/manage.rs b/gateway/src/handler/friends/manage.rs index d8d9358..7302502 100644 --- a/gateway/src/handler/friends/manage.rs +++ b/gateway/src/handler/friends/manage.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -6,7 +7,7 @@ use crate::{ net::{io, state::State}, proto::{ BlockListPayload, BlockUserPayload, FriendRemovePayload, PacketId, SessionCrypto, - UnblockUserPayload, to_payload, + SimpleResponsePayload, UnblockUserPayload, to_payload, }, }; @@ -18,7 +19,7 @@ pub async fn handle_friend_remove( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: FriendRemovePayload = serde_json::from_slice(payload)?; + let req = FriendRemovePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -32,7 +33,7 @@ pub async fn handle_friend_remove( stream, PacketId::FriendRemove, seq, - &to_payload(&serde_json::json!({"removed": true})), + &to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }), crypto, ) .await?; @@ -47,7 +48,7 @@ pub async fn handle_block_user( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: BlockUserPayload = serde_json::from_slice(payload)?; + let req = BlockUserPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -61,7 +62,7 @@ pub async fn handle_block_user( stream, PacketId::BlockUser, seq, - &to_payload(&serde_json::json!({"blocked": true})), + &to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }), crypto, ) .await?; @@ -76,7 +77,7 @@ pub async fn handle_unblock_user( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: UnblockUserPayload = serde_json::from_slice(payload)?; + let req = UnblockUserPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -90,7 +91,7 @@ pub async fn handle_unblock_user( stream, PacketId::UnblockUser, seq, - &to_payload(&serde_json::json!({"unblocked": true})), + &to_payload(&SimpleResponsePayload { ok: true, user_id: None, role_id: None }), crypto, ) .await?; diff --git a/gateway/src/handler/friends/requests.rs b/gateway/src/handler/friends/requests.rs index 37e60a1..f89773c 100644 --- a/gateway/src/handler/friends/requests.rs +++ b/gateway/src/handler/friends/requests.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -23,7 +24,7 @@ pub async fn handle_friend_request( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: FriendRequestPayload = serde_json::from_slice(payload)?; + let req = FriendRequestPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -92,7 +93,9 @@ pub async fn handle_friend_request( stream, PacketId::FriendRequest, 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, ) .await?; @@ -107,7 +110,7 @@ pub async fn handle_friend_accept( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: FriendAcceptPayload = serde_json::from_slice(payload)?; + let req = FriendAcceptPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -144,7 +147,9 @@ pub async fn handle_friend_accept( stream, PacketId::FriendAccept, 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, ) .await?; diff --git a/gateway/src/handler/guild/audit.rs b/gateway/src/handler/guild/audit.rs index 1c08d07..80b66a4 100644 --- a/gateway/src/handler/guild/audit.rs +++ b/gateway/src/handler/guild/audit.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -19,7 +20,7 @@ pub async fn handle_audit_log_fetch( crypto: &SessionCrypto, state: &State, ) -> 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 { Some(s) => s, None => return Ok(()), diff --git a/gateway/src/handler/guild/crud.rs b/gateway/src/handler/guild/crud.rs index 8637c72..bb6552d 100644 --- a/gateway/src/handler/guild/crud.rs +++ b/gateway/src/handler/guild/crud.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::debug; @@ -18,7 +19,7 @@ pub async fn handle_guild_create( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: GuildCreatePayload = serde_json::from_slice(payload)?; + let req = GuildCreatePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -74,11 +75,7 @@ pub async fn handle_guild_delete( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - #[derive(serde::Deserialize)] - struct Req { - guild_id: String, - } - let req: Req = serde_json::from_slice(payload)?; + let req = crate::proto::GuildDeletePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -112,11 +109,14 @@ pub async fn handle_guild_delete( None, ) .await?; + io::send_encrypted( stream, PacketId::GuildDelete, seq, - &to_payload(&serde_json::json!({"guild_id": req.guild_id})), + &to_payload(&crate::proto::GuildDeletePayload { + guild_id: req.guild_id.clone(), + }), crypto, ) .await?; diff --git a/gateway/src/handler/guild/invites.rs b/gateway/src/handler/guild/invites.rs index 191c861..98bc480 100644 --- a/gateway/src/handler/guild/invites.rs +++ b/gateway/src/handler/guild/invites.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -20,7 +21,7 @@ pub async fn handle_invite_create( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: InviteCreatePayload = serde_json::from_slice(payload)?; + let req = InviteCreatePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -96,7 +97,7 @@ pub async fn handle_invite_accept( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: InviteAcceptPayload = serde_json::from_slice(payload)?; + let req = InviteAcceptPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -156,7 +157,11 @@ pub async fn handle_invite_accept( stream, PacketId::InviteAccept, 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, ) .await?; @@ -171,7 +176,7 @@ pub async fn handle_invite_delete( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: InviteDeletePayload = serde_json::from_slice(payload)?; + let req = InviteDeletePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -211,7 +216,10 @@ pub async fn handle_invite_delete( stream, PacketId::InviteDelete, 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, ) .await?; diff --git a/gateway/src/handler/guild/list.rs b/gateway/src/handler/guild/list.rs index 21c113a..36b2e6b 100644 --- a/gateway/src/handler/guild/list.rs +++ b/gateway/src/handler/guild/list.rs @@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ domain::session, 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( @@ -41,9 +41,11 @@ pub async fn handle_guild_list( stream, PacketId::UserRoleUpdate, seq, - &to_payload( - &serde_json::json!({"user_id": sess.user_id, "guild_id": g.id, "color": color}), - ), + &to_payload(&UserRoleUpdatePayload { + user_id: sess.user_id.clone(), + guild_id: g.id.clone(), + color, + }), crypto, ) .await?; diff --git a/gateway/src/handler/guild/members.rs b/gateway/src/handler/guild/members.rs index 1a1cd2b..3b6358e 100644 --- a/gateway/src/handler/guild/members.rs +++ b/gateway/src/handler/guild/members.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -20,7 +21,7 @@ pub async fn handle_guild_member_join( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: GuildMemberJoinPayload = serde_json::from_slice(payload)?; + let req = GuildMemberJoinPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -44,7 +45,9 @@ pub async fn handle_guild_member_join( stream, PacketId::GuildMemberJoin, 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, ) .await?; @@ -53,9 +56,11 @@ pub async fn handle_guild_member_join( stream, PacketId::UserRoleUpdate, seq, - &to_payload( - &serde_json::json!({"user_id": sess.user_id, "guild_id": req.guild_id, "color": color}), - ), + &to_payload(&crate::proto::UserRoleUpdatePayload { + user_id: sess.user_id.clone(), + guild_id: req.guild_id.clone(), + color, + }), crypto, ) .await?; @@ -70,25 +75,28 @@ pub async fn handle_guild_member_leave( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: GuildMemberLeavePayload = serde_json::from_slice(payload)?; + let req = GuildMemberLeavePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; let target = if req.user_id.is_empty() { - &sess.user_id + sess.user_id.clone() } else { - &req.user_id + req.user_id.clone() }; state .storage - .remove_guild_member(&req.guild_id, target) + .remove_guild_member(&req.guild_id, target.as_str()) .await?; io::send_encrypted( stream, PacketId::GuildMemberLeave, 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, ) .await?; @@ -103,7 +111,7 @@ pub async fn handle_guild_member_kick( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: GuildMemberKickPayload = serde_json::from_slice(payload)?; + let req = GuildMemberKickPayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -146,7 +154,10 @@ pub async fn handle_guild_member_kick( stream, PacketId::GuildMemberKick, 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, ) .await?; diff --git a/gateway/src/handler/guild/members_list.rs b/gateway/src/handler/guild/members_list.rs index 3cfc8ff..efb9684 100644 --- a/gateway/src/handler/guild/members_list.rs +++ b/gateway/src/handler/guild/members_list.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -22,7 +23,7 @@ pub async fn handle_member_list_fetch( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: GuildMemberListFetchPayload = serde_json::from_slice(payload)?; + let req = GuildMemberListFetchPayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), @@ -72,7 +73,7 @@ pub async fn handle_role_assign( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: RoleAssignPayload = serde_json::from_slice(payload)?; + let req = RoleAssignPayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), @@ -118,9 +119,11 @@ pub async fn handle_role_assign( stream, PacketId::GuildRoleAssign, seq, - &to_payload( - &serde_json::json!({"ok": true, "user_id": req.user_id, "role_id": req.role_id}), - ), + &to_payload(&RoleAssignPayload { + guild_id: req.guild_id.clone(), + user_id: req.user_id.clone(), + role_id: req.role_id.clone(), + }), crypto, ) .await?; @@ -136,7 +139,7 @@ pub async fn handle_role_unassign( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: RoleAssignPayload = serde_json::from_slice(payload)?; + let req = RoleAssignPayload::decode(payload)?; let sess = match session::get(&state.sessions, session_id).await { Some(s) => s, None => return Ok(()), @@ -181,9 +184,11 @@ pub async fn handle_role_unassign( stream, PacketId::GuildRoleUnassign, seq, - &to_payload( - &serde_json::json!({"ok": true, "user_id": req.user_id, "role_id": req.role_id}), - ), + &to_payload(&RoleAssignPayload { + guild_id: req.guild_id.clone(), + user_id: req.user_id.clone(), + role_id: req.role_id.clone(), + }), crypto, ) .await?; @@ -199,7 +204,7 @@ pub async fn handle_role_list_fetch( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: GuildRoleListFetchPayload = serde_json::from_slice(payload)?; + let req = GuildRoleListFetchPayload::decode(payload)?; let _sess = session::get(&state.sessions, session_id).await; let rows = state.storage.list_guild_roles(&req.guild_id).await?; diff --git a/gateway/src/handler/guild/roles.rs b/gateway/src/handler/guild/roles.rs index 19d061f..3111d33 100644 --- a/gateway/src/handler/guild/roles.rs +++ b/gateway/src/handler/guild/roles.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{ @@ -17,7 +18,7 @@ pub async fn handle_role_create( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: RoleCreatePayload = serde_json::from_slice(payload)?; + let req = RoleCreatePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -63,9 +64,13 @@ pub async fn handle_role_create( stream, PacketId::RoleCreate, seq, - &to_payload( - &serde_json::json!({"id": role_id, "guild_id": req.guild_id, "name": req.name}), - ), + &to_payload(&RoleCreatePayload { + guild_id: req.guild_id.clone(), + name: req.name.clone(), + color: req.color.clone(), + permissions: req.permissions, + id: role_id, + }), crypto, ) .await?; @@ -80,7 +85,7 @@ pub async fn handle_role_delete( crypto: &SessionCrypto, state: &State, ) -> Result<()> { - let req: RoleDeletePayload = serde_json::from_slice(payload)?; + let req = RoleDeletePayload::decode(payload)?; let sess = session::get(&state.sessions, session_id) .await .ok_or_else(|| anyhow::anyhow!("session not found"))?; @@ -120,7 +125,10 @@ pub async fn handle_role_delete( stream, PacketId::RoleDelete, 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, ) .await?; diff --git a/gateway/src/net/handshake.rs b/gateway/src/net/handshake.rs index a49205b..70282e0 100644 --- a/gateway/src/net/handshake.rs +++ b/gateway/src/net/handshake.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use prost::Message; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::{debug, info, warn}; @@ -23,17 +24,17 @@ pub async fn run( ) -> Result<(session::Session, SessionCrypto)> { // Generate ephemeral X25519 keypair for forward secrecy 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 let challenge = auth::new_challenge(); let private_mode = state.config.is_private(); let hello = HelloPayload { lnex_version: LNEX_VERSION.into(), - server_pubkey: state.server_identity.pubkey_hex(), - challenge_nonce: hex::encode(challenge), + server_pubkey: hex::decode(state.server_identity.pubkey_hex())?, + challenge_nonce: challenge.to_vec(), node_name: state.config.node.name.clone(), - server_eph_pubkey: eph_pk_hex, + server_eph_pubkey: eph_pk_raw.to_vec(), private_mode, }; 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")); } - let msg: AuthPayload = serde_json::from_slice(&payload)?; + let msg = AuthPayload::decode(payload.as_slice())?; debug!("{addr} → AUTH nick={}", msg.nickname); if msg.lnex_version != LNEX_VERSION { @@ -66,9 +67,8 @@ pub async fn run( return Err(anyhow::anyhow!("version mismatch")); } - let pubkey = - hex_to_32(&msg.client_pubkey).ok_or_else(|| anyhow::anyhow!("bad client pubkey"))?; - let sig = hex_to_64(&msg.signature).ok_or_else(|| anyhow::anyhow!("bad signature"))?; + let pubkey: [u8; 32] = msg.client_pubkey.as_slice().try_into()?; + let sig: [u8; 64] = msg.signature.as_slice().try_into()?; if let Err(e) = auth::verify_auth(&challenge, &pubkey, &sig) { warn!("{addr} auth failed: {e}"); @@ -83,7 +83,7 @@ pub async fn run( 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); io::send_error(stream, seq, proto::ErrorCode::AuthFailed, "banned").await?; return Err(anyhow::anyhow!("banned")); @@ -91,19 +91,18 @@ pub async fn run( state .storage - .upsert_user(&msg.client_pubkey, &msg.nickname) + .upsert_user(&hex::encode(&msg.client_pubkey), &msg.nickname) .await?; // ECDH: compute shared secret from server's ephemeral sk + client's ephemeral pk - let client_eph_raw = hex_to_32(&msg.client_eph_pubkey) - .ok_or_else(|| anyhow::anyhow!("bad client eph pubkey"))?; + let client_eph_raw: [u8; 32] = msg.client_eph_pubkey.as_slice().try_into()?; let client_eph_pk = x25519_dalek::PublicKey::from(client_eph_raw); let shared_secret = SessionCrypto::ecdh(eph_sk, &client_eph_pk); // SESSION let sess = session::create( &state.sessions, - msg.client_pubkey.clone(), + hex::encode(&msg.client_pubkey), msg.nickname.clone(), ) .await; @@ -131,10 +130,3 @@ pub async fn run( 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() -} diff --git a/gateway/src/proto/crypto/cipher.rs b/gateway/src/proto/crypto/cipher.rs index 8944105..e02dfa8 100644 --- a/gateway/src/proto/crypto/cipher.rs +++ b/gateway/src/proto/crypto/cipher.rs @@ -1,6 +1,6 @@ use chacha20poly1305::{ - ChaCha20Poly1305, Key, KeyInit, Nonce, aead::{Aead, Payload}, + ChaCha20Poly1305, Key, KeyInit, Nonce, }; pub(super) fn make_nonce(cid: &[u8; 8], seq: u64) -> [u8; 12] { diff --git a/gateway/src/proto/framing.rs b/gateway/src/proto/framing.rs index 4723c9c..fdaff18 100644 --- a/gateway/src/proto/framing.rs +++ b/gateway/src/proto/framing.rs @@ -1,7 +1,7 @@ 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 { let header = PacketHeader::new(id, seq, payload.len() as u32); 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 { out } -/// Serialize a payload struct to JSON bytes. -pub fn to_payload(v: &T) -> Vec { - serde_json::to_vec(v).expect("payload serialization is infallible") +/// Encode a protobuf payload struct to bytes. +pub fn to_payload(msg: &impl Message) -> Vec { + msg.encode_to_vec() } diff --git a/gateway/src/proto/mod.rs b/gateway/src/proto/mod.rs index d05f767..d8ad057 100644 --- a/gateway/src/proto/mod.rs +++ b/gateway/src/proto/mod.rs @@ -1,11 +1,104 @@ mod crypto; mod framing; mod packet; -mod payloads; + +pub mod lnex { + include!(concat!(env!("OUT_DIR"), "/lnex.rs")); +} + +pub use lnex::*; pub use crypto::SessionCrypto; // Re-export everything so `crate::proto::X` works as before pub use framing::{encode_packet, to_payload}; -pub use packet::{ErrorCode, PacketHeader, PacketId, flags}; -pub use payloads::*; +pub use packet::{flags, ErrorCode, PacketHeader, PacketId}; + +// ─── 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; diff --git a/gateway/src/proto/payloads/channel.rs b/gateway/src/proto/payloads/channel.rs deleted file mode 100644 index f058d86..0000000 --- a/gateway/src/proto/payloads/channel.rs +++ /dev/null @@ -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, - 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, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChannelDeletePayload { - pub channel_id: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChannelListPayload { - pub channels: Vec, -} - -#[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, -} - -#[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, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ErrorPayload { - pub code: u32, - pub message: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct DisconnectPayload { - pub reason: String, -} diff --git a/gateway/src/proto/payloads/guild.rs b/gateway/src/proto/payloads/guild.rs deleted file mode 100644 index 1d56532..0000000 --- a/gateway/src/proto/payloads/guild.rs +++ /dev/null @@ -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, -} - -#[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, - pub permissions: Option, -} - -#[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, - pub expires_in_seconds: Option, -} - -#[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, - pub uses: i64, - pub expires_at: Option, - 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, - pub target_type: Option, - pub reason: Option, - pub created_at: i64, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GuildAuditLogPayload { - pub guild_id: String, - pub entries: Vec, -} - -#[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, -} - -#[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, -} diff --git a/gateway/src/proto/payloads/mod.rs b/gateway/src/proto/payloads/mod.rs deleted file mode 100644 index 23ae5b4..0000000 --- a/gateway/src/proto/payloads/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod channel; -mod guild; -mod social; - -pub use channel::*; -pub use guild::*; -pub use social::*; diff --git a/gateway/src/proto/payloads/social.rs b/gateway/src/proto/payloads/social.rs deleted file mode 100644 index cefe808..0000000 --- a/gateway/src/proto/payloads/social.rs +++ /dev/null @@ -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, - #[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, - #[serde(default)] - pub search_query: Option, - #[serde(default)] - pub limit: Option, -} - -#[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, -} - -#[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, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct FriendEventPayload { - pub event: String, - pub user_id: String, - pub nickname: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct PresenceUpdatePayload { - pub status: String, - pub activity_type: Option, - pub activity_text: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct PresenceInfo { - pub user_id: String, - pub nickname: String, - pub status: String, - pub activity_type: Option, - pub activity_text: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct PresenceSyncPayload { - pub presences: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct PresenceEventPayload { - pub user_id: String, - pub nickname: String, - pub status: String, - pub activity_type: Option, - pub activity_text: Option, -} - -#[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, -} diff --git a/protocol/lnex.proto b/protocol/lnex.proto index dbc3082..d4b60b8 100644 --- a/protocol/lnex.proto +++ b/protocol/lnex.proto @@ -3,129 +3,359 @@ package lnex; // ─── HANDSHAKE ────────────────────────────────────────────────────────────── -// Server → Client on TCP connect (packet_id = 0x0001) message Hello { - string lnex_version = 1; // "v1" - bytes server_pubkey = 2; // 32-byte Ed25519 public key - bytes challenge_nonce = 3; // 32-byte random challenge - string node_name = 4; + string lnex_version = 1; + bytes server_pubkey = 2; + bytes challenge_nonce = 3; + string node_name = 4; + bytes server_eph_pubkey = 5; + bool private_mode = 6; } -// Client → Server (packet_id = 0x0002) message Auth { - bytes client_pubkey = 1; // 32-byte Ed25519 public key - string nickname = 2; - string lnex_version = 3; - bytes signature = 4; // Ed25519 sig over challenge_nonce + bytes client_pubkey = 1; + string nickname = 2; + string lnex_version = 3; + bytes signature = 4; + bytes client_eph_pubkey = 5; } -// Server → Client on success (packet_id = 0x0003) message Session { - string session_id = 1; - string token = 2; - int64 expires_at = 3; // Unix ms, 0 = no expiry + string session_id = 1; + string token = 2; + int64 expires_at = 3; } // ─── KEEPALIVE ────────────────────────────────────────────────────────────── -// Either direction (0x0004 / 0x0005) -message Ping { - int64 timestamp = 1; // Unix ms -} +message Ping { int64 timestamp = 1; } +message Pong { int64 timestamp = 1; } -message Pong { - int64 timestamp = 1; // Echo of Ping.timestamp -} +// ─── ERROR / DISCONNECT ───────────────────────────────────────────────────── + +message Error { uint32 code = 1; string message = 2; } +message Disconnect { string reason = 1; } // ─── CHANNELS ─────────────────────────────────────────────────────────────── -// Client → Gateway (0x0030) -message JoinChannel { - string channel_id = 1; -} +message JoinChannel { string channel_id = 1; } +message LeaveChannel { string channel_id = 1; } -// Client → Gateway (0x0031) -message LeaveChannel { - string channel_id = 1; -} - -// Gateway → Client (0x0032) — full channel snapshot on join message ChannelState { string channel_id = 1; string channel_name = 2; - ChannelKind kind = 3; - repeated Member members = 4; - string voice_endpoint = 5; // host:port of voice-node UDP - - enum ChannelKind { - TEXT = 0; - VOICE = 1; - } - - message Member { - string user_id = 1; - string nickname = 2; - bool in_voice = 3; - } + string kind = 3; + repeated MemberInfo members = 4; + string voice_endpoint = 5; } -// Broadcast → all channel members (0x0040 / 0x0041) -message UserJoin { - string channel_id = 1; - string user_id = 2; - string nickname = 3; +message MemberInfo { + string user_id = 1; + string nickname = 2; + bool in_voice = 3; } -message UserLeave { - string channel_id = 1; - string user_id = 2; +message ChannelCreate { + string channel_id = 1; + string channel_name = 2; + string kind = 3; + optional string guild_id = 4; } -// ─── CHAT ─────────────────────────────────────────────────────────────────── +message ChannelDelete { string channel_id = 1; } + +message ChannelList { repeated ChannelListItem channels = 1; } + +message ChannelListItem { + string channel_id = 1; + string channel_name = 2; + string kind = 3; +} + +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 { - string message_id = 1; // UUID v4, generated by client - string channel_id = 2; - string sender_id = 3; // sender pubkey short form - string content = 4; - int64 timestamp = 5; // Unix ms + string message_id = 1; + string channel_id = 2; + string sender_id = 3; + string content = 4; + int64 timestamp = 5; + bool edited = 6; + optional string reply_to = 7; } -// Gateway → Client on join (0x0021) — recent history message ChatHistory { - string channel_id = 1; - repeated ChatMessage messages = 2; + string channel_id = 1; + 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 { string user_id = 1; string channel_id = 2; - - enum State { - SILENT = 0; - SPEAKING = 1; - MUTED = 2; - DEAFENED = 3; - } - - State state = 3; + string state = 3; } -// ─── ERRORS ───────────────────────────────────────────────────────────────── +// ─── GUILDS ───────────────────────────────────────────────────────────────── -// Any direction (0x00F0) -message Error { - uint32 code = 1; // ErrorCode enum value - string message = 2; +message GuildCreate { string name = 1; } + +message GuildInfo { + 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 Disconnect { - string reason = 1; // optional human-readable reason +message GuildMemberJoin { string guild_id = 1; } +message GuildMemberLeave { string guild_id = 1; string user_id = 2; } +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; }