mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 03:20: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-*' |   - 'modernize-*' | ||||||
|   - 'performance-*' |   - 'performance-*' | ||||||
|   - '-bugprone-easily-swappable-parameters' |   - '-bugprone-easily-swappable-parameters' | ||||||
|   - '-cppcoreguidelines-pro-type-reinterpret-cast' |  | ||||||
|   - '-cppcoreguidelines-avoid-do-while' |   - '-cppcoreguidelines-avoid-do-while' | ||||||
|  |   - '-cppcoreguidelines-avoid-non-const-global-variables' | ||||||
|  |   - '-cppcoreguidelines-pro-type-reinterpret-cast' | ||||||
|  |   - '-cppcoreguidelines-pro-type-union-access' | ||||||
|   - '-misc-include-cleaner' |   - '-misc-include-cleaner' | ||||||
|   - '-misc-non-private-member-variables-in-classes' |   - '-misc-non-private-member-variables-in-classes' | ||||||
|   - '-misc-use-anonymous-namespace' |   - '-misc-use-anonymous-namespace' | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -393,7 +393,7 @@ jobs: | ||||||
|       env: |       env: | ||||||
|         MAPBOX_API_KEY:   ${{ secrets.MAPBOX_API_KEY }} |         MAPBOX_API_KEY:   ${{ secrets.MAPBOX_API_KEY }} | ||||||
|         MAPTILER_API_KEY: ${{ secrets.MAPTILER_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 |     - name: Upload Test Logs | ||||||
|       if: ${{ !cancelled() }} |       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/radar_product_manager_notifier.hpp | ||||||
|                 source/scwx/qt/manager/resource_manager.hpp |                 source/scwx/qt/manager/resource_manager.hpp | ||||||
|                 source/scwx/qt/manager/settings_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/text_event_manager.hpp | ||||||
|                 source/scwx/qt/manager/thread_manager.hpp |                 source/scwx/qt/manager/thread_manager.hpp | ||||||
|                 source/scwx/qt/manager/timeline_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/radar_product_manager_notifier.cpp | ||||||
|                 source/scwx/qt/manager/resource_manager.cpp |                 source/scwx/qt/manager/resource_manager.cpp | ||||||
|                 source/scwx/qt/manager/settings_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/text_event_manager.cpp | ||||||
|                 source/scwx/qt/manager/thread_manager.cpp |                 source/scwx/qt/manager/thread_manager.cpp | ||||||
|                 source/scwx/qt/manager/timeline_manager.cpp |                 source/scwx/qt/manager/timeline_manager.cpp | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| #include <scwx/qt/util/texture_atlas.hpp> | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
| 
 | 
 | ||||||
|  | @ -313,7 +314,7 @@ void GeoIcons::Render(const QMapLibre::CustomLayerRenderParameters& params, | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  | @ -930,7 +931,7 @@ bool GeoIcons::RunMousePicking( | ||||||
|    // If no time has been selected, use the current time
 |    // If no time has been selected, use the current time
 | ||||||
|    std::chrono::system_clock::time_point selectedTime = |    std::chrono::system_clock::time_point selectedTime = | ||||||
|       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|          std::chrono::system_clock::now() : |          scwx::util::time::now() : | ||||||
|          p->selectedTime_; |          p->selectedTime_; | ||||||
| 
 | 
 | ||||||
|    // For each pickable icon
 |    // For each pickable icon
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
| 
 | 
 | ||||||
|  | @ -284,7 +285,7 @@ void GeoLines::Render(const QMapLibre::CustomLayerRenderParameters& params) | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  | @ -723,7 +724,7 @@ bool GeoLines::RunMousePicking( | ||||||
|    // If no time has been selected, use the current time
 |    // If no time has been selected, use the current time
 | ||||||
|    std::chrono::system_clock::time_point selectedTime = |    std::chrono::system_clock::time_point selectedTime = | ||||||
|       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|          std::chrono::system_clock::now() : |          scwx::util::time::now() : | ||||||
|          p->selectedTime_; |          p->selectedTime_; | ||||||
| 
 | 
 | ||||||
|    // For each pickable line
 |    // For each pickable line
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| #include <scwx/qt/util/texture_atlas.hpp> | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
| 
 | 
 | ||||||
|  | @ -295,7 +296,7 @@ void PlacefileIcons::Render( | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  | @ -720,7 +721,7 @@ bool PlacefileIcons::RunMousePicking( | ||||||
|    // If no time has been selected, use the current time
 |    // If no time has been selected, use the current time
 | ||||||
|    std::chrono::system_clock::time_point selectedTime = |    std::chrono::system_clock::time_point selectedTime = | ||||||
|       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|          std::chrono::system_clock::now() : |          scwx::util::time::now() : | ||||||
|          p->selectedTime_; |          p->selectedTime_; | ||||||
| 
 | 
 | ||||||
|    // For each pickable icon
 |    // For each pickable icon
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/qt/util/texture_atlas.hpp> | #include <scwx/qt/util/texture_atlas.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <QDir> | #include <QDir> | ||||||
| #include <QUrl> | #include <QUrl> | ||||||
|  | @ -264,7 +265,7 @@ void PlacefileImages::Render( | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
| 
 | 
 | ||||||
|  | @ -248,7 +249,7 @@ void PlacefileLines::Render( | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  | @ -526,7 +527,7 @@ bool PlacefileLines::RunMousePicking( | ||||||
|    // If no time has been selected, use the current time
 |    // If no time has been selected, use the current time
 | ||||||
|    std::chrono::system_clock::time_point selectedTime = |    std::chrono::system_clock::time_point selectedTime = | ||||||
|       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |       (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|          std::chrono::system_clock::now() : |          scwx::util::time::now() : | ||||||
|          p->selectedTime_; |          p->selectedTime_; | ||||||
| 
 | 
 | ||||||
|    // For each pickable line
 |    // For each pickable line
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #include <scwx/qt/gl/draw/placefile_polygons.hpp> | #include <scwx/qt/gl/draw/placefile_polygons.hpp> | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <mutex> | #include <mutex> | ||||||
| 
 | 
 | ||||||
|  | @ -259,7 +260,7 @@ void PlacefilePolygons::Render( | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| #include <imgui.h> | #include <imgui.h> | ||||||
|  | @ -127,7 +128,7 @@ void PlacefileText::Impl::RenderTextDrawItem( | ||||||
|    // If no time has been selected, use the current time
 |    // If no time has been selected, use the current time
 | ||||||
|    std::chrono::system_clock::time_point selectedTime = |    std::chrono::system_clock::time_point selectedTime = | ||||||
|       (selectedTime_ == std::chrono::system_clock::time_point {}) ? |       (selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|          std::chrono::system_clock::now() : |          scwx::util::time::now() : | ||||||
|          selectedTime_; |          selectedTime_; | ||||||
| 
 | 
 | ||||||
|    const bool thresholdMet = |    const bool thresholdMet = | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #include <scwx/qt/gl/draw/placefile_triangles.hpp> | #include <scwx/qt/gl/draw/placefile_triangles.hpp> | ||||||
| #include <scwx/qt/util/maplibre.hpp> | #include <scwx/qt/util/maplibre.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <mutex> | #include <mutex> | ||||||
| 
 | 
 | ||||||
|  | @ -203,7 +204,7 @@ void PlacefileTriangles::Render( | ||||||
|       // Selected time
 |       // Selected time
 | ||||||
|       std::chrono::system_clock::time_point selectedTime = |       std::chrono::system_clock::time_point selectedTime = | ||||||
|          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? |          (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? | ||||||
|             std::chrono::system_clock::now() : |             scwx::util::time::now() : | ||||||
|             p->selectedTime_; |             p->selectedTime_; | ||||||
|       glUniform1i( |       glUniform1i( | ||||||
|          p->uSelectedTimeLocation_, |          p->uSelectedTimeLocation_, | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/manager/resource_manager.hpp> | #include <scwx/qt/manager/resource_manager.hpp> | ||||||
| #include <scwx/qt/manager/settings_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/manager/thread_manager.hpp> | ||||||
| #include <scwx/qt/settings/general_settings.hpp> | #include <scwx/qt/settings/general_settings.hpp> | ||||||
| #include <scwx/qt/types/qt_types.hpp> | #include <scwx/qt/types/qt_types.hpp> | ||||||
|  | @ -53,10 +54,19 @@ int main(int argc, char* argv[]) | ||||||
|       args.push_back(argv[i]); |       args.push_back(argv[i]); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  |    if (!scwx::util::GetEnvironment("SCWX_TEST").empty()) | ||||||
|  |    { | ||||||
|  |       QStandardPaths::setTestModeEnabled(true); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|    // Initialize logger
 |    // Initialize logger
 | ||||||
|    auto& logManager = scwx::qt::manager::LogManager::Instance(); |    auto& logManager = scwx::qt::manager::LogManager::Instance(); | ||||||
|    logManager.Initialize(); |    logManager.Initialize(); | ||||||
| 
 | 
 | ||||||
|  |    QCoreApplication::setApplicationName("Supercell Wx"); | ||||||
|  | 
 | ||||||
|  |    logManager.InitializeLogFile(); | ||||||
|  | 
 | ||||||
|    logger_->info("Supercell Wx v{}.{} ({})", |    logger_->info("Supercell Wx v{}.{} ({})", | ||||||
|                  scwx::qt::main::kVersionString_, |                  scwx::qt::main::kVersionString_, | ||||||
|                  scwx::qt::main::kBuildNumber_, |                  scwx::qt::main::kBuildNumber_, | ||||||
|  | @ -66,7 +76,6 @@ int main(int argc, char* argv[]) | ||||||
| 
 | 
 | ||||||
|    QApplication a(argc, argv); |    QApplication a(argc, argv); | ||||||
| 
 | 
 | ||||||
|    QCoreApplication::setApplicationName("Supercell Wx"); |  | ||||||
|    scwx::network::cpr::SetUserAgent( |    scwx::network::cpr::SetUserAgent( | ||||||
|       fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_)); |       fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_)); | ||||||
| 
 | 
 | ||||||
|  | @ -77,11 +86,6 @@ int main(int argc, char* argv[]) | ||||||
|       QCoreApplication::installTranslator(&translator); |       QCoreApplication::installTranslator(&translator); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (!scwx::util::GetEnvironment("SCWX_TEST").empty()) |  | ||||||
|    { |  | ||||||
|       QStandardPaths::setTestModeEnabled(true); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    // Test to see if scwx was run with high privilege
 |    // Test to see if scwx was run with high privilege
 | ||||||
|    scwx::qt::main::PrivilegeChecker privilegeChecker; |    scwx::qt::main::PrivilegeChecker privilegeChecker; | ||||||
|    if (privilegeChecker.pre_settings_check()) |    if (privilegeChecker.pre_settings_check()) | ||||||
|  | @ -116,9 +120,9 @@ int main(int argc, char* argv[]) | ||||||
|    Aws::InitAPI(awsSdkOptions); |    Aws::InitAPI(awsSdkOptions); | ||||||
| 
 | 
 | ||||||
|    // Initialize application
 |    // Initialize application
 | ||||||
|    logManager.InitializeLogFile(); |  | ||||||
|    scwx::qt::config::RadarSite::Initialize(); |    scwx::qt::config::RadarSite::Initialize(); | ||||||
|    scwx::qt::config::CountyDatabase::Initialize(); |    scwx::qt::config::CountyDatabase::Initialize(); | ||||||
|  |    scwx::qt::manager::TaskManager::Initialize(); | ||||||
|    scwx::qt::manager::SettingsManager::Instance().Initialize(); |    scwx::qt::manager::SettingsManager::Instance().Initialize(); | ||||||
|    scwx::qt::manager::ResourceManager::Initialize(); |    scwx::qt::manager::ResourceManager::Initialize(); | ||||||
| 
 | 
 | ||||||
|  | @ -179,6 +183,7 @@ int main(int argc, char* argv[]) | ||||||
|    // Shutdown application
 |    // Shutdown application
 | ||||||
|    scwx::qt::manager::ResourceManager::Shutdown(); |    scwx::qt::manager::ResourceManager::Shutdown(); | ||||||
|    scwx::qt::manager::SettingsManager::Instance().Shutdown(); |    scwx::qt::manager::SettingsManager::Instance().Shutdown(); | ||||||
|  |    scwx::qt::manager::TaskManager::Shutdown(); | ||||||
| 
 | 
 | ||||||
|    // Shutdown AWS SDK
 |    // Shutdown AWS SDK
 | ||||||
|    Aws::ShutdownAPI(awsSdkOptions); |    Aws::ShutdownAPI(awsSdkOptions); | ||||||
|  |  | ||||||
|  | @ -1300,8 +1300,8 @@ void MainWindowImpl::ConnectOtherSignals() | ||||||
|            this, |            this, | ||||||
|            [this]() |            [this]() | ||||||
|            { |            { | ||||||
|               timeLabel_->setText(QString::fromStdString( |               timeLabel_->setText( | ||||||
|                  util::TimeString(std::chrono::system_clock::now()))); |                  QString::fromStdString(util::TimeString(util::time::now()))); | ||||||
|               timeLabel_->setVisible(true); |               timeLabel_->setVisible(true); | ||||||
|            }); |            }); | ||||||
|    clockTimer_.start(1000); |    clockTimer_.start(1000); | ||||||
|  |  | ||||||
|  | @ -2,12 +2,13 @@ | ||||||
| #include <scwx/qt/manager/media_manager.hpp> | #include <scwx/qt/manager/media_manager.hpp> | ||||||
| #include <scwx/qt/manager/position_manager.hpp> | #include <scwx/qt/manager/position_manager.hpp> | ||||||
| #include <scwx/qt/manager/text_event_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/audio_settings.hpp> | ||||||
|  | #include <scwx/qt/settings/general_settings.hpp> | ||||||
| #include <scwx/qt/types/location_types.hpp> | #include <scwx/qt/types/location_types.hpp> | ||||||
| #include <scwx/qt/util/geographic_lib.hpp> | #include <scwx/qt/util/geographic_lib.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/qt/config/radar_site.hpp> | #include <scwx/util/time.hpp> | ||||||
| #include <scwx/qt/settings/general_settings.hpp> |  | ||||||
| 
 | 
 | ||||||
| #include <boost/asio/post.hpp> | #include <boost/asio/post.hpp> | ||||||
| #include <boost/asio/thread_pool.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,
 |       // If the event has ended or is inactive, or if the alert is not enabled,
 | ||||||
|       // skip it
 |       // skip it
 | ||||||
|       if (eventEnd < std::chrono::system_clock::now() || !alertActive || |       if (eventEnd < scwx::util::time::now() || !alertActive || | ||||||
|           !audioSettings.alert_enabled(phenomenon).GetValue()) |           !audioSettings.alert_enabled(phenomenon).GetValue()) | ||||||
|       { |       { | ||||||
|          continue; |          continue; | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/map.hpp> | #include <scwx/util/map.hpp> | ||||||
| #include <scwx/util/threads.hpp> | #include <scwx/util/threads.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| #include <scwx/wsr88d/nexrad_file_factory.hpp> | #include <scwx/wsr88d/nexrad_file_factory.hpp> | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
|  | @ -821,7 +822,7 @@ void RadarProductManagerImpl::RefreshDataSync( | ||||||
|       auto latestTime        = providerManager->provider_->FindLatestTime(); |       auto latestTime        = providerManager->provider_->FindLatestTime(); | ||||||
|       auto updatePeriod      = providerManager->provider_->update_period(); |       auto updatePeriod      = providerManager->provider_->update_period(); | ||||||
|       auto lastModified      = providerManager->provider_->last_modified(); |       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
 |       // For the default interval, assume products are updated at a
 | ||||||
|       // constant rate. Expect the next product at a time based on the
 |       // constant rate. Expect the next product at a time based on the
 | ||||||
|  | @ -939,7 +940,7 @@ RadarProductManager::GetActiveVolumeTimes( | ||||||
|                        [&](const auto& date) |                        [&](const auto& date) | ||||||
|                        { |                        { | ||||||
|                           // Don't query for a time point in the future
 |                           // Don't query for a time point in the future
 | ||||||
|                           if (date > std::chrono::system_clock::now()) |                           if (date > scwx::util::time::now()) | ||||||
|                           { |                           { | ||||||
|                              return; |                              return; | ||||||
|                           } |                           } | ||||||
|  | @ -1259,7 +1260,7 @@ void RadarProductManagerImpl::PopulateProductTimes( | ||||||
|                  [&](const auto& date) |                  [&](const auto& date) | ||||||
|                  { |                  { | ||||||
|                     // Don't query for a time point in the future
 |                     // Don't query for a time point in the future
 | ||||||
|                     if (date > std::chrono::system_clock::now()) |                     if (date > scwx::util::time::now()) | ||||||
|                     { |                     { | ||||||
|                        return; |                        return; | ||||||
|                     } |                     } | ||||||
|  | @ -1556,7 +1557,7 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|    bool              needArchive   = true; |    bool              needArchive   = true; | ||||||
|    static const auto maxChunkDelay = std::chrono::minutes(10); |    static const auto maxChunkDelay = std::chrono::minutes(10); | ||||||
|    const std::chrono::system_clock::time_point firstValidChunkTime = |    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.
 |    // See if we have this one in the chunk provider.
 | ||||||
|    auto chunkFile = std::dynamic_pointer_cast<wsr88d::Ar2vFile>( |    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
 |    // If the time jumps, we should attempt to load from no later than the
 | ||||||
|    // previous load time
 |    // previous load time
 | ||||||
|    auto loadTime = |    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_; |    auto startTime = loadTime - loadHistoryDuration_; | ||||||
| 
 | 
 | ||||||
|    if (prevLoadTime_ != std::chrono::sys_time<std::chrono::hours> {}) |    if (prevLoadTime_ != std::chrono::sys_time<std::chrono::hours> {}) | ||||||
|  |  | ||||||
|  | @ -218,7 +218,7 @@ void TimelineManager::AnimationStepBegin() | ||||||
|        p->pinnedTime_ == std::chrono::system_clock::time_point {}) |        p->pinnedTime_ == std::chrono::system_clock::time_point {}) | ||||||
|    { |    { | ||||||
|       // If the selected view type is live, select the current products
 |       // 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 |    else | ||||||
|    { |    { | ||||||
|  | @ -385,8 +385,8 @@ TimelineManager::Impl::GetLoopStartAndEndTimes() | ||||||
|    if (viewType_ == types::MapTime::Live || |    if (viewType_ == types::MapTime::Live || | ||||||
|        pinnedTime_ == std::chrono::system_clock::time_point {}) |        pinnedTime_ == std::chrono::system_clock::time_point {}) | ||||||
|    { |    { | ||||||
|       endTime = std::chrono::floor<std::chrono::minutes>( |       endTime = | ||||||
|          std::chrono::system_clock::now()); |          std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); | ||||||
|    } |    } | ||||||
|    else |    else | ||||||
|    { |    { | ||||||
|  | @ -656,8 +656,8 @@ void TimelineManager::Impl::Step(Direction direction) | ||||||
|    { |    { | ||||||
|       if (direction == Direction::Back) |       if (direction == Direction::Back) | ||||||
|       { |       { | ||||||
|          newTime = std::chrono::floor<std::chrono::minutes>( |          newTime = | ||||||
|             std::chrono::system_clock::now()); |             std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); | ||||||
|       } |       } | ||||||
|       else |       else | ||||||
|       { |       { | ||||||
|  | @ -688,7 +688,7 @@ void TimelineManager::Impl::Step(Direction direction) | ||||||
|          newTime += 1min; |          newTime += 1min; | ||||||
| 
 | 
 | ||||||
|          // If the new time is more than 2 minutes in the future, stop stepping
 |          // 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; |             break; | ||||||
|          } |          } | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| #include <scwx/qt/util/color.hpp> | #include <scwx/qt/util/color.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <mutex> | #include <mutex> | ||||||
|  | @ -581,8 +582,7 @@ void AlertLayer::Impl::ScheduleRefresh() | ||||||
| 
 | 
 | ||||||
|    // Expires at the top of the next minute
 |    // Expires at the top of the next minute
 | ||||||
|    std::chrono::system_clock::time_point now = |    std::chrono::system_clock::time_point now = | ||||||
|       std::chrono::floor<std::chrono::minutes>( |       std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); | ||||||
|          std::chrono::system_clock::now()); |  | ||||||
|    refreshTimer_.expires_at(now + 1min); |    refreshTimer_.expires_at(now + 1min); | ||||||
| 
 | 
 | ||||||
|    refreshTimer_.async_wait( |    refreshTimer_.async_wait( | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| #include <scwx/qt/types/qt_types.hpp> | #include <scwx/qt/types/qt_types.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/threads.hpp> | #include <scwx/util/threads.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <mutex> | #include <mutex> | ||||||
|  | @ -67,7 +68,7 @@ bool AlertProxyModel::filterAcceptsRow(int                sourceRow, | ||||||
|                         .value<std::chrono::system_clock::time_point>(); |                         .value<std::chrono::system_clock::time_point>(); | ||||||
| 
 | 
 | ||||||
|       // Compare end time to current
 |       // Compare end time to current
 | ||||||
|       if (endTime < std::chrono::system_clock::now()) |       if (endTime < scwx::util::time::now()) | ||||||
|       { |       { | ||||||
|          acceptAlertActiveFilter = false; |          acceptAlertActiveFilter = false; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #include <scwx/qt/settings/general_settings.hpp> | #include <scwx/qt/settings/general_settings.hpp> | ||||||
| #include <scwx/qt/util/time.hpp> | #include <scwx/qt/util/time.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
| #include <QTimer> | #include <QTimer> | ||||||
| 
 | 
 | ||||||
|  | @ -81,8 +82,7 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : | ||||||
|    p->timeZone_ = date::get_tzdb().locate_zone("UTC"); |    p->timeZone_ = date::get_tzdb().locate_zone("UTC"); | ||||||
| #endif | #endif | ||||||
|    const std::chrono::sys_seconds currentTimePoint = |    const std::chrono::sys_seconds currentTimePoint = | ||||||
|       std::chrono::floor<std::chrono::minutes>( |       std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); | ||||||
|          std::chrono::system_clock::now()); |  | ||||||
|    p->SetTimePoint(currentTimePoint); |    p->SetTimePoint(currentTimePoint); | ||||||
| 
 | 
 | ||||||
|    // Update maximum date on a timer
 |    // Update maximum date on a timer
 | ||||||
|  |  | ||||||
|  | @ -187,7 +187,7 @@ void OverlayProductView::Impl::LoadProduct( | ||||||
|                   header.date_of_message(), header.time_of_message() * 1000); |                   header.date_of_message(), header.time_of_message() * 1000); | ||||||
| 
 | 
 | ||||||
|                // If the record is from the last 30 minutes
 |                // 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 {} && |                    (selectedTime_ != std::chrono::system_clock::time_point {} && | ||||||
|                     productTime + 30min >= selectedTime_)) |                     productTime + 30min >= selectedTime_)) | ||||||
|                { |                { | ||||||
|  |  | ||||||
|  | @ -8,9 +8,13 @@ Checks: | ||||||
|   - 'performance-*' |   - 'performance-*' | ||||||
|   - '-bugprone-easily-swappable-parameters' |   - '-bugprone-easily-swappable-parameters' | ||||||
|   - '-cppcoreguidelines-avoid-magic-numbers' |   - '-cppcoreguidelines-avoid-magic-numbers' | ||||||
|  |   - '-cppcoreguidelines-avoid-do-while' | ||||||
|  |   - '-cppcoreguidelines-avoid-non-const-global-variables' | ||||||
|   - '-cppcoreguidelines-pro-type-reinterpret-cast' |   - '-cppcoreguidelines-pro-type-reinterpret-cast' | ||||||
|  |   - '-cppcoreguidelines-pro-type-union-access' | ||||||
|   - '-misc-include-cleaner' |   - '-misc-include-cleaner' | ||||||
|   - '-misc-non-private-member-variables-in-classes' |   - '-misc-non-private-member-variables-in-classes' | ||||||
|  |   - '-misc-use-anonymous-namespace' | ||||||
|   - '-modernize-return-braced-init-list' |   - '-modernize-return-braced-init-list' | ||||||
|   - '-modernize-use-trailing-return-type' |   - '-modernize-use-trailing-return-type' | ||||||
| FormatStyle: 'file' | 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 | set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp | ||||||
|                      source/scwx/common/products.test.cpp) |                      source/scwx/common/products.test.cpp) | ||||||
| set(SRC_GR_TESTS source/scwx/gr/placefile.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 | 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/aws_level3_data_provider.test.cpp | ||||||
|                        source/scwx/provider/iem_api_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> | #   include <date/tz.h> | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::util::time | ||||||
| { |  | ||||||
| namespace util |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| #if (__cpp_lib_chrono >= 201907L) | #if (__cpp_lib_chrono >= 201907L) | ||||||
|  | @ -34,6 +32,9 @@ typedef scwx::util:: | ||||||
| ClockFormat        GetClockFormat(const std::string& name); | ClockFormat        GetClockFormat(const std::string& name); | ||||||
| const std::string& GetClockFormatName(ClockFormat clockFormat); | 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, | std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate, | ||||||
|                                                 uint32_t milliseconds); |                                                 uint32_t milliseconds); | ||||||
| 
 | 
 | ||||||
|  | @ -46,5 +47,17 @@ template<typename T> | ||||||
| std::optional<std::chrono::sys_time<T>> | std::optional<std::chrono::sys_time<T>> | ||||||
| TryParseDateTime(const std::string& dateTimeFormat, const std::string& str); | TryParseDateTime(const std::string& dateTimeFormat, const std::string& str); | ||||||
| 
 | 
 | ||||||
| } // namespace util
 | } // namespace scwx::util::time
 | ||||||
| } // namespace scwx
 | 
 | ||||||
|  | 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 (scanTimeIt != scanTimes_.cend()) | ||||||
|    { |    { | ||||||
|       // If the time is greater than 2 hours ago, it may be a new scan
 |       // 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) |       if (scanTimeIt->second > replaceBy) | ||||||
|       { |       { | ||||||
|          return scanTimeIt->second; |          return scanTimeIt->second; | ||||||
|  | @ -333,8 +333,7 @@ AwsLevel2ChunksDataProvider::Impl::ListObjects() | ||||||
|    size_t       newObjects   = 0; |    size_t       newObjects   = 0; | ||||||
|    const size_t totalObjects = 0; |    const size_t totalObjects = 0; | ||||||
| 
 | 
 | ||||||
|    const std::chrono::system_clock::time_point now = |    const std::chrono::system_clock::time_point now = util::time::now(); | ||||||
|       std::chrono::system_clock::now(); |  | ||||||
| 
 | 
 | ||||||
|    if (currentScan_.valid_ && !currentScan_.hasAllFiles_ && |    if (currentScan_.valid_ && !currentScan_.hasAllFiles_ && | ||||||
|        lastTimeListed_ + std::chrono::minutes(2) > now) |        lastTimeListed_ + std::chrono::minutes(2) > now) | ||||||
|  |  | ||||||
|  | @ -352,7 +352,7 @@ std::pair<size_t, size_t> AwsNexradDataProvider::Refresh() | ||||||
| 
 | 
 | ||||||
|    logger_->debug("Refresh()"); |    logger_->debug("Refresh()"); | ||||||
| 
 | 
 | ||||||
|    auto today     = floor<days>(system_clock::now()); |    auto today     = floor<days>(util::time::now()); | ||||||
|    auto yesterday = today - days {1}; |    auto yesterday = today - days {1}; | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock(p->refreshMutex_); |    std::unique_lock lock(p->refreshMutex_); | ||||||
|  | @ -388,7 +388,7 @@ void AwsNexradDataProvider::Impl::PruneObjects() | ||||||
| { | { | ||||||
|    using namespace std::chrono; |    using namespace std::chrono; | ||||||
| 
 | 
 | ||||||
|    auto today     = floor<days>(system_clock::now()); |    auto today     = floor<days>(util::time::now()); | ||||||
|    auto yesterday = today - days {1}; |    auto yesterday = today - days {1}; | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock(objectsMutex_); |    std::unique_lock lock(objectsMutex_); | ||||||
|  |  | ||||||
|  | @ -108,7 +108,7 @@ WarningsProvider::LoadUpdatedFiles( | ||||||
|    std::vector<std::shared_ptr<awips::TextProductFile>> updatedFiles; |    std::vector<std::shared_ptr<awips::TextProductFile>> updatedFiles; | ||||||
| 
 | 
 | ||||||
|    const std::chrono::sys_time<std::chrono::hours> now = |    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 = |    std::chrono::sys_time<std::chrono::hours> currentHour = | ||||||
|       (startTime != std::chrono::sys_time<std::chrono::hours> {}) ? |       (startTime != std::chrono::sys_time<std::chrono::hours> {}) ? | ||||||
|          startTime : |          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 | #   define __cpp_lib_format 202110L | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #include <scwx/network/ntp_client.hpp> | ||||||
| #include <scwx/util/time.hpp> | #include <scwx/util/time.hpp> | ||||||
| #include <scwx/util/enum.hpp> | #include <scwx/util/enum.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
|  | @ -21,7 +22,7 @@ | ||||||
| #   include <date/date.h> | #   include <date/date.h> | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace scwx::util | namespace scwx::util::time | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "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::_24Hour, "24-hour"}, | ||||||
|    {ClockFormat::Unknown, "?"}}; |    {ClockFormat::Unknown, "?"}}; | ||||||
| 
 | 
 | ||||||
|  | static std::shared_ptr<network::NtpClient> ntpClient_ {nullptr}; | ||||||
|  | 
 | ||||||
| SCWX_GET_ENUM(ClockFormat, GetClockFormat, clockFormatName_) | SCWX_GET_ENUM(ClockFormat, GetClockFormat, clockFormatName_) | ||||||
| 
 | 
 | ||||||
| const std::string& GetClockFormatName(ClockFormat clockFormat) | const std::string& GetClockFormatName(ClockFormat clockFormat) | ||||||
|  | @ -39,6 +42,26 @@ const std::string& GetClockFormatName(ClockFormat clockFormat) | ||||||
|    return clockFormatName_.at(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, | std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate, | ||||||
|                                                 uint32_t milliseconds) |                                                 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, | TryParseDateTime<std::chrono::seconds>(const std::string& dateTimeFormat, | ||||||
|                                        const std::string& str); |                                        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 | set(SRC_GR source/scwx/gr/color.cpp | ||||||
|            source/scwx/gr/placefile.cpp) |            source/scwx/gr/placefile.cpp) | ||||||
| set(HDR_NETWORK include/scwx/network/cpr.hpp | 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 | 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 | set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp | ||||||
|                  include/scwx/provider/aws_level2_chunks_data_provider.hpp |                  include/scwx/provider/aws_level2_chunks_data_provider.hpp | ||||||
|                  include/scwx/provider/aws_level3_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.cpp | ||||||
|                  source/scwx/provider/nexrad_data_provider_factory.cpp |                  source/scwx/provider/nexrad_data_provider_factory.cpp | ||||||
|                  source/scwx/provider/warnings_provider.cpp) |                  source/scwx/provider/warnings_provider.cpp) | ||||||
| set(HDR_TYPES include/scwx/types/iem_types.hpp) | set(HDR_TYPES include/scwx/types/iem_types.hpp | ||||||
| set(SRC_TYPES source/scwx/types/iem_types.cpp) |               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 | set(HDR_UTIL include/scwx/util/digest.hpp | ||||||
|              include/scwx/util/enum.hpp |              include/scwx/util/enum.hpp | ||||||
|              include/scwx/util/environment.hpp |              include/scwx/util/environment.hpp | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat