//! The minimal OmniVox server: one enum, one closure, one call. //! //! Run this, then run `cargo run --example omnivox-client` in another //! terminal. The client sends `Hello`; we echo a `Note` back and finish //! after three exchanges — the same callback shape as the original C++ //! demo, minus the boilerplate. use omnivox::Decision; use serde::{Deserialize, Serialize}; /// The one message type of our tiny protocol. Because this is an enum, /// `on_data`'s `match` is the switch-on-id pattern from the C++ days, /// checked by the compiler instead of a runtime default case. #[derive(Serialize, Deserialize, Debug)] enum ChatMsg { Hello, Note(String), } #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_target(false) .without_time() .init(); let server = omnivox::server::() // Both peers must derive the same key. scrypt runs once, at startup, // on a blocking thread (~1 s, 128 MiB). Prefer a random 32-byte PSK // via `.key([u8; 32])` in real deployments. .password("SecurePassword123", b"static-demo-salt") .on_data(|msg, ctx| match msg { ChatMsg::Hello => { tracing::info!("client {} says hello", ctx.id()); // Synchronous, non-blocking enqueue. A `false` return would // mean the peer stopped reading — never a panic, never a stall. let _ = ctx.reply(&ChatMsg::Note(format!("echo {}", ctx.id()))); } ChatMsg::Note(text) => { tracing::info!("client {} wrote: {text}", ctx.id()); } }) // Every authenticated client is welcome; return `Decision::Deny` // to close specific peers (per-IP policy, allowlists, ...). .on_connect(|ctx| { tracing::info!("accepting {}:{}", ctx.ip(), ctx.port()); Decision::Allow }) .on_disconnect(|ctx| tracing::info!("client {} left", ctx.id())) .serve(8080) .await?; tracing::info!("listening on :8080 (Ctrl-C to stop)"); // The handle can live forever; `server.stop().await` tears everything // down cleanly when you want it to. let _ = &server; std::future::pending::<()>().await; Ok(()) }