- 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
85 lines
2.9 KiB
Rust
85 lines
2.9 KiB
Rust
//! The dump **viewer** side: receives an encrypted memory stream while
|
|
//! control messages arrive on the same session, in order, interleaved.
|
|
//!
|
|
//! Run this first, then `cargo run --example dump-agent`. Pages are
|
|
//! appended to `/tmp/omnivox-dump.bin`; the `FNV-1a` hash printed at the
|
|
//! end proves byte-exact arrival (run the agent twice — same hash).
|
|
|
|
use omnivox::Config;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::io::Write;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
enum AgentMsg {
|
|
Progress { page: u32, pages: u32 },
|
|
Done { bytes: u64 },
|
|
}
|
|
|
|
/// Running FNV-1a over everything the stream lane delivers.
|
|
#[derive(Default)]
|
|
struct Digest(u64);
|
|
|
|
impl Digest {
|
|
fn add(&mut self, bytes: &[u8]) {
|
|
for &b in bytes {
|
|
self.0 ^= b as u64;
|
|
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt()
|
|
.with_target(false)
|
|
.without_time()
|
|
.init();
|
|
|
|
// Shared across both callbacks (and the Done handler) — a plain
|
|
// Arc<Mutex<..>> like any app state.
|
|
let digest = Arc::new(Mutex::new(Digest::default()));
|
|
let file = Arc::new(Mutex::new(std::fs::File::create("/tmp/omnivox-dump.bin")?));
|
|
|
|
let _server = omnivox::server::<AgentMsg>()
|
|
.password("SecurePassword123", b"static-demo-salt")
|
|
// Larger stream frames than the default, matching the agent.
|
|
.config(Config {
|
|
max_stream_frame: 1024 * 1024,
|
|
..Config::default()
|
|
})
|
|
.on_data({
|
|
let file = Arc::clone(&file);
|
|
let digest = Arc::clone(&digest);
|
|
move |msg, _ctx| match msg {
|
|
AgentMsg::Progress { page, pages } => {
|
|
tracing::info!("page {page}/{pages} inbound");
|
|
file.lock().unwrap().write_all(b"\n").ok(); // page boundary marker
|
|
}
|
|
AgentMsg::Done { bytes } => {
|
|
tracing::info!(
|
|
"dump complete: {bytes} bytes, fnv1a={:016x}, saved to /tmp/omnivox-dump.bin",
|
|
digest.lock().unwrap().0
|
|
);
|
|
std::process::exit(0);
|
|
}
|
|
}
|
|
})
|
|
// The bulk lane: raw decrypted bytes, zero serde, zero copies beyond
|
|
// the queue hop. This is where multi-GB dumps land.
|
|
.on_stream({
|
|
let file = Arc::clone(&file);
|
|
let digest = Arc::clone(&digest);
|
|
move |chunk: &[u8], _ctx| {
|
|
digest.lock().unwrap().add(chunk);
|
|
file.lock().unwrap().write_all(chunk).ok();
|
|
}
|
|
})
|
|
.serve(8080)
|
|
.await?;
|
|
|
|
tracing::info!("viewer listening on :8080, waiting for agent...");
|
|
std::future::pending::<()>().await;
|
|
Ok(())
|
|
}
|