"""Message-paths tests: matching a chat message back to its logged arrivals. Channel arrivals are matched by decrypting overheard GRP_TXT frames with the channel's own key (firmware-shaped frames are built here exactly as the packet-viewer tests build theirs); direct arrivals are matched by time alone. Everything runs against a real temporary repository, so the window query is exercised too. """ from __future__ import annotations from datetime import timedelta from pathlib import Path from Crypto.Cipher import AES from Crypto.Hash import HMAC, SHA256 from meshterm.core.channels import channel_hash, derive_secret from meshterm.core.models import ChatMessage, Observation, utcnow from meshterm.persistence.repository import Repository from meshterm.services.message_paths import ( channel_arrivals, direct_frames_near, distinct_paths, ) SECRET = derive_secret("#general") def _grp_txt_raw(secret: bytes, text: str, *, attempt: int = 0) -> dict: """A firmware-shaped GRP_TXT packet payload for ``text``, encrypted under ``secret``.""" plain = (0).to_bytes(4, "little") + bytes([attempt]) + text.encode("utf-8") plain += b"\x00" * (-len(plain) % 16) crypted = AES.new(secret, AES.MODE_ECB).encrypt(plain) mac = HMAC.new(secret, digestmod=SHA256) mac.update(crypted) return { "payload_typename": "GRP_TXT", "chan_hash": channel_hash(secret), "cipher_mac": mac.digest()[:2].hex(), "crypted": crypted.hex(), } def _repo(tmp_path: Path) -> tuple[Repository, int]: repo = Repository(tmp_path / "test.db") run_id = repo.start_run("monitor", {}, None) return repo, run_id def _record_frame(repo: Repository, run_id: int, *, when, path: str, raw: dict, snr=2.0): repo.record_observation( run_id, Observation(node=None, kind="packet", path=path, snr=snr, observed_at=when, raw=raw), ) def test_channel_arrivals_match_by_decrypted_content(tmp_path: Path) -> None: """Every overheard copy of a channel message surfaces, each with its own path.""" repo, run = _repo(tmp_path) now = utcnow() wire = "Alice: hi mesh" _record_frame(repo, run, when=now - timedelta(seconds=5), path="3d63", raw=_grp_txt_raw(SECRET, wire)) _record_frame(repo, run, when=now - timedelta(seconds=3), path="3d63,a1b2", raw=_grp_txt_raw(SECRET, wire)) _record_frame(repo, run, when=now - timedelta(seconds=1), path="", raw=_grp_txt_raw(SECRET, "Alice: other")) message = ChatMessage(text=wire, is_channel=True, created_at=now) arrivals = channel_arrivals(repo, message, channel_name="#general", secret=SECRET) assert [a.hops for a in arrivals] == [("3d63",), ("3d63", "a1b2")] assert distinct_paths(arrivals) == 2 repo.close() def test_channel_arrivals_tolerate_the_sender_prefix_on_the_wire(tmp_path: Path) -> None: """Our own outbound message (stored as typed) matches its prefixed on-air copies.""" repo, run = _repo(tmp_path) now = utcnow() _record_frame( repo, run, when=now + timedelta(seconds=2), path="3d63", raw=_grp_txt_raw(SECRET, "Homestead: on my way"), ) message = ChatMessage(text="on my way", outbound=True, is_channel=True, created_at=now) arrivals = channel_arrivals(repo, message, channel_name="#general", secret=SECRET) assert len(arrivals) == 1 and arrivals[0].hops == ("3d63",) repo.close() def test_channel_arrivals_carry_the_resend_counter(tmp_path: Path) -> None: """A decrypted frame's resend counter rides along, telling copies from retries.""" repo, run = _repo(tmp_path) now = utcnow() _record_frame(repo, run, when=now, path="", raw=_grp_txt_raw(SECRET, "Alice: hi", attempt=0)) _record_frame( repo, run, when=now + timedelta(seconds=9), path="", raw=_grp_txt_raw(SECRET, "Alice: hi", attempt=1), ) message = ChatMessage(text="Alice: hi", is_channel=True, created_at=now) arrivals = channel_arrivals(repo, message, channel_name="#general", secret=SECRET) assert [a.resend for a in arrivals] == [0, 1] assert distinct_paths(arrivals) == 1 # both arrived direct repo.close() def test_channel_arrivals_ignore_frames_outside_the_window(tmp_path: Path) -> None: """A matching frame far outside the message's window is someone else's message.""" repo, run = _repo(tmp_path) now = utcnow() _record_frame( repo, run, when=now - timedelta(hours=2), path="3d63", raw=_grp_txt_raw(SECRET, "Alice: hi"), ) message = ChatMessage(text="Alice: hi", is_channel=True, created_at=now) assert channel_arrivals(repo, message, channel_name="#general", secret=SECRET) == [] repo.close() def test_direct_frames_match_by_time_only(tmp_path: Path) -> None: """Direct frames are encrypted, so the window's TXT_MSG frames are the evidence.""" repo, run = _repo(tmp_path) now = utcnow() _record_frame( repo, run, when=now + timedelta(seconds=3), path="3d63", raw={"payload_typename": "TXT_MSG"}, ) _record_frame( # a channel frame in the window is not direct-message evidence repo, run, when=now + timedelta(seconds=4), path="", raw=_grp_txt_raw(SECRET, "Alice: hi"), ) _record_frame( # a direct frame far outside the tight window doesn't correlate repo, run, when=now + timedelta(minutes=10), path="", raw={"payload_typename": "TXT_MSG"}, ) message = ChatMessage(text="see you at 8", peer="d4e5", created_at=now) arrivals = direct_frames_near(repo, message) assert len(arrivals) == 1 and arrivals[0].hops == ("3d63",) repo.close()