-
Notifications
You must be signed in to change notification settings - Fork 4
repeater time sync
Repeater time sync via advert timestamps / Синхронизация времени ретранслятора через таймстемпы адвертов
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.
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.
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 |
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)
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
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) |
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.
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.
| 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 |
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
| 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 с
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 |
Пользовательская документация |
Доступна удалённо через 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 | Для коррекции дрейфа |