Files
MeshTerm/tests/test_message_paths.py
T
Jean-Pierre MartineauandClaude Opus 5 92d31a3191 Every file is spelled the way the formatter spells it
`ruff format .` across the repo: 153 files, no behaviour change. This is the
second gate CONTRIBUTING.md promises, and until now it reported 154 files needing
reformat — so a first contributor ran the command the guide told them to run and
could not tell their own diff from the background.

Landed on its own, with nothing else in it, because a whole-repo reformat is only
safe to skip past in blame if it is provably nothing but layout. It was checked
that way rather than assumed: every changed file was formatted in memory and
compared by full `ast.dump()`, and 152 of 153 come out structurally identical
(the one exception was a docstring opening on a quote character, fixed in the
commit before this one). The string and comment token streams were diffed
separately, so a quote flip or an edited comment could not hide inside a
re-layout — five quote-style normalisations and sixteen implicit-concatenation
joins, all value-identical, and one commented-out line gaining the space after
its `#`. `meshterm specimen` is byte-identical on both platforms, and the suite
is 1888 green, gallery gate and `test_theme16` included.

What actually changed, in order of how much of the diff it is: wrapped calls and
signatures rejoin onto one line where they fit inside the hundred columns; the
magic trailing comma expands what it is there to expand; complex slices gain
their spaces (`[len(noise) :]`); a few redundant parens go. The tables that are
laid out to be read were fenced in the previous commit and are untouched here.

The aligned trailing-comment column is gone from about ninety lanes that were not
fenced — including the `heat.*` ladder in `MESH_THEME`, which cannot be fenced
where it sits. Every comment stays on the entry it describes, so nothing there
reads wrong; it just no longer lines up.

Add this commit to `.git-blame-ignore-revs`, which follows immediately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 01:07:19 -04:00

401 lines
16 KiB
Python

"""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,
collapse,
direct_arrivals,
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 _direct_raw(dest: str = "", src: str = "", mac: str = "", route: str = "") -> dict:
"""A direct-message frame's raw payload, spelled as the meshcore library reports it."""
raw: dict = {"payload_typename": "TEXT_MSG"}
if dest:
raw["dest_hash"] = dest
if src:
raw["src_hash"] = src
if mac:
raw["cipher_mac"] = mac
if route:
raw["route_typename"] = route
return raw
def test_direct_frames_without_a_mac_fall_back_to_address_and_time(tmp_path: Path) -> None:
"""History recorded before the MAC was kept still correlates — and says that it did.
The fallback is honest evidence, not a claim: a frame between the right pair in the
right window is only *probably* this message, and ``exact`` comes back ``False`` so the
view can bill it that way.
"""
repo, run = _repo(tmp_path)
now = utcnow()
_record_frame(
repo,
run,
when=now + timedelta(seconds=3),
path="3d63",
raw=_direct_raw(dest="d4", src="a1"),
)
_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 window doesn't correlate
repo,
run,
when=now + timedelta(minutes=10),
path="",
raw=_direct_raw(dest="d4", src="a1"),
)
message = ChatMessage(text="see you at 8", outbound=True, peer="d4e5", created_at=now)
arrivals, exact = direct_arrivals(repo, message, self_key="a1" + "0" * 62, peer_key="d4e5")
assert [a.hops for a in arrivals] == [("3d63",)]
assert exact is False
repo.close()
def test_direct_frames_keep_only_the_direction_the_message_travelled(tmp_path: Path) -> None:
"""A send shows our outgoing frames; a received message shows the incoming ones.
Both ends' hashes sit on every frame of a conversation whichever way it went, so
matching on the pair alone put our own sends into a received message's view. That is
where the implausible one-hop rows came from (JP, 2026-09-02): our transmissions heard
coming back off the repeaters in earshot, correctly one hop, shown as if they were an
inbound route to us.
"""
repo, run = _repo(tmp_path)
now = utcnow()
ours = dict(dest="d4", src="a1", mac="beef") # us -> peer
theirs = dict(dest="a1", src="d4", mac="f00d") # peer -> us
_record_frame(repo, run, when=now + timedelta(seconds=1), path="3d63", raw=_direct_raw(**ours))
_record_frame(repo, run, when=now + timedelta(seconds=2), path="c0", raw=_direct_raw(**theirs))
# Someone else's traffic, overheard in the same window.
_record_frame(
repo,
run,
when=now + timedelta(seconds=3),
path="3d63",
raw=_direct_raw(dest="7f", src="c0", mac="dead"),
)
_record_frame(
repo,
run,
when=now + timedelta(seconds=4),
path="",
raw=_direct_raw(dest="a1", src="7f", mac="cafe"),
)
sent = ChatMessage(text="see you at 8", outbound=True, peer="d4e5f6a7", created_at=now)
got = ChatMessage(text="ok", outbound=False, peer="d4e5f6a7", created_at=now)
keys = dict(self_key="a1" + "0" * 62, peer_key="d4e5f6a7")
assert [a.hops for a in direct_arrivals(repo, sent, **keys)[0]] == [("3d63",)]
assert [a.hops for a in direct_arrivals(repo, got, **keys)[0]] == [("c0",)]
repo.close()
def test_a_shared_mac_separates_one_message_from_the_next(tmp_path: Path) -> None:
"""Two sends seconds apart keep their own frames — the MAC is the fingerprint.
This is the direct-message counterpart of the channel view's content match: the frames
cannot be read, but copies of one message carry the same MAC over its ciphertext and the
next message's carry a different one.
"""
repo, run = _repo(tmp_path)
now = utcnow()
# First send, retried twice and heard off two repeaters.
for offset, path in ((1, "3d63"), (1, "27d4"), (6, "3d63"), (6, "27d4")):
_record_frame(
repo,
run,
when=now + timedelta(seconds=offset),
path=path,
raw=_direct_raw(dest="d4", src="a1", mac="beef"),
)
# Second send, twelve seconds later, same pair and same paths.
for offset, path in ((13, "3d63"), (13, "27d4")):
_record_frame(
repo,
run,
when=now + timedelta(seconds=offset),
path=path,
raw=_direct_raw(dest="d4", src="a1", mac="f00d"),
)
keys = dict(self_key="a1" + "0" * 62, peer_key="d4e5f6a7")
first = ChatMessage(text="one", outbound=True, peer="d4e5f6a7", created_at=now)
second = ChatMessage(
text="two",
outbound=True,
peer="d4e5f6a7",
created_at=now + timedelta(seconds=12),
)
got, exact = direct_arrivals(repo, first, **keys)
assert exact is True
assert len(got) == 4, "the first send's four frames, and not the second's"
assert len(direct_arrivals(repo, second, **keys)[0]) == 2
repo.close()
def test_the_window_clamps_to_the_messages_going_the_same_way(tmp_path: Path) -> None:
"""Each direction is bounded by its own neighbours, and bounded differently.
Without a clamp a fast exchange put every message's frames in every other's view — nine
messages of one real conversation fell inside a single flat window. With the wrong
clamp, a *received* message got an empty one: its stamp is the sender's clock, so
ordering it against our own sends compares two clocks and can bound it by an edge that,
in its own clock, hasn't happened yet (JP, 2026-09-02).
"""
from meshterm.services.message_paths import _DIRECT_WINDOW, direct_window
repo, run = _repo(tmp_path)
now = utcnow()
sent = [now, now + timedelta(seconds=20)]
received = [now - timedelta(seconds=30), now + timedelta(seconds=50)]
for at in sent:
repo.record_chat_message(
ChatMessage(text="ours", outbound=True, peer="d4e5f6a7", created_at=at)
)
for at in received:
repo.record_chat_message(
ChatMessage(text="theirs", outbound=False, peer="d4e5f6a7", created_at=at)
)
# Our own send: our clock, so nothing of it predates it — the window opens at the
# message and runs forward to the next send, where retries actually live.
ours = ChatMessage(text="ours", outbound=True, peer="d4e5f6a7", created_at=now)
start, end = direct_window(repo, ours)
assert (now - start) < timedelta(seconds=5), "no room behind a send"
assert end == sent[1], "forward to the next send, not to the next received message"
# A received message keeps a centred window, reaching halfway to the received messages
# either side of it — and is untouched by our own sends in between.
theirs = ChatMessage(
text="theirs", outbound=False, peer="d4e5f6a7", created_at=now + timedelta(seconds=50)
)
start, end = direct_window(repo, theirs)
assert start == theirs.created_at - timedelta(seconds=40), "halfway back to the last one"
assert end == theirs.created_at + _DIRECT_WINDOW, "nothing follows, so that side stands"
repo.close()
def test_collapse_folds_a_repeated_path_into_one_counted_row(tmp_path: Path) -> None:
"""One row per path, carrying its copy count, first sighting and best SNR."""
from meshterm.services.message_paths import Arrival
base = utcnow()
arrivals = [
Arrival(when=base, hops=("3d63",), snr=10.0),
Arrival(when=base + timedelta(seconds=1), hops=("27d4",), snr=5.0),
Arrival(when=base + timedelta(seconds=5), hops=("3d63",), snr=13.5),
]
folded = collapse(arrivals)
assert [a.hops for a in folded] == [("3d63",), ("27d4",)], "first-heard order"
assert folded[0].copies == 2 and folded[1].copies == 1
assert folded[0].when == base, "the first sighting is when the path first worked"
assert folded[0].snr == 13.5, "the best reading is what the path can do"
def test_a_routed_frames_path_is_where_it_was_going_not_where_it_has_been(
tmp_path: Path,
) -> None:
"""The route type decides what ``path`` means, and an empty routed one means nothing.
A flooded packet accumulates its path — every relay appends itself — so the hops are the
route it travelled to us. A direct-routed packet carries a route its sender wrote and
its relays consume, so an empty path means the route was used up, not that the packet
crossed no relays. Read the second as the first and a message from five hops away is
drawn as having arrived out of thin air: the "impossible direct path" (JP, 2026-09-02).
"""
repo, run = _repo(tmp_path)
now = utcnow()
_record_frame( # flooded, one relay: it really did come to us that way
repo,
run,
when=now + timedelta(seconds=1),
path="3d63",
raw=_direct_raw(dest="a1", src="d4", mac="beef", route="FLOOD"),
)
_record_frame( # direct-routed, route consumed: says nothing about how it got here
repo,
run,
when=now + timedelta(seconds=2),
path="",
raw=_direct_raw(dest="a1", src="d4", mac="beef", route="DIRECT"),
)
message = ChatMessage(text="hi", outbound=False, peer="d4e5f6a7", created_at=now)
arrivals, _exact = direct_arrivals(repo, message, self_key="a1" + "0" * 62, peer_key="d4e5f6a7")
flooded, routed = arrivals
assert flooded.routed is False and flooded.route_known is True
assert routed.routed is True and routed.route_known is False, (
"an empty routed path is not a zero-hop arrival"
)
repo.close()
def test_history_without_a_route_type_is_unknown_rather_than_assumed(tmp_path: Path) -> None:
"""Frames recorded before the route type was kept read as before, not as guesses."""
repo, run = _repo(tmp_path)
now = utcnow()
_record_frame(
repo,
run,
when=now + timedelta(seconds=1),
path="",
raw=_direct_raw(dest="a1", src="d4", mac="beef"),
)
message = ChatMessage(text="hi", outbound=False, peer="d4e5f6a7", created_at=now)
arrivals, _exact = direct_arrivals(repo, message, self_key="a1" + "0" * 62, peer_key="d4e5f6a7")
assert arrivals[0].routed is None
assert arrivals[0].route_known is True, "unknown falls back to the old reading"
repo.close()
def test_collapse_keeps_a_routed_path_apart_from_the_same_hops_flooded(tmp_path: Path) -> None:
"""Identical hashes mean two different things under the two route types."""
from meshterm.services.message_paths import Arrival
base = utcnow()
folded = collapse(
[
Arrival(when=base, hops=("3d63",), snr=1.0, routed=False),
Arrival(when=base + timedelta(seconds=1), hops=("3d63",), snr=2.0, routed=True),
]
)
assert len(folded) == 2, "a route travelled is not a route intended"
assert [a.routed for a in folded] == [False, True]