mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-29 18:50:05 +00:00
Merge pull request #498 from dpaulat/feature/ntp
Use NTP to synchronize current time
This commit is contained in:
commit
5b031b7095
36 changed files with 918 additions and 61 deletions
|
|
@ -7,8 +7,10 @@ Checks:
|
|||
- 'modernize-*'
|
||||
- 'performance-*'
|
||||
- '-bugprone-easily-swappable-parameters'
|
||||
- '-cppcoreguidelines-pro-type-reinterpret-cast'
|
||||
- '-cppcoreguidelines-avoid-do-while'
|
||||
- '-cppcoreguidelines-avoid-non-const-global-variables'
|
||||
- '-cppcoreguidelines-pro-type-reinterpret-cast'
|
||||
- '-cppcoreguidelines-pro-type-union-access'
|
||||
- '-misc-include-cleaner'
|
||||
- '-misc-non-private-member-variables-in-classes'
|
||||
- '-misc-use-anonymous-namespace'
|
||||
|
|
|
|||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -393,7 +393,7 @@ jobs:
|
|||
env:
|
||||
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
|
||||
MAPTILER_API_KEY: ${{ secrets.MAPTILER_API_KEY }}
|
||||
run: ctest -C ${{ matrix.build_type }} --exclude-regex "test_mln.*|UpdateManager.*"
|
||||
run: ctest -C ${{ matrix.build_type }} --exclude-regex "test_mln.*|NtpClient.*|UpdateManager.*"
|
||||
|
||||
- name: Upload Test Logs
|
||||
if: ${{ !cancelled() }}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
|
|||
source/scwx/qt/manager/radar_product_manager_notifier.hpp
|
||||
source/scwx/qt/manager/resource_manager.hpp
|
||||
source/scwx/qt/manager/settings_manager.hpp
|
||||
source/scwx/qt/manager/task_manager.hpp
|
||||
source/scwx/qt/manager/text_event_manager.hpp
|
||||
source/scwx/qt/manager/thread_manager.hpp
|
||||
source/scwx/qt/manager/timeline_manager.hpp
|
||||
|
|
@ -126,6 +127,7 @@ set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp
|
|||
source/scwx/qt/manager/radar_product_manager_notifier.cpp
|
||||
source/scwx/qt/manager/resource_manager.cpp
|
||||
source/scwx/qt/manager/settings_manager.cpp
|
||||
source/scwx/qt/manager/task_manager.cpp
|
||||
source/scwx/qt/manager/text_event_manager.cpp
|
||||
source/scwx/qt/manager/thread_manager.cpp
|
||||
source/scwx/qt/manager/timeline_manager.cpp
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <scwx/qt/util/texture_atlas.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
||||
|
|
@ -313,7 +314,7 @@ void GeoIcons::Render(const QMapLibre::CustomLayerRenderParameters& params,
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
@ -930,7 +931,7 @@ bool GeoIcons::RunMousePicking(
|
|||
// If no time has been selected, use the current time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
|
||||
// For each pickable icon
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
||||
|
|
@ -284,7 +285,7 @@ void GeoLines::Render(const QMapLibre::CustomLayerRenderParameters& params)
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
@ -723,7 +724,7 @@ bool GeoLines::RunMousePicking(
|
|||
// If no time has been selected, use the current time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
|
||||
// For each pickable line
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <scwx/qt/util/texture_atlas.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
||||
|
|
@ -295,7 +296,7 @@ void PlacefileIcons::Render(
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
@ -720,7 +721,7 @@ bool PlacefileIcons::RunMousePicking(
|
|||
// If no time has been selected, use the current time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
|
||||
// For each pickable icon
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/texture_atlas.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
|
|
@ -264,7 +265,7 @@ void PlacefileImages::Render(
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
||||
|
|
@ -248,7 +249,7 @@ void PlacefileLines::Render(
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
@ -526,7 +527,7 @@ bool PlacefileLines::RunMousePicking(
|
|||
// If no time has been selected, use the current time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
|
||||
// For each pickable line
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <scwx/qt/gl/draw/placefile_polygons.hpp>
|
||||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
|
|
@ -259,7 +260,7 @@ void PlacefilePolygons::Render(
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <imgui.h>
|
||||
|
|
@ -127,7 +128,7 @@ void PlacefileText::Impl::RenderTextDrawItem(
|
|||
// If no time has been selected, use the current time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
selectedTime_;
|
||||
|
||||
const bool thresholdMet =
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <scwx/qt/gl/draw/placefile_triangles.hpp>
|
||||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ void PlacefileTriangles::Render(
|
|||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
scwx::util::time::now() :
|
||||
p->selectedTime_;
|
||||
glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||
#include <scwx/qt/manager/resource_manager.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/manager/task_manager.hpp>
|
||||
#include <scwx/qt/manager/thread_manager.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/types/qt_types.hpp>
|
||||
|
|
@ -53,10 +54,19 @@ int main(int argc, char* argv[])
|
|||
args.push_back(argv[i]);
|
||||
}
|
||||
|
||||
if (!scwx::util::GetEnvironment("SCWX_TEST").empty())
|
||||
{
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
}
|
||||
|
||||
// Initialize logger
|
||||
auto& logManager = scwx::qt::manager::LogManager::Instance();
|
||||
logManager.Initialize();
|
||||
|
||||
QCoreApplication::setApplicationName("Supercell Wx");
|
||||
|
||||
logManager.InitializeLogFile();
|
||||
|
||||
logger_->info("Supercell Wx v{}.{} ({})",
|
||||
scwx::qt::main::kVersionString_,
|
||||
scwx::qt::main::kBuildNumber_,
|
||||
|
|
@ -66,7 +76,6 @@ int main(int argc, char* argv[])
|
|||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
QCoreApplication::setApplicationName("Supercell Wx");
|
||||
scwx::network::cpr::SetUserAgent(
|
||||
fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_));
|
||||
|
||||
|
|
@ -77,11 +86,6 @@ int main(int argc, char* argv[])
|
|||
QCoreApplication::installTranslator(&translator);
|
||||
}
|
||||
|
||||
if (!scwx::util::GetEnvironment("SCWX_TEST").empty())
|
||||
{
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
}
|
||||
|
||||
// Test to see if scwx was run with high privilege
|
||||
scwx::qt::main::PrivilegeChecker privilegeChecker;
|
||||
if (privilegeChecker.pre_settings_check())
|
||||
|
|
@ -116,9 +120,9 @@ int main(int argc, char* argv[])
|
|||
Aws::InitAPI(awsSdkOptions);
|
||||
|
||||
// Initialize application
|
||||
logManager.InitializeLogFile();
|
||||
scwx::qt::config::RadarSite::Initialize();
|
||||
scwx::qt::config::CountyDatabase::Initialize();
|
||||
scwx::qt::manager::TaskManager::Initialize();
|
||||
scwx::qt::manager::SettingsManager::Instance().Initialize();
|
||||
scwx::qt::manager::ResourceManager::Initialize();
|
||||
|
||||
|
|
@ -179,6 +183,7 @@ int main(int argc, char* argv[])
|
|||
// Shutdown application
|
||||
scwx::qt::manager::ResourceManager::Shutdown();
|
||||
scwx::qt::manager::SettingsManager::Instance().Shutdown();
|
||||
scwx::qt::manager::TaskManager::Shutdown();
|
||||
|
||||
// Shutdown AWS SDK
|
||||
Aws::ShutdownAPI(awsSdkOptions);
|
||||
|
|
|
|||
|
|
@ -1300,8 +1300,8 @@ void MainWindowImpl::ConnectOtherSignals()
|
|||
this,
|
||||
[this]()
|
||||
{
|
||||
timeLabel_->setText(QString::fromStdString(
|
||||
util::TimeString(std::chrono::system_clock::now())));
|
||||
timeLabel_->setText(
|
||||
QString::fromStdString(util::TimeString(util::time::now())));
|
||||
timeLabel_->setVisible(true);
|
||||
});
|
||||
clockTimer_.start(1000);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
#include <scwx/qt/manager/media_manager.hpp>
|
||||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/qt/manager/text_event_manager.hpp>
|
||||
#include <scwx/qt/config/radar_site.hpp>
|
||||
#include <scwx/qt/settings/audio_settings.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/qt/config/radar_site.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
|
|
@ -172,7 +173,7 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
|
|||
|
||||
// If the event has ended or is inactive, or if the alert is not enabled,
|
||||
// skip it
|
||||
if (eventEnd < std::chrono::system_clock::now() || !alertActive ||
|
||||
if (eventEnd < scwx::util::time::now() || !alertActive ||
|
||||
!audioSettings.alert_enabled(phenomenon).GetValue())
|
||||
{
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/map.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
#include <scwx/wsr88d/nexrad_file_factory.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
|
@ -821,7 +822,7 @@ void RadarProductManagerImpl::RefreshDataSync(
|
|||
auto latestTime = providerManager->provider_->FindLatestTime();
|
||||
auto updatePeriod = providerManager->provider_->update_period();
|
||||
auto lastModified = providerManager->provider_->last_modified();
|
||||
auto sinceLastModified = std::chrono::system_clock::now() - lastModified;
|
||||
auto sinceLastModified = scwx::util::time::now() - lastModified;
|
||||
|
||||
// For the default interval, assume products are updated at a
|
||||
// constant rate. Expect the next product at a time based on the
|
||||
|
|
@ -939,7 +940,7 @@ RadarProductManager::GetActiveVolumeTimes(
|
|||
[&](const auto& date)
|
||||
{
|
||||
// Don't query for a time point in the future
|
||||
if (date > std::chrono::system_clock::now())
|
||||
if (date > scwx::util::time::now())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -1259,7 +1260,7 @@ void RadarProductManagerImpl::PopulateProductTimes(
|
|||
[&](const auto& date)
|
||||
{
|
||||
// Don't query for a time point in the future
|
||||
if (date > std::chrono::system_clock::now())
|
||||
if (date > scwx::util::time::now())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -1556,7 +1557,7 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
|
|||
bool needArchive = true;
|
||||
static const auto maxChunkDelay = std::chrono::minutes(10);
|
||||
const std::chrono::system_clock::time_point firstValidChunkTime =
|
||||
(isEpox ? std::chrono::system_clock::now() : time) - maxChunkDelay;
|
||||
(isEpox ? scwx::util::time::now() : time) - maxChunkDelay;
|
||||
|
||||
// See if we have this one in the chunk provider.
|
||||
auto chunkFile = std::dynamic_pointer_cast<wsr88d::Ar2vFile>(
|
||||
|
|
|
|||
30
scwx-qt/source/scwx/qt/manager/task_manager.cpp
Normal file
30
scwx-qt/source/scwx/qt/manager/task_manager.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#include <scwx/qt/manager/task_manager.hpp>
|
||||
#include <scwx/network/ntp_client.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
namespace scwx::qt::manager::TaskManager
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::manager::task_manager";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static std::shared_ptr<network::NtpClient> ntpClient_ {};
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
logger_->debug("Initialize");
|
||||
|
||||
ntpClient_ = network::NtpClient::Instance();
|
||||
|
||||
ntpClient_->Start();
|
||||
ntpClient_->WaitForInitialOffset();
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
logger_->debug("Shutdown");
|
||||
|
||||
ntpClient_->Stop();
|
||||
}
|
||||
|
||||
} // namespace scwx::qt::manager::TaskManager
|
||||
9
scwx-qt/source/scwx/qt/manager/task_manager.hpp
Normal file
9
scwx-qt/source/scwx/qt/manager/task_manager.hpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
namespace scwx::qt::manager::TaskManager
|
||||
{
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
} // namespace scwx::qt::manager::TaskManager
|
||||
|
|
@ -678,7 +678,7 @@ void TextEventManager::Impl::Refresh()
|
|||
// If the time jumps, we should attempt to load from no later than the
|
||||
// previous load time
|
||||
auto loadTime =
|
||||
std::chrono::floor<std::chrono::hours>(std::chrono::system_clock::now());
|
||||
std::chrono::floor<std::chrono::hours>(scwx::util::time::now());
|
||||
auto startTime = loadTime - loadHistoryDuration_;
|
||||
|
||||
if (prevLoadTime_ != std::chrono::sys_time<std::chrono::hours> {})
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ void TimelineManager::AnimationStepBegin()
|
|||
p->pinnedTime_ == std::chrono::system_clock::time_point {})
|
||||
{
|
||||
// If the selected view type is live, select the current products
|
||||
p->SelectTimeAsync(std::chrono::system_clock::now() - p->loopTime_);
|
||||
p->SelectTimeAsync(scwx::util::time::now() - p->loopTime_);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -385,8 +385,8 @@ TimelineManager::Impl::GetLoopStartAndEndTimes()
|
|||
if (viewType_ == types::MapTime::Live ||
|
||||
pinnedTime_ == std::chrono::system_clock::time_point {})
|
||||
{
|
||||
endTime = std::chrono::floor<std::chrono::minutes>(
|
||||
std::chrono::system_clock::now());
|
||||
endTime =
|
||||
std::chrono::floor<std::chrono::minutes>(scwx::util::time::now());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -656,8 +656,8 @@ void TimelineManager::Impl::Step(Direction direction)
|
|||
{
|
||||
if (direction == Direction::Back)
|
||||
{
|
||||
newTime = std::chrono::floor<std::chrono::minutes>(
|
||||
std::chrono::system_clock::now());
|
||||
newTime =
|
||||
std::chrono::floor<std::chrono::minutes>(scwx::util::time::now());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -688,7 +688,7 @@ void TimelineManager::Impl::Step(Direction direction)
|
|||
newTime += 1min;
|
||||
|
||||
// If the new time is more than 2 minutes in the future, stop stepping
|
||||
if (newTime > std::chrono::system_clock::now() + 2min)
|
||||
if (newTime > scwx::util::time::now() + 2min)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <scwx/qt/util/color.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
|
@ -581,8 +582,7 @@ void AlertLayer::Impl::ScheduleRefresh()
|
|||
|
||||
// Expires at the top of the next minute
|
||||
std::chrono::system_clock::time_point now =
|
||||
std::chrono::floor<std::chrono::minutes>(
|
||||
std::chrono::system_clock::now());
|
||||
std::chrono::floor<std::chrono::minutes>(scwx::util::time::now());
|
||||
refreshTimer_.expires_at(now + 1min);
|
||||
|
||||
refreshTimer_.async_wait(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <scwx/qt/types/qt_types.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
|
@ -67,7 +68,7 @@ bool AlertProxyModel::filterAcceptsRow(int sourceRow,
|
|||
.value<std::chrono::system_clock::time_point>();
|
||||
|
||||
// Compare end time to current
|
||||
if (endTime < std::chrono::system_clock::now())
|
||||
if (endTime < scwx::util::time::now())
|
||||
{
|
||||
acceptAlertActiveFilter = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/util/time.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
|
|
@ -81,8 +82,7 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) :
|
|||
p->timeZone_ = date::get_tzdb().locate_zone("UTC");
|
||||
#endif
|
||||
const std::chrono::sys_seconds currentTimePoint =
|
||||
std::chrono::floor<std::chrono::minutes>(
|
||||
std::chrono::system_clock::now());
|
||||
std::chrono::floor<std::chrono::minutes>(scwx::util::time::now());
|
||||
p->SetTimePoint(currentTimePoint);
|
||||
|
||||
// Update maximum date on a timer
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ void OverlayProductView::Impl::LoadProduct(
|
|||
header.date_of_message(), header.time_of_message() * 1000);
|
||||
|
||||
// If the record is from the last 30 minutes
|
||||
if (productTime + 30min >= std::chrono::system_clock::now() ||
|
||||
if (productTime + 30min >= scwx::util::time::now() ||
|
||||
(selectedTime_ != std::chrono::system_clock::time_point {} &&
|
||||
productTime + 30min >= selectedTime_))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ Checks:
|
|||
- 'performance-*'
|
||||
- '-bugprone-easily-swappable-parameters'
|
||||
- '-cppcoreguidelines-avoid-magic-numbers'
|
||||
- '-cppcoreguidelines-avoid-do-while'
|
||||
- '-cppcoreguidelines-avoid-non-const-global-variables'
|
||||
- '-cppcoreguidelines-pro-type-reinterpret-cast'
|
||||
- '-cppcoreguidelines-pro-type-union-access'
|
||||
- '-misc-include-cleaner'
|
||||
- '-misc-non-private-member-variables-in-classes'
|
||||
- '-misc-use-anonymous-namespace'
|
||||
- '-modernize-return-braced-init-list'
|
||||
- '-modernize-use-trailing-return-type'
|
||||
FormatStyle: 'file'
|
||||
|
|
|
|||
32
test/source/scwx/network/ntp_client.test.cpp
Normal file
32
test/source/scwx/network/ntp_client.test.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#include <scwx/network/ntp_client.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace scwx::network
|
||||
{
|
||||
|
||||
TEST(NtpClient, Poll)
|
||||
{
|
||||
NtpClient client {};
|
||||
|
||||
const std::string firstServer = client.RotateServer();
|
||||
std::string currentServer = firstServer;
|
||||
std::string lastServer = firstServer;
|
||||
bool error = false;
|
||||
|
||||
do
|
||||
{
|
||||
client.RunOnce();
|
||||
error = client.error();
|
||||
|
||||
EXPECT_EQ(error, false);
|
||||
|
||||
// Loop until the current server repeats the first server, or fails to
|
||||
// rotate
|
||||
lastServer = currentServer;
|
||||
currentServer = client.RotateServer();
|
||||
} while (currentServer != firstServer && currentServer != lastServer &&
|
||||
!error);
|
||||
}
|
||||
|
||||
} // namespace scwx::network
|
||||
|
|
@ -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
|
||||
|
|
|
|||
48
wxdata/include/scwx/network/ntp_client.hpp
Normal file
48
wxdata/include/scwx/network/ntp_client.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#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;
|
||||
|
||||
bool error();
|
||||
|
||||
[[nodiscard]] std::chrono::system_clock::duration time_offset() const;
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
void Open(std::string_view host, std::string_view service);
|
||||
void OpenCurrentServer();
|
||||
void Poll();
|
||||
std::string RotateServer();
|
||||
void RunOnce();
|
||||
|
||||
void WaitForInitialOffset();
|
||||
|
||||
static std::shared_ptr<NtpClient> Instance();
|
||||
|
||||
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 <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
|
||||
{
|
||||
struct LiVnMode
|
||||
{
|
||||
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.
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
std::uint8_t li_vn_mode;
|
||||
LiVnMode 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; // 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
|
||||
|
|
@ -10,9 +10,7 @@
|
|||
# include <date/tz.h>
|
||||
#endif
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace util
|
||||
namespace scwx::util::time
|
||||
{
|
||||
|
||||
#if (__cpp_lib_chrono >= 201907L)
|
||||
|
|
@ -34,6 +32,9 @@ typedef scwx::util::
|
|||
ClockFormat GetClockFormat(const std::string& name);
|
||||
const std::string& GetClockFormatName(ClockFormat clockFormat);
|
||||
|
||||
template<typename Clock = std::chrono::system_clock>
|
||||
std::chrono::time_point<Clock> now();
|
||||
|
||||
std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate,
|
||||
uint32_t milliseconds);
|
||||
|
||||
|
|
@ -46,5 +47,17 @@ template<typename T>
|
|||
std::optional<std::chrono::sys_time<T>>
|
||||
TryParseDateTime(const std::string& dateTimeFormat, const std::string& str);
|
||||
|
||||
} // namespace util
|
||||
} // namespace scwx
|
||||
} // namespace scwx::util::time
|
||||
|
||||
namespace scwx::util
|
||||
{
|
||||
// Add types and functions to scwx::util for compatibility
|
||||
using time::ClockFormat;
|
||||
using time::ClockFormatIterator;
|
||||
using time::GetClockFormat;
|
||||
using time::GetClockFormatName;
|
||||
using time::time_zone;
|
||||
using time::TimePoint;
|
||||
using time::TimeString;
|
||||
using time::TryParseDateTime;
|
||||
} // namespace scwx::util
|
||||
|
|
|
|||
571
wxdata/source/scwx/network/ntp_client.cpp
Normal file
571
wxdata/source/scwx/network/ntp_client.cpp
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
#include <scwx/network/ntp_client.hpp>
|
||||
#include <scwx/types/ntp_types.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/asio/use_future.hpp>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
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};
|
||||
|
||||
// Reasonable min/max values for polling intervals. We don't want to poll too
|
||||
// quickly and upset the server, but we don't want to poll too slowly in the
|
||||
// event of a time jump.
|
||||
static constexpr std::uint32_t kMinPollInterval_ = 6u; // 2^6 = 64 seconds
|
||||
static constexpr std::uint32_t kMaxPollInterval_ = 9u; // 2^9 = 512 seconds
|
||||
|
||||
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>
|
||||
[[nodiscard]] 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(Impl&&) = delete;
|
||||
Impl& operator=(Impl&&) = delete;
|
||||
|
||||
void Open(std::string_view host, std::string_view service);
|
||||
void OpenCurrentServer();
|
||||
void Poll();
|
||||
void ReceivePacket(std::size_t length);
|
||||
std::string RotateServer();
|
||||
void Run();
|
||||
void RunOnce();
|
||||
|
||||
void FinishInitialization();
|
||||
|
||||
boost::asio::thread_pool threadPool_ {2u};
|
||||
|
||||
boost::asio::steady_timer pollTimer_ {threadPool_};
|
||||
std::uint32_t pollInterval_ {kMinPollInterval_};
|
||||
|
||||
bool enabled_ {true};
|
||||
bool error_ {false};
|
||||
bool disableServer_ {false};
|
||||
bool rotateServer_ {false};
|
||||
|
||||
std::mutex initializationMutex_ {};
|
||||
std::condition_variable initializationCondition_ {};
|
||||
std::atomic<bool> initialized_ {false};
|
||||
|
||||
types::ntp::NtpPacket transmitPacket_ {};
|
||||
|
||||
boost::asio::ip::udp::socket socket_ {threadPool_};
|
||||
std::optional<boost::asio::ip::udp::endpoint> serverEndpoint_ {};
|
||||
std::array<std::uint8_t, kReceiveBufferSize_> receiveBuffer_ {};
|
||||
|
||||
std::chrono::system_clock::duration timeOffset_ {};
|
||||
|
||||
const std::vector<std::string> serverList_ {"time.nist.gov",
|
||||
"time.cloudflare.com",
|
||||
"pool.ntp.org",
|
||||
"time.aws.com",
|
||||
"time.windows.com",
|
||||
"time.apple.com"};
|
||||
std::vector<std::string> disabledServers_ {};
|
||||
|
||||
std::vector<std::string>::const_iterator currentServer_ =
|
||||
serverList_.begin();
|
||||
};
|
||||
|
||||
NtpClient::NtpClient() : p(std::make_unique<Impl>()) {}
|
||||
NtpClient::~NtpClient() = default;
|
||||
|
||||
NtpClient::NtpClient(NtpClient&&) noexcept = default;
|
||||
NtpClient& NtpClient::operator=(NtpClient&&) noexcept = default;
|
||||
|
||||
NtpClient::Impl::Impl()
|
||||
{
|
||||
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 dates prior to 1970 result in a Unix timestamp after 2036. Additional
|
||||
// handling for the year 2106 and 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)
|
||||
|
||||
// If the NTP client is enabled, wait until the first refresh to consider
|
||||
// "initialized". Otherwise, mark as initialized immediately to prevent a
|
||||
// deadlock.
|
||||
if (!enabled_)
|
||||
{
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
NtpClient::Impl::~Impl()
|
||||
{
|
||||
threadPool_.join();
|
||||
}
|
||||
|
||||
bool NtpClient::error()
|
||||
{
|
||||
const bool returnValue = p->error_;
|
||||
p->error_ = false;
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
std::chrono::system_clock::duration NtpClient::time_offset() const
|
||||
{
|
||||
return p->timeOffset_;
|
||||
}
|
||||
|
||||
void NtpClient::Start()
|
||||
{
|
||||
if (p->enabled_)
|
||||
{
|
||||
boost::asio::post(p->threadPool_, [this]() { p->Run(); });
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::Stop()
|
||||
{
|
||||
p->enabled_ = false;
|
||||
p->socket_.cancel();
|
||||
p->pollTimer_.cancel();
|
||||
p->threadPool_.join();
|
||||
}
|
||||
|
||||
void NtpClient::Open(std::string_view host, std::string_view service)
|
||||
{
|
||||
p->Open(host, service);
|
||||
}
|
||||
|
||||
void NtpClient::OpenCurrentServer()
|
||||
{
|
||||
p->OpenCurrentServer();
|
||||
}
|
||||
|
||||
void NtpClient::Poll()
|
||||
{
|
||||
p->Poll();
|
||||
}
|
||||
|
||||
std::string NtpClient::RotateServer()
|
||||
{
|
||||
return p->RotateServer();
|
||||
}
|
||||
|
||||
void NtpClient::RunOnce()
|
||||
{
|
||||
p->RunOnce();
|
||||
}
|
||||
|
||||
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());
|
||||
rotateServer_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::Impl::OpenCurrentServer()
|
||||
{
|
||||
Open(*currentServer_, "123");
|
||||
}
|
||||
|
||||
void NtpClient::Impl::Poll()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static constexpr auto kTimeout_ = 5s;
|
||||
|
||||
if (!serverEndpoint_.has_value())
|
||||
{
|
||||
logger_->error("Server endpoint not set");
|
||||
error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto originTimestamp =
|
||||
NtpTimestamp::FromTimePoint(std::chrono::system_clock::now());
|
||||
transmitPacket_.txTm_s = ntohl(originTimestamp.seconds_);
|
||||
transmitPacket_.txTm_f = ntohl(originTimestamp.fraction_);
|
||||
|
||||
const 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();
|
||||
error_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
logger_->error("Error polling: {}", ex.what());
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::Impl::ReceivePacket(std::size_t length)
|
||||
{
|
||||
if (length >= sizeof(types::ntp::NtpPacket))
|
||||
{
|
||||
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")
|
||||
{
|
||||
// The client MUST demobilize any associations to that server and
|
||||
// stop sending packets to that server
|
||||
disableServer_ = true;
|
||||
}
|
||||
else if (kod == "RATE")
|
||||
{
|
||||
// The client MUST immediately reduce its polling interval to that
|
||||
// server and continue to reduce it each time it receives a RATE
|
||||
// kiss code
|
||||
if (pollInterval_ < kMaxPollInterval_)
|
||||
{
|
||||
++pollInterval_;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The server wants us to reduce the polling interval lower than
|
||||
// what we deem useful. Move to the next server.
|
||||
rotateServer_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Consider a KoD packet an error
|
||||
error_ = true;
|
||||
}
|
||||
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
|
||||
{
|
||||
logger_->warn("Received too few bytes: {}", length);
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string NtpClient::Impl::RotateServer()
|
||||
{
|
||||
socket_.close();
|
||||
|
||||
bool newServerFound = false;
|
||||
|
||||
// Save the current server
|
||||
const auto oldServer = currentServer_;
|
||||
|
||||
while (!newServerFound)
|
||||
{
|
||||
// Increment the current server
|
||||
++currentServer_;
|
||||
|
||||
// If we are at the end of the list, start over at the beginning
|
||||
if (currentServer_ == serverList_.end())
|
||||
{
|
||||
currentServer_ = serverList_.begin();
|
||||
}
|
||||
|
||||
// If we have reached the end of the list, give up
|
||||
if (currentServer_ == oldServer)
|
||||
{
|
||||
enabled_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the current server is disabled, continue searching
|
||||
while (std::find(disabledServers_.cbegin(),
|
||||
disabledServers_.cend(),
|
||||
*currentServer_) != disabledServers_.cend())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// A new server has been found
|
||||
newServerFound = true;
|
||||
}
|
||||
|
||||
pollInterval_ = kMinPollInterval_;
|
||||
rotateServer_ = false;
|
||||
|
||||
return *currentServer_;
|
||||
}
|
||||
|
||||
void NtpClient::Impl::Run()
|
||||
{
|
||||
RunOnce();
|
||||
|
||||
if (enabled_)
|
||||
{
|
||||
const std::chrono::seconds pollIntervalSeconds {1u << pollInterval_};
|
||||
pollTimer_.expires_after(pollIntervalSeconds);
|
||||
pollTimer_.async_wait(
|
||||
[this](const boost::system::error_code& e)
|
||||
{
|
||||
if (e == boost::asio::error::operation_aborted)
|
||||
{
|
||||
logger_->debug("Poll timer cancelled");
|
||||
}
|
||||
else if (e != boost::system::errc::success)
|
||||
{
|
||||
logger_->warn("Poll timer error: {}", e.message());
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Run();
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
logger_->error(ex.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::Impl::RunOnce()
|
||||
{
|
||||
if (disableServer_)
|
||||
{
|
||||
// Disable the current server
|
||||
disabledServers_.push_back(*currentServer_);
|
||||
|
||||
// Disable the NTP client if all servers are disabled
|
||||
enabled_ = disabledServers_.size() == serverList_.size();
|
||||
|
||||
if (!enabled_)
|
||||
{
|
||||
error_ = true;
|
||||
}
|
||||
|
||||
disableServer_ = false;
|
||||
rotateServer_ = enabled_;
|
||||
}
|
||||
|
||||
if (!enabled_ && socket_.is_open())
|
||||
{
|
||||
// Sockets should be closed if the client is disabled
|
||||
socket_.close();
|
||||
}
|
||||
|
||||
if (rotateServer_)
|
||||
{
|
||||
// Rotate the server if requested
|
||||
RotateServer();
|
||||
}
|
||||
|
||||
if (enabled_ && !socket_.is_open())
|
||||
{
|
||||
// Open the current server if it is not open
|
||||
OpenCurrentServer();
|
||||
}
|
||||
|
||||
if (socket_.is_open())
|
||||
{
|
||||
// Send an NTP message to determine the current time offset
|
||||
Poll();
|
||||
}
|
||||
else if (enabled_)
|
||||
{
|
||||
// Did not poll this frame
|
||||
error_ = true;
|
||||
}
|
||||
|
||||
FinishInitialization();
|
||||
}
|
||||
|
||||
void NtpClient::Impl::FinishInitialization()
|
||||
{
|
||||
if (!initialized_)
|
||||
{
|
||||
// Set initialized to true
|
||||
std::unique_lock lock(initializationMutex_);
|
||||
initialized_ = true;
|
||||
lock.unlock();
|
||||
|
||||
// Notify any threads waiting for initialization
|
||||
initializationCondition_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void NtpClient::WaitForInitialOffset()
|
||||
{
|
||||
std::unique_lock lock(p->initializationMutex_);
|
||||
|
||||
// While not yet initialized
|
||||
while (!p->initialized_)
|
||||
{
|
||||
// Wait for initialization
|
||||
p->initializationCondition_.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<NtpClient> NtpClient::Instance()
|
||||
{
|
||||
static std::weak_ptr<NtpClient> ntpClientReference_ {};
|
||||
static std::mutex instanceMutex_ {};
|
||||
|
||||
const std::unique_lock lock(instanceMutex_);
|
||||
|
||||
std::shared_ptr<NtpClient> ntpClient = ntpClientReference_.lock();
|
||||
|
||||
if (ntpClient == nullptr)
|
||||
{
|
||||
ntpClient = std::make_shared<NtpClient>();
|
||||
ntpClientReference_ = ntpClient;
|
||||
}
|
||||
|
||||
return ntpClient;
|
||||
}
|
||||
|
||||
} // namespace scwx::network
|
||||
|
|
@ -302,7 +302,7 @@ AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix)
|
|||
if (scanTimeIt != scanTimes_.cend())
|
||||
{
|
||||
// If the time is greater than 2 hours ago, it may be a new scan
|
||||
auto replaceBy = system_clock::now() - hours {2};
|
||||
auto replaceBy = util::time::now() - hours {2};
|
||||
if (scanTimeIt->second > replaceBy)
|
||||
{
|
||||
return scanTimeIt->second;
|
||||
|
|
@ -333,8 +333,7 @@ AwsLevel2ChunksDataProvider::Impl::ListObjects()
|
|||
size_t newObjects = 0;
|
||||
const size_t totalObjects = 0;
|
||||
|
||||
const std::chrono::system_clock::time_point now =
|
||||
std::chrono::system_clock::now();
|
||||
const std::chrono::system_clock::time_point now = util::time::now();
|
||||
|
||||
if (currentScan_.valid_ && !currentScan_.hasAllFiles_ &&
|
||||
lastTimeListed_ + std::chrono::minutes(2) > now)
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ std::pair<size_t, size_t> AwsNexradDataProvider::Refresh()
|
|||
|
||||
logger_->debug("Refresh()");
|
||||
|
||||
auto today = floor<days>(system_clock::now());
|
||||
auto today = floor<days>(util::time::now());
|
||||
auto yesterday = today - days {1};
|
||||
|
||||
std::unique_lock lock(p->refreshMutex_);
|
||||
|
|
@ -388,7 +388,7 @@ void AwsNexradDataProvider::Impl::PruneObjects()
|
|||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
auto today = floor<days>(system_clock::now());
|
||||
auto today = floor<days>(util::time::now());
|
||||
auto yesterday = today - days {1};
|
||||
|
||||
std::unique_lock lock(objectsMutex_);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ WarningsProvider::LoadUpdatedFiles(
|
|||
std::vector<std::shared_ptr<awips::TextProductFile>> updatedFiles;
|
||||
|
||||
const std::chrono::sys_time<std::chrono::hours> now =
|
||||
std::chrono::floor<std::chrono::hours>(std::chrono::system_clock::now());
|
||||
std::chrono::floor<std::chrono::hours>(util::time::now());
|
||||
std::chrono::sys_time<std::chrono::hours> currentHour =
|
||||
(startTime != std::chrono::sys_time<std::chrono::hours> {}) ?
|
||||
startTime :
|
||||
|
|
|
|||
42
wxdata/source/scwx/types/ntp_types.cpp
Normal file
42
wxdata/source/scwx/types/ntp_types.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#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());
|
||||
|
||||
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
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
# define __cpp_lib_format 202110L
|
||||
#endif
|
||||
|
||||
#include <scwx/network/ntp_client.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
#include <scwx/util/enum.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
# include <date/date.h>
|
||||
#endif
|
||||
|
||||
namespace scwx::util
|
||||
namespace scwx::util::time
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::util::time";
|
||||
|
|
@ -32,6 +33,8 @@ static const std::unordered_map<ClockFormat, std::string> clockFormatName_ {
|
|||
{ClockFormat::_24Hour, "24-hour"},
|
||||
{ClockFormat::Unknown, "?"}};
|
||||
|
||||
static std::shared_ptr<network::NtpClient> ntpClient_ {nullptr};
|
||||
|
||||
SCWX_GET_ENUM(ClockFormat, GetClockFormat, clockFormatName_)
|
||||
|
||||
const std::string& GetClockFormatName(ClockFormat clockFormat)
|
||||
|
|
@ -39,6 +42,26 @@ const std::string& GetClockFormatName(ClockFormat clockFormat)
|
|||
return clockFormatName_.at(clockFormat);
|
||||
}
|
||||
|
||||
template<typename Clock>
|
||||
std::chrono::time_point<Clock> now()
|
||||
{
|
||||
if (ntpClient_ == nullptr)
|
||||
{
|
||||
ntpClient_ = network::NtpClient::Instance();
|
||||
}
|
||||
|
||||
if (ntpClient_ != nullptr)
|
||||
{
|
||||
return Clock::now() + ntpClient_->time_offset();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
template std::chrono::time_point<std::chrono::system_clock> now();
|
||||
|
||||
std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate,
|
||||
uint32_t milliseconds)
|
||||
{
|
||||
|
|
@ -153,4 +176,4 @@ template std::optional<std::chrono::sys_time<std::chrono::seconds>>
|
|||
TryParseDateTime<std::chrono::seconds>(const std::string& dateTimeFormat,
|
||||
const std::string& str);
|
||||
|
||||
} // namespace scwx::util
|
||||
} // namespace scwx::util::time
|
||||
|
|
|
|||
|
|
@ -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