61 lines
2.1 KiB
Python
61 lines
2.1 KiB
Python
"""Shared message bus + loop-prevention core for the archy-messh Phase 0 bridge.
|
|
|
|
Each protocol adapter runs on its own thread/event loop (Meshtastic's pubsub
|
|
callbacks fire from its internal reader thread, MeshCore is asyncio-native,
|
|
Reticulum/LXMF invoke callbacks from RNS's internal transport thread). All
|
|
three push into this single thread-safe queue so one dispatcher can fan
|
|
messages out across protocols without each adapter needing to know about
|
|
the others.
|
|
"""
|
|
|
|
import hashlib
|
|
import queue
|
|
import threading
|
|
import time
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass
|
|
class BridgeMessage:
|
|
network: str # "meshtastic" | "meshcore" | "reticulum"
|
|
sender: str
|
|
text: str
|
|
received_at: float
|
|
|
|
|
|
class MessageBus:
|
|
"""Thread-safe inbox with content-hash dedup to prevent repeat loops.
|
|
|
|
Dedup is content-hash based (not per-network packet-id based), since the
|
|
whole point of the bridge is that the same text shows up natively on all
|
|
three networks once relayed. A short TTL window is enough to stop the
|
|
obvious loop (bridge re-hears its own relayed copy) without needing any
|
|
cross-protocol message-id scheme, which doesn't exist.
|
|
"""
|
|
|
|
def __init__(self, dedup_ttl_seconds: float = 300.0):
|
|
self._queue: "queue.Queue[BridgeMessage]" = queue.Queue()
|
|
self._dedup_ttl = dedup_ttl_seconds
|
|
self._seen: dict[str, float] = {}
|
|
self._lock = threading.Lock()
|
|
|
|
@staticmethod
|
|
def _content_hash(text: str) -> str:
|
|
return hashlib.sha256(text.strip().encode("utf-8")).hexdigest()
|
|
|
|
def publish(self, message: BridgeMessage) -> None:
|
|
self._queue.put(message)
|
|
|
|
def is_duplicate(self, text: str) -> bool:
|
|
h = self._content_hash(text)
|
|
now = time.monotonic()
|
|
with self._lock:
|
|
self._seen = {k: v for k, v in self._seen.items() if v > now}
|
|
if h in self._seen:
|
|
return True
|
|
self._seen[h] = now + self._dedup_ttl
|
|
return False
|
|
|
|
def get(self, timeout: float | None = None) -> BridgeMessage:
|
|
return self._queue.get(timeout=timeout)
|