Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,12 @@ pub const NO_SYNC_UNIVERSE: u16 = 0;
/// The timeout before data loss is assumed for an E131 source, as defined in Appendix A of ANSI E1.31-2018.
pub const E131_NETWORK_DATA_LOSS_TIMEOUT: Duration = Duration::from_millis(2500);

/// The timeout before a discovered source is assumed to be lost as defined in section 12.2 of ANSI E1.31-2018.
pub const UNIVERSE_DISCOVERY_SOURCE_TIMEOUT: Duration = E131_NETWORK_DATA_LOSS_TIMEOUT;
/// The timeout before a discovered source is assumed to be lost.
///
/// Sources send universe discovery packets every `E131_UNIVERSE_DISCOVERY_INTERVAL` (10 seconds, ANSI E1.31-2016 Appendix A),
/// so this must be longer than that interval or discovered sources expire between adverts. The standard doesn't define a
/// receiver timeout, so two intervals are used: a source is only treated as lost after missing a whole advert.
pub const UNIVERSE_DISCOVERY_SOURCE_TIMEOUT: Duration = Duration::from_secs(2 * E131_UNIVERSE_DISCOVERY_INTERVAL.as_secs());

/// Converts the given ANSI E1.31-2018 universe into an Ipv4 multicast address with the port set to the acn multicast port as defined
/// in `packet::ACN_SDT_MULTICAST_PORT`.
Expand Down
29 changes: 29 additions & 0 deletions src/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,35 @@ mod test {
assert_eq!(dmx_rcv.discovered_sources[0].pages[0].universes, universes);
}

/// Sources only advertise every `E131_UNIVERSE_DISCOVERY_INTERVAL`, so a discovered source must not expire before its next advert.
#[test]
fn test_discovered_source_kept_between_adverts() {
assert!(UNIVERSE_DISCOVERY_SOURCE_TIMEOUT > E131_UNIVERSE_DISCOVERY_INTERVAL);

let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ACN_SDT_MULTICAST_PORT);
let mut dmx_rcv = SacnReceiver::with_ip(addr, None).unwrap();

let discovery_pkt = UniverseDiscoveryPacketFramingLayer {
source_name: "Test Src".into(),
data: UniverseDiscoveryPacketUniverseDiscoveryLayer {
page: 0,
last_page: 0,
universes: vec![1u16, 2].into(),
},
};
dmx_rcv.handle_universe_discovery_packet(Uuid::new_v4(), discovery_pkt);

// Pretend the advert arrived just before the next one is due.
let advert_age = E131_UNIVERSE_DISCOVERY_INTERVAL - Duration::from_millis(100);
dmx_rcv.discovered_sources[0].last_updated = Instant::now() - advert_age;
assert_eq!(dmx_rcv.get_discovered_sources().len(), 1);

// Once the source has missed adverts for longer than the timeout it is removed.
dmx_rcv.discovered_sources[0].last_updated =
Instant::now() - UNIVERSE_DISCOVERY_SOURCE_TIMEOUT - Duration::from_millis(100);
assert!(dmx_rcv.get_discovered_sources().is_empty());
}

#[test]
fn test_handle_multi_page_discovery_packet() {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), ACN_SDT_MULTICAST_PORT);
Expand Down
Loading