- TLS 1.3 on TCP (rustls + tokio-rustls, optional self-signed cert) - Server-side channel CRUD with SQLite persistence - Gateway → voice-node membership bridge via broadcast channel
113 lines
3 KiB
Rust
113 lines
3 KiB
Rust
use anyhow::Result;
|
|
use tokio::io::{AsyncRead, AsyncWrite};
|
|
|
|
use crate::{
|
|
domain::session,
|
|
net::{io, state::State},
|
|
proto::{self, DmStartPayload, DmStartResponsePayload, PacketId, SessionCrypto, to_payload},
|
|
};
|
|
|
|
pub async fn handle_dm_start(
|
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
|
seq: &mut u32,
|
|
session_id: &str,
|
|
payload: &[u8],
|
|
crypto: &SessionCrypto,
|
|
state: &State,
|
|
) -> Result<()> {
|
|
let req: DmStartPayload = serde_json::from_slice(payload)?;
|
|
|
|
let sess = session::get(&state.sessions, session_id)
|
|
.await
|
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
|
let my_id = sess.user_id.clone();
|
|
drop(sess);
|
|
|
|
if state
|
|
.storage
|
|
.is_blocked(&req.target_user_id, &my_id)
|
|
.await?
|
|
{
|
|
io::send_encrypted(
|
|
stream,
|
|
PacketId::Error,
|
|
seq,
|
|
&to_payload(&proto::ErrorPayload {
|
|
code: proto::ErrorCode::Blocked as u32,
|
|
message: "blocked".into(),
|
|
}),
|
|
crypto,
|
|
)
|
|
.await?;
|
|
return Ok(());
|
|
}
|
|
|
|
if req.target_user_id == my_id {
|
|
io::send_encrypted(
|
|
stream,
|
|
PacketId::Error,
|
|
seq,
|
|
&to_payload(&proto::ErrorPayload {
|
|
code: proto::ErrorCode::InvalidPacket as u32,
|
|
message: "cannot DM yourself".into(),
|
|
}),
|
|
crypto,
|
|
)
|
|
.await?;
|
|
return Ok(());
|
|
}
|
|
|
|
let (dm_id, unread_count) = state
|
|
.storage
|
|
.find_or_create_dm(&my_id, &req.target_user_id)
|
|
.await?;
|
|
let other_id = state
|
|
.storage
|
|
.get_dm_user_id(&dm_id, &my_id)
|
|
.await?
|
|
.ok_or_else(|| anyhow::anyhow!("user not in DM"))?;
|
|
let nickname = state
|
|
.storage
|
|
.get_dm_nickname(&other_id)
|
|
.await?
|
|
.unwrap_or_else(|| other_id[..8].to_string());
|
|
let messages = state
|
|
.storage
|
|
.get_dm_messages(&dm_id, 50, None, None)
|
|
.await?;
|
|
|
|
let resp = DmStartResponsePayload {
|
|
dm_id,
|
|
other_user_id: other_id,
|
|
other_nickname: nickname,
|
|
messages,
|
|
unread_count: unread_count as u32,
|
|
};
|
|
io::send_encrypted(stream, PacketId::DmStart, seq, &to_payload(&resp), crypto).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn handle_dm_read_ack(
|
|
stream: &mut (impl AsyncRead + AsyncWrite + Unpin),
|
|
seq: &mut u32,
|
|
session_id: &str,
|
|
payload: &[u8],
|
|
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 sess = session::get(&state.sessions, session_id)
|
|
.await
|
|
.ok_or_else(|| anyhow::anyhow!("session not found"))?;
|
|
let my_id = sess.user_id.clone();
|
|
drop(sess);
|
|
|
|
state.storage.reset_dm_unread(dm_id, &my_id).await?;
|
|
|
|
io::send_encrypted(stream, PacketId::DmReadAck, seq, b"{}", crypto).await?;
|
|
Ok(())
|
|
}
|