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:
agessaman committed 2026-09-09 12:58:37 -07:00
1 parent 47e49e64aa
commit c91b331c93
2 files changed
+59

No files matched your search

+6
View File
@@ -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]:
+53
View File
@@ -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