mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-26 19:42:46 +02:00
fix(trace): gate intermediate-hop trace relay on forward mode
Firmware only relays a TRACE at an intermediate hop when allowPacketForward passes, so disabling forwarding stops trace relay. The trace helper forwarded via packet injection, which is gated only by the local-TX check — a repeater in monitor mode kept repeating traces while reporting repeat off. Consult the repeater mode before relaying; locally originated pings are injected directly and keep working in monitor mode, and ping-response matching still runs before the relay decision.
This commit is contained in:
@@ -135,11 +135,16 @@ class TraceHelper:
|
||||
logger.info(f"Path SNRs: [{', '.join(path_snrs)}], Hashes: [{', '.join(path_hashes)}]")
|
||||
|
||||
should_forward = self._should_forward_trace(packet, trace_bytes, flags, hash_width)
|
||||
mode_blocked = should_forward and not self._relay_allowed()
|
||||
if mode_blocked:
|
||||
should_forward = False
|
||||
logger.info("Trace relay suppressed (repeat disabled)")
|
||||
|
||||
if should_forward:
|
||||
await self._forward_trace_packet(packet, num_hops)
|
||||
else:
|
||||
self._log_no_forward_reason(packet, trace_bytes, hash_width)
|
||||
if not mode_blocked:
|
||||
self._log_no_forward_reason(packet, trace_bytes, hash_width)
|
||||
if (
|
||||
self.on_trace_complete
|
||||
and self._is_trace_complete(packet, trace_bytes, hash_width)
|
||||
@@ -271,6 +276,20 @@ class TraceHelper:
|
||||
|
||||
return path_snrs, path_hashes
|
||||
|
||||
def _relay_allowed(self) -> bool:
|
||||
"""Firmware gates intermediate-hop TRACE relay on ``allowPacketForward``
|
||||
(Mesh.cpp), so a repeater that is not forwarding (monitor / no_tx mode)
|
||||
must not relay traces. Locally originated pings are injected directly
|
||||
and never pass through this gate.
|
||||
"""
|
||||
config = getattr(self.repeater_handler, "config", None)
|
||||
mode = "forward"
|
||||
if isinstance(config, dict):
|
||||
mode = config.get("repeater", {}).get("mode", "forward")
|
||||
if mode not in ("forward", "monitor", "no_tx"):
|
||||
mode = "forward"
|
||||
return mode == "forward"
|
||||
|
||||
def _should_forward_trace(
|
||||
self, packet, trace_bytes: bytes, flags: int, hash_width: int
|
||||
) -> bool:
|
||||
|
||||
@@ -201,6 +201,70 @@ async def test_trace_helper_forward_trace_packet_updates_recent_record_and_injec
|
||||
packet_injector.assert_awaited_once()
|
||||
|
||||
|
||||
def _trace_relay_helper(mode, forward=True):
|
||||
"""TraceHelper wired so _should_forward_trace says yes; mode comes from config."""
|
||||
repeater_handler = MagicMock()
|
||||
repeater_handler.is_duplicate.return_value = False
|
||||
repeater_handler.calculate_packet_score.return_value = 0.9
|
||||
repeater_handler.config = {"repeater": {"mode": mode}}
|
||||
helper = TraceHelper(
|
||||
local_hash=0x42,
|
||||
local_identity=FakeIdentity(0x42),
|
||||
repeater_handler=repeater_handler,
|
||||
)
|
||||
helper._forward_trace_packet = AsyncMock()
|
||||
helper._extract_path_info = MagicMock(return_value=([], []))
|
||||
helper._should_forward_trace = MagicMock(return_value=forward)
|
||||
helper.trace_handler._parse_trace_payload = MagicMock(
|
||||
return_value={
|
||||
"valid": True,
|
||||
"trace_path_bytes": b"\x42\x43",
|
||||
"flags": 0,
|
||||
"trace_hops": [b"\x42", b"\x43"],
|
||||
"trace_path": [0x42, 0x43],
|
||||
"tag": 1234,
|
||||
}
|
||||
)
|
||||
helper.trace_handler._format_trace_response = MagicMock(return_value="trace ok")
|
||||
return helper
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trace_relay_suppressed_in_monitor_and_no_tx_mode():
|
||||
# Firmware gates TRACE relay on allowPacketForward, so a repeater with
|
||||
# repeat off must not relay an intermediate-hop trace.
|
||||
for mode in ("monitor", "no_tx"):
|
||||
helper = _trace_relay_helper(mode)
|
||||
packet = DummyPacket(path=b"\x01", payload=b"\xaa\xbb\xcc")
|
||||
await helper.process_trace_packet(packet)
|
||||
helper._forward_trace_packet.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trace_relay_allowed_in_forward_and_unknown_mode():
|
||||
for mode in ("forward", "weird-mode"):
|
||||
helper = _trace_relay_helper(mode)
|
||||
packet = DummyPacket(path=b"\x01", payload=b"\xaa\xbb\xcc")
|
||||
await helper.process_trace_packet(packet)
|
||||
helper._forward_trace_packet.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trace_ping_response_still_matches_in_monitor_mode():
|
||||
# Ping origination/response handling is not relay: a pending ping must
|
||||
# resolve even when the repeater is not forwarding.
|
||||
helper = _trace_relay_helper("monitor", forward=False)
|
||||
tag = 555
|
||||
helper.trace_handler._parse_trace_payload.return_value["tag"] = tag
|
||||
evt = helper.register_ping(tag, 0x42)
|
||||
|
||||
packet = DummyPacket(path=b"\x01", payload=b"\xaa\xbb\xcc")
|
||||
await helper.process_trace_packet(packet)
|
||||
|
||||
assert evt.is_set()
|
||||
helper._forward_trace_packet.assert_not_awaited()
|
||||
|
||||
|
||||
def test_trace_helper_cleanup_stale_pings():
|
||||
helper = TraceHelper(
|
||||
local_hash=0x42, local_identity=FakeIdentity(0x42), repeater_handler=MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user