From 168d4a0a8a779c2a5099236331d8df352f5c6443 Mon Sep 17 00:00:00 2001 From: agessaman Date: Fri, 14 Aug 2026 19:41:04 -0700 Subject: [PATCH] fix(mqtt): make the accepted NTP epoch authoritative before any JWT work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit syncTimeWithNTP() read an epoch over UDP, called configTime(), set _ntp_synced, and then had the stale-token test and createSlotAuthToken() read time(nullptr) — without anything having put the accepted epoch there. configTime() restarts SNTP and returns; the clock lands whenever a packet does. _rtc->setCurrentTime() looks like it covers this and does not. AutoDiscoverRTCClock::setCurrentTime() writes a detected DS3231/RV3028/PCF8563/ RX8130CE *instead of* delegating to its fallback, and only that fallback (ESP32RTCClock) calls settimeofday(). So on every board carrying an RTC chip — T-Beam Supreme and Station G3 both compile this bridge and both instantiate AutoDiscoverRTCClock — libc kept the pre-correction time, and the correction path tested staleness and minted iat claims against exactly the clock it had just proven wrong. Boards without a chip take the fallback and were unaffected, which is why the soak rig (Heltec V3/V4, no RTC) never showed it. settimeofday() with the accepted epoch first, so the invariant downstream code already assumes actually holds: once _ntp_synced is true, time(nullptr) returns the epoch we accepted. configTime() still follows, to keep future syncs running. --- src/helpers/bridges/MQTTBridge.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index b73ab34b..adbe0cb2 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -4013,6 +4013,18 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { #endif if (ntp_ok && ntp_server_used) { + // Take ownership of the system clock here, before anything reads it. configTime() + // only restarts SNTP and returns, and _rtc reaches settimeofday() on exactly one + // path: AutoDiscoverRTCClock writes a detected DS3231/RV3028/PCF8563/RX8130CE chip + // *instead of* its fallback, so on any board carrying one, libc keeps the pre-sync + // time. Everything downstream reads time(nullptr) — the stale-token test below, and + // the iat of every JWT minted from here on — so once _ntp_synced is true that call + // has to already return the epoch we accepted. + struct timeval accepted; + accepted.tv_sec = (time_t)epochTime; + accepted.tv_usec = 0; + settimeofday(&accepted, nullptr); + configTime(0, 0, ntp_server_used); if (_rtc) {