//! 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> { tracing_subscriber::fmt() .with_target(false) .without_time() .init(); // Shared across both callbacks (and the Done handler) — a plain // Arc> 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::() .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(()) }