From edcdf7dea585db9cef7b1fa95a253e4bcf4e0fcd Mon Sep 17 00:00:00 2001 From: Wouter Bijen Date: Sun, 1 Mar 2026 13:25:03 +0100 Subject: [PATCH] Fix room/repeater/sensor silent message rejection when companion RTC 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 #1551 --- examples/simple_repeater/MyMesh.cpp | 5 ++++- examples/simple_room_server/MyMesh.cpp | 5 ++++- examples/simple_sensor/SensorMesh.cpp | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 81c1dcb42..e636e1154 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -117,7 +117,10 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } MESH_DEBUG_PRINTLN("Login success!"); - client->last_timestamp = sender_timestamp; + // NOTE: don't update last_timestamp here - login uses companion RTC which may differ + // from the app clock used for messages. Mixing the two causes silent message rejection + // when the companion RTC runs ahead of the app clock (see #1551). + // Login replay protection is already handled by hasSeen() in the mesh layer. client->last_activity = getRTCClock()->getCurrentTime(); client->permissions &= ~0x03; client->permissions |= perms; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 5451505a2..e8a36afcb 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -321,7 +321,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m } MESH_DEBUG_PRINTLN("Login success!"); - client->last_timestamp = sender_timestamp; + // NOTE: don't update last_timestamp here - login uses companion RTC which may differ + // from the app clock used for messages. Mixing the two causes silent message rejection + // when the companion RTC runs ahead of the app clock (see #1551). + // Login replay protection is already handled by hasSeen() in the mesh layer. client->extra.room.sync_since = sender_sync_since; client->extra.room.pending_ack = 0; client->extra.room.push_failures = 0; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 68fea474e..7b7b286bc 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -352,7 +352,10 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* } MESH_DEBUG_PRINTLN("Login success!"); - client->last_timestamp = sender_timestamp; + // NOTE: don't update last_timestamp here - login uses companion RTC which may differ + // from the app clock used for messages. Mixing the two causes silent message rejection + // when the companion RTC runs ahead of the app clock (see #1551). + // Login replay protection is already handled by hasSeen() in the mesh layer. client->last_activity = getRTCClock()->getCurrentTime(); client->permissions |= PERM_ACL_ADMIN; memcpy(client->shared_secret, secret, PUB_KEY_SIZE);