diff --git a/src/adn_server/application/routing/helpers.py b/src/adn_server/application/routing/helpers.py index 470a34e..2dc518c 100644 --- a/src/adn_server/application/routing/helpers.py +++ b/src/adn_server/application/routing/helpers.py @@ -358,11 +358,20 @@ def peer_hotspot_voice_slot_busy( return True active = (peer_slots or {}).get(int(voice_slot)) if isinstance(active, dict): - if active.get("ingress"): - return True - active_stream = active.get("stream_id") incoming_tgid = int_id(incoming_tgid_b) active_tgid = int(active.get("tgid", 0) or 0) + if active.get("ingress"): + if ( + isinstance(peer, dict) + and sys_cfg is not None + and not peer_single_mode(peer, sys_cfg) + and active_tgid + and incoming_tgid != active_tgid + ): + pass + else: + return True + active_stream = active.get("stream_id") if stream_id and active_stream and active_stream == stream_id: pass elif ( @@ -374,6 +383,14 @@ def peer_hotspot_voice_slot_busy( and (pkt_time - float(active.get("time", 0) or 0)) >= STREAM_TO ): peer_slots.pop(int(voice_slot), None) + elif ( + isinstance(peer, dict) + and sys_cfg is not None + and not peer_single_mode(peer, sys_cfg) + and active_tgid + and incoming_tgid != active_tgid + ): + return False else: return True if isinstance(peer, dict) and peer_single_blocks_foreign_same_tg_downlink( @@ -1350,8 +1367,9 @@ def peer_single_blocks_foreign_same_tg_downlink( return False active = (peer_slots or {}).get(int(voice_slot)) if isinstance(active, dict) and active.get("ingress"): - if int(active.get("tgid", 0) or 0) == incoming: - return False + return True + if isinstance(peer, dict) and peer_receives_group_tgid(peer, voice_slot, incoming): + return False return True diff --git a/tests/application/test_peer_single_downlink.py b/tests/application/test_peer_single_downlink.py index 2747536..f1acb7c 100644 --- a/tests/application/test_peer_single_downlink.py +++ b/tests/application/test_peer_single_downlink.py @@ -578,6 +578,20 @@ def test_single_blocks_foreign_same_tg_while_local_ua() -> None: ) +def test_single_allows_static_same_tg_cross_rx_after_local_ptt() -> None: + """Static TG in OPTIONS: network downlink on same TG after local PTT (cross-rx).""" + from adn_server.application.routing.helpers import peer_single_blocks_foreign_same_tg_downlink + + peer = {"OPTIONS": b"TS1=730500;TS2=730501,730502,730503;SINGLE=1;TIMER=60;"} + sys_cfg = _sys_cfg() + peer_id = _peer_id() + now = 1_000_000.0 + register_peer_ua_session(peer, peer_id, 1, 730500, sys_cfg, now=now) + assert not peer_single_blocks_foreign_same_tg_downlink( + peer, peer_id, 1, bytes_3(730500), None, sys_cfg, now=now + 10, + ) + + def test_downlink_vterm_does_not_clear_local_ua_session() -> None: """Network VTERM on session TG must not wipe a local PTT lock (TIMER session).""" from adn_server.application.routing.downlink import DownlinkContext, track_peer_group_dmrd diff --git a/tests/application/test_slot_contention.py b/tests/application/test_slot_contention.py index 0f03516..698e77e 100644 --- a/tests/application/test_slot_contention.py +++ b/tests/application/test_slot_contention.py @@ -488,8 +488,8 @@ def test_peer_hotspot_hangtime_blocks_other_tg() -> None: ) -def test_single0_ingress_tx_blocks_other_static_tg_on_slot() -> None: - """SINGLE=0: local TX on one TG blocks any other downlink on the same RF slot.""" +def test_single0_ingress_tx_allows_other_static_tg_on_slot() -> None: + """SINGLE=0: RX on another OPTIONS TG while local TX on the same RF slot.""" now = 1_000_000.0 hs = bytes_4(730039253) peer = {"OPTIONS": b"TS2=730507,730508;SINGLE=0;"} @@ -503,7 +503,7 @@ def test_single0_ingress_tx_blocks_other_static_tg_on_slot() -> None: "ingress": True, }, } - assert peer_hotspot_voice_slot_busy( + assert not peer_hotspot_voice_slot_busy( hs, 2, bytes_4(0x22222222), @@ -523,7 +523,7 @@ def test_lab_witness_nine_static_tgs_blocks_second_tg_on_slot() -> None: now = 1_000_000.0 hs = bytes_4(730039257) peer = { - "OPTIONS": b"TS2=730500,730501,730502,730503,730504,730505,730506,730507,730508;", + "OPTIONS": b"TS2=730500,730501,730502,730503,730504,730505,730506,730507,730508;SINGLE=1;TIMER=60;", } sys_cfg = {"SINGLE_MODE": False, "DEFAULT_UA_TIMER": 10} slot = {