Skip to content

repeater time sync

rogovogor edited this page May 15, 2026 · 2 revisions

Repeater time sync via advert timestamps / Синхронизация времени ретранслятора через таймстемпы адвертов

[English] · [Русский]


English

Problem

Repeaters without GPS use VolatileRTCClock or ESP32RTCClock (hardcoded start date). Time is lost or corrupted after a reboot. Accurate time is necessary for replay-attack protection and message timestamps.

Solution

Accumulate timestamps from incoming verified advert packets into a ring buffer. Filter nodes with incorrect clocks using a quorum/median algorithm, then adjust the local clock once enough sources agree.

Security

Each advert timestamp is covered by an Ed25519 signature over pub_key(32) || timestamp(4) || app_data. The mesh layer verifies the signature before calling onAdvertRecv — forged timestamps are rejected cryptographically and never reach the sync logic.

Residual threats:

Threat Protection
Legitimate node with a wrong clock Quorum — a single outlier does not reach the threshold
Timestamp far in the future MAX_VALID_TS = 2050-01-01 limit + quorum
Clock rollback MAX_JUMP = 36000 s limit per single correction

Sync modes

Fast initial sync

Active when: !clock.isTimeReliable() && sync_count == 0 (no hardware RTC chip found, no advert sync performed yet)

  • Buffer: last 5 samples
  • Quorum: 3/5 within ±60 s window
  • Action: apply median immediately (no drift threshold)

Drift correction

Active when: clock is already trusted (hardware RTC found or previously synced)

  • Buffer: last 10 samples
  • Quorum: 7/10 within ±60 s window
  • Action: apply only if |median − current| > 120 s && < 36000 s

Clock trust (isTimeReliable())

RTCClock::isTimeReliable() — new virtual method, false by default.

Clock source isTimeReliable()
VolatileRTCClock false
ESP32RTCClock (hardcoded start) false
AutoDiscoverRTCClock with hardware chip true (DS3231 / RV3028 / PCF8563 / RX8130CE)

Clustering algorithm

Timestamps are sorted. A sliding window of CLUSTER_WINDOW seconds passes over the sorted array to find the densest cluster (maximum samples in the window). This correctly handles mixed networks where GPS-synchronized nodes (accurate) are outnumbered by nodes with incorrect hardcoded clocks — the GPS cluster is detected even when smaller, as long as it reaches the quorum size.

The median of the winning cluster (not all samples) is used as the sync target.

Application logic

When quorum is reached and correction is needed:

if GPS available && GPS has fix:
    gps->syncTime()                    // GPS re-syncs clock on next NMEA
else:
    clock->setCurrentTime(median)      // direct correction

This ensures GPS remains the authoritative time source when a fix is available. If GPS does not yet have a fix, the mesh-quorum time is used.

Changed files

File Change
src/MeshCore.h Added virtual bool isTimeReliable() const to RTCClock
src/helpers/AutoDiscoverRTCClock.h/.cpp Override isTimeReliable() — true if hardware chip found
examples/simple_repeater/MyMesh.h Ring buffer fields and sync counters
examples/simple_repeater/MyMesh.cpp tryTimeSyncFromBuf(), updated onAdvertRecv(), CLI command timesync
docs/repeater_time_sync.md User-facing documentation

CLI: timesync

Available remotely via companion radio. Example output before first sync:

TimeSync: no sync yet
Adverts: 3 rx / 3 valid
Buf: 3/10
Clock: 08:50:51 2024-05-15 UTC

After sync:

TimeSync: 2 syncs
Last: 14:22 2025-01-15 UTC (4320s ago)
Adj: +145s
Adverts: 18 rx / 18 valid
Buf: 10/10
Clock: 15:54:01 2025-01-15 UTC

Constants

Constant Value Description
MIN_VALID_TS 1577836800 Lower bound 2020-01-01 UTC
MAX_VALID_TS 2524608000 Upper bound 2050-01-01 UTC
CLUSTER_WINDOW 60 s Clustering window for quorum
DRIFT_THRESHOLD 120 s Minimum drift to trigger correction
MAX_JUMP 36000 s Maximum single correction
Fast quorum 3 / 5 For initial sync
Full quorum 7 / 10 For drift correction

Русский

Проблема

Ретрансляторы без GPS используют VolatileRTCClock или ESP32RTCClock (захардкоженная дата старта). Время теряется или сбивается после перезагрузки. Корректное время необходимо для защиты от replay-атак и временны́х меток сообщений.

Решение

Накапливать таймстемпы из входящих верифицированных advert-пакетов в кольцевой буфер. Фильтровать узлы с неправильными часами через алгоритм кворума/медианы, затем корректировать локальные часы, когда достаточно источников согласны.

Безопасность

Каждый таймстемп advert покрыт Ed25519-подписью над pub_key(32) || timestamp(4) || app_data. Mesh-слой верифицирует подпись до вызова onAdvertRecv — поддельные таймстемпы отвергаются криптографически и никогда не достигают логики синхронизации.

Остаточные угрозы:

Угроза Защита
Легитимный узел с неправильными часами Кворум — одиночный выброс не достигает порога
Таймстемп из далёкого будущего Ограничение MAX_VALID_TS = 2050-01-01 + кворум
Сдвиг часов назад Ограничение MAX_JUMP = 36000 с на одну коррекцию

Режимы синхронизации

Быстрая начальная синхронизация

Активна когда: !clock.isTimeReliable() && sync_count == 0 (аппаратный RTC-чип не найден, синхронизация через advert не проводилась)

  • Буфер: последние 5 сэмплов
  • Кворум: 3/5 в пределах окна ±60 с
  • Действие: применить медиану немедленно (без порога дрейфа)

Коррекция дрейфа

Активна когда: часы уже доверенные (аппаратный RTC найден или ранее синхронизированы)

  • Буфер: последние 10 сэмплов
  • Кворум: 7/10 в пределах окна ±60 с
  • Действие: применить только если |медиана − текущее| > 120 с && < 36000 с

Доверие к часам (isTimeReliable())

RTCClock::isTimeReliable() — новый виртуальный метод, false по умолчанию.

Источник часов isTimeReliable()
VolatileRTCClock false
ESP32RTCClock (захардкоженный старт) false
AutoDiscoverRTCClock с аппаратным чипом true (DS3231 / RV3028 / PCF8563 / RX8130CE)

Алгоритм кластеризации

Таймстемпы сортируются. Скользящее окно CLUSTER_WINDOW секунд проходит по отсортированному массиву для поиска наиболее плотного кластера (максимум сэмплов в окне). Это корректно обрабатывает смешанные сети, где GPS-синхронизированных узлов (точных) меньше, чем узлов с неправильными захардкоженными часами — GPS-кластер обнаруживается даже если он меньше, при условии достижения размера кворума.

Медиана выигравшего кластера (не всех сэмплов) используется как цель синхронизации.

Логика применения

При достижении кворума и необходимости коррекции:

if GPS доступен && GPS имеет фикс:
    gps->syncTime()                    // GPS пересинхронизирует часы при следующем NMEA
else:
    clock->setCurrentTime(median)      // прямая коррекция

Это гарантирует, что GPS остаётся авторитетным источником времени при наличии фикса. Если GPS ещё не имеет фикса, используется время из mesh-кворума.

Изменённые файлы

Файл Изменение
src/MeshCore.h Добавлен virtual bool isTimeReliable() const в RTCClock
src/helpers/AutoDiscoverRTCClock.h/.cpp Переопределение isTimeReliable() — true если найден аппаратный чип
examples/simple_repeater/MyMesh.h Поля кольцевого буфера и счётчики синхронизации
examples/simple_repeater/MyMesh.cpp tryTimeSyncFromBuf(), обновлён onAdvertRecv(), CLI-команда timesync
docs/repeater_time_sync.md Пользовательская документация

CLI: timesync

Доступна удалённо через companion-радио. Пример вывода до первой синхронизации:

TimeSync: no sync yet
Adverts: 3 rx / 3 valid
Buf: 3/10
Clock: 08:50:51 2024-05-15 UTC

После синхронизации:

TimeSync: 2 syncs
Last: 14:22 2025-01-15 UTC (4320s ago)
Adj: +145s
Adverts: 18 rx / 18 valid
Buf: 10/10
Clock: 15:54:01 2025-01-15 UTC

Константы

Константа Значение Описание
MIN_VALID_TS 1577836800 Нижняя граница 2020-01-01 UTC
MAX_VALID_TS 2524608000 Верхняя граница 2050-01-01 UTC
CLUSTER_WINDOW 60 с Окно кластеризации для кворума
DRIFT_THRESHOLD 120 с Минимальный дрейф для коррекции
MAX_JUMP 36000 с Максимальная одиночная коррекция
Быстрый кворум 3 / 5 Для начальной синхронизации
Полный кворум 7 / 10 Для коррекции дрейфа

Clone this wiki locally