Re-apply slot contention when a MASTER holds TX_RFS=5000 for VHEAD/TTS announcements so inject-only OBP deferral cannot interleave and chop audio.pull/38/head
parent
ab28cc725e
commit
cae5d57d8e
@ -0,0 +1,50 @@
|
||||
# ADN DMR Peer Server - server broadcast slot contention
|
||||
#
|
||||
# Copyright (C) 2026 Rodrigo Pérez, CE5RPY <ce5rpy@qmd.cl>
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from adn_server.application.routing.helpers import (
|
||||
hbp_slot_blocks_group_voice,
|
||||
master_slot_holds_server_broadcast,
|
||||
)
|
||||
from adn_server.domain import HBPF_SLT_VHEAD, HBPF_SLT_VTERM, STREAM_TO, bytes_3, bytes_4
|
||||
|
||||
|
||||
def test_master_slot_holds_server_broadcast_detects_announcement_row() -> None:
|
||||
slot = {
|
||||
"TX_TYPE": HBPF_SLT_VHEAD,
|
||||
"TX_TIME": 100.0,
|
||||
"TX_RFS": bytes_3(5000),
|
||||
"TX_TGID": bytes_3(91),
|
||||
}
|
||||
assert master_slot_holds_server_broadcast(slot, 100.1) is True
|
||||
assert master_slot_holds_server_broadcast(slot, 100.0 + STREAM_TO + 1) is False
|
||||
|
||||
|
||||
def test_master_slot_holds_server_broadcast_ignores_obp_tx() -> None:
|
||||
slot = {
|
||||
"TX_TYPE": HBPF_SLT_VHEAD,
|
||||
"TX_TIME": 100.0,
|
||||
"TX_RFS": bytes_3(3340062),
|
||||
"TX_TGID": bytes_3(7144),
|
||||
}
|
||||
assert master_slot_holds_server_broadcast(slot, 100.1) is False
|
||||
|
||||
|
||||
def test_obp_blocked_when_server_broadcast_holds_slot() -> None:
|
||||
slot = {
|
||||
"RX_TYPE": HBPF_SLT_VTERM,
|
||||
"TX_TYPE": HBPF_SLT_VHEAD,
|
||||
"TX_TIME": 200.0,
|
||||
"TX_RFS": bytes_3(5000),
|
||||
"TX_TGID": bytes_3(91),
|
||||
}
|
||||
blocked = hbp_slot_blocks_group_voice(
|
||||
slot,
|
||||
bytes_3(7144),
|
||||
bytes_4(0x11111111),
|
||||
200.05,
|
||||
group_hangtime=3.0,
|
||||
)
|
||||
assert blocked is True
|
||||
@ -0,0 +1,71 @@
|
||||
# ADN DMR Peer Server - OBP vs server announcement slot contention
|
||||
#
|
||||
# Copyright (C) 2026 Rodrigo Pérez, CE5RPY <ce5rpy@qmd.cl>
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from tests.harness.deterministic import DeterministicScenario, PacketSpec, patch_routing_wall_time
|
||||
from tests.harness.scenarios import obp_bridge_scenario
|
||||
|
||||
from adn_server.domain import HBPF_SLT_VHEAD, HBPF_SLT_VTERM, bytes_3, bytes_4
|
||||
|
||||
|
||||
def _seed_server_broadcast_slot(scenario: DeterministicScenario, *, ann_tg: int = 91) -> None:
|
||||
proto = scenario.protocols["MASTER-A"]
|
||||
t = scenario.clock.time()
|
||||
proto.STATUS[2] = {
|
||||
"RX_TYPE": HBPF_SLT_VTERM,
|
||||
"TX_TYPE": HBPF_SLT_VHEAD,
|
||||
"TX_TIME": t,
|
||||
"TX_RFS": bytes_3(5000),
|
||||
"TX_TGID": bytes_3(ann_tg),
|
||||
"TX_STREAM_ID": bytes_4(0xA0A0A0A0),
|
||||
}
|
||||
|
||||
|
||||
def test_obp_blocked_while_server_broadcast_holds_master_ts2() -> None:
|
||||
"""Legacy parity: OBP must not route onto TS2 while announcement holds TX row."""
|
||||
scenario = obp_bridge_scenario("OBP-CL", tg=7144)
|
||||
scenario.config["PROXY"] = {"TARGET_SYSTEM": "MASTER-A"}
|
||||
scenario.config["SYSTEMS"]["MASTER-A"]["PEERS"] = {
|
||||
bytes_4(730002): {"CONNECTION": "YES", "OPTIONS": b"TS2=7144;"},
|
||||
}
|
||||
_seed_server_broadcast_slot(scenario, ann_tg=91)
|
||||
base = PacketSpec(
|
||||
peer_id=73010,
|
||||
rf_src=3340062,
|
||||
dst_id=7144,
|
||||
slot=1,
|
||||
stream_id=0x11111111,
|
||||
)
|
||||
with patch_routing_wall_time(scenario.clock):
|
||||
scenario.inject_obp("OBP-CL", DeterministicScenario.voice_head_spec(base))
|
||||
assert scenario.capture.for_system("MASTER-A") == []
|
||||
|
||||
|
||||
def test_obp_still_forwards_when_no_server_broadcast_hold() -> None:
|
||||
"""Inject-only defer remains when the slot is not held by server voice."""
|
||||
scenario = obp_bridge_scenario("OBP-CL", tg=7144)
|
||||
scenario.config["PROXY"] = {"TARGET_SYSTEM": "MASTER-A"}
|
||||
scenario.config["SYSTEMS"]["MASTER-A"]["PEERS"] = {
|
||||
bytes_4(730002): {"CONNECTION": "YES", "OPTIONS": b"TS2=7144;"},
|
||||
}
|
||||
proto = scenario.protocols["MASTER-A"]
|
||||
t = scenario.clock.time()
|
||||
proto.STATUS[2] = {
|
||||
"RX_TYPE": HBPF_SLT_VTERM,
|
||||
"TX_TYPE": HBPF_SLT_VTERM,
|
||||
"TX_TIME": t - 60.0,
|
||||
"TX_RFS": bytes_3(3340062),
|
||||
"TX_TGID": bytes_3(7144),
|
||||
}
|
||||
base = PacketSpec(
|
||||
peer_id=73010,
|
||||
rf_src=3340062,
|
||||
dst_id=7144,
|
||||
slot=1,
|
||||
stream_id=0x22222222,
|
||||
)
|
||||
with patch_routing_wall_time(scenario.clock):
|
||||
scenario.inject_obp("OBP-CL", DeterministicScenario.voice_head_spec(base))
|
||||
assert len(scenario.capture.for_system("MASTER-A")) > 0
|
||||
Loading…
Reference in new issue