Time Shield Library
C++ library for working with time
Loading...
Searching...
No Matches
ntp_time_service.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef _TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED
4#define _TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED
5
6#include "config.hpp"
7
8#if TIME_SHIELD_ENABLE_NTP_CLIENT
9
10#include "ntp_client_pool.hpp"
12#include "time_utils.hpp"
13
14#include <atomic>
15#include <chrono>
16#include <condition_variable>
17#include <cstdint>
18#include <memory>
19#include <mutex>
20#include <thread>
21#include <utility>
22#include <vector>
23
24namespace time_shield {
25
26 template <class RunnerT>
27 class NtpTimeServiceT;
28
29 namespace detail {
30#ifdef TIME_SHIELD_TEST_FAKE_NTP
33 public:
35 FakeNtpRunner() = default;
38
39 FakeNtpRunner(const FakeNtpRunner&) = delete;
41
44 stop();
45 }
46
48 bool start(std::chrono::milliseconds interval = std::chrono::seconds(30),
49 bool measure_immediately = true) {
50 if (m_is_running.load()) {
51 return false;
52 }
53 if (interval.count() <= 0) {
54 interval = std::chrono::milliseconds(1);
55 }
56 m_interval = interval;
57 m_is_stop_requested.store(false);
58 m_is_force_requested.store(false);
59 m_is_running.store(true);
60 m_measure_immediately = measure_immediately;
61 try {
62 m_thread = std::thread(&FakeNtpRunner::run_loop, this);
63 } catch (...) {
64 m_is_running.store(false);
65 return false;
66 }
67 return true;
68 }
69
71 bool start(int interval_ms, bool measure_immediately = true) {
72 return start(std::chrono::milliseconds(interval_ms), measure_immediately);
73 }
74
76 void stop() {
77 m_is_stop_requested.store(true);
78 m_cv.notify_all();
79 if (m_thread.joinable()) {
80 m_thread.join();
81 }
82 m_is_running.store(false);
83 }
84
86 bool running() const noexcept { return m_is_running.load(); }
87
90 if (!m_is_running.load()) {
91 return false;
92 }
93 m_is_force_requested.store(true);
94 m_cv.notify_one();
95 return true;
96 }
97
99 bool measure_now() {
100 return do_measure();
101 }
102
104 int64_t offset_us() const noexcept { return m_offset_us.load(); }
106 int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); }
108 int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; }
110 int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; }
111
113 bool last_measure_ok() const noexcept { return m_last_measure_ok.load(); }
115 uint64_t measure_count() const noexcept { return m_measure_count.load(); }
117 uint64_t fail_count() const noexcept { return m_fail_count.load(); }
119 int64_t last_update_realtime_us() const noexcept { return m_last_update_realtime_us.load(); }
121 int64_t last_success_realtime_us() const noexcept { return m_last_success_realtime_us.load(); }
122
124 std::vector<NtpSample> last_samples() const { return {}; }
125
126 private:
128 void run_loop() {
129 const auto sleep_interval = m_interval;
130 bool is_first = m_measure_immediately;
131 while (!m_is_stop_requested.load()) {
132 if (is_first) {
133 do_measure();
134 is_first = false;
135 } else {
136 std::unique_lock<std::mutex> lk(m_cv_mtx);
137 m_cv.wait_for(lk, sleep_interval, [this]() {
138 return m_is_stop_requested.load() || m_is_force_requested.load();
139 });
140 if (m_is_stop_requested.load()) {
141 break;
142 }
143 m_is_force_requested.store(false);
144 do_measure();
145 }
146 }
147 m_is_running.store(false);
148 }
149
151 bool do_measure() {
152 const uint64_t count = m_measure_count.fetch_add(1) + 1;
153 m_offset_us.store(static_cast<int64_t>(count * 1000));
154 m_last_measure_ok.store(true);
155 const int64_t now = now_realtime_us();
158 return true;
159 }
160
161 private:
162 std::chrono::milliseconds m_interval{std::chrono::seconds(30)};
164
165 std::thread m_thread;
166 std::condition_variable m_cv;
167 std::mutex m_cv_mtx;
168
169 std::atomic<bool> m_is_running{false};
170 std::atomic<bool> m_is_stop_requested{false};
171 std::atomic<bool> m_is_force_requested{false};
172
173 std::atomic<bool> m_last_measure_ok{false};
174 std::atomic<uint64_t> m_measure_count{0};
175 std::atomic<uint64_t> m_fail_count{0};
176 std::atomic<int64_t> m_last_update_realtime_us{0};
177 std::atomic<int64_t> m_last_success_realtime_us{0};
178 std::atomic<int64_t> m_offset_us{0};
179 };
180#endif // _TIME_SHIELD_TEST_FAKE_NTP
181
182#ifndef _TIME_SHIELD_CPP17
183#if defined(TIME_SHIELD_TEST_FAKE_NTP)
185#else
187#endif
188
190#endif // !TIME_SHIELD_CPP17
191 } // namespace detail
192
199 template <class RunnerT>
201 public:
204 static NtpTimeServiceT& instance() noexcept {
205#ifdef TIME_SHIELD_CPP17
206 return m_instance;
207#else
209#endif
210 }
211
214
217 bool init() {
219 }
220
225 bool init(std::chrono::milliseconds interval, bool measure_immediately = true) {
226 std::unique_ptr<RunnerT> local_runner;
227 {
228 std::lock_guard<std::mutex> lk(m_mtx);
229 if (is_running_locked()) {
230 return true;
231 }
232 if (interval.count() <= 0) {
233 interval = std::chrono::milliseconds(1);
234 }
235 m_interval = interval;
236 m_measure_immediately = measure_immediately;
237
238 local_runner = build_runner_locked();
239 if (!local_runner) {
240 return false;
241 }
242 }
243
244 if (!local_runner->start(m_interval, m_measure_immediately)) {
245 return false;
246 }
247
248 bool is_ok = false;
249 try {
250 is_ok = local_runner->measure_now();
251 } catch (...) {
252 is_ok = false;
253 }
254
255 {
256 std::lock_guard<std::mutex> lk(m_mtx);
257 m_runner = std::move(local_runner);
258 }
259 return is_ok;
260 }
261
263 void shutdown() {
264 std::unique_ptr<RunnerT> local_runner;
265 {
266 std::lock_guard<std::mutex> lk(m_mtx);
267 if (!m_runner) {
268 return;
269 }
270 local_runner = std::move(m_runner);
271 }
272 try {
273 local_runner->stop();
274 } catch (...) {
275 // no-throw
276 }
277 }
278
281 bool running() const noexcept {
282 std::lock_guard<std::mutex> lk(m_mtx);
283 return is_running_locked();
284 }
285
287 void ensure_started() noexcept {
288 if (running()) {
289 return;
290 }
291 (void)init();
292 }
293
296 int64_t offset_us() noexcept {
298 std::lock_guard<std::mutex> lk(m_mtx);
299 if (!m_runner) return 0;
300 return m_runner->offset_us();
301 }
302
305 int64_t utc_time_us() noexcept {
307 std::lock_guard<std::mutex> lk(m_mtx);
308 if (!m_runner) return now_realtime_us();
309 return m_runner->utc_time_us();
310 }
311
314 int64_t utc_time_ms() noexcept {
315 return utc_time_us() / 1000;
316 }
317
320 int64_t utc_time_sec() noexcept {
321 return utc_time_us() / 1000000;
322 }
323
326 bool last_measure_ok() const noexcept {
327 std::lock_guard<std::mutex> lk(m_mtx);
328 if (!m_runner) return false;
329 return m_runner->last_measure_ok();
330 }
331
334 uint64_t measure_count() const noexcept {
335 std::lock_guard<std::mutex> lk(m_mtx);
336 if (!m_runner) return 0;
337 return m_runner->measure_count();
338 }
339
342 uint64_t fail_count() const noexcept {
343 std::lock_guard<std::mutex> lk(m_mtx);
344 if (!m_runner) return 0;
345 return m_runner->fail_count();
346 }
347
350 int64_t last_update_realtime_us() const noexcept {
351 std::lock_guard<std::mutex> lk(m_mtx);
352 if (!m_runner) return 0;
353 return m_runner->last_update_realtime_us();
354 }
355
358 int64_t last_success_realtime_us() const noexcept {
359 std::lock_guard<std::mutex> lk(m_mtx);
360 if (!m_runner) return 0;
361 return m_runner->last_success_realtime_us();
362 }
363
367 bool stale(std::chrono::milliseconds max_age) const noexcept {
368 const int64_t last = last_update_realtime_us();
369 if (last == 0) {
370 return true;
371 }
372 const int64_t age = now_realtime_us() - last;
373 return age > max_age.count() * 1000;
374 }
375
379 bool set_servers(std::vector<NtpServerConfig> servers) {
380 std::lock_guard<std::mutex> lk(m_mtx);
381 if (is_running_locked()) {
382 return false;
383 }
385 m_servers = std::move(servers);
386 return true;
387 }
388
392 std::lock_guard<std::mutex> lk(m_mtx);
393 if (is_running_locked()) {
394 return false;
395 }
398 return true;
399 }
400
404 std::lock_guard<std::mutex> lk(m_mtx);
405 if (is_running_locked()) {
406 return false;
407 }
408 m_has_custom_servers = false;
409 m_servers.clear();
410 return true;
411 }
412
417 std::lock_guard<std::mutex> lk(m_mtx);
418 if (is_running_locked()) {
419 return false;
420 }
422 m_pool_cfg = std::move(cfg);
423 return true;
424 }
425
429 std::lock_guard<std::mutex> lk(m_mtx);
431 return m_pool_cfg;
432 }
433 return NtpPoolConfig{};
434 }
435
438 std::vector<NtpSample> last_samples() const {
439 std::lock_guard<std::mutex> lk(m_mtx);
440 if (!m_runner) return {};
441 return m_runner->last_samples();
442 }
443
445 NtpTimeServiceT() = default;
448 shutdown();
449 }
450
454 std::unique_ptr<RunnerT> new_runner;
455 std::unique_ptr<RunnerT> old_runner;
456 std::chrono::milliseconds interval;
457 bool measure_immediately = true;
458 {
459 std::lock_guard<std::mutex> lk(m_mtx);
460 new_runner = build_runner_locked();
461 if (!new_runner) {
462 return false;
463 }
464 interval = m_interval;
465 measure_immediately = m_measure_immediately;
466 old_runner = std::move(m_runner);
467 }
468
469 if (old_runner) {
470 try {
471 old_runner->stop();
472 } catch (...) {
473 }
474 }
475
476 if (!new_runner->start(interval, measure_immediately)) {
477 return false;
478 }
479
480 bool is_ok = false;
481 try {
482 is_ok = new_runner->measure_now();
483 } catch (...) {
484 is_ok = false;
485 }
486
487 {
488 std::lock_guard<std::mutex> lk(m_mtx);
489 m_runner = std::move(new_runner);
490 }
491 return is_ok;
492 }
493
494 private:
496 bool is_running_locked() const noexcept {
497 return m_runner && m_runner->running();
498 }
499
501 std::unique_ptr<RunnerT> build_runner_locked() {
502 std::vector<NtpServerConfig> servers;
504 servers = m_servers;
505 } else {
507 }
508
510 NtpClientPool pool(cfg);
511 pool.set_servers(std::move(servers));
512
513 std::unique_ptr<RunnerT> runner;
514 try {
515 runner.reset(new RunnerT(std::move(pool)));
516 } catch (...) {
517 return nullptr;
518 }
519 return runner;
520 }
521
522 private:
523 mutable std::mutex m_mtx;
524 std::chrono::milliseconds m_interval{std::chrono::seconds(30)};
526
528 std::vector<NtpServerConfig> m_servers;
529
532
533 std::unique_ptr<RunnerT> m_runner;
534
535#ifdef TIME_SHIELD_CPP17
537#endif
538 };
539
540#ifdef TIME_SHIELD_CPP17
541 template <class RunnerT>
543#endif
544
545#ifndef _TIME_SHIELD_CPP17
546namespace detail {
547#if defined(TIME_SHIELD_NTP_TIME_SERVICE_DEFINE)
549#endif
550} // namespace detail
551#endif
552
553#if defined(TIME_SHIELD_TEST_FAKE_NTP)
557#else
561#endif
562
563namespace ntp {
564
570 inline bool init(std::chrono::milliseconds interval = std::chrono::seconds(30),
571 bool measure_immediately = true) {
572 return NtpTimeService::instance().init(interval, measure_immediately);
573 }
574
580 inline bool init(int interval_ms,
581 bool measure_immediately = true) {
582 return NtpTimeService::instance().init(std::chrono::milliseconds(interval_ms), measure_immediately);
583 }
584
587 inline void shutdown() {
589 }
590
594 inline int64_t offset_us() noexcept {
596 }
597
601 inline int64_t utc_time_us() noexcept {
603 }
604
608 inline int64_t utc_time_ms() noexcept {
610 }
611
615 inline int64_t utc_time_sec() noexcept {
617 }
618
622 inline bool last_measure_ok() noexcept {
624 }
625
629 inline uint64_t measure_count() noexcept {
631 }
632
636 inline uint64_t fail_count() noexcept {
638 }
639
643 inline int64_t last_update_realtime_us() noexcept {
645 }
646
650 inline int64_t last_success_realtime_us() noexcept {
652 }
653
658 inline bool stale(std::chrono::milliseconds max_age) noexcept {
659 return NtpTimeService::instance().stale(max_age);
660 }
661
662} // namespace ntp
663
664} // namespace time_shield
665
666#else // TIME_SHIELD_ENABLE_NTP_CLIENT
667
668namespace time_shield {
670 public:
672 static_assert(sizeof(void*) == 0, "NtpTimeService is disabled by configuration.");
673 return *reinterpret_cast<NtpTimeService*>(0);
674 }
675 };
676} // namespace time_shield
677
678#endif // _TIME_SHIELD_ENABLE_NTP_CLIENT
679
680#endif // _TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED
static std::vector< NtpServerConfig > build_default_servers()
Singleton service for background NTP measurements.
std::unique_ptr< RunnerT > build_runner_locked()
Build a runner with current server list and pool config.
NtpTimeServiceT()=default
Construct service.
NtpTimeServiceT(const NtpTimeServiceT &)=delete
uint64_t measure_count() const noexcept
Return total number of measurement attempts.
bool is_running_locked() const noexcept
Check runner status under lock.
int64_t last_success_realtime_us() const noexcept
Return realtime timestamp of last successful measurement.
NtpPoolConfig pool_config() const
Return current pool configuration.
void shutdown()
Stop background measurements and release resources.
~NtpTimeServiceT()
Stop background runner on destruction.
bool clear_servers()
Clear custom server list and return to default behavior.
int64_t utc_time_sec() noexcept
Return current UTC time in seconds based on offset.
bool init()
Start background measurements using stored interval.
bool set_pool_config(NtpPoolConfig cfg)
Override pool configuration for new runner instances.
NtpTimeServiceT & operator=(const NtpTimeServiceT &)=delete
bool running() const noexcept
Return true when background runner is active.
bool stale(std::chrono::milliseconds max_age) const noexcept
Return true when last measurement is older than max_age.
int64_t utc_time_ms() noexcept
Return current UTC time in milliseconds based on offset.
static NtpTimeServiceT & instance() noexcept
Return the singleton instance.
bool last_measure_ok() const noexcept
Return whether last measurement updated the offset.
bool set_servers(std::vector< NtpServerConfig > servers)
Replace server list used for new runner instances.
int64_t offset_us() noexcept
Return last estimated offset in microseconds.
uint64_t fail_count() const noexcept
Return number of failed measurement attempts.
bool set_default_servers()
Use conservative default servers for new runner instances.
bool apply_config_now()
Apply current config by rebuilding the runner.
std::unique_ptr< detail::FakeNtpRunner > m_runner
int64_t utc_time_us() noexcept
Return current UTC time in microseconds based on offset.
bool init(std::chrono::milliseconds interval, bool measure_immediately=true)
Start background measurements with interval and immediate flag.
std::vector< NtpSample > last_samples() const
Return copy of last measurement samples.
void ensure_started() noexcept
Ensure background runner is started with current config.
int64_t last_update_realtime_us() const noexcept
Return realtime timestamp of last measurement attempt.
static NtpTimeService & instance()
Fake runner for tests without network access.
int64_t last_update_realtime_us() const noexcept
Return realtime timestamp of last measurement attempt.
int64_t utc_time_sec() const noexcept
Return current UTC time in seconds based on offset.
std::atomic< int64_t > m_last_success_realtime_us
FakeNtpRunner()=default
Construct fake runner.
int64_t utc_time_us() const noexcept
Return current UTC time in microseconds based on offset.
FakeNtpRunner(const FakeNtpRunner &)=delete
bool last_measure_ok() const noexcept
Return whether last measurement updated the offset.
int64_t last_success_realtime_us() const noexcept
Return realtime timestamp of last successful measurement.
bool force_measure()
Wake the worker thread and request a measurement.
bool running() const noexcept
Return true when background thread is running.
uint64_t fail_count() const noexcept
Return number of failed measurement attempts.
std::atomic< int64_t > m_last_update_realtime_us
bool start(std::chrono::milliseconds interval=std::chrono::seconds(30), bool measure_immediately=true)
Start fake measurements on a background thread.
int64_t offset_us() const noexcept
Return last estimated offset in microseconds.
bool do_measure()
Update fake offset and stats.
std::atomic< uint64_t > m_measure_count
void run_loop()
Background loop for fake measurements.
int64_t utc_time_ms() const noexcept
Return current UTC time in milliseconds based on offset.
std::chrono::milliseconds m_interval
bool measure_now()
Perform one measurement immediately.
~FakeNtpRunner()
Stop background thread on destruction.
uint64_t measure_count() const noexcept
Return total number of measurement attempts.
bool start(int interval_ms, bool measure_immediately=true)
Start fake measurements using milliseconds.
void stop()
Stop background measurements.
FakeNtpRunner(NtpClientPool)
Construct fake runner with an unused pool.
FakeNtpRunner & operator=(const FakeNtpRunner &)=delete
std::vector< NtpSample > last_samples() const
Return copy of most recent samples.
Configuration macros for the library.
void init()
Initializes the Time Shield library.
int64_t utc_time_sec() noexcept
Return current UTC time in seconds based on offset.
uint64_t measure_count() noexcept
Return total number of measurement attempts.
bool stale(std::chrono::milliseconds max_age) noexcept
Return true when last measurement is older than max_age.
bool last_measure_ok() noexcept
Return whether last measurement updated the offset.
int64_t offset_us() noexcept
Return last estimated offset in microseconds.
void shutdown()
Stop NTP time service.
int64_t utc_time_ms() noexcept
Return current UTC time in milliseconds based on offset.
int64_t last_success_realtime_us() noexcept
Return realtime timestamp of last successful measurement.
uint64_t fail_count() noexcept
Return number of failed measurement attempts.
int64_t last_update_realtime_us() noexcept
Return realtime timestamp of last measurement attempt.
int64_t utc_time_us() noexcept
Return current UTC time in microseconds based on offset.
ts_ms_t now() noexcept
Get the current UTC timestamp in milliseconds.
int64_t now_realtime_us()
Get current real time in microseconds using a platform-specific method.
detail::FakeNtpRunner RunnerAlias
NtpTimeServiceT< RunnerAlias > g_ntp_time_service
Main namespace for the Time Shield library.
Header file with time-related utility functions.