From 7934711b63cd2ecfc7fdd1818dc8ae076d573454 Mon Sep 17 00:00:00 2001 From: Wiktor Muller Date: Sat, 2 May 2026 19:35:12 +0000 Subject: [PATCH 1/5] Discovery manager implementation - NodeInfo storage using std::unordered_map - Implemented best neighbor selection algorithm - Unit tests for discovery manager --- mw/timestamp_mw/ntp/discovery/BUILD | 12 +++ .../ntp/discovery/discovery_manager.cpp | 85 ++++++++++++++++++ .../ntp/discovery/discovery_manager.hpp | 59 +++++++++++++ mw/timestamp_mw/ntp/ut/BUILD | 11 +++ .../ntp/ut/discovery_manager_test.cc | 86 +++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 mw/timestamp_mw/ntp/discovery/BUILD create mode 100644 mw/timestamp_mw/ntp/discovery/discovery_manager.cpp create mode 100644 mw/timestamp_mw/ntp/discovery/discovery_manager.hpp create mode 100644 mw/timestamp_mw/ntp/ut/discovery_manager_test.cc diff --git a/mw/timestamp_mw/ntp/discovery/BUILD b/mw/timestamp_mw/ntp/discovery/BUILD new file mode 100644 index 00000000..278a9532 --- /dev/null +++ b/mw/timestamp_mw/ntp/discovery/BUILD @@ -0,0 +1,12 @@ +cc_library( + name = "ntp_discovery", + srcs = [ + "discovery_manager.cpp", + ], + hdrs = [ + "discovery_manager.hpp", + ], + deps = [ + ], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp b/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp new file mode 100644 index 00000000..1f942040 --- /dev/null +++ b/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp @@ -0,0 +1,85 @@ +/** + * @file discovery_manager.hpp + * @brief Manages the selection of the best time provider + * @author Wiktor Müller (wiktor.muller8@gmail.com) + * @version 0.1 + * @date 2026-05-02 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "mw/timestamp_mw/ntp/discovery/discovery_manager.hpp" + +namespace srp { +namespace tinyNTP { + +/** + * @brief Usuwa nieaktywne węzły z mapy urządzeń sieciowych + * + * @warning Funkcja nie jest thread-safe. + * Przed wywołaniem funkcji należy zablokować `map_mutex_`. + * + * @param timeout_seconds Czas wygaśnięcia sąsiada + */ +void DiscoveryManager::RemoveExpiredNodes(int timeout_seconds) { + const auto now = std::chrono::steady_clock::now(); + + for (auto it = neighbors_.begin(); it != neighbors_.end(); ) { + const auto delta = std::chrono::duration_cast(now - it->second.last_seen).count(); + + if (delta > timeout_seconds) { + it = neighbors_.erase(it); + } else { + ++it; + } + } +} + +void DiscoveryManager::UpdateNode(const std::string& ip, uint8_t ntp_class, bool holdover) { + std::lock_guard lock(map_mutex_); + + NodeInfo& node = neighbors_[ip]; + + node.ip = ip; + node.ntp_class = ntp_class; + node.holdover = holdover; + node.last_seen = std::chrono::steady_clock::now(); +} + +/** + * @brief Zwraca najlepszy węzeł w sieci + * + * @param local_node + * @return std::optional - W przypadku gdy lokalny node jest najlepszym w sieci zwrócony optional jest pusty + */ +std::optional DiscoveryManager::GetBestMaster(const NodeInfo& local_node) { + std::lock_guard lock(map_mutex_); + + RemoveExpiredNodes(); + + NodeInfo best_neighbor = local_node; + + for (const auto& [ip, node] : neighbors_) { + if (node.ntp_class != best_neighbor.ntp_class) { + if (node.ntp_class < best_neighbor.ntp_class) { + best_neighbor = node; + } + } + else if (node.holdover != best_neighbor.holdover) { + if (!node.holdover) { + best_neighbor = node; + } + } + else if (node.ip < best_neighbor.ip) { + best_neighbor = node; + } + + } + + if (best_neighbor.ip == local_node.ip) return std::nullopt; + return best_neighbor; +} + +} // namespace tinyNTP +} // namespace srp \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp b/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp new file mode 100644 index 00000000..a2690cf8 --- /dev/null +++ b/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp @@ -0,0 +1,59 @@ +/** + * @file discovery_manager.hpp + * @brief Manages the selection of the best time provider + * @author Wiktor Müller (wiktor.muller8@gmail.com) + * @version 0.1 + * @date 2026-05-02 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef MW_TIMESTAMP_MW_NTP_DISCOVERY_DISCOVERY_MANAGER_HPP_ +#define MW_TIMESTAMP_MW_NTP_DISCOVERY_DISCOVERY_MANAGER_HPP_ + +#include +#include +#include +#include +#include +#include + +namespace srp { +namespace tinyNTP { + +constexpr int TIMEOUT_SECONDS = 15; + +/** + * @brief Pojedynczy węzeł sieci + * + * ip - adres ip + * ntp_class - klasa urządzenia (0-7), im niższa wartość, tym wyższy priorytet. + * holdover - 1 = brak aktualnej referencji (czas niepewny); 0 = zsynchronizowany. + * last_seen - czas otrzymania ostatniej wiadomości od danego urządzenia + */ +struct NodeInfo { + std::string ip; + uint8_t ntp_class; + bool holdover; + std::chrono::steady_clock::time_point last_seen; +}; + +class DiscoveryManager { +private: + void RemoveExpiredNodes(int timeout_seconds = TIMEOUT_SECONDS); + + std::unordered_map neighbors_; // ip -> NodeInfo + mutable std::mutex map_mutex_; + public: + DiscoveryManager() = default; + ~DiscoveryManager() = default; + + void UpdateNode(const std::string& ip, uint8_t ntp_class, bool holdover); + std::optional GetBestMaster(const NodeInfo& local_node); +}; + +} // namespace tinyNTP +} // namespace srp + +#endif // MW_TIMESTAMP_MW_NTP_DISCOVERY_DISCOVERY_MANAGER_HPP_ \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/ut/BUILD b/mw/timestamp_mw/ntp/ut/BUILD index 96f11916..d5a657c6 100644 --- a/mw/timestamp_mw/ntp/ut/BUILD +++ b/mw/timestamp_mw/ntp/ut/BUILD @@ -8,4 +8,15 @@ cc_test( "@com_google_googletest//:gtest_main", "//mw/timestamp_mw/ntp/controller:ntp_controller", ], +) + +cc_test( + name = "discovery_manager_test", + size = "small", + srcs = ["discovery_manager_test.cc"], + visibility = ["//visibility:public"], + deps = [ + "@com_google_googletest//:gtest_main", + "//mw/timestamp_mw/ntp/discovery:ntp_discovery", + ], ) \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc b/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc new file mode 100644 index 00000000..fa60d036 --- /dev/null +++ b/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc @@ -0,0 +1,86 @@ +/** + * @file discovery_manager_test.cc + * @author Wiktor Müller (wiktor.muller8@gmail.com) + * @brief + * @version 0.1 + * @date 2026-05-02 + * + * @copyright Copyright (c) 2025 + * + */ + +#include +#include +#include +#include +#include "mw/timestamp_mw/ntp/discovery/discovery_manager.hpp" + +using namespace srp::tinyNTP; + +class DiscoveryManagerTest : public ::testing::Test { + protected: + DiscoveryManager discoveryManager; + + NodeInfo default_local_node; + + void SetUp() override { + default_local_node.ip = "192.168.0.50"; + default_local_node.ntp_class = 5; + default_local_node.holdover = true; + default_local_node.last_seen = std::chrono::steady_clock::now(); + } +}; + +TEST_F(DiscoveryManagerTest, LocalNodeIsBestWhenNetworkEmpty) { + auto best_master = discoveryManager.GetBestMaster(default_local_node); + + EXPECT_FALSE(best_master.has_value()); +} + +TEST_F(DiscoveryManagerTest, ExternalNodeWinsByClass) { + discoveryManager.UpdateNode("192.168.0.100", 2, true); + + auto best_master = discoveryManager.GetBestMaster(default_local_node); + + ASSERT_TRUE(best_master.has_value()); + EXPECT_EQ(best_master->ip, "192.168.0.100"); +} + +TEST_F(DiscoveryManagerTest, LocalNodeWinsByClass) { + discoveryManager.UpdateNode("192.168.0.100", 7, true); + + auto best_master = discoveryManager.GetBestMaster(default_local_node); + + ASSERT_FALSE(best_master.has_value()); +} + +TEST_F(DiscoveryManagerTest, ExternalNodeWinsByHoldover) { + discoveryManager.UpdateNode("192.168.0.100", 5, false); + + auto best_master = discoveryManager.GetBestMaster(default_local_node); + + ASSERT_TRUE(best_master.has_value()); + EXPECT_EQ(best_master->ip, "192.168.0.100"); +} + +TEST_F(DiscoveryManagerTest, ExternalNodeWinsByIpTieBreaker) { + discoveryManager.UpdateNode("192.168.0.10", 5, true); + auto best_master = discoveryManager.GetBestMaster(default_local_node); + + ASSERT_TRUE(best_master.has_value()); + EXPECT_EQ(best_master->ip, "192.168.0.10"); + + discoveryManager.UpdateNode("10.168.0.10", 5, true); + best_master = discoveryManager.GetBestMaster(default_local_node); + + ASSERT_TRUE(best_master.has_value()); + EXPECT_EQ(best_master->ip, "10.168.0.10"); +} + +TEST_F(DiscoveryManagerTest, LocalNodeDefeatsLowerIpWithBetterClass) { + discoveryManager.UpdateNode("10.0.0.1", 7, false); + + auto best_master = discoveryManager.GetBestMaster(default_local_node); + + EXPECT_FALSE(best_master.has_value()); +} \ No newline at end of file From da5415653e05868513acf91b75650e3f5c689d99 Mon Sep 17 00:00:00 2001 From: Wiktor Muller Date: Mon, 4 May 2026 19:46:03 +0200 Subject: [PATCH 2/5] Loading configuration from json files - configuration manager - integration with controller - unit tests --- deployment/cpu/ec/BUILD | 1 + deployment/cpu/ec/ntp_config.json | 5 ++ deployment/cpu/fc/BUILD | 1 + deployment/cpu/fc/ntp_config.json | 5 ++ mw/timestamp_mw/ntp/config/BUILD | 14 ++++ mw/timestamp_mw/ntp/config/config_manager.cpp | 67 +++++++++++++++ mw/timestamp_mw/ntp/config/config_manager.hpp | 43 ++++++++++ mw/timestamp_mw/ntp/controller/BUILD | 2 + .../ntp/controller/ntp_controller.cpp | 67 +++++++-------- .../ntp/controller/ntp_controller.hpp | 9 +- mw/timestamp_mw/ntp/ut/BUILD | 11 +++ mw/timestamp_mw/ntp/ut/config_manager_test.cc | 84 +++++++++++++++++++ mw/timestamp_mw/service/timestamp_service.cpp | 6 +- 13 files changed, 280 insertions(+), 35 deletions(-) create mode 100644 deployment/cpu/ec/ntp_config.json create mode 100644 deployment/cpu/fc/ntp_config.json create mode 100644 mw/timestamp_mw/ntp/config/BUILD create mode 100644 mw/timestamp_mw/ntp/config/config_manager.cpp create mode 100644 mw/timestamp_mw/ntp/config/config_manager.hpp create mode 100644 mw/timestamp_mw/ntp/ut/config_manager_test.cc diff --git a/deployment/cpu/ec/BUILD b/deployment/cpu/ec/BUILD index 3e950d73..c23e6e69 100644 --- a/deployment/cpu/ec/BUILD +++ b/deployment/cpu/ec/BUILD @@ -14,6 +14,7 @@ cpu_def( etcs = [ "diag_config.json", "logger_config.json", + "ntp_config.json", ], platform_etcs = [ "//deployment/cpu/ec:sm_config", diff --git a/deployment/cpu/ec/ntp_config.json b/deployment/cpu/ec/ntp_config.json new file mode 100644 index 00000000..0392420a --- /dev/null +++ b/deployment/cpu/ec/ntp_config.json @@ -0,0 +1,5 @@ +{ + "ip": "192.168.10.51", + "ntp_class": 3, + "T_hb_ms": 1000 +} \ No newline at end of file diff --git a/deployment/cpu/fc/BUILD b/deployment/cpu/fc/BUILD index ba830d4d..b1266eb5 100644 --- a/deployment/cpu/fc/BUILD +++ b/deployment/cpu/fc/BUILD @@ -13,6 +13,7 @@ cpu_def( etcs = [ "diag_config.json", "logger_config.json", + "ntp_config.json", ], platform_etcs = [ "//deployment/cpu/fc:sm_config", diff --git a/deployment/cpu/fc/ntp_config.json b/deployment/cpu/fc/ntp_config.json new file mode 100644 index 00000000..75546782 --- /dev/null +++ b/deployment/cpu/fc/ntp_config.json @@ -0,0 +1,5 @@ +{ + "ip": "192.168.10.52", + "ntp_class": 3, + "T_hb_ms": 1000 +} \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/config/BUILD b/mw/timestamp_mw/ntp/config/BUILD new file mode 100644 index 00000000..4dc9e795 --- /dev/null +++ b/mw/timestamp_mw/ntp/config/BUILD @@ -0,0 +1,14 @@ +cc_library( + name = "ntp_config_manager", + srcs = [ + "config_manager.cpp", + ], + hdrs = [ + "config_manager.hpp", + ], + deps = [ + "//core/json:simba_json", + "@srp_platform//ara/log", + ], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/config/config_manager.cpp b/mw/timestamp_mw/ntp/config/config_manager.cpp new file mode 100644 index 00000000..7e6c1323 --- /dev/null +++ b/mw/timestamp_mw/ntp/config/config_manager.cpp @@ -0,0 +1,67 @@ +/** + * @file config_manager.hpp + * @brief Loads device configuration + * @author Wiktor Müller (wiktor.muller8@gmail.com) + * @version 0.1 + * @date 2026-05-02 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "mw/timestamp_mw/ntp/config/config_manager.hpp" +#include "core/json/json_parser.h" +#include "ara/log/log.h" +#include + +namespace srp { +namespace tinyNTP { + +NtpConfig ConfigManager::LoadConfig(const std::string& filepath) { + NtpConfig config; + // Podstawowe dane do fallbacku + config.ip = "127.0.0.1"; + config.ntp_class = 7; + config.t_hb_ms = 1000; + + auto parser_opt = srp::core::json::JsonParser::Parser(filepath); + + if (!parser_opt.has_value()) { + ara::log::LogError() << "Cannot open or parse config file: " << filepath + << ". Using fallback values.\n"; + return config; + } + + const auto& parser = parser_opt.value(); + + auto ip_opt = parser.GetString("ip"); + if (ip_opt.has_value()) { + config.ip = ip_opt.value(); + } else { + ara::log::LogWarn() << "Missing or invalid 'ip'. Using fallback ip.\n"; + } + + auto raw_json = parser.GetObject(); + + if (raw_json.contains("ntp_class") && raw_json["ntp_class"].is_number_integer()) { + config.ntp_class = raw_json["ntp_class"]; + + if (config.ntp_class > 7) { + ara::log::LogWarn() << "Config ntp_class > 7. Using fallback class 7.\n"; + config.ntp_class = 7; + } + } else { + ara::log::LogWarn() << "Missing or invalid 'ntp_class'. Using fallback class 7.\n"; + } + + if (raw_json.contains("T_hb_ms") && raw_json["T_hb_ms"].is_number_integer()) { + config.t_hb_ms = raw_json["T_hb_ms"]; + } else { + ara::log::LogWarn() << "Missing or invalid 'T_hb_ms'. Using fallback T_hb_ms.\n"; + } + + return config; +} + +} // namespace tinyNTP +} // namespace srp \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/config/config_manager.hpp b/mw/timestamp_mw/ntp/config/config_manager.hpp new file mode 100644 index 00000000..79207121 --- /dev/null +++ b/mw/timestamp_mw/ntp/config/config_manager.hpp @@ -0,0 +1,43 @@ +/** + * @file config_manager.hpp + * @brief Loads device configuration + * @author Wiktor Müller (wiktor.muller8@gmail.com) + * @version 0.1 + * @date 2026-05-02 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef MW_TIMESTAMP_MW_NTP_CONFIG_CONFIG_MANAGER_HPP_ +#define MW_TIMESTAMP_MW_NTP_CONFIG_CONFIG_MANAGER_HPP_ + +#include +#include + +namespace srp { +namespace tinyNTP { + +constexpr const char* CONFIG_FILEPATH = "/srp/opt/cpu_srp/ntp_config.json"; + +struct NtpConfig { + std::string ip; + uint8_t ntp_class; + uint32_t t_hb_ms; +}; + +class ConfigManager { + public: + /** + * @brief Wczytuje konfiguracje z pliku JSON + * + * @param filepath Ścieżka do pliku konfiguracyjnego + * @return NtpConfig Struktura z wczytanymi parametrami + */ + static NtpConfig LoadConfig(const std::string& filepath = CONFIG_FILEPATH); +}; + +} // namespace tinyNTP +} // namespace srp + +#endif // MW_TIMESTAMP_MW_NTP_CONFIG_CONFIG_MANAGER_HPP_ \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/controller/BUILD b/mw/timestamp_mw/ntp/controller/BUILD index 1a776591..bd561065 100644 --- a/mw/timestamp_mw/ntp/controller/BUILD +++ b/mw/timestamp_mw/ntp/controller/BUILD @@ -6,6 +6,8 @@ cc_library( "//core/json:simba_json", "//core/timestamp:timestamp_controller", "//mw/timestamp_mw/ntp/controller:ntp_com_data", + "//mw/timestamp_mw/ntp/config:ntp_config_manager", + "//mw/timestamp_mw/ntp/discovery:ntp_discovery", ], srcs = ["ntp_controller.cpp"], hdrs = ["ntp_controller.hpp"], diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp index b7f3bc89..3b4aa101 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp @@ -15,6 +15,7 @@ #include "core/common/condition.h" #include "core/json/json_parser.h" #include "ara/log/log.h" +#include namespace srp { namespace tinyNTP { namespace { @@ -26,6 +27,20 @@ namespace { constexpr auto kDelay_time = 4000; } +bool NtpController::Init(const NtpConfig& config) { + myIP = config.ip; + ntp_class_ = config.ntp_class; + t_hb_ms_ = config.t_hb_ms; + + timestamp_.Init(); + + ara::log::LogInfo() << "NtpController initialized with IP: " << myIP + << ", NTP Class: " << static_cast(ntp_class_) + << ", interval [ms]: " << t_hb_ms_; + + return true; +} + int64_t NtpController::CalculateOffset(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3) { return ((T1 - T0) + (T2 - T3)) / 2; @@ -82,39 +97,25 @@ void NtpController::thread_loop(std::stop_token token) { } } -bool NtpController::Init() { - auto ip = readMyIP(); - if (!ip.has_value()) { - return false; - } - myIP = ip.value(); - timestamp_.Init(); - if (myIP == masterIP) { - sock_.Init(com::soc::SocketConfig{myIP, kRX_port, kTx_port}); - this->sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3)); - sock_.StartRXThread(); - } else { - ntp_thread = std::jthread([this](std::stop_token token){ - thread_loop(token); - }); - } - return true; -} - -// TODO(matikrajek42@gmail.com) fix this shit func -std::optional NtpController::readMyIP() { - const std::string path = kIp_file_path; - auto parser = core::json::JsonParser::Parser(path); - if (!parser.has_value()) { - return std::nullopt; - } - auto ip = parser.value().GetString("ip"); - if (!ip.has_value()) { - return std::nullopt; - } - return ip.value(); -} +// bool NtpController::Init() { +// auto ip = readMyIP(); +// if (!ip.has_value()) { +// return false; +// } +// myIP = ip.value(); + +// if (myIP == masterIP) { +// sock_.Init(com::soc::SocketConfig{myIP, kRX_port, kTx_port}); +// this->sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, +// std::placeholders::_2, std::placeholders::_3)); +// sock_.StartRXThread(); +// } else { +// ntp_thread = std::jthread([this](std::stop_token token){ +// thread_loop(token); +// }); +// } +// return true; +// } } // namespace tinyNTP } // namespace srp diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp index 7ac55988..fd55311d 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp @@ -18,6 +18,8 @@ #include "communication-core/sockets/tcp_socket.h" #include "core/timestamp/timestamp_driver.hpp" #include "srp/mw/tinyNTP/ntpStruct.h" +#include "mw/timestamp_mw/ntp/config/config_manager.hpp" +#include "mw/timestamp_mw/ntp/discovery/discovery_manager.hpp" namespace srp { namespace tinyNTP { @@ -27,14 +29,19 @@ class NtpController { core::timestamp::TimestampMaster timestamp_; std::jthread ntp_thread; std::string myIP; + uint8_t ntp_class_; + uint32_t t_hb_ms_; + + DiscoveryManager discovery_manager_; public: + bool Init(const NtpConfig& config); + std::optional readMyIP(); std::vector socket_callback(const std::string& ip, const std::uint16_t& port, const std::vector payload); void thread_loop(std::stop_token token); int64_t CalculateOffset(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3); uint64_t CalculateRoundTripDelay(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3); - bool Init(); int64_t GetTimestamp(); }; diff --git a/mw/timestamp_mw/ntp/ut/BUILD b/mw/timestamp_mw/ntp/ut/BUILD index d5a657c6..37afbd1a 100644 --- a/mw/timestamp_mw/ntp/ut/BUILD +++ b/mw/timestamp_mw/ntp/ut/BUILD @@ -19,4 +19,15 @@ cc_test( "@com_google_googletest//:gtest_main", "//mw/timestamp_mw/ntp/discovery:ntp_discovery", ], +) + +cc_test( + name = "config_manager_test", + size = "small", + srcs = ["config_manager_test.cc"], + visibility = ["//visibility:public"], + deps = [ + "@com_google_googletest//:gtest_main", + "//mw/timestamp_mw/ntp/config:ntp_config_manager", + ], ) \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/ut/config_manager_test.cc b/mw/timestamp_mw/ntp/ut/config_manager_test.cc new file mode 100644 index 00000000..51ba840f --- /dev/null +++ b/mw/timestamp_mw/ntp/ut/config_manager_test.cc @@ -0,0 +1,84 @@ +#include +#include +#include + +#include "mw/timestamp_mw/ntp/config/config_manager.hpp" + +using namespace srp::tinyNTP; + +class ConfigManagerTest : public ::testing::Test { + protected: + const std::string temp_filepath = "test_config.json"; + + void CreateJsonFile(const std::string& content) { + std::ofstream file(temp_filepath); + file << content; + file.close(); + } + + void TearDown() override { + std::remove(temp_filepath.c_str()); // Usuwamy plik testowy + } +}; + +TEST_F(ConfigManagerTest, LoadsValidConfigCorrectly) { + CreateJsonFile(R"({ + "ip": "192.168.1.100", + "ntp_class": 3, + "T_hb_ms": 500 + })"); + + auto config = ConfigManager::LoadConfig(temp_filepath); + + EXPECT_EQ(config.ip, "192.168.1.100"); + EXPECT_EQ(config.ntp_class, 3); + EXPECT_EQ(config.t_hb_ms, 500); +} + +TEST_F(ConfigManagerTest, UsesFallbackWhenFileDoesNotExist) { + auto config = ConfigManager::LoadConfig("fake_path_that_doesnt_exist.json"); + + EXPECT_EQ(config.ip, "127.0.0.1"); + EXPECT_EQ(config.ntp_class, 7); + EXPECT_EQ(config.t_hb_ms, 1000); +} + +TEST_F(ConfigManagerTest, UsesFallbackOnInvalidJsonSyntax) { + CreateJsonFile(R"({ + "ip": "10.0.0.1", + "ntp_class": 2 + "T_hb_ms": 500 + )"); + + auto config = ConfigManager::LoadConfig(temp_filepath); + + EXPECT_EQ(config.ip, "127.0.0.1"); + EXPECT_EQ(config.ntp_class, 7); + EXPECT_EQ(config.t_hb_ms, 1000); +} + +TEST_F(ConfigManagerTest, ForcesFallbackClassIfOutOfBounds) { + CreateJsonFile(R"({ + "ip": "10.0.0.2", + "ntp_class": 15, + "T_hb_ms": 200 + })"); + + auto config = ConfigManager::LoadConfig(temp_filepath); + + EXPECT_EQ(config.ip, "10.0.0.2"); + EXPECT_EQ(config.t_hb_ms, 200); + EXPECT_EQ(config.ntp_class, 7); +} + +TEST_F(ConfigManagerTest, HandlesMissingFieldsGracefully) { + CreateJsonFile(R"({ + "ip": "172.16.0.5" + })"); // Brak ntp_class i T_hb_ms + + auto config = ConfigManager::LoadConfig(temp_filepath); + + EXPECT_EQ(config.ip, "172.16.0.5"); + EXPECT_EQ(config.ntp_class, 7); + EXPECT_EQ(config.t_hb_ms, 1000); +} \ No newline at end of file diff --git a/mw/timestamp_mw/service/timestamp_service.cpp b/mw/timestamp_mw/service/timestamp_service.cpp index b84387eb..eef68b75 100644 --- a/mw/timestamp_mw/service/timestamp_service.cpp +++ b/mw/timestamp_mw/service/timestamp_service.cpp @@ -20,12 +20,16 @@ int TimestampService::Run(const std::stop_token& token) { core::condition::wait(token); return 0; } + int TimestampService::Initialize(const std::map parms) { - if (!this->ntp_controller.Init()) { + auto config = srp::tinyNTP::ConfigManager::LoadConfig(); + + if (!this->ntp_controller.Init(config)) { ara::log::LogError() << "NTP controller initialization failed."; return -1; } + ara::log::LogInfo() << "Init completed"; return 0; } From cd4d23d38537a603a8ed48d9e8d7cc60e483a37a Mon Sep 17 00:00:00 2001 From: Wiktor Muller Date: Mon, 1 Jun 2026 13:33:38 +0200 Subject: [PATCH 3/5] Connecting tinyNTP to sockets - Initialization of udp and udp multicast sockets - Connecting to mechanism of chosing best neighbor --- mw/timestamp_mw/ntp/controller/BUILD | 3 +- .../ntp/controller/ntp_controller.cpp | 205 +++++++++++++----- .../ntp/controller/ntp_controller.hpp | 19 +- .../ntp/discovery/discovery_manager.cpp | 10 +- .../ntp/discovery/discovery_manager.hpp | 10 +- 5 files changed, 177 insertions(+), 70 deletions(-) diff --git a/mw/timestamp_mw/ntp/controller/BUILD b/mw/timestamp_mw/ntp/controller/BUILD index bd561065..3426f875 100644 --- a/mw/timestamp_mw/ntp/controller/BUILD +++ b/mw/timestamp_mw/ntp/controller/BUILD @@ -2,7 +2,8 @@ load("@srp_platform//tools/model_generator/ara:data_structure_generator.bzl", "d cc_library( name = "ntp_controller", deps = [ - "//communication-core/sockets:socket_tcp", + "//communication-core/sockets:socket_udp", + "//communication-core/sockets:socket_udp_multicast", "//core/json:simba_json", "//core/timestamp:timestamp_controller", "//mw/timestamp_mw/ntp/controller:ntp_com_data", diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp index 3b4aa101..d9618a8f 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp @@ -10,21 +10,20 @@ */ #include "mw/timestamp_mw/ntp/controller/ntp_controller.hpp" +#include "mw/timestamp_mw/ntp/config/config_manager.hpp" #include #include #include "core/common/condition.h" #include "core/json/json_parser.h" #include "ara/log/log.h" -#include + namespace srp { namespace tinyNTP { namespace { - constexpr auto kIp_file_path = "/srp/opt/cpu_srp/logger_config.json"; - constexpr auto kRX_port = 9999; - constexpr auto kTx_port = 9998; - constexpr auto masterIP = "192.168.10.102"; + constexpr auto kRX_Tx_udp_port = 9998; + constexpr auto kRX_Tx_multicast_port = 9999; + constexpr auto kMulticastIP = "231.255.42.99"; constexpr auto kHeader_size = 33; - constexpr auto kDelay_time = 4000; } bool NtpController::Init(const NtpConfig& config) { @@ -33,8 +32,32 @@ bool NtpController::Init(const NtpConfig& config) { t_hb_ms_ = config.t_hb_ms; timestamp_.Init(); + discovery_manager_.Init(myIP, ntp_class_, false); + + if (udp_sock_.Init(myIP, kRX_Tx_udp_port) != srp::core::ErrorCode::kOk) { + ara::log::LogError() << "Failed to initialize udp socket!"; + } else { + ara::log::LogInfo() << "Udp socket initialized!"; + } + if (multicast_sock_.Init(myIP, kMulticastIP, kRX_Tx_multicast_port) != srp::core::ErrorCode::kOk) { + ara::log::LogError() << "Failed to initialize multicast socket!"; + } else { + ara::log::LogInfo() << "Multicast socket initialized!"; + } + + this->udp_sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + this->multicast_sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + + udp_sock_.StartRXThread(); + multicast_sock_.StartRXThread(); - ara::log::LogInfo() << "NtpController initialized with IP: " << myIP + ntp_thread = std::jthread([this](std::stop_token token){ + thread_loop(token); + }); + + ara::log::LogError() << "NtpController initialized with IP: " << myIP << ", NTP Class: " << static_cast(ntp_class_) << ", interval [ms]: " << t_hb_ms_; @@ -51,20 +74,114 @@ uint64_t NtpController::CalculateRoundTripDelay(const int64_t T0, const int64_t return static_cast((T3 - T0) - (T2 - T1)); } +uint8_t NtpController::EncodeSettings(uint8_t device_class, bool is_holdover, uint8_t msg_type) { + uint8_t settings = 0; + + // Bity 0-2: Klasa urządzenia + settings |= (device_class & 0x07); + + // Bit 3: Holdover + if (is_holdover) { + settings |= (1 << 3); + } + + // Bity 4-5: Version + // Bit 6: msg_type (0 dla Unicast, 1 dla Announce) + if (msg_type == 1) { + settings |= (1 << 6); + } + + // Bit 7: Reserved + + return settings; +} + +void NtpController::SendAnnounce() { + srp::mw::tinyNTP::ntpStruct frame; + + /** + * @todo: Implement holdover + */ + frame.settings = EncodeSettings(ntp_class_, false, 1); + frame.t0 = 0; frame.t1 = 0; frame.t2 = 0; frame.t3 = 0; + + auto buf = srp::data::Convert2Vector::Conv(frame); + + if (multicast_sock_.Transmit(buf) != srp::core::ErrorCode::kOk) { + ara::log::LogError() << "Failed to send Announce multicast frame!"; + } else { + ara::log::LogDebug() << "Announce multicast frame sent correctly!"; + } +} + +void NtpController::SendSyncRequest(const std::string& current_master_ip) { + srp::mw::tinyNTP::ntpStruct header; + header.settings = EncodeSettings(ntp_class_, false, 0); + header.t0 = GetTimestamp(); + header.t1 = 0; header.t2 = 0; header.t3 = 0; + + last_t0_ = header.t0; + + auto buf = srp::data::Convert2Vector::Conv(header); + + this->udp_sock_.Transmit(current_master_ip, kRX_Tx_udp_port, buf); +} + +void NtpController::socket_callback(const std::string& ip, + const uint16_t& port, + const std::vector& payload) { -std::vector NtpController::socket_callback(const std::string& ip, const std::uint16_t& port, - const std::vector payload) { - auto now_ms = GetTimestamp(); - ara::log::LogDebug() << "Receive socket callback"; + int64_t now_ms = GetTimestamp(); + + if (payload.size() != kHeader_size) { + ara::log::LogError() << "Invalid payload size! Rejecting the packet."; + return; + } + + // Zabezpieczenie przed odebraniem swojego announce + if (ip == myIP) { + ara::log::LogDebug() << "Rejecting own packet."; + return; + } + auto val = srp::data::Convert::Conv(payload); - if (!val.has_value()) { - return {}; + if (!val.has_value()) return; + srp::mw::tinyNTP::ntpStruct header = val.value(); + + uint8_t msg_type = (header.settings >> 6) & 0x01; + uint8_t sender_class = header.settings & 0x07; + bool holdover = (header.settings >> 3) & 0x01; + + if (msg_type == 1) { // Announce + ara::log::LogDebug() << "Updating node with ip: " << ip; + discovery_manager_.UpdateNode(ip, sender_class, holdover); + } + else if (msg_type == 0) { // Sync + auto master_opt = discovery_manager_.GetBestMaster(); + bool is_server = (!master_opt.has_value() || master_opt.value().ip == myIP); + + if (is_server) { + header.t1 = now_ms; + header.t2 = GetTimestamp(); + + auto buf = srp::data::Convert2Vector::Conv(header); + + udp_sock_.Transmit(ip, kRX_Tx_udp_port, buf); + + ara::log::LogDebug() << "Sent sync response to " << ip; + } + else { + int64_t t3 = now_ms; + + auto offset = CalculateOffset(header.t0, header.t1, header.t2, t3); + auto round_trip_time = CalculateRoundTripDelay(header.t0, header.t1, header.t2, t3); + + this->timestamp_.CorrectStartPoint(offset); + + ara::log::LogDebug() << "Round trip time [ms]: " << round_trip_time + << " ,offset value [ms]: " << offset; + } } - mw::tinyNTP::ntpStruct hdr = val.value(); - hdr.t1 = now_ms; - ara::log::LogDebug() << "Success send response for callback"; - hdr.t2 = GetTimestamp(); - return srp::data::Convert2Vector::Conv(hdr); } @@ -73,49 +190,23 @@ int64_t NtpController::GetTimestamp() { } void NtpController::thread_loop(std::stop_token token) { + ara::log::LogError() << "Start NTP Sync."; + while (!token.stop_requested()) { - ara::log::LogDebug() << "Start NTP SYNC"; - srp::mw::tinyNTP::ntpStruct header; - header.t0 = GetTimestamp(); - auto buf = srp::data::Convert2Vector::Conv(header); - auto res = this->sock_.Transmit(masterIP, kRX_port, buf); - auto t3 = GetTimestamp(); - if (!res.has_value()) { - continue; - } - auto val = srp::data::Convert::Conv(res.value()); - if (!val.has_value()) { - continue; + auto master_opt = discovery_manager_.GetBestMaster(); + + if (!master_opt.has_value() || master_opt.value().ip == myIP) { + ara::log::LogError() << "Working as a server. Broadcasting Announce."; + SendAnnounce(); + } + else { + ara::log::LogError() << "Sending Sync to Master: " << master_opt.value().ip; + SendSyncRequest(master_opt.value().ip); } - srp::mw::tinyNTP::ntpStruct hdr = val.value(); - auto offset = CalculateOffset(hdr.t0, hdr.t1, hdr.t2, t3); - auto round_trip_time = CalculateRoundTripDelay(hdr.t0, hdr.t1, hdr.t2, t3); - this->timestamp_.CorrectStartPoint(offset); - ara::log::LogDebug() << "Round trip time [ms]: " << round_trip_time - << " ,offset value [ms]: " << offset; - core::condition::wait_for(std::chrono::milliseconds(kDelay_time), token); + + core::condition::wait_for(std::chrono::milliseconds(t_hb_ms_), token); } } -// bool NtpController::Init() { -// auto ip = readMyIP(); -// if (!ip.has_value()) { -// return false; -// } -// myIP = ip.value(); - -// if (myIP == masterIP) { -// sock_.Init(com::soc::SocketConfig{myIP, kRX_port, kTx_port}); -// this->sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, -// std::placeholders::_2, std::placeholders::_3)); -// sock_.StartRXThread(); -// } else { -// ntp_thread = std::jthread([this](std::stop_token token){ -// thread_loop(token); -// }); -// } -// return true; -// } - } // namespace tinyNTP } // namespace srp diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp index fd55311d..e3662710 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp @@ -15,7 +15,8 @@ #include #include #include // NOLINT -#include "communication-core/sockets/tcp_socket.h" +#include "communication-core/sockets/udp_socket.h" +#include "communication-core/sockets/udp_multicast_socket.h" #include "core/timestamp/timestamp_driver.hpp" #include "srp/mw/tinyNTP/ntpStruct.h" #include "mw/timestamp_mw/ntp/config/config_manager.hpp" @@ -25,20 +26,26 @@ namespace tinyNTP { class NtpController { private: - com::soc::StreamTCPSocket sock_; + com::soc::UdpSocket udp_sock_; + com::soc::UdpMulticastSocket multicast_sock_; + core::timestamp::TimestampMaster timestamp_; std::jthread ntp_thread; std::string myIP; uint8_t ntp_class_; uint32_t t_hb_ms_; - + int64_t last_t0_; DiscoveryManager discovery_manager_; + + void SendAnnounce(); + uint8_t EncodeSettings(uint8_t device_class, bool is_holdover, uint8_t msg_type); + void SendSyncRequest(const std::string& current_master_ip); public: bool Init(const NtpConfig& config); - std::optional readMyIP(); - std::vector socket_callback(const std::string& ip, const std::uint16_t& port, - const std::vector payload); + void socket_callback(const std::string& ip, const std::uint16_t& port, + const std::vector& payload); + void thread_loop(std::stop_token token); int64_t CalculateOffset(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3); uint64_t CalculateRoundTripDelay(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3); diff --git a/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp b/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp index 1f942040..408be1f1 100644 --- a/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp +++ b/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp @@ -14,6 +14,10 @@ namespace srp { namespace tinyNTP { +void DiscoveryManager::Init(const std::string& ip, uint8_t ntp_class, bool holdover) { + local_node_ = NodeInfo{ip, ntp_class, holdover}; +}; + /** * @brief Usuwa nieaktywne węzły z mapy urządzeń sieciowych * @@ -53,12 +57,12 @@ void DiscoveryManager::UpdateNode(const std::string& ip, uint8_t ntp_class, bool * @param local_node * @return std::optional - W przypadku gdy lokalny node jest najlepszym w sieci zwrócony optional jest pusty */ -std::optional DiscoveryManager::GetBestMaster(const NodeInfo& local_node) { +std::optional DiscoveryManager::GetBestMaster() { std::lock_guard lock(map_mutex_); RemoveExpiredNodes(); - NodeInfo best_neighbor = local_node; + NodeInfo best_neighbor = local_node_; for (const auto& [ip, node] : neighbors_) { if (node.ntp_class != best_neighbor.ntp_class) { @@ -77,7 +81,7 @@ std::optional DiscoveryManager::GetBestMaster(const NodeInfo& local_no } - if (best_neighbor.ip == local_node.ip) return std::nullopt; + if (best_neighbor.ip == local_node_.ip) return std::nullopt; return best_neighbor; } diff --git a/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp b/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp index a2690cf8..b891992a 100644 --- a/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp +++ b/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp @@ -41,16 +41,20 @@ struct NodeInfo { class DiscoveryManager { private: - void RemoveExpiredNodes(int timeout_seconds = TIMEOUT_SECONDS); - + NodeInfo local_node_; std::unordered_map neighbors_; // ip -> NodeInfo + + void RemoveExpiredNodes(int timeout_seconds = TIMEOUT_SECONDS); + mutable std::mutex map_mutex_; public: DiscoveryManager() = default; ~DiscoveryManager() = default; + void Init(const std::string& ip, uint8_t ntp_class, bool holdover); + void UpdateNode(const std::string& ip, uint8_t ntp_class, bool holdover); - std::optional GetBestMaster(const NodeInfo& local_node); + std::optional GetBestMaster(); }; } // namespace tinyNTP From 4590a45170e32e923bf0657cb63a33be8af8c660 Mon Sep 17 00:00:00 2001 From: Wiktor Muller Date: Tue, 2 Jun 2026 23:46:45 +0200 Subject: [PATCH 4/5] Resolving threads and cpp lint - Resolving conversations - Fixing code to pass cpplint tests - Fixing code to pass unit tests --- deployment/cpu/ec/ntp_config.json | 2 +- mw/timestamp_mw/ntp/config/config_manager.cpp | 45 +++++------- mw/timestamp_mw/ntp/config/config_manager.hpp | 6 +- .../ntp/controller/ntp_controller.cpp | 68 +++++++++---------- .../ntp/controller/ntp_controller.hpp | 9 +-- .../ntp/discovery/discovery_manager.cpp | 23 +++---- .../ntp/discovery/discovery_manager.hpp | 18 ++--- mw/timestamp_mw/ntp/ut/config_manager_test.cc | 21 ++++-- .../ntp/ut/discovery_manager_test.cc | 47 +++++++------ mw/timestamp_mw/service/timestamp_service.cpp | 4 +- 10 files changed, 120 insertions(+), 123 deletions(-) diff --git a/deployment/cpu/ec/ntp_config.json b/deployment/cpu/ec/ntp_config.json index 0392420a..8c013625 100644 --- a/deployment/cpu/ec/ntp_config.json +++ b/deployment/cpu/ec/ntp_config.json @@ -1,5 +1,5 @@ { "ip": "192.168.10.51", - "ntp_class": 3, + "ntp_class": 4, "T_hb_ms": 1000 } \ No newline at end of file diff --git a/mw/timestamp_mw/ntp/config/config_manager.cpp b/mw/timestamp_mw/ntp/config/config_manager.cpp index 7e6c1323..6cef3822 100644 --- a/mw/timestamp_mw/ntp/config/config_manager.cpp +++ b/mw/timestamp_mw/ntp/config/config_manager.cpp @@ -12,10 +12,14 @@ #include "mw/timestamp_mw/ntp/config/config_manager.hpp" #include "core/json/json_parser.h" #include "ara/log/log.h" -#include namespace srp { namespace tinyNTP { +namespace { + static constexpr auto kDefault_ip = "127.0.0.1"; + static constexpr auto kDefault_device_class = 7; + static constexpr auto kDefault_announce_interval = 1000; +} // namespace NtpConfig ConfigManager::LoadConfig(const std::string& filepath) { NtpConfig config; @@ -25,43 +29,28 @@ NtpConfig ConfigManager::LoadConfig(const std::string& filepath) { config.t_hb_ms = 1000; auto parser_opt = srp::core::json::JsonParser::Parser(filepath); - if (!parser_opt.has_value()) { - ara::log::LogError() << "Cannot open or parse config file: " << filepath - << ". Using fallback values.\n"; + ara::log::LogError() << "Cannot open or parse config file: " << filepath + << ". Using fallback values."; return config; } - const auto& parser = parser_opt.value(); - auto ip_opt = parser.GetString("ip"); - if (ip_opt.has_value()) { - config.ip = ip_opt.value(); - } else { - ara::log::LogWarn() << "Missing or invalid 'ip'. Using fallback ip.\n"; - } - - auto raw_json = parser.GetObject(); + auto ip = parser.GetString("ip"); + config.ip = ip.value_or(kDefault_ip); - if (raw_json.contains("ntp_class") && raw_json["ntp_class"].is_number_integer()) { - config.ntp_class = raw_json["ntp_class"]; - - if (config.ntp_class > 7) { - ara::log::LogWarn() << "Config ntp_class > 7. Using fallback class 7.\n"; - config.ntp_class = 7; - } - } else { - ara::log::LogWarn() << "Missing or invalid 'ntp_class'. Using fallback class 7.\n"; + auto ntp_class = parser.GetNumber("ntp_class"); + config.ntp_class = ntp_class.value_or(kDefault_device_class); + if (config.ntp_class > 7) { + ara::log::LogWarn() << "Config ntp_class > 7. Using fallback class 7."; + config.ntp_class = kDefault_device_class; } - if (raw_json.contains("T_hb_ms") && raw_json["T_hb_ms"].is_number_integer()) { - config.t_hb_ms = raw_json["T_hb_ms"]; - } else { - ara::log::LogWarn() << "Missing or invalid 'T_hb_ms'. Using fallback T_hb_ms.\n"; - } + auto t_hb_ms = parser.GetNumber("T_hb_ms"); + config.t_hb_ms = t_hb_ms.value_or(kDefault_announce_interval); return config; } } // namespace tinyNTP -} // namespace srp \ No newline at end of file +} // namespace srp diff --git a/mw/timestamp_mw/ntp/config/config_manager.hpp b/mw/timestamp_mw/ntp/config/config_manager.hpp index 79207121..6d2d70f9 100644 --- a/mw/timestamp_mw/ntp/config/config_manager.hpp +++ b/mw/timestamp_mw/ntp/config/config_manager.hpp @@ -18,7 +18,7 @@ namespace srp { namespace tinyNTP { -constexpr const char* CONFIG_FILEPATH = "/srp/opt/cpu_srp/ntp_config.json"; +constexpr auto kConfig_file_path = "/srp/opt/cpu_srp/ntp_config.json"; struct NtpConfig { std::string ip; @@ -34,10 +34,10 @@ class ConfigManager { * @param filepath Ścieżka do pliku konfiguracyjnego * @return NtpConfig Struktura z wczytanymi parametrami */ - static NtpConfig LoadConfig(const std::string& filepath = CONFIG_FILEPATH); + static NtpConfig LoadConfig(const std::string& filepath = kConfig_file_path); }; } // namespace tinyNTP } // namespace srp -#endif // MW_TIMESTAMP_MW_NTP_CONFIG_CONFIG_MANAGER_HPP_ \ No newline at end of file +#endif // MW_TIMESTAMP_MW_NTP_CONFIG_CONFIG_MANAGER_HPP_ diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp index d9618a8f..d99e8a77 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp @@ -10,9 +10,9 @@ */ #include "mw/timestamp_mw/ntp/controller/ntp_controller.hpp" -#include "mw/timestamp_mw/ntp/config/config_manager.hpp" #include #include +#include "mw/timestamp_mw/ntp/config/config_manager.hpp" #include "core/common/condition.h" #include "core/json/json_parser.h" #include "ara/log/log.h" @@ -64,41 +64,41 @@ bool NtpController::Init(const NtpConfig& config) { return true; } -int64_t NtpController::CalculateOffset(const int64_t T0, const int64_t T1, - const int64_t T2, const int64_t T3) { +int64_t NtpController::CalculateOffset(const int64_t& T0, const int64_t& T1, + const int64_t& T2, const int64_t& T3) { return ((T1 - T0) + (T2 - T3)) / 2; } -uint64_t NtpController::CalculateRoundTripDelay(const int64_t T0, const int64_t T1, - const int64_t T2, const int64_t T3) { +uint64_t NtpController::CalculateRoundTripDelay(const int64_t& T0, const int64_t& T1, + const int64_t& T2, const int64_t& T3) { return static_cast((T3 - T0) - (T2 - T1)); } uint8_t NtpController::EncodeSettings(uint8_t device_class, bool is_holdover, uint8_t msg_type) { uint8_t settings = 0; - + // Bity 0-2: Klasa urządzenia - settings |= (device_class & 0x07); - + settings |= (device_class & 0x07); + // Bit 3: Holdover if (is_holdover) { settings |= (1 << 3); } - + // Bity 4-5: Version // Bit 6: msg_type (0 dla Unicast, 1 dla Announce) if (msg_type == 1) { settings |= (1 << 6); } - + // Bit 7: Reserved - + return settings; } void NtpController::SendAnnounce() { srp::mw::tinyNTP::ntpStruct frame; - + /** * @todo: Implement holdover */ @@ -119,18 +119,17 @@ void NtpController::SendSyncRequest(const std::string& current_master_ip) { header.settings = EncodeSettings(ntp_class_, false, 0); header.t0 = GetTimestamp(); header.t1 = 0; header.t2 = 0; header.t3 = 0; - + last_t0_ = header.t0; auto buf = srp::data::Convert2Vector::Conv(header); - - this->udp_sock_.Transmit(current_master_ip, kRX_Tx_udp_port, buf); + + this->udp_sock_.Transmit(current_master_ip, kRX_Tx_udp_port, buf); } -void NtpController::socket_callback(const std::string& ip, - const uint16_t& port, +void NtpController::socket_callback(const std::string& ip, + const uint16_t& port, const std::vector& payload) { - int64_t now_ms = GetTimestamp(); if (payload.size() != kHeader_size) { @@ -141,9 +140,9 @@ void NtpController::socket_callback(const std::string& ip, // Zabezpieczenie przed odebraniem swojego announce if (ip == myIP) { ara::log::LogDebug() << "Rejecting own packet."; - return; + return; } - + auto val = srp::data::Convert::Conv(payload); if (!val.has_value()) return; srp::mw::tinyNTP::ntpStruct header = val.value(); @@ -152,32 +151,30 @@ void NtpController::socket_callback(const std::string& ip, uint8_t sender_class = header.settings & 0x07; bool holdover = (header.settings >> 3) & 0x01; - if (msg_type == 1) { // Announce + if (msg_type == 1) { // Announce ara::log::LogDebug() << "Updating node with ip: " << ip; discovery_manager_.UpdateNode(ip, sender_class, holdover); - } - else if (msg_type == 0) { // Sync + } else if (msg_type == 0) { // Sync auto master_opt = discovery_manager_.GetBestMaster(); bool is_server = (!master_opt.has_value() || master_opt.value().ip == myIP); if (is_server) { - header.t1 = now_ms; + header.t1 = now_ms; header.t2 = GetTimestamp(); - + auto buf = srp::data::Convert2Vector::Conv(header); - + udp_sock_.Transmit(ip, kRX_Tx_udp_port, buf); - + ara::log::LogDebug() << "Sent sync response to " << ip; - } - else { + } else { int64_t t3 = now_ms; - + auto offset = CalculateOffset(header.t0, header.t1, header.t2, t3); auto round_trip_time = CalculateRoundTripDelay(header.t0, header.t1, header.t2, t3); - + this->timestamp_.CorrectStartPoint(offset); - + ara::log::LogDebug() << "Round trip time [ms]: " << round_trip_time << " ,offset value [ms]: " << offset; } @@ -196,11 +193,10 @@ void NtpController::thread_loop(std::stop_token token) { auto master_opt = discovery_manager_.GetBestMaster(); if (!master_opt.has_value() || master_opt.value().ip == myIP) { - ara::log::LogError() << "Working as a server. Broadcasting Announce."; + ara::log::LogDebug() << "Working as a server. Broadcasting Announce."; SendAnnounce(); - } - else { - ara::log::LogError() << "Sending Sync to Master: " << master_opt.value().ip; + } else { + ara::log::LogDebug() << "Sending Sync to Master: " << master_opt.value().ip; SendSyncRequest(master_opt.value().ip); } diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp index e3662710..aabb6e9c 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp @@ -40,15 +40,16 @@ class NtpController { void SendAnnounce(); uint8_t EncodeSettings(uint8_t device_class, bool is_holdover, uint8_t msg_type); void SendSyncRequest(const std::string& current_master_ip); + public: bool Init(const NtpConfig& config); void socket_callback(const std::string& ip, const std::uint16_t& port, - const std::vector& payload); - + const std::vector& payload); + void thread_loop(std::stop_token token); - int64_t CalculateOffset(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3); - uint64_t CalculateRoundTripDelay(const int64_t T0, const int64_t T1, const int64_t T2, const int64_t T3); + int64_t CalculateOffset(const int64_t& T0, const int64_t& T1, const int64_t& T2, const int64_t& T3); + uint64_t CalculateRoundTripDelay(const int64_t& T0, const int64_t& T1, const int64_t& T2, const int64_t& T3); int64_t GetTimestamp(); }; diff --git a/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp b/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp index 408be1f1..a1404659 100644 --- a/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp +++ b/mw/timestamp_mw/ntp/discovery/discovery_manager.cpp @@ -14,9 +14,9 @@ namespace srp { namespace tinyNTP { -void DiscoveryManager::Init(const std::string& ip, uint8_t ntp_class, bool holdover) { +void DiscoveryManager::Init(const std::string& ip, const uint8_t ntp_class, const bool holdover) { local_node_ = NodeInfo{ip, ntp_class, holdover}; -}; +} /** * @brief Usuwa nieaktywne węzły z mapy urządzeń sieciowych @@ -26,7 +26,7 @@ void DiscoveryManager::Init(const std::string& ip, uint8_t ntp_class, bool holdo * * @param timeout_seconds Czas wygaśnięcia sąsiada */ -void DiscoveryManager::RemoveExpiredNodes(int timeout_seconds) { +void DiscoveryManager::RemoveExpiredNodes(const int& timeout_seconds) { const auto now = std::chrono::steady_clock::now(); for (auto it = neighbors_.begin(); it != neighbors_.end(); ) { @@ -40,11 +40,11 @@ void DiscoveryManager::RemoveExpiredNodes(int timeout_seconds) { } } -void DiscoveryManager::UpdateNode(const std::string& ip, uint8_t ntp_class, bool holdover) { +void DiscoveryManager::UpdateNode(const std::string& ip, const uint8_t ntp_class, const bool holdover) { std::lock_guard lock(map_mutex_); NodeInfo& node = neighbors_[ip]; - + node.ip = ip; node.ntp_class = ntp_class; node.holdover = holdover; @@ -69,21 +69,18 @@ std::optional DiscoveryManager::GetBestMaster() { if (node.ntp_class < best_neighbor.ntp_class) { best_neighbor = node; } - } - else if (node.holdover != best_neighbor.holdover) { + } else if (node.holdover != best_neighbor.holdover) { if (!node.holdover) { best_neighbor = node; } - } - else if (node.ip < best_neighbor.ip) { + } else if (node.ip < best_neighbor.ip) { best_neighbor = node; - } - + } } if (best_neighbor.ip == local_node_.ip) return std::nullopt; return best_neighbor; } -} // namespace tinyNTP -} // namespace srp \ No newline at end of file +} // namespace tinyNTP +} // namespace srp diff --git a/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp b/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp index b891992a..613f9bea 100644 --- a/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp +++ b/mw/timestamp_mw/ntp/discovery/discovery_manager.hpp @@ -14,8 +14,8 @@ #include #include -#include -#include +#include // NOLINT +#include // NOLINT #include #include @@ -40,24 +40,24 @@ struct NodeInfo { }; class DiscoveryManager { -private: + private: NodeInfo local_node_; - std::unordered_map neighbors_; // ip -> NodeInfo + std::unordered_map neighbors_; // ip -> NodeInfo + + void RemoveExpiredNodes(const int& timeout_seconds = TIMEOUT_SECONDS); - void RemoveExpiredNodes(int timeout_seconds = TIMEOUT_SECONDS); - mutable std::mutex map_mutex_; public: DiscoveryManager() = default; ~DiscoveryManager() = default; - void Init(const std::string& ip, uint8_t ntp_class, bool holdover); + void Init(const std::string& ip, const uint8_t ntp_class, const bool holdover); - void UpdateNode(const std::string& ip, uint8_t ntp_class, bool holdover); + void UpdateNode(const std::string& ip, const uint8_t ntp_class, const bool holdover); std::optional GetBestMaster(); }; } // namespace tinyNTP } // namespace srp -#endif // MW_TIMESTAMP_MW_NTP_DISCOVERY_DISCOVERY_MANAGER_HPP_ \ No newline at end of file +#endif // MW_TIMESTAMP_MW_NTP_DISCOVERY_DISCOVERY_MANAGER_HPP_ diff --git a/mw/timestamp_mw/ntp/ut/config_manager_test.cc b/mw/timestamp_mw/ntp/ut/config_manager_test.cc index 51ba840f..0d97f56e 100644 --- a/mw/timestamp_mw/ntp/ut/config_manager_test.cc +++ b/mw/timestamp_mw/ntp/ut/config_manager_test.cc @@ -1,10 +1,21 @@ +/** + * @file config_manager_test.cc + * @author Wiktor Müller (wiktor.muller8@gmail.com) + * @brief + * @version 0.1 + * @date 2026-05-04 + * + * @copyright Copyright (c) 2025 + * + */ + #include #include #include #include "mw/timestamp_mw/ntp/config/config_manager.hpp" -using namespace srp::tinyNTP; +using srp::tinyNTP::ConfigManager; class ConfigManagerTest : public ::testing::Test { protected: @@ -17,7 +28,7 @@ class ConfigManagerTest : public ::testing::Test { } void TearDown() override { - std::remove(temp_filepath.c_str()); // Usuwamy plik testowy + std::remove(temp_filepath.c_str()); // Usuwamy plik testowy } }; @@ -60,7 +71,7 @@ TEST_F(ConfigManagerTest, UsesFallbackOnInvalidJsonSyntax) { TEST_F(ConfigManagerTest, ForcesFallbackClassIfOutOfBounds) { CreateJsonFile(R"({ "ip": "10.0.0.2", - "ntp_class": 15, + "ntp_class": 15, "T_hb_ms": 200 })"); @@ -74,11 +85,11 @@ TEST_F(ConfigManagerTest, ForcesFallbackClassIfOutOfBounds) { TEST_F(ConfigManagerTest, HandlesMissingFieldsGracefully) { CreateJsonFile(R"({ "ip": "172.16.0.5" - })"); // Brak ntp_class i T_hb_ms + })"); // Brak ntp_class i T_hb_ms auto config = ConfigManager::LoadConfig(temp_filepath); EXPECT_EQ(config.ip, "172.16.0.5"); EXPECT_EQ(config.ntp_class, 7); EXPECT_EQ(config.t_hb_ms, 1000); -} \ No newline at end of file +} diff --git a/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc b/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc index fa60d036..2b8951e1 100644 --- a/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc +++ b/mw/timestamp_mw/ntp/ut/discovery_manager_test.cc @@ -12,66 +12,69 @@ #include #include #include -#include +#include // NOLINT #include "mw/timestamp_mw/ntp/discovery/discovery_manager.hpp" -using namespace srp::tinyNTP; +using srp::tinyNTP::DiscoveryManager; +using srp::tinyNTP::NodeInfo; class DiscoveryManagerTest : public ::testing::Test { protected: DiscoveryManager discoveryManager; - + NodeInfo default_local_node; void SetUp() override { default_local_node.ip = "192.168.0.50"; default_local_node.ntp_class = 5; default_local_node.holdover = true; - default_local_node.last_seen = std::chrono::steady_clock::now(); + default_local_node.last_seen = std::chrono::steady_clock::now(); + + discoveryManager.Init(default_local_node.ip, default_local_node.ntp_class, default_local_node.holdover); } }; TEST_F(DiscoveryManagerTest, LocalNodeIsBestWhenNetworkEmpty) { - auto best_master = discoveryManager.GetBestMaster(default_local_node); - + auto best_master = discoveryManager.GetBestMaster(); + EXPECT_FALSE(best_master.has_value()); } TEST_F(DiscoveryManagerTest, ExternalNodeWinsByClass) { discoveryManager.UpdateNode("192.168.0.100", 2, true); - - auto best_master = discoveryManager.GetBestMaster(default_local_node); - + + auto best_master = discoveryManager.GetBestMaster(); + ASSERT_TRUE(best_master.has_value()); EXPECT_EQ(best_master->ip, "192.168.0.100"); } TEST_F(DiscoveryManagerTest, LocalNodeWinsByClass) { discoveryManager.UpdateNode("192.168.0.100", 7, true); - - auto best_master = discoveryManager.GetBestMaster(default_local_node); - + + auto best_master = discoveryManager.GetBestMaster(); + ASSERT_FALSE(best_master.has_value()); } TEST_F(DiscoveryManagerTest, ExternalNodeWinsByHoldover) { discoveryManager.UpdateNode("192.168.0.100", 5, false); - - auto best_master = discoveryManager.GetBestMaster(default_local_node); - + + auto best_master = discoveryManager.GetBestMaster(); + ASSERT_TRUE(best_master.has_value()); EXPECT_EQ(best_master->ip, "192.168.0.100"); } TEST_F(DiscoveryManagerTest, ExternalNodeWinsByIpTieBreaker) { discoveryManager.UpdateNode("192.168.0.10", 5, true); - auto best_master = discoveryManager.GetBestMaster(default_local_node); - + auto best_master = discoveryManager.GetBestMaster(); + ASSERT_TRUE(best_master.has_value()); EXPECT_EQ(best_master->ip, "192.168.0.10"); discoveryManager.UpdateNode("10.168.0.10", 5, true); - best_master = discoveryManager.GetBestMaster(default_local_node); + best_master = discoveryManager.GetBestMaster(); ASSERT_TRUE(best_master.has_value()); EXPECT_EQ(best_master->ip, "10.168.0.10"); @@ -79,8 +82,8 @@ TEST_F(DiscoveryManagerTest, ExternalNodeWinsByIpTieBreaker) { TEST_F(DiscoveryManagerTest, LocalNodeDefeatsLowerIpWithBetterClass) { discoveryManager.UpdateNode("10.0.0.1", 7, false); - - auto best_master = discoveryManager.GetBestMaster(default_local_node); - + + auto best_master = discoveryManager.GetBestMaster(); + EXPECT_FALSE(best_master.has_value()); -} \ No newline at end of file +} diff --git a/mw/timestamp_mw/service/timestamp_service.cpp b/mw/timestamp_mw/service/timestamp_service.cpp index eef68b75..a80c49ec 100644 --- a/mw/timestamp_mw/service/timestamp_service.cpp +++ b/mw/timestamp_mw/service/timestamp_service.cpp @@ -24,12 +24,12 @@ int TimestampService::Run(const std::stop_token& token) { int TimestampService::Initialize(const std::map parms) { auto config = srp::tinyNTP::ConfigManager::LoadConfig(); - + if (!this->ntp_controller.Init(config)) { ara::log::LogError() << "NTP controller initialization failed."; return -1; } - + ara::log::LogInfo() << "Init completed"; return 0; } From 9124f76256007288723e80f3438d20cf2b7b54e4 Mon Sep 17 00:00:00 2001 From: Wiktor Muller Date: Wed, 3 Jun 2026 12:36:45 +0200 Subject: [PATCH 5/5] Separating sockets callbacks - separate callback functions for socket - safety checks for invalid message types and rapid change of function between server and client mode - header setting encoding test --- .../ntp/controller/ntp_controller.cpp | 101 ++++++++++++------ .../ntp/controller/ntp_controller.hpp | 7 +- mw/timestamp_mw/ntp/ut/controler_test.cc | 9 ++ 3 files changed, 83 insertions(+), 34 deletions(-) diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp index d99e8a77..6c85c68a 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.cpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.cpp @@ -45,9 +45,9 @@ bool NtpController::Init(const NtpConfig& config) { ara::log::LogInfo() << "Multicast socket initialized!"; } - this->udp_sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, + this->udp_sock_.SetRXCallback(std::bind(&NtpController::udp_socket_callback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - this->multicast_sock_.SetRXCallback(std::bind(&NtpController::socket_callback, this, std::placeholders::_1, + this->multicast_sock_.SetRXCallback(std::bind(&NtpController::multicast_socket_callback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); udp_sock_.StartRXThread(); @@ -127,60 +127,97 @@ void NtpController::SendSyncRequest(const std::string& current_master_ip) { this->udp_sock_.Transmit(current_master_ip, kRX_Tx_udp_port, buf); } -void NtpController::socket_callback(const std::string& ip, - const uint16_t& port, - const std::vector& payload) { - int64_t now_ms = GetTimestamp(); - +std::optional NtpController::ParseAndValidatePayload( + const std::vector& payload, const std::string& ip) { if (payload.size() != kHeader_size) { ara::log::LogError() << "Invalid payload size! Rejecting the packet."; - return; + return std::nullopt; } - // Zabezpieczenie przed odebraniem swojego announce if (ip == myIP) { ara::log::LogDebug() << "Rejecting own packet."; - return; + return std::nullopt; } - auto val = srp::data::Convert::Conv(payload); + return srp::data::Convert::Conv(payload); +} + +void NtpController::udp_socket_callback(const std::string& ip, + const uint16_t& port, + const std::vector& payload) { + int64_t now_ms = GetTimestamp(); + + auto val = ParseAndValidatePayload(payload, ip); if (!val.has_value()) return; srp::mw::tinyNTP::ntpStruct header = val.value(); uint8_t msg_type = (header.settings >> 6) & 0x01; - uint8_t sender_class = header.settings & 0x07; - bool holdover = (header.settings >> 3) & 0x01; - if (msg_type == 1) { // Announce - ara::log::LogDebug() << "Updating node with ip: " << ip; - discovery_manager_.UpdateNode(ip, sender_class, holdover); - } else if (msg_type == 0) { // Sync - auto master_opt = discovery_manager_.GetBestMaster(); - bool is_server = (!master_opt.has_value() || master_opt.value().ip == myIP); + if (msg_type != 0) { + ara::log::LogWarn() << "Received non-Sync message on unicast socket from: " << ip; + return; + } - if (is_server) { - header.t1 = now_ms; - header.t2 = GetTimestamp(); + auto master_opt = discovery_manager_.GetBestMaster(); + bool is_server = (!master_opt.has_value() || master_opt.value().ip == myIP); - auto buf = srp::data::Convert2Vector::Conv(header); + if (is_server) { + if (header.t1 != 0 || header.t2 != 0) { + ara::log::LogWarn() << "Received Sync response, but I am the server now. Dropping."; + return; + } - udp_sock_.Transmit(ip, kRX_Tx_udp_port, buf); + header.t1 = now_ms; + header.t2 = GetTimestamp(); - ara::log::LogDebug() << "Sent sync response to " << ip; - } else { - int64_t t3 = now_ms; + auto buf = srp::data::Convert2Vector::Conv(header); - auto offset = CalculateOffset(header.t0, header.t1, header.t2, t3); - auto round_trip_time = CalculateRoundTripDelay(header.t0, header.t1, header.t2, t3); + udp_sock_.Transmit(ip, kRX_Tx_udp_port, buf); - this->timestamp_.CorrectStartPoint(offset); + ara::log::LogDebug() << "Sent sync response to " << ip; + } else { + if (header.t1 == 0 || header.t2 == 0) { + ara::log::LogWarn() << "Received Sync request, but I am a client now. Dropping."; + return; + } - ara::log::LogDebug() << "Round trip time [ms]: " << round_trip_time - << " ,offset value [ms]: " << offset; + if (header.t0 != last_t0_) { + ara::log::LogWarn() << "Received Sync response for an old or unknown request. Dropping."; + return; } + + int64_t t3 = now_ms; + + auto offset = CalculateOffset(header.t0, header.t1, header.t2, t3); + auto round_trip_time = CalculateRoundTripDelay(header.t0, header.t1, header.t2, t3); + + this->timestamp_.CorrectStartPoint(offset); + + ara::log::LogDebug() << "Round trip time [ms]: " << round_trip_time + << " ,offset value [ms]: " << offset; } } +void NtpController::multicast_socket_callback(const std::string& ip, + const uint16_t& port, + const std::vector& payload) { + auto val = ParseAndValidatePayload(payload, ip); + if (!val.has_value()) return; + srp::mw::tinyNTP::ntpStruct header = val.value(); + + uint8_t msg_type = (header.settings >> 6) & 0x01; + + if (msg_type != 1) { + ara::log::LogWarn() << "Received non-Announce message on multicast socket from: " << ip; + return; + } + + uint8_t sender_class = header.settings & 0x07; + bool holdover = (header.settings >> 3) & 0x01; + + ara::log::LogDebug() << "Updating node with ip: " << ip; + discovery_manager_.UpdateNode(ip, sender_class, holdover); +} int64_t NtpController::GetTimestamp() { return this->timestamp_.GetNewTimeStamp(); diff --git a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp index aabb6e9c..33f68336 100644 --- a/mw/timestamp_mw/ntp/controller/ntp_controller.hpp +++ b/mw/timestamp_mw/ntp/controller/ntp_controller.hpp @@ -38,16 +38,19 @@ class NtpController { DiscoveryManager discovery_manager_; void SendAnnounce(); - uint8_t EncodeSettings(uint8_t device_class, bool is_holdover, uint8_t msg_type); void SendSyncRequest(const std::string& current_master_ip); + std::optional ParseAndValidatePayload(const std::vector& payload, const std::string& ip); public: bool Init(const NtpConfig& config); - void socket_callback(const std::string& ip, const std::uint16_t& port, + void udp_socket_callback(const std::string& ip, const std::uint16_t& port, const std::vector& payload); + void multicast_socket_callback(const std::string& ip, const std::uint16_t& port, + const std::vector& payload); void thread_loop(std::stop_token token); + uint8_t EncodeSettings(uint8_t device_class, bool is_holdover, uint8_t msg_type); int64_t CalculateOffset(const int64_t& T0, const int64_t& T1, const int64_t& T2, const int64_t& T3); uint64_t CalculateRoundTripDelay(const int64_t& T0, const int64_t& T1, const int64_t& T2, const int64_t& T3); int64_t GetTimestamp(); diff --git a/mw/timestamp_mw/ntp/ut/controler_test.cc b/mw/timestamp_mw/ntp/ut/controler_test.cc index f762a111..dec65ef1 100644 --- a/mw/timestamp_mw/ntp/ut/controler_test.cc +++ b/mw/timestamp_mw/ntp/ut/controler_test.cc @@ -36,3 +36,12 @@ TEST_F(NtpControllerTest, CalculateRoundTripDelayTest) { int64_t expected_delay = 1500; // (3000 - 1000) - (2000 - 1500) = 1500 EXPECT_EQ(ntpController.CalculateRoundTripDelay(T0, T1, T2, T3), expected_delay); } + +TEST_F(NtpControllerTest, EncodeSettingsTest) { + uint8_t device_class = 3; + bool is_holdover = true; + uint8_t msg_type = 1; + + uint8_t expected_encode = 0b01001011; + EXPECT_EQ(ntpController.EncodeSettings(device_class, is_holdover, msg_type), expected_encode); +}