From 322e3da33adf056eee71bc7a59f381779e492c34 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 08:59:51 +0000 Subject: [PATCH 1/2] Check every source for timeouts when announce_timeout is set In the announce_timeout branch of check_timeouts, the outer loop broke when the first source had no timed out universe, because the condition was inverted. Only the first source in the HashMap was ever checked, so UniverseTimeout (E1.31 network data loss) was missed for every other source and their stale sequence numbers were never removed. Break once a timed out universe has been found instead. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KzaQVQThQpmqhvLapk99Vs --- src/receive.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/receive.rs b/src/receive.rs index 15f4141..7537a1e 100644 --- a/src/receive.rs +++ b/src/receive.rs @@ -1961,7 +1961,7 @@ fn check_timeouts( break; } } - if timedout_uni.is_none() { + if timedout_uni.is_some() { break; } } @@ -2192,6 +2192,37 @@ mod test { assert_eq!(dmx_rcv.discovered_sources[0].pages[0].universes, universes); } + /// With announce_timeout set, a universe that has timed out must be reported whichever source it belongs to, not only if + /// it belongs to the first source in the map. + #[test] + fn test_check_timeouts_announce_finds_any_source() { + let mut src_sequences: HashMap> = HashMap::new(); + for _ in 0..16 { + let mut universes = HashMap::new(); + universes.insert(1, TimedStampedSeqNo::new(0, Instant::now())); + src_sequences.insert(Uuid::new_v4(), universes); + } + + let timed_out_cid = Uuid::new_v4(); + let stale = Instant::now() - E131_NETWORK_DATA_LOSS_TIMEOUT - Duration::from_millis(100); + let mut universes = HashMap::new(); + universes.insert(7, TimedStampedSeqNo::new(0, stale)); + src_sequences.insert(timed_out_cid, universes); + + match check_timeouts(&mut src_sequences, E131_NETWORK_DATA_LOSS_TIMEOUT, true) { + Err(SacnError::UniverseTimeout(cid, universe)) => { + assert_eq!(cid, timed_out_cid); + assert_eq!(universe, 7); + } + other => panic!("Expected UniverseTimeout, got {other:?}"), + } + + // The timed out source is removed and nothing else times out. + assert!(!src_sequences.contains_key(&timed_out_cid)); + assert_eq!(src_sequences.len(), 16); + assert!(check_timeouts(&mut src_sequences, E131_NETWORK_DATA_LOSS_TIMEOUT, true).is_ok()); + } + #[test] fn test_handle_multi_page_discovery_packet() { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ACN_SDT_MULTICAST_PORT); From 6d9062e3495087ae87ebe2a082012fe6d8f99ea5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 09:00:00 +0000 Subject: [PATCH 2/2] Add backticks in test doc comment for clippy Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KzaQVQThQpmqhvLapk99Vs --- src/receive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/receive.rs b/src/receive.rs index 7537a1e..fd42c22 100644 --- a/src/receive.rs +++ b/src/receive.rs @@ -2192,7 +2192,7 @@ mod test { assert_eq!(dmx_rcv.discovered_sources[0].pages[0].universes, universes); } - /// With announce_timeout set, a universe that has timed out must be reported whichever source it belongs to, not only if + /// With `announce_timeout` set, a universe that has timed out must be reported whichever source it belongs to, not only if /// it belongs to the first source in the map. #[test] fn test_check_timeouts_announce_finds_any_source() {