- omnivox::server::<T>()/client::<T>() builders; plain closures, enum in, match in the callback — the original C++ UX with the ceremony removed - serde + bincode replace the hand-rolled Cereal codec (vectors.rs still governs the crypto leaves via the C++ golden oracles) - v2.1 frames: u32 len | type | ct | tag; nonce derived from the sequence counter (no wire nonce), type byte authenticated as AAD; any frame-level failure is fatal, authenticated-but-undecodable payloads are skipped - stream lane: send_stream/on_stream, 1 MiB frames, shared sequence with messages (exact sender interleaving preserved); see dump-agent/viewer - per-connection bounded queues + writer tasks; reply/reply_stream are non-blocking try_send lanes for callbacks - benches: seal/open criterion group + transport harness (spray msg/s, stream GiB/s, raw loopback baseline) - profile.dev.package scrypt/pbkdf2 opt-level 3: golden-vector scrypt at N=2^17 was unusable in debug builds - PROTOCOL-V2.md and README rewritten for v2.1
61 lines
2.3 KiB
Rust
61 lines
2.3 KiB
Rust
//! 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<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt()
|
|
.with_target(false)
|
|
.without_time()
|
|
.init();
|
|
|
|
let server = omnivox::server::<ChatMsg>()
|
|
// 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(())
|
|
}
|