Skip to content

Separate login and message replay timestamps (proper fix for #1551, supersedes #1889)#2834

Draft
usrflo wants to merge 1 commit into
meshcore-dev:devfrom
usrflo:bugfix/login-msg-timer
Draft

Separate login and message replay timestamps (proper fix for #1551, supersedes #1889)#2834
usrflo wants to merge 1 commit into
meshcore-dev:devfrom
usrflo:bugfix/login-msg-timer

Conversation

@usrflo

@usrflo usrflo commented Jun 24, 2026

Copy link
Copy Markdown

Summary

Properly fixes the silent-message-rejection issue (#1551) without introducing the login regression that #1889 caused (after the first admin login, no subsequent login was possible). It does so by tracking login replay and message replay in two separate per-client fields instead of sharing one.

Background

#1889 addressed #1551 by no longer storing the login timestamp into client->last_timestamp. The reasoning: login packets carry the companion device's RTC (BaseChatMesh::sendLogingetRTCClock()->getCurrentTimeUnique()), while regular messages carry the app clock (passed as a timestamp parameter to sendMessage/sendCommandData). If the RTC runs ahead of the app clock, writing the RTC value into last_timestamp on login caused later, lower app-clock messages to be dropped by the replay check (sender_timestamp < last_timestamp).

However, #1889 left the login replay check comparing against last_timestamp and left the message handlers updating last_timestamp. This inverts the failure:

  1. First login succeeds (last_timestamp is still low/zero).
  2. Normal post-login traffic (onPeerDataRecv TXT/REQ handlers) drives last_timestamp up to a large app-clock value.
  3. The next login arrives with a companion-RTC timestamp. Since the RTC is not guaranteed to exceed the last app-clock message timestamp, the login check sender_timestamp <= client->last_timestamp trips → possible replay attack → the login is silently dropped (client times out).

Reverting #1889 (the stock firmware) makes login work again, but reintroduces #1551. So each direction is broken depending on which clock leads.

Root cause

A single replay variable, client->last_timestamp, is fed and checked by two independent clocks:

  • Login: companion RTC (getCurrentTimeUnique()) — also used only as a uniqueness blob for the packet hash.
  • Messages: app clock (caller-supplied).

Neither clock is monotonic relative to the other, so any scheme that mixes them fails in one direction or the other. Note that hasSeen() (the mesh-layer packet-hash dedup) does not substitute here: it only catches exact-duplicate packets, not the "is this login newer than the last one" ordering the timestamp check provides.

Fix

Split the two replay tracks:

  • Add a transient field ClientInfo::last_login_timestamp.
  • The login replay check and the login timestamp assignment use last_login_timestamp.
  • The message/REQ handlers are unchanged and keep using last_timestamp.

Result:

  • Login replay now runs purely on the companion RTC. Because getCurrentTimeUnique() is monotonic per device, every login's timestamp strictly exceeds the previous one → repeated logins work again (regression fixed).
  • Message replay still runs on last_timestamp, which is no longer poisoned by the login's RTC value → messages are no longer rejected when the RTC runs ahead (Fix Room Server message delivery failure when companion RTC differs from app clock #1551 fixed).
  • The forward-only RTC sync (clock sync / time refuse to go backwards) no longer matters, because the two replay tracks are independent.

Applied uniformly to the three servers that share this pattern: simple_room_server, simple_repeater, simple_sensor.

Backward compatibility

No wire-protocol change. Old clients keep working against a patched server (the server just tracks login and messages separately), and patched clients work against an unpatched server.

…o prevent being stuck using different timer values
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