- 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
3.7 KiB
omnivox-rs
Encrypted async TCP client-server messaging — protocol v2.1. A Rust library plus example binaries, descended from the OmniVox C++ library and hardened past it after a full red-team review.
The API is the original C++ shape — your struct goes in, a match arms over it in one callback — with all the boilerplate removed:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
enum AppMsg { Hello, Note(String) }
// server
let server = omnivox::server::<AppMsg>()
.password("hunter2", b"static-salt") // or .key([u8; 32])
.on_data(|msg, ctx| match msg {
AppMsg::Hello => println!("hello from {}", ctx.ip()),
AppMsg::Note(n) => { ctx.reply(&AppMsg::Note(n.to_uppercase())); }
})
.serve(8080)
.await?;
// client
let client = omnivox::client::<AppMsg>()
.password("hunter2", b"static-salt")
.on_data(|msg, _ctx| println!("server said {msg:?}"))
.connect("127.0.0.1", 8080)
.await?;
client.send(&AppMsg::Hello).await;
Any Serialize + DeserializeOwned type is a message — serde+bincode
replaces the original's hand-written Cereal. For bulk data there is a
second lane: client.send_stream(&bytes) pairs with an on_stream
callback — no encoding, 1 MiB frames, same sequence numbers, so messages
and stream chunks arrive in exactly the order they were sent.
- X25519 handshake on every connection → forward secrecy, session-bound keys
- ChaCha20-Poly1305, nonce derived from per-direction sequence numbers → replay-proof, no nonce on the wire
- Direction-separated traffic keys; frame type authenticated as AAD
- scrypt password KDF (blocking-thread), random-PSK support, panic-isolated
callbacks, connection caps, idle/stall/send timeouts, zero
unsafe
Not wire-compatible with the C++ original or v1 — by design; v1 peers
are refused at the handshake. See PROTOCOL-V2.md for the normative spec
and PARITY-CONTRACT.md (retired) for the port's history.
Security posture
| v1 weakness | v2.1 answer |
|---|---|
| captured frames replay forever | sequence-number nonces; a replay kills the connection |
| frames portable across connections | keys bound to per-session nonces |
| 5 MiB pre-auth allocation from 8 bytes | fixed 69-byte handshake before any attacker-sized read |
| one symmetric key, either peer can forge as itself | direction-separated HKDF session keys |
| static key, no forward secrecy | ephemeral X25519 per connection |
| message/stream confusion impossible to detect | type byte authenticated as AAD |
| no client-side timeouts | full idle/stall discipline both roles |
| panicking user callback killed the read loop | callbacks panic-isolated |
| demo password in every repo checkout | release builds refuse the demo key |
Use random 32-byte PSKs where you can; an active attacker can grind scrypt against human passwords (PAKE planned).
Examples
cargo run --example omnivox-server # terminal 1 — enum echo server
cargo run --example omnivox-client # terminal 2
cargo run --example dump-agent # terminal 1 — streams a toy 16 MiB
cargo run --example dump-viewer # terminal 2 — receives, verifies, saves
Benchmarks
cargo bench --bench transport — measured on the author's machine
(Intel Core Ultra 7 258V, loopback):
| What | Result |
|---|---|
| ChaCha20-Poly1305 seal, 1 MiB frame | ≈1.18 GiB/s single-threaded |
| 256 B messages, encrypted round trip | ≈120 000 msg/s |
| Stream lane, 1 MiB frames | ≈0.7 GiB/s (vs ≈2.0 raw loopback echo) |
Build & test
cargo test # unit, attack regressions, golden vectors, doctests
cargo clippy --all-targets -- -D warnings
License
GPL-3.0-or-later.