fix: deliver one DMRD when TG is on both OPTIONS slots (#53)

Dual-slot fan-out from TS1+TS2 static OPTIONS doubled OBP/HBP
downlink rate and made unpaced bridges (e.g. ysf2dmr) sound slow.
pull/54/head
ce5rpy 2 months ago committed by GitHub
parent 5f2f69dd19
commit 7ff3010c23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -91,7 +91,12 @@ def normalize_ua_voice_slot(peer: dict[str, Any], wire_slot: int) -> int:
def peer_listen_slots(peer: dict[str, Any], tgid: int) -> list[int]:
"""Voice slots where this peer listens for ``tgid`` (static OPTIONS or wire fallback)."""
"""Candidate RF slots where this peer is subscribed to ``tgid`` (OPTIONS).
When the same TG is listed on both TS1 and TS2, both slots are returned as
candidates; :func:`iter_downlink_voice_slots` collapses that to **one**
delivery slot (never duplicate DMRD).
"""
from adn_server.application.report.payloads import parse_peer_options_static
ts1, ts2 = parse_peer_options_static(peer.get("OPTIONS"))
@ -651,10 +656,20 @@ def iter_downlink_voice_slots(
wire_slot: int,
tgid: int,
) -> list[int]:
"""P4: slots to deliver when static TG spans TS1+TS2."""
"""Exactly one voice slot for group downlink to this peer.
Legacy ``send_peers`` sends one DMRD per peer. If OPTIONS lists the same TG
on TS1 and TS2, keep a single copy on the bridge/OBP wire slot (remap only
when the peer listens on a different unambiguous slot).
"""
listen = peer_listen_slots(peer, tgid)
if len(listen) > 1:
ws = int(wire_slot)
if ws in listen:
return [ws]
return [int(peer_downlink_voice_slot(peer, wire_slot, tgid))]
if listen:
return listen
if peer_receives_group_tgid(peer, wire_slot, tgid):
return [peer_downlink_voice_slot(peer, wire_slot, tgid)]
return [wire_slot]
return [int(wire_slot)]

@ -25,6 +25,7 @@ from __future__ import annotations
from adn_server.application.routing.downlink import (
DownlinkContext,
iter_downlink_voice_slots,
peer_listen_slots,
peer_slot_blocks_downlink,
synthetic_group_dmrd_route_packet,
touch_peer_voice_slot,
@ -169,10 +170,20 @@ def test_inject_only_hangtime_blocks_static_tg_after_dynamic_tx() -> None:
assert not peer_slot_blocks_downlink(ctx, peer_id, peer, same7306, pkt_time=now + 8)
def test_same_static_tg_on_both_slots_delivers_twice() -> None:
"""P4: TG listed in TS1 and TS2 OPTIONS maps to both voice slots."""
def test_same_static_tg_on_both_slots_delivers_once_on_wire_slot() -> None:
"""TG on TS1+TS2 OPTIONS: one DMRD on the bridge wire slot (not dual fan-out)."""
peer = {"OPTIONS": b"TS1=730444;TS2=730444;"}
assert iter_downlink_voice_slots(peer, 1, 730444) == [1, 2]
assert iter_downlink_voice_slots(peer, 1, 730444) == [1]
assert iter_downlink_voice_slots(peer, 2, 730444) == [2]
# Subscription still visible on both; delivery collapses in iter_*.
assert peer_listen_slots(peer, 730444) == [1, 2]
def test_static_tg_on_one_slot_unchanged() -> None:
peer_ts2 = {"OPTIONS": b"TS2=9140;"}
assert iter_downlink_voice_slots(peer_ts2, 1, 9140) == [2]
peer_ts1 = {"OPTIONS": b"TS1=9140;"}
assert iter_downlink_voice_slots(peer_ts1, 2, 9140) == [1]
def test_hp3icc_style_slot_busy_until_vterm() -> None:

Loading…
Cancel
Save

Powered by TurnKey Linux.