Skip to content

fix: deduplicate MeshCore client retransmissions in room server#4

Open
tjdownes wants to merge 4 commits intodevfrom
fix/room-server-dedup
Open

fix: deduplicate MeshCore client retransmissions in room server#4
tjdownes wants to merge 4 commits intodevfrom
fix/room-server-dedup

Conversation

@tjdownes
Copy link
Copy Markdown
Owner

@tjdownes tjdownes commented May 1, 2026

Summary

  • The MeshCore client retransmits a message up to 3 times when no ACK is received, without dedup all three land as separate room messages
  • Root cause: pymc_core decodes bytes with errors="replace", turning invalid trailing bytes into U+FFFD (□). The existing rstrip("\x00") didn't strip □, so "Test" and "Test□" had different dedup keys
  • Fix 1 (text.py): extend rstrip to also strip so all retransmissions normalise to identical text
  • Fix 2 (room_server.py): in-memory _recent_posts cache keyed on (author_pubkey_hex, normalised_text), identical (author, text) within 30 s is dropped

Test plan

  • pytest tests/test_room_server_dedup.py: 7 tests, all pass
  • Device test: send a message from a weak-signal node, verify only 1 copy appears in the room

tjdownes and others added 3 commits May 1, 2026 05:56
Clients retry a message when they don't receive an ack fast enough
(common for distant nodes with weak signal). Without deduplication every
retry lands as a separate message in the room.

Add an in-memory `_recent_posts` cache keyed on (pubkey_hex, message_text).
Any identical (author, text) pair arriving within DEDUP_WINDOW_SECS (30s)
is silently dropped. Cache is evicted when it grows past 500 entries to
bound memory usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pymc_core decodes incoming bytes with errors="replace", so invalid
trailing bytes (common in C-string firmware payloads) become U+FFFD (□).
The MeshCore client retransmits a message up to 3 times when no ACK is
received; without this fix "Test" and "Test□" were treated as different
strings and all three retransmissions were stored as separate messages.

Extend the existing rstrip("\x00") to also strip "�" so all
retransmissions of the same message produce identical normalised text
before being handed to add_post() for dedup comparison.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7 tests in TestAddPostDedup:
- First post is stored normally
- Identical (author, text) within 30s window is dropped
- Same text accepted again after the window expires
- Different text from the same author always accepted
- Same text from different authors both stored independently
- sender_timestamp=0 (web-API path) dedup within window
- sender_timestamp=0 (web-API path) accepted after window

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tjdownes tjdownes closed this May 1, 2026
@tjdownes tjdownes reopened this May 1, 2026
…t.py

The rstrip("\x00�").rstrip() was only applied in text.py, which
handles radio-received packets.  Messages posted via the web UI go
directly to add_post() and were never normalised, so trailing newlines
from the textarea were persisted to the DB and rendered as visible
whitespace in the room messages UI.

Move the normalisation to the top of add_post() so it applies to every
code path: radio, web UI, companion, and CLI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant