Time Shield Library
C++ library for working with time
Loading...
Searching...
No Matches
ntp_packet.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef _TIME_SHIELD_NTP_PACKET_HPP_INCLUDED
4#define _TIME_SHIELD_NTP_PACKET_HPP_INCLUDED
5
6#include <cstdint>
7#include <cstring>
8
9#if TIME_SHIELD_PLATFORM_WINDOWS
10# include <winsock2.h>
11#else
12# include <arpa/inet.h>
13#endif
14
15namespace time_shield {
16namespace detail {
17
20 struct NtpPacket {
21 uint8_t li_vn_mode;
22 uint8_t stratum;
23 uint8_t poll;
24 uint8_t precision;
25 uint32_t root_delay;
27 uint32_t ref_id;
28 uint32_t ref_ts_sec;
29 uint32_t ref_ts_frac;
30 uint32_t orig_ts_sec;
31 uint32_t orig_ts_frac;
32 uint32_t recv_ts_sec;
33 uint32_t recv_ts_frac;
34 uint32_t tx_ts_sec;
35 uint32_t tx_ts_frac;
36 };
37
38 static_assert(sizeof(NtpPacket) == 48, "NtpPacket must be 48 bytes");
39
51
53 static inline uint8_t ntp_li(uint8_t li_vn_mode) noexcept {
54 return static_cast<uint8_t>((li_vn_mode >> 6) & 0x03);
55 }
56
58 static inline uint8_t ntp_vn(uint8_t li_vn_mode) noexcept {
59 return static_cast<uint8_t>((li_vn_mode >> 3) & 0x07);
60 }
61
63 static inline uint8_t ntp_mode(uint8_t li_vn_mode) noexcept {
64 return static_cast<uint8_t>(li_vn_mode & 0x07);
65 }
66
68 static inline uint64_t ntp_frac_to_us(uint32_t frac_net) noexcept {
69 const uint64_t frac = static_cast<uint64_t>(ntohl(frac_net));
70 return (frac * 1000000ULL) >> 32;
71 }
72
74 static inline bool ntp_ts_to_unix_us(uint32_t sec_net, uint32_t frac_net, uint64_t& out_us) noexcept {
75 static const int64_t NTP_TIMESTAMP_DELTA = 2208988800ll;
76 const int64_t sec = static_cast<int64_t>(ntohl(sec_net)) - NTP_TIMESTAMP_DELTA;
77 if (sec < 0) return false;
78 out_us = static_cast<uint64_t>(sec) * 1000000ULL + ntp_frac_to_us(frac_net);
79 return true;
80 }
81
83 static inline void fill_client_packet(NtpPacket& pkt, uint64_t now_us) {
84 std::memset(&pkt, 0, sizeof(pkt));
85 pkt.li_vn_mode = static_cast<uint8_t>((0 << 6) | (3 << 3) | 3); // LI=0, VN=3, Mode=3
86
87 const uint64_t sec = now_us / 1000000 + 2208988800ULL;
88 const uint64_t frac = ((now_us % 1000000) * 0x100000000ULL) / 1000000;
89
90 pkt.tx_ts_sec = htonl(static_cast<uint32_t>(sec));
91 pkt.tx_ts_frac = htonl(static_cast<uint32_t>(frac));
92 }
93
95 static inline bool parse_server_packet(const NtpPacket& pkt,
96 uint64_t arrival_us,
97 int64_t& offset_us,
98 int64_t& delay_us,
99 int& stratum,
100 int& out_error_code) noexcept {
101 const uint8_t li = ntp_li(pkt.li_vn_mode);
102 const uint8_t vn = ntp_vn(pkt.li_vn_mode);
103 const uint8_t mode = ntp_mode(pkt.li_vn_mode);
104
105 if (mode != 4) {
106 out_error_code = NTP_E_BAD_MODE;
107 return false;
108 }
109 if (vn < 3 || vn > 4) {
110 out_error_code = NTP_E_BAD_VERSION;
111 return false;
112 }
113 if (li == 3) {
114 out_error_code = NTP_E_BAD_LI;
115 return false;
116 }
117 if (pkt.stratum == 0) {
118 out_error_code = NTP_E_KOD;
119 return false;
120 }
121 if (pkt.stratum >= 16) {
122 out_error_code = NTP_E_BAD_STRATUM;
123 return false;
124 }
125
126 uint64_t originate_us = 0;
127 uint64_t receive_us = 0;
128 uint64_t transmit_us = 0;
129
130 if (!ntp_ts_to_unix_us(pkt.orig_ts_sec, pkt.orig_ts_frac, originate_us)) {
131 out_error_code = NTP_E_BAD_TS;
132 return false;
133 }
134 if (!ntp_ts_to_unix_us(pkt.recv_ts_sec, pkt.recv_ts_frac, receive_us)) {
135 out_error_code = NTP_E_BAD_TS;
136 return false;
137 }
138 if (!ntp_ts_to_unix_us(pkt.tx_ts_sec, pkt.tx_ts_frac, transmit_us)) {
139 out_error_code = NTP_E_BAD_TS;
140 return false;
141 }
142
143 const int64_t t1 = static_cast<int64_t>(originate_us);
144 const int64_t t2 = static_cast<int64_t>(receive_us);
145 const int64_t t3 = static_cast<int64_t>(transmit_us);
146 const int64_t t4 = static_cast<int64_t>(arrival_us);
147
148 if (t3 < t2) {
149 out_error_code = NTP_E_BAD_TS;
150 return false;
151 }
152
153 offset_us = ((t2 - t1) + (t3 - t4)) / 2;
154 delay_us = (t4 - t1) - (t3 - t2);
155 if (delay_us < 0) {
156 out_error_code = NTP_E_BAD_TS;
157 return false;
158 }
159 stratum = pkt.stratum;
160 return true;
161 }
162
163} // namespace detail
164} // namespace time_shield
165
166#endif // _TIME_SHIELD_NTP_PACKET_HPP_INCLUDED
NtpProtoError
Protocol-level error codes for NTP parsing.
static uint64_t ntp_frac_to_us(uint32_t frac_net) noexcept
Convert NTP fractional seconds to microseconds.
static uint8_t ntp_li(uint8_t li_vn_mode) noexcept
Extract leap indicator from LI/VN/Mode field.
static uint8_t ntp_mode(uint8_t li_vn_mode) noexcept
Extract mode from LI/VN/Mode field.
static uint8_t ntp_vn(uint8_t li_vn_mode) noexcept
Extract version number from LI/VN/Mode field.
static bool ntp_ts_to_unix_us(uint32_t sec_net, uint32_t frac_net, uint64_t &out_us) noexcept
Convert NTP timestamp parts to Unix microseconds.
static bool parse_server_packet(const NtpPacket &pkt, uint64_t arrival_us, int64_t &offset_us, int64_t &delay_us, int &stratum, int &out_error_code) noexcept
Parse server response and compute offset and delay.
static void fill_client_packet(NtpPacket &pkt, uint64_t now_us)
Fill an NTP client request packet using local time.
Main namespace for the Time Shield library.
NTP packet layout (48 bytes).