Files
omnivox-rs/examples/omnivox-server.rs
T
michael 268b986bb9 Rust port of OmniVox, wire-compatible with the C++ original
Byte-exact cereal binary layout, RFC 8439 ChaCha20-Poly1305, RFC 7914
scrypt defaults; tokio transport preserving the original's observable
behavior (contract B1-B3, B5 quirks included). Parity evidence: golden
C++ vectors in tests/vectors.rs and loopback behavior tests; both
cross-directions verified against the unmodified C++ binaries.

Source: https://git.0xfeedc0de.com/michael/OmniVox
2026-09-30 06:35:24 -04:00

90 lines
3.0 KiB
Rust

//! 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::{ConnectionContext, Decision, OmniVox, derive_encrypt_key};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_target(false)
.without_time()
.init();
tracing::info!("Starting server on port {SERVER_PORT} with 2 threads");
let server = Arc::new(OmniVox::<DatagramPayload>::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<Mutex<HashMap<u32, bool>>> = 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, 2, 15000, 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(())
}