mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-29 18:00:05 +00:00
Initial NTP protocol functionality
This commit is contained in:
parent
5a8ebfa7ae
commit
94d81c0c6b
7 changed files with 318 additions and 5 deletions
21
test/source/scwx/network/ntp_client.test.cpp
Normal file
21
test/source/scwx/network/ntp_client.test.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include <scwx/network/ntp_client.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace network
|
||||
{
|
||||
|
||||
TEST(NtpClient, Poll)
|
||||
{
|
||||
NtpClient client {};
|
||||
|
||||
client.Open("time.nist.gov", "123");
|
||||
//client.Open("pool.ntp.org", "123");
|
||||
//client.Open("time.windows.com", "123");
|
||||
client.Poll();
|
||||
}
|
||||
|
||||
} // namespace network
|
||||
} // namespace scwx
|
||||
|
|
@ -17,7 +17,8 @@ set(SRC_AWIPS_TESTS source/scwx/awips/coded_location.test.cpp
|
|||
set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp
|
||||
source/scwx/common/products.test.cpp)
|
||||
set(SRC_GR_TESTS source/scwx/gr/placefile.test.cpp)
|
||||
set(SRC_NETWORK_TESTS source/scwx/network/dir_list.test.cpp)
|
||||
set(SRC_NETWORK_TESTS source/scwx/network/dir_list.test.cpp
|
||||
source/scwx/network/ntp_client.test.cpp)
|
||||
set(SRC_PROVIDER_TESTS source/scwx/provider/aws_level2_data_provider.test.cpp
|
||||
source/scwx/provider/aws_level3_data_provider.test.cpp
|
||||
source/scwx/provider/iem_api_provider.test.cpp
|
||||
|
|
|
|||
32
wxdata/include/scwx/network/ntp_client.hpp
Normal file
32
wxdata/include/scwx/network/ntp_client.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace scwx::network
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief NTP Client
|
||||
*/
|
||||
class NtpClient
|
||||
{
|
||||
public:
|
||||
explicit NtpClient();
|
||||
~NtpClient();
|
||||
|
||||
NtpClient(const NtpClient&) = delete;
|
||||
NtpClient& operator=(const NtpClient&) = delete;
|
||||
|
||||
NtpClient(NtpClient&&) noexcept;
|
||||
NtpClient& operator=(NtpClient&&) noexcept;
|
||||
|
||||
void Open(std::string_view host, std::string_view service);
|
||||
void Poll();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace scwx::network
|
||||
61
wxdata/include/scwx/types/ntp_types.hpp
Normal file
61
wxdata/include/scwx/types/ntp_types.hpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace scwx::types::ntp
|
||||
{
|
||||
|
||||
/* Adapted from:
|
||||
* https://github.com/lettier/ntpclient/blob/master/source/c/main.c
|
||||
*
|
||||
* Copyright (c) 2014 David Lettier
|
||||
* Copyright (c) 2020 Krystian Stasiowski
|
||||
* Distributed under the BSD 3-Clause License (See
|
||||
* https://github.com/lettier/ntpclient/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct NtpPacket
|
||||
{
|
||||
union
|
||||
{
|
||||
std::uint8_t li_vn_mode;
|
||||
struct
|
||||
{
|
||||
std::uint8_t mode : 3; // Client will pick mode 3 for client.
|
||||
std::uint8_t vn : 3; // Version number of the protocol.
|
||||
std::uint8_t li : 2; // Leap indicator.
|
||||
} fields;
|
||||
};
|
||||
|
||||
std::uint8_t stratum; // Stratum level of the local clock.
|
||||
std::uint8_t poll; // Maximum interval between successive messages.
|
||||
std::uint8_t precision; // Precision of the local clock.
|
||||
|
||||
std::uint32_t rootDelay; // Total round trip delay time.
|
||||
std::uint32_t rootDispersion; // Max error aloud from primary clock source.
|
||||
std::uint32_t refId; // Reference clock identifier.
|
||||
|
||||
std::uint32_t refTm_s; // Reference time-stamp seconds.
|
||||
std::uint32_t refTm_f; // Reference time-stamp fraction of a second.
|
||||
|
||||
std::uint32_t origTm_s; // Originate time-stamp seconds.
|
||||
std::uint32_t origTm_f; // Originate time-stamp fraction of a second.
|
||||
|
||||
std::uint32_t rxTm_s; // Received time-stamp seconds.
|
||||
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.
|
||||
// Transmit time-stamp seconds.
|
||||
std::uint32_t txTm_f; // Transmit time-stamp fraction of a second.
|
||||
|
||||
static NtpPacket Parse(const std::span<std::uint8_t> data);
|
||||
};
|
||||
// Total: 48 bytes.
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
} // namespace scwx::types::ntp
|
||||
143
wxdata/source/scwx/network/ntp_client.cpp
Normal file
143
wxdata/source/scwx/network/ntp_client.cpp
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#include <scwx/network/ntp_client.hpp>
|
||||
#include <scwx/types/ntp_types.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/asio/use_future.hpp>
|
||||
|
||||
namespace scwx::network
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::network::ntp_client";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static constexpr std::size_t kReceiveBufferSize_ {48u};
|
||||
|
||||
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;
|
||||
|
||||
void Open(std::string_view host, std::string_view service);
|
||||
void Poll();
|
||||
void ReceivePacket(std::size_t length);
|
||||
|
||||
boost::asio::thread_pool threadPool_ {2u};
|
||||
|
||||
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"};
|
||||
};
|
||||
|
||||
NtpClient::NtpClient() : p(std::make_unique<Impl>()) {}
|
||||
NtpClient::~NtpClient() = default;
|
||||
|
||||
NtpClient::NtpClient(NtpClient&&) noexcept = default;
|
||||
NtpClient& NtpClient::operator=(NtpClient&&) noexcept = default;
|
||||
|
||||
void NtpClient::Open(std::string_view host, std::string_view service)
|
||||
{
|
||||
p->Open(host, service);
|
||||
}
|
||||
|
||||
void NtpClient::Poll()
|
||||
{
|
||||
p->Poll();
|
||||
}
|
||||
|
||||
NtpClient::Impl::Impl() : socket_ {threadPool_}
|
||||
{
|
||||
transmitPacket_.fields.vn = 3; // Version
|
||||
transmitPacket_.fields.mode = 3; // Client (3)
|
||||
}
|
||||
|
||||
NtpClient::Impl::~Impl()
|
||||
{
|
||||
threadPool_.join();
|
||||
}
|
||||
|
||||
void NtpClient::Impl::Open(std::string_view host, std::string_view service)
|
||||
{
|
||||
boost::asio::ip::udp::resolver resolver(threadPool_);
|
||||
boost::system::error_code ec;
|
||||
|
||||
auto results = resolver.resolve(host, service, ec);
|
||||
if (ec.value() == boost::system::errc::success && !results.empty())
|
||||
{
|
||||
logger_->info("Using NTP server: {}", host);
|
||||
serverEndpoint_ = *results.begin();
|
||||
socket_.open(serverEndpoint_->protocol());
|
||||
}
|
||||
else
|
||||
{
|
||||
serverEndpoint_ = std::nullopt;
|
||||
logger_->warn("Could not resolve host {}: {}", host, ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::Impl::Poll()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static constexpr auto kTimeout_ = 15s;
|
||||
|
||||
try
|
||||
{
|
||||
std::size_t transmitPacketSize = sizeof(transmitPacket_);
|
||||
// Send NTP request
|
||||
socket_.send_to(boost::asio::buffer(&transmitPacket_, transmitPacketSize),
|
||||
*serverEndpoint_);
|
||||
|
||||
// Receive NTP response
|
||||
auto future =
|
||||
socket_.async_receive_from(boost::asio::buffer(receiveBuffer_),
|
||||
*serverEndpoint_,
|
||||
boost::asio::use_future);
|
||||
std::size_t bytesReceived = 0;
|
||||
|
||||
switch (future.wait_for(kTimeout_))
|
||||
{
|
||||
case std::future_status::ready:
|
||||
bytesReceived = future.get();
|
||||
ReceivePacket(bytesReceived);
|
||||
break;
|
||||
|
||||
case std::future_status::timeout:
|
||||
case std::future_status::deferred:
|
||||
logger_->warn("Timeout waiting for NTP response");
|
||||
socket_.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
logger_->error("Error polling: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::Impl::ReceivePacket(std::size_t length)
|
||||
{
|
||||
if (length >= sizeof(types::ntp::NtpPacket))
|
||||
{
|
||||
auto packet = types::ntp::NtpPacket::Parse(receiveBuffer_);
|
||||
(void) packet;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->warn("Received too few bytes: {}", length);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace scwx::network
|
||||
51
wxdata/source/scwx/types/ntp_types.cpp
Normal file
51
wxdata/source/scwx/types/ntp_types.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include <scwx/types/ntp_types.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <WinSock2.h>
|
||||
#else
|
||||
# include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
namespace scwx::types::ntp
|
||||
{
|
||||
|
||||
NtpPacket NtpPacket::Parse(const std::span<std::uint8_t> data)
|
||||
{
|
||||
NtpPacket packet;
|
||||
|
||||
assert(data.size() >= sizeof(NtpPacket));
|
||||
|
||||
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);
|
||||
|
||||
packet.refTm_s = ntohl(packet.refTm_s);
|
||||
packet.refTm_f = ntohl(packet.refTm_f);
|
||||
|
||||
packet.origTm_s = ntohl(packet.origTm_s);
|
||||
packet.origTm_f = ntohl(packet.origTm_f);
|
||||
|
||||
packet.rxTm_s = ntohl(packet.rxTm_s);
|
||||
packet.rxTm_f = ntohl(packet.rxTm_f);
|
||||
|
||||
packet.txTm_s = ntohl(packet.txTm_s);
|
||||
packet.txTm_f = ntohl(packet.txTm_f);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
} // namespace scwx::types::ntp
|
||||
|
|
@ -60,9 +60,11 @@ set(HDR_GR include/scwx/gr/color.hpp
|
|||
set(SRC_GR source/scwx/gr/color.cpp
|
||||
source/scwx/gr/placefile.cpp)
|
||||
set(HDR_NETWORK include/scwx/network/cpr.hpp
|
||||
include/scwx/network/dir_list.hpp)
|
||||
include/scwx/network/dir_list.hpp
|
||||
include/scwx/network/ntp_client.hpp)
|
||||
set(SRC_NETWORK source/scwx/network/cpr.cpp
|
||||
source/scwx/network/dir_list.cpp)
|
||||
source/scwx/network/dir_list.cpp
|
||||
source/scwx/network/ntp_client.cpp)
|
||||
set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp
|
||||
include/scwx/provider/aws_level2_chunks_data_provider.hpp
|
||||
include/scwx/provider/aws_level3_data_provider.hpp
|
||||
|
|
@ -80,8 +82,10 @@ set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp
|
|||
source/scwx/provider/nexrad_data_provider.cpp
|
||||
source/scwx/provider/nexrad_data_provider_factory.cpp
|
||||
source/scwx/provider/warnings_provider.cpp)
|
||||
set(HDR_TYPES include/scwx/types/iem_types.hpp)
|
||||
set(SRC_TYPES source/scwx/types/iem_types.cpp)
|
||||
set(HDR_TYPES include/scwx/types/iem_types.hpp
|
||||
include/scwx/types/ntp_types.hpp)
|
||||
set(SRC_TYPES source/scwx/types/iem_types.cpp
|
||||
source/scwx/types/ntp_types.cpp)
|
||||
set(HDR_UTIL include/scwx/util/digest.hpp
|
||||
include/scwx/util/enum.hpp
|
||||
include/scwx/util/environment.hpp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue