Files
omnivox-rs/examples/dump-agent.rs
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

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(())
}