Files
omnivox-rs/README.md
T
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

100 lines
3.7 KiB
Markdown

# 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::<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
```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.