Phase 2: PostgreSQL, E2EE, channel CRUD, voice signaling, Slint migration

Server:
- PostgreSQL backend (Pool enum over Sqlite/PgPool, all storage methods dual-dialect)
- E2EE for DM (4 new protobuf packets, handlers, storage of ciphertext only)
- ChannelEdit packet + server-authoritative channel create/delete
- Gateway → voice-node membership signaling via broadcast channel

Client:
- E2EE crypto (X25519 from Ed25519, HKDF, ChaCha20-Poly1305) + UI lock icons
- E2EE net layer (PID 0x0071-0x0074, JSON payloads, dispatch, session handlers)
- Slint migration: all .slint files, bridge.rs, update.rs, main.rs rewritten
- Removed egui UI (~50 files) and all dead code (clean clippy -D warnings)
This commit is contained in:
loki5512344 2026-07-14 13:25:14 +02:00
parent c34ed2a192
commit 742cf03a5f
Signed by: boba
GPG key ID: 253067914055423B
31 changed files with 2203 additions and 639 deletions

View file

@ -24,3 +24,4 @@ anyhow.workspace = true
thiserror.workspace = true
toml.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -95,3 +95,31 @@ pub async fn touch_member(channels: &ChannelMap, channel_id: u64, addr: SocketAd
.members
.insert(addr, Instant::now());
}
// ─── Gateway-to-voice-node membership bridge ──────────────────────────────────
/// user_id → socket address (the UDP endpoint reported by the gateway).
pub type UserMap = Arc<RwLock<HashMap<String, SocketAddr>>>;
pub fn new_user_map() -> UserMap {
Arc::new(RwLock::new(HashMap::new()))
}
pub async fn add_user_mapping(map: &UserMap, user_id: String, addr: SocketAddr) {
map.write().await.insert(user_id, addr);
}
pub async fn remove_user_mapping(map: &UserMap, user_id: &str) -> Option<SocketAddr> {
map.write().await.remove(user_id)
}
/// Remove `addr` from every channel's member and sender sets,
/// dropping any channel that becomes empty.
pub async fn remove_member_from_all(channels: &ChannelMap, addr: &SocketAddr) {
let mut lock = channels.write().await;
lock.retain(|_, state| {
state.members.remove(addr);
state.senders.retain(|_, s| s != addr);
!state.members.is_empty()
});
}

View file

@ -1,6 +1,8 @@
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::net::UdpSocket;
use tokio::sync::broadcast;
use tracing::{debug, error, info, warn};
use crate::relay;
@ -9,10 +11,15 @@ use crate::{
};
pub async fn run(config: Config) -> anyhow::Result<()> {
run_bind(&config.node.name, &config.voice.bind).await
let (_, rx) = broadcast::channel(1);
run_bind(&config.node.name, &config.voice.bind, rx).await
}
pub async fn run_bind(node_name: &str, bind: &str) -> anyhow::Result<()> {
pub async fn run_bind(
node_name: &str,
bind: &str,
mut voice_member_rx: broadcast::Receiver<String>,
) -> anyhow::Result<()> {
info!("VNOX Voice Node starting — node: {node_name}");
info!("UDP bind: {bind}");
@ -20,6 +27,7 @@ pub async fn run_bind(node_name: &str, bind: &str) -> anyhow::Result<()> {
info!("voice node listening on {bind}");
let channels = relay::new_channel_map();
let user_map = relay::new_user_map();
let channels_cleanup = channels.clone();
tokio::spawn(async move {
@ -30,6 +38,42 @@ pub async fn run_bind(node_name: &str, bind: &str) -> anyhow::Result<()> {
}
});
let channels_events = channels.clone();
let user_map_events = user_map.clone();
tokio::spawn(async move {
while let Ok(event_str) = voice_member_rx.recv().await {
if let Ok(event) = serde_json::from_str::<serde_json::Value>(&event_str) {
let event_type = event["type"].as_str().unwrap_or("");
let channel_id = event["channel_id"].as_str().unwrap_or("");
let user_id = event["user_id"].as_str().unwrap_or("");
match event_type {
"join" => {
let endpoint_str = match event["endpoint"].as_str() {
Some(s) => s,
None => continue,
};
let addr = match endpoint_str.parse::<SocketAddr>() {
Ok(a) => a,
Err(_) => continue,
};
relay::add_user_mapping(&user_map_events, user_id.to_string(), addr).await;
if let Ok(ch) = channel_id.parse::<u64>() {
relay::touch_member(&channels_events, ch, addr).await;
}
}
"leave" => {
if let Some(addr) =
relay::remove_user_mapping(&user_map_events, user_id).await
{
relay::remove_member_from_all(&channels_events, &addr).await;
}
}
_ => {}
}
}
}
});
let socket = Arc::new(socket);
let socket_relay = socket.clone();
let channels_playout = channels.clone();