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
This commit is contained in:
@@ -1,44 +1,65 @@
|
||||
# omnivox-rs
|
||||
|
||||
Encrypted async TCP client-server messaging — protocol **v2**. A Rust
|
||||
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 of v1.
|
||||
hardened past it after a full red-team review.
|
||||
|
||||
- X25519 handshake on every connection → forward secrecy, session-bound keys
|
||||
- ChaCha20-Poly1305 with strictly increasing sequence numbers → replay-proof
|
||||
- Direction-separated traffic keys, fixed-size pre-auth surface
|
||||
- scrypt (RFC 7914) password-based key derivation; `Cereal` payload encoding
|
||||
(byte-exact with the original's cereal binary format) for data portability
|
||||
- tokio transport: connection caps, idle/stall/send timeouts, panic-isolated
|
||||
callbacks; 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 full normative
|
||||
spec and `PARITY-CONTRACT.md` (retired) for the port's history.
|
||||
|
||||
## Usage
|
||||
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 omnivox::{Config, Cereal, CodecError, Decision, OmniVox, derive_encrypt_key};
|
||||
use std::sync::Arc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
let endpoint = OmniVox::<MyPayload>::new();
|
||||
endpoint.set_encrypt_key(derive_encrypt_key(b"password", b"salt", 17, 8, 1)?);
|
||||
endpoint.start_server(8080, Config::default(), on_data, on_disconnect, on_connect).await?;
|
||||
#[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;
|
||||
```
|
||||
|
||||
Payload types implement `Cereal` (raw little-endian fields, `u64` vector
|
||||
lengths — the original's serialization). Runnable demos: `examples/`.
|
||||
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 answer |
|
||||
| v1 weakness | v2.1 answer |
|
||||
|---|---|
|
||||
| captured frames replay forever | strict sequence numbers; a replay kills the connection |
|
||||
| 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 |
|
||||
@@ -46,15 +67,34 @@ lengths — the original's serialization). Runnable demos: `examples/`.
|
||||
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 # includes attack regressions: replay, version, panic, timeouts
|
||||
cargo test # unit, attack regressions, golden vectors, doctests
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo run --example omnivox-server # terminal 1
|
||||
cargo run --example omnivox-client # terminal 2
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0-or-later.
|
||||
GPL-3.0-or-later.
|
||||
Reference in New Issue
Block a user