michael fea8ea3ab1 0.15.0: simple builder API, v2.1 framing, stream lane, benches
- 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
2026-09-30 08:07:38 -04:00

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.

S
Description
No description provided
Readme
69 KiB
Languages
Rust 96.7%
C++ 3.3%