//! Port of OmniVox-server.cpp: echoes each client payload back with the //! client id, refusing no client that holds the key. #[path = "common/mod.rs"] mod common; use common::{DatagramPayload, PASSWORD, SALT, SERVER_PORT}; use omnivox::{Config, ConnectionContext, Decision, OmniVox, derive_encrypt_key}; use std::{ collections::HashMap, sync::{Arc, Mutex}, }; #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_target(false) .without_time() .init(); tracing::info!("Starting server on port {SERVER_PORT}"); let server = Arc::new(OmniVox::::new()); let key = derive_encrypt_key(PASSWORD.as_bytes(), SALT, 17, 8, 1)?; server.set_encrypt_key(key); // The C++ example's g_clientHalt map, kept for behavioral parity. let client_halt: Arc>> = Arc::new(Mutex::new(HashMap::new())); let responder = Arc::clone(&server); let received = Arc::new(move |data: &DatagramPayload, ctx: &ConnectionContext| { tracing::info!( "Received data from client {}: Payload size={}", ctx.id, data.payload.len() ); if !data.payload.is_empty() { tracing::info!( "Payload content: {}", String::from_utf8_lossy(&data.payload) ); } let response = DatagramPayload { id: ctx.id as u16, v1: 0, payload: data.payload.clone(), }; let server = Arc::clone(&responder); let id = ctx.id; // C++ echoed synchronously inside the callback; the Rust callback is // sync, so the echo rides a task — per-connection socket order is // still serialized by the client's write lock (contract B3). tokio::spawn(async move { if server.send_to_client(&response, id).await { tracing::info!("Sent response to client {id}"); } else { tracing::info!("Error sending data to client {id}"); } }); }); let halt = Arc::clone(&client_halt); let disconnect = Arc::new(move |ctx: &ConnectionContext| { tracing::info!( "Client {} {}:{} is disconnecting from the server cleaning up objects.", ctx.id, ctx.ip, ctx.port ); *halt.lock().unwrap().entry(ctx.id).or_default() = true; }); let halt = Arc::clone(&client_halt); let connect = Arc::new(move |ctx: &ConnectionContext| { *halt.lock().unwrap().entry(ctx.id).or_default() = false; tracing::info!("New client has connected - {}:{}", ctx.ip, ctx.port); Decision::Allow }); server .start_server( SERVER_PORT, Config::default(), received, disconnect, connect, ) .await?; tracing::info!("Server started successfully"); // C++ slept u32::MAX hours; we serve until killed, like the reference. std::future::pending::<()>().await; Ok(()) }