Skip to content

Fix silent message rejection when companion RTC diverges from app clock#1889

Open
wbijen wants to merge 1 commit into
meshcore-dev:devfrom
wbijen:fix/room-server-timestamp-mismatch
Open

Fix silent message rejection when companion RTC diverges from app clock#1889
wbijen wants to merge 1 commit into
meshcore-dev:devfrom
wbijen:fix/room-server-timestamp-mismatch

Conversation

@wbijen

@wbijen wbijen commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Fix silent message rejection when companion RTC diverges from app clock

Problem

When a companion device's RTC runs ahead of the app's clock, messages sent to Room Servers (and Repeaters/Sensors) are silently rejected. This happens because last_timestamp mixes two different clock sources:

  1. Login (ANON_REQ) uses the companion's RTC → e.g. last_timestamp = 1500
  2. Messages (TXT_MSG) use the app's clock → e.g. sender_timestamp = 1001
  3. The replay check sender_timestamp >= last_timestamp fails: 1001 >= 1500rejected silently

The user sees a successful login but their messages never arrive. No error is shown on either side.

This is the same root cause described in #1551 — the proposed fix there was to change the companion side, but that broke repeater counting for channel messages. This PR fixes it on the server side instead, which avoids touching the companion radio at all.

Solution

Don't store the login timestamp in last_timestamp. Keep last_timestamp purely for message/request replay protection (where the clock source is consistent).

Login replay protection is already handled by hasSeen() in the mesh layer, which deduplicates packets by their SHA256 hash. The existing replay check at login time (sender_timestamp <= client->last_timestamp) still works — it just compares against the last message timestamp rather than the last login timestamp.

The fix is applied to all three server types:

  • simple_room_server
  • simple_repeater
  • simple_sensor

Walkthrough

Step Before (broken) After (fixed)
Login (RTC=1500) last_timestamp = 1500 last_timestamp unchanged (stays 0)
Message (app=1001) 1001 >= 1500rejected 1001 >= 0 → passes
Message (app=1002) never reaches here 1002 >= 1001 → passes
Re-login (RTC=1600) 1600 > 1500 → passes 1600 > 1002 → passes

Testing

  • Login from companion with RTC ahead of app clock → messages should no longer be silently dropped
  • Login replay protection still works via mesh-layer hasSeen() deduplication
  • Message replay protection is unaffected (still uses last_timestamp from previous messages)

…diverges from app clock

Don't store the login timestamp in last_timestamp. Login uses the
companion's RTC while messages use the app's clock - when these diverge,
messages get silently rejected by the replay check (sender_timestamp <
last_timestamp). Login replay protection is already handled by hasSeen()
in the mesh layer.

Fixes meshcore-dev#1551
robekl added a commit to robekl/MeshCore that referenced this pull request Mar 23, 2026
Reimplements meshcore-dev/MeshCore PR meshcore-dev#1889 on top of 467959c for the 1.14.1 maintenance branch.

References:
PR: meshcore-dev#1889
Issue: meshcore-dev#1551

Rationale:
Login requests use the companion RTC while subsequent messages are validated against the application clock. Persisting the login timestamp into client state can therefore cause silent message rejection when the companion RTC runs ahead. This change stops updating last_timestamp during login and relies on existing mesh-layer replay protection instead. The behavior change is tightly scoped to login handling in three example firmware variants and does not alter packet formats or transport code.
@usrflo

usrflo commented Jun 28, 2026

Copy link
Copy Markdown

Hello @wbijen, applying this fix allowed me to login on the first attempt but failed on subsequent logins.
So I proposed #2834 to separate the login from the message replay timestamps and this works in my tests.

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.

2 participants