//! A whole `kestrove-nmh` connection as the app's Broker sees it: length-prefixed frames, outer //! JSON, and the pairing / open / msg handlers (src/broker.rs). The input is the byte stream the //! browser side sends; replies go to a sink. Each frame must get exactly one well-formed reply. #![no_main] use std::io::{self, Read, Write}; use std::sync::{Arc, OnceLock}; use kestrove_desktop::broker::{Broker, PairingStore, PairingView, SaveView, Ui}; use kestrove_desktop::framing::read_json; use kv_ffi::VaultCore; use libfuzzer_sys::fuzz_target; struct NoUi; impl Ui for NoUi { fn pairing_requested(&self, _: &PairingView) {} fn save_requested(&self, _: &SaveView) {} } struct Duplex<'a> { input: &'a [u8], output: Vec, } impl Read for Duplex<'_> { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.input.read(buf) } } impl Write for Duplex<'_> { fn write(&mut self, buf: &[u8]) -> io::Result { self.output.write(buf) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } fn broker() -> &'static Arc { static B: OnceLock> = OnceLock::new(); B.get_or_init(|| { Broker::new( VaultCore::new_in_memory(), Arc::new(NoUi), PairingStore::in_memory(), ) }) } fuzz_target!(|data: &[u8]| { let mut io = Duplex { input: data, output: Vec::new(), }; let _ = broker().serve(&mut io); let mut replies = io.output.as_slice(); while let Some(v) = read_json(&mut replies).expect("app replies are valid frames") { assert!( v.get("t").and_then(|t| t.as_str()).is_some(), "reply without t: {v}" ); } });