mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-10-31 04:01:59 +01:00
fix(engine): preserve scope on relayed flood packets
After mesh.default_region was wired to the core dispatcher's send-time scope resolver, a relayed plain flood was indistinguishable from a locally originated one, so Dispatcher._apply_flood_scope() converted it to the repeater's default transport scope and sent it out-of-region. Successful flood forwards now mark their existing scope decision as authoritative before entering Dispatcher.send_packet(): relayed plain floods stay plain and relayed transport floods retain the originator's codes. Locally originated floods and REPLY_SCOPE_DEFAULT fallback replies still pick up the configured default scope. Marking only on the flood_forward() success path leaves packets rejected by validation, policy, duplicate, or loop detection untouched, and leaves locally injected transmissions (which bypass forwarding) alone.
This commit is contained in:
1 parent
47e49e64aa
commit
c91b331c93
2 files changed
+59
No files matched your search
@@ -1197,6 +1197,12 @@ class RepeaterHandler(BaseHandler):
|
||||
packet.path.extend(self.local_hash_bytes[:hash_size])
|
||||
packet.path_len = PathUtils.encode_path_len(hash_size, hop_count + 1)
|
||||
|
||||
# This is a transit packet received over RF. Its existing scope decision
|
||||
# is authoritative: a plain flood must remain plain, and a transport
|
||||
# flood must retain the originator's codes when the shared dispatcher
|
||||
# performs its send-time scope resolution.
|
||||
packet._flood_scope_applied = True
|
||||
|
||||
return packet
|
||||
|
||||
def direct_forward(self, packet: Packet, packet_hash: Optional[str] = None) -> Optional[Packet]:
|
||||
|
||||
@@ -13,6 +13,7 @@ import time
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from openhop_core.node.dispatcher import Dispatcher
|
||||
from openhop_core.protocol import Packet, PacketBuilder
|
||||
from openhop_core.protocol.constants import (
|
||||
MAX_PATH_SIZE,
|
||||
@@ -27,6 +28,7 @@ from openhop_core.protocol.constants import (
|
||||
ROUTE_TYPE_TRANSPORT_FLOOD,
|
||||
)
|
||||
from openhop_core.protocol.packet_utils import PathUtils
|
||||
from openhop_core.protocol.transport_keys import get_auto_key_for
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers — build minimal config / mocks needed by RepeaterHandler.__init__
|
||||
@@ -106,6 +108,13 @@ def _make_dispatcher(radio=None):
|
||||
return dispatcher
|
||||
|
||||
|
||||
def _make_scope_resolver(default_region: str = "#washington") -> Dispatcher:
|
||||
"""Create the real core TX resolver used after repeater forwarding."""
|
||||
dispatcher = Dispatcher(_make_radio())
|
||||
dispatcher.default_flood_transport_key = get_auto_key_for(default_region)
|
||||
return dispatcher
|
||||
|
||||
|
||||
def _make_handler_with_hash(local_hash_bytes: bytes):
|
||||
"""Create a RepeaterHandler whose local path hash is exactly local_hash_bytes."""
|
||||
with (
|
||||
@@ -317,6 +326,50 @@ class TestFloodForward:
|
||||
assert result is not None
|
||||
assert list(result.path) == [LOCAL_HASH]
|
||||
|
||||
def test_relayed_plain_flood_does_not_inherit_dispatcher_default(self, handler):
|
||||
"""A repeater preserves the originator's deliberate unscoped flood."""
|
||||
packet = _make_flood_packet(payload=b"remote unscoped message")
|
||||
|
||||
forwarded = handler.flood_forward(packet)
|
||||
|
||||
assert forwarded is not None
|
||||
assert forwarded._flood_scope_applied is True
|
||||
|
||||
resolver = _make_scope_resolver("#washington")
|
||||
resolver._apply_flood_scope(forwarded)
|
||||
|
||||
assert forwarded.get_route_type() == ROUTE_TYPE_FLOOD
|
||||
assert forwarded.transport_codes == [0, 0]
|
||||
|
||||
def test_relayed_transport_flood_preserves_foreign_scope(self, handler):
|
||||
"""A local default cannot replace the transport codes received over RF."""
|
||||
packet = _make_transport_flood_packet(
|
||||
payload=b"remote scoped message",
|
||||
transport_codes=(0x1234, 0x5678),
|
||||
)
|
||||
|
||||
with patch.object(handler, "_check_transport_codes", return_value=(True, "")):
|
||||
forwarded = handler.flood_forward(packet)
|
||||
|
||||
assert forwarded is not None
|
||||
assert forwarded._flood_scope_applied is True
|
||||
|
||||
resolver = _make_scope_resolver("#washington")
|
||||
resolver._apply_flood_scope(forwarded)
|
||||
|
||||
assert forwarded.get_route_type() == ROUTE_TYPE_TRANSPORT_FLOOD
|
||||
assert forwarded.transport_codes == [0x1234, 0x5678]
|
||||
|
||||
def test_fresh_local_flood_still_inherits_dispatcher_default(self):
|
||||
"""The relay fix must not weaken default scoping for local sends."""
|
||||
packet = _make_flood_packet(payload=b"locally originated message")
|
||||
resolver = _make_scope_resolver("#washington")
|
||||
|
||||
resolver._apply_flood_scope(packet)
|
||||
|
||||
assert packet.get_route_type() == ROUTE_TYPE_TRANSPORT_FLOOD
|
||||
assert packet.transport_codes[0] != 0
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# 2. direct_forward
|
||||
|
||||
Reference in new issue
Block a user