fix: echo TG 9990 isolation for multi-peer inject proxy (#32)
Echo (TG 9990-9999) must be point-to-point: only the originating hotspot receives the playback. Two bugs in the inject-only proxy broke this: Data plane (udp_hbp.py): _peer_should_receive_dmrd had a fuzzy-match fallback (peer_id // 100 == rf_src) for special TGs that leaked the first VHEAD to every hotspot sharing the user's DMR-id prefix. Removed the fallback so echo delivers only to the exact RX_PEER. Report plane (monitor_topology.py): _echo_tx_target_peer resolved the monitor chip peer via fuzzy rf_src matching, which picked the wrong hotspot when a user has several radios sharing a base id (e.g. rf_src 7140023 matched peer 714002301 instead of the real originator 714000103). Now resolves from STATUS[slot].RX_PEER when available.pull/33/head
parent
a1c8ff03f8
commit
86983597b2
@ -0,0 +1,123 @@
|
|||||||
|
# ADN DMR Peer Server - echo / special TG downlink filter non-regression
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Rodrigo Pérez, CE5RPY <ce5rpy@qmd.cl>
|
||||||
|
#
|
||||||
|
# Regression test: TG 9990 (echo) and special TGs 9990-9999 must only be
|
||||||
|
# delivered to the exact peer that originated the call, never replicated
|
||||||
|
# to other hotspots of the same user (same DMR ID base).
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from adn_server.application.routing.helpers import synthetic_group_dmrd_route_packet
|
||||||
|
from adn_server.domain import bytes_3, bytes_4
|
||||||
|
from adn_server.infrastructure.config_normalizer import ensure_system_runtime_config
|
||||||
|
from adn_server.infrastructure.twisted_adapters.udp_hbp import HBPProtocol
|
||||||
|
|
||||||
|
|
||||||
|
def _master_config() -> dict:
|
||||||
|
config = {
|
||||||
|
"GLOBAL": {"USE_ACL": False},
|
||||||
|
"SYSTEMS": {
|
||||||
|
"TEST": {
|
||||||
|
"MODE": "MASTER",
|
||||||
|
"ENABLED": True,
|
||||||
|
"MAX_PEERS": 8,
|
||||||
|
"GROUP_HANGTIME": 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ensure_system_runtime_config(config)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def _make_echo_packet(rf_src_base: int, tgid: int = 9990) -> bytes:
|
||||||
|
"""Build a DMRD group packet for TG 9990 with a given rf_src (3-byte base)."""
|
||||||
|
pkt = synthetic_group_dmrd_route_packet(2, tgid)
|
||||||
|
return pkt[:5] + bytes_3(rf_src_base) + pkt[8:]
|
||||||
|
|
||||||
|
|
||||||
|
def test_echo_9990_only_to_originating_peer() -> None:
|
||||||
|
"""Echo playback must deliver ONLY to the RX_PEER that originated the call."""
|
||||||
|
config = _master_config()
|
||||||
|
proto = HBPProtocol("TEST", config)
|
||||||
|
peer_a = bytes_4(730039101)
|
||||||
|
peer_b = bytes_4(730039210)
|
||||||
|
proto._peers = {
|
||||||
|
peer_a: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62031)},
|
||||||
|
peer_b: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62032)},
|
||||||
|
}
|
||||||
|
# Simulate: peer_a transmitted to TG 9990 on slot 2 (RX_PEER set by dmrd_received).
|
||||||
|
proto.STATUS[2] = {
|
||||||
|
"RX_PEER": peer_a,
|
||||||
|
"RX_TGID": bytes_3(9990),
|
||||||
|
"RX_STREAM_ID": b"\x00\x00\x00\x01",
|
||||||
|
}
|
||||||
|
rf_src_base = 7300392 # both peers share this base ID (same user)
|
||||||
|
echo_pkt = _make_echo_packet(rf_src_base, 9990)
|
||||||
|
assert proto._peer_should_receive_dmrd(peer_a, echo_pkt)
|
||||||
|
assert not proto._peer_should_receive_dmrd(peer_b, echo_pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_echo_9990_no_fuzzy_match_when_no_rx_peer() -> None:
|
||||||
|
"""When RX_PEER is not set, echo must NOT use fuzzy rf_src matching.
|
||||||
|
|
||||||
|
With multiple peers connected, no peer should receive the echo if the
|
||||||
|
originating RX_PEER is unknown (single-peer fallback is the only exception).
|
||||||
|
"""
|
||||||
|
config = _master_config()
|
||||||
|
proto = HBPProtocol("TEST", config)
|
||||||
|
peer_a = bytes_4(730039101)
|
||||||
|
peer_b = bytes_4(730039210)
|
||||||
|
proto._peers = {
|
||||||
|
peer_a: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62031)},
|
||||||
|
peer_b: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62032)},
|
||||||
|
}
|
||||||
|
# RX_PEER not set (stale / different TG on slot)
|
||||||
|
proto.STATUS[2] = {
|
||||||
|
"RX_PEER": b"\x00\x00\x00\x00",
|
||||||
|
"RX_TGID": bytes_3(0),
|
||||||
|
"RX_STREAM_ID": b"\x00\x00\x00\x00",
|
||||||
|
}
|
||||||
|
rf_src_base = 7300392
|
||||||
|
echo_pkt = _make_echo_packet(rf_src_base, 9990)
|
||||||
|
assert not proto._peer_should_receive_dmrd(peer_a, echo_pkt)
|
||||||
|
assert not proto._peer_should_receive_dmrd(peer_b, echo_pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_echo_9990_single_peer_fallback() -> None:
|
||||||
|
"""When only one peer is connected, echo is delivered to it (legacy parity)."""
|
||||||
|
config = _master_config()
|
||||||
|
proto = HBPProtocol("TEST", config)
|
||||||
|
peer_a = bytes_4(730039101)
|
||||||
|
proto._peers = {
|
||||||
|
peer_a: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62031)},
|
||||||
|
}
|
||||||
|
proto.STATUS[2] = {
|
||||||
|
"RX_PEER": b"\x00\x00\x00\x00",
|
||||||
|
"RX_TGID": bytes_3(0),
|
||||||
|
"RX_STREAM_ID": b"\x00\x00\x00\x00",
|
||||||
|
}
|
||||||
|
rf_src_base = 7300392
|
||||||
|
echo_pkt = _make_echo_packet(rf_src_base, 9990)
|
||||||
|
assert proto._peer_should_receive_dmrd(peer_a, echo_pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_special_tg_9991_same_isolation() -> None:
|
||||||
|
"""All special TGs 9990-9999 share the same point-to-point isolation."""
|
||||||
|
config = _master_config()
|
||||||
|
proto = HBPProtocol("TEST", config)
|
||||||
|
peer_a = bytes_4(730039101)
|
||||||
|
peer_b = bytes_4(730039210)
|
||||||
|
proto._peers = {
|
||||||
|
peer_a: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62031)},
|
||||||
|
peer_b: {"CONNECTION": "YES", "OPTIONS": b"", "SOCKADDR": ("127.0.0.1", 62032)},
|
||||||
|
}
|
||||||
|
proto.STATUS[2] = {
|
||||||
|
"RX_PEER": peer_b,
|
||||||
|
"RX_TGID": bytes_3(9991),
|
||||||
|
"RX_STREAM_ID": b"\x00\x00\x00\x01",
|
||||||
|
}
|
||||||
|
rf_src_base = 7300392
|
||||||
|
pkt = _make_echo_packet(rf_src_base, 9991)
|
||||||
|
assert proto._peer_should_receive_dmrd(peer_b, pkt)
|
||||||
|
assert not proto._peer_should_receive_dmrd(peer_a, pkt)
|
||||||
Loading…
Reference in new issue