# 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: ```rust use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] enum AppMsg { Hello, Note(String) } // server let server = omnivox::server::() .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::() .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 ```bash 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 ```bash cargo test # unit, attack regressions, golden vectors, doctests cargo clippy --all-targets -- -D warnings ``` ## License GPL-3.0-or-later.