From dbfdf1c8510dacd6933cb5add362db6cf018d46f Mon Sep 17 00:00:00 2001 From: Dorian Date: Wed, 1 Jul 2026 15:31:52 +0100 Subject: [PATCH] fix: close relay loop from prefixed dedup mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dispatcher deduped on inbound text but relayed a prefixed copy ("[net/sender] text"), so a re-heard relayed message hashed differently and was re-relayed, stacking another prefix each hop — an unbounded loop, exactly what the dedup was meant to prevent. Add MessageBus.mark_seen() and record the on-air relayed string before sending, so the bridge recognises and drops its own relayed copies. Co-Authored-By: Claude Opus 4.8 (1M context) --- host-bridge/bridge_core.py | 16 ++++++++++++++++ host-bridge/main.py | 7 ++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/host-bridge/bridge_core.py b/host-bridge/bridge_core.py index 90e21f0..9656d72 100644 --- a/host-bridge/bridge_core.py +++ b/host-bridge/bridge_core.py @@ -56,5 +56,21 @@ class MessageBus: self._seen[h] = now + self._dedup_ttl return False + def mark_seen(self, text: str) -> None: + """Record text as already-seen without reporting duplicate status. + + The dispatcher relays a *prefixed* copy of each message (e.g. + "[meshtastic/x] hello"), not the original text. Dedup is content-hash + based, so unless the exact on-air relayed string is recorded here, the + bridge won't recognise its own relayed copy when it echoes back from + another network — it would re-relay it, stacking another prefix each + hop (an unbounded loop). Marking every outbound string seen closes + that loop. + """ + h = self._content_hash(text) + now = time.monotonic() + with self._lock: + self._seen[h] = now + self._dedup_ttl + def get(self, timeout: float | None = None) -> BridgeMessage: return self._queue.get(timeout=timeout) diff --git a/host-bridge/main.py b/host-bridge/main.py index 99f51a1..abddd17 100644 --- a/host-bridge/main.py +++ b/host-bridge/main.py @@ -82,11 +82,16 @@ def main() -> int: if bus.is_duplicate(message.text): continue print(f"[{message.network}] {message.sender}: {message.text}") + relayed = f"[{message.network}/{message.sender}] {message.text}" + # Record the exact on-air string so that if this relayed copy is + # re-heard from another network it's recognised as a duplicate and + # not re-relayed (which would stack another prefix each hop). + bus.mark_seen(relayed) for network, adapter in adapters.items(): if network == message.network: continue # don't echo back onto the network it came from try: - adapter.send(f"[{message.network}/{message.sender}] {message.text}") + adapter.send(relayed) except Exception as exc: # noqa: BLE001 print(f" ! failed to relay onto {network}: {exc}", file=sys.stderr) except KeyboardInterrupt: