From 302a43e0a9a6c324c3b8fad8c034890031e0b788 Mon Sep 17 00:00:00 2001 From: Valery Lavrov Date: Mon, 27 Apr 2026 12:28:28 +0200 Subject: [PATCH 1/4] Add the new score::time API --- examples/time/BUILD | 33 +++ examples/time/hpls_time/BUILD | 64 +++++ examples/time/hpls_time/hpls_time_handler.h | 67 +++++ .../time/hpls_time/hpls_time_handler_test.cpp | 79 ++++++ examples/time/hpls_time/main.cpp | 85 ++++++ examples/time/steady_time/BUILD | 64 +++++ examples/time/steady_time/main.cpp | 85 ++++++ .../time/steady_time/steady_time_handler.h | 67 +++++ .../steady_time/steady_time_handler_test.cpp | 79 ++++++ examples/time/system_time/BUILD | 63 ++++ examples/time/system_time/main.cpp | 85 ++++++ .../time/system_time/system_time_handler.h | 67 +++++ .../system_time/system_time_handler_test.cpp | 81 ++++++ examples/time/vehicle_time/BUILD | 74 +++++ examples/time/vehicle_time/main.cpp | 93 ++++++ .../time/vehicle_time/vehicle_time_handler.h | 97 +++++++ .../vehicle_time_handler_test.cpp | 133 +++++++++ score/TimeDaemon/code/ipc/BUILD | 6 +- score/time/BUILD | 46 +++ .../time/HighPrecisionLocalSteadyClock/BUILD | 6 + score/time/SynchronizedVehicleTime/BUILD | 6 + score/time/clock/BUILD | 54 ++++ score/time/clock/availability_hook.h | 62 ++++ score/time/clock/clock.h | 188 ++++++++++++ score/time/clock/clock_override_guard.h | 84 ++++++ score/time/clock/clock_snapshot.h | 72 +++++ score/time/clock/clock_status.h | 160 +++++++++++ score/time/clock/clock_status_test.cpp | 155 ++++++++++ score/time/clock/clock_traits.h | 42 +++ score/time/clock/no_status.h | 34 +++ score/time/clock/subscription_hook.h | 40 +++ score/time/common/BUILD | 10 +- score/time/common/time_base_status_test.cpp | 33 +-- score/time/hpls_time/BUILD | 131 +++++++++ score/time/hpls_time/details/BUILD | 33 +++ score/time/hpls_time/details/qtime/BUILD | 100 +++++++ .../hpls_time/details/qtime/factory_test.cpp | 56 ++++ .../hpls_time/details/qtime/hpls_qclock.cpp | 73 +++++ .../hpls_time/details/qtime/hpls_qclock.h | 59 ++++ .../details/qtime/hpls_qclock_test.cpp | 178 ++++++++++++ .../hpls_time/details/qtime/tick_provider.cpp | 34 +++ .../hpls_time/details/qtime/tick_provider.h | 37 +++ .../details/qtime/tick_provider_mock.cpp | 36 +++ .../details/qtime/tick_provider_mock.h | 76 +++++ .../details/qtime/tick_provider_test.cpp | 43 +++ score/time/hpls_time/details/stub_impl/BUILD | 30 ++ .../details/stub_impl/hpls_clock_impl.cpp | 30 ++ .../details/stub_impl/hpls_clock_impl.h | 56 ++++ .../time/hpls_time/details/system_clock/BUILD | 60 ++++ .../details/system_clock/hpls_time_impl.cpp | 50 ++++ .../details/system_clock/hpls_time_impl.h | 53 ++++ .../system_clock/hpls_time_impl_test.cpp | 57 ++++ score/time/hpls_time/hpls_clock.cpp | 27 ++ score/time/hpls_time/hpls_clock.h | 48 ++++ score/time/hpls_time/hpls_clock_iface.h | 39 +++ score/time/hpls_time/hpls_clock_test.cpp | 76 +++++ score/time/hpls_time/hpls_time.h | 34 +++ score/time/hpls_time/hpls_time_mock.h | 56 ++++ score/time/hpls_time/test/benchmark/BUILD | 26 ++ .../hpls_time/test/benchmark/benchmark.cpp | 50 ++++ score/time/ptp/BUILD | 68 +++++ score/time/ptp/local_ptp_device_timer.h | 38 +++ score/time/ptp/master_ptp_device_timer.h | 39 +++ score/time/ptp/pdelay_measurement_data.h | 103 +++++++ .../time/ptp/pdelay_measurement_data_test.cpp | 77 +++++ score/time/ptp/port_identity.h | 53 ++++ score/time/ptp/time_slave_sync_data.h | 97 +++++++ score/time/ptp/time_slave_sync_data_test.cpp | 73 +++++ score/time/steady_time/BUILD | 117 ++++++++ score/time/steady_time/details/BUILD | 23 ++ .../details/steady_time_impl/BUILD | 55 ++++ .../steady_time_impl/steady_clock_impl.cpp | 30 ++ .../steady_time_impl/steady_clock_impl.h | 54 ++++ .../steady_clock_impl_test.cpp | 64 +++++ .../time/steady_time/details/stub_impl/BUILD | 31 ++ .../details/stub_impl/steady_clock_impl.cpp | 30 ++ .../details/stub_impl/steady_clock_impl.h | 54 ++++ score/time/steady_time/steady_clock.cpp | 28 ++ score/time/steady_time/steady_clock.h | 48 ++++ score/time/steady_time/steady_clock_iface.h | 45 +++ score/time/steady_time/steady_clock_mock.h | 56 ++++ score/time/steady_time/steady_clock_test.cpp | 120 ++++++++ score/time/system_time/BUILD | 117 ++++++++ score/time/system_time/details/BUILD | 23 ++ .../time/system_time/details/stub_impl/BUILD | 31 ++ .../details/stub_impl/system_clock_impl.cpp | 30 ++ .../details/stub_impl/system_clock_impl.h | 54 ++++ .../details/system_time_impl/BUILD | 55 ++++ .../system_time_impl/system_clock_impl.cpp | 30 ++ .../system_time_impl/system_clock_impl.h | 54 ++++ .../system_clock_impl_test.cpp | 66 +++++ score/time/system_time/system_clock.cpp | 28 ++ score/time/system_time/system_clock.h | 48 ++++ score/time/system_time/system_clock_iface.h | 49 ++++ score/time/system_time/system_clock_mock.h | 56 ++++ score/time/system_time/system_clock_test.cpp | 120 ++++++++ score/time/vehicle_time/BUILD | 140 +++++++++ score/time/vehicle_time/details/BUILD | 29 ++ .../vehicle_time/details/logging_contexts.h | 31 ++ .../time/vehicle_time/details/stub_impl/BUILD | 27 ++ .../details/stub_impl/vehicle_clock_impl.cpp | 30 ++ .../details/stub_impl/vehicle_clock_impl.h | 79 ++++++ score/time/vehicle_time/details/td_impl/BUILD | 65 +++++ .../details/td_impl/vehicle_clock_impl.cpp | 169 +++++++++++ .../details/td_impl/vehicle_clock_impl.h | 97 +++++++ .../td_impl/vehicle_clock_impl_test.cpp | 268 ++++++++++++++++++ score/time/vehicle_time/vehicle_clock.cpp | 69 +++++ score/time/vehicle_time/vehicle_clock.h | 88 ++++++ score/time/vehicle_time/vehicle_clock_iface.h | 72 +++++ .../time/vehicle_time/vehicle_clock_test.cpp | 210 ++++++++++++++ score/time/vehicle_time/vehicle_time.h | 168 +++++++++++ score/time/vehicle_time/vehicle_time_mock.h | 78 +++++ .../vehicle_time/vehicle_time_status_test.cpp | 169 +++++++++++ 113 files changed, 7684 insertions(+), 31 deletions(-) create mode 100644 examples/time/BUILD create mode 100644 examples/time/hpls_time/BUILD create mode 100644 examples/time/hpls_time/hpls_time_handler.h create mode 100644 examples/time/hpls_time/hpls_time_handler_test.cpp create mode 100644 examples/time/hpls_time/main.cpp create mode 100644 examples/time/steady_time/BUILD create mode 100644 examples/time/steady_time/main.cpp create mode 100644 examples/time/steady_time/steady_time_handler.h create mode 100644 examples/time/steady_time/steady_time_handler_test.cpp create mode 100644 examples/time/system_time/BUILD create mode 100644 examples/time/system_time/main.cpp create mode 100644 examples/time/system_time/system_time_handler.h create mode 100644 examples/time/system_time/system_time_handler_test.cpp create mode 100644 examples/time/vehicle_time/BUILD create mode 100644 examples/time/vehicle_time/main.cpp create mode 100644 examples/time/vehicle_time/vehicle_time_handler.h create mode 100644 examples/time/vehicle_time/vehicle_time_handler_test.cpp create mode 100644 score/time/BUILD create mode 100644 score/time/clock/BUILD create mode 100644 score/time/clock/availability_hook.h create mode 100644 score/time/clock/clock.h create mode 100644 score/time/clock/clock_override_guard.h create mode 100644 score/time/clock/clock_snapshot.h create mode 100644 score/time/clock/clock_status.h create mode 100644 score/time/clock/clock_status_test.cpp create mode 100644 score/time/clock/clock_traits.h create mode 100644 score/time/clock/no_status.h create mode 100644 score/time/clock/subscription_hook.h create mode 100644 score/time/hpls_time/BUILD create mode 100644 score/time/hpls_time/details/BUILD create mode 100644 score/time/hpls_time/details/qtime/BUILD create mode 100644 score/time/hpls_time/details/qtime/factory_test.cpp create mode 100644 score/time/hpls_time/details/qtime/hpls_qclock.cpp create mode 100644 score/time/hpls_time/details/qtime/hpls_qclock.h create mode 100644 score/time/hpls_time/details/qtime/hpls_qclock_test.cpp create mode 100644 score/time/hpls_time/details/qtime/tick_provider.cpp create mode 100644 score/time/hpls_time/details/qtime/tick_provider.h create mode 100644 score/time/hpls_time/details/qtime/tick_provider_mock.cpp create mode 100644 score/time/hpls_time/details/qtime/tick_provider_mock.h create mode 100644 score/time/hpls_time/details/qtime/tick_provider_test.cpp create mode 100644 score/time/hpls_time/details/stub_impl/BUILD create mode 100644 score/time/hpls_time/details/stub_impl/hpls_clock_impl.cpp create mode 100644 score/time/hpls_time/details/stub_impl/hpls_clock_impl.h create mode 100644 score/time/hpls_time/details/system_clock/BUILD create mode 100644 score/time/hpls_time/details/system_clock/hpls_time_impl.cpp create mode 100644 score/time/hpls_time/details/system_clock/hpls_time_impl.h create mode 100644 score/time/hpls_time/details/system_clock/hpls_time_impl_test.cpp create mode 100644 score/time/hpls_time/hpls_clock.cpp create mode 100644 score/time/hpls_time/hpls_clock.h create mode 100644 score/time/hpls_time/hpls_clock_iface.h create mode 100644 score/time/hpls_time/hpls_clock_test.cpp create mode 100644 score/time/hpls_time/hpls_time.h create mode 100644 score/time/hpls_time/hpls_time_mock.h create mode 100644 score/time/hpls_time/test/benchmark/BUILD create mode 100644 score/time/hpls_time/test/benchmark/benchmark.cpp create mode 100644 score/time/ptp/BUILD create mode 100644 score/time/ptp/local_ptp_device_timer.h create mode 100644 score/time/ptp/master_ptp_device_timer.h create mode 100644 score/time/ptp/pdelay_measurement_data.h create mode 100644 score/time/ptp/pdelay_measurement_data_test.cpp create mode 100644 score/time/ptp/port_identity.h create mode 100644 score/time/ptp/time_slave_sync_data.h create mode 100644 score/time/ptp/time_slave_sync_data_test.cpp create mode 100644 score/time/steady_time/BUILD create mode 100644 score/time/steady_time/details/BUILD create mode 100644 score/time/steady_time/details/steady_time_impl/BUILD create mode 100644 score/time/steady_time/details/steady_time_impl/steady_clock_impl.cpp create mode 100644 score/time/steady_time/details/steady_time_impl/steady_clock_impl.h create mode 100644 score/time/steady_time/details/steady_time_impl/steady_clock_impl_test.cpp create mode 100644 score/time/steady_time/details/stub_impl/BUILD create mode 100644 score/time/steady_time/details/stub_impl/steady_clock_impl.cpp create mode 100644 score/time/steady_time/details/stub_impl/steady_clock_impl.h create mode 100644 score/time/steady_time/steady_clock.cpp create mode 100644 score/time/steady_time/steady_clock.h create mode 100644 score/time/steady_time/steady_clock_iface.h create mode 100644 score/time/steady_time/steady_clock_mock.h create mode 100644 score/time/steady_time/steady_clock_test.cpp create mode 100644 score/time/system_time/BUILD create mode 100644 score/time/system_time/details/BUILD create mode 100644 score/time/system_time/details/stub_impl/BUILD create mode 100644 score/time/system_time/details/stub_impl/system_clock_impl.cpp create mode 100644 score/time/system_time/details/stub_impl/system_clock_impl.h create mode 100644 score/time/system_time/details/system_time_impl/BUILD create mode 100644 score/time/system_time/details/system_time_impl/system_clock_impl.cpp create mode 100644 score/time/system_time/details/system_time_impl/system_clock_impl.h create mode 100644 score/time/system_time/details/system_time_impl/system_clock_impl_test.cpp create mode 100644 score/time/system_time/system_clock.cpp create mode 100644 score/time/system_time/system_clock.h create mode 100644 score/time/system_time/system_clock_iface.h create mode 100644 score/time/system_time/system_clock_mock.h create mode 100644 score/time/system_time/system_clock_test.cpp create mode 100644 score/time/vehicle_time/BUILD create mode 100644 score/time/vehicle_time/details/BUILD create mode 100644 score/time/vehicle_time/details/logging_contexts.h create mode 100644 score/time/vehicle_time/details/stub_impl/BUILD create mode 100644 score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.cpp create mode 100644 score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.h create mode 100644 score/time/vehicle_time/details/td_impl/BUILD create mode 100644 score/time/vehicle_time/details/td_impl/vehicle_clock_impl.cpp create mode 100644 score/time/vehicle_time/details/td_impl/vehicle_clock_impl.h create mode 100644 score/time/vehicle_time/details/td_impl/vehicle_clock_impl_test.cpp create mode 100644 score/time/vehicle_time/vehicle_clock.cpp create mode 100644 score/time/vehicle_time/vehicle_clock.h create mode 100644 score/time/vehicle_time/vehicle_clock_iface.h create mode 100644 score/time/vehicle_time/vehicle_clock_test.cpp create mode 100644 score/time/vehicle_time/vehicle_time.h create mode 100644 score/time/vehicle_time/vehicle_time_mock.h create mode 100644 score/time/vehicle_time/vehicle_time_status_test.cpp diff --git a/examples/time/BUILD b/examples/time/BUILD new file mode 100644 index 0000000..fe87ebd --- /dev/null +++ b/examples/time/BUILD @@ -0,0 +1,33 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") + +# Aggregates the unit_test_suite targets from all example time-domain applications. +# +# Run all example tests with: +# bazel test //examples/time:unit_test_suite_host +# +# Or run everything — library, TimeDaemon, and examples — together: +# bazel test //score/time:unit_test_suite_host +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [], + test_suites_from_sub_packages = [ + "//examples/time/vehicle_time:unit_test_suite", + "//examples/time/hpls_time:unit_test_suite", + "//examples/time/steady_time:unit_test_suite", + "//examples/time/system_time:unit_test_suite", + ], + visibility = ["//visibility:public"], +) diff --git a/examples/time/hpls_time/BUILD b/examples/time/hpls_time/BUILD new file mode 100644 index 0000000..f09db68 --- /dev/null +++ b/examples/time/hpls_time/BUILD @@ -0,0 +1,64 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Library containing only the testable business-logic — no main(), no signal handling. +cc_library( + name = "time_handler", + hdrs = ["hpls_time_handler.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/time/hpls_time:interface", + ], +) + +# Example binary: reads HplsTime once per second and prints the snapshot to stdout. +# +# Run with: +# bazel run //examples/time/hpls_time +# +# HplsTime wraps a local monotonic hardware clock (high_resolution_clock on Linux, +# ClockCycles() on QNX) and has no synchronisation-quality concept, so the snapshot +# carries only a time-point. +cc_binary( + name = "hpls_time", + srcs = ["main.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + # Single entry point: brings in HplsClock (hpls_clock.h) + # AND the production HplsTime backend (CreateBackend()). + "//score/time/hpls_time", + ], +) + +# Unit test for the time handler: replaces HplsClock with a mock. +cc_test( + name = "hpls_time_handler_test", + srcs = ["hpls_time_handler_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + "//score/time/hpls_time:hpls_time_mock", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":hpls_time_handler_test"], + visibility = ["//examples/time:__pkg__"], +) diff --git a/examples/time/hpls_time/hpls_time_handler.h b/examples/time/hpls_time/hpls_time_handler.h new file mode 100644 index 0000000..b733681 --- /dev/null +++ b/examples/time/hpls_time/hpls_time_handler.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef EXAMPLES_TIME_HPLS_TIME_HPLS_TIME_HANDLER_H +#define EXAMPLES_TIME_HPLS_TIME_HPLS_TIME_HANDLER_H + +#include "score/time/hpls_time/hpls_clock.h" + +#include + +namespace examples +{ +namespace time +{ +namespace hpls_time +{ + +/// @brief A snapshot of the time report produced by HplsTimeHandler. +struct TimeReport +{ + /// @brief Current HPLS monotonic time in nanoseconds since an unspecified epoch. + std::int64_t time_ns{0}; +}; + +/// @brief Convenience wrapper that reads HplsClock in one call. +/// +/// @par Testing pattern +/// @code +/// auto mock = std::make_shared(); +/// score::time::ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// HplsTimeHandler handler; +/// const auto report = handler.GetCurrentTime(); +/// @endcode +class HplsTimeHandler +{ + public: + HplsTimeHandler() = default; + ~HplsTimeHandler() = default; + + HplsTimeHandler(const HplsTimeHandler&) = delete; + HplsTimeHandler& operator=(const HplsTimeHandler&) = delete; + HplsTimeHandler(HplsTimeHandler&&) = delete; + HplsTimeHandler& operator=(HplsTimeHandler&&) = delete; + + /// @brief Reads the current HPLS time and returns a report. + [[nodiscard]] TimeReport GetCurrentTime() const noexcept + { + const auto snapshot = score::time::HplsClock::GetInstance().Now(); + return TimeReport{snapshot.TimePointNs().count()}; + } +}; + +} // namespace hpls_time +} // namespace time +} // namespace examples + +#endif // EXAMPLES_TIME_HPLS_TIME_HPLS_TIME_HANDLER_H diff --git a/examples/time/hpls_time/hpls_time_handler_test.cpp b/examples/time/hpls_time/hpls_time_handler_test.cpp new file mode 100644 index 0000000..d89ce50 --- /dev/null +++ b/examples/time/hpls_time/hpls_time_handler_test.cpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "examples/time/hpls_time/hpls_time_handler.h" + +#include "score/time/hpls_time/hpls_time_mock.h" +#include "score/time/clock/clock_override_guard.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include +#include + +#include +#include + +using ::testing::Return; + +namespace examples +{ +namespace time +{ +namespace hpls_time +{ +namespace test +{ + +class HplsTimeHandlerTest : public ::testing::Test +{ + protected: + HplsTimeHandlerTest() + : mock_{std::make_shared()} + , guard_{mock_} + { + } + + std::shared_ptr mock_; + score::time::ClockOverrideGuard guard_; +}; + +TEST_F(HplsTimeHandlerTest, ReportContainsTimePointFromMock) +{ + const score::time::HplsTime::Timepoint tp{std::chrono::nanoseconds{7'654'321'000LL}}; + EXPECT_CALL(*mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + tp, score::time::NoStatus{}})); + + HplsTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.time_ns, 7'654'321'000LL); +} + +TEST_F(HplsTimeHandlerTest, ReportContainsZeroForEpochTimepoint) +{ + const score::time::HplsTime::Timepoint tp{std::chrono::nanoseconds{0}}; + EXPECT_CALL(*mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + tp, score::time::NoStatus{}})); + + HplsTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.time_ns, 0LL); +} + +} // namespace test +} // namespace hpls_time +} // namespace time +} // namespace examples diff --git a/examples/time/hpls_time/main.cpp b/examples/time/hpls_time/main.cpp new file mode 100644 index 0000000..b553880 --- /dev/null +++ b/examples/time/hpls_time/main.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +/** + * @file + * @brief Example: periodic HplsTime report printer. + * + * Uses HplsTimeHandler to obtain the current high-precision local steady time + * and prints it to stdout once per second until interrupted by SIGINT or SIGTERM. + * + * The handler class can be unit-tested in isolation — see time_handler_test.cpp. + */ + +#include "examples/time/hpls_time/hpls_time_handler.h" + +#include +#include +#include +#include +#include + +namespace +{ + +/** @brief Flag set by the signal handler to request a clean shutdown. */ +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +volatile std::sig_atomic_t gShutdownRequested{0}; + +/** @brief Signal handler for SIGINT / SIGTERM. */ +extern "C" void HandleSignal(int /*signal*/) noexcept +{ + gShutdownRequested = 1; +} + +/** + * @brief Prints a single TimeReport to stdout. + * + * @param report The time report to format. + * @param seq Monotonic sequence number of this print. + */ +void PrintReport(const examples::time::hpls_time::TimeReport& report, std::uint64_t seq) noexcept +{ + const auto seconds = report.time_ns / 1'000'000'000LL; + const auto nanoseconds = report.time_ns % 1'000'000'000LL; + + std::cout << "[" << seq << "]" + << " time=" << seconds << "." << nanoseconds << " s\n"; +} + +} // namespace + +int main() +{ + // Install signal handlers for clean shutdown. + static_cast(std::signal(SIGINT, HandleSignal)); + static_cast(std::signal(SIGTERM, HandleSignal)); + + examples::time::hpls_time::HplsTimeHandler handler; + + std::cout << "HplsTime printer started. Press Ctrl+C to stop.\n"; + + std::uint64_t seq{0U}; + + while (gShutdownRequested == 0) + { + const auto report = handler.GetCurrentTime(); + PrintReport(report, seq); + ++seq; + + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + + std::cout << "Shutdown requested. Exiting.\n"; + return 0; +} diff --git a/examples/time/steady_time/BUILD b/examples/time/steady_time/BUILD new file mode 100644 index 0000000..cc51419 --- /dev/null +++ b/examples/time/steady_time/BUILD @@ -0,0 +1,64 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Library containing only the testable business-logic — no main(), no signal handling. +cc_library( + name = "time_handler", + hdrs = ["steady_time_handler.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/time/steady_time:interface", + ], +) + +# Example binary: reads SteadyTime once per second and prints the snapshot to stdout. +# +# Run with: +# bazel run //examples/time/steady_time +# +# SteadyClock wraps std::chrono::steady_clock — a monotonic clock that measures +# time relative to an unspecified epoch (typically system boot). The snapshot +# carries only a time-point; there is no synchronisation status. +cc_binary( + name = "steady_time", + srcs = ["main.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + # Single entry point: brings in SteadyClock (steady_clock.h) + # AND the production SteadyTime backend (CreateBackend()). + "//score/time/steady_time", + ], +) + +# Unit test for the time handler: replaces SteadyClock with a mock. +cc_test( + name = "steady_time_handler_test", + srcs = ["steady_time_handler_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + "//score/time/steady_time:steady_time_mock", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":steady_time_handler_test"], + visibility = ["//examples/time:__pkg__"], +) diff --git a/examples/time/steady_time/main.cpp b/examples/time/steady_time/main.cpp new file mode 100644 index 0000000..2412904 --- /dev/null +++ b/examples/time/steady_time/main.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +/** + * @file + * @brief Example: periodic SteadyTime report printer. + * + * Uses SteadyTimeHandler to obtain the current monotonic time and prints it + * to stdout once per second until interrupted by SIGINT or SIGTERM. + * + * The handler class can be unit-tested in isolation — see time_handler_test.cpp. + */ + +#include "examples/time/steady_time/steady_time_handler.h" + +#include +#include +#include +#include +#include + +namespace +{ + +/** @brief Flag set by the signal handler to request a clean shutdown. */ +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +volatile std::sig_atomic_t gShutdownRequested{0}; + +/** @brief Signal handler for SIGINT / SIGTERM. */ +extern "C" void HandleSignal(int /*signal*/) noexcept +{ + gShutdownRequested = 1; +} + +/** + * @brief Prints a single TimeReport to stdout. + * + * @param report The time report to format. + * @param seq Monotonic sequence number of this print. + */ +void PrintReport(const examples::time::steady_time::TimeReport& report, std::uint64_t seq) noexcept +{ + const auto seconds = report.monotonic_ns / 1'000'000'000LL; + const auto nanoseconds = report.monotonic_ns % 1'000'000'000LL; + + std::cout << "[" << seq << "]" + << " monotonic=" << seconds << "." << nanoseconds << " s\n"; +} + +} // namespace + +int main() +{ + // Install signal handlers for clean shutdown. + static_cast(std::signal(SIGINT, HandleSignal)); + static_cast(std::signal(SIGTERM, HandleSignal)); + + examples::time::steady_time::SteadyTimeHandler handler; + + std::cout << "SteadyTime printer started. Press Ctrl+C to stop.\n"; + + std::uint64_t seq{0U}; + + while (gShutdownRequested == 0) + { + const auto report = handler.GetCurrentTime(); + PrintReport(report, seq); + ++seq; + + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + + std::cout << "Shutdown requested. Exiting.\n"; + return 0; +} diff --git a/examples/time/steady_time/steady_time_handler.h b/examples/time/steady_time/steady_time_handler.h new file mode 100644 index 0000000..3264d2b --- /dev/null +++ b/examples/time/steady_time/steady_time_handler.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef EXAMPLES_TIME_STEADY_TIME_STEADY_TIME_HANDLER_H +#define EXAMPLES_TIME_STEADY_TIME_STEADY_TIME_HANDLER_H + +#include "score/time/steady_time/steady_clock.h" + +#include + +namespace examples +{ +namespace time +{ +namespace steady_time +{ + +/// @brief A snapshot of the time report produced by SteadyTimeHandler. +struct TimeReport +{ + /// @brief Current monotonic time in nanoseconds since an unspecified epoch (typically boot). + std::int64_t monotonic_ns{0}; +}; + +/// @brief Convenience wrapper that reads SteadyClock in one call. +/// +/// @par Testing pattern +/// @code +/// auto mock = std::make_shared(); +/// score::time::ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// SteadyTimeHandler handler; +/// const auto report = handler.GetCurrentTime(); +/// @endcode +class SteadyTimeHandler +{ + public: + SteadyTimeHandler() = default; + ~SteadyTimeHandler() = default; + + SteadyTimeHandler(const SteadyTimeHandler&) = delete; + SteadyTimeHandler& operator=(const SteadyTimeHandler&) = delete; + SteadyTimeHandler(SteadyTimeHandler&&) = delete; + SteadyTimeHandler& operator=(SteadyTimeHandler&&) = delete; + + /// @brief Reads the current steady time and returns a report. + [[nodiscard]] TimeReport GetCurrentTime() const noexcept + { + const auto snapshot = score::time::SteadyClock::GetInstance().Now(); + return TimeReport{snapshot.TimePointNs().count()}; + } +}; + +} // namespace steady_time +} // namespace time +} // namespace examples + +#endif // EXAMPLES_TIME_STEADY_TIME_STEADY_TIME_HANDLER_H diff --git a/examples/time/steady_time/steady_time_handler_test.cpp b/examples/time/steady_time/steady_time_handler_test.cpp new file mode 100644 index 0000000..2258487 --- /dev/null +++ b/examples/time/steady_time/steady_time_handler_test.cpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "examples/time/steady_time/steady_time_handler.h" + +#include "score/time/steady_time/steady_clock_mock.h" +#include "score/time/clock/clock_override_guard.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include +#include + +#include +#include + +using ::testing::Return; + +namespace examples +{ +namespace time +{ +namespace steady_time +{ +namespace test +{ + +class SteadyTimeHandlerTest : public ::testing::Test +{ + protected: + SteadyTimeHandlerTest() + : mock_{std::make_shared()} + , guard_{mock_} + { + } + + std::shared_ptr mock_; + score::time::ClockOverrideGuard guard_; +}; + +TEST_F(SteadyTimeHandlerTest, ReportContainsMonotonicTimeFromMock) +{ + const std::chrono::steady_clock::time_point tp{std::chrono::nanoseconds{3'000'000'000LL}}; + EXPECT_CALL(*mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + tp, score::time::NoStatus{}})); + + SteadyTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.monotonic_ns, 3'000'000'000LL); +} + +TEST_F(SteadyTimeHandlerTest, ReportContainsZeroForEpochTimepoint) +{ + const std::chrono::steady_clock::time_point tp{std::chrono::nanoseconds{0}}; + EXPECT_CALL(*mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + tp, score::time::NoStatus{}})); + + SteadyTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.monotonic_ns, 0LL); +} + +} // namespace test +} // namespace steady_time +} // namespace time +} // namespace examples diff --git a/examples/time/system_time/BUILD b/examples/time/system_time/BUILD new file mode 100644 index 0000000..b8c4cea --- /dev/null +++ b/examples/time/system_time/BUILD @@ -0,0 +1,63 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Library containing only the testable business-logic — no main(), no signal handling. +cc_library( + name = "time_handler", + hdrs = ["system_time_handler.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/time/system_time:interface", + ], +) + +# Example binary: reads SystemTime once per second and prints the snapshot to stdout. +# +# Run with: +# bazel run //examples/time/system_time +# +# SystemClock wraps std::chrono::system_clock — the system wall-clock (Unix epoch). +# The snapshot carries only a time-point; there is no synchronisation status. +cc_binary( + name = "system_time", + srcs = ["main.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + # Single entry point: brings in SystemClock (system_clock.h) + # AND the production SystemTime backend (CreateBackend()). + "//score/time/system_time", + ], +) + +# Unit test for the time handler: replaces SystemClock with a mock. +cc_test( + name = "system_time_handler_test", + srcs = ["system_time_handler_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + "//score/time/system_time:system_time_mock", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":system_time_handler_test"], + visibility = ["//examples/time:__pkg__"], +) diff --git a/examples/time/system_time/main.cpp b/examples/time/system_time/main.cpp new file mode 100644 index 0000000..760d568 --- /dev/null +++ b/examples/time/system_time/main.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +/** + * @file + * @brief Example: periodic SystemTime report printer. + * + * Uses SystemTimeHandler to obtain the current wall-clock (Unix epoch) time + * and prints it to stdout once per second until interrupted by SIGINT or SIGTERM. + * + * The handler class can be unit-tested in isolation — see time_handler_test.cpp. + */ + +#include "examples/time/system_time/system_time_handler.h" + +#include +#include +#include +#include +#include + +namespace +{ + +/** @brief Flag set by the signal handler to request a clean shutdown. */ +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +volatile std::sig_atomic_t gShutdownRequested{0}; + +/** @brief Signal handler for SIGINT / SIGTERM. */ +extern "C" void HandleSignal(int /*signal*/) noexcept +{ + gShutdownRequested = 1; +} + +/** + * @brief Prints a single TimeReport to stdout. + * + * @param report The time report to format. + * @param seq Monotonic sequence number of this print. + */ +void PrintReport(const examples::time::system_time::TimeReport& report, std::uint64_t seq) noexcept +{ + const auto seconds = report.unix_ns / 1'000'000'000LL; + const auto nanoseconds = report.unix_ns % 1'000'000'000LL; + + std::cout << "[" << seq << "]" + << " unix=" << seconds << "." << nanoseconds << " s\n"; +} + +} // namespace + +int main() +{ + // Install signal handlers for clean shutdown. + static_cast(std::signal(SIGINT, HandleSignal)); + static_cast(std::signal(SIGTERM, HandleSignal)); + + examples::time::system_time::SystemTimeHandler handler; + + std::cout << "SystemTime printer started. Press Ctrl+C to stop.\n"; + + std::uint64_t seq{0U}; + + while (gShutdownRequested == 0) + { + const auto report = handler.GetCurrentTime(); + PrintReport(report, seq); + ++seq; + + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + + std::cout << "Shutdown requested. Exiting.\n"; + return 0; +} diff --git a/examples/time/system_time/system_time_handler.h b/examples/time/system_time/system_time_handler.h new file mode 100644 index 0000000..82d7e7c --- /dev/null +++ b/examples/time/system_time/system_time_handler.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef EXAMPLES_TIME_SYSTEM_TIME_SYSTEM_TIME_HANDLER_H +#define EXAMPLES_TIME_SYSTEM_TIME_SYSTEM_TIME_HANDLER_H + +#include "score/time/system_time/system_clock.h" + +#include + +namespace examples +{ +namespace time +{ +namespace system_time +{ + +/// @brief A snapshot of the time report produced by SystemTimeHandler. +struct TimeReport +{ + /// @brief Current wall-clock (Unix epoch) time in nanoseconds. + std::int64_t unix_ns{0}; +}; + +/// @brief Convenience wrapper that reads SystemClock in one call. +/// +/// @par Testing pattern +/// @code +/// auto mock = std::make_shared(); +/// score::time::ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// SystemTimeHandler handler; +/// const auto report = handler.GetCurrentTime(); +/// @endcode +class SystemTimeHandler +{ + public: + SystemTimeHandler() = default; + ~SystemTimeHandler() = default; + + SystemTimeHandler(const SystemTimeHandler&) = delete; + SystemTimeHandler& operator=(const SystemTimeHandler&) = delete; + SystemTimeHandler(SystemTimeHandler&&) = delete; + SystemTimeHandler& operator=(SystemTimeHandler&&) = delete; + + /// @brief Reads the current system (wall-clock) time and returns a report. + [[nodiscard]] TimeReport GetCurrentTime() const noexcept + { + const auto snapshot = score::time::SystemClock::GetInstance().Now(); + return TimeReport{snapshot.TimePointNs().count()}; + } +}; + +} // namespace system_time +} // namespace time +} // namespace examples + +#endif // EXAMPLES_TIME_SYSTEM_TIME_SYSTEM_TIME_HANDLER_H diff --git a/examples/time/system_time/system_time_handler_test.cpp b/examples/time/system_time/system_time_handler_test.cpp new file mode 100644 index 0000000..1bd69bf --- /dev/null +++ b/examples/time/system_time/system_time_handler_test.cpp @@ -0,0 +1,81 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "examples/time/system_time/system_time_handler.h" + +#include "score/time/system_time/system_clock_mock.h" +#include "score/time/clock/clock_override_guard.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include +#include + +#include +#include + +using ::testing::Return; + +namespace examples +{ +namespace time +{ +namespace system_time +{ +namespace test +{ + +class SystemTimeHandlerTest : public ::testing::Test +{ + protected: + SystemTimeHandlerTest() + : mock_{std::make_shared()} + , guard_{mock_} + { + } + + std::shared_ptr mock_; + score::time::ClockOverrideGuard guard_; +}; + +TEST_F(SystemTimeHandlerTest, ReportContainsUnixTimeFromMock) +{ + // 2026-01-01 00:00:00 UTC in nanoseconds since Unix epoch + constexpr std::int64_t kYear2026Ns = 1'767'225'600LL * 1'000'000'000LL; + const std::chrono::system_clock::time_point tp{std::chrono::nanoseconds{kYear2026Ns}}; + EXPECT_CALL(*mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + tp, score::time::NoStatus{}})); + + SystemTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.unix_ns, kYear2026Ns); +} + +TEST_F(SystemTimeHandlerTest, ReportContainsZeroForEpochTimepoint) +{ + const std::chrono::system_clock::time_point tp{std::chrono::nanoseconds{0}}; + EXPECT_CALL(*mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + tp, score::time::NoStatus{}})); + + SystemTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.unix_ns, 0LL); +} + +} // namespace test +} // namespace system_time +} // namespace time +} // namespace examples diff --git a/examples/time/vehicle_time/BUILD b/examples/time/vehicle_time/BUILD new file mode 100644 index 0000000..813753c --- /dev/null +++ b/examples/time/vehicle_time/BUILD @@ -0,0 +1,74 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Library containing only the testable business-logic — no main(), no signal handling. +cc_library( + name = "time_handler", + hdrs = ["vehicle_time_handler.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/time/hpls_time:interface", + "//score/time/vehicle_time:interface", + ], +) + +# Example binary: reads VehicleTime + HplsTime once per second and prints a combined report. +# +# Run with: +# bazel run //examples/time/vehicle_time +# +# The production VehicleTime backend (TimeDaemon TDR receiver) is pulled in via +# //score/time/vehicle_time:vehicle_time. On a development host that backend will +# return zero-valued snapshots with kTimeOut set — which is the expected behaviour +# when the TimeDaemon is not running. +cc_binary( + name = "vehicle_time", + srcs = ["main.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + # Single entry point: brings in VehicleClock (vehicle_clock.h) + # AND the production VehicleTime backend (CreateBackend()). + "//score/time/vehicle_time", + # VehicleTime's production backend uses HplsClock internally. + # Provide its production backend so CreateBackend() resolves at link time. + "//score/time/hpls_time", + # mw::log frontend is pulled in transitively via td_impl. + # A binary must link exactly one concrete recorder implementation. + "@score_baselibs//score/mw/log:console_only_backend", + ], +) + +# Unit test for the time handler: replaces both VehicleClock and HplsClock with mocks. +cc_test( + name = "vehicle_time_handler_test", + srcs = ["vehicle_time_handler_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":time_handler", + "//score/time/hpls_time:hpls_time_mock", + "//score/time/vehicle_time:vehicle_time_mock", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score_baselibs//score/mw/log:console_only_backend", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":vehicle_time_handler_test"], + visibility = ["//examples/time:__pkg__"], +) diff --git a/examples/time/vehicle_time/main.cpp b/examples/time/vehicle_time/main.cpp new file mode 100644 index 0000000..56b49fb --- /dev/null +++ b/examples/time/vehicle_time/main.cpp @@ -0,0 +1,93 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +/** + * @file + * @brief Example: periodic VehicleTime + HplsTime report printer. + * + * Uses VehicleTimeHandler to obtain a combined snapshot of the PTP-synchronized + * vehicle time and the local monotonic HPLS time, then prints both to stdout + * once per second until interrupted by SIGINT or SIGTERM. + * + * The handler class can be unit-tested in isolation — see time_handler_test.cpp. + */ + +#include "examples/time/vehicle_time/vehicle_time_handler.h" + +#include +#include +#include +#include +#include + +namespace +{ + +/** @brief Flag set by the signal handler to request a clean shutdown. */ +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +volatile std::sig_atomic_t gShutdownRequested{0}; + +/** @brief Signal handler for SIGINT / SIGTERM. */ +extern "C" void HandleSignal(int /*signal*/) noexcept +{ + gShutdownRequested = 1; +} + +/** + * @brief Prints a single TimeReport to stdout. + * + * @param report The combined time report to format. + * @param seq Monotonic sequence number of this print. + */ +void PrintReport(const examples::time::vehicle_time::TimeReport& report, std::uint64_t seq) noexcept +{ + const auto v_sec = report.vehicle_time_ns / 1'000'000'000LL; + const auto v_ns = report.vehicle_time_ns % 1'000'000'000LL; + const auto h_sec = report.hpls_time_ns / 1'000'000'000LL; + const auto h_ns = report.hpls_time_ns % 1'000'000'000LL; + + std::cout << "[" << seq << "]" + << " vehicle=" << v_sec << "." << v_ns << " s" + << " hpls=" << h_sec << "." << h_ns << " s" + << " synchronized=" << (report.synchronized ? "yes" : "no") + << " valid=" << (report.valid ? "yes" : "no") + << " rate_deviation=" << report.rate_deviation + << "\n"; +} + +} // namespace + +int main() +{ + // Install signal handlers for clean shutdown. + static_cast(std::signal(SIGINT, HandleSignal)); + static_cast(std::signal(SIGTERM, HandleSignal)); + + examples::time::vehicle_time::VehicleTimeHandler handler; + + std::cout << "VehicleTime + HplsTime printer started. Press Ctrl+C to stop.\n"; + + std::uint64_t seq{0U}; + + while (gShutdownRequested == 0) + { + const auto report = handler.GetCurrentTime(); + PrintReport(report, seq); + ++seq; + + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + + std::cout << "Shutdown requested. Exiting.\n"; + return 0; +} diff --git a/examples/time/vehicle_time/vehicle_time_handler.h b/examples/time/vehicle_time/vehicle_time_handler.h new file mode 100644 index 0000000..72fa51c --- /dev/null +++ b/examples/time/vehicle_time/vehicle_time_handler.h @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef EXAMPLES_TIME_VEHICLE_TIME_VEHICLE_TIME_HANDLER_H +#define EXAMPLES_TIME_VEHICLE_TIME_VEHICLE_TIME_HANDLER_H + +#include "score/time/vehicle_time/vehicle_clock.h" +#include "score/time/hpls_time/hpls_clock.h" + +#include + +namespace examples +{ +namespace time +{ +namespace vehicle_time +{ + +/// @brief A snapshot of the combined time report produced by VehicleTimeHandler. +struct TimeReport +{ + /// @brief Current vehicle (PTP-synchronized) time, nanoseconds since VehicleTime epoch. + std::int64_t vehicle_time_ns{0}; + + /// @brief Current local monotonic time from HplsClock, nanoseconds since process boot. + /// Provided as a local-time reference alongside the network-synchronized vehicle time. + std::int64_t hpls_time_ns{0}; + + /// @brief True if the vehicle time is currently synchronized to the PTP grand master. + bool synchronized{false}; + + /// @brief True if the vehicle time is in a valid (non-error) state. + bool valid{false}; + + /// @brief Fractional rate deviation of the local clock relative to the grand master. + /// Unit: dimensionless (e.g. 1.0e-9 == 1 ppb). Zero when not synchronized. + double rate_deviation{0.0}; +}; + +/// @brief Convenience wrapper that reads VehicleClock and HplsClock in one call. +/// +/// This class demonstrates how to write a component that depends on two different +/// time bases. In unit tests, both clocks can be replaced independently via +/// ClockOverrideGuard, without any special constructor injection. +/// +/// @par Testing pattern +/// @code +/// auto vehicle_mock = std::make_shared(); +/// auto hpls_mock = std::make_shared(); +/// score::time::ClockOverrideGuard vg{vehicle_mock}; +/// score::time::ClockOverrideGuard hg{hpls_mock}; +/// EXPECT_CALL(*vehicle_mock, Now()).WillOnce(Return(...)); +/// EXPECT_CALL(*hpls_mock, Now()).WillOnce(Return(...)); +/// VehicleTimeHandler handler; +/// const auto report = handler.GetCurrentTime(); +/// @endcode +class VehicleTimeHandler +{ + public: + VehicleTimeHandler() = default; + ~VehicleTimeHandler() = default; + + VehicleTimeHandler(const VehicleTimeHandler&) = delete; + VehicleTimeHandler& operator=(const VehicleTimeHandler&) = delete; + VehicleTimeHandler(VehicleTimeHandler&&) = delete; + VehicleTimeHandler& operator=(VehicleTimeHandler&&) = delete; + + /// @brief Reads the current vehicle time and local HPLS time and returns a combined report. + [[nodiscard]] TimeReport GetCurrentTime() const noexcept + { + const auto vehicle_snapshot = score::time::VehicleClock::GetInstance().Now(); + const auto hpls_snapshot = score::time::HplsClock::GetInstance().Now(); + + return TimeReport{ + vehicle_snapshot.TimePointNs().count(), + hpls_snapshot.TimePointNs().count(), + vehicle_snapshot.Status().IsSynchronized(), + vehicle_snapshot.Status().IsValid(), + vehicle_snapshot.Status().rate_deviation, + }; + } +}; + +} // namespace vehicle_time +} // namespace time +} // namespace examples + +#endif // EXAMPLES_TIME_VEHICLE_TIME_VEHICLE_TIME_HANDLER_H diff --git a/examples/time/vehicle_time/vehicle_time_handler_test.cpp b/examples/time/vehicle_time/vehicle_time_handler_test.cpp new file mode 100644 index 0000000..7ed077f --- /dev/null +++ b/examples/time/vehicle_time/vehicle_time_handler_test.cpp @@ -0,0 +1,133 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "examples/time/vehicle_time/vehicle_time_handler.h" + +#include "score/time/vehicle_time/vehicle_time_mock.h" +#include "score/time/hpls_time/hpls_time_mock.h" +#include "score/time/clock/clock_override_guard.h" +#include "score/time/clock/clock_snapshot.h" + +#include +#include + +#include +#include + +using ::testing::Return; + +namespace examples +{ +namespace time +{ +namespace vehicle_time +{ +namespace test +{ + +/// @brief Test fixture: replaces both VehicleClock and HplsClock with mocks. +/// +/// This is the key pattern for components that depend on multiple time bases: +/// each clock domain has its own ClockOverrideGuard that injects the mock +/// backend for the duration of the test. +class VehicleTimeHandlerTest : public ::testing::Test +{ + protected: + VehicleTimeHandlerTest() + : vehicle_mock_{std::make_shared()} + , hpls_mock_{std::make_shared()} + , vehicle_guard_{vehicle_mock_} + , hpls_guard_{hpls_mock_} + { + } + + std::shared_ptr vehicle_mock_; + std::shared_ptr hpls_mock_; + score::time::ClockOverrideGuard vehicle_guard_; + score::time::ClockOverrideGuard hpls_guard_; +}; + +TEST_F(VehicleTimeHandlerTest, ReportContainsSynchronizedVehicleTimeAndHplsTime) +{ + // Prepare a synchronized vehicle time snapshot. + score::time::VehicleTimeStatus status; + status.flags = score::time::ClockStatus{ + {score::time::VehicleTime::StatusFlag::kSynchronized}}; + status.rate_deviation = 1.5e-9; + + const score::time::VehicleTime::Timepoint vehicle_tp{std::chrono::nanoseconds{5'000'000'000LL}}; + const score::time::ClockSnapshot + vehicle_snapshot{vehicle_tp, status}; + + const score::time::HplsTime::Timepoint hpls_tp{std::chrono::nanoseconds{1'234'567'890LL}}; + const score::time::ClockSnapshot + hpls_snapshot{hpls_tp, score::time::NoStatus{}}; + + EXPECT_CALL(*vehicle_mock_, Now()).WillOnce(Return(vehicle_snapshot)); + EXPECT_CALL(*hpls_mock_, Now()).WillOnce(Return(hpls_snapshot)); + + VehicleTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.vehicle_time_ns, 5'000'000'000LL); + EXPECT_EQ(report.hpls_time_ns, 1'234'567'890LL); + EXPECT_TRUE(report.synchronized); + EXPECT_TRUE(report.valid); + EXPECT_DOUBLE_EQ(report.rate_deviation, 1.5e-9); +} + +TEST_F(VehicleTimeHandlerTest, ReportShowsNotSynchronizedWhenTimeOutFlagIsSet) +{ + score::time::VehicleTimeStatus status; + status.flags = score::time::ClockStatus{ + {score::time::VehicleTime::StatusFlag::kTimeOut}}; + + EXPECT_CALL(*vehicle_mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + score::time::VehicleTime::Timepoint{}, status})); + EXPECT_CALL(*hpls_mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + score::time::HplsTime::Timepoint{}, score::time::NoStatus{}})); + + VehicleTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_FALSE(report.synchronized); + EXPECT_EQ(report.vehicle_time_ns, 0LL); +} + +TEST_F(VehicleTimeHandlerTest, HplsTimeIsIndependentFromVehicleTimeSynchronization) +{ + // Even when vehicle time is not synchronized, hpls_time_ns is still populated + // from the local monotonic clock. + score::time::VehicleTimeStatus unsync_status; + + const score::time::HplsTime::Timepoint hpls_tp{std::chrono::nanoseconds{99'000'000LL}}; + + EXPECT_CALL(*vehicle_mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + score::time::VehicleTime::Timepoint{}, unsync_status})); + EXPECT_CALL(*hpls_mock_, Now()).WillOnce(Return( + score::time::ClockSnapshot{ + hpls_tp, score::time::NoStatus{}})); + + VehicleTimeHandler handler; + const TimeReport report = handler.GetCurrentTime(); + + EXPECT_EQ(report.hpls_time_ns, 99'000'000LL); + EXPECT_FALSE(report.synchronized); +} + +} // namespace test +} // namespace vehicle_time +} // namespace time +} // namespace examples diff --git a/score/TimeDaemon/code/ipc/BUILD b/score/TimeDaemon/code/ipc/BUILD index 2dc48b1..585990c 100644 --- a/score/TimeDaemon/code/ipc/BUILD +++ b/score/TimeDaemon/code/ipc/BUILD @@ -35,6 +35,7 @@ alias( # Added visibility for integration test purpose "//score/TimeDaemon:__subpackages__", "//score/time/SynchronizedVehicleTime/details/tdr:__pkg__", + "//score/time/vehicle_time/details/td_impl:__pkg__", ], ) @@ -43,7 +44,10 @@ alias( name = "svt_receiver_mock", actual = "//score/TimeDaemon/code/ipc/svt/receiver:factory_mock", tags = ["QM"], - visibility = ["//score/time/SynchronizedVehicleTime/details/tdr:__pkg__"], + visibility = [ + "//score/time/SynchronizedVehicleTime/details/tdr:__pkg__", + "//score/time/vehicle_time/details/td_impl:__pkg__", + ], ) # receiver interface alias diff --git a/score/time/BUILD b/score/time/BUILD new file mode 100644 index 0000000..75d798f --- /dev/null +++ b/score/time/BUILD @@ -0,0 +1,46 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//third_party/itf:py_unittest_qnx_test.bzl", "py_unittest_qnx_test") + +py_unittest_qnx_test( + name = "qnx_unit_test_cases", + test_suites = [ + # TODO(migration): remove after HighPrecisionLocalSteadyClock is deleted + "//score/time/HighPrecisionLocalSteadyClock:qnx_unit_test_cases", + "//score/time/hpls_time:qnx_unit_test_cases", + ], + visibility = ["//score/time:__pkg__"], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + # TODO(migration): remove after HighPrecisionLocalSteadyClock / SynchronizedVehicleTime are deleted + "//score/time/HighPrecisionLocalSteadyClock:unit_tests", + "//score/time/SynchronizedVehicleTime:unit_tests", + ], + test_suites_from_sub_packages = [ + "//score/time/clock:unit_test_suite", + "//score/time/common:unit_test_suite", + "//score/time/ptp:unit_test_suite", + "//score/time/hpls_time:unit_test_suite", + "//score/time/vehicle_time:unit_test_suite", + "//score/time/steady_time:unit_test_suite", + "//score/time/system_time:unit_test_suite", + # TODO(migration): remove after SynchronizedVehicleTime is deleted + "//score/time/SynchronizedVehicleTime:unit_test_suite", + ], + visibility = ["//score:__pkg__"], +) diff --git a/score/time/HighPrecisionLocalSteadyClock/BUILD b/score/time/HighPrecisionLocalSteadyClock/BUILD index ba1d318..4ddecfa 100644 --- a/score/time/HighPrecisionLocalSteadyClock/BUILD +++ b/score/time/HighPrecisionLocalSteadyClock/BUILD @@ -11,12 +11,17 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +# DEPRECATED — replaced by //score/time/hpls_time. +# This package will be deleted once all consumers (TimeDaemon) have been migrated. +# Do NOT add new dependencies on any target in this package. + load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") load("@score_baselibs//third_party/itf:py_unittest_qnx_test.bzl", "py_unittest_qnx_test") alias( name = "HighPrecisionLocalSteadyClock", actual = "//score/time/HighPrecisionLocalSteadyClock/details", + deprecation = "DEPRECATED: use //score/time/hpls_time:hpls_time instead. This target will be removed after TimeDaemon migration.", visibility = ["//visibility:public"], ) @@ -24,6 +29,7 @@ alias( name = "utest_mock", testonly = True, actual = "//score/time/HighPrecisionLocalSteadyClock/details:factory_impl_for_utests", + deprecation = "DEPRECATED: use //score/time/hpls_time:hpls_time_mock instead. This target will be removed after TimeDaemon migration.", visibility = ["//visibility:public"], ) diff --git a/score/time/SynchronizedVehicleTime/BUILD b/score/time/SynchronizedVehicleTime/BUILD index 5827a48..70ff399 100644 --- a/score/time/SynchronizedVehicleTime/BUILD +++ b/score/time/SynchronizedVehicleTime/BUILD @@ -11,12 +11,17 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +# DEPRECATED — replaced by //score/time/vehicle_time. +# This package will be deleted once all consumers (TimeDaemon) have been migrated. +# Do NOT add new dependencies on any target in this package. + load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") alias( name = "SynchronizedVehicleTime", actual = "//score/time/SynchronizedVehicleTime/details:factory_impl", + deprecation = "DEPRECATED: use //score/time/vehicle_time:vehicle_time instead. This target will be removed after TimeDaemon migration.", visibility = [ "//visibility:public", ], @@ -26,6 +31,7 @@ alias( name = "utest_mock", testonly = True, actual = "//score/time/SynchronizedVehicleTime/details:factory_impl_for_utests", + deprecation = "DEPRECATED: use //score/time/vehicle_time:vehicle_time_mock instead. This target will be removed after TimeDaemon migration.", visibility = [ "//visibility:public", ], diff --git a/score/time/clock/BUILD b/score/time/clock/BUILD new file mode 100644 index 0000000..4c453a3 --- /dev/null +++ b/score/time/clock/BUILD @@ -0,0 +1,54 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +cc_library( + name = "clock_core", + hdrs = [ + "availability_hook.h", + "clock.h", + "clock_override_guard.h", + "clock_snapshot.h", + "clock_status.h", + "clock_traits.h", + "no_status.h", + "subscription_hook.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_test( + name = "clock_status_test", + srcs = ["clock_status_test.cpp"], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + ":clock_core", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":clock_status_test", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/clock/availability_hook.h b/score/time/clock/availability_hook.h new file mode 100644 index 0000000..535a3f8 --- /dev/null +++ b/score/time/clock/availability_hook.h @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_CLOCK_AVAILABILITY_HOOK_H +#define SCORE_TIME_CLOCK_AVAILABILITY_HOOK_H + +#include + +namespace score +{ +namespace time +{ + +/// @brief SFINAE gate for @c Clock::IsAvailable() and @c WaitUntilAvailable(). +/// +/// The primary template is intentionally undefined. Clock domains that are always +/// available (e.g. @c HplsTime, @c std::chrono::steady_clock) must NOT provide a +/// specialization — attempting to call @c IsAvailable() / @c WaitUntilAvailable() +/// on such clocks is a compile error (use of incomplete type). +/// +/// Clock domains that require a readiness check (e.g. @c VehicleTime, whose backend +/// opens shared memory) must provide a full explicit specialization supplying: +/// - @c static bool CallIsAvailable(const Backend&) noexcept +/// - @c static bool CallWaitUntilAvailable(const Backend&, +/// const score::cpp::stop_token&, +/// std::chrono::steady_clock::time_point) noexcept +/// +/// @tparam Tag Clock domain tag struct (e.g. VehicleTime). +template +struct AvailabilityHook; + +/// @brief Detects whether @c AvailabilityHook has been specialised. +/// +/// @c HasAvailability::value is @c true only for clock domains that require a +/// readiness check (e.g. VehicleTime). For always-available clocks (HplsTime, +/// std::chrono::steady_clock, std::chrono::system_clock) the hook is undefined and +/// this trait is @c false. +template +struct HasAvailability : std::false_type +{ +}; + +template +struct HasAvailability::CallIsAvailable)>> + : std::true_type +{ +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_AVAILABILITY_HOOK_H diff --git a/score/time/clock/clock.h b/score/time/clock/clock.h new file mode 100644 index 0000000..fe4b936 --- /dev/null +++ b/score/time/clock/clock.h @@ -0,0 +1,188 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_CLOCK_CLOCK_H +#define SCORE_TIME_CLOCK_CLOCK_H + +#include "score/time/clock/availability_hook.h" +#include "score/time/clock/clock_traits.h" +#include "score/time/clock/subscription_hook.h" + +#include +#include + +#include +#include +#include + +namespace score +{ +namespace time +{ + +// Forward declaration — ClockOverrideGuard is defined in clock_override_guard.h, +// which includes this header. The forward declaration breaks the circular dependency +// and allows Clock to declare ClockOverrideGuard as a friend. +template +class ClockOverrideGuard; + +namespace detail +{ + +/// @brief Link-selected backend factory. +/// +/// Declared here; defined only in the link-selected backend implementation .cpp +/// Clients never call this directly — +/// only @c Clock::GetInstance() does. +template +std::shared_ptr::Backend> CreateBackend(); + +} // namespace detail + +/// @brief Unified clock value wrapper. +/// +/// @tparam Tag Clock domain tag struct (e.g. VehicleTime, HplsTime, +/// std::chrono::steady_clock). +/// +/// All clock domains share the same API surface: +/// - @c Now() — snapshot (time_point + optional quality status) +/// - @c Subscribe() — async event subscription (where supported) +/// - @c Unsubscribe() — remove subscription +/// - @c IsAvailable() — non-blocking readiness check +/// - @c WaitUntilAvailable() — blocking readiness wait with stop_token +/// - @c GetInstance() — process-wide singleton factory +/// +/// @c Clock is cheaply copyable: copying bumps the shared_ptr ref-count so +/// both copies share the same backend. Move transfers exclusive ownership. +/// +template +class Clock +{ + using Trait = ClockTraits; + using Backend = typename Trait::Backend; + + public: + using duration = typename Trait::Duration; + using time_point = typename Trait::Timepoint; + + /// @brief Snapshot type returned by @c Now(). + using Snapshot = typename Trait::Snapshot; + + /// @brief Returns the process-wide @c Clock handle. + [[nodiscard]] static Clock GetInstance() noexcept + { + std::lock_guard lock{instance_guard_}; + if (instance_override_) + { + return Clock{instance_override_}; + } + if (auto shared = instance_cache_.lock()) + { + return Clock{shared}; + } + auto fresh = detail::CreateBackend(); + instance_cache_ = fresh; + return Clock{fresh}; + } + + /// @brief Returns the current clock snapshot (time_point + optional status). + [[nodiscard]] Snapshot Now() const noexcept + { + return Trait::CallNow(*impl_); + } + + /// @brief Subscribes to a clock event of type @p EventType. + /// + /// A @c SubscriptionHook specialization must exist; otherwise + /// this call is a compile error (incomplete type). + /// + /// @tparam EventType The event struct type. + /// @param cb Callback invoked on each event. + template + void Subscribe(typename SubscriptionHook::Callback cb) noexcept + { + SubscriptionHook::Subscribe(*impl_, std::move(cb)); + } + + /// @brief Removes the subscription for @p EventType. + template + void Unsubscribe() noexcept + { + SubscriptionHook::Unsubscribe(*impl_); + } + + /// @brief Returns @c true if the clock backend resource is ready. + /// + /// Only available for clock domains that require a readiness check (e.g. VehicleTime). + /// Calling this on an always-available clock (HplsTime, steady_clock) is a compile error. + template ::value, int> = 0> + [[nodiscard]] bool IsAvailable() const noexcept + { + return AvailabilityHook::CallIsAvailable(*impl_); + } + + /// @brief Blocks until the clock resource is available or the stop-token / deadline fires. + /// + /// Only available for clock domains that require a readiness check (e.g. VehicleTime). + /// Calling this on an always-available clock (HplsTime, steady_clock) is a compile error. + /// + /// @param token Stop token that can interrupt the wait from outside. + /// @param until Steady-clock deadline after which the wait is abandoned. + /// + /// @return @c true if the resource became available before the deadline. + template ::value, int> = 0> + [[nodiscard]] bool WaitUntilAvailable(const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until) const noexcept + { + return AvailabilityHook::CallWaitUntilAvailable(*impl_, token, until); + } + + private: + friend class ClockOverrideGuard; + + /// @brief Installs a test-double backend for this @c Tag. + /// + /// Called only by @c ClockOverrideGuard constructor. + static void OverrideForTest(std::shared_ptr impl) noexcept + { + std::lock_guard lock{instance_guard_}; + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + !instance_override_, + "score::time: Clock::OverrideForTest() called while an override is already active. " + "Nesting ClockOverrideGuard for the same Tag is not allowed."); + instance_override_ = std::move(impl); + } + + /// @brief Removes the test-double backend for this @c Tag. + /// + /// Called only by @c ClockOverrideGuard destructor. + static void ResetOverride() noexcept + { + std::lock_guard lock{instance_guard_}; + instance_override_.reset(); + } + + /// @c GetInstance() is the sole caller of this constructor. + explicit Clock(std::shared_ptr impl) noexcept : impl_{std::move(impl)} {} + + /// Shared backend handle. Copying @c Clock shares the same backend instance. + std::shared_ptr impl_; + + inline static std::mutex instance_guard_{}; + inline static std::weak_ptr instance_cache_{}; + inline static std::shared_ptr instance_override_{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_CLOCK_H diff --git a/score/time/clock/clock_override_guard.h b/score/time/clock/clock_override_guard.h new file mode 100644 index 0000000..58f49ff --- /dev/null +++ b/score/time/clock/clock_override_guard.h @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_CLOCK_CLOCK_OVERRIDE_GUARD_H +#define SCORE_TIME_CLOCK_CLOCK_OVERRIDE_GUARD_H + +#include "score/time/clock/clock.h" +#include "score/time/clock/clock_traits.h" + +#include + +namespace score +{ +namespace time +{ + +/// @brief RAII guard that installs a test-double backend for @c Clock. +/// +/// On construction calls @c Clock::OverrideForTest(); on destruction calls +/// @c Clock::ResetOverride(). +/// +/// Properties: +/// - Move-only (non-copyable, move-assignment deleted). +/// - Non-re-entrant: nesting two guards for the same @c Tag is a logic error +/// and is caught by an assertion inside @c OverrideForTest(). +/// - Scope-bound: the guard must not be interleaved with another guard of the +/// same @c Tag within the same process. +/// +/// Usage: +/// @code +/// auto mock = std::make_shared>(); +/// { +/// ClockOverrideGuard guard{mock}; +/// MySvc svc{}; // GetInstance() returns mock +/// svc.DoSomething(); +/// } // ResetOverride() called here automatically +/// @endcode +template +class ClockOverrideGuard final +{ + using Backend = typename ClockTraits::Backend; + + public: + /// @brief Installs @p impl as the test backend for @c Clock. + explicit ClockOverrideGuard(std::shared_ptr impl) noexcept : active_{true} + { + Clock::OverrideForTest(std::move(impl)); + } + + ~ClockOverrideGuard() noexcept + { + if (active_) + { + Clock::ResetOverride(); + } + } + + ClockOverrideGuard(const ClockOverrideGuard&) = delete; + ClockOverrideGuard& operator=(const ClockOverrideGuard&) = delete; + + ClockOverrideGuard(ClockOverrideGuard&& other) noexcept : active_{other.active_} + { + other.active_ = false; + } + + ClockOverrideGuard& operator=(ClockOverrideGuard&&) = delete; + + private: + bool active_; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_CLOCK_OVERRIDE_GUARD_H diff --git a/score/time/clock/clock_snapshot.h b/score/time/clock/clock_snapshot.h new file mode 100644 index 0000000..fd05473 --- /dev/null +++ b/score/time/clock/clock_snapshot.h @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef SCORE_TIME_CLOCK_CLOCK_SNAPSHOT_H +#define SCORE_TIME_CLOCK_CLOCK_SNAPSHOT_H + +#include + +namespace score +{ +namespace time +{ + +/// @brief A snapshot of a clock reading at one instant, bundled with its quality metadata. +/// +/// @tparam TimepointT The time-point type (e.g. VehicleTime::Timepoint or +/// std::chrono::steady_clock::time_point). +/// @tparam StatusT The status type that accompanies the reading (e.g. VehicleTimeStatus +/// or NoStatus for clocks with no quality concept). +template +class ClockSnapshot +{ + public: + /// @brief Default constructor. Value-initialises both fields. + ClockSnapshot() = default; + + /// @brief Explicit constructor. + /// + /// @param tp The time-point value. + /// @param st The status value. + explicit ClockSnapshot(TimepointT tp, StatusT st) noexcept : time_point_{tp}, status_{st} {} + + /// @brief Returns the time-point obtained from the clock. + TimepointT TimePoint() const noexcept { return time_point_; } + + /// @brief Returns the quality status associated with the reading. + StatusT Status() const noexcept { return status_; } + + /// @brief Returns the duration elapsed since the clock's epoch in the clock's native resolution. + typename TimepointT::duration TimeSinceEpoch() const noexcept + { + return time_point_.time_since_epoch(); + } + + /// @brief Returns the duration elapsed since the clock's epoch, converted to nanoseconds. + std::chrono::nanoseconds TimePointNs() const noexcept + { + return std::chrono::duration_cast(TimeSinceEpoch()); + } + + private: + /// @brief The time-point obtained from the clock. + TimepointT time_point_{}; + + /// @brief The quality status associated with the reading. + StatusT status_{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_CLOCK_SNAPSHOT_H diff --git a/score/time/clock/clock_status.h b/score/time/clock/clock_status.h new file mode 100644 index 0000000..de1a045 --- /dev/null +++ b/score/time/clock/clock_status.h @@ -0,0 +1,160 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef SCORE_TIME_CLOCK_CLOCK_STATUS_H +#define SCORE_TIME_CLOCK_CLOCK_STATUS_H + +#include +#include +#include + +#include + +namespace score +{ +namespace time +{ + +/// @brief Encapsulates the synchronisation-quality status of a timebase as a set of bit flags. +/// +/// This class is the successor to @c TimeBaseStatus. It is used by timebases that express their +/// quality state as a set of named bit-positions (e.g. @c VehicleTime::StatusFlag). Timebases +/// with no quality concept (e.g. HplsTime) use @c NoStatus instead. +/// +/// @tparam FlagEnumT Scoped enum type whose enumerators are **bit positions** (not bitmasks). +/// The underlying type must be an unsigned integer type. +template +class ClockStatus final +{ + static_assert(std::is_enum::value, "FlagEnumT must be an enum"); + using StatusFlagT = std::underlying_type_t; + + public: + /// @brief Constructs a @c ClockStatus with the given set of active flags. + /// + /// @param flag_list List of flag bit-positions to set as active. + ClockStatus(const std::initializer_list flag_list) noexcept : status_flags_{} + { + for (const auto flag : flag_list) + { + AddStatusFlagTo(status_flags_, flag); + } + } + + ClockStatus() = default; + ClockStatus(const ClockStatus&) = default; + ClockStatus(ClockStatus&&) noexcept = default; + ClockStatus& operator=(const ClockStatus&) = default; + ClockStatus& operator=(ClockStatus&&) noexcept = default; + ~ClockStatus() noexcept = default; + + /// @brief Returns @c true if the given flag bit-position is set in this status. + /// + /// @param flag The bit-position to test. + bool IsFlagActive(const FlagEnumT flag) const noexcept + { + const StatusFlagT flag_position{static_cast(flag)}; + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + (flag_position < (sizeof(StatusFlagT) * 8U)), + "score::time: IsFlagActive() argument 'flag' exceeds possible size of status_flags_ type"); + return static_cast(status_flags_ & (static_cast(1U << flag_position))); + } + + /// @brief Returns @c true if any of the given flag bit-positions is set in this status. + /// + /// @param flag_list The bit-positions to test. + bool IsAnyOfFlagsActive(const std::initializer_list& flag_list) const noexcept + { + bool is_any_flag_set{false}; + for (const auto flag : flag_list) + { + if (IsFlagActive(flag)) + { + is_any_flag_set = true; + break; + } + } + return is_any_flag_set; + } + + /// @brief Returns @c true if the timebase is synchronized. + /// + /// @note Requires a template specialization for the concrete @c FlagEnumT. + bool IsSynchronized() const noexcept; + + /// @brief Returns @c true if the timebase is in a valid (non-error) state. + /// + /// @note Requires a template specialization for the concrete @c FlagEnumT. + bool IsValid() const noexcept; + + /// @brief Formats all active flags into an @c ostringstream for diagnostics. + /// + /// @note Requires a template specialization for the concrete @c FlagEnumT. + std::ostringstream PrintTo() const; + + /// @brief Adds a flag bit-position to this status. + /// + /// @param flag The bit-position to set. + void AddFlag(const FlagEnumT flag) noexcept + { + AddStatusFlagTo(status_flags_, flag); + } + + /// @brief Compares two @c ClockStatus objects for equality. + friend bool operator==(const ClockStatus& lhs, const ClockStatus& rhs) noexcept + { + return lhs.status_flags_ == rhs.status_flags_; + } + + /// @brief Returns the underlying raw flag bitmask. + constexpr StatusFlagT ToUnderlying() const noexcept + { + return status_flags_; + } + + /// @brief Replaces the internal flag bitmask with the given raw value. + /// + /// @param status_flags New raw bitmask to store. + void FromUnderlying(const StatusFlagT status_flags) noexcept + { + status_flags_ = status_flags; + } + + private: + /// @brief Adds the bit-position represented by @p flag into @p status_container. + static void AddStatusFlagTo(StatusFlagT& status_container, const FlagEnumT flag) noexcept + { + const StatusFlagT flag_position{static_cast(flag)}; + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + (flag_position < (sizeof(status_container) * 8U)), + "score::time: AddStatusFlagTo() argument 'flag' exceeds possible size of 'StatusFlagT& status_container'"); + const StatusFlagT temp{static_cast(1U << flag_position)}; + status_container = static_cast(status_container | temp); + } + + /// @brief Raw storage for all active flag bits. + StatusFlagT status_flags_{}; +}; + +/// @brief Stream insertion operator — delegates to ClockStatus::PrintTo(). +template +auto operator<<(OutputStream& output_stream, const ClockStatus& clock_status) -> OutputStream& +{ + output_stream << clock_status.PrintTo().str(); + return output_stream; +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_CLOCK_STATUS_H diff --git a/score/time/clock/clock_status_test.cpp b/score/time/clock/clock_status_test.cpp new file mode 100644 index 0000000..5cc97f9 --- /dev/null +++ b/score/time/clock/clock_status_test.cpp @@ -0,0 +1,155 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/clock/clock_status.h" + +#include + +#include +#include + +namespace score +{ +namespace time +{ + +class TestClockStatus : public ::testing::Test +{ + public: + enum class StatusFlag : std::uint8_t + { + kTimeOut = 0U, + kSynchronized = 1U, + kSynchToGateway = 2U, + kUnknown = 3U, + }; +}; + +TEST_F(TestClockStatus, ObjectCreatedFromFlagList) +{ + ClockStatus status{StatusFlag::kSynchronized, StatusFlag::kSynchToGateway}; + + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchronized)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_FALSE(status.IsAnyOfFlagsActive({StatusFlag::kTimeOut, StatusFlag::kUnknown})); +} + +TEST_F(TestClockStatus, FlagsAppendedCorrectly) +{ + ClockStatus status{}; + + EXPECT_FALSE(status.IsAnyOfFlagsActive( + {StatusFlag::kSynchToGateway, StatusFlag::kTimeOut, StatusFlag::kSynchronized, StatusFlag::kUnknown})); + + status.AddFlag(StatusFlag::kSynchToGateway); + status.AddFlag(StatusFlag::kTimeOut); + + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kTimeOut)); + EXPECT_FALSE(status.IsAnyOfFlagsActive({StatusFlag::kSynchronized, StatusFlag::kUnknown})); + + status.AddFlag(StatusFlag::kUnknown); + status.AddFlag(StatusFlag::kSynchronized); + + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kTimeOut)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchronized)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kUnknown)); +} + +TEST_F(TestClockStatus, FlagAppendedMoreThanOnce) +{ + ClockStatus status{StatusFlag::kSynchronized}; + EXPECT_FALSE(status.IsFlagActive(StatusFlag::kSynchToGateway)); + + status.AddFlag(StatusFlag::kSynchToGateway); + status.AddFlag(StatusFlag::kSynchToGateway); + + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchronized)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_FALSE(status.IsAnyOfFlagsActive({StatusFlag::kTimeOut, StatusFlag::kUnknown})); + + status.AddFlag(StatusFlag::kTimeOut); + status.AddFlag(StatusFlag::kTimeOut); + + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchronized)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_TRUE(status.IsFlagActive(StatusFlag::kTimeOut)); + EXPECT_FALSE(status.IsFlagActive(StatusFlag::kUnknown)); +} + +TEST_F(TestClockStatus, StatusSerializedCorrectlyToFlagsContainer) +{ + ClockStatus status{StatusFlag::kSynchToGateway, StatusFlag::kTimeOut}; + + const auto raw = status.ToUnderlying(); + + ClockStatus deserialized{}; + deserialized.FromUnderlying(raw); + + EXPECT_TRUE(deserialized.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_TRUE(deserialized.IsFlagActive(StatusFlag::kTimeOut)); + EXPECT_FALSE(deserialized.IsAnyOfFlagsActive({StatusFlag::kSynchronized, StatusFlag::kUnknown})); + + ClockStatus all_set{}; + all_set.FromUnderlying(std::numeric_limits::max()); + + EXPECT_TRUE(all_set.IsFlagActive(StatusFlag::kSynchToGateway)); + EXPECT_TRUE(all_set.IsFlagActive(StatusFlag::kTimeOut)); + EXPECT_TRUE(all_set.IsFlagActive(StatusFlag::kSynchronized)); + EXPECT_TRUE(all_set.IsFlagActive(StatusFlag::kUnknown)); +} + +TEST_F(TestClockStatus, EqualityOperator) +{ + const ClockStatus a{StatusFlag::kSynchronized, StatusFlag::kTimeOut}; + const ClockStatus b{StatusFlag::kSynchronized, StatusFlag::kTimeOut}; + const ClockStatus c{StatusFlag::kSynchToGateway}; + + EXPECT_TRUE(a == b); + EXPECT_FALSE(a == c); +} + +TEST_F(TestClockStatus, DefaultConstructedStatusHasNoFlagsActive) +{ + const ClockStatus status{}; + + EXPECT_FALSE(status.IsAnyOfFlagsActive( + {StatusFlag::kTimeOut, StatusFlag::kSynchronized, StatusFlag::kSynchToGateway, StatusFlag::kUnknown})); + EXPECT_EQ(status.ToUnderlying(), std::uint8_t{0U}); +} + +TEST_F(TestClockStatus, OutOfRangeFlagAborts) +{ + enum class StatusFlagOutOfRange : std::uint8_t + { + kTimeOut = 0U, + kSynchronized = 1U, + kSynchToGateway = 200U, + kUnknown = 255U, + }; + + ClockStatus status{}; + + status.AddFlag(StatusFlagOutOfRange::kSynchronized); + EXPECT_TRUE(status.IsFlagActive(StatusFlagOutOfRange::kSynchronized)); + + ASSERT_DEATH(status.AddFlag(StatusFlagOutOfRange::kSynchToGateway), ""); + + status.AddFlag(StatusFlagOutOfRange::kTimeOut); + EXPECT_TRUE(status.IsFlagActive(StatusFlagOutOfRange::kTimeOut)); + + ASSERT_DEATH(status.IsFlagActive(StatusFlagOutOfRange::kUnknown), ""); +} + +} // namespace time +} // namespace score diff --git a/score/time/clock/clock_traits.h b/score/time/clock/clock_traits.h new file mode 100644 index 0000000..7f75b93 --- /dev/null +++ b/score/time/clock/clock_traits.h @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_CLOCK_CLOCK_TRAITS_H +#define SCORE_TIME_CLOCK_CLOCK_TRAITS_H + +namespace score +{ +namespace time +{ + +/// @brief Type and behaviour traits for a clock domain. +/// +/// The primary template is intentionally incomplete. Every clock domain must provide +/// a full explicit specialization supplying at minimum: +/// +/// - @c Backend — abstract backend type (virtual interface) +/// - @c Duration — std::chrono duration type +/// - @c Timepoint — std::chrono::time_point +/// - @c Snapshot — ClockSnapshot +/// - @c CallNow(const Backend&) — obtains a snapshot +/// +/// Readiness-check support is opt-in via @c AvailabilityHook (see availability_hook.h). +/// Subscription support is opt-in via @c SubscriptionHook (see subscription_hook.h). +/// Quality-status types (@c StatusFlag, @c Status) live on the tag struct itself (e.g. @c VehicleTime). +/// +template +struct ClockTraits; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_CLOCK_TRAITS_H diff --git a/score/time/clock/no_status.h b/score/time/clock/no_status.h new file mode 100644 index 0000000..214515a --- /dev/null +++ b/score/time/clock/no_status.h @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef SCORE_TIME_CLOCK_NO_STATUS_H +#define SCORE_TIME_CLOCK_NO_STATUS_H + +namespace score +{ +namespace time +{ + +/// @brief Empty placeholder status type for clocks that have no quality concept +/// (e.g. HplsClock, std::chrono::steady_clock). +/// +/// Used as the @c StatusT parameter of @c ClockSnapshot when the clock does not +/// produce a synchronisation-quality indicator alongside its time-point. +struct NoStatus +{ +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_NO_STATUS_H diff --git a/score/time/clock/subscription_hook.h b/score/time/clock/subscription_hook.h new file mode 100644 index 0000000..8af53e0 --- /dev/null +++ b/score/time/clock/subscription_hook.h @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_CLOCK_SUBSCRIPTION_HOOK_H +#define SCORE_TIME_CLOCK_SUBSCRIPTION_HOOK_H + +namespace score +{ +namespace time +{ + +/// @brief SFINAE gate for @c Clock::Subscribe() and @c Unsubscribe(). +/// +/// The primary template is intentionally undefined. Attempting to subscribe to an +/// EventType for which no specialization exists produces a compile error +/// (use of incomplete type). +/// +/// Each specialization must provide: +/// - @c Callback — the callable type accepted by @c Subscribe() +/// - @c static void Subscribe(Backend&, Callback) +/// - @c static void Unsubscribe(Backend&) +/// +/// @tparam Tag The clock domain tag (e.g. VehicleTime). +/// @tparam EventType The event struct type (e.g. TimeSlaveSyncData). +template +struct SubscriptionHook; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CLOCK_SUBSCRIPTION_HOOK_H diff --git a/score/time/common/BUILD b/score/time/common/BUILD index 9f7a55d..aba5d48 100644 --- a/score/time/common/BUILD +++ b/score/time/common/BUILD @@ -11,6 +11,7 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") cc_library( @@ -32,10 +33,17 @@ cc_test( "time_base_status_test.cpp", ], tags = ["unit"], - visibility = ["//score/time:__subpackages__"], deps = [ ":time_base_status", "@googletest//:gtest", "@googletest//:gtest_main", ], ) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":time_base_status_test", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/common/time_base_status_test.cpp b/score/time/common/time_base_status_test.cpp index 828f3bc..e9aba6e 100644 --- a/score/time/common/time_base_status_test.cpp +++ b/score/time/common/time_base_status_test.cpp @@ -34,10 +34,8 @@ class TestTimeBaseStatus : public ::testing::Test TEST_F(TestTimeBaseStatus, ObjectCreatedFromFlagList) { - // Create TimeBaseStatus object TimeBaseStatus time_base_status{StatusFlag::kSynchronized, StatusFlag::kSynchToGateway}; - // Verify that correct flags are set to active EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlag::kSynchronized)); EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlag::kSynchToGateway)); EXPECT_FALSE(time_base_status.IsAnyOfFlagsActive({StatusFlag::kTimeOut, StatusFlag::kUnknown})); @@ -45,14 +43,11 @@ TEST_F(TestTimeBaseStatus, ObjectCreatedFromFlagList) TEST_F(TestTimeBaseStatus, FlagsAppendedCorrectly) { - // Create TimeBaseStatus object TimeBaseStatus time_base_status{}; - // Verify that none of the flags should be active EXPECT_FALSE(time_base_status.IsAnyOfFlagsActive( {StatusFlag::kSynchToGateway, StatusFlag::kTimeOut, StatusFlag::kSynchronized, StatusFlag::kUnknown})); - // Append status flags using AddFlag method and verify they are added correctly time_base_status.AddFlag(StatusFlag::kSynchToGateway); time_base_status.AddFlag(StatusFlag::kTimeOut); @@ -60,7 +55,6 @@ TEST_F(TestTimeBaseStatus, FlagsAppendedCorrectly) EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlag::kTimeOut)); EXPECT_FALSE(time_base_status.IsAnyOfFlagsActive({StatusFlag::kSynchronized, StatusFlag::kUnknown})); - // Append status flags using AddFlag methods and verify they are added correctly time_base_status.AddFlag(StatusFlag::kUnknown); time_base_status.AddFlag(StatusFlag::kSynchronized); @@ -72,20 +66,16 @@ TEST_F(TestTimeBaseStatus, FlagsAppendedCorrectly) TEST_F(TestTimeBaseStatus, FlagAppendedMoreThanOnce) { - // Create TimeBaseStatus object and verify that correct flag is set to active TimeBaseStatus time_base_status{StatusFlag::kSynchronized}; EXPECT_FALSE(time_base_status.IsFlagActive(StatusFlag::kSynchToGateway)); - // Append new flag twice time_base_status.AddFlag(StatusFlag::kSynchToGateway); time_base_status.AddFlag(StatusFlag::kSynchToGateway); - // Verify correct flags are active despite adding kSynchToGateway twice EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlag::kSynchronized)); EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlag::kSynchToGateway)); EXPECT_FALSE(time_base_status.IsAnyOfFlagsActive({StatusFlag::kTimeOut, StatusFlag::kUnknown})); - // Append another status flag twice and verify flags time_base_status.AddFlag(StatusFlag::kTimeOut); time_base_status.AddFlag(StatusFlag::kTimeOut); @@ -97,39 +87,30 @@ TEST_F(TestTimeBaseStatus, FlagAppendedMoreThanOnce) TEST_F(TestTimeBaseStatus, StatusSerializedCorrectlyToFlagsContainer) { - // Create TimeBaseStatus object TimeBaseStatus time_base_status{StatusFlag::kSynchToGateway, StatusFlag::kTimeOut}; - - // Create status container from TimeBaseStatus object const auto status_container = time_base_status.ToUnderlying(); - // Create TimeBaseStatus object and get active flags from status container TimeBaseStatus deserialized_status{}; deserialized_status.FromUnderlying(status_container); - // Check if correct flags are active EXPECT_TRUE(deserialized_status.IsFlagActive(StatusFlag::kSynchToGateway)); EXPECT_TRUE(deserialized_status.IsFlagActive(StatusFlag::kTimeOut)); EXPECT_FALSE(deserialized_status.IsAnyOfFlagsActive({StatusFlag::kSynchronized, StatusFlag::kUnknown})); - // Create TimeBaseObject status and get active flags from status container std::uint8_t max_container_uint8 = std::numeric_limits::max(); TimeBaseStatus status_uint8{}; status_uint8.FromUnderlying(max_container_uint8); - // Check if correct flags are active - all flags should be set to active EXPECT_TRUE(status_uint8.IsFlagActive(StatusFlag::kSynchToGateway)); EXPECT_TRUE(status_uint8.IsFlagActive(StatusFlag::kTimeOut)); EXPECT_TRUE(status_uint8.IsFlagActive(StatusFlag::kSynchronized)); EXPECT_TRUE(status_uint8.IsFlagActive(StatusFlag::kUnknown)); - // Create TimeBaseObject status and get active flags from status container + // FromUnderlying truncates to the underlying type (uint8_t) on narrowing. std::uint32_t max_container_uint32 = std::numeric_limits::max(); TimeBaseStatus status_uint32{}; - // Implicit conversion to uint8_t status_uint32.FromUnderlying(max_container_uint32); - // Check if correct flags are active - all flags should be set to active EXPECT_TRUE(status_uint32.IsFlagActive(StatusFlag::kSynchToGateway)); EXPECT_TRUE(status_uint32.IsFlagActive(StatusFlag::kTimeOut)); EXPECT_TRUE(status_uint32.IsFlagActive(StatusFlag::kSynchronized)); @@ -138,29 +119,23 @@ TEST_F(TestTimeBaseStatus, StatusSerializedCorrectlyToFlagsContainer) TEST_F(TestTimeBaseStatus, StatusSerializedFromInvalidStatusFlagEnum) { - // Define StatusFlags with flags out of range enum class StatusFlagOutOfRange : StatusFlagType { - kTimeOut = 0x0, - kSynchronized = 0x1, + kTimeOut = 0x0, + kSynchronized = 0x1, kSynchToGateway = 200, - kUnknown = 255 + kUnknown = 255 }; - // Create TimeBaseStatus object TimeBaseStatus time_base_status{}; - // Add kSynchronized flag and verify it's active time_base_status.AddFlag(StatusFlagOutOfRange::kSynchronized); EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlagOutOfRange::kSynchronized)); - // Adding a flag should be terminated when trying to add out of range kSynchToGateway ASSERT_DEATH(time_base_status.AddFlag(StatusFlagOutOfRange::kSynchToGateway), ""); - // Add kTimeOut flag and verify it's active time_base_status.AddFlag(StatusFlagOutOfRange::kTimeOut); EXPECT_TRUE(time_base_status.IsFlagActive(StatusFlagOutOfRange::kTimeOut)); - // Adding a flag should be terminated when trying to check if out of range kUnknown flag is active ASSERT_DEATH(time_base_status.IsFlagActive(StatusFlagOutOfRange::kUnknown), ""); } diff --git a/score/time/hpls_time/BUILD b/score/time/hpls_time/BUILD new file mode 100644 index 0000000..ba7803d --- /dev/null +++ b/score/time/hpls_time/BUILD @@ -0,0 +1,131 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("@score_baselibs//third_party/itf:py_unittest_qnx_test.bzl", "py_unittest_qnx_test") + +# The public interface of the HplsTime clock domain +alias( + name = "hpls_time", + actual = ":hpls_clock_impl", + visibility = ["//visibility:public"], +) + +# The public interface for test purposes (includes mock backend) +alias( + name = "hpls_time_mock", + testonly = True, + actual = ":hpls_clock_mock", + visibility = ["//visibility:public"], +) + +# Interface without backend — for business-logic libraries that are tested +# via :hpls_time_mock and deployed via :hpls_time. +alias( + name = "interface", + actual = ":hpls_clock", + visibility = ["//visibility:public"], +) + +cc_library( + name = "hpls_clock_iface", + hdrs = [ + "hpls_clock_iface.h", + "hpls_time.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "hpls_clock", + srcs = ["hpls_clock.cpp"], + hdrs = ["hpls_clock.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], + deps = [ + ":hpls_clock_iface", + "//score/time/clock:clock_core", + ], +) + +cc_library( + name = "hpls_clock_mock", + testonly = True, + hdrs = ["hpls_time_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + ":hpls_clock_iface", + # Stub backend — provides CreateBackend() at link time. + # Bundled here so test targets only need hpls_time_mock as a single dep. + "//score/time/hpls_time/details/stub_impl", + ":hpls_clock", + "@googletest//:gtest", + ], +) + +# Production entry point: hpls_clock + production backend in a single dep. +# Production binaries dep on :hpls_time; test binaries dep on :hpls_time_mock. +cc_library( + name = "hpls_clock_impl", + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/time:__subpackages__", + ], + deps = select({ + "@platforms//os:qnx": ["//score/time/hpls_time/details/qtime:qclock"], + "//conditions:default": ["//score/time/hpls_time/details/system_clock"], + }) + [ + ":hpls_clock", + ], +) + +py_unittest_qnx_test( + name = "qnx_unit_test_cases", + test_suites = [ + "//score/time/hpls_time/details:qnx_unit_test_cases", + ], + visibility = ["//score/time:__pkg__"], +) + +cc_test( + name = "hpls_clock_test", + srcs = ["hpls_clock_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":hpls_time_mock", + "//score/time/clock:clock_core", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":hpls_clock_test", + ], + test_suites_from_sub_packages = [ + "//score/time/hpls_time/details:unit_test_suite", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/hpls_time/details/BUILD b/score/time/hpls_time/details/BUILD new file mode 100644 index 0000000..3dfb394 --- /dev/null +++ b/score/time/hpls_time/details/BUILD @@ -0,0 +1,33 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//third_party/itf:py_unittest_qnx_test.bzl", "py_unittest_qnx_test") + +py_unittest_qnx_test( + name = "qnx_unit_test_cases", + test_suites = [ + "//score/time/hpls_time/details/qtime:qnx_unit_test_cases", + "//score/time/hpls_time/details/system_clock:qnx_unit_test_cases", + ], + visibility = ["//score/time/hpls_time:__pkg__"], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [], + test_suites_from_sub_packages = [ + "//score/time/hpls_time/details/system_clock:unit_test_suite", + ], + visibility = ["//score/time/hpls_time:__pkg__"], +) diff --git a/score/time/hpls_time/details/qtime/BUILD b/score/time/hpls_time/details/qtime/BUILD new file mode 100644 index 0000000..ea22243 --- /dev/null +++ b/score/time/hpls_time/details/qtime/BUILD @@ -0,0 +1,100 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("@score_baselibs//third_party/itf:py_unittest_qnx_test.bzl", "py_unittest_qnx_test") + +cc_library( + name = "qclock", + srcs = [ + "hpls_qclock.cpp", + "tick_provider.cpp", + "tick_provider.h", + ], + hdrs = [ + "hpls_qclock.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + target_compatible_with = ["@platforms//os:qnx"], + visibility = ["//score/time/hpls_time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/hpls_time:hpls_clock", + "//score/time/hpls_time:hpls_clock_iface", + "@score_baselibs//score/os/qnx:neutrino", + ], +) + +cc_test( + name = "hpls_qclock_test", + testonly = True, + srcs = [ + "hpls_qclock.cpp", + "hpls_qclock.h", + "hpls_qclock_test.cpp", + "tick_provider.h", + "tick_provider_mock.cpp", + "tick_provider_mock.h", + ], + tags = ["unit"], + target_compatible_with = ["@platforms//os:qnx"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/hpls_time:hpls_clock", + "//score/time/hpls_time:hpls_clock_iface", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score_baselibs//score/os/qnx:neutrino_qnx_mock", + ], +) + +cc_test( + name = "tick_provider_test", + testonly = True, + srcs = [ + "tick_provider.cpp", + "tick_provider.h", + "tick_provider_test.cpp", + ], + tags = ["unit"], + target_compatible_with = ["@platforms//os:qnx"], + deps = [ + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "factory_test", + srcs = [ + "factory_test.cpp", + ], + tags = ["unit"], + target_compatible_with = ["@platforms//os:qnx"], + deps = [ + ":qclock", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +py_unittest_qnx_test( + name = "qnx_unit_test_cases", + test_cases = [ + ":factory_test", + ":hpls_qclock_test", + ":tick_provider_test", + ], + visibility = ["//score/time/hpls_time/details:__pkg__"], +) diff --git a/score/time/hpls_time/details/qtime/factory_test.cpp b/score/time/hpls_time/details/qtime/factory_test.cpp new file mode 100644 index 0000000..f3bd14d --- /dev/null +++ b/score/time/hpls_time/details/qtime/factory_test.cpp @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/qtime/hpls_qclock.h" +#include "score/time/hpls_time/hpls_clock.h" + +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ +namespace +{ + +TEST(HplsQClockFactoryTest, CreateBackendReturnsNonNull) +{ + RecordProperty("Description", + "Verifies that detail::CreateBackend() returns a valid shared_ptr."); + RecordProperty("Verifies", "score::time::detail::CreateBackend()"); + RecordProperty("TestType", "Interface test"); + RecordProperty("ASIL", "QM"); + + auto backend = detail::CreateBackend(); + ASSERT_NE(backend, nullptr); +} + +TEST(HplsQClockFactoryTest, CreateBackendReturnsHplsQClock) +{ + RecordProperty("Description", + "Verifies that detail::CreateBackend() returns an HplsQClock instance."); + RecordProperty("ASIL", "QM"); + + auto backend = detail::CreateBackend(); + ASSERT_NE(backend, nullptr); + EXPECT_NE(dynamic_cast(backend.get()), nullptr); +} + +} // namespace +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/qtime/hpls_qclock.cpp b/score/time/hpls_time/details/qtime/hpls_qclock.cpp new file mode 100644 index 0000000..b0ff9ee --- /dev/null +++ b/score/time/hpls_time/details/qtime/hpls_qclock.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/qtime/hpls_qclock.h" + +#include "score/time/hpls_time/details/qtime/tick_provider.h" +#include "score/time/hpls_time/hpls_time.h" +#include "score/time/hpls_time/hpls_clock.h" + +#include "score/lib/os/qnx/neutrino.h" + +#include +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ + +ClockSnapshot HplsQClock::Now() const noexcept +{ + const HplsTime::Timepoint tp{ + ClockCyclesToNanoseconds(score::os::qnx::Neutrino::instance().ClockCycles())}; + return ClockSnapshot{tp, NoStatus{}}; +} + +std::chrono::nanoseconds HplsQClock::ClockCyclesToNanoseconds( + const std::uint64_t clock_cycles) const noexcept +{ + const std::uint64_t cycles_per_sec = GetClockCyclesPerSec(); + std::chrono::nanoseconds converted{0}; + + if (cycles_per_sec > 0U) + { + constexpr auto billion = static_cast(std::nano::den); + constexpr auto max_value = static_cast(std::chrono::nanoseconds::max().count()); + constexpr std::uint64_t upper_bound{max_value / billion}; + const std::uint64_t division_result{clock_cycles / cycles_per_sec}; + const std::uint64_t division_rest{clock_cycles % cycles_per_sec}; + if ((division_result <= upper_bound) && (division_rest <= upper_bound)) + { + converted = std::chrono::nanoseconds{division_result * billion + + division_rest * billion / cycles_per_sec}; + } + } + + return converted; +} + +} // namespace qtime +} // namespace hpls_time + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/qtime/hpls_qclock.h b/score/time/hpls_time/details/qtime/hpls_qclock.h new file mode 100644 index 0000000..b57e780 --- /dev/null +++ b/score/time/hpls_time/details/qtime/hpls_qclock.h @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_DETAILS_HPLSCIMPL_QTIME_HPLS_QCLOCK_H +#define SCORE_TIME_HPLS_TIME_DETAILS_HPLSCIMPL_QTIME_HPLS_QCLOCK_H + +#include "score/time/hpls_time/hpls_clock_iface.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ + +/// +/// \brief QNX production backend for HplsTime. +/// +/// Reads the current time from the QNX hardware clock via @c Neutrino::ClockCycles() +/// and converts the raw cycle count to nanoseconds using @c GetClockCyclesPerSec(). +/// +class HplsQClock final : public HplsClockIface +{ + public: + HplsQClock() noexcept = default; + HplsQClock(const HplsQClock&) noexcept = delete; + HplsQClock& operator=(const HplsQClock&) = delete; + HplsQClock(HplsQClock&&) noexcept = delete; + HplsQClock& operator=(HplsQClock&&) = delete; + ~HplsQClock() noexcept override = default; + + /// \brief Returns the current HPLS snapshot from the QNX hardware clock. + ClockSnapshot Now() const noexcept override; + + private: + /// \brief Converts raw hardware clock cycles to nanoseconds. + std::chrono::nanoseconds ClockCyclesToNanoseconds(std::uint64_t clock_cycles) const noexcept; +}; + +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_DETAILS_QTIME_HPLS_QCLOCK_H diff --git a/score/time/hpls_time/details/qtime/hpls_qclock_test.cpp b/score/time/hpls_time/details/qtime/hpls_qclock_test.cpp new file mode 100644 index 0000000..c26262c --- /dev/null +++ b/score/time/hpls_time/details/qtime/hpls_qclock_test.cpp @@ -0,0 +1,178 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/qtime/hpls_qclock.h" +#include "score/time/hpls_time/details/qtime/tick_provider_mock.h" + +#include "score/lib/os/mocklib/qnx/neutrino_qnx_mock.h" + +#include +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ +namespace test +{ + +using namespace ::testing; + +struct TestCaseParams +{ + std::uint64_t tickCount{}; + std::uint64_t tickPerSec{}; + std::uint64_t tickInNano{}; + + friend void PrintTo(const TestCaseParams& p, std::ostream* os) + { + *os << "tickCount=" << p.tickCount << ", tickPerSec=" << p.tickPerSec + << ", tickInNano=" << p.tickInNano; + } +}; + +class HplsQClockTest : public ::testing::TestWithParam +{ + public: + void SetUp() override + { + TickProviderMock::CreateMockInstance(); + } + + void TearDown() override + { + TickProviderMock::DestroyMockInstance(); + } +}; + +// clang-format off +INSTANTIATE_TEST_SUITE_P( + HplsQClockInstantiate, + HplsQClockTest, + ::testing::Values( + // Zero ticks + TestCaseParams{0, 19'200'000, 0}, + // Some values + TestCaseParams{3'462'637'232, 19'200'000, 180'345'689'166}, + // ~960 s + TestCaseParams{2'266'560'000'000, 2'361'000'000, 960'000'000'000}, + // ~1 hour + TestCaseParams{8'499'600'654'123, 2'361'000'000, 3'600'000'277'053}, + // ~24 hours + TestCaseParams{203'990'415'698'952, 2'361'000'000, 86'400'006'649'280}, + // ~48 hours + TestCaseParams{407'980'831'397'904, 2'361'000'000, 172'800'013'298'561}, + // ~96 hours + TestCaseParams{815'961'662'795'808, 2'361'000'000, 345'600'026'597'123}, + // ~22 days + TestCaseParams{0x000FFFFFFFFFFFFF, 2'361'000'000, 1'907'496'665'552'941}, + // ~353 days + TestCaseParams{0x00FFFFFFFFFFFFFF, 2'361'000'000, 30'519'946'648'847'071}, + // ~15 years + TestCaseParams{0x0FFFFFFFFFFFFFFF, 2'361'000'000, 488'319'146'381'553'144}, + // ~247 years + TestCaseParams{0xFFFFFFFFFFFFFFFF, 2'361'000'000, 7'813'106'342'104'850'324}, + // boundary: cycles_per_sec == max + TestCaseParams{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 1000000000}, + // overflow → returns 0 + TestCaseParams{0xFFFFFFFFFFFFFFFF, 1, 0}, + TestCaseParams{0xFFFFFFFFFFFFFFFF, 1'000'000'000, 0}, + TestCaseParams{0xFFFFFFFFF, 1, 0}, + // zero cycles_per_sec → returns 0 + TestCaseParams{3'462'637'232, 0, 0} + )); +// clang-format on + +TEST_P(HplsQClockTest, ConvertsCyclesToNanoseconds) +{ + RecordProperty("Description", + "Verifies that HplsQClock::Now() converts QNX hardware clock cycles to nanoseconds " + "using the equation: ns = cycles * 1e9 / cycles_per_sec"); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + RecordProperty("ASIL", "QM"); + + const auto params = GetParam(); + + score::os::qnx::NeutrinoMock neutrino_mock; + score::os::qnx::Neutrino::set_testing_instance(neutrino_mock); + + EXPECT_CALL(neutrino_mock, ClockCycles()).Times(Exactly(1)).WillOnce(Return(params.tickCount)); + EXPECT_CALL(TickProviderMock::GetMockInstance(), GetClockCyclesPerSec()) + .Times(Exactly(1)) + .WillOnce(Return(params.tickPerSec)); + + HplsQClock clock; + const auto snapshot = clock.Now(); + + EXPECT_EQ(params.tickInNano, snapshot.TimePointNs().count()); +} + +TEST(HplsQClockTest, WhenClockCyclesReturnZeroNowReturnsZero) +{ + RecordProperty("Description", + "Fault injection: Neutrino::ClockCycles() returns 0 — Now() must return 0 ns."); + RecordProperty("TestType", "Fault injection test"); + RecordProperty("ASIL", "QM"); + + constexpr std::uint64_t kZeroCycles = 0U; + constexpr std::uint64_t kValidCpsValue = 10U; + + TickProviderMock::CreateMockInstance(); + auto& tick_mock = TickProviderMock::GetMockInstance(); + + score::os::qnx::NeutrinoMock neutrino_mock; + score::os::qnx::Neutrino::set_testing_instance(neutrino_mock); + + EXPECT_CALL(neutrino_mock, ClockCycles()).WillOnce(Return(kZeroCycles)); + EXPECT_CALL(tick_mock, GetClockCyclesPerSec()).WillOnce(Return(kValidCpsValue)); + + HplsQClock clock; + EXPECT_EQ(clock.Now().TimePointNs().count(), 0); + + TickProviderMock::DestroyMockInstance(); +} + +TEST(HplsQClockTest, WhenClockCyclesAreValidNowReturnsCorrectNanoseconds) +{ + RecordProperty("Description", + "Verifies that HplsQClock::Now() returns the correct ns for known valid inputs."); + RecordProperty("ASIL", "QM"); + + constexpr std::uint64_t kCps = 100U; + constexpr std::uint64_t kCycles = 25U; + + TickProviderMock::CreateMockInstance(); + auto& tick_mock = TickProviderMock::GetMockInstance(); + + score::os::qnx::NeutrinoMock neutrino_mock; + score::os::qnx::Neutrino::set_testing_instance(neutrino_mock); + + EXPECT_CALL(tick_mock, GetClockCyclesPerSec()).WillOnce(Return(kCps)); + EXPECT_CALL(neutrino_mock, ClockCycles()).WillOnce(Return(kCycles)); + + HplsQClock clock; + EXPECT_EQ(clock.Now().TimePointNs().count(), + kCycles * 1'000'000'000U / kCps); + + TickProviderMock::DestroyMockInstance(); +} + +} // namespace test +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/qtime/tick_provider.cpp b/score/time/hpls_time/details/qtime/tick_provider.cpp new file mode 100644 index 0000000..1752d25 --- /dev/null +++ b/score/time/hpls_time/details/qtime/tick_provider.cpp @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/qtime/tick_provider.h" + +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ + +std::uint64_t GetClockCyclesPerSec() +{ + return static_cast(SYSPAGE_ENTRY(qtime)->cycles_per_sec); +} + +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/qtime/tick_provider.h b/score/time/hpls_time/details/qtime/tick_provider.h new file mode 100644 index 0000000..98430e9 --- /dev/null +++ b/score/time/hpls_time/details/qtime/tick_provider.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLSTIME_DETAILS_QTIME_TICK_PROVIDER_H +#define SCORE_TIME_HPLSTIME_DETAILS_QTIME_TICK_PROVIDER_H + +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ + +/// \brief Returns the number of hardware clock cycles per second from the QNX syspage. +/// +/// Extracted as a free function so it can be replaced by a mock in unit tests. +std::uint64_t GetClockCyclesPerSec(); + +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLSTIME_DETAILS_QTIME_TICK_PROVIDER_H diff --git a/score/time/hpls_time/details/qtime/tick_provider_mock.cpp b/score/time/hpls_time/details/qtime/tick_provider_mock.cpp new file mode 100644 index 0000000..97453e7 --- /dev/null +++ b/score/time/hpls_time/details/qtime/tick_provider_mock.cpp @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/qtime/tick_provider_mock.h" + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ + +// GetClockCyclesPerSec() routes to the mock when the mock is active. +std::uint64_t GetClockCyclesPerSec() +{ + return TickProviderMock::GetMockInstance().GetClockCyclesPerSec(); +} + +std::unique_ptr TickProviderMock::mock_instance_{}; +std::mutex TickProviderMock::mutex_{}; + +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/qtime/tick_provider_mock.h b/score/time/hpls_time/details/qtime/tick_provider_mock.h new file mode 100644 index 0000000..6438e00 --- /dev/null +++ b/score/time/hpls_time/details/qtime/tick_provider_mock.h @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_DETAILS_QTIME_TICK_PROVIDER_MOCK_H +#define SCORE_TIME_HPLS_TIME_DETAILS_QTIME_TICK_PROVIDER_MOCK_H + +#include "score/time/hpls_time/details/qtime/tick_provider.h" + +#include +#include +#include +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ + +/// \brief Singleton GMock replacement for @c GetClockCyclesPerSec(). +/// +/// Usage: +/// @code +/// TickProviderMock::CreateMockInstance(); +/// EXPECT_CALL(TickProviderMock::GetMockInstance(), GetClockCyclesPerSec()).WillOnce(Return(1'000'000'000U)); +/// // ... run code under test ... +/// TickProviderMock::DestroyMockInstance(); +/// @endcode +class TickProviderMock +{ + public: + MOCK_METHOD(std::uint64_t, GetClockCyclesPerSec, (), ()); + + static TickProviderMock& GetMockInstance() noexcept + { + const std::lock_guard guard{mutex_}; + assert(mock_instance_.operator bool()); + return *mock_instance_; + } + + static void CreateMockInstance() + { + const std::lock_guard guard{mutex_}; + mock_instance_ = std::make_unique(); + } + + static void DestroyMockInstance() noexcept + { + const std::lock_guard guard{mutex_}; + testing::Mock::VerifyAndClearExpectations(mock_instance_.get()); + mock_instance_.reset(); + } + + private: + static std::unique_ptr mock_instance_; + static std::mutex mutex_; +}; + +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_DETAILS_QTIME_TICK_PROVIDER_MOCK_H diff --git a/score/time/hpls_time/details/qtime/tick_provider_test.cpp b/score/time/hpls_time/details/qtime/tick_provider_test.cpp new file mode 100644 index 0000000..4f766b8 --- /dev/null +++ b/score/time/hpls_time/details/qtime/tick_provider_test.cpp @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/qtime/tick_provider.h" + +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace qtime +{ +namespace test +{ + +TEST(TickProviderTest, GetClockCyclesPerSecReturnsPositiveValue) +{ + RecordProperty("Description", "Verifies that GetClockCyclesPerSec() returns a positive value on QNX."); + RecordProperty("Verifies", "score::time::hpls_time::qtime::GetClockCyclesPerSec()"); + RecordProperty("TestType", "Interface test"); + RecordProperty("DerivationTechnique", "Error guessing based on knowledge or experience"); + RecordProperty("ASIL", "QM"); + + EXPECT_GT(GetClockCyclesPerSec(), 0U); +} + +} // namespace test +} // namespace qtime +} // namespace hpls_time +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/stub_impl/BUILD b/score/time/hpls_time/details/stub_impl/BUILD new file mode 100644 index 0000000..a9a4e38 --- /dev/null +++ b/score/time/hpls_time/details/stub_impl/BUILD @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Stub backend for unit tests. +# Provides CreateBackend() via std::chrono::steady_clock +cc_library( + name = "stub_impl", + testonly = True, + srcs = ["hpls_clock_impl.cpp"], + hdrs = ["hpls_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/hpls_time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/hpls_time:hpls_clock", + "//score/time/hpls_time:hpls_clock_iface", + ], +) diff --git a/score/time/hpls_time/details/stub_impl/hpls_clock_impl.cpp b/score/time/hpls_time/details/stub_impl/hpls_clock_impl.cpp new file mode 100644 index 0000000..4d07349 --- /dev/null +++ b/score/time/hpls_time/details/stub_impl/hpls_clock_impl.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/stub_impl/hpls_clock_impl.h" +#include "score/time/hpls_time/hpls_clock.h" + +#include + +namespace score +{ +namespace time +{ + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/stub_impl/hpls_clock_impl.h b/score/time/hpls_time/details/stub_impl/hpls_clock_impl.h new file mode 100644 index 0000000..36262b6 --- /dev/null +++ b/score/time/hpls_time/details/stub_impl/hpls_clock_impl.h @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_DETAILS_STUB_IMPL_HPLS_CLOCK_IMPL_H +#define SCORE_TIME_HPLS_TIME_DETAILS_STUB_IMPL_HPLS_CLOCK_IMPL_H + +// Internal header — include ONLY from stub_impl/hpls_clock_impl.cpp. + +#include "score/time/hpls_time/hpls_clock_iface.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Stub backend for the HPLS clock domain (host/test-only). +/// +/// Wraps std::chrono::steady_clock so that Clock::GetInstance() +/// resolves at link time even when ClockOverrideGuard is not active. +class HplsClockImpl final : public HplsClockIface +{ + public: + HplsClockImpl() noexcept = default; + ~HplsClockImpl() noexcept override = default; + HplsClockImpl(const HplsClockImpl&) = delete; + HplsClockImpl& operator=(const HplsClockImpl&) = delete; + HplsClockImpl(HplsClockImpl&&) = delete; + HplsClockImpl& operator=(HplsClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override + { + const auto raw = std::chrono::steady_clock::now().time_since_epoch(); + const HplsTime::Timepoint tp{std::chrono::duration_cast(raw)}; + return ClockSnapshot{tp, NoStatus{}}; + } +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_DETAILS_STUB_IMPL_HPLS_CLOCK_IMPL_H diff --git a/score/time/hpls_time/details/system_clock/BUILD b/score/time/hpls_time/details/system_clock/BUILD new file mode 100644 index 0000000..d7236cb --- /dev/null +++ b/score/time/hpls_time/details/system_clock/BUILD @@ -0,0 +1,60 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Non-QNX production backend for HplsTime. +# Wraps std::chrono::high_resolution_clock and provides CreateBackend(). +cc_library( + name = "system_clock", + srcs = [ + "hpls_time_impl.cpp", + ], + hdrs = [ + "hpls_time_impl.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/hpls_time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/hpls_time:hpls_clock", + "//score/time/hpls_time:hpls_clock_iface", + ], +) + +cc_test( + name = "hpls_time_impl_test", + testonly = True, + srcs = [ + "hpls_time_impl.cpp", + "hpls_time_impl.h", + "hpls_time_impl_test.cpp", + ], + tags = ["unit"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/hpls_time:hpls_clock", + "//score/time/hpls_time:hpls_clock_iface", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":hpls_time_impl_test", + ], + visibility = ["//score/time/hpls_time/details:__pkg__"], +) diff --git a/score/time/hpls_time/details/system_clock/hpls_time_impl.cpp b/score/time/hpls_time/details/system_clock/hpls_time_impl.cpp new file mode 100644 index 0000000..f98f75c --- /dev/null +++ b/score/time/hpls_time/details/system_clock/hpls_time_impl.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/system_clock/hpls_time_impl.h" + +#include "score/time/hpls_time/hpls_time.h" +#include "score/time/hpls_time/hpls_clock.h" + +#include +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace sys_time +{ + +HplsTimeImpl::HplsTimeImpl() noexcept = default; +HplsTimeImpl::~HplsTimeImpl() noexcept = default; + +ClockSnapshot HplsTimeImpl::Now() const noexcept +{ + const auto raw = std::chrono::high_resolution_clock::now().time_since_epoch(); + const HplsTime::Timepoint tp{std::chrono::duration_cast(raw)}; + return ClockSnapshot{tp, NoStatus{}}; +} + +} // namespace sys_time +} // namespace hpls_time + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/details/system_clock/hpls_time_impl.h b/score/time/hpls_time/details/system_clock/hpls_time_impl.h new file mode 100644 index 0000000..47c3901 --- /dev/null +++ b/score/time/hpls_time/details/system_clock/hpls_time_impl.h @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_DETAILS_SYSTEMCLOCK_HPLS_TIME_IMPL_H +#define SCORE_TIME_HPLS_TIME_DETAILS_SYSTEMCLOCK_HPLS_TIME_IMPL_H + +#include "score/time/hpls_time/hpls_clock_iface.h" +#include "score/time/clock/no_status.h" + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace sys_time +{ + +/// +/// \brief Production HplsTime backend for non-QNX platforms. +/// +/// Reads the current time from @c std::chrono::high_resolution_clock and +/// converts it to an @c HplsTime::Timepoint. +/// +class HplsTimeImpl final : public HplsClockIface +{ + public: + HplsTimeImpl() noexcept; + HplsTimeImpl(const HplsTimeImpl&) noexcept = delete; + HplsTimeImpl& operator=(const HplsTimeImpl&) noexcept = delete; + HplsTimeImpl(HplsTimeImpl&&) noexcept = delete; + HplsTimeImpl& operator=(HplsTimeImpl&&) noexcept = delete; + ~HplsTimeImpl() noexcept override; + + /// \brief Returns the current HPLS snapshot using @c high_resolution_clock. + ClockSnapshot Now() const noexcept override; +}; + +} // namespace sys_time +} // namespace hpls_time +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_DETAILS_SYSTEMCLOCK_HPLS_TIME_IMPL_H diff --git a/score/time/hpls_time/details/system_clock/hpls_time_impl_test.cpp b/score/time/hpls_time/details/system_clock/hpls_time_impl_test.cpp new file mode 100644 index 0000000..62c59c3 --- /dev/null +++ b/score/time/hpls_time/details/system_clock/hpls_time_impl_test.cpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/details/system_clock/hpls_time_impl.h" + +#include +#include + +namespace score +{ +namespace time +{ +namespace hpls_time +{ +namespace sys_time +{ +namespace test +{ + +class HplsTimeImplTest : public ::testing::Test +{ +}; + +TEST_F(HplsTimeImplTest, TimePointIsNotNegative) +{ + RecordProperty("Description", + "Verifies that HplsTimeImpl::Now() returns a non-negative time point " + "and that the clock is strictly monotonically increasing."); + RecordProperty("Verifies", "score::time::hpls_time::sys_time::HplsTimeImpl::Now()"); + RecordProperty("TestType", "Interface test"); + RecordProperty("DerivationTechnique", "Error guessing based on knowledge or experience"); + RecordProperty("ASIL", "QM"); + + HplsTimeImpl clock; + const auto snapshot = clock.Now(); + + ASSERT_GE(snapshot.TimePointNs().count(), 0); + + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + + EXPECT_GT(clock.Now().TimePoint(), snapshot.TimePoint()); +} + +} // namespace test +} // namespace sys_time +} // namespace hpls_time +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/hpls_clock.cpp b/score/time/hpls_time/hpls_clock.cpp new file mode 100644 index 0000000..6d7576b --- /dev/null +++ b/score/time/hpls_time/hpls_clock.cpp @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/hpls_clock.h" +#include "score/time/hpls_time/hpls_clock_iface.h" + +namespace score +{ +namespace time +{ + +ClockTraits::Snapshot ClockTraits::CallNow(const Backend& impl) noexcept +{ + return impl.Now(); +} + +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/hpls_clock.h b/score/time/hpls_time/hpls_clock.h new file mode 100644 index 0000000..b15abd6 --- /dev/null +++ b/score/time/hpls_time/hpls_clock.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_HPLS_CLOCK_H +#define SCORE_TIME_HPLS_TIME_HPLS_CLOCK_H + +#include "score/time/hpls_time/hpls_time.h" +#include "score/time/clock/clock.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + + +namespace score +{ +namespace time +{ + +class HplsClockIface; + +template <> +struct ClockTraits +{ + using Backend = HplsClockIface; + using Duration = HplsTime::Duration; + using Timepoint = HplsTime::Timepoint; + using Snapshot = ClockSnapshot; + + /// \brief Obtains the current HPLS clock snapshot from the backend. + static Snapshot CallNow(const Backend& impl) noexcept; +}; + +using HplsClock = Clock; +using HplsTimePoint = ClockTraits::Timepoint; +using HplsSnapshot = ClockTraits::Snapshot; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_HPLS_CLOCK_H diff --git a/score/time/hpls_time/hpls_clock_iface.h b/score/time/hpls_time/hpls_clock_iface.h new file mode 100644 index 0000000..148cf36 --- /dev/null +++ b/score/time/hpls_time/hpls_clock_iface.h @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_HPLS_CLOCK_IFACE_H +#define SCORE_TIME_HPLS_TIME_HPLS_CLOCK_IFACE_H + +#include "score/time/hpls_time/hpls_time.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +namespace score +{ +namespace time +{ + +/// +/// \brief Pure-virtual pimpl interface for the HPLSC time domain backend. +class HplsClockIface +{ + public: + virtual ~HplsClockIface() noexcept = default; + + /// \brief Returns the current HPLSC snapshot (time-point; status is NoStatus). + virtual ClockSnapshot Now() const noexcept = 0; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_HPLS_CLOCK_IFACE_H diff --git a/score/time/hpls_time/hpls_clock_test.cpp b/score/time/hpls_time/hpls_clock_test.cpp new file mode 100644 index 0000000..6821ff8 --- /dev/null +++ b/score/time/hpls_time/hpls_clock_test.cpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/hpls_time_mock.h" +#include "score/time/clock/clock_override_guard.h" + +#include +#include + +#include +#include + +using ::testing::Return; + +namespace score +{ +namespace time +{ + +// ── UC5: HplsClock::Now().time_point + duration arithmetic ─────────────────── + +TEST(HplsClockTest, NowReturnsTimepointSuitableForDurationArithmetic) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const HplsTime::Timepoint tp{std::chrono::nanoseconds{1'000'000LL}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + + const auto result = HplsClock::GetInstance().Now(); + const auto deadline = result.TimePoint() + std::chrono::seconds{3}; + + EXPECT_EQ(deadline.time_since_epoch(), + std::chrono::nanoseconds{1'000'000LL} + std::chrono::seconds{3}); +} + +TEST(HplsClockTest, NowReturnsZeroTimepointByDefault) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{HplsTime::Timepoint{}, NoStatus{}})); + + const auto result = HplsClock::GetInstance().Now(); + + EXPECT_EQ(result.TimePoint().time_since_epoch(), std::chrono::nanoseconds{0}); +} + +TEST(HplsClockTest, NowSnapshotCarriesNoStatus) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{HplsTime::Timepoint{}, NoStatus{}})); + + const auto result = HplsClock::GetInstance().Now(); + // NoStatus is an empty struct — verify it is accessible (compile + link check). + const NoStatus status = result.Status(); + (void)status; + SUCCEED(); +} + +} // namespace time +} // namespace score diff --git a/score/time/hpls_time/hpls_time.h b/score/time/hpls_time/hpls_time.h new file mode 100644 index 0000000..66a21fd --- /dev/null +++ b/score/time/hpls_time/hpls_time.h @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_HPLS_TIME_H +#define SCORE_TIME_HPLS_TIME_HPLS_TIME_H + +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Tag struct for the High-Precision Local Steady Clock (HPLSC) time domain. +struct HplsTime +{ + using Duration = std::chrono::nanoseconds; + using Timepoint = std::chrono::time_point; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_HPLS_TIME_H diff --git a/score/time/hpls_time/hpls_time_mock.h b/score/time/hpls_time/hpls_time_mock.h new file mode 100644 index 0000000..b13525e --- /dev/null +++ b/score/time/hpls_time/hpls_time_mock.h @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_HPLS_TIME_HPLS_TIME_MOCK_H +#define SCORE_TIME_HPLS_TIME_HPLS_TIME_MOCK_H + +#include "score/time/hpls_time/hpls_clock_iface.h" +#include "score/time/hpls_time/hpls_clock.h" + +#include + +namespace score +{ +namespace time +{ + +/// @brief GMock test double for the HPLS clock domain. +/// +/// Implements @c HplsClockIface so it can be injected via +/// @c ClockOverrideGuard in unit tests. +/// +/// Usage: +/// @code +/// auto mock = std::make_shared(); +/// ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// @endcode +class HplsTimeMock : public HplsClockIface +{ + public: + HplsTimeMock() = default; + ~HplsTimeMock() noexcept override = default; + HplsTimeMock(const HplsTimeMock&) = delete; + HplsTimeMock& operator=(const HplsTimeMock&) = delete; + HplsTimeMock(HplsTimeMock&&) = delete; + HplsTimeMock& operator=(HplsTimeMock&&) = delete; + + MOCK_METHOD((ClockSnapshot), + Now, + (), + (const, noexcept, override)); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_HPLS_TIME_HPLS_TIME_MOCK_H diff --git a/score/time/hpls_time/test/benchmark/BUILD b/score/time/hpls_time/test/benchmark/BUILD new file mode 100644 index 0000000..d4c1637 --- /dev/null +++ b/score/time/hpls_time/test/benchmark/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +cc_binary( + name = "HplsTimeBenchmark", + srcs = [ + "benchmark.cpp", + ], + features = [ + "third_party_warnings", + ], + deps = [ + "//score/time/hpls_time", + "@google_benchmark//:benchmark_main", + ], +) diff --git a/score/time/hpls_time/test/benchmark/benchmark.cpp b/score/time/hpls_time/test/benchmark/benchmark.cpp new file mode 100644 index 0000000..81f5ef6 --- /dev/null +++ b/score/time/hpls_time/test/benchmark/benchmark.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/hpls_time/hpls_clock.h" + +#include + +namespace score +{ +namespace time +{ + +class HplsClockBenchmarkFixture : public benchmark::Fixture +{ + public: + HplsClockBenchmarkFixture() : clock_{HplsClock::GetInstance()} + { + this->Repetitions(1); + this->ReportAggregatesOnly(true); + this->ThreadRange(1, 16); + this->UseRealTime(); + this->MeasureProcessCPUTime(); + } + + HplsClock clock_; +}; + +// Benchmark HplsClock::Now() end-to-end latency using the production backend. +BENCHMARK_F(HplsClockBenchmarkFixture, NowLatency)(benchmark::State& state) +{ + for (auto _ : state) + { + benchmark::DoNotOptimize(clock_.Now()); + } +} + +// Run the benchmark +BENCHMARK_MAIN(); + +} // namespace time +} // namespace score diff --git a/score/time/ptp/BUILD b/score/time/ptp/BUILD new file mode 100644 index 0000000..9f8d1c8 --- /dev/null +++ b/score/time/ptp/BUILD @@ -0,0 +1,68 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# PTP-protocol notification data types, reusable across all PTP time domains +# (e.g. VehicleTime, EptmTime, ...). +# +# These types are purely structural (header-only, no OS/IPC deps) and carry no +# knowledge of any specific time domain. Time-domain tags are injected as +# template parameters (Timebase). +cc_library( + name = "ptp_types", + hdrs = [ + "local_ptp_device_timer.h", + "master_ptp_device_timer.h", + "pdelay_measurement_data.h", + "port_identity.h", + "time_slave_sync_data.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], +) + +cc_test( + name = "time_slave_sync_data_test", + srcs = ["time_slave_sync_data_test.cpp"], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + ":ptp_types", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "pdelay_measurement_data_test", + srcs = ["pdelay_measurement_data_test.cpp"], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + ":ptp_types", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":time_slave_sync_data_test", + ":pdelay_measurement_data_test", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/ptp/local_ptp_device_timer.h b/score/time/ptp/local_ptp_device_timer.h new file mode 100644 index 0000000..dbd51ac --- /dev/null +++ b/score/time/ptp/local_ptp_device_timer.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_PTP_LOCAL_PTP_DEVICE_TIMER_H +#define SCORE_TIME_PTP_LOCAL_PTP_DEVICE_TIMER_H + +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Tag for the HW timer on the local Ethernet device where PTP messages are +/// received and which gets utilized for ingress & egress timestamping. +/// +struct LocalPTPDeviceTimer +{ + using Duration = std::chrono::nanoseconds; +}; + +/// \brief Time-point type for the local PTP device timer. +using LocalPTPDeviceTimerValue = std::chrono::time_point; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_PTP_LOCAL_PTP_DEVICE_TIMER_H diff --git a/score/time/ptp/master_ptp_device_timer.h b/score/time/ptp/master_ptp_device_timer.h new file mode 100644 index 0000000..6d9a796 --- /dev/null +++ b/score/time/ptp/master_ptp_device_timer.h @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_PTP_MASTER_PTP_DEVICE_TIMER_H +#define SCORE_TIME_PTP_MASTER_PTP_DEVICE_TIMER_H + +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Tag for the HW timer on the PTP time master's Ethernet device where the +/// PTP client's pDelay measurement request packets are received and the +/// corresponding reply packets will be sent. +/// +struct MasterPTPDeviceTimer +{ + using Duration = std::chrono::nanoseconds; +}; + +/// \brief Time-point type for the master PTP device timer. +using MasterPTPDeviceTimerValue = std::chrono::time_point; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_PTP_MASTER_PTP_DEVICE_TIMER_H diff --git a/score/time/ptp/pdelay_measurement_data.h b/score/time/ptp/pdelay_measurement_data.h new file mode 100644 index 0000000..27b7057 --- /dev/null +++ b/score/time/ptp/pdelay_measurement_data.h @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_PTP_PDELAY_MEASUREMENT_DATA_H +#define SCORE_TIME_PTP_PDELAY_MEASUREMENT_DATA_H + +#include "score/time/ptp/local_ptp_device_timer.h" +#include "score/time/ptp/master_ptp_device_timer.h" +#include "score/time/ptp/port_identity.h" + +#include +#include +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Data delivered after a pDelay measurement performed by a Time Slave. +/// +/// @tparam Timebase The clock domain tag (e.g. @c VehicleTime). +/// +template +struct PDelayMeasurementData +{ + /// \brief Time Slave's local time at the point where it transmitted the pDelay Request Frame. + LocalPTPDeviceTimerValue request_origin_timestamp{}; + + /// \brief Time Master's timestamp at which it received the pDelay Request Frame. + MasterPTPDeviceTimerValue request_receipt_timestamp{}; + + /// \brief Time Master's timestamp at which it transmitted the pDelay Response Frame. + MasterPTPDeviceTimerValue response_origin_timestamp{}; + + /// \brief Time Slave's local time at the point where it received the pDelay Response Frame. + LocalPTPDeviceTimerValue response_receipt_timestamp{}; + + /// \brief Time Slave's global timestamp at which the pDelay measurement was initiated. + typename Timebase::Timepoint reference_global_timestamp{}; + + /// \brief Time Slave's local time at the point where it captured @c reference_global_timestamp. + LocalPTPDeviceTimerValue reference_local_timestamp{}; + + /// \brief Sequence number of the pDelay Request Frame. + std::uint16_t sequence_id{}; + + /// \brief Measured pDelay value. + std::chrono::nanoseconds pdelay{}; + + /// \brief Identity of the port from where the pDelay measurement was initiated. + PortIdentity request_port_identity{}; + + /// \brief Identity of the port that responded to the pDelay measurement. + PortIdentity response_port_identity{}; + + /// \brief Prints the data to any output stream. + template + auto& PrintTo(OutputStream& output_stream) const + { + /* False positives: << incorrectly identified as bitwise operators, no address of a local variable is + * returned. */ + return output_stream << "[" << request_origin_timestamp.time_since_epoch().count() << ", " + << request_receipt_timestamp.time_since_epoch().count() << ", " + << response_origin_timestamp.time_since_epoch().count() << ", " + << response_receipt_timestamp.time_since_epoch().count() << ", " + << reference_global_timestamp.time_since_epoch().count() << ", " + << reference_local_timestamp.time_since_epoch().count() << ", " << sequence_id << ", " + << pdelay.count() << ", " << request_port_identity << ", " << response_port_identity + << "]"; + } +}; + +/// \brief Stream output operator for @c PDelayMeasurementData. +template +auto& operator<<(OutputStream& output_stream, const PDelayMeasurementData& pdelay_data) +{ + return pdelay_data.PrintTo(output_stream); +} + +/// \brief GTest PrintTo overload for @c PDelayMeasurementData. +template +/* The dereference output_stream is passed as a non-const reference. Therefore, output_stream should be a pointer to a + * non-const. */ +void PrintTo(const PDelayMeasurementData& pdelay_data, std::ostream* const output_stream) +{ + pdelay_data.PrintTo(*output_stream); +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_PTP_PDELAY_MEASUREMENT_DATA_H diff --git a/score/time/ptp/pdelay_measurement_data_test.cpp b/score/time/ptp/pdelay_measurement_data_test.cpp new file mode 100644 index 0000000..d1892b0 --- /dev/null +++ b/score/time/ptp/pdelay_measurement_data_test.cpp @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/ptp/pdelay_measurement_data.h" + +#include + +#include +#include + +namespace score +{ +namespace time +{ +namespace +{ + +using namespace std::chrono_literals; + +// Minimal synthetic timebase — ptp_types are timebase-agnostic; only Timepoint is needed. +struct TestTimebase +{ + using Timepoint = std::chrono::time_point; +}; + +TEST(PDelayMeasurementDataTest, PrintToStream) +{ + std::ostringstream os; + + const PDelayMeasurementData pdelay_data{LocalPTPDeviceTimerValue{12ns}, + MasterPTPDeviceTimerValue{34ns}, + MasterPTPDeviceTimerValue{56ns}, + LocalPTPDeviceTimerValue{78ns}, + TestTimebase::Timepoint{90ns}, + LocalPTPDeviceTimerValue{123ns}, + 456U, + 789ns, + {123U, 45U}, + {678U, 90U}}; + + PrintTo(pdelay_data, &os); + + EXPECT_STREQ("[12, 34, 56, 78, 90, 123, 456, 789, (123, 45), (678, 90)]", os.str().c_str()); +} + +TEST(PDelayMeasurementDataTest, OperatorToStream) +{ + std::stringstream os; + + const PDelayMeasurementData pdelay_data{LocalPTPDeviceTimerValue{12ns}, + MasterPTPDeviceTimerValue{34ns}, + MasterPTPDeviceTimerValue{56ns}, + LocalPTPDeviceTimerValue{78ns}, + TestTimebase::Timepoint{90ns}, + LocalPTPDeviceTimerValue{123ns}, + 456U, + 789ns, + {123U, 45U}, + {678U, 90U}}; + + os << pdelay_data; + + EXPECT_STREQ("[12, 34, 56, 78, 90, 123, 456, 789, (123, 45), (678, 90)]", os.str().c_str()); +} + +} // namespace +} // namespace time +} // namespace score diff --git a/score/time/ptp/port_identity.h b/score/time/ptp/port_identity.h new file mode 100644 index 0000000..052acd7 --- /dev/null +++ b/score/time/ptp/port_identity.h @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_PTP_PORT_IDENTITY_H +#define SCORE_TIME_PTP_PORT_IDENTITY_H + +#include +#include + +namespace score +{ +namespace time +{ + +/// \brief Identifies a participant (port) involved in PTP communication. +struct PortIdentity +{ + /// \brief Clock identity of the PTP port. + std::uint64_t clock_identity{}; + + /// \brief Port number of the PTP port. + std::uint16_t port_number{}; + + /// \brief Prints the identity to any output stream. + template + auto& PrintTo(OutputStream& output_stream) const + { + /* False positives: << incorrectly identified as bitwise operators, no address of a local variable is + * returned.*/ + return output_stream << "(" << clock_identity << ", " << port_number << ")"; + } +}; + +/// \brief Stream output operator for @c PortIdentity. +template +auto& operator<<(OutputStream& output_stream, const PortIdentity& port_identity) +{ + return port_identity.PrintTo(output_stream); +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_PTP_PORT_IDENTITY_H diff --git a/score/time/ptp/time_slave_sync_data.h b/score/time/ptp/time_slave_sync_data.h new file mode 100644 index 0000000..de03759 --- /dev/null +++ b/score/time/ptp/time_slave_sync_data.h @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_PTP_TIME_SLAVE_SYNC_DATA_H +#define SCORE_TIME_PTP_TIME_SLAVE_SYNC_DATA_H + +#include "score/time/ptp/local_ptp_device_timer.h" +#include "score/time/ptp/port_identity.h" + +#include +#include +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Data delivered when new time-synchronization information is received by a Time Slave. +/// +/// @tparam Timebase The clock domain tag (e.g. @c VehicleTime). +/// +template +struct TimeSlaveSyncData +{ + /// \brief Time Master's timestamp of global time at which a Sync Frame has actually been transmitted + /// at its Ethernet port and which gets put into the Sync Follow-Up Frame sent afterwards. + typename Timebase::Timepoint precise_origin_timestamp{}; + + /// \brief Time Slave's timestamp of global time after the sync process took place. + typename Timebase::Timepoint reference_global_timestamp{}; + + /// \brief Time Slave's local time (e.g. Ethernet device's HW timer) after the sync process took place. + LocalPTPDeviceTimerValue reference_local_timestamp{}; + + /// \brief Time Slave's local time at the point where it received the Sync Frame at its Ethernet port. + LocalPTPDeviceTimerValue sync_ingress_timestamp{}; + + /// \brief Correction value taken from the Sync Follow-Up Frame. + /// + /// Unit: 1 / 0x10000 nanoseconds (i.e. a value of 0x10000 == 1 nanosecond). + std::int64_t correction_field{}; + + /// \brief Sequence number of the received Sync Frame. + std::uint16_t sequence_id{}; + + /// \brief Currently valid pDelay value. + std::chrono::nanoseconds pdelay{}; + + /// \brief Identity of the port the Sync & Follow-Up Frames originate from. + PortIdentity source_port_identity{}; + + /// \brief Prints the data to any output stream. + template + auto& PrintTo(OutputStream& output_stream) const + { + /* False positives: << incorrectly identified as bitwise operators, no address of a local variable is + * returned. */ + return output_stream << "[" << precise_origin_timestamp.time_since_epoch().count() << ", " + << reference_global_timestamp.time_since_epoch().count() << ", " + << reference_local_timestamp.time_since_epoch().count() << ", " + << sync_ingress_timestamp.time_since_epoch().count() << ", " << correction_field + << " / 0x10000, " << sequence_id << ", " << pdelay.count() << ", " + << source_port_identity << "]"; + } +}; + +/// \brief Stream output operator for @c TimeSlaveSyncData. +template +auto& operator<<(OutputStream& output_stream, const TimeSlaveSyncData& sync_data) +{ + return sync_data.PrintTo(output_stream); +} + +/// \brief GTest PrintTo overload for @c TimeSlaveSyncData. +template +/* The dereference output_stream is passed as a non-const reference. Therefore, output_stream should be a pointer to a + * non-const. */ +void PrintTo(const TimeSlaveSyncData& sync_data, std::ostream* const output_stream) +{ + sync_data.PrintTo(*output_stream); +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_PTP_TIME_SLAVE_SYNC_DATA_H diff --git a/score/time/ptp/time_slave_sync_data_test.cpp b/score/time/ptp/time_slave_sync_data_test.cpp new file mode 100644 index 0000000..f413350 --- /dev/null +++ b/score/time/ptp/time_slave_sync_data_test.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/ptp/time_slave_sync_data.h" + +#include + +#include +#include + +namespace score +{ +namespace time +{ +namespace +{ + +using namespace std::chrono_literals; + +// Minimal synthetic timebase — ptp_types are timebase-agnostic; only Timepoint is needed. +struct TestTimebase +{ + using Timepoint = std::chrono::time_point; +}; + +TEST(TimeSlaveSyncDataTest, PrintToStream) +{ + std::ostringstream os; + + const TimeSlaveSyncData sync_data{TestTimebase::Timepoint{123ns}, + TestTimebase::Timepoint{456ns}, + LocalPTPDeviceTimerValue{789ns}, + LocalPTPDeviceTimerValue{123ns}, + 45, + 67, + 89ns, + {12345U, 6789U}}; + + PrintTo(sync_data, &os); + + EXPECT_STREQ("[123, 456, 789, 123, 45 / 0x10000, 67, 89, (12345, 6789)]", os.str().c_str()); +} + +TEST(TimeSlaveSyncDataTest, OperatorToStream) +{ + std::stringstream os; + + const TimeSlaveSyncData sync_data{TestTimebase::Timepoint{123ns}, + TestTimebase::Timepoint{456ns}, + LocalPTPDeviceTimerValue{789ns}, + LocalPTPDeviceTimerValue{123ns}, + 45, + 67, + 89ns, + {12345U, 6789U}}; + + os << sync_data; + + EXPECT_STREQ("[123, 456, 789, 123, 45 / 0x10000, 67, 89, (12345, 6789)]", os.str().c_str()); +} + +} // namespace +} // namespace time +} // namespace score diff --git a/score/time/steady_time/BUILD b/score/time/steady_time/BUILD new file mode 100644 index 0000000..1c7cc07 --- /dev/null +++ b/score/time/steady_time/BUILD @@ -0,0 +1,117 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# The public interface of the SteadyTime clock domain +alias( + name = "steady_time", + actual = ":steady_clock_impl", + visibility = ["//visibility:public"], +) + +# The public interface for test purposes (includes mock backend) +alias( + name = "steady_time_mock", + testonly = True, + actual = ":steady_clock_mock", + visibility = ["//visibility:public"], +) + +# Interface without backend — for business-logic libraries that are tested +# via :steady_time_mock and deployed via :steady_time. +alias( + name = "interface", + actual = ":steady_clock", + visibility = ["//visibility:public"], +) + +cc_library( + name = "steady_clock_iface", + hdrs = [ + "steady_clock_iface.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "steady_clock", + srcs = ["steady_clock.cpp"], + hdrs = ["steady_clock.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], + deps = [ + ":steady_clock_iface", + "//score/time/clock:clock_core", + ], +) + +cc_library( + name = "steady_clock_mock", + testonly = True, + hdrs = ["steady_clock_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + ":steady_clock_iface", + # Stub backend — provides CreateBackend() at link time. + # Bundled here so test targets only need steady_clock_mock as a single dep. + "//score/time/steady_time/details/stub_impl", + ":steady_clock", + "@googletest//:gtest", + ], +) + +# Production entry point: steady_clock + production backend in a single dep. +# Production binaries dep on :steady_time; test binaries dep on :steady_time_mock. +cc_library( + name = "steady_clock_impl", + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/time:__subpackages__", + ], + deps = [ + ":steady_clock", + "//score/time/steady_time/details/steady_time_impl", + ], +) + +cc_test( + name = "steady_clock_test", + srcs = ["steady_clock_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":steady_clock_mock", + "//score/time/clock:clock_core", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":steady_clock_test", + ], + test_suites_from_sub_packages = [ + "//score/time/steady_time/details:unit_test_suite", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/steady_time/details/BUILD b/score/time/steady_time/details/BUILD new file mode 100644 index 0000000..6407612 --- /dev/null +++ b/score/time/steady_time/details/BUILD @@ -0,0 +1,23 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [], + test_suites_from_sub_packages = [ + "//score/time/steady_time/details/steady_time_impl:unit_test_suite", + ], + visibility = ["//score/time/steady_time:__pkg__"], +) diff --git a/score/time/steady_time/details/steady_time_impl/BUILD b/score/time/steady_time/details/steady_time_impl/BUILD new file mode 100644 index 0000000..e363439 --- /dev/null +++ b/score/time/steady_time/details/steady_time_impl/BUILD @@ -0,0 +1,55 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Production backend: delegates directly to std::chrono::steady_clock::now(). +# Link this target (via //score/time/steady_time:steady_time) into production binaries. +cc_library( + name = "steady_time_impl", + srcs = ["steady_clock_impl.cpp"], + hdrs = ["steady_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/steady_time:__pkg__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/steady_time:steady_clock", + "//score/time/steady_time:steady_clock_iface", + ], +) + +cc_test( + name = "steady_clock_impl_test", + testonly = True, + srcs = [ + "steady_clock_impl.cpp", + "steady_clock_impl.h", + "steady_clock_impl_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/steady_time:steady_clock", + "//score/time/steady_time:steady_clock_iface", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":steady_clock_impl_test"], + visibility = ["//score/time/steady_time/details:__pkg__"], +) diff --git a/score/time/steady_time/details/steady_time_impl/steady_clock_impl.cpp b/score/time/steady_time/details/steady_time_impl/steady_clock_impl.cpp new file mode 100644 index 0000000..dd6a41a --- /dev/null +++ b/score/time/steady_time/details/steady_time_impl/steady_clock_impl.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/steady_time/details/steady_time_impl/steady_clock_impl.h" +#include "score/time/steady_time/steady_clock.h" + +#include + +namespace score +{ +namespace time +{ + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/steady_time/details/steady_time_impl/steady_clock_impl.h b/score/time/steady_time/details/steady_time_impl/steady_clock_impl.h new file mode 100644 index 0000000..5767b30 --- /dev/null +++ b/score/time/steady_time/details/steady_time_impl/steady_clock_impl.h @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_STEADY_TIME_DETAILS_STEADY_TIME_IMPL_STEADY_CLOCK_IMPL_H +#define SCORE_TIME_STEADY_TIME_DETAILS_STEADY_TIME_IMPL_STEADY_CLOCK_IMPL_H + +// Internal header — include ONLY from steady_time_impl/steady_clock_impl.cpp. + +#include "score/time/steady_time/steady_clock_iface.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Production backend for the steady-clock domain. +/// +/// Delegates directly to std::chrono::steady_clock::now(). +class SteadyClockImpl final : public SteadyClockIface +{ + public: + SteadyClockImpl() noexcept = default; + ~SteadyClockImpl() noexcept override = default; + SteadyClockImpl(const SteadyClockImpl&) = delete; + SteadyClockImpl& operator=(const SteadyClockImpl&) = delete; + SteadyClockImpl(SteadyClockImpl&&) = delete; + SteadyClockImpl& operator=(SteadyClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override + { + return ClockSnapshot{ + std::chrono::steady_clock::now(), NoStatus{}}; + } +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_STEADY_TIME_DETAILS_STEADY_TIME_IMPL_STEADY_CLOCK_IMPL_H diff --git a/score/time/steady_time/details/steady_time_impl/steady_clock_impl_test.cpp b/score/time/steady_time/details/steady_time_impl/steady_clock_impl_test.cpp new file mode 100644 index 0000000..062a419 --- /dev/null +++ b/score/time/steady_time/details/steady_time_impl/steady_clock_impl_test.cpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/steady_time/details/steady_time_impl/steady_clock_impl.h" + +#include + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ +namespace test +{ + +class SteadyClockImplTest : public ::testing::Test +{ +}; + +TEST_F(SteadyClockImplTest, NowReturnsNonNegativeTimePoint) +{ + SteadyClockImpl clock; + const auto snapshot = clock.Now(); + + EXPECT_GE(snapshot.TimePointNs().count(), 0); +} + +TEST_F(SteadyClockImplTest, NowIsMonotonicallyIncreasing) +{ + SteadyClockImpl clock; + const auto first = clock.Now(); + + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + + EXPECT_GT(clock.Now().TimePoint(), first.TimePoint()); +} + +TEST_F(SteadyClockImplTest, NowSnapshotCarriesNoStatus) +{ + SteadyClockImpl clock; + const auto snapshot = clock.Now(); + + // NoStatus is an empty struct — verify it is accessible (compile + link check). + const NoStatus status = snapshot.Status(); + (void)status; + SUCCEED(); +} + +} // namespace test +} // namespace detail +} // namespace time +} // namespace score diff --git a/score/time/steady_time/details/stub_impl/BUILD b/score/time/steady_time/details/stub_impl/BUILD new file mode 100644 index 0000000..bc9f875 --- /dev/null +++ b/score/time/steady_time/details/stub_impl/BUILD @@ -0,0 +1,31 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Test-only stub: provides CreateBackend() returning +# an epoch-zero snapshot. Link this (via :steady_clock_mock) into test binaries +# instead of the production steady_time_impl backend. +cc_library( + name = "stub_impl", + testonly = True, + srcs = ["steady_clock_impl.cpp"], + hdrs = ["steady_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/steady_time:__pkg__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/steady_time:steady_clock", + "//score/time/steady_time:steady_clock_iface", + ], +) diff --git a/score/time/steady_time/details/stub_impl/steady_clock_impl.cpp b/score/time/steady_time/details/stub_impl/steady_clock_impl.cpp new file mode 100644 index 0000000..f972d91 --- /dev/null +++ b/score/time/steady_time/details/stub_impl/steady_clock_impl.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/steady_time/details/stub_impl/steady_clock_impl.h" +#include "score/time/steady_time/steady_clock.h" + +#include + +namespace score +{ +namespace time +{ + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/steady_time/details/stub_impl/steady_clock_impl.h b/score/time/steady_time/details/stub_impl/steady_clock_impl.h new file mode 100644 index 0000000..332580d --- /dev/null +++ b/score/time/steady_time/details/stub_impl/steady_clock_impl.h @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_STEADY_TIME_DETAILS_STUB_IMPL_STEADY_CLOCK_IMPL_H +#define SCORE_TIME_STEADY_TIME_DETAILS_STUB_IMPL_STEADY_CLOCK_IMPL_H + +// Internal header — include ONLY from stub_impl/steady_clock_impl.cpp. + +#include "score/time/steady_time/steady_clock_iface.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Stub backend for the steady-clock domain (host/test-only). +/// +/// Returns a default-constructed (epoch-zero) snapshot for all calls so that +/// tests without an active ClockOverrideGuard get a deterministic, safe value. +class SteadyClockImpl final : public SteadyClockIface +{ + public: + SteadyClockImpl() noexcept = default; + ~SteadyClockImpl() noexcept override = default; + SteadyClockImpl(const SteadyClockImpl&) = delete; + SteadyClockImpl& operator=(const SteadyClockImpl&) = delete; + SteadyClockImpl(SteadyClockImpl&&) = delete; + SteadyClockImpl& operator=(SteadyClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override + { + return ClockSnapshot{}; + } +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_STEADY_TIME_DETAILS_STUB_IMPL_STEADY_CLOCK_IMPL_H diff --git a/score/time/steady_time/steady_clock.cpp b/score/time/steady_time/steady_clock.cpp new file mode 100644 index 0000000..76e017d --- /dev/null +++ b/score/time/steady_time/steady_clock.cpp @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/steady_time/steady_clock.h" +#include "score/time/steady_time/steady_clock_iface.h" + +namespace score +{ +namespace time +{ + +ClockTraits::Snapshot +ClockTraits::CallNow(const Backend& impl) noexcept +{ + return impl.Now(); +} + +} // namespace time +} // namespace score diff --git a/score/time/steady_time/steady_clock.h b/score/time/steady_time/steady_clock.h new file mode 100644 index 0000000..e7b0827 --- /dev/null +++ b/score/time/steady_time/steady_clock.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_STEADY_TIME_STEADY_CLOCK_H +#define SCORE_TIME_STEADY_TIME_STEADY_CLOCK_H + +#include "score/time/clock/clock.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ + +class SteadyClockIface; + +template <> +struct ClockTraits +{ + using Backend = SteadyClockIface; + using Duration = std::chrono::steady_clock::duration; + using Timepoint = std::chrono::steady_clock::time_point; + using Snapshot = ClockSnapshot; + + /// \brief Obtains the current steady-clock snapshot from the backend. + static Snapshot CallNow(const Backend& impl) noexcept; +}; + +using SteadyClock = Clock; +using SteadyTimePoint = ClockTraits::Timepoint; +using SteadySnapshot = ClockTraits::Snapshot; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_STEADY_TIME_STEADY_CLOCK_H diff --git a/score/time/steady_time/steady_clock_iface.h b/score/time/steady_time/steady_clock_iface.h new file mode 100644 index 0000000..c4764b6 --- /dev/null +++ b/score/time/steady_time/steady_clock_iface.h @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_STEADY_TIME_STEADY_CLOCK_IFACE_H +#define SCORE_TIME_STEADY_TIME_STEADY_CLOCK_IFACE_H + +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include + +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Abstract backend interface for the steady-clock domain. +/// +class SteadyClockIface +{ + public: + virtual ~SteadyClockIface() noexcept = default; + + /// \brief Returns the current steady-clock snapshot. + virtual ClockSnapshot + Now() const noexcept = 0; + +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_STEADY_TIME_STEADY_CLOCK_IFACE_H diff --git a/score/time/steady_time/steady_clock_mock.h b/score/time/steady_time/steady_clock_mock.h new file mode 100644 index 0000000..a2f1a69 --- /dev/null +++ b/score/time/steady_time/steady_clock_mock.h @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_STEADY_TIME_STEADY_CLOCK_MOCK_H +#define SCORE_TIME_STEADY_TIME_STEADY_CLOCK_MOCK_H + +#include "score/time/steady_time/steady_clock_iface.h" +#include "score/time/steady_time/steady_clock.h" + +#include + +namespace score +{ +namespace time +{ + +/// @brief GMock test double for the steady-clock domain. +/// +/// Implements @c SteadyClockIface so it can be injected via +/// @c ClockOverrideGuard in unit tests. +/// +/// Usage: +/// @code +/// auto mock = std::make_shared(); +/// ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// @endcode +class SteadyClockMock : public SteadyClockIface +{ + public: + SteadyClockMock() = default; + ~SteadyClockMock() noexcept override = default; + SteadyClockMock(const SteadyClockMock&) = delete; + SteadyClockMock& operator=(const SteadyClockMock&) = delete; + SteadyClockMock(SteadyClockMock&&) = delete; + SteadyClockMock& operator=(SteadyClockMock&&) = delete; + + MOCK_METHOD((ClockSnapshot), + Now, + (), + (const, noexcept, override)); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_STEADY_TIME_STEADY_CLOCK_MOCK_H diff --git a/score/time/steady_time/steady_clock_test.cpp b/score/time/steady_time/steady_clock_test.cpp new file mode 100644 index 0000000..7f01eac --- /dev/null +++ b/score/time/steady_time/steady_clock_test.cpp @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/steady_time/steady_clock_mock.h" +#include "score/time/clock/clock_override_guard.h" + +#include +#include + +#include +#include +#include + +using ::testing::Return; + +namespace score +{ +namespace time +{ + +namespace +{ + +class SampleSteadyService +{ + public: + [[nodiscard]] std::chrono::steady_clock::time_point GetCurrentTime() const noexcept + { + return SteadyClock::GetInstance().Now().TimePoint(); + } +}; + +} // namespace + +TEST(SteadyClockTest, NowReturnsTimepointSuitableForDurationArithmetic) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const std::chrono::steady_clock::time_point tp{std::chrono::nanoseconds{1'000'000LL}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + + const auto result = SteadyClock::GetInstance().Now(); + const auto deadline = result.TimePoint() + std::chrono::seconds{5}; + + EXPECT_EQ(deadline.time_since_epoch(), + std::chrono::nanoseconds{1'000'000LL} + std::chrono::seconds{5}); +} + +TEST(SteadyClockTest, NowReturnsExactTimepointFromMock) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const std::chrono::steady_clock::time_point tp{std::chrono::seconds{42}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + + EXPECT_EQ(SteadyClock::GetInstance().Now().TimePoint(), tp); +} + +TEST(SteadyClockTest, NowSnapshotCarriesNoStatus) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{ + std::chrono::steady_clock::time_point{}, NoStatus{}})); + + const auto result = SteadyClock::GetInstance().Now(); + const NoStatus status = result.Status(); + (void)status; + SUCCEED(); +} + +TEST(SteadyClockTest, ClockOverrideGuardInjectsMockIntoSut) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const std::chrono::steady_clock::time_point expected{std::chrono::nanoseconds{999LL}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{expected, NoStatus{}})); + + SampleSteadyService sut; + EXPECT_EQ(sut.GetCurrentTime(), expected); +} + +TEST(SteadyClockTest, ClockOverrideGuardRestoresBackendAfterScope) +{ + auto mock = std::make_shared(); + { + ClockOverrideGuard guard{mock}; + const std::chrono::steady_clock::time_point tp{std::chrono::seconds{1}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + EXPECT_EQ(SteadyClock::GetInstance().Now().TimePoint(), tp); + } + // After guard goes out of scope, a new guard must succeed without assertion. + auto mock2 = std::make_shared(); + ClockOverrideGuard guard2{mock2}; + const std::chrono::steady_clock::time_point tp2{std::chrono::seconds{2}}; + EXPECT_CALL(*mock2, Now()).WillOnce(Return( + ClockSnapshot{tp2, NoStatus{}})); + EXPECT_EQ(SteadyClock::GetInstance().Now().TimePoint(), tp2); +} + +} // namespace time +} // namespace score diff --git a/score/time/system_time/BUILD b/score/time/system_time/BUILD new file mode 100644 index 0000000..616ed09 --- /dev/null +++ b/score/time/system_time/BUILD @@ -0,0 +1,117 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# The public interface of the SystemTime clock domain +alias( + name = "system_time", + actual = ":system_clock_impl", + visibility = ["//visibility:public"], +) + +# The public interface for test purposes (includes mock backend) +alias( + name = "system_time_mock", + testonly = True, + actual = ":system_clock_mock", + visibility = ["//visibility:public"], +) + +# Interface without backend — for business-logic libraries that are tested +# via :system_time_mock and deployed via :system_time. +alias( + name = "interface", + actual = ":system_clock", + visibility = ["//visibility:public"], +) + +cc_library( + name = "system_clock_iface", + hdrs = [ + "system_clock_iface.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "system_clock", + srcs = ["system_clock.cpp"], + hdrs = ["system_clock.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], + deps = [ + ":system_clock_iface", + "//score/time/clock:clock_core", + ], +) + +cc_library( + name = "system_clock_mock", + testonly = True, + hdrs = ["system_clock_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + ":system_clock_iface", + # Stub backend — provides CreateBackend() at link time. + # Bundled here so test targets only need system_clock_mock as a single dep. + "//score/time/system_time/details/stub_impl", + ":system_clock", + "@googletest//:gtest", + ], +) + +# Production entry point: system_clock + production backend in a single dep. +# Production binaries dep on :system_time; test binaries dep on :system_time_mock. +cc_library( + name = "system_clock_impl", + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/time:__subpackages__", + ], + deps = [ + ":system_clock", + "//score/time/system_time/details/system_time_impl", + ], +) + +cc_test( + name = "system_clock_test", + srcs = ["system_clock_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":system_clock_mock", + "//score/time/clock:clock_core", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":system_clock_test", + ], + test_suites_from_sub_packages = [ + "//score/time/system_time/details:unit_test_suite", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/system_time/details/BUILD b/score/time/system_time/details/BUILD new file mode 100644 index 0000000..751a171 --- /dev/null +++ b/score/time/system_time/details/BUILD @@ -0,0 +1,23 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [], + test_suites_from_sub_packages = [ + "//score/time/system_time/details/system_time_impl:unit_test_suite", + ], + visibility = ["//score/time/system_time:__pkg__"], +) diff --git a/score/time/system_time/details/stub_impl/BUILD b/score/time/system_time/details/stub_impl/BUILD new file mode 100644 index 0000000..a711a85 --- /dev/null +++ b/score/time/system_time/details/stub_impl/BUILD @@ -0,0 +1,31 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Test-only stub: provides CreateBackend() returning +# an epoch-zero snapshot. Link this (via :system_clock_mock) into test binaries +# instead of the production system_time_impl backend. +cc_library( + name = "stub_impl", + testonly = True, + srcs = ["system_clock_impl.cpp"], + hdrs = ["system_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/system_time:__pkg__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/system_time:system_clock", + "//score/time/system_time:system_clock_iface", + ], +) diff --git a/score/time/system_time/details/stub_impl/system_clock_impl.cpp b/score/time/system_time/details/stub_impl/system_clock_impl.cpp new file mode 100644 index 0000000..c3acb10 --- /dev/null +++ b/score/time/system_time/details/stub_impl/system_clock_impl.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/system_time/details/stub_impl/system_clock_impl.h" +#include "score/time/system_time/system_clock.h" + +#include + +namespace score +{ +namespace time +{ + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/system_time/details/stub_impl/system_clock_impl.h b/score/time/system_time/details/stub_impl/system_clock_impl.h new file mode 100644 index 0000000..95eb07c --- /dev/null +++ b/score/time/system_time/details/stub_impl/system_clock_impl.h @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SYSTEM_TIME_DETAILS_STUB_IMPL_SYSTEM_CLOCK_IMPL_H +#define SCORE_TIME_SYSTEM_TIME_DETAILS_STUB_IMPL_SYSTEM_CLOCK_IMPL_H + +// Internal header — include ONLY from stub_impl/system_clock_impl.cpp. + +#include "score/time/system_time/system_clock_iface.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Stub backend for the system-clock domain (host/test-only). +/// +/// Returns a default-constructed (epoch-zero) snapshot for all calls so that +/// tests without an active ClockOverrideGuard get a deterministic, safe value. +class SystemClockImpl final : public SystemClockIface +{ + public: + SystemClockImpl() noexcept = default; + ~SystemClockImpl() noexcept override = default; + SystemClockImpl(const SystemClockImpl&) = delete; + SystemClockImpl& operator=(const SystemClockImpl&) = delete; + SystemClockImpl(SystemClockImpl&&) = delete; + SystemClockImpl& operator=(SystemClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override + { + return ClockSnapshot{}; + } +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSTEM_TIME_DETAILS_STUB_IMPL_SYSTEM_CLOCK_IMPL_H diff --git a/score/time/system_time/details/system_time_impl/BUILD b/score/time/system_time/details/system_time_impl/BUILD new file mode 100644 index 0000000..d7c3c11 --- /dev/null +++ b/score/time/system_time/details/system_time_impl/BUILD @@ -0,0 +1,55 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Production backend: delegates directly to std::chrono::system_clock::now(). +# Link this target (via //score/time/system_time:system_time) into production binaries. +cc_library( + name = "system_time_impl", + srcs = ["system_clock_impl.cpp"], + hdrs = ["system_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/system_time:__pkg__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/system_time:system_clock", + "//score/time/system_time:system_clock_iface", + ], +) + +cc_test( + name = "system_clock_impl_test", + testonly = True, + srcs = [ + "system_clock_impl.cpp", + "system_clock_impl.h", + "system_clock_impl_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/system_time:system_clock", + "//score/time/system_time:system_clock_iface", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":system_clock_impl_test"], + visibility = ["//score/time/system_time/details:__pkg__"], +) diff --git a/score/time/system_time/details/system_time_impl/system_clock_impl.cpp b/score/time/system_time/details/system_time_impl/system_clock_impl.cpp new file mode 100644 index 0000000..b5b0a60 --- /dev/null +++ b/score/time/system_time/details/system_time_impl/system_clock_impl.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/system_time/details/system_time_impl/system_clock_impl.h" +#include "score/time/system_time/system_clock.h" + +#include + +namespace score +{ +namespace time +{ + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/system_time/details/system_time_impl/system_clock_impl.h b/score/time/system_time/details/system_time_impl/system_clock_impl.h new file mode 100644 index 0000000..0e14e15 --- /dev/null +++ b/score/time/system_time/details/system_time_impl/system_clock_impl.h @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SYSTEM_TIME_DETAILS_SYSTEM_TIME_IMPL_SYSTEM_CLOCK_IMPL_H +#define SCORE_TIME_SYSTEM_TIME_DETAILS_SYSTEM_TIME_IMPL_SYSTEM_CLOCK_IMPL_H + +// Internal header — include ONLY from system_time_impl/system_clock_impl.cpp. + +#include "score/time/system_time/system_clock_iface.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Production backend for the system-clock domain. +/// +/// Delegates directly to std::chrono::system_clock::now(). +class SystemClockImpl final : public SystemClockIface +{ + public: + SystemClockImpl() noexcept = default; + ~SystemClockImpl() noexcept override = default; + SystemClockImpl(const SystemClockImpl&) = delete; + SystemClockImpl& operator=(const SystemClockImpl&) = delete; + SystemClockImpl(SystemClockImpl&&) = delete; + SystemClockImpl& operator=(SystemClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override + { + return ClockSnapshot{ + std::chrono::system_clock::now(), NoStatus{}}; + } +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSTEM_TIME_DETAILS_SYSTEM_TIME_IMPL_SYSTEM_CLOCK_IMPL_H diff --git a/score/time/system_time/details/system_time_impl/system_clock_impl_test.cpp b/score/time/system_time/details/system_time_impl/system_clock_impl_test.cpp new file mode 100644 index 0000000..630a141 --- /dev/null +++ b/score/time/system_time/details/system_time_impl/system_clock_impl_test.cpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/system_time/details/system_time_impl/system_clock_impl.h" + +#include + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ +namespace test +{ + +class SystemClockImplTest : public ::testing::Test +{ +}; + +TEST_F(SystemClockImplTest, NowReturnsPositiveUnixEpochTime) +{ + SystemClockImpl clock; + const auto snapshot = clock.Now(); + + // Any timestamp after 2000-01-01 00:00:00 UTC (946684800 s) is valid. + constexpr auto kYear2000Ns = 946684800LL * 1'000'000'000LL; + EXPECT_GT(snapshot.TimePointNs().count(), kYear2000Ns); +} + +TEST_F(SystemClockImplTest, NowIsMonotonicallyIncreasing) +{ + SystemClockImpl clock; + const auto first = clock.Now(); + + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + + EXPECT_GT(clock.Now().TimePoint(), first.TimePoint()); +} + +TEST_F(SystemClockImplTest, NowSnapshotCarriesNoStatus) +{ + SystemClockImpl clock; + const auto snapshot = clock.Now(); + + // NoStatus is an empty struct — verify it is accessible (compile + link check). + const NoStatus status = snapshot.Status(); + (void)status; + SUCCEED(); +} + +} // namespace test +} // namespace detail +} // namespace time +} // namespace score diff --git a/score/time/system_time/system_clock.cpp b/score/time/system_time/system_clock.cpp new file mode 100644 index 0000000..cffa3ae --- /dev/null +++ b/score/time/system_time/system_clock.cpp @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/system_time/system_clock.h" +#include "score/time/system_time/system_clock_iface.h" + +namespace score +{ +namespace time +{ + +ClockTraits::Snapshot +ClockTraits::CallNow(const Backend& impl) noexcept +{ + return impl.Now(); +} + +} // namespace time +} // namespace score diff --git a/score/time/system_time/system_clock.h b/score/time/system_time/system_clock.h new file mode 100644 index 0000000..63fd96d --- /dev/null +++ b/score/time/system_time/system_clock.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_H +#define SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_H + +#include "score/time/clock/clock.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include + +namespace score +{ +namespace time +{ + +class SystemClockIface; + +template <> +struct ClockTraits +{ + using Backend = SystemClockIface; + using Duration = std::chrono::system_clock::duration; + using Timepoint = std::chrono::system_clock::time_point; + using Snapshot = ClockSnapshot; + + /// \brief Obtains the current system-clock snapshot from the backend. + static Snapshot CallNow(const Backend& impl) noexcept; +}; + +using SystemClock = Clock; +using SystemTimePoint = ClockTraits::Timepoint; +using SystemSnapshot = ClockTraits::Snapshot; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_H diff --git a/score/time/system_time/system_clock_iface.h b/score/time/system_time/system_clock_iface.h new file mode 100644 index 0000000..5b19a4b --- /dev/null +++ b/score/time/system_time/system_clock_iface.h @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_IFACE_H +#define SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_IFACE_H + +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include + +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Abstract backend interface for the system-clock domain. +/// +/// Concrete implementations live in SystemTime/details/. +/// This header is the contract shared by Clock, +/// test mocks, and production/test backends. +/// +class SystemClockIface +{ + public: + virtual ~SystemClockIface() noexcept = default; + + /// \brief Returns the current system-clock snapshot. + virtual ClockSnapshot + Now() const noexcept = 0; + +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_IFACE_H diff --git a/score/time/system_time/system_clock_mock.h b/score/time/system_time/system_clock_mock.h new file mode 100644 index 0000000..792eea8 --- /dev/null +++ b/score/time/system_time/system_clock_mock.h @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_MOCK_H +#define SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_MOCK_H + +#include "score/time/system_time/system_clock_iface.h" +#include "score/time/system_time/system_clock.h" + +#include + +namespace score +{ +namespace time +{ + +/// @brief GMock test double for the system-clock domain. +/// +/// Implements @c SystemClockIface so it can be injected via +/// @c ClockOverrideGuard in unit tests. +/// +/// Usage: +/// @code +/// auto mock = std::make_shared(); +/// ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// @endcode +class SystemClockMock : public SystemClockIface +{ + public: + SystemClockMock() = default; + ~SystemClockMock() noexcept override = default; + SystemClockMock(const SystemClockMock&) = delete; + SystemClockMock& operator=(const SystemClockMock&) = delete; + SystemClockMock(SystemClockMock&&) = delete; + SystemClockMock& operator=(SystemClockMock&&) = delete; + + MOCK_METHOD((ClockSnapshot), + Now, + (), + (const, noexcept, override)); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSTEM_TIME_SYSTEM_CLOCK_MOCK_H diff --git a/score/time/system_time/system_clock_test.cpp b/score/time/system_time/system_clock_test.cpp new file mode 100644 index 0000000..8e732b5 --- /dev/null +++ b/score/time/system_time/system_clock_test.cpp @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/system_time/system_clock_mock.h" +#include "score/time/clock/clock_override_guard.h" + +#include +#include + +#include +#include +#include + +using ::testing::Return; + +namespace score +{ +namespace time +{ + +namespace +{ + +class SampleSystemService +{ + public: + [[nodiscard]] std::chrono::system_clock::time_point GetCurrentTime() const noexcept + { + return SystemClock::GetInstance().Now().TimePoint(); + } +}; + +} // namespace + +TEST(SystemClockTest, NowReturnsTimepointSuitableForDurationArithmetic) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const std::chrono::system_clock::time_point tp{std::chrono::nanoseconds{1'000'000LL}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + + const auto result = SystemClock::GetInstance().Now(); + const auto deadline = result.TimePoint() + std::chrono::seconds{5}; + + EXPECT_EQ(deadline.time_since_epoch(), + std::chrono::nanoseconds{1'000'000LL} + std::chrono::seconds{5}); +} + +TEST(SystemClockTest, NowReturnsExactTimepointFromMock) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const std::chrono::system_clock::time_point tp{std::chrono::seconds{42}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + + EXPECT_EQ(SystemClock::GetInstance().Now().TimePoint(), tp); +} + +TEST(SystemClockTest, NowSnapshotCarriesNoStatus) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{ + std::chrono::system_clock::time_point{}, NoStatus{}})); + + const auto result = SystemClock::GetInstance().Now(); + const NoStatus status = result.Status(); + (void)status; + SUCCEED(); +} + +TEST(SystemClockTest, ClockOverrideGuardInjectsMockIntoSut) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const std::chrono::system_clock::time_point expected{std::chrono::nanoseconds{999LL}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{expected, NoStatus{}})); + + SampleSystemService sut; + EXPECT_EQ(sut.GetCurrentTime(), expected); +} + +TEST(SystemClockTest, ClockOverrideGuardRestoresBackendAfterScope) +{ + auto mock = std::make_shared(); + { + ClockOverrideGuard guard{mock}; + const std::chrono::system_clock::time_point tp{std::chrono::seconds{1}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{tp, NoStatus{}})); + EXPECT_EQ(SystemClock::GetInstance().Now().TimePoint(), tp); + } + // After guard goes out of scope, a new guard must succeed without assertion. + auto mock2 = std::make_shared(); + ClockOverrideGuard guard2{mock2}; + const std::chrono::system_clock::time_point tp2{std::chrono::seconds{2}}; + EXPECT_CALL(*mock2, Now()).WillOnce(Return( + ClockSnapshot{tp2, NoStatus{}})); + EXPECT_EQ(SystemClock::GetInstance().Now().TimePoint(), tp2); +} + +} // namespace time +} // namespace score diff --git a/score/time/vehicle_time/BUILD b/score/time/vehicle_time/BUILD new file mode 100644 index 0000000..acaa57e --- /dev/null +++ b/score/time/vehicle_time/BUILD @@ -0,0 +1,140 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# The public interface of the VehicleTime clock domain +alias( + name = "vehicle_time", + actual = ":vehicle_clock_impl", + visibility = [ + "//visibility:public", + ], +) + +# The public interface for test purposes (includes mock backend) +alias( + name = "vehicle_time_mock", + testonly = True, + actual = ":vehicle_clock_mock", + visibility = [ + "//visibility:public", + ], +) + +# Interface without backend — for business-logic libraries that are tested +# via :vehicle_time_mock and deployed via :vehicle_time. +alias( + name = "interface", + actual = ":vehicle_clock", + visibility = ["//visibility:public"], +) + +cc_library( + name = "vehicle_clock_iface", + hdrs = [ + "vehicle_clock_iface.h", + "vehicle_time.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], + deps = [ + "//score/time/clock:clock_core", + "//score/time/ptp:ptp_types", + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "vehicle_clock", + srcs = ["vehicle_clock.cpp"], + hdrs = ["vehicle_clock.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/time:__subpackages__"], + deps = [ + ":vehicle_clock_iface", + "//score/time/clock:clock_core", + "//score/time/ptp:ptp_types", + ], +) + +# Production entry point: vehicle_clock + production backend in a single dep. +# Production binaries dep on :vehicle_time; test binaries dep on :vehicle_time_mock. +cc_library( + name = "vehicle_clock_impl", + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/time:__subpackages__", + ], + deps = [ + ":vehicle_clock", + "//score/time/vehicle_time/details/td_impl", + ], +) + +cc_library( + name = "vehicle_clock_mock", + testonly = True, + hdrs = ["vehicle_time_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time:__subpackages__"], + deps = [ + ":vehicle_clock_iface", + # Stub backend — provides CreateBackend() at link time. + # Bundled here so test targets only need vehicle_time_mock as a single dep. + ":vehicle_clock", + "//score/time/vehicle_time/details/stub_impl", + "@googletest//:gtest", + ], +) + +cc_test( + name = "vehicle_clock_test", + srcs = ["vehicle_clock_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":vehicle_time_mock", + "//score/time/clock:clock_core", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "vehicle_time_status_test", + srcs = ["vehicle_time_status_test.cpp"], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + ":vehicle_clock_iface", + "//score/time/clock:clock_core", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [ + ":vehicle_clock_test", + ":vehicle_time_status_test", + ], + test_suites_from_sub_packages = [ + "//score/time/vehicle_time/details:unit_test_suite", + ], + visibility = ["//score/time:__pkg__"], +) diff --git a/score/time/vehicle_time/details/BUILD b/score/time/vehicle_time/details/BUILD new file mode 100644 index 0000000..887eb4d --- /dev/null +++ b/score/time/vehicle_time/details/BUILD @@ -0,0 +1,29 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") + +filegroup( + name = "logging_contexts", + srcs = ["logging_contexts.h"], + visibility = ["//score/time/vehicle_time:__subpackages__"], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [], + test_suites_from_sub_packages = [ + "//score/time/vehicle_time/details/td_impl:unit_test_suite", + ], + visibility = ["//score/time/vehicle_time:__pkg__"], +) diff --git a/score/time/vehicle_time/details/logging_contexts.h b/score/time/vehicle_time/details/logging_contexts.h new file mode 100644 index 0000000..a2bf48c --- /dev/null +++ b/score/time/vehicle_time/details/logging_contexts.h @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_DETAILS_LOGGING_CONTEXTS_H +#define SCORE_TIME_VEHICLE_TIME_DETAILS_LOGGING_CONTEXTS_H + +// Internal header — included by translation units under vehicle_time/details/. + +namespace score +{ +namespace time +{ +namespace detail +{ + +constexpr auto kVehicleTimeLogContext = "MVTC"; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_DETAILS_LOGGING_CONTEXTS_H diff --git a/score/time/vehicle_time/details/stub_impl/BUILD b/score/time/vehicle_time/details/stub_impl/BUILD new file mode 100644 index 0000000..7ed5b5b --- /dev/null +++ b/score/time/vehicle_time/details/stub_impl/BUILD @@ -0,0 +1,27 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +cc_library( + name = "stub_impl", + testonly = True, + srcs = ["vehicle_clock_impl.cpp"], + hdrs = ["vehicle_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/vehicle_time:__pkg__"], + deps = [ + "//score/time/vehicle_time:vehicle_clock", + "//score/time/vehicle_time:vehicle_clock_iface", + ], +) diff --git a/score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.cpp b/score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.cpp new file mode 100644 index 0000000..54566d3 --- /dev/null +++ b/score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.h" +#include "score/time/vehicle_time/vehicle_clock.h" + +#include + +namespace score +{ +namespace time +{ + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(); +} + +} // namespace time +} // namespace score diff --git a/score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.h b/score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.h new file mode 100644 index 0000000..cb0abc0 --- /dev/null +++ b/score/time/vehicle_time/details/stub_impl/vehicle_clock_impl.h @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_DETAILS_STUB_IMPL_VEHICLE_CLOCK_IMPL_H +#define SCORE_TIME_VEHICLE_TIME_DETAILS_STUB_IMPL_VEHICLE_CLOCK_IMPL_H + +// Internal header — include ONLY from stub_impl/vehicle_clock_impl.cpp. + +#include "score/time/vehicle_time/vehicle_clock_iface.h" + +#include + +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Stub backend for the vehicle time domain (host/test-only). +/// +/// Returns safe zero/false defaults for all methods. +class VehicleClockImpl final : public VehicleClockIface +{ + public: + VehicleClockImpl() noexcept = default; + ~VehicleClockImpl() noexcept override = default; + VehicleClockImpl(const VehicleClockImpl&) = delete; + VehicleClockImpl& operator=(const VehicleClockImpl&) = delete; + VehicleClockImpl(VehicleClockImpl&&) = delete; + VehicleClockImpl& operator=(VehicleClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override + { + return ClockSnapshot{}; + } + + bool IsAvailable() const noexcept override + { + return false; + } + + bool WaitUntilAvailable(const score::cpp::stop_token& /*token*/, + std::chrono::steady_clock::time_point /*until*/) const noexcept override + { + return false; + } + + void SetTimeSlaveSyncDataReceivedCallback( + VehicleTime::TimeSlaveSyncDataReceivedCallback&& /*callback*/) noexcept override + { + } + + void UnsetTimeSlaveSyncDataReceivedCallback() noexcept override {} + + void SetPDelayMeasurementFinishedCallback( + VehicleTime::PDelayMeasurementFinishedCallback&& /*callback*/) noexcept override + { + } + + void UnsetPDelayMeasurementFinishedCallback() noexcept override {} +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_DETAILS_STUB_IMPL_VEHICLE_CLOCK_IMPL_H diff --git a/score/time/vehicle_time/details/td_impl/BUILD b/score/time/vehicle_time/details/td_impl/BUILD new file mode 100644 index 0000000..513b064 --- /dev/null +++ b/score/time/vehicle_time/details/td_impl/BUILD @@ -0,0 +1,65 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +# Production backend: reads live PTP data from the TimeDaemon via SvtReceiver. +# Link this target (or the forwarding alias //score/time/vehicle_time/details:td_impl) +# into the production binary to provide CreateBackend(). +cc_library( + name = "td_impl", + srcs = [ + "vehicle_clock_impl.cpp", + "//score/time/vehicle_time/details:logging_contexts", # internal header + ], + hdrs = ["vehicle_clock_impl.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/time/vehicle_time:__pkg__"], + deps = [ + "//score/TimeDaemon/code/ipc:svt_receiver", + "//score/time/hpls_time:hpls_clock", + "//score/time/vehicle_time:vehicle_clock", + "//score/time/vehicle_time:vehicle_clock_iface", + "@score_baselibs//score/mw/log:frontend", + ], +) + +cc_test( + name = "vehicle_clock_impl_test", + srcs = [ + "vehicle_clock_impl.cpp", + "vehicle_clock_impl.h", + "vehicle_clock_impl_test.cpp", + "//score/time/vehicle_time/details:logging_contexts", # internal header + ], + features = COMPILER_WARNING_FEATURES, + tags = ["unit"], + deps = [ + "//score/TimeDaemon/code/ipc:svt_receiver_mock", + "//score/time/hpls_time:hpls_clock_mock", + "//score/time/vehicle_time:vehicle_clock", + "//score/time/vehicle_time:vehicle_clock_iface", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score_baselibs//score/language/safecpp/coverage_termination_handler", + "@score_baselibs//score/mw/log:console_only_backend", + "@score_baselibs//score/mw/log:frontend", + ], +) + +cc_unit_test_suites_for_host_and_qnx( + name = "unit_test_suite", + cc_unit_tests = [":vehicle_clock_impl_test"], + visibility = ["//score/time/vehicle_time:__pkg__"], +) diff --git a/score/time/vehicle_time/details/td_impl/vehicle_clock_impl.cpp b/score/time/vehicle_time/details/td_impl/vehicle_clock_impl.cpp new file mode 100644 index 0000000..37cb946 --- /dev/null +++ b/score/time/vehicle_time/details/td_impl/vehicle_clock_impl.cpp @@ -0,0 +1,169 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/vehicle_time/details/td_impl/vehicle_clock_impl.h" +#include "score/time/vehicle_time/details/logging_contexts.h" + +#include "score/TimeDaemon/code/ipc/svt/receiver/factory.h" +#include "score/TimeDaemon/code/ipc/svt/svt_time_info.h" + +#include "score/mw/log/logging.h" + +#include +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +VehicleClockImpl::VehicleClockImpl( + std::shared_ptr receiver, + HplsClock local_clock) noexcept + : is_ready_{false} + , svt_receiver_{std::move(receiver)} + , local_clock_{std::move(local_clock)} +{ +} + +ClockSnapshot +VehicleClockImpl::Now() const noexcept +{ + const ClockSnapshot kUnknownSnapshot{ + VehicleTime::Timepoint{}, + VehicleTimeStatus{ + ClockStatus{{VehicleTime::StatusFlag::kUnknown}}, 0.0}}; + + if (!is_ready_) + { + return kUnknownSnapshot; + } + + const auto rx_data = svt_receiver_->Receive(); + if (!rx_data.has_value()) + { + return kUnknownSnapshot; + } + + const auto now_local = local_clock_.Now().TimePoint().time_since_epoch(); + const auto local_at_capture = std::chrono::nanoseconds{rx_data.value().local_time}; + const auto ptp_at_capture = std::chrono::nanoseconds{rx_data.value().ptp_assumed_time}; + + if (now_local < local_at_capture) + { + score::mw::log::LogError(kVehicleTimeLogContext) + << "Local clock is behind PTP capture reference — returning unknown status."; + return kUnknownSnapshot; + } + + const VehicleTime::Timepoint adjusted_tp{ptp_at_capture + (now_local - local_at_capture)}; + + VehicleTimeStatus status; + status.flags = ConvertPtpStatus(rx_data.value().status); + status.rate_deviation = rx_data.value().rate_deviation; + + return ClockSnapshot{adjusted_tp, status}; +} + +bool VehicleClockImpl::IsAvailable() const noexcept +{ + if (!is_ready_) + { + is_ready_ = svt_receiver_->Init(); + } + return is_ready_; +} + +bool VehicleClockImpl::WaitUntilAvailable( + const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until) const noexcept +{ + bool should_poll = false; + do + { + if (IsAvailable()) + { + return true; + } + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + should_poll = (!token.stop_requested()) && (std::chrono::steady_clock::now() <= until); + } while (should_poll); + + score::mw::log::LogError(kVehicleTimeLogContext) + << "Vehicle time IPC to TimeDaemon not ready within deadline."; + return false; +} + +void VehicleClockImpl::SetTimeSlaveSyncDataReceivedCallback( + VehicleTime::TimeSlaveSyncDataReceivedCallback&& /*callback*/) noexcept +{ + // Not yet supported by the TimeDaemon IPC subscription layer. +} + +void VehicleClockImpl::UnsetTimeSlaveSyncDataReceivedCallback() noexcept +{ + // Not yet supported. +} + +void VehicleClockImpl::SetPDelayMeasurementFinishedCallback( + VehicleTime::PDelayMeasurementFinishedCallback&& /*callback*/) noexcept +{ + // Not yet supported. +} + +void VehicleClockImpl::UnsetPDelayMeasurementFinishedCallback() noexcept +{ + // Not yet supported. +} + +ClockStatus +VehicleClockImpl::ConvertPtpStatus( + const score::td::svt::TimeBaseStatus& ptp_status) noexcept +{ + using Flag = VehicleTime::StatusFlag; + ClockStatus status; + if (ptp_status.is_synchronized) + { + status.AddFlag(Flag::kSynchronized); + } + if (ptp_status.is_timeout) + { + status.AddFlag(Flag::kTimeOut); + } + if (ptp_status.is_time_jump_future) + { + status.AddFlag(Flag::kTimeLeapFuture); + } + if (ptp_status.is_time_jump_past) + { + status.AddFlag(Flag::kTimeLeapPast); + } + if (!ptp_status.is_correct) + { + status.AddFlag(Flag::kUnknown); + } + return status; +} + +} // namespace detail + +template <> +std::shared_ptr detail::CreateBackend() +{ + return std::make_shared(score::td::CreateSvtReceiver(), + HplsClock::GetInstance()); +} + +} // namespace time +} // namespace score diff --git a/score/time/vehicle_time/details/td_impl/vehicle_clock_impl.h b/score/time/vehicle_time/details/td_impl/vehicle_clock_impl.h new file mode 100644 index 0000000..2158e29 --- /dev/null +++ b/score/time/vehicle_time/details/td_impl/vehicle_clock_impl.h @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_DETAILS_TD_IMPL_VEHICLE_CLOCK_IMPL_H +#define SCORE_TIME_VEHICLE_TIME_DETAILS_TD_IMPL_VEHICLE_CLOCK_IMPL_H + +// Internal header — include ONLY from vehicle_clock_impl.cpp and vehicle_clock_impl_test.cpp. +// NOT part of the public API of td_impl. + +#include "score/time/vehicle_time/vehicle_clock_iface.h" +#include "score/time/vehicle_time/vehicle_clock.h" +#include "score/time/hpls_time/hpls_clock.h" +#include "score/TimeDaemon/code/ipc/svt/receiver/svt_receiver.h" + +#include + +#include +#include +#include + +namespace score +{ +namespace time +{ +namespace detail +{ + +/// @brief Production backend for the vehicle time domain. +/// +/// Implements @c VehicleClockIface by reading live PTP data from the TimeDaemon +/// via @c score::td::SvtReceiver. The adjusted vehicle time is computed as: +/// +/// adjusted_ptp = ptp_stamp_at_capture + (local_now - local_at_capture) +/// +/// where the local reference clock is supplied via @c HplsClock::GetInstance() +/// (captured once at construction to avoid per-call mutex overhead). +/// +/// Callback registration is not yet supported; all Set/Unset methods are no-ops +/// until the TimeDaemon IPC layer provides a subscription facility. +/// +/// @note Placed in @c score::time::detail (rather than an anonymous namespace) so +/// that vehicle_clock_impl_test.cpp can construct it directly with injected mocks. +/// Only one backend translation unit must be linked per binary to avoid ODR issues. +class VehicleClockImpl final : public VehicleClockIface +{ + public: + VehicleClockImpl(std::shared_ptr receiver, + HplsClock local_clock) noexcept; + + ~VehicleClockImpl() noexcept override = default; + VehicleClockImpl(const VehicleClockImpl&) = delete; + VehicleClockImpl& operator=(const VehicleClockImpl&) = delete; + VehicleClockImpl(VehicleClockImpl&&) = delete; + VehicleClockImpl& operator=(VehicleClockImpl&&) = delete; + + ClockSnapshot Now() const noexcept override; + + bool IsAvailable() const noexcept override; + + bool WaitUntilAvailable(const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until) const noexcept override; + + void SetTimeSlaveSyncDataReceivedCallback( + VehicleTime::TimeSlaveSyncDataReceivedCallback&& callback) noexcept override; + + void UnsetTimeSlaveSyncDataReceivedCallback() noexcept override; + + void SetPDelayMeasurementFinishedCallback( + VehicleTime::PDelayMeasurementFinishedCallback&& callback) noexcept override; + + void UnsetPDelayMeasurementFinishedCallback() noexcept override; + + private: + /// @brief Converts PTP status flags from the TimeDaemon IPC representation to + /// the @c ClockStatus representation. + static ClockStatus ConvertPtpStatus( + const score::td::svt::TimeBaseStatus& ptp_status) noexcept; + + mutable std::atomic_bool is_ready_; + std::shared_ptr svt_receiver_; + HplsClock local_clock_; +}; + +} // namespace detail +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_DETAILS_TD_IMPL_VEHICLE_CLOCK_IMPL_H diff --git a/score/time/vehicle_time/details/td_impl/vehicle_clock_impl_test.cpp b/score/time/vehicle_time/details/td_impl/vehicle_clock_impl_test.cpp new file mode 100644 index 0000000..c8d1585 --- /dev/null +++ b/score/time/vehicle_time/details/td_impl/vehicle_clock_impl_test.cpp @@ -0,0 +1,268 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/vehicle_time/details/td_impl/vehicle_clock_impl.h" + +#include "score/TimeDaemon/code/ipc/receiver_mock.h" +#include "score/TimeDaemon/code/ipc/svt/svt_time_info.h" +#include "score/time/hpls_time/hpls_time_mock.h" +#include "score/time/clock/clock_override_guard.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/no_status.h" + +#include + +#include +#include + +namespace score +{ +namespace time +{ +namespace +{ + +using namespace std::chrono_literals; +using ::testing::Return; + +using SvtMock = score::td::ReceiverMock; +using SvtSnapshot = score::td::svt::TimeBaseSnapshot; +using SvtStatus = score::td::svt::TimeBaseStatus; + +class VehicleTimeImplTest : public ::testing::Test +{ + protected: + VehicleTimeImplTest() + : mock_hpls_{std::make_shared()} + , hpls_guard_{mock_hpls_} + , mock_svt_{std::make_shared()} + , impl_{std::make_unique(mock_svt_, HplsClock::GetInstance())} + { + } + + std::shared_ptr mock_hpls_; + ClockOverrideGuard hpls_guard_; + std::shared_ptr mock_svt_; + std::unique_ptr impl_; +}; + +// ── IsAvailable ────────────────────────────────────────────────────────────── + +TEST_F(VehicleTimeImplTest, IsAvailableReturnsFalseWhenInitFails) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(false)); + EXPECT_FALSE(impl_->IsAvailable()); +} + +TEST_F(VehicleTimeImplTest, IsAvailableReturnsTrueAfterSuccessfulInit) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + EXPECT_TRUE(impl_->IsAvailable()); +} + +TEST_F(VehicleTimeImplTest, IsAvailableCachesResultAfterSuccess) +{ + // Init() is called only once even though IsAvailable() is queried twice. + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + EXPECT_TRUE(impl_->IsAvailable()); + EXPECT_TRUE(impl_->IsAvailable()); +} + +// ── Now — pre-conditions ────────────────────────────────────────────────────── + +TEST_F(VehicleTimeImplTest, NowReturnsUnknownStatusWhenNotReady) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(false)); + impl_->IsAvailable(); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kUnknown)); +} + +TEST_F(VehicleTimeImplTest, NowReturnsUnknownStatusWhenReceiveReturnsNullopt) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(std::nullopt)); + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kUnknown)); +} + +TEST_F(VehicleTimeImplTest, NowReturnsUnknownStatusWhenLocalClockBehindCapture) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + // local_time = 600, now_local = 500 → now_local < local_at_capture → unknown + const SvtSnapshot data{1000ULL, 600ULL, 0.0, {true, false, false, false, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + + const HplsTime::Timepoint local_now{500ns}; + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{local_now, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kUnknown)); +} + +// ── Now — adjusted timestamp ────────────────────────────────────────────────── + +TEST_F(VehicleTimeImplTest, NowComputesAdjustedTimestamp) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + // adjusted = ptp_at_capture + (now_local - local_at_capture) + // = 1000 + (600 - 500) = 1100 ns + const SvtSnapshot data{1000ULL, 500ULL, 0.0, {true, false, false, false, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + + const HplsTime::Timepoint local_now{600ns}; + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{local_now, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_EQ(snapshot.TimePoint().time_since_epoch().count(), 1100); +} + +// ── Now — ConvertPtpStatus mapping ──────────────────────────────────────────── + +TEST_F(VehicleTimeImplTest, NowSetsSynchronizedFlagFromSvtStatus) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + const SvtSnapshot data{0ULL, 0ULL, 0.0, {true, false, false, false, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{HplsTime::Timepoint{0ns}, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kSynchronized)); +} + +TEST_F(VehicleTimeImplTest, NowSetsTimeOutFlagFromSvtStatus) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + // is_timeout = true + const SvtSnapshot data{0ULL, 0ULL, 0.0, {false, true, false, false, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{HplsTime::Timepoint{0ns}, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kTimeOut)); +} + +TEST_F(VehicleTimeImplTest, NowSetsTimeLeapFutureFlagFromSvtStatus) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + // is_time_jump_future = true + const SvtSnapshot data{0ULL, 0ULL, 0.0, {false, false, true, false, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{HplsTime::Timepoint{0ns}, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kTimeLeapFuture)); +} + +TEST_F(VehicleTimeImplTest, NowSetsTimeLeapPastFlagFromSvtStatus) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + // is_time_jump_past = true + const SvtSnapshot data{0ULL, 0ULL, 0.0, {false, false, false, true, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{HplsTime::Timepoint{0ns}, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kTimeLeapPast)); +} + +TEST_F(VehicleTimeImplTest, NowSetsUnknownFlagWhenIsCorrectIsFalse) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + // is_correct = false → kUnknown + const SvtSnapshot data{0ULL, 0ULL, 0.0, {false, false, false, false, false}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{HplsTime::Timepoint{0ns}, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_TRUE(snapshot.Status().IsFlagActive(VehicleTime::StatusFlag::kUnknown)); +} + +TEST_F(VehicleTimeImplTest, NowForwardsRateDeviation) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + impl_->IsAvailable(); + + const SvtSnapshot data{0ULL, 0ULL, 2.5, {false, false, false, false, true}, {}, {}}; + EXPECT_CALL(*mock_svt_, Receive()).WillOnce(Return(data)); + EXPECT_CALL(*mock_hpls_, Now()) + .WillOnce(Return(ClockSnapshot{HplsTime::Timepoint{0ns}, NoStatus{}})); + + const auto snapshot = impl_->Now(); + EXPECT_DOUBLE_EQ(snapshot.Status().rate_deviation, 2.5); +} + +// ── WaitUntilAvailable ─────────────────────────────────────────────────────── + +TEST_F(VehicleTimeImplTest, WaitUntilAvailableReturnsTrueWhenInitSucceeds) +{ + EXPECT_CALL(*mock_svt_, Init()).WillOnce(Return(true)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{3}; + EXPECT_TRUE(impl_->WaitUntilAvailable(score::cpp::stop_source{}.get_token(), deadline)); +} + +TEST_F(VehicleTimeImplTest, WaitUntilAvailableReturnsFalseWhenStopRequested) +{ + EXPECT_CALL(*mock_svt_, Init()).WillRepeatedly(Return(false)); + score::cpp::stop_source ss; + ss.request_stop(); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{3}; + EXPECT_FALSE(impl_->WaitUntilAvailable(ss.get_token(), deadline)); +} + +TEST_F(VehicleTimeImplTest, WaitUntilAvailableReturnsFalseWhenDeadlinePassed) +{ + EXPECT_CALL(*mock_svt_, Init()).WillRepeatedly(Return(false)); + const auto past_deadline = std::chrono::steady_clock::now() - std::chrono::seconds{1}; + EXPECT_FALSE(impl_->WaitUntilAvailable(score::cpp::stop_source{}.get_token(), past_deadline)); +} + +// ── Callback no-ops ────────────────────────────────────────────────────────── + +TEST_F(VehicleTimeImplTest, CallbackMethodsAreNoOps) +{ + impl_->SetTimeSlaveSyncDataReceivedCallback( + [](const TimeSlaveSyncData&) {}); + impl_->UnsetTimeSlaveSyncDataReceivedCallback(); + impl_->SetPDelayMeasurementFinishedCallback( + [](const PDelayMeasurementData&) {}); + impl_->UnsetPDelayMeasurementFinishedCallback(); + // No crash = pass. +} + +} // namespace +} // namespace time +} // namespace score diff --git a/score/time/vehicle_time/vehicle_clock.cpp b/score/time/vehicle_time/vehicle_clock.cpp new file mode 100644 index 0000000..00f423d --- /dev/null +++ b/score/time/vehicle_time/vehicle_clock.cpp @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/vehicle_time/vehicle_clock.h" +#include "score/time/vehicle_time/vehicle_clock_iface.h" + +#include + +namespace score +{ +namespace time +{ + +ClockTraits::Snapshot +ClockTraits::CallNow(const Backend& impl) noexcept +{ + return impl.Now(); +} + +bool AvailabilityHook::CallIsAvailable(const Backend& impl) noexcept +{ + return impl.IsAvailable(); +} + +bool AvailabilityHook::CallWaitUntilAvailable( + const Backend& impl, + const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until) noexcept +{ + return impl.WaitUntilAvailable(token, until); +} + +void SubscriptionHook>::Subscribe( + Backend& impl, + VehicleTime::TimeSlaveSyncDataReceivedCallback cb) noexcept +{ + impl.SetTimeSlaveSyncDataReceivedCallback(std::move(cb)); +} + +void SubscriptionHook>::Unsubscribe( + Backend& impl) noexcept +{ + impl.UnsetTimeSlaveSyncDataReceivedCallback(); +} + +void SubscriptionHook>::Subscribe( + Backend& impl, + VehicleTime::PDelayMeasurementFinishedCallback cb) noexcept +{ + impl.SetPDelayMeasurementFinishedCallback(std::move(cb)); +} + +void SubscriptionHook>::Unsubscribe( + Backend& impl) noexcept +{ + impl.UnsetPDelayMeasurementFinishedCallback(); +} + +} // namespace time +} // namespace score diff --git a/score/time/vehicle_time/vehicle_clock.h b/score/time/vehicle_time/vehicle_clock.h new file mode 100644 index 0000000..266be87 --- /dev/null +++ b/score/time/vehicle_time/vehicle_clock.h @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_VEHICLE_CLOCK_H +#define SCORE_TIME_VEHICLE_TIME_VEHICLE_CLOCK_H + +#include "score/time/ptp/pdelay_measurement_data.h" +#include "score/time/ptp/time_slave_sync_data.h" +#include "score/time/vehicle_time/vehicle_time.h" +#include "score/time/clock/availability_hook.h" +#include "score/time/clock/clock.h" +#include "score/time/clock/clock_snapshot.h" +#include "score/time/clock/clock_status.h" + +#include + +#include + +namespace score +{ +namespace time +{ + +class VehicleClockIface; + +template <> +struct ClockTraits +{ + using Backend = VehicleClockIface; + using Duration = VehicleTime::Duration; + using Timepoint = VehicleTime::Timepoint; + using Snapshot = ClockSnapshot; + + /// \brief Obtains the current vehicle time snapshot from the backend. + static Snapshot CallNow(const Backend& impl) noexcept; +}; + +template <> +struct AvailabilityHook +{ + using Backend = ClockTraits::Backend; + + /// \brief Returns true if the vehicle time backend resource is available. + static bool CallIsAvailable(const Backend& impl) noexcept; + + /// @brief Blocks until the vehicle time resource is available or deadline / stop-token fires. + static bool CallWaitUntilAvailable(const Backend& impl, + const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until) noexcept; +}; + +template <> +struct SubscriptionHook> +{ + using Backend = ClockTraits::Backend; + using Callback = VehicleTime::TimeSlaveSyncDataReceivedCallback; + + static void Subscribe(Backend& impl, Callback cb) noexcept; + static void Unsubscribe(Backend& impl) noexcept; +}; + +template <> +struct SubscriptionHook> +{ + using Backend = ClockTraits::Backend; + using Callback = VehicleTime::PDelayMeasurementFinishedCallback; + + static void Subscribe(Backend& impl, Callback cb) noexcept; + static void Unsubscribe(Backend& impl) noexcept; +}; + +using VehicleClock = Clock; +using VehicleTimePoint = ClockTraits::Timepoint; +using VehicleSnapshot = ClockTraits::Snapshot; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_VEHICLE_CLOCK_H diff --git a/score/time/vehicle_time/vehicle_clock_iface.h b/score/time/vehicle_time/vehicle_clock_iface.h new file mode 100644 index 0000000..b3cf1dd --- /dev/null +++ b/score/time/vehicle_time/vehicle_clock_iface.h @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_VEHICLE_CLOCK_IFACE_H +#define SCORE_TIME_VEHICLE_TIME_VEHICLE_CLOCK_IFACE_H + +#include "score/time/vehicle_time/vehicle_time.h" +#include "score/time/clock/clock_snapshot.h" + +#include + +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Pure-virtual pimpl interface for the vehicle time domain backend. +/// +/// INTERNAL — must not be included by user code. +/// Consumers: test mocks and backend implementations under details/. +/// +class VehicleClockIface +{ + public: + virtual ~VehicleClockIface() noexcept = default; + + /// \brief Returns the current vehicle time snapshot (time-point + quality status). + virtual ClockSnapshot Now() const noexcept = 0; + + /// \brief Returns true if the vehicle time backend resource is available. + virtual bool IsAvailable() const noexcept = 0; + + /// \brief Blocks until the vehicle time resource is available or the deadline / stop-token fires. + /// + /// \param token Stop token that can interrupt the wait. + /// \param until Steady-clock deadline after which the wait is abandoned. + /// + /// \return true if the resource became available, false if the wait was aborted. + virtual bool WaitUntilAvailable(const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until) const noexcept = 0; + + /// \brief Installs the callback invoked when new time-sync data arrives. + virtual void SetTimeSlaveSyncDataReceivedCallback( + VehicleTime::TimeSlaveSyncDataReceivedCallback&& callback) noexcept = 0; + + /// \brief Removes the time-sync data callback. + virtual void UnsetTimeSlaveSyncDataReceivedCallback() noexcept = 0; + + /// \brief Installs the callback invoked after a finished pDelay measurement. + virtual void SetPDelayMeasurementFinishedCallback( + VehicleTime::PDelayMeasurementFinishedCallback&& callback) noexcept = 0; + + /// \brief Removes the pDelay measurement callback. + virtual void UnsetPDelayMeasurementFinishedCallback() noexcept = 0; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_VEHICLE_CLOCK_IFACE_H diff --git a/score/time/vehicle_time/vehicle_clock_test.cpp b/score/time/vehicle_time/vehicle_clock_test.cpp new file mode 100644 index 0000000..6564cdb --- /dev/null +++ b/score/time/vehicle_time/vehicle_clock_test.cpp @@ -0,0 +1,210 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/vehicle_time/vehicle_time_mock.h" +#include "score/time/clock/clock_override_guard.h" + +#include + +#include +#include + +#include +#include +#include +#include + +using ::testing::_; +using ::testing::Return; + +namespace score +{ +namespace time +{ + +namespace +{ + +class SampleVehicleService +{ + public: + [[nodiscard]] bool CheckAvailable() const noexcept + { + return VehicleClock::GetInstance().IsAvailable(); + } + + [[nodiscard]] VehicleTime::Timepoint GetCurrentTime() const noexcept + { + return VehicleClock::GetInstance().Now().TimePoint(); + } +}; + +} // namespace + +TEST(VehicleClockTest, NowReturnsSynchronizedStatusAndTimepoint) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + VehicleTimeStatus status; + status.flags = ClockStatus{{VehicleTime::StatusFlag::kSynchronized}}; + const ClockSnapshot snapshot{ + VehicleTime::Timepoint{std::chrono::nanoseconds{42LL}}, status}; + + EXPECT_CALL(*mock, Now()).WillOnce(Return(snapshot)); + + const auto result = VehicleClock::GetInstance().Now(); + + EXPECT_TRUE(result.Status().IsFlagActive(VehicleTime::StatusFlag::kSynchronized)); + EXPECT_EQ(result.TimePoint().time_since_epoch(), std::chrono::nanoseconds{42LL}); +} + +TEST(VehicleClockTest, NowIsSynchronizedReturnsTrueWhenFlagSet) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + VehicleTimeStatus status; + status.flags = ClockStatus{{VehicleTime::StatusFlag::kSynchronized}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{VehicleTime::Timepoint{}, status})); + + EXPECT_TRUE(VehicleClock::GetInstance().Now().Status().IsSynchronized()); +} + +TEST(VehicleClockTest, NowIsSynchronizedReturnsFalseWhenTimeoutSet) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + VehicleTimeStatus status; + status.flags = ClockStatus{ + {VehicleTime::StatusFlag::kSynchronized, VehicleTime::StatusFlag::kTimeOut}}; + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{VehicleTime::Timepoint{}, status})); + + EXPECT_FALSE(VehicleClock::GetInstance().Now().Status().IsSynchronized()); +} + +// ── UC2: IsAvailable / WaitUntilAvailable ───────────────────────────────────── + +TEST(VehicleClockTest, IsAvailableReturnsTrueWhenBackendReports) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, IsAvailable()).WillOnce(Return(true)); + + EXPECT_TRUE(VehicleClock::GetInstance().IsAvailable()); +} + +TEST(VehicleClockTest, IsAvailableReturnsFalseWhenBackendUnavailable) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, IsAvailable()).WillOnce(Return(false)); + + EXPECT_FALSE(VehicleClock::GetInstance().IsAvailable()); +} + +TEST(VehicleClockTest, WaitUntilAvailableForwardsTokenAndDeadlineToBackend) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + score::cpp::stop_source source; + const auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds{100}; + + EXPECT_CALL(*mock, WaitUntilAvailable(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(VehicleClock::GetInstance().WaitUntilAvailable(source.get_token(), until)); +} + +// ── UC3: Subscribe / Unsubscribe — callback is captured and invocable ───────── + +TEST(VehicleClockTest, SubscribeTimeSlaveSyncDataCapturesAndInvokesCallback) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + VehicleTime::TimeSlaveSyncDataReceivedCallback captured_cb; + EXPECT_CALL(*mock, SetTimeSlaveSyncDataReceivedCallback(_)) + .WillOnce([&captured_cb](VehicleTime::TimeSlaveSyncDataReceivedCallback&& cb) { + captured_cb = std::move(cb); + }); + + bool invoked{false}; + VehicleClock::GetInstance().Subscribe>( + [&invoked](const TimeSlaveSyncData&) { invoked = true; }); + + TimeSlaveSyncData data{}; + captured_cb(data); + + EXPECT_TRUE(invoked); +} + +TEST(VehicleClockTest, UnsubscribeTimeSlaveSyncDataForwardsToBackend) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + EXPECT_CALL(*mock, UnsetTimeSlaveSyncDataReceivedCallback()).Times(1); + + VehicleClock::GetInstance().Unsubscribe>(); +} + +// ── UC4: StatusFlag type alias is accessible via VehicleClock ───────────────── + +TEST(VehicleClockTest, VehicleTimeStatusFlagValues) +{ + EXPECT_EQ(static_cast(VehicleTime::StatusFlag::kSynchronized), 1U); + EXPECT_EQ(static_cast(VehicleTime::StatusFlag::kTimeOut), 0U); + EXPECT_EQ(static_cast(VehicleTime::StatusFlag::kTimeLeapFuture), 3U); +} + +// ── UC6: ClockOverrideGuard injects mock into a SUT that calls GetInstance() ── + +TEST(VehicleClockTest, ClockOverrideGuardInjectsMockIntoSut) +{ + auto mock = std::make_shared(); + ClockOverrideGuard guard{mock}; + + const VehicleTime::Timepoint expected_tp{std::chrono::nanoseconds{999LL}}; + + EXPECT_CALL(*mock, IsAvailable()).WillOnce(Return(true)); + EXPECT_CALL(*mock, Now()).WillOnce(Return( + ClockSnapshot{expected_tp, VehicleTimeStatus{}})); + + SampleVehicleService sut; + EXPECT_TRUE(sut.CheckAvailable()); + EXPECT_EQ(sut.GetCurrentTime(), expected_tp); +} + +TEST(VehicleClockTest, ClockOverrideGuardRestoresBackendAfterScope) +{ + auto mock = std::make_shared(); + { + ClockOverrideGuard guard{mock}; + EXPECT_CALL(*mock, IsAvailable()).WillOnce(Return(false)); + EXPECT_FALSE(VehicleClock::GetInstance().IsAvailable()); + } + // After guard goes out of scope, override is cleared. + // A new guard for the same Tag must succeed without assertion. + auto mock2 = std::make_shared(); + ClockOverrideGuard guard2{mock2}; + EXPECT_CALL(*mock2, IsAvailable()).WillOnce(Return(true)); + EXPECT_TRUE(VehicleClock::GetInstance().IsAvailable()); +} + +} // namespace time +} // namespace score diff --git a/score/time/vehicle_time/vehicle_time.h b/score/time/vehicle_time/vehicle_time.h new file mode 100644 index 0000000..812f087 --- /dev/null +++ b/score/time/vehicle_time/vehicle_time.h @@ -0,0 +1,168 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_VEHICLE_TIME_H +#define SCORE_TIME_VEHICLE_TIME_VEHICLE_TIME_H + +#include "score/time/ptp/pdelay_measurement_data.h" +#include "score/time/ptp/time_slave_sync_data.h" +#include "score/time/clock/clock_status.h" + +#include + +#include +#include +#include +#include +#include + +namespace score +{ +namespace time +{ + +/// +/// \brief Tag struct for the PTP-synchronized vehicle time domain. +/// +/// Contains only domain types — no vtable, no factory, no virtual methods. +/// The implementation is hidden behind VehicleClockIface (see VehicleTime/vehicle_clock_iface.h). +/// +struct VehicleTime +{ + /// + /// \brief Enumeration expressing the synchronisation quality state of the vehicle time domain. + /// Each enumerator is a **bit position** (not a bitmask). + /// + enum class StatusFlag : std::uint8_t + { + kTimeOut = 0U, /*!< TB was not synchronized within a certain time frame. */ + kSynchronized = 1U, /*!< The TB was synchronized at least once. */ + kSynchToGateway = 2U, /*!< The TB is in sync with the gateway. */ + kTimeLeapFuture = 3U, /*!< An adjustment greater than a certain threshold has been made. */ + kTimeLeapPast = 4U, /*!< An adjustment back in time greater than a certain threshold has been made. */ + kUnknown = 7U /*!< Unknown status. */ + }; + + using Duration = std::chrono::nanoseconds; + using Timepoint = std::chrono::time_point; + + static constexpr std::uint64_t kCallbackCapacity{64U}; + + using TimeSlaveSyncDataReceivedCallback = + score::cpp::callback&), kCallbackCapacity>; + using PDelayMeasurementFinishedCallback = + score::cpp::callback&), kCallbackCapacity>; +}; + +// Explicit specialisations for ClockStatus. +// Defined inline here so they are visible before VehicleTimeStatus uses them. + +/// \brief Returns true if vehicle time is synchronized. +/// +/// Synchronized := kSynchronized is set AND none of {kTimeOut, kTimeLeapFuture, kTimeLeapPast} is set. +template <> +inline bool ClockStatus::IsSynchronized() const noexcept +{ + return (IsFlagActive(VehicleTime::StatusFlag::kSynchronized) && + (!IsAnyOfFlagsActive({VehicleTime::StatusFlag::kTimeOut, + VehicleTime::StatusFlag::kTimeLeapFuture, + VehicleTime::StatusFlag::kTimeLeapPast}))); +} + +/// \brief Returns true if the vehicle time status is in a valid (non-error) state. +/// +/// Valid := kUnknown is NOT set, at least one non-kUnknown flag is set, +/// and kTimeLeapFuture and kTimeLeapPast are not both set simultaneously. +template <> +inline bool ClockStatus::IsValid() const noexcept +{ + if (IsFlagActive(VehicleTime::StatusFlag::kUnknown)) + { + return false; + } + if (!IsAnyOfFlagsActive({VehicleTime::StatusFlag::kTimeOut, + VehicleTime::StatusFlag::kSynchronized, + VehicleTime::StatusFlag::kSynchToGateway, + VehicleTime::StatusFlag::kTimeLeapFuture, + VehicleTime::StatusFlag::kTimeLeapPast})) + { + return false; + } + if (IsFlagActive(VehicleTime::StatusFlag::kTimeLeapFuture) && + IsFlagActive(VehicleTime::StatusFlag::kTimeLeapPast)) + { + return false; + } + return true; +} + +/// \brief Formats all active VehicleTime status flags into an ostringstream for diagnostics. +template <> +inline std::ostringstream ClockStatus::PrintTo() const +{ + static const std::map kFlagNames = { + {VehicleTime::StatusFlag::kTimeOut, "kTimeOut"}, + {VehicleTime::StatusFlag::kSynchronized, "kSynchronized"}, + {VehicleTime::StatusFlag::kSynchToGateway, "kSynchToGateway"}, + {VehicleTime::StatusFlag::kTimeLeapFuture, "kTimeLeapFuture"}, + {VehicleTime::StatusFlag::kTimeLeapPast, "kTimeLeapPast"}, + {VehicleTime::StatusFlag::kUnknown, "kUnknown"}, + }; + std::ostringstream oss; + oss << "["; + for (const auto& entry : kFlagNames) + { + oss << entry.second << ": " << (IsFlagActive(entry.first) ? "true" : "false") << ", "; + } + oss << "]"; + return oss; +} + +/// +/// \brief Synchronisation-quality status snapshot for the vehicle time domain. +/// +/// Bundled alongside the time-point inside ClockSnapshot. +/// +struct VehicleTimeStatus +{ + /// \brief Bit-flag quality indicators for the vehicle time signal. + ClockStatus flags{}; + + /// \brief Fractional rate deviation of the local clock relative to the Grand Master. + /// Unit: dimensionless (e.g. 1.0e-9 == 1 ppb). Zero when kTimeOut is set. + double rate_deviation{0.0}; + + // Convenience delegates — avoid .flags. indirection at call sites. + + /// \brief Returns true if the vehicle time is currently synchronized. + bool IsSynchronized() const noexcept + { + return flags.IsSynchronized(); + } + + /// \brief Returns true if the vehicle time is in a valid (non-error) state. + bool IsValid() const noexcept + { + return flags.IsValid(); + } + + /// \brief Returns true if the given StatusFlag bit-position is active. + bool IsFlagActive(const VehicleTime::StatusFlag flag) const noexcept + { + return flags.IsFlagActive(flag); + } +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_VEHICLE_TIME_H diff --git a/score/time/vehicle_time/vehicle_time_mock.h b/score/time/vehicle_time/vehicle_time_mock.h new file mode 100644 index 0000000..5f302ff --- /dev/null +++ b/score/time/vehicle_time/vehicle_time_mock.h @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_TIME_VEHICLE_TIME_VEHICLE_TIME_MOCK_H +#define SCORE_TIME_VEHICLE_TIME_VEHICLE_TIME_MOCK_H + +#include "score/time/vehicle_time/vehicle_clock_iface.h" +#include "score/time/vehicle_time/vehicle_clock.h" + +#include + +namespace score +{ +namespace time +{ + +/// @brief GMock test double for the vehicle time domain. +/// +/// Implements @c VehicleClockIface so it can be injected via +/// @c ClockOverrideGuard in unit tests. +/// +/// Usage: +/// @code +/// auto mock = std::make_shared(); +/// ClockOverrideGuard guard{mock}; +/// EXPECT_CALL(*mock, Now()).WillOnce(Return(...)); +/// @endcode +class VehicleTimeMock : public VehicleClockIface +{ + public: + VehicleTimeMock() = default; + ~VehicleTimeMock() noexcept override = default; + VehicleTimeMock(const VehicleTimeMock&) = delete; + VehicleTimeMock& operator=(const VehicleTimeMock&) = delete; + VehicleTimeMock(VehicleTimeMock&&) = delete; + VehicleTimeMock& operator=(VehicleTimeMock&&) = delete; + + MOCK_METHOD((ClockSnapshot), + Now, + (), + (const, noexcept, override)); + + MOCK_METHOD(bool, IsAvailable, (), (const, noexcept, override)); + + MOCK_METHOD(bool, + WaitUntilAvailable, + (const score::cpp::stop_token& token, + std::chrono::steady_clock::time_point until), + (const, noexcept, override)); + + MOCK_METHOD(void, + SetTimeSlaveSyncDataReceivedCallback, + (VehicleTime::TimeSlaveSyncDataReceivedCallback && callback), + (noexcept, override)); + + MOCK_METHOD(void, UnsetTimeSlaveSyncDataReceivedCallback, (), (noexcept, override)); + + MOCK_METHOD(void, + SetPDelayMeasurementFinishedCallback, + (VehicleTime::PDelayMeasurementFinishedCallback && callback), + (noexcept, override)); + + MOCK_METHOD(void, UnsetPDelayMeasurementFinishedCallback, (), (noexcept, override)); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_VEHICLE_TIME_VEHICLE_TIME_MOCK_H diff --git a/score/time/vehicle_time/vehicle_time_status_test.cpp b/score/time/vehicle_time/vehicle_time_status_test.cpp new file mode 100644 index 0000000..98daf08 --- /dev/null +++ b/score/time/vehicle_time/vehicle_time_status_test.cpp @@ -0,0 +1,169 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/time/vehicle_time/vehicle_time.h" + +#include + +#include + +namespace score +{ +namespace time +{ +namespace +{ + +using Flag = VehicleTime::StatusFlag; + +class TestVehicleTimeStatus : public ::testing::Test +{ +}; + +// ── IsSynchronized ──────────────────────────────────────────────────────────── + +TEST_F(TestVehicleTimeStatus, IsSynchronizedReturnsTrueOnlyWhenFlagSetWithoutErrorFlags) +{ + VehicleTimeStatus status{}; + EXPECT_FALSE(status.IsSynchronized()); + + status.flags.AddFlag(Flag::kSynchToGateway); + EXPECT_FALSE(status.IsSynchronized()); + + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsSynchronized()); + + status.flags.AddFlag(Flag::kTimeOut); + EXPECT_FALSE(status.IsSynchronized()); +} + +TEST_F(TestVehicleTimeStatus, IsSynchronizedReturnsFalseWhenTimeLeapFutureSet) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsSynchronized()); + + status.flags.AddFlag(Flag::kTimeLeapFuture); + EXPECT_FALSE(status.IsSynchronized()); +} + +TEST_F(TestVehicleTimeStatus, IsSynchronizedReturnsFalseWhenTimeLeapPastSet) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsSynchronized()); + + status.flags.AddFlag(Flag::kTimeLeapPast); + EXPECT_FALSE(status.IsSynchronized()); +} + +// ── IsValid ─────────────────────────────────────────────────────────────────── + +TEST_F(TestVehicleTimeStatus, IsValidReturnsFalseWhenNoFlagsSet) +{ + const VehicleTimeStatus status{}; + EXPECT_FALSE(status.IsValid()); +} + +TEST_F(TestVehicleTimeStatus, IsValidReturnsTrueWithSynchToGatewayAndSynchronized) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchToGateway); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsValid()); +} + +TEST_F(TestVehicleTimeStatus, IsValidReturnsTrueWithLeapPastFlag) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchToGateway); + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kTimeLeapPast); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kTimeOut); + EXPECT_TRUE(status.IsValid()); +} + +TEST_F(TestVehicleTimeStatus, IsValidReturnsTrueWithLeapFutureFlag) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchToGateway); + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kTimeLeapFuture); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kTimeOut); + EXPECT_TRUE(status.IsValid()); +} + +TEST_F(TestVehicleTimeStatus, IsValidReturnsFalseWhenUnknownFlagSet) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchToGateway); + status.flags.AddFlag(Flag::kSynchronized); + status.flags.AddFlag(Flag::kTimeLeapPast); + status.flags.AddFlag(Flag::kTimeOut); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kUnknown); + EXPECT_FALSE(status.IsValid()); +} + +TEST_F(TestVehicleTimeStatus, IsValidReturnsFalseWhenBothLeapFlagsSet) +{ + VehicleTimeStatus status{}; + status.flags.AddFlag(Flag::kSynchToGateway); + status.flags.AddFlag(Flag::kSynchronized); + status.flags.AddFlag(Flag::kTimeLeapPast); + EXPECT_TRUE(status.IsValid()); + + status.flags.AddFlag(Flag::kTimeLeapFuture); + EXPECT_FALSE(status.IsValid()); +} + +// ── PrintTo ─────────────────────────────────────────────────────────────────── + +TEST_F(TestVehicleTimeStatus, PrintToFormatsActiveFlagsCorrectly) +{ + ClockStatus status{Flag::kSynchToGateway}; + + std::ostringstream os; + os << status; + + EXPECT_STREQ( + "[kTimeOut: false, kSynchronized: false, kSynchToGateway: true, kTimeLeapFuture: false, " + "kTimeLeapPast: false, kUnknown: false, ]", + os.str().c_str()); +} + +// ── IsFlagActive convenience delegate ──────────────────────────────────────── + +TEST_F(TestVehicleTimeStatus, IsFlagActiveDelegatesCorrectly) +{ + VehicleTimeStatus status{}; + EXPECT_FALSE(status.IsFlagActive(Flag::kSynchronized)); + + status.flags.AddFlag(Flag::kSynchronized); + EXPECT_TRUE(status.IsFlagActive(Flag::kSynchronized)); + EXPECT_FALSE(status.IsFlagActive(Flag::kTimeOut)); +} + +} // namespace +} // namespace time +} // namespace score From ea3b13208a83701a4d4d15278a1a12e3b13c8a65 Mon Sep 17 00:00:00 2001 From: Valery Lavrov Date: Thu, 30 Apr 2026 09:35:20 +0200 Subject: [PATCH 2/4] Update documentaiton --- .../_assets/app/app_class.puml | 0 .../_assets/app/app_init_seq.puml | 0 .../_assets/app/app_workflow_seq.puml | 0 .../_assets/ctrlflow/ctrlflow_class.puml | 0 .../_assets/ctrlflow/ctrlflow_init_seq.puml | 0 .../ctrlflow/ctrlflow_workflow_seq.puml | 0 .../_assets/dd_class.puml | 0 .../_assets/dd_data_control_flow.puml | 0 .../_assets/dd_deployment.puml | 0 .../abs_time/abs_time_data_control_flow.puml | 0 .../abs_time/abs_time_deployment.puml | 0 .../examples/qvt/qvt_data_control_flow.puml | 0 .../_assets/examples/qvt/qvt_deployment.puml | 0 .../_assets/ipc/ipc_class.puml | 0 .../_assets/ipc/ipc_init_seq.puml | 0 .../_assets/ipc/ipc_publish_seq.puml | 0 .../_assets/ipc/ipc_receive_seq.puml | 0 .../_assets/msg_broker/msg_broker_class.puml | 0 .../msg_broker/msg_broker_init_seq.puml | 0 .../msg_broker/msg_broker_workflow_seq.puml | 0 .../_assets/mw/mw_class.puml | 0 .../_assets/mw/mw_time_receive_seq.puml | 0 .../mw/mw_time_receive_simple_seq.puml | 0 .../ptp_machine/ptp_machine_class.puml | 0 .../ptp_machine_get_new_data_seq.puml | 0 .../ptp_machine/ptp_machine_init_seq.puml | 0 .../_assets/sad_deployment.puml | 0 .../_assets/sad_sdat_components.drawio | 55 + .../_assets/sad_vt_components.drawio | 70 + .../_assets/ver_machine/ver_class.puml | 0 .../_assets/ver_machine/ver_init_seq.puml | 0 .../ver_machine/ver_verification_seq.puml | 0 docs/TimeDaemon/index.rst | 983 ++++++++++++++ docs/index.rst | 3 +- docs/time/_assets/architecture_layers.puml | 82 ++ docs/time/_assets/class_overview.puml | 133 ++ docs/time/_assets/uc_availability.puml | 68 + docs/time/_assets/uc_polling.puml | 56 + docs/time/_assets/uc_subscription.puml | 69 + docs/time/_assets/uc_testing.puml | 89 ++ docs/time/index.rst | 1192 +++++------------ 41 files changed, 1960 insertions(+), 840 deletions(-) rename docs/{time => TimeDaemon}/_assets/app/app_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/app/app_init_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/app/app_workflow_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ctrlflow/ctrlflow_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/ctrlflow/ctrlflow_init_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ctrlflow/ctrlflow_workflow_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/dd_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/dd_data_control_flow.puml (100%) rename docs/{time => TimeDaemon}/_assets/dd_deployment.puml (100%) rename docs/{time => TimeDaemon}/_assets/examples/abs_time/abs_time_data_control_flow.puml (100%) rename docs/{time => TimeDaemon}/_assets/examples/abs_time/abs_time_deployment.puml (100%) rename docs/{time => TimeDaemon}/_assets/examples/qvt/qvt_data_control_flow.puml (100%) rename docs/{time => TimeDaemon}/_assets/examples/qvt/qvt_deployment.puml (100%) rename docs/{time => TimeDaemon}/_assets/ipc/ipc_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/ipc/ipc_init_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ipc/ipc_publish_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ipc/ipc_receive_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/msg_broker/msg_broker_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/msg_broker/msg_broker_init_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/msg_broker/msg_broker_workflow_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/mw/mw_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/mw/mw_time_receive_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/mw/mw_time_receive_simple_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ptp_machine/ptp_machine_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/ptp_machine/ptp_machine_get_new_data_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ptp_machine/ptp_machine_init_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/sad_deployment.puml (100%) create mode 100644 docs/TimeDaemon/_assets/sad_sdat_components.drawio create mode 100644 docs/TimeDaemon/_assets/sad_vt_components.drawio rename docs/{time => TimeDaemon}/_assets/ver_machine/ver_class.puml (100%) rename docs/{time => TimeDaemon}/_assets/ver_machine/ver_init_seq.puml (100%) rename docs/{time => TimeDaemon}/_assets/ver_machine/ver_verification_seq.puml (100%) create mode 100644 docs/TimeDaemon/index.rst create mode 100644 docs/time/_assets/architecture_layers.puml create mode 100644 docs/time/_assets/class_overview.puml create mode 100644 docs/time/_assets/uc_availability.puml create mode 100644 docs/time/_assets/uc_polling.puml create mode 100644 docs/time/_assets/uc_subscription.puml create mode 100644 docs/time/_assets/uc_testing.puml diff --git a/docs/time/_assets/app/app_class.puml b/docs/TimeDaemon/_assets/app/app_class.puml similarity index 100% rename from docs/time/_assets/app/app_class.puml rename to docs/TimeDaemon/_assets/app/app_class.puml diff --git a/docs/time/_assets/app/app_init_seq.puml b/docs/TimeDaemon/_assets/app/app_init_seq.puml similarity index 100% rename from docs/time/_assets/app/app_init_seq.puml rename to docs/TimeDaemon/_assets/app/app_init_seq.puml diff --git a/docs/time/_assets/app/app_workflow_seq.puml b/docs/TimeDaemon/_assets/app/app_workflow_seq.puml similarity index 100% rename from docs/time/_assets/app/app_workflow_seq.puml rename to docs/TimeDaemon/_assets/app/app_workflow_seq.puml diff --git a/docs/time/_assets/ctrlflow/ctrlflow_class.puml b/docs/TimeDaemon/_assets/ctrlflow/ctrlflow_class.puml similarity index 100% rename from docs/time/_assets/ctrlflow/ctrlflow_class.puml rename to docs/TimeDaemon/_assets/ctrlflow/ctrlflow_class.puml diff --git a/docs/time/_assets/ctrlflow/ctrlflow_init_seq.puml b/docs/TimeDaemon/_assets/ctrlflow/ctrlflow_init_seq.puml similarity index 100% rename from docs/time/_assets/ctrlflow/ctrlflow_init_seq.puml rename to docs/TimeDaemon/_assets/ctrlflow/ctrlflow_init_seq.puml diff --git a/docs/time/_assets/ctrlflow/ctrlflow_workflow_seq.puml b/docs/TimeDaemon/_assets/ctrlflow/ctrlflow_workflow_seq.puml similarity index 100% rename from docs/time/_assets/ctrlflow/ctrlflow_workflow_seq.puml rename to docs/TimeDaemon/_assets/ctrlflow/ctrlflow_workflow_seq.puml diff --git a/docs/time/_assets/dd_class.puml b/docs/TimeDaemon/_assets/dd_class.puml similarity index 100% rename from docs/time/_assets/dd_class.puml rename to docs/TimeDaemon/_assets/dd_class.puml diff --git a/docs/time/_assets/dd_data_control_flow.puml b/docs/TimeDaemon/_assets/dd_data_control_flow.puml similarity index 100% rename from docs/time/_assets/dd_data_control_flow.puml rename to docs/TimeDaemon/_assets/dd_data_control_flow.puml diff --git a/docs/time/_assets/dd_deployment.puml b/docs/TimeDaemon/_assets/dd_deployment.puml similarity index 100% rename from docs/time/_assets/dd_deployment.puml rename to docs/TimeDaemon/_assets/dd_deployment.puml diff --git a/docs/time/_assets/examples/abs_time/abs_time_data_control_flow.puml b/docs/TimeDaemon/_assets/examples/abs_time/abs_time_data_control_flow.puml similarity index 100% rename from docs/time/_assets/examples/abs_time/abs_time_data_control_flow.puml rename to docs/TimeDaemon/_assets/examples/abs_time/abs_time_data_control_flow.puml diff --git a/docs/time/_assets/examples/abs_time/abs_time_deployment.puml b/docs/TimeDaemon/_assets/examples/abs_time/abs_time_deployment.puml similarity index 100% rename from docs/time/_assets/examples/abs_time/abs_time_deployment.puml rename to docs/TimeDaemon/_assets/examples/abs_time/abs_time_deployment.puml diff --git a/docs/time/_assets/examples/qvt/qvt_data_control_flow.puml b/docs/TimeDaemon/_assets/examples/qvt/qvt_data_control_flow.puml similarity index 100% rename from docs/time/_assets/examples/qvt/qvt_data_control_flow.puml rename to docs/TimeDaemon/_assets/examples/qvt/qvt_data_control_flow.puml diff --git a/docs/time/_assets/examples/qvt/qvt_deployment.puml b/docs/TimeDaemon/_assets/examples/qvt/qvt_deployment.puml similarity index 100% rename from docs/time/_assets/examples/qvt/qvt_deployment.puml rename to docs/TimeDaemon/_assets/examples/qvt/qvt_deployment.puml diff --git a/docs/time/_assets/ipc/ipc_class.puml b/docs/TimeDaemon/_assets/ipc/ipc_class.puml similarity index 100% rename from docs/time/_assets/ipc/ipc_class.puml rename to docs/TimeDaemon/_assets/ipc/ipc_class.puml diff --git a/docs/time/_assets/ipc/ipc_init_seq.puml b/docs/TimeDaemon/_assets/ipc/ipc_init_seq.puml similarity index 100% rename from docs/time/_assets/ipc/ipc_init_seq.puml rename to docs/TimeDaemon/_assets/ipc/ipc_init_seq.puml diff --git a/docs/time/_assets/ipc/ipc_publish_seq.puml b/docs/TimeDaemon/_assets/ipc/ipc_publish_seq.puml similarity index 100% rename from docs/time/_assets/ipc/ipc_publish_seq.puml rename to docs/TimeDaemon/_assets/ipc/ipc_publish_seq.puml diff --git a/docs/time/_assets/ipc/ipc_receive_seq.puml b/docs/TimeDaemon/_assets/ipc/ipc_receive_seq.puml similarity index 100% rename from docs/time/_assets/ipc/ipc_receive_seq.puml rename to docs/TimeDaemon/_assets/ipc/ipc_receive_seq.puml diff --git a/docs/time/_assets/msg_broker/msg_broker_class.puml b/docs/TimeDaemon/_assets/msg_broker/msg_broker_class.puml similarity index 100% rename from docs/time/_assets/msg_broker/msg_broker_class.puml rename to docs/TimeDaemon/_assets/msg_broker/msg_broker_class.puml diff --git a/docs/time/_assets/msg_broker/msg_broker_init_seq.puml b/docs/TimeDaemon/_assets/msg_broker/msg_broker_init_seq.puml similarity index 100% rename from docs/time/_assets/msg_broker/msg_broker_init_seq.puml rename to docs/TimeDaemon/_assets/msg_broker/msg_broker_init_seq.puml diff --git a/docs/time/_assets/msg_broker/msg_broker_workflow_seq.puml b/docs/TimeDaemon/_assets/msg_broker/msg_broker_workflow_seq.puml similarity index 100% rename from docs/time/_assets/msg_broker/msg_broker_workflow_seq.puml rename to docs/TimeDaemon/_assets/msg_broker/msg_broker_workflow_seq.puml diff --git a/docs/time/_assets/mw/mw_class.puml b/docs/TimeDaemon/_assets/mw/mw_class.puml similarity index 100% rename from docs/time/_assets/mw/mw_class.puml rename to docs/TimeDaemon/_assets/mw/mw_class.puml diff --git a/docs/time/_assets/mw/mw_time_receive_seq.puml b/docs/TimeDaemon/_assets/mw/mw_time_receive_seq.puml similarity index 100% rename from docs/time/_assets/mw/mw_time_receive_seq.puml rename to docs/TimeDaemon/_assets/mw/mw_time_receive_seq.puml diff --git a/docs/time/_assets/mw/mw_time_receive_simple_seq.puml b/docs/TimeDaemon/_assets/mw/mw_time_receive_simple_seq.puml similarity index 100% rename from docs/time/_assets/mw/mw_time_receive_simple_seq.puml rename to docs/TimeDaemon/_assets/mw/mw_time_receive_simple_seq.puml diff --git a/docs/time/_assets/ptp_machine/ptp_machine_class.puml b/docs/TimeDaemon/_assets/ptp_machine/ptp_machine_class.puml similarity index 100% rename from docs/time/_assets/ptp_machine/ptp_machine_class.puml rename to docs/TimeDaemon/_assets/ptp_machine/ptp_machine_class.puml diff --git a/docs/time/_assets/ptp_machine/ptp_machine_get_new_data_seq.puml b/docs/TimeDaemon/_assets/ptp_machine/ptp_machine_get_new_data_seq.puml similarity index 100% rename from docs/time/_assets/ptp_machine/ptp_machine_get_new_data_seq.puml rename to docs/TimeDaemon/_assets/ptp_machine/ptp_machine_get_new_data_seq.puml diff --git a/docs/time/_assets/ptp_machine/ptp_machine_init_seq.puml b/docs/TimeDaemon/_assets/ptp_machine/ptp_machine_init_seq.puml similarity index 100% rename from docs/time/_assets/ptp_machine/ptp_machine_init_seq.puml rename to docs/TimeDaemon/_assets/ptp_machine/ptp_machine_init_seq.puml diff --git a/docs/time/_assets/sad_deployment.puml b/docs/TimeDaemon/_assets/sad_deployment.puml similarity index 100% rename from docs/time/_assets/sad_deployment.puml rename to docs/TimeDaemon/_assets/sad_deployment.puml diff --git a/docs/TimeDaemon/_assets/sad_sdat_components.drawio b/docs/TimeDaemon/_assets/sad_sdat_components.drawio new file mode 100644 index 0000000..be54d13 --- /dev/null +++ b/docs/TimeDaemon/_assets/sad_sdat_components.drawio @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TimeDaemon/_assets/sad_vt_components.drawio b/docs/TimeDaemon/_assets/sad_vt_components.drawio new file mode 100644 index 0000000..b90cdd9 --- /dev/null +++ b/docs/TimeDaemon/_assets/sad_vt_components.drawio @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/time/_assets/ver_machine/ver_class.puml b/docs/TimeDaemon/_assets/ver_machine/ver_class.puml similarity index 100% rename from docs/time/_assets/ver_machine/ver_class.puml rename to docs/TimeDaemon/_assets/ver_machine/ver_class.puml diff --git a/docs/time/_assets/ver_machine/ver_init_seq.puml b/docs/TimeDaemon/_assets/ver_machine/ver_init_seq.puml similarity index 100% rename from docs/time/_assets/ver_machine/ver_init_seq.puml rename to docs/TimeDaemon/_assets/ver_machine/ver_init_seq.puml diff --git a/docs/time/_assets/ver_machine/ver_verification_seq.puml b/docs/TimeDaemon/_assets/ver_machine/ver_verification_seq.puml similarity index 100% rename from docs/time/_assets/ver_machine/ver_verification_seq.puml rename to docs/TimeDaemon/_assets/ver_machine/ver_verification_seq.puml diff --git a/docs/TimeDaemon/index.rst b/docs/TimeDaemon/index.rst new file mode 100644 index 0000000..aa2d387 --- /dev/null +++ b/docs/TimeDaemon/index.rst @@ -0,0 +1,983 @@ +Concept for TimeDaemon +====================== + +.. contents:: Table of Contents + :depth: 3 + :local: + +TimeDaemon concept +------------------ + +Use Cases +~~~~~~~~~ + +TimeDaemon is the non Autosar adaptive process who is intended to get the Vehicle Time from the ptp slave daemon (ptpd or any other), verify and validate the timepoints and distribute time information across the clients. + +More precisely we can specify the following use cases for the time daemon: + +1. Providing current Vehicle time to different applications +2. Setting the synchronization qualifier (aka Synchronized, Timeout, so on) +3. Providing needed information for diagnostics +4. Providing needed information for addition verification, ex SafeCarTime + +The raw architectural diagram is represented below. + +.. raw:: html + +
+ +.. uml:: _assets/sad_deployment.puml + :alt: Raw architectural diagram + +.. raw:: html + +
+ +Components decomposition +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The design consists of several sw components: + +1. `Application <#application-sw-component>`_ +2. `Message Broker <#message-broker-sw-component>`_ +3. `ControlFlowDivider <#controlflowdivider-sw-component>`_ +4. `PTP Machine <#ptp-machine-sw-component>`_ +5. `Verification Machine <#verification-machine-sw-component>`_ +6. `IPC Machine <#ipc-machine-sw-component>`_ +7. `score::time::svt <#score-time-synchronizedvehicletime-sw-component>`_ + +Deployment view +~~~~~~~~~~~~~~~ + +The design deployment is represented on the following diagram: + +.. raw:: html + +
+ +.. uml:: _assets/dd_deployment.puml + :alt: Deployment View + +.. raw:: html + +
+ +Class view +~~~~~~~~~~ + +Main classes and components are presented on this diagram: + +.. raw:: html + +
+ +.. uml:: _assets/dd_class.puml + :alt: Class View + :width: 100% + :align: center + +.. raw:: html + +
+ +Data and control flow +~~~~~~~~~~~~~~~~~~~~~ + +The Data and Control flow are presented in the following diagram: + +.. raw:: html + +
+ +.. uml:: _assets/dd_data_control_flow.puml + :alt: Data and Control flow View + +.. raw:: html + +
+ +On this view you could see several "workers" scopes: + +1. PTP retrieving scope +2. PTPTimeInfo handling scope +3. PTPTimeInfo receiving on Application side scope + +Each control flow is implemented with the dedicated thread or process and is independent form another ones. + +Control flows +^^^^^^^^^^^^^ + +PTP retrieving scope +'''''''''''''''''''' + +This control flow is responsible for the: + +1. retrieve the latest information from the ptp stack and +2. provide it to the ``PTPTimeInfo handling`` control flow + +PTPTimeInfo handling scope +''''''''''''''''''''''''''' + +This control flow is responsible for the: + +1. Validate the time information, provided by the ``PTP retrieving`` workflow and +2. publish it to the ``Applications`` via some IPC + +PTPTimeInfo receiving on Application side scope +'''''''''''''''''''''''''''''''''''''''''''''''' + +This control flow is responsible for the: + +1. Propagate the time information from the ``PTPTimeInfo handling`` to the business logic of the applications. + +Data types or events +^^^^^^^^^^^^^^^^^^^^ + +There are also several data types, which components are communicating to each other: + +Raw ptp data +'''''''''''' + +``raw_ptp_data`` is the data, which is provided by ``PTPMachine`` component and is just the raw data from ptp stack. is handled in the "PTP retrieving scope" + +Input ptp data +'''''''''''''' + +``input_ptp_data`` is the same data as `raw_ptp_data <#raw-ptp-data>`_ but which is handled already in "PTPTimeInfo handling scope" + +Verified ptp data +''''''''''''''''' + +``verified_ptp_data`` is the `input_ptp_data <#input-ptp-data>`_ which was verified according to the business logic and updated accordingly. This data should be published to the Applications. + +SW Components decomposition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Application SW component +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Application`` component is the main entry point for the ``TimeDaemon``. It is responsible for orchestrating the overall lifecycle and initialization of all daemon components. + +The ``TimebaseHandler`` component is an timebase-specific logic implementation. There might be several handlers available in the ``Application`` per amount of timebases supported. This separation allows for different timebase implementations while maintaining a consistent application structure. + +Component requirements +'''''''''''''''''''''' + +The ``Application`` has the following requirements: + +- The ``Application`` shall implement the ``Initialize()`` method to create and initialize all daemon components +- The ``Application`` shall implement the ``Run()`` method to start all components and wait for termination +- The ``Application`` shall connect components to the ``MessageBroker`` by setting up all required subscriptions during initialization stage +- The ``Application`` shall support extension for different timebases. + +Class view +'''''''''' + +The Class Diagram is presented below: + +.. raw:: html + +
+ +.. uml:: _assets/app/app_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Initialization flow +''''''''''''''''''' + +During initialization, the ``Application`` uses the ``MachineFactory`` to create, configure and subscribe all components in a specific order: + +- Create the ``MessageBroker`` first, as other components depend on it +- Create ProactiveMachines (``PtpMachine``, ``ControlFlowDivider``) that drive system behavior + + - Initialize each component + - Set up MessageBroker subscriptions to component notifications + - Set up component subscriptions to MessageBroker topics + +- Create ReactiveMachines (``VerificationMachine``, ``IPCMachine``) that respond to events + + - Initialize each component + - Set up MessageBroker subscriptions to component notifications + - Set up component subscriptions to MessageBroker topics + +The initialization workflow is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/app/app_init_seq.puml + :alt: Initialization workflow + +.. raw:: html + +
+ +Execution and shutdown flow +'''''''''''''''''''''''''''' + +During execution, the ``Application``: + +- Starts all ``ProactiveMachines`` in the correct order +- Monitors the stop token for termination requests +- When termination is requested, stops all ``ProactiveMachines`` in reverse order + +The execution and shutdown workflow is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/app/app_workflow_seq.puml + :alt: Execution workflow + +.. raw:: html + +
+ +Message Broker SW component +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Message Broker`` component is the central communication hub that implements the Publish-Subscribe pattern within the ``TimeDaemon``. It enables decoupled communication between components by managing topics and distributing messages to interested subscribers. + +The component maintains a registry of topics and their subscribers, delivering messages to all registered subscribers when a component publishes to a topic. This decoupling allows components to evolve independently without direct dependencies on each other. + +Component requirements +'''''''''''''''''''''' + +The ``Message Broker`` has the following requirements: + +- The ``Message Broker`` shall maintain a registry of topics and their subscribers +- The ``Message Broker`` shall allow components to subscribe to topics of interest +- The ``Message Broker`` shall distribute messages to all subscribers when a topic is published to + +Class view +'''''''''' + +The Class Diagram is presented below: + +.. raw:: html + +
+ +.. uml:: _assets/msg_broker/msg_broker_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Initialization flow +''''''''''''''''''' + +During initialization, all machine objects, see ``BaseMachine``, the ``Application`` component needs to subscribe machines to ``Message Broker`` to the topics of interest. + +The initialization workflow is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/msg_broker/msg_broker_init_seq.puml + :alt: Initialization workflow + +.. raw:: html + +
+ +Message flow +'''''''''''' + +The message flow through the ``Message Broker`` is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/msg_broker/msg_broker_workflow_seq.puml + :alt: Message DiagramFlow + +.. raw:: html + +
+ +Concurrency aspects +''''''''''''''''''' + +The ``Message Broker`` doesn't provide any synchronization between the publish-callback invoking processes. +Moreover, the callback invoke will happened in the scope of the thread, where the ``publish`` method is called. +To separate the control flows, the `ControlFlowDivider <#controlflowdivider-sw-component>`_ shall be used + +Scalability +''''''''''' + +The ``Message Broker`` can be extended to support configuration-driven subscriptions, where topic relationships are defined in configuration files rather than hardcoded. + +ControlFlowDivider SW component +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``ControlFlowDivider`` component is responsible for separating control (execution) flows within the ``TimeDaemon`` and providing the execution control flow for the data processing. It contains dedicated threads where data is published to the ``Message Broker``, ensuring that blocking operations in one component do not affect the execution of other components and data missing is not affecting the data analysis in processing pipeline. + +This component acts as a crucial intermediary that maintains the responsiveness of the system by decoupling the execution contexts of different operations, particularly between the PTP data retrieval and the time data processing pipelines. + +Component requirements +'''''''''''''''''''''' + +The ``ControlFlowDivider`` has the following requirements: + +- The ``ControlFlowDivider`` shall provide separate execution threads for different control flows +- The ``ControlFlowDivider`` shall isolate components from execution time variations in other components +- The ``ControlFlowDivider`` shall maintain consistent data publishing rates to the subscribers +- - The ``ControlFlowDivider`` shall push the last received data to the subscribers if there is no new data for some time with the predefined rate, to avoid data missing in the processing pipeline +- The ``ControlFlowDivider`` shall enable periodic processing of the pipeline through consistent event generation +- The ``ControlFlowDivider`` shall buffer incoming data from fast producers + +Class view +'''''''''' + +The Class Diagram is presented below: + +.. raw:: html + +
+ +.. uml:: _assets/ctrlflow/ctrlflow_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Initialization flow +''''''''''''''''''' + +During initialization, the ``ControlFlowDivider`` performs the following steps: + +- Initialize internal data structures (queue, mutex, condition variable) +- Create a worker thread to process data independently +- Start the worker thread which enters a waiting state + +The initialization workflow is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/ctrlflow/ctrlflow_init_seq.puml + :alt: Initialization workflow + +.. raw:: html + +
+ +Message flow +'''''''''''' + +When the ``ControlFlowDivider`` receives new data from the ``PTP Machine`` via the ``Message Broker``, it processes it through the following workflow: + +1. The ``Message Broker`` executes the onNewData callback and provides the new data +2. The data is placed in a thread-safe queue and exists from the callback +3. The worker thread wakes up, retrieves the data from the queue and +4. The worker thread publishes the retrieved data to the `input_ptp_data <#input-ptp-data>`_ topic +5. if there was no data for some timeout, the worker shall published the empty data to the `input_ptp_data <#input-ptp-data>`_ topic. + +This separation of control flows ensures that slow or blocking operations in the PTP stack communication do not affect the responsiveness of time data processing in the ``TimeDaemon``. + +The execution workflow is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/ctrlflow/ctrlflow_workflow_seq.puml + :alt: Execution workflow + +.. raw:: html + +
+ +PTP Machine SW component +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``PTP Machine`` component shall retrieve all needed information from the ptp stack (ex ``ptpd``) and provide it to the ``Message Broker`` for routing. +All communication with the ptp stack ight use ``devctl`` calls, which take some time, thus these calls shall be done in the dedicated thread. + +Component requirements +'''''''''''''''''''''' + +The ``PTP Machine`` has the following requirements: + +- The ``PTP Machine`` shall retrieve the latest time information from the PTP stack (e.g., ``ptpd``) +- The ``PTP Machine`` shall publish retrieved time information to the ``Message Broker`` using the defined topic +- The ``PTP Machine`` shall format data according to the ``PTPTimeInfo`` structure required by downstream components +- The ``PTP Machine`` shall retrieve time information at a consistent rate to maintain time synchronization +- The ``PTP Machine`` shall maintain consistent publishing rates for time data even when experiencing delays in PTP stack communication. +- The ``PTP Machine`` shall support exchangeability with different PTP stack implementations + +Class view +'''''''''' + +The Class Diagram is presented below. + +.. raw:: html + +
+ +.. uml:: _assets/ptp_machine/ptp_machine_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +As long as it wraps the particular communication with the ptp stack, the implementations should be easily exchangeable with another one in case of stack change. + +Component initialization +''''''''''''''''''''''''' + +During initialization the ``PTP Machine`` shall initialize the ptp stack to be able to communicate with it. + +The initialization workflow is described below. + +.. raw:: html + +
+ +.. uml:: _assets/ptp_machine/ptp_machine_init_seq.puml + :alt: Initialization workflow + +.. raw:: html + +
+ +Publish new data +'''''''''''''''' + +After ``PTP Machine`` collects new data from the ptp stack, the component shall publish it to the ``Message Broker`` as `raw-ptp-data <#raw-ptp-data>`_. + +The publish workflow is described below. + +.. raw:: html + +
+ +.. uml:: _assets/ptp_machine/ptp_machine_get_new_data_seq.puml + :alt: Publish workflow + +.. raw:: html + +
+ +Verification Machine SW component +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Verification Machine`` component is responsible for validating and qualifying the time information received from the ``PTP Machine``. It applies various validation rules to ensure the time data meets quality requirements before distribution to applications. + +The component implements a pipeline pattern where each stage performs a specific validation and adds appropriate qualifiers to the time data. This modular design allows for easy extension with additional validation steps. + +Component requirements +'''''''''''''''''''''' + +The ``Verification Machine`` has the following requirements: + +- The ``Verification Machine`` shall validate and qualify time information received from the PTP Machine +- The ``Verification Machine`` shall validate if the time base is synchronized state +- The ``Verification Machine`` shall validate if the time base is in timeout state +- The ``Verification Machine`` shall validate timestamp for time jumps based on local clock +- The ``Verification Machine`` shall subscribe to the `input_ptp_data <#input-ptp-data>`_ topic via the ``Message Broker`` +- The ``Verification Machine`` shall publish verified time data to the ``Message Broker`` using the `verified-ptp-data <#verified-ptp-data>`_ topic +- The ``Verification Machine`` shall support extensibility to add new validation stages in the pipeline + +Class view +'''''''''' + +The Class Diagram is presented below. + +.. raw:: html + +
+ +.. uml:: _assets/ver_machine/ver_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Component initialization +''''''''''''''''''''''''' + +During initialization, the ``Verification Machine`` performs the following steps: + +1. Set up the validation pipeline by creating and connecting validation stages + +The component shall be subscribed by the ``Application`` to the `input_ptp_data <#input-ptp-data>`_ topic of the ``MessageBroker`` + +The initialization workflow is represented in the following sequence diagram: + +.. raw:: html + +
+ +.. uml:: _assets/ver_machine/ver_init_seq.puml + :alt: Initialization workflow + +.. raw:: html + +
+ +Data verification workflow +''''''''''''''''''''''''''' + +When the ``Verification Machine`` receives new PTP data, it processes it through the validation pipeline: + +.. raw:: html + +
+ +.. uml:: _assets/ver_machine/ver_verification_seq.puml + :alt: Validation pipeline + +.. raw:: html + +
+ +IPC Machine SW component +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``IPC Machine`` component shall get the `verified-ptp-data <#verified-ptp-data>`_ from the ``Verification Machine`` and provide it to the ``score::time::svt`` through the ``score::communication`` module. As the fast initial implementation, a custom shared memory backend is used. + +The component provides two sub components: publisher and receiver to be deployed on the TimeDaemon and Application sides accordingly. + +Component requirements +'''''''''''''''''''''' + +The ``IPC Machine`` has the following requirements: + +- The ``IPC Machine`` shall provide verified time data to the ``score::time::svt`` component through the ``score::communication`` module +- The ``IPC Machine`` shall create and initialize the IPC +- The ``IPC Machine`` shall support multiple client applications accessing the same time data +- The ``IPC Machine`` shall subscribe to the `verified_ptp_data <#verified-ptp-data>`_ topic via the ``Message Broker`` + +Class view +'''''''''' + +The Class Diagram is presented below. + +.. raw:: html + +
+ +.. uml:: _assets/ipc/ipc_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Component initialization +''''''''''''''''''''''''' + +Initialization is divided to two parts: + +1. Initialization on the TimeDaemon side +2. Initialization on the Application side + +Important thing, the ``score::communication`` publisher shall be created and offered by the ``TimeDaemon`` before the Application side subscriber can connect. The Application shall retry until the service is found. + +The main workflow is described below. + +.. raw:: html + +
+ +.. uml:: _assets/ipc/ipc_init_seq.puml + :alt: Main workflow + +.. raw:: html + +
+ +The component shall be subscribed during initialization by the ``Application`` on the `verified-ptp-data <#verified-ptp-data>`_ updates from the ``Message Broker`` + +Publish new data +'''''''''''''''' + +When ``IPC Machine`` receives the new `verified-ptp-data <#verified-ptp-data>`_ from Message Broker, it shall serialize data and publish it via ``score::communication``. + +As long as there are different use cases by using it, like: + +1. Get current Vehicle time +2. Get data for diagnostics + +All ``PTPTimeInfo`` data (or almost all) shall be published to the subscribed applications. + +The publish workflow is described below. + +.. raw:: html + +
+ +.. uml:: _assets/ipc/ipc_publish_seq.puml + :alt: Publish workflow + +.. raw:: html + +
+ +Receive data +'''''''''''' + +From Application side the receiver shall subscribe via ``score::communication`` and provide the data to the caller. + +The receive workflow is described below. + +.. raw:: html + +
+ +.. uml:: _assets/ipc/ipc_receive_seq.puml + :alt: Receive workflow + +.. raw:: html + +
+ +score::time::SynchronizedVehicleTime SW Component +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``score::time::svt`` is the interface towards Applications, how they could get the access to the Vehicle Time. + +Component requirements +'''''''''''''''''''''' + +The ``score::time::svt`` has the following requirements: + +- The ``score::time::svt`` shall expose vehicle time amd it synchronization status to applications +- The ``score::time::svt`` shall retrieve time data from ``IPC Machine`` receiver component +- The ``score::time::svt`` shall adjust vehicle time with local clock to provide accurate timestamps +- The ``score::time::svt`` shall support fast and low-latency time access via the ``Now()`` method + +Class view +'''''''''' + +The Class Diagram is presented below. + +.. raw:: html + +
+ +.. uml:: _assets/mw/mw_class.puml + :alt: Class Diagram + +.. raw:: html + +
+ +Receive data +'''''''''''' + +In case of receiving data, the ``Application`` shall just call ``score::time::svt::Now()`` and it shall return the latest published Vehicle Time, which is already adjusted with local clock. + +To do so, in the ``score::time::svt`` there is a thread, who polls for new data the ``IPCMachine::receiver`` and put the data to the process-internal shared buffer (memory), from where it is being read on ``score::time::svt::Now()`` call. + +The main workflow is described below. + +.. raw:: html + +
+ +.. uml:: _assets/mw/mw_time_receive_seq.puml + :alt: Receive data workflow + +.. raw:: html + +
+ +This design guarantees very low latency of the executing the ``score::time::svt::Now()`` function but brings additional efforts for the thread, memory buffer, synchronizing and so on. + +Receive data (simplified) +'''''''''''''''''''''''''' + +As an alternative design, the receiving concept could be simplified and ``score::time::svt::Now()`` could directly invoke the ``IPCMachine::receiver`` call, adjust the ``Vehicle time`` and return it to the ``Application``. + +The design is represented below. + +.. raw:: html + +
+ +.. uml:: _assets/mw/mw_time_receive_simple_seq.puml + :alt: Receive data (simplified) workflow + +.. raw:: html + +
+ +In this case, there will be no need for additional thread, shared buffer and synchronization, but the ``score::time::svt::Now()`` call will take longer. To decide which approach to use, additional tests shall be + +Deployment +'''''''''' + +The implementation of ``score::time::svt::details::timed`` could be placed in parallel to other implementations, like ``score::time::svt::details::mocked`` one and could be selected by Bazel select. Also it will ease the integration process. + +Logging configuration +~~~~~~~~~~~~~~~~~~~~~ + +The daemon should have the following logging contexts: + +.. list-table:: Logging Contexts + :header-rows: 1 + :widths: 30 20 50 + + * - component + - App/Context ID + - Comments + * - TimeDaemon + - TDON + - **T**\ ime\ **D**\ aem\ **ON** + * - Application + - TDAP + - **T**\ ime\ **D**\ aemon **AP**\ plication + * - MessageBroker + - TDMB + - **T**\ ime\ **D**\ aemon **M**\ essage\ **B**\ roker + * - ControlFlowDivider + - TDCD + - **T**\ ime\ **D**\ aemon **C**\ ontrolFlow\ **D**\ ivider + * - PTPMachine + - TDPM + - **T**\ ime\ **D**\ aemon **P**\ TP\ **M**\ achine + * - VerificationMachine + - TDVM + - **T**\ ime\ **D**\ aemon **V**\ erification\ **M**\ achine + * - IPCMachine::receiver + - TDIR + - **T**\ ime\ **D**\ aemon **I**\ PCMachine::\ **R**\ eceiver + * - IPCMachine::publisher + - TDIP + - **T**\ ime\ **D**\ aemon **I**\ PCMachine::\ **P**\ ublisher + +Variability +~~~~~~~~~~~ + +Configuration files +^^^^^^^^^^^^^^^^^^^ + +The ``TimeDaemon`` uses structured configuration files to enable customization of its runtime behavior. These data could be configured: + +1. Component-specific Configuration: + + a. Each component can have dedicated configuration sections + b. Parameters such as update rates, timeouts, and thresholds can be specified + +2. Topic Configuration: + + a. Topics for the ``Message Broker`` can be defined in configuration + b. Publisher and subscriber relationships can be specified externally + c. Component roles (publisher/subscriber) can be assigned through configuration + +3. File Format and Structure: The configuration files use JSON format for readability and easy parsing: + +.. code-block:: json + + { + "message_broker": { + "topics": [ + { + "name": "raw_ptp_data", + "publishers": ["PtpMachine"], + "subscribers": ["ControlFlowDivider"] + }, + { + "name": "input_ptp_data", + "publishers": ["ControlFlowDivider"], + "subscribers": ["VerificationMachine"] + }, + { + "name": "verified_ptp_data", + "publishers": ["VerificationMachine"], + "subscribers": ["IPCMachine"] + } + ] + }, + "ptp_machine": { + "update_interval_ms": 50, + "ptp_stack_type": "ptp", + "ptp_stack_parameters": { + "device": "/dev/ptp0" + } + }, + "control_flow_divider": { + "timeout_ms": 500, + "publishing_rate_ms": 100 + }, + "verification_machine": { + "validation_stages": ["synchronization", "timejumps", "timeout"], + "timejumps_parameters": { + "max_backward_jump_ns": 100000 + }, + "timeout_parameters": { + "threshold_ns": 100000 + } + }, + "ipc_machine": { + "shared_memory_name": "vehicle_time", + "shared_memory_size": 4096 + } + } + +Scalability +^^^^^^^^^^^ + +The ``TimeDaemon``'s architecture supports scalability in the following ways: + +Component Extensibility: +'''''''''''''''''''''''' + +1. New machine components can be added by implementing the ``BaseMachine`` interface +2. Additional validation stages can be plugged into the ``VerificationMachine`` pipeline +3. Alternative IPC mechanisms or communication with ptp stack can be implemented by alternative the ``IPCMachine`` or ``PTPMachine`` implementation + +Example based on Qualified Vehicle Time integration +''''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``Qualified Vehicle Time`` integration extends the standard ``TimeDaemon`` architecture with: + +1. A ``Qualified Vehicle Time`` component that performs additional time qualification and provide new topics: ``qualified_ptp_data`` and ``diagnostic_sct_data`` +2. A dedicated IPC channel for SCT diagnostic data +3. A ``score::time::qvt`` library for diagnostic applications + +.. raw:: html + +
+ +.. uml:: _assets/examples/qvt/qvt_deployment.puml + :alt: Deployment view + +.. raw:: html + +
+ +The ``Qualified Vehicle Time`` component is integrated into the existing processing pipeline: + +1. It subscribes to the `verified_ptp_data <#verified-ptp-data>`_ topic from the ``VerificationMachine`` +2. It processes and qualifies the time data with additional QVT-specific checks +3. It publishes two types of data: + + a. Qualified time data to the standard IPC Machine towards clients interested in the qualified Vehicle Time + b. Diagnostic data to a dedicated QVT IPC channel towards Diagnostic and Central Validator notifications + +The extended data flow with Qualified Vehicle Time integration is shown below: + +.. raw:: html + +
+ +.. uml:: _assets/examples/qvt/qvt_data_control_flow.puml + :alt: Data flow + +.. raw:: html + +
+ +Example based on Absolute Time integration +'''''''''''''''''''''''''''''''''''''''''' + +Another example of the ``TimeDaemon`` extension is the integration of an ``Absolute Time`` source, such as GNSS, to provide absolute time information alongside the relative Vehicle Time from PTP. + +The ``Absolute Time`` integration extends the standard ``TimeDaemon`` architecture with: + +1. An ``SDatMachine`` component that retrieves absolute time from GNSS via SOMEIP or other sources and provide new topics: ``absolute_time_data`` +2. A dedicated verification stage in the ``VerificationMachine`` for Absolute Time qualification +3. A dedicated IPC channel for Absolute Time data +4. A ``score::time::abs`` library for applications requiring absolute time on Clients side. + +The way how it is integrated is presented below. + +.. raw:: html + +
+ +.. uml:: _assets/examples/abs_time/abs_time_deployment.puml + :alt: Data flow + +.. raw:: html + +
+ + +The control and data flow with Absolute Time integration is shown below. + +.. raw:: html + +
+ +.. uml:: _assets/examples/abs_time/abs_time_data_control_flow.puml + :alt: Data flow + +.. raw:: html + +
+ +Using in test environment +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using in ITF +^^^^^^^^^^^^ + +Normal behavior is expected. + +Using in Component Tests on the host +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Overview +'''''''' + +The ``TimeDaemon`` can be utilized in the ``Component Tests`` environment to enable comprehensive testing of time-dependent components without relying on physical PTP hardware. +This approach allows test cases to manipulate time values and synchronization states to validate application behavior under various timing conditions. + +For the Component tests the ``PtpMachine::PtpEngine`` library is the only one platform-dependent. +Thus the ``TimeDaemon`` components remain largely unchanged except for the ``PTPMachine`` component, which is replaced with an test-specific implementation that can be controlled via test cases +This component shall: + +1. simulate "normal" ``PTPMachine`` behavior +2. have the communication channel to the test case and react on the manipulations + +Next steps: plugin system +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``TimeDaemon`` could be extended with a flexible plugin system that enables dynamic component loading, configuration, subscription and extension without requiring code changes or recompilation. + +Plugin Architecture +^^^^^^^^^^^^^^^^^^^ + +The plugin system is structured around the following key elements: + +1. ``Component Registry``: A central registry that maintains information about available component implementations +2. ``Component Factory``: Creates component instances based on configuration +3. ``Plugin Manager``: Loads and initializes plugins at runtime +4. ``Configuration-Driven Assembly``: Components and their relationships defined in configuration files + +Component Creation Process +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +During ``TimeDaemon`` initialization: + +1. The ``Plugin Manager`` loads all specified plugins from configured directories or bazel targets +2. Each plugin registers its component factories with the registry +3. The ``Application`` reads the component configuration +4. For each component in the configuration: + + a. The appropriate factory is retrieved from the registry + b. The component is created with its specified parameters + c. Components are connected based on the ``MessageBroker`` topic configuration + +ASIL-B qualification +~~~~~~~~~~~~~~~~~~~~~ + +Clean separation of concerns allows ``score::time::svt`` as well as ``TimeDaemon`` to be qualified according to ASIL-B requirements following ISO 26262 standard. diff --git a/docs/index.rst b/docs/index.rst index 14bf088..ae5cd0b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,12 +36,13 @@ The main responsibilities of TimeDaemon include: - **Providing diagnostic information** for system monitoring - **Supporting additional verification mechanisms** such as QualifiedVehicleTime (QVT) for safety-critical applications -For a detailed concept and architectural design, please refer to the :doc:`TimeDaemon Concept Documentation