libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
ntptime.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_NETWORK
13#include <chrono>
14#include <cstdint>
15#include <promeki/duration.h>
16#include <promeki/namespace.h>
17
18PROMEKI_NAMESPACE_BEGIN
19
50class NtpTime {
51 public:
56 static constexpr uint64_t UnixEpochOffsetSeconds = 2208988800ULL;
57
59 NtpTime() = default;
60
62 NtpTime(uint32_t sec, uint32_t frac) : _seconds(sec), _fraction(frac) {}
63
65 static NtpTime now() { return fromSystemClock(std::chrono::system_clock::now()); }
66
73 static NtpTime fromSystemClock(std::chrono::system_clock::time_point tp) {
74 // system_clock has implementation-defined epoch
75 // (Unix 1970-01-01 on every platform we ship to).
76 // Bridge to NTP via the constant above.
77 const auto nsSinceUnix =
78 std::chrono::duration_cast<std::chrono::nanoseconds>(tp.time_since_epoch()).count();
79 // Negative inputs (pre-1970) clamp to NTP epoch
80 // because NtpTime is unsigned.
81 if (nsSinceUnix < 0) return NtpTime(0, 0);
82 const uint64_t totalNs = static_cast<uint64_t>(nsSinceUnix) +
83 (UnixEpochOffsetSeconds * 1'000'000'000ULL);
84 const uint64_t secs = totalNs / 1'000'000'000ULL;
85 const uint64_t remNs = totalNs % 1'000'000'000ULL;
86 // fraction = remNs * 2^32 / 1e9. Compute with
87 // 64-bit intermediate so the 2^32 multiply
88 // doesn't overflow.
89 const uint64_t frac = (remNs << 32) / 1'000'000'000ULL;
90 return NtpTime(static_cast<uint32_t>(secs & 0xFFFFFFFFu),
91 static_cast<uint32_t>(frac & 0xFFFFFFFFu));
92 }
93
95 uint32_t seconds() const { return _seconds; }
96
98 uint32_t fraction() const { return _fraction; }
99
101 uint64_t toUint64() const {
102 return (static_cast<uint64_t>(_seconds) << 32) | static_cast<uint64_t>(_fraction);
103 }
104
109 uint32_t toCompact32() const { return ((_seconds & 0xFFFFu) << 16) | (_fraction >> 16); }
110
111 bool isValid() const { return _seconds != 0 || _fraction != 0; }
112
113 bool operator==(const NtpTime &o) const { return _seconds == o._seconds && _fraction == o._fraction; }
114 bool operator!=(const NtpTime &o) const { return !(*this == o); }
115
138 NtpTime operator+(const Duration &d) const {
139 const int64_t ns = d.nanoseconds();
140 const uint64_t packed = (static_cast<uint64_t>(_seconds) << 32) |
141 static_cast<uint64_t>(_fraction);
142 // Split the offset into whole-seconds + sub-
143 // second nanoseconds. Each piece converts to
144 // the 1/2^32-second packed representation
145 // independently, so the intermediate never
146 // overflows 64 bits even when the user hands
147 // us a multi-day duration:
148 // * wholeSec contributes wholeSec << 32.
149 // * subNs ∈ (-1e9, +1e9) maps via
150 // subNs * 2^32 / 1e9 — and 1e9 * 2^32 fits
151 // in 63 bits (≈ 4.29e18 < 2^63).
152 // Sums happen in uint64_t (well-defined modular
153 // arithmetic) — overflow at the 2^64 boundary
154 // wraps the same way NTP receivers handle
155 // wraparound across the 2^32-second epoch.
156 const int64_t wholeSec = ns / 1'000'000'000;
157 const int64_t subNs = ns % 1'000'000'000;
158 const int64_t subFrac = (subNs << 32) / 1'000'000'000;
159 const uint64_t deltaPacked =
160 (static_cast<uint64_t>(wholeSec) << 32) + static_cast<uint64_t>(subFrac);
161 const uint64_t result = packed + deltaPacked;
162 return NtpTime(static_cast<uint32_t>(result >> 32) & 0xFFFFFFFFu,
163 static_cast<uint32_t>(result & 0xFFFFFFFFu));
164 }
165
167 NtpTime operator-(const Duration &d) const { return *this + (-d); }
168
169 private:
170 uint32_t _seconds = 0;
171 uint32_t _fraction = 0;
172};
173
174PROMEKI_NAMESPACE_END
175
176#endif // PROMEKI_ENABLE_NETWORK