mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 06:00:05 +00:00
NTP time offset calculation
This commit is contained in:
parent
f85bf9283a
commit
258466e02c
3 changed files with 154 additions and 20 deletions
|
|
@ -48,8 +48,7 @@ struct NtpPacket
|
||||||
std::uint32_t rxTm_s; // Received time-stamp seconds.
|
std::uint32_t rxTm_s; // Received time-stamp seconds.
|
||||||
std::uint32_t rxTm_f; // Received time-stamp fraction of a second.
|
std::uint32_t rxTm_f; // Received time-stamp fraction of a second.
|
||||||
|
|
||||||
std::uint32_t txTm_s; // The most important field the client cares about.
|
std::uint32_t txTm_s; // Transmit time-stamp seconds.
|
||||||
// Transmit time-stamp seconds.
|
|
||||||
std::uint32_t txTm_f; // Transmit time-stamp fraction of a second.
|
std::uint32_t txTm_f; // Transmit time-stamp fraction of a second.
|
||||||
|
|
||||||
static NtpPacket Parse(const std::span<std::uint8_t> data);
|
static NtpPacket Parse(const std::span<std::uint8_t> data);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <boost/asio/ip/udp.hpp>
|
#include <boost/asio/ip/udp.hpp>
|
||||||
#include <boost/asio/thread_pool.hpp>
|
#include <boost/asio/thread_pool.hpp>
|
||||||
#include <boost/asio/use_future.hpp>
|
#include <boost/asio/use_future.hpp>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
namespace scwx::network
|
namespace scwx::network
|
||||||
{
|
{
|
||||||
|
|
@ -15,15 +16,86 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||||
|
|
||||||
static constexpr std::size_t kReceiveBufferSize_ {48u};
|
static constexpr std::size_t kReceiveBufferSize_ {48u};
|
||||||
|
|
||||||
|
class NtpTimestamp
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// NTP epoch: January 1, 1900
|
||||||
|
// Unix epoch: January 1, 1970
|
||||||
|
// Difference = 70 years = 2,208,988,800 seconds
|
||||||
|
static constexpr std::uint32_t kNtpToUnixOffset_ = 2208988800UL;
|
||||||
|
|
||||||
|
// NTP fractional part represents 1/2^32 of a second
|
||||||
|
static constexpr std::uint64_t kFractionalMultiplier_ = 0x100000000ULL;
|
||||||
|
|
||||||
|
static constexpr std::uint64_t _1e9 = 1000000000ULL;
|
||||||
|
|
||||||
|
std::uint32_t seconds_ {0};
|
||||||
|
std::uint32_t fraction_ {0};
|
||||||
|
|
||||||
|
explicit NtpTimestamp() = default;
|
||||||
|
explicit NtpTimestamp(std::uint32_t seconds, std::uint32_t fraction) :
|
||||||
|
seconds_ {seconds}, fraction_ {fraction}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~NtpTimestamp() = default;
|
||||||
|
|
||||||
|
NtpTimestamp(const NtpTimestamp&) = default;
|
||||||
|
NtpTimestamp& operator=(const NtpTimestamp&) = default;
|
||||||
|
NtpTimestamp(NtpTimestamp&&) = default;
|
||||||
|
NtpTimestamp& operator=(NtpTimestamp&&) = default;
|
||||||
|
|
||||||
|
template<typename Clock = std::chrono::system_clock>
|
||||||
|
std::chrono::time_point<Clock> ToTimePoint() const
|
||||||
|
{
|
||||||
|
// Convert NTP seconds to Unix seconds
|
||||||
|
// Don't cast to a larger type to account for rollover, and this should
|
||||||
|
// work until 2106
|
||||||
|
const std::uint32_t unixSeconds = seconds_ - kNtpToUnixOffset_;
|
||||||
|
|
||||||
|
// Convert NTP fraction to nanoseconds
|
||||||
|
const auto nanoseconds =
|
||||||
|
static_cast<std::uint64_t>(fraction_) * _1e9 / kFractionalMultiplier_;
|
||||||
|
|
||||||
|
return std::chrono::time_point<Clock>(
|
||||||
|
std::chrono::duration_cast<typename Clock::duration>(
|
||||||
|
std::chrono::seconds {unixSeconds} +
|
||||||
|
std::chrono::nanoseconds {nanoseconds}));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Clock = std::chrono::system_clock>
|
||||||
|
static NtpTimestamp FromTimePoint(std::chrono::time_point<Clock> timePoint)
|
||||||
|
{
|
||||||
|
// Convert to duration since Unix epoch
|
||||||
|
const auto unixDuration = timePoint.time_since_epoch();
|
||||||
|
|
||||||
|
// Extract seconds and nanoseconds
|
||||||
|
const auto unixSeconds =
|
||||||
|
std::chrono::duration_cast<std::chrono::seconds>(unixDuration);
|
||||||
|
const auto nanoseconds =
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(unixDuration -
|
||||||
|
unixSeconds);
|
||||||
|
|
||||||
|
// Convert Unix seconds to NTP seconds
|
||||||
|
const auto ntpSeconds =
|
||||||
|
static_cast<std::uint32_t>(unixSeconds.count() + kNtpToUnixOffset_);
|
||||||
|
|
||||||
|
// Convert nanoseconds to NTP fractional seconds
|
||||||
|
const auto ntpFraction = static_cast<std::uint32_t>(
|
||||||
|
nanoseconds.count() * kFractionalMultiplier_ / _1e9);
|
||||||
|
|
||||||
|
return NtpTimestamp(ntpSeconds, ntpFraction);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class NtpClient::Impl
|
class NtpClient::Impl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit Impl();
|
explicit Impl();
|
||||||
~Impl();
|
~Impl();
|
||||||
Impl(const Impl&) = delete;
|
Impl(const Impl&) = delete;
|
||||||
Impl& operator=(const Impl&) = delete;
|
Impl& operator=(const Impl&) = delete;
|
||||||
Impl(const Impl&&) = delete;
|
Impl(Impl&&) = delete;
|
||||||
Impl& operator=(const Impl&&) = delete;
|
Impl& operator=(Impl&&) = delete;
|
||||||
|
|
||||||
void Open(std::string_view host, std::string_view service);
|
void Open(std::string_view host, std::string_view service);
|
||||||
void Poll();
|
void Poll();
|
||||||
|
|
@ -31,14 +103,22 @@ public:
|
||||||
|
|
||||||
boost::asio::thread_pool threadPool_ {2u};
|
boost::asio::thread_pool threadPool_ {2u};
|
||||||
|
|
||||||
|
bool enabled_;
|
||||||
|
|
||||||
types::ntp::NtpPacket transmitPacket_ {};
|
types::ntp::NtpPacket transmitPacket_ {};
|
||||||
|
|
||||||
boost::asio::ip::udp::socket socket_;
|
boost::asio::ip::udp::socket socket_;
|
||||||
std::optional<boost::asio::ip::udp::endpoint> serverEndpoint_ {};
|
std::optional<boost::asio::ip::udp::endpoint> serverEndpoint_ {};
|
||||||
std::array<std::uint8_t, kReceiveBufferSize_> receiveBuffer_ {};
|
std::array<std::uint8_t, kReceiveBufferSize_> receiveBuffer_ {};
|
||||||
|
|
||||||
std::vector<std::string> serverList_ {
|
std::chrono::system_clock::duration timeOffset_ {};
|
||||||
"time.nist.gov", "ntp.pool.org", "time.windows.com"};
|
|
||||||
|
std::vector<std::string> serverList_ {"time.nist.gov",
|
||||||
|
"time.cloudflare.com",
|
||||||
|
"ntp.pool.org",
|
||||||
|
"time.aws.com",
|
||||||
|
"time.windows.com",
|
||||||
|
"time.apple.com"};
|
||||||
};
|
};
|
||||||
|
|
||||||
NtpClient::NtpClient() : p(std::make_unique<Impl>()) {}
|
NtpClient::NtpClient() : p(std::make_unique<Impl>()) {}
|
||||||
|
|
@ -59,6 +139,18 @@ void NtpClient::Poll()
|
||||||
|
|
||||||
NtpClient::Impl::Impl() : socket_ {threadPool_}
|
NtpClient::Impl::Impl() : socket_ {threadPool_}
|
||||||
{
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
const auto now =
|
||||||
|
std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now());
|
||||||
|
|
||||||
|
// The NTP timestamp will overflow in 2036. Overflow is handled in such a way
|
||||||
|
// that should work until 2106. Additional handling for subsequent eras is
|
||||||
|
// required.
|
||||||
|
static constexpr auto kMaxYear_ = 2106y;
|
||||||
|
|
||||||
|
enabled_ = now < kMaxYear_ / 1 / 1;
|
||||||
|
|
||||||
transmitPacket_.fields.vn = 3; // Version
|
transmitPacket_.fields.vn = 3; // Version
|
||||||
transmitPacket_.fields.mode = 3; // Client (3)
|
transmitPacket_.fields.mode = 3; // Client (3)
|
||||||
}
|
}
|
||||||
|
|
@ -95,10 +187,15 @@ void NtpClient::Impl::Poll()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
const auto originTimestamp =
|
||||||
|
NtpTimestamp::FromTimePoint(std::chrono::system_clock::now());
|
||||||
|
transmitPacket_.txTm_s = ntohl(originTimestamp.seconds_);
|
||||||
|
transmitPacket_.txTm_f = ntohl(originTimestamp.fraction_);
|
||||||
|
|
||||||
std::size_t transmitPacketSize = sizeof(transmitPacket_);
|
std::size_t transmitPacketSize = sizeof(transmitPacket_);
|
||||||
// Send NTP request
|
// Send NTP request
|
||||||
socket_.send_to(boost::asio::buffer(&transmitPacket_, transmitPacketSize),
|
socket_.send_to(boost::asio::buffer(&transmitPacket_, transmitPacketSize),
|
||||||
*serverEndpoint_);
|
*serverEndpoint_);
|
||||||
|
|
||||||
// Receive NTP response
|
// Receive NTP response
|
||||||
auto future =
|
auto future =
|
||||||
|
|
@ -131,8 +228,55 @@ void NtpClient::Impl::ReceivePacket(std::size_t length)
|
||||||
{
|
{
|
||||||
if (length >= sizeof(types::ntp::NtpPacket))
|
if (length >= sizeof(types::ntp::NtpPacket))
|
||||||
{
|
{
|
||||||
auto packet = types::ntp::NtpPacket::Parse(receiveBuffer_);
|
const auto destinationTime = std::chrono::system_clock::now();
|
||||||
(void) packet;
|
|
||||||
|
const auto packet = types::ntp::NtpPacket::Parse(receiveBuffer_);
|
||||||
|
|
||||||
|
if (packet.stratum == 0)
|
||||||
|
{
|
||||||
|
const std::uint32_t refId = ntohl(packet.refId);
|
||||||
|
const std::string kod =
|
||||||
|
std::string(reinterpret_cast<const char*>(&refId), 4);
|
||||||
|
|
||||||
|
logger_->warn("KoD packet received: {}", kod);
|
||||||
|
|
||||||
|
if (kod == "DENY" || kod == "RSTR")
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
// The client MUST demobilize any associations to that server and
|
||||||
|
// stop sending packets to that server
|
||||||
|
}
|
||||||
|
else if (kod == "RATE")
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
// The client MUST immediately reduce its polling interval to that
|
||||||
|
// server and continue to reduce it each time it receives a RATE
|
||||||
|
// kiss code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto originTimestamp =
|
||||||
|
NtpTimestamp(packet.origTm_s, packet.origTm_f);
|
||||||
|
const auto receiveTimestamp =
|
||||||
|
NtpTimestamp(packet.rxTm_s, packet.rxTm_f);
|
||||||
|
const auto transmitTimestamp =
|
||||||
|
NtpTimestamp(packet.txTm_s, packet.txTm_f);
|
||||||
|
|
||||||
|
const auto originTime = originTimestamp.ToTimePoint();
|
||||||
|
const auto receiveTime = receiveTimestamp.ToTimePoint();
|
||||||
|
const auto transmitTime = transmitTimestamp.ToTimePoint();
|
||||||
|
|
||||||
|
const auto& t0 = originTime;
|
||||||
|
const auto& t1 = receiveTime;
|
||||||
|
const auto& t2 = transmitTime;
|
||||||
|
const auto& t3 = destinationTime;
|
||||||
|
|
||||||
|
// Update time offset
|
||||||
|
timeOffset_ = ((t1 - t0) + (t2 - t3)) / 2;
|
||||||
|
|
||||||
|
logger_->debug("Time offset updated: {:%jd %T}", timeOffset_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,6 @@ NtpPacket NtpPacket::Parse(const std::span<std::uint8_t> data)
|
||||||
|
|
||||||
packet = *reinterpret_cast<const NtpPacket*>(data.data());
|
packet = *reinterpret_cast<const NtpPacket*>(data.data());
|
||||||
|
|
||||||
// Detect Kiss-o'-Death (KoD) packet
|
|
||||||
if (packet.stratum == 0)
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
std::string kissCode =
|
|
||||||
std::string(reinterpret_cast<char*>(&packet.refId), 4);
|
|
||||||
(void) kissCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.rootDelay = ntohl(packet.rootDelay);
|
packet.rootDelay = ntohl(packet.rootDelay);
|
||||||
packet.rootDispersion = ntohl(packet.rootDispersion);
|
packet.rootDispersion = ntohl(packet.rootDispersion);
|
||||||
packet.refId = ntohl(packet.refId);
|
packet.refId = ntohl(packet.refId);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue