Time Shield Library
C++ library for working with time
Loading...
Searching...
No Matches
ntp_client.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef _TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED
4#define _TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED
5
14
15#include "config.hpp"
16
17#if TIME_SHIELD_ENABLE_NTP_CLIENT
18
20#include "time_utils.hpp"
21
22#include <cstdint>
23#include <cstring>
24#include <string>
25#include <mutex>
26#include <atomic>
27#include <functional>
28#include <chrono>
29
30namespace time_shield {
31
34 class NtpClient {
35 public:
37 NtpClient(std::string server = "pool.ntp.org", int port = 123)
38 : m_host(std::move(server)), m_port(port) {
40 }
41
44 bool query() {
46 if (!WsaGuard::instance().success()) {
47 m_is_success = false;
48 throw std::runtime_error("WSAStartup failed with error: " + std::to_string(WsaGuard::instance().ret_code()));
49 }
50
51 SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
52 if (sock == INVALID_SOCKET) {
53 m_is_success = false;
54 return false;
55 }
56
57 struct sockaddr_in addr{};
58 addr.sin_family = AF_INET;
59 addr.sin_port = htons(static_cast<u_short>(m_port));
60
61 addrinfo hints{}, *res = nullptr;
62 hints.ai_family = AF_INET; // IPv4
63 hints.ai_socktype = SOCK_DGRAM;
64 hints.ai_protocol = IPPROTO_UDP;
65
66 if (getaddrinfo(m_host.c_str(), nullptr, &hints, &res) != 0 || !res) {
67 last_error_code_slot() = WSAGetLastError();
68 closesocket(sock);
69 m_is_success = false;
70 return false;
71 }
72
73 sockaddr_in* resolved = reinterpret_cast<sockaddr_in*>(res->ai_addr);
74 addr.sin_addr = resolved->sin_addr;
75 freeaddrinfo(res);
76
77 ntp_packet pkt;
78 fill_packet(pkt);
79
80 int timeout_ms = 5000;
81 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout_ms), sizeof(timeout_ms));
82 if (sendto(sock, reinterpret_cast<const char*>(&pkt), sizeof(pkt), 0,
83 reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
84 last_error_code_slot() = WSAGetLastError();
85 closesocket(sock);
86 m_is_success = false;
87 return false;
88 }
89
90 sockaddr_in from;
91 int from_len = sizeof(from);
92 if (recvfrom(sock, reinterpret_cast<char*>(&pkt), sizeof(pkt), 0,
93 reinterpret_cast<sockaddr*>(&from), &from_len) < 0) {
94 last_error_code_slot() = WSAGetLastError();
95 closesocket(sock);
96 m_is_success = false;
97 return false;
98 }
99
100 closesocket(sock);
101
102 int64_t result_offset;
103 if (parse_packet(pkt, result_offset)) {
104 m_offset_us = result_offset;
105 m_is_success = true;
106 return true;
107 }
108
109 m_is_success = false;
110 return false;
111 }
112
115 bool success() const noexcept {
116 return m_is_success.load();
117 }
118
120 int64_t get_offset_us() const noexcept {
121 return m_offset_us;
122 }
123
126 int64_t get_utc_time_us() const noexcept {
127 const int64_t offset = m_offset_us.load();
128 return now_realtime_us() + offset;
129 }
130
133 int64_t get_utc_time_ms() const noexcept {
134 return get_utc_time_us() / 1000;
135 }
136
139 time_t get_utc_time() const noexcept {
140 return static_cast<time_t>(get_utc_time_us() / 1000000);
141 }
142
144 int get_last_error_code() const noexcept {
145 return last_error_code_slot();
146 }
147
148 private:
149 static constexpr int64_t NTP_TIMESTAMP_DELTA = 2208988800ll;
150
153 struct ntp_packet {
154 uint8_t li_vn_mode; // Eight bits. li, vn, and mode.
155 // li. Two bits. Leap indicator.
156 // vn. Three bits. Version number of the protocol.
157 // mode. Three bits. Client will pick mode 3 for client.
158 uint8_t stratum; // Eight bits. Stratum level of the local clock.
159 uint8_t poll; // Eight bits. Maximum interval between successive messages.
160 uint8_t precision; // Eight bits. Precision of the local clock.
161 uint32_t root_delay; // 32 bits. Total round trip delay time.
162 uint32_t root_dispersion; // 32 bits. Max error aloud from primary clock source.
163 uint32_t ref_id; // 32 bits. Reference clock identifier.
164 uint32_t ref_ts_sec; // 32 bits. Reference time-stamp seconds.
165 uint32_t ref_ts_frac; // 32 bits. Reference time-stamp fraction of a second.
166 uint32_t orig_ts_sec; // 32 bits. Originate time-stamp seconds.
167 uint32_t orig_ts_frac; // 32 bits. Originate time-stamp fraction of a second.
168 uint32_t recv_ts_sec; // 32 bits. Received time-stamp seconds.
169 uint32_t recv_ts_frac; // 32 bits. Received time-stamp fraction of a second.
170 uint32_t tx_ts_sec; // 32 bits and the most important field the client cares about. Transmit time-stamp seconds.
171 uint32_t tx_ts_frac; // 32 bits. Transmit time-stamp fraction of a second.
172 };
173
174 std::string m_host;
175 int m_port = 123;
176 std::atomic<int64_t> m_offset_us{0};
177 std::atomic<bool> m_is_success{false};
178 static int& last_error_code_slot() noexcept {
179 static thread_local int value = 0;
180 return value;
181 }
182
184 void fill_packet(ntp_packet& pkt) const {
185 std::memset(&pkt, 0, sizeof(pkt));
186 pkt.li_vn_mode = (0 << 6) | (3 << 3) | 3; // LI=0, VN=3, Mode=3 (client)
187
188 const uint64_t now_us = time_shield::now_realtime_us();
189 const uint64_t sec = now_us / 1000000 + NTP_TIMESTAMP_DELTA;
190 const uint64_t frac = ((now_us % 1000000) * 0x100000000ULL) / 1000000;
191
192 pkt.tx_ts_sec = htonl(static_cast<uint32_t>(sec));
193 pkt.tx_ts_frac = htonl(static_cast<uint32_t>(frac));
194 }
195
197 bool parse_packet(const ntp_packet& pkt, int64_t& result_offset_us) const {
198 const uint64_t arrival_us = time_shield::now_realtime_us();
199
200 const uint64_t originate_us = ((static_cast<uint64_t>(ntohl(pkt.orig_ts_sec)) - NTP_TIMESTAMP_DELTA) * 1000000) +
201 (static_cast<uint64_t>(ntohl(pkt.orig_ts_frac)) * 1000000 / 0xFFFFFFFFull);
202 const uint64_t receive_us = ((static_cast<uint64_t>(ntohl(pkt.recv_ts_sec)) - NTP_TIMESTAMP_DELTA) * 1000000) +
203 (static_cast<uint64_t>(ntohl(pkt.recv_ts_frac)) * 1000000 / 0xFFFFFFFFull);
204 const uint64_t transmit_us = ((static_cast<uint64_t>(ntohl(pkt.tx_ts_sec)) - NTP_TIMESTAMP_DELTA) * 1000000) +
205 (static_cast<uint64_t>(ntohl(pkt.tx_ts_frac)) * 1000000 / 0xFFFFFFFFull);
206
207 // RFC 5905
208 result_offset_us = ((static_cast<int64_t>(receive_us) - static_cast<int64_t>(originate_us))
209 + (static_cast<int64_t>(transmit_us) - static_cast<int64_t>(arrival_us))) / 2;
210 return true;
211 }
212 };
213
214} // namespace time_shield
215
216#else // !TIME_SHIELD_ENABLE_NTP_CLIENT
217
218# warning "NtpClient is disabled or unsupported on this platform."
219
220namespace time_shield {
221
223 class NtpClient {
224 public:
226 static_assert(sizeof(void*) == 0, "time_shield::NtpClient is disabled by configuration.");
227 }
228 };
229
230} // namespace time_shield
231
232#endif // TIME_SHIELD_ENABLE_NTP_CLIENT
233
234#endif // _TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED
Simple Windows-only NTP client for measuring time offset.
void fill_packet(ntp_packet &pkt) const
Converts local time to NTP timestamp format.
bool success() const noexcept
Returns whether the last NTP query was successful.
NtpClient(std::string server="pool.ntp.org", int port=123)
Constructs NTP client with specified host and port.
bool parse_packet(const ntp_packet &pkt, int64_t &result_offset_us) const
Parses response and calculates offset.
int64_t get_utc_time_us() const noexcept
Returns current UTC time in microseconds based on last NTP offset.
static constexpr int64_t NTP_TIMESTAMP_DELTA
Seconds between 1900 and 1970 epochs.
time_t get_utc_time() const noexcept
Returns current UTC time as time_t (seconds since Unix epoch).
int64_t get_offset_us() const noexcept
Returns the last measured offset in microseconds.
int64_t get_utc_time_ms() const noexcept
Returns current UTC time in milliseconds based on last NTP offset.
int get_last_error_code() const noexcept
Returns last WinSock error code (if any).
static int & last_error_code_slot() noexcept
std::atomic< int64_t > m_offset_us
std::atomic< bool > m_is_success
bool query()
Queries the NTP server and updates the local offset.
static const WsaGuard & instance()
Returns the singleton instance, initializing WSA if needed.
Definition wsa_guard.hpp:31
Configuration macros for the library.
int64_t now_realtime_us()
Get current real time in microseconds using a hybrid method.
Main namespace for the Time Shield library.
Структура пакета NTP Total: 384 bits or 48 bytes.
Header file with time-related utility functions.
Singleton guard for WinSock initialization.