From 277daae89f89a733a43c9e9b2de0c74098b64359 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Sun, 18 Oct 2026 03:57:08 -0700 Subject: [PATCH] fix(core): rebuild_send_queue excludes addressed messages --- tines-core/src/exchange.rs | 49 +++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/tines-core/src/exchange.rs b/tines-core/src/exchange.rs index 7687ba0..fabe271 100644 --- a/tines-core/src/exchange.rs +++ b/tines-core/src/exchange.rs @@ -519,7 +519,10 @@ fn rebuild_send_queue(peer: &mut Peer, store: &S, now: Tick) { // capacity. When full, only a candidate younger than the current oldest // (last element) is admitted, dropping that oldest. let mut heap: Vec<(ObservationId, u64), { consts::SEND_QUEUE_CAP }> = Vec::new(); - store.for_each_id(&mut |id| { + // Broadcasts only (`to == None`): addressed messages are delivered through + // `forward_addressed` and must never leak into the broadcast diff, mirroring + // the bloom-summary path in `next_frame` which also iterates broadcast ids. + store.for_each_broadcast_id(&mut |id| { if their.might_contain(id) { return; } @@ -676,6 +679,50 @@ mod tests { .is_none()); } + #[test] + fn rebuild_send_queue_excludes_addressed() { + // A store with one broadcast (`to == None`) and one addressed + // (`to = Some([3; 16])`) observation. After a peer contacts us and + // sends an empty Summary (lacking everything), the broadcast diff must + // queue only the broadcast id and never leak in the addressed id. + let mut ex = Exchange::new(ME); + let mut store = MemStore::new(); + let b = obs([2; 16], 1, 0, None, b"t", b"b"); + let a_id = { + let mut a = obs([2; 16], 2, 0, None, b"t", b"a"); + a.to = Some([3; 16]); + a.id() + }; + let b_id = b.id(); + store.insert(b, 0).unwrap(); + store + .insert( + { + let mut a = obs([2; 16], 2, 0, None, b"t", b"a"); + a.to = Some([3; 16]); + a + }, + 0, + ) + .unwrap(); + + // Peer contacts us, then sends an empty Summary (holds nothing). + ex.on_event(Event::Contact(1), &mut store, 0); + let bits = heapless::Vec::from_slice(&[0u8; consts::SUMMARY_BYTES]).unwrap(); + let sum = Frame::Summary { + from: [2; 16], + salt: 0, + bits, + }; + ex.on_event(Event::Frame(1, frame_bytes(&sum)), &mut store, 0); + + let peer = &ex.peers()[0]; + let q: std::vec::Vec<_> = peer.send_queue.iter().copied().collect(); + assert!(q.contains(&b_id), "broadcast id must be queued: {q:?}"); + assert!(!q.contains(&a_id), "addressed id must NOT be queued: {q:?}"); + assert_eq!(q.len(), 1, "only the broadcast id should be queued"); + } + fn assert_obs_id(frame: &Frame, id: ObservationId) { match frame { Frame::Obs { obs, .. } => assert_eq!(obs.id(), id),