Files
Sergio Ammirata 8da9e7f737 feat(nack): route retransmissions by per-peer recovery_priority
When a receiver flow is carried by more than one RTCP-capable peer,
send_nack_group historically sent every NACK to the eligible peer
with the lowest RTT.  That is wrong when the lowest-RTT peer cannot
actually retransmit: e.g. a duplicate/relay feed that delivers the
same packets but keeps no retransmit buffer.  NACKs go to it, the
peer that holds the buffer never hears them, and recovery stalls.

Add a per-peer recovery_priority (new ?recovery-priority=<n> URL
parameter, rist_peer_config.recovery_priority).  NACK groups now go
to the eligible peer with the highest recovery_priority, ties broken
by lowest RTT.  The default of 0 on every peer reduces exactly to the
prior lowest-RTT selection, so existing deployments are unchanged.

The selection predicate is factored into src/rist-nack-select.h
(rist_nack_peer_preferred) so it can be unit-tested without building
peers or starting threads.

Also name the weight=0 duplicate sentinel: new public macro
RIST_PEER_WEIGHT_DUPLICATE makes the load-balancer's "duplicate to
all peers" branch read as intent rather than a magic 0.

ABI: RIST_PEER_CONFIG_VERSION 4 -> 5 (one new trailing field,
defaulted to 0 by rist_peer_config_defaults_set; zero-initialised
configs keep legacy behaviour).

Tests:
  - test/rist/unit/test_url_recovery_priority_parse.c: ?recovery-priority=
    parsing through the public rist_parse_address2 API.
  - test/rist/unit/test_nack_peer_select.c: priority/RTT selection,
    tie-breaks, legacy-equivalence and unmeasured-RTT edge cases.
  - meson test --suite unit: 8/8 OK.
  - meson test (full): 51 OK, 11 Expected Fail, 0 unexpected.
2026-06-10 17:07:37 -04:00
..