mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 03:40: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
				
			
		|  | @ -6,6 +6,7 @@ | |||
| #include <boost/asio/ip/udp.hpp> | ||||
| #include <boost/asio/thread_pool.hpp> | ||||
| #include <boost/asio/use_future.hpp> | ||||
| #include <fmt/chrono.h> | ||||
| 
 | ||||
| namespace scwx::network | ||||
| { | ||||
|  | @ -15,15 +16,86 @@ static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | |||
| 
 | ||||
| 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 | ||||
| { | ||||
| public: | ||||
|    explicit Impl(); | ||||
|    ~Impl(); | ||||
|    Impl(const Impl&)             = delete; | ||||
|    Impl& operator=(const Impl&)  = delete; | ||||
|    Impl(const Impl&&)            = delete; | ||||
|    Impl& operator=(const Impl&&) = delete; | ||||
|    Impl(const Impl&)            = delete; | ||||
|    Impl& operator=(const Impl&) = delete; | ||||
|    Impl(Impl&&)                 = delete; | ||||
|    Impl& operator=(Impl&&)      = delete; | ||||
| 
 | ||||
|    void Open(std::string_view host, std::string_view service); | ||||
|    void Poll(); | ||||
|  | @ -31,14 +103,22 @@ public: | |||
| 
 | ||||
|    boost::asio::thread_pool threadPool_ {2u}; | ||||
| 
 | ||||
|    bool enabled_; | ||||
| 
 | ||||
|    types::ntp::NtpPacket transmitPacket_ {}; | ||||
| 
 | ||||
|    boost::asio::ip::udp::socket                  socket_; | ||||
|    std::optional<boost::asio::ip::udp::endpoint> serverEndpoint_ {}; | ||||
|    std::array<std::uint8_t, kReceiveBufferSize_> receiveBuffer_ {}; | ||||
| 
 | ||||
|    std::vector<std::string> serverList_ { | ||||
|       "time.nist.gov", "ntp.pool.org", "time.windows.com"}; | ||||
|    std::chrono::system_clock::duration timeOffset_ {}; | ||||
| 
 | ||||
|    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>()) {} | ||||
|  | @ -59,6 +139,18 @@ void NtpClient::Poll() | |||
| 
 | ||||
| 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.mode = 3; // Client (3)
 | ||||
| } | ||||
|  | @ -95,10 +187,15 @@ void NtpClient::Impl::Poll() | |||
| 
 | ||||
|    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_); | ||||
|       // Send NTP request
 | ||||
|       socket_.send_to(boost::asio::buffer(&transmitPacket_, transmitPacketSize), | ||||
|          *serverEndpoint_); | ||||
|                       *serverEndpoint_); | ||||
| 
 | ||||
|       // Receive NTP response
 | ||||
|       auto future = | ||||
|  | @ -131,8 +228,55 @@ void NtpClient::Impl::ReceivePacket(std::size_t length) | |||
| { | ||||
|    if (length >= sizeof(types::ntp::NtpPacket)) | ||||
|    { | ||||
|       auto packet = types::ntp::NtpPacket::Parse(receiveBuffer_); | ||||
|       (void) packet; | ||||
|       const auto destinationTime = std::chrono::system_clock::now(); | ||||
| 
 | ||||
|       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 | ||||
|    { | ||||
|  |  | |||
|  | @ -20,15 +20,6 @@ NtpPacket NtpPacket::Parse(const std::span<std::uint8_t> 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.rootDispersion = ntohl(packet.rootDispersion); | ||||
|    packet.refId          = ntohl(packet.refId); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat