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:
2026-09-30 08:07:38 -04:00
parent c614d8345f
commit fea8ea3ab1
15 changed files with 2827 additions and 1193 deletions
+42 -26
View File
@@ -1,39 +1,55 @@
//! Shared helpers for the integration tests: v2 client sessions against a
//! live server, and raw frame construction.
#![allow(dead_code)] // each test binary uses a subset
use omnivox::{Cereal, CodecError, Session, encrypt_with_nonce, frame_nonce, handshake::perform};
//! Shared helpers for the integration tests: raw v2.1 client sessions
//! (handshake completed, manual framing) for the attack regressions.
use omnivox::{
FrameType,
frame::{FrameType as Ft, seal},
handshake::{Session, perform},
};
use serde::{Deserialize, Serialize};
use tokio::net::TcpStream;
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
#[derive(Debug, Default)]
/// Test message type: serde-derives make it a `Message` by blanket impl.
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Msg(pub Vec<u8>);
impl Cereal for Msg {
fn encode(&self, out: &mut Vec<u8>) {
omnivox::write_byte_vec(out, &self.0);
}
fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
Ok(Self(omnivox::Reader::new(bytes).byte_vec()?))
}
}
pub const TEST_PSK: [u8; 32] = [42u8; 32];
/// Builds one framed message with the given sequence number.
pub fn frame(key: &[u8; 32], counter: u64, msg: &[u8]) -> Vec<u8> {
let mut plain = Vec::new();
Msg(msg.to_vec()).encode(&mut plain);
let pkt = encrypt_with_nonce(key, &frame_nonce(counter), &plain);
let mut bytes = Vec::new();
pkt.encode(&mut bytes);
let mut out = Vec::new();
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(&bytes);
out
pub fn bincode_cfg() -> bincode::config::Configuration<
bincode::config::LittleEndian,
bincode::config::Fixint,
bincode::config::Limit<16777216>,
> {
bincode::config::standard()
.with_fixed_int_encoding()
.with_limit::<16777216>()
}
/// Connects and completes the v2 handshake as the client role, returning
/// the raw halves plus the session keys for manual framing.
/// Serialize like the library does.
pub fn encode(msg: &Msg) -> Vec<u8> {
bincode::serde::encode_to_vec(msg, bincode_cfg()).unwrap()
}
/// One complete framed message (with u32 length prefix) ready to write.
pub fn msg_frame(key: &[u8; 32], counter: u64, payload: &[u8]) -> Vec<u8> {
seal(key, counter, Ft::Message, &encode(&Msg(payload.to_vec())))
}
/// One complete framed stream chunk.
pub fn stream_frame(key: &[u8; 32], counter: u64, payload: &[u8]) -> Vec<u8> {
seal(key, counter, Ft::Stream, payload)
}
/// Arbitrary-kind frame for protocol-violation tests.
pub fn raw_frame(key: &[u8; 32], counter: u64, kind: FrameType, plaintext: &[u8]) -> Vec<u8> {
seal(key, counter, kind, plaintext)
}
/// Connect and complete the v2 handshake as the client role, returning
/// raw halves and session keys for manual framing.
pub async fn client_session(port: u16, psk: &[u8; 32]) -> (OwnedReadHalf, OwnedWriteHalf, Session) {
let stream = TcpStream::connect(("127.0.0.1", port)).await.unwrap();
let (mut r, mut w) = stream.into_split();