From c7d0cd23b6bfd9ebabd131e76dc4f78f721584fe Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Fri, 4 Sep 2026 20:34:36 +0200 Subject: [PATCH] fix(rtc): bootstrap from contacts is a floor, never a rollback BaseChatMesh::bootstrapRTCfromContacts() called setCurrentTime(latest+1) unconditionally. The newest contact's lastmod proves the clock must be at *least* that late -- it says nothing about it being any earlier -- so on any node that already has a better time source (a board with a real RTC, or the Emscripten sim whose SimRTCClock is backed by the host's wall clock) this dragged a correct clock backwards to whatever timestamp happened to be persisted alongside the contact list. Measured in the sim: ~9s behind real time after every reboot, growing with how long the previous session ran. Also adds sim_test_sync_time(epoch_secs) next to the other SIM_PLATFORM/__EMSCRIPTEN__ test hooks, so a host page can re-anchor the RTC on every (re)boot. The clock is live state the real mesh writes to at runtime -- a peer whose own clock runs ahead pushes ours forward, which is right on a node with no better source and, with meshcore-solo-site's cross-visitor relay, means one skewed visitor can drag everyone. On a demo running on someone's computer, "reset the device" should also mean "the clock is correct again". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018iubftDmKNWmkNnhJRz8UH --- examples/companion_radio/main.cpp | 19 +++++++++++++++++++ src/helpers/BaseChatMesh.cpp | 9 ++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index a2729d84..90eb15cc 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -498,6 +498,25 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_disable_screen_timeout() { return 1; } +// Re-anchors this instance's RTC to the host's real wall clock. SimRTCClock +// (variants/sim/SimRTCClock.h) already starts out reading time(NULL), so a +// freshly booted instance needs no help -- but the clock is a shared, live +// thing the real mesh code legitimately writes to at runtime: a received +// packet or a contact-list bootstrap carrying a timestamp ahead of ours +// pushes it forward (BaseChatMesh::bootstrapRTCfromContacts(), +// MyMesh's own timestamp handling), which is correct behaviour on a real +// node with no better time source, and means one peer with a badly skewed +// clock can drag every node that hears it. Calling this on every (re)boot +// makes "reset the device" mean "the demo's clock matches the visitor's own +// computer again", the same guarantee the timezone hook above gives. +// Seconds since the Unix epoch, as a double because a JS Date.now()/1000 +// value has no exact int32 representation to pass through ccall. +extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_sync_time(double epoch_secs) { + if (epoch_secs < 1000000000.0) return 0; // obvious nonsense (pre-2001) -- leave the clock alone + rtc_clock.setCurrentTime((uint32_t)epoch_secs); + return 1; +} + #ifdef DISPLAY_CLASS // Jumps the on-device UI straight to the DM thread with the first known // ADV_TYPE_CHAT contact (UITask::openContactDM() -- the exact same real diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index c91a2d02..b1cfaeec 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -62,7 +62,14 @@ void BaseChatMesh::bootstrapRTCfromContacts() { latest = contacts[i].lastmod; } } - if (latest != 0) { + // A bootstrap is a *floor*, not an assignment: the newest contact we know + // of proves the clock must be at least that late, it says nothing about it + // being any earlier. Setting it unconditionally drags a clock that already + // knows better (a board with a real RTC, or the sim's host wall clock -- + // see variants/sim/SimRTCClock.h) *backwards* to whatever timestamp + // happened to be persisted with the contact list, which then reads as a + // stopped/lagging clock on the Home screen after every reboot. + if (latest != 0 && latest + 1 > getRTCClock()->getCurrentTime()) { getRTCClock()->setCurrentTime(latest + 1); } }