- 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
74 lines
2.5 KiB
Rust
74 lines
2.5 KiB
Rust
//! The original OmniVox use case: a dump **agent** streams physical
|
|
//! memory to a viewer while spraying small control messages over the same
|
|
//! encrypted session.
|
|
//!
|
|
//! This example fabricates a toy 16 MiB "memory" image (no root needed :),
|
|
//! streams it in 1 MiB encrypted chunks through the stream lane, and
|
|
//! interleaves `Progress` messages through the message lane. Run
|
|
//! `dump-viewer` first, then this.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::time::Duration;
|
|
|
|
/// Control-plane messages. Bulk bytes deliberately do NOT travel as
|
|
/// messages — the stream lane skips serde entirely.
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
enum AgentMsg {
|
|
/// "About to stream page {page} of {pages}"
|
|
Progress { page: u32, pages: u32 },
|
|
/// "Entire image streamed; {bytes} bytes sent."
|
|
Done { bytes: u64 },
|
|
}
|
|
|
|
const PAGE: usize = 1024 * 1024; // 1 MiB stream frames
|
|
const PAGES: u32 = 16;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt()
|
|
.with_target(false)
|
|
.without_time()
|
|
.init();
|
|
|
|
let agent = omnivox::client::<AgentMsg>()
|
|
.password("SecurePassword123", b"static-demo-salt")
|
|
.connect("127.0.0.1", 8080)
|
|
.await?;
|
|
|
|
let mut image = Vec::with_capacity(PAGE * PAGES as usize);
|
|
for page in 0..(PAGE * PAGES as usize) {
|
|
// A deterministic fake "memory image": anything recognizable works.
|
|
image.push((page % 251) as u8);
|
|
}
|
|
|
|
for (n, page) in image.chunks(PAGE).enumerate() {
|
|
// Control plane: announce the page (tiny, latency-sensitive).
|
|
agent
|
|
.send(&AgentMsg::Progress {
|
|
page: n as u32,
|
|
pages: PAGES,
|
|
})
|
|
.await;
|
|
|
|
// Data plane: stream the page. `send_stream` splits anything larger
|
|
// than Config::max_stream_frame and awaits each chunk, so even a
|
|
// 4 GiB dump here would flow with proper backpressure — a slow
|
|
// viewer throttles the agent instead of exploding memory.
|
|
if !agent.send_stream(page).await {
|
|
tracing::error!("viewer vanished mid-dump");
|
|
break;
|
|
}
|
|
// Pace the toy dump so the interleaving is visible in the logs.
|
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
|
}
|
|
|
|
agent
|
|
.send(&AgentMsg::Done {
|
|
bytes: image.len() as u64,
|
|
})
|
|
.await;
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
agent.stop().await;
|
|
Ok(())
|
|
}
|