From 1c744a5eb249e783ab9e88b1f52c4e25fb44f45f Mon Sep 17 00:00:00 2001 From: Nitesh Purohit Date: Thu, 10 Sep 2026 17:40:08 -0400 Subject: [PATCH 1/3] feat: add deterministic time and entropy support - Introduced `clocks.cpp` and `clocks.hpp` for managing monotonic and realtime clocks. - Added `laghu_test_time_entropy` library for testing deterministic time and entropy. - Implemented `ManualClock` and `DeterministicEntropy` classes for controlled testing. - Created tests for clock operations and deterministic entropy to ensure reliability. - Updated CMake configuration to include new sources and test targets. closes: #79 --- CMakeLists.txt | 25 ++- cmake/LaghuApiBoundaries.cmake | 1 + cmake/LaghuBuildIdentity.cmake | 4 + src/core/clocks.cpp | 99 ++++++++++++ src/core/contract/laghu/core/clocks.hpp | 29 ++++ .../laghu/core/deadlines_cancellation.hpp | 36 ++++- .../laghu/core/internal/clock_operations.hpp | 12 ++ tests/support/laghu_test_time_entropy.cpp | 78 ++++++++++ tests/support/laghu_test_time_entropy.hpp | 56 +++++++ tests/support/time_entropy.cpp | 147 ++++++++++++++++++ 10 files changed, 483 insertions(+), 4 deletions(-) create mode 100644 src/core/clocks.cpp create mode 100644 src/core/contract/laghu/core/clocks.hpp create mode 100644 src/core/private/laghu/core/internal/clock_operations.hpp create mode 100644 tests/support/laghu_test_time_entropy.cpp create mode 100644 tests/support/laghu_test_time_entropy.hpp create mode 100644 tests/support/time_entropy.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0dfcd..82fd410 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ laghu_configure_capability_header() laghu_configure_build_identity() add_library(laghu_core STATIC + src/core/clocks.cpp src/core/contract.cpp src/core/digest.cpp src/core/fingerprints.cpp @@ -102,6 +103,27 @@ laghu_apply_first_party_contract(laghu_os) laghu_configure_api_consumer(laghu_os os) target_include_directories(laghu_os PRIVATE "${CMAKE_SOURCE_DIR}/src/os/private") +# Deterministic time and entropy are test-only controls. They are kept in a +# separate non-installed target so production entropy remains OS-provided. +add_library(laghu_test_time_entropy STATIC tests/support/laghu_test_time_entropy.cpp) +laghu_apply_first_party_contract(laghu_test_time_entropy) +target_include_directories(laghu_test_time_entropy PUBLIC + "${CMAKE_SOURCE_DIR}/tests/support" + "${CMAKE_SOURCE_DIR}/src/core/contract" + "${CMAKE_SOURCE_DIR}/src/adapters/private") + +add_executable(laghu_test_time_entropy_test + tests/support/time_entropy.cpp + src/adapters/entropy.cpp) +laghu_apply_first_party_contract(laghu_test_time_entropy_test) +laghu_configure_api_consumer(laghu_test_time_entropy_test adapters) +target_include_directories(laghu_test_time_entropy_test PRIVATE + "${CMAKE_SOURCE_DIR}/src/core/private" + "${CMAKE_SOURCE_DIR}/src/adapters/private") +target_link_libraries(laghu_test_time_entropy_test PRIVATE + laghu_test_support laghu_test_time_entropy laghu_core) +laghu_add_native_test(laghu.test_support.time_entropy laghu_test_time_entropy_test) + list(FIND LAGHU_EFFECTIVE_FEATURES tls laghu_tls_feature_index) if(NOT laghu_tls_feature_index EQUAL -1) add_library(laghu_crypto STATIC @@ -289,7 +311,8 @@ set(laghu_verify_targets laghu_core_state_transitions_test laghu_core_deadlines_cancellation_test laghu_core_shared_offsets_test laghu_core_mapped_regions_test laghu_core_binary_envelope_test laghu_core_digest_primitives_test - laghu_os_iovec_translation_test laghu_test_support laghu_test_faults laghu_test_support_fixtures_test + laghu_os_iovec_translation_test laghu_test_support laghu_test_faults laghu_test_time_entropy + laghu_test_time_entropy_test laghu_test_support_fixtures_test laghu_test_support_runner_contract_test laghu_test_fault_injection_test laghu laghu_capability_header_parity laghu_visibility_probe visibility_negative_fixture) if(TARGET laghu_crypto_provider_test) diff --git a/cmake/LaghuApiBoundaries.cmake b/cmake/LaghuApiBoundaries.cmake index c69653d..af69bb1 100644 --- a/cmake/LaghuApiBoundaries.cmake +++ b/cmake/LaghuApiBoundaries.cmake @@ -193,6 +193,7 @@ endfunction() function(laghu_add_visibility_probe) add_library(laghu_visibility_probe SHARED + src/core/clocks.cpp src/core/contract.cpp src/core/digest.cpp src/core/fingerprints.cpp diff --git a/cmake/LaghuBuildIdentity.cmake b/cmake/LaghuBuildIdentity.cmake index 75bfa63..ac14468 100644 --- a/cmake/LaghuBuildIdentity.cmake +++ b/cmake/LaghuBuildIdentity.cmake @@ -35,11 +35,14 @@ function(laghu_build_identity_input_hashes output) cmake/LaghuToolchain.cmake src/cli/main.cpp src/cli/private/laghu/cli/internal/build_manifest.hpp + src/core/clocks.cpp src/core/contract.cpp src/core/digest.cpp src/core/fingerprints.cpp src/core/contract/laghu/core/contract.hpp + src/core/contract/laghu/core/clocks.hpp src/core/contract/laghu/core/crypto.hpp + src/core/contract/laghu/core/deadlines_cancellation.hpp src/core/contract/laghu/core/digest.hpp src/core/contract/laghu/core/bounded_arena.hpp src/core/contract/laghu/core/binary_envelope.hpp @@ -49,6 +52,7 @@ function(laghu_build_identity_input_hashes output) src/core/contract/laghu/core/mapped_regions.hpp src/core/handles.cpp src/core/mapped_regions.cpp + src/core/private/laghu/core/internal/clock_operations.hpp src/core/private/laghu/core/internal/compiler_extensions.hpp src/core/private/laghu/core/internal/descriptor_operations.hpp src/core/private/laghu/core/internal/fingerprints.hpp diff --git a/src/core/clocks.cpp b/src/core/clocks.cpp new file mode 100644 index 0000000..e75a8bf --- /dev/null +++ b/src/core/clocks.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#include +#include +#include + +#include + +#include + +#include + +namespace laghu::core { +namespace { + +inline constexpr std::int64_t nanoseconds_per_second = 1000000000; + +[[nodiscard]] Result posix_monotonic_now(void*) noexcept { + timespec value{}; + if (::clock_gettime(CLOCK_MONOTONIC, &value) != 0) { + return std::unexpected{Error::from_errno(errno, "CLOCK_MONOTONIC query failed")}; + } + if (value.tv_sec < 0 || value.tv_nsec < 0 || value.tv_nsec >= nanoseconds_per_second) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::corrupt_data, 0, + "CLOCK_MONOTONIC result is invalid"}}; + } + const auto seconds = static_cast(value.tv_sec); + const auto nanoseconds = static_cast(value.tv_nsec); + if (seconds > (std::numeric_limits::max() - nanoseconds) / + static_cast(nanoseconds_per_second)) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::overflow, 0, + "CLOCK_MONOTONIC result overflows"}}; + } + return seconds * static_cast(nanoseconds_per_second) + nanoseconds; +} + +[[nodiscard]] Result posix_realtime_now(void*) noexcept { + timespec value{}; + if (::clock_gettime(CLOCK_REALTIME, &value) != 0) { + return std::unexpected{Error::from_errno(errno, "CLOCK_REALTIME query failed")}; + } + return internal::realtime_instant_from_parts(static_cast(value.tv_sec), + static_cast(value.tv_nsec)); +} + +const ClockOperations default_operations{nullptr, posix_monotonic_now, posix_realtime_now}; + +} // namespace + +Result read_monotonic_clock(const ClockOperations& operations) noexcept { + if (operations.monotonic_now == nullptr) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::invalid_state, 0, + "monotonic clock operation is missing"}}; + } + return operations.monotonic_now(operations.context); +} + +Result read_realtime_clock(const ClockOperations& operations) noexcept { + if (operations.realtime_now == nullptr) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::invalid_state, 0, + "realtime clock operation is missing"}}; + } + return operations.realtime_now(operations.context); +} + +namespace internal { + +const ClockOperations& default_clock_operations() noexcept { return default_operations; } + +Result realtime_instant_from_parts(RealtimeInstant seconds, + RealtimeInstant nanoseconds) noexcept { + if (nanoseconds < 0 || nanoseconds >= nanoseconds_per_second) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::corrupt_data, 0, + "CLOCK_REALTIME result is invalid"}}; + } + + constexpr RealtimeInstant maximum = std::numeric_limits::max(); + constexpr RealtimeInstant minimum = std::numeric_limits::min(); + constexpr RealtimeInstant maximum_seconds = maximum / nanoseconds_per_second; + constexpr RealtimeInstant maximum_nanoseconds = maximum % nanoseconds_per_second; + constexpr RealtimeInstant minimum_truncated_seconds = minimum / nanoseconds_per_second; + constexpr RealtimeInstant minimum_remainder = minimum % nanoseconds_per_second; + constexpr RealtimeInstant minimum_seconds = minimum_truncated_seconds - 1; + constexpr RealtimeInstant minimum_nanoseconds = nanoseconds_per_second + minimum_remainder; + + if (seconds > maximum_seconds || + (seconds == maximum_seconds && nanoseconds > maximum_nanoseconds) || + seconds < minimum_seconds || + (seconds == minimum_seconds && nanoseconds < minimum_nanoseconds)) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::overflow, 0, + "CLOCK_REALTIME result overflows"}}; + } + if (seconds == minimum_seconds) { + return minimum + (nanoseconds - minimum_nanoseconds); + } + return seconds * nanoseconds_per_second + nanoseconds; +} + +} // namespace internal +} // namespace laghu::core diff --git a/src/core/contract/laghu/core/clocks.hpp b/src/core/contract/laghu/core/clocks.hpp new file mode 100644 index 0000000..c90a121 --- /dev/null +++ b/src/core/contract/laghu/core/clocks.hpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#pragma once + +#include + +#include + +namespace laghu::core { + +using MonotonicInstant = std::uint64_t; +using RealtimeInstant = std::int64_t; + +using MonotonicNowFunction = Result (*)(void* context) noexcept; +using RealtimeNowFunction = Result (*)(void* context) noexcept; + +// A caller owns the context and every function referenced by this table. +// Monotonic time is used for expiry; realtime is for externally visible time. +struct ClockOperations final { + void* context{}; + MonotonicNowFunction monotonic_now{}; + RealtimeNowFunction realtime_now{}; +}; + +[[nodiscard]] Result read_monotonic_clock( + const ClockOperations& operations) noexcept; +[[nodiscard]] Result read_realtime_clock( + const ClockOperations& operations) noexcept; + +} // namespace laghu::core diff --git a/src/core/contract/laghu/core/deadlines_cancellation.hpp b/src/core/contract/laghu/core/deadlines_cancellation.hpp index a271cff..843b03a 100644 --- a/src/core/contract/laghu/core/deadlines_cancellation.hpp +++ b/src/core/contract/laghu/core/deadlines_cancellation.hpp @@ -9,12 +9,10 @@ #include #include -#include +#include namespace laghu::core { -using MonotonicInstant = std::uint64_t; - // Clocks are supplied by the owner. Production code can inject its POSIX // monotonic clock and tests can inject a deterministic clock without a timer // thread or dynamic dispatch. @@ -40,6 +38,19 @@ class Deadline final { return Deadline{now + duration}; } + [[nodiscard]] static Result after(const ClockOperations& operations, + MonotonicInstant duration) noexcept { + const auto now = read_monotonic_clock(operations); + if (!now.has_value()) { + return std::unexpected{now.error()}; + } + if (duration > std::numeric_limits::max() - *now) { + return std::unexpected{Error{ErrorDomain::core, ErrorCode::overflow, 0, + "monotonic deadline overflows"}}; + } + return Deadline{*now + duration}; + } + [[nodiscard]] static constexpr Deadline child(Deadline parent, Deadline requested) noexcept { return Deadline{parent.instant_ < requested.instant_ ? parent.instant_ : requested.instant_}; } @@ -51,6 +62,14 @@ class Deadline final { return clock.now() >= instant_; } + [[nodiscard]] Result expired(const ClockOperations& operations) const noexcept { + const auto now = read_monotonic_clock(operations); + if (!now.has_value()) { + return std::unexpected{now.error()}; + } + return *now >= instant_; + } + template [[nodiscard]] constexpr Result require_not_expired(const Clock& clock) const noexcept { if (expired(clock)) { @@ -59,6 +78,17 @@ class Deadline final { return {}; } + [[nodiscard]] Result require_not_expired(const ClockOperations& operations) const noexcept { + const auto is_expired = expired(operations); + if (!is_expired.has_value()) { + return std::unexpected{is_expired.error()}; + } + if (*is_expired) { + return std::unexpected{deadline_error()}; + } + return {}; + } + [[nodiscard]] static constexpr Error deadline_error() noexcept { return Error{ErrorDomain::core, ErrorCode::deadline, 0, "monotonic deadline expired"}; } diff --git a/src/core/private/laghu/core/internal/clock_operations.hpp b/src/core/private/laghu/core/internal/clock_operations.hpp new file mode 100644 index 0000000..909a395 --- /dev/null +++ b/src/core/private/laghu/core/internal/clock_operations.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#pragma once + +#include + +namespace laghu::core::internal { + +[[nodiscard]] const ClockOperations& default_clock_operations() noexcept; +[[nodiscard]] Result realtime_instant_from_parts( + RealtimeInstant seconds, RealtimeInstant nanoseconds) noexcept; + +} // namespace laghu::core::internal diff --git a/tests/support/laghu_test_time_entropy.cpp b/tests/support/laghu_test_time_entropy.cpp new file mode 100644 index 0000000..2ab69b2 --- /dev/null +++ b/tests/support/laghu_test_time_entropy.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#include "laghu_test_time_entropy.hpp" + +#include +#include + +namespace laghu::test { + +core::Result ManualClock::advance_monotonic_to(core::MonotonicInstant target) noexcept { + if (target < monotonic_) { + return std::unexpected{core::Error{core::ErrorDomain::core, core::ErrorCode::invalid_range, 0, + "manual monotonic time cannot move backward"}}; + } + monotonic_ = target; + return {}; +} + +core::Result ManualClock::advance_monotonic_by(core::MonotonicInstant elapsed) noexcept { + if (elapsed > std::numeric_limits::max() - monotonic_) { + return std::unexpected{core::Error{core::ErrorDomain::core, core::ErrorCode::overflow, 0, + "manual monotonic time overflows"}}; + } + monotonic_ += elapsed; + return {}; +} + +core::ClockOperations ManualClock::operations() noexcept { + return core::ClockOperations{this, read_monotonic, read_realtime}; +} + +core::Result ManualClock::read_monotonic(void* context) noexcept { + if (context == nullptr) { + return std::unexpected{core::Error{core::ErrorDomain::core, core::ErrorCode::invalid_state, 0, + "manual monotonic clock has no context"}}; + } + return static_cast(context)->monotonic_; +} + +core::Result ManualClock::read_realtime(void* context) noexcept { + if (context == nullptr) { + return std::unexpected{core::Error{core::ErrorDomain::core, core::ErrorCode::invalid_state, 0, + "manual realtime clock has no context"}}; + } + return static_cast(context)->realtime_; +} + +bool DeterministicEntropy::fail_on_call(std::size_t call, int native_error) noexcept { + if (call == 0 || native_error == 0) { + return false; + } + failure_call_ = call; + failure_error_ = native_error; + return true; +} + +int DeterministicEntropy::fill(core::MutableByteView output, void* context) noexcept { + if (context == nullptr) { + errno = EINVAL; + return -1; + } + auto& entropy = *static_cast(context); + ++entropy.calls_; + if (entropy.failure_call_ == entropy.calls_) { + errno = entropy.failure_error_; + return -1; + } + if (output.size() > entropy.bytes_.size() - entropy.offset_) { + errno = ENOSPC; + return -1; + } + for (std::size_t index = 0; index < output.size(); ++index) { + output.span()[index] = entropy.bytes_[entropy.offset_ + index]; + } + entropy.offset_ += output.size(); + return 0; +} + +} // namespace laghu::test diff --git a/tests/support/laghu_test_time_entropy.hpp b/tests/support/laghu_test_time_entropy.hpp new file mode 100644 index 0000000..100a51a --- /dev/null +++ b/tests/support/laghu_test_time_entropy.hpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#pragma once + +#include +#include +#include + +#include +#include + +namespace laghu::test { + +class ManualClock final { + public: + constexpr ManualClock(core::MonotonicInstant monotonic, core::RealtimeInstant realtime) noexcept + : monotonic_(monotonic), realtime_(realtime) {} + + [[nodiscard]] core::Result advance_monotonic_to(core::MonotonicInstant target) noexcept; + [[nodiscard]] core::Result advance_monotonic_by(core::MonotonicInstant elapsed) noexcept; + constexpr void jump_realtime_to(core::RealtimeInstant target) noexcept { realtime_ = target; } + + [[nodiscard]] constexpr core::MonotonicInstant monotonic() const noexcept { return monotonic_; } + [[nodiscard]] constexpr core::RealtimeInstant realtime() const noexcept { return realtime_; } + [[nodiscard]] core::ClockOperations operations() noexcept; + + private: + [[nodiscard]] static core::Result read_monotonic( + void* context) noexcept; + [[nodiscard]] static core::Result read_realtime(void* context) noexcept; + + core::MonotonicInstant monotonic_{}; + core::RealtimeInstant realtime_{}; +}; + +class DeterministicEntropy final { + public: + explicit constexpr DeterministicEntropy(std::span bytes) noexcept + : bytes_(bytes) {} + + [[nodiscard]] bool fail_on_call(std::size_t call, int native_error) noexcept; + [[nodiscard]] constexpr adapters::internal::EntropyCall call() const noexcept { return fill; } + [[nodiscard]] constexpr void* context() noexcept { return this; } + [[nodiscard]] constexpr std::size_t calls() const noexcept { return calls_; } + [[nodiscard]] constexpr std::size_t consumed() const noexcept { return offset_; } + + private: + [[nodiscard]] static int fill(core::MutableByteView output, void* context) noexcept; + + std::span bytes_{}; + std::size_t offset_{}; + std::size_t calls_{}; + std::size_t failure_call_{}; + int failure_error_{}; +}; + +} // namespace laghu::test diff --git a/tests/support/time_entropy.cpp b/tests/support/time_entropy.cpp new file mode 100644 index 0000000..0ef926c --- /dev/null +++ b/tests/support/time_entropy.cpp @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#include "laghu_test_support.hpp" +#include "laghu_test_time_entropy.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +template +[[nodiscard]] laghu::core::MutableByteView mutable_view(std::array& bytes) noexcept { + return *laghu::core::MutableByteView::from(std::span{bytes}); +} + +[[nodiscard]] bool check_manual_clocks_and_deadlines() noexcept { + laghu::test::ManualClock clock{100, 1000}; + const auto operations = clock.operations(); + const auto deadline = laghu::core::Deadline::after(operations, 20); + if (!deadline.has_value() || deadline->instant() != 120 || + !deadline->require_not_expired(operations).has_value() || + !clock.advance_monotonic_by(19).has_value()) { + return false; + } + const auto before_expiry = deadline->expired(operations); + if (!before_expiry.has_value() || *before_expiry || !clock.advance_monotonic_to(120).has_value() || + !deadline->expired(operations).has_value() || !*deadline->expired(operations) || + deadline->require_not_expired(operations).has_value()) { + return false; + } + const auto backward_monotonic = clock.advance_monotonic_to(119); + if (backward_monotonic.has_value() || + backward_monotonic.error().code() != laghu::core::ErrorCode::invalid_range || + clock.monotonic() != 120) { + return false; + } + const auto initial_realtime = laghu::core::read_realtime_clock(operations); + clock.jump_realtime_to(1500); + const auto forward_realtime = laghu::core::read_realtime_clock(operations); + clock.jump_realtime_to(750); + const auto backward_realtime = laghu::core::read_realtime_clock(operations); + return initial_realtime.has_value() && *initial_realtime == 1000 && + forward_realtime.has_value() && *forward_realtime == 1500 && + backward_realtime.has_value() && *backward_realtime == 750 && clock.monotonic() == 120; +} + +[[nodiscard]] bool check_clock_tables() noexcept { + const auto& operations = laghu::core::internal::default_clock_operations(); + const auto monotonic = laghu::core::read_monotonic_clock(operations); + const auto realtime = laghu::core::read_realtime_clock(operations); + const laghu::core::ClockOperations incomplete{}; + const auto missing_monotonic = laghu::core::read_monotonic_clock(incomplete); + const auto missing_realtime = laghu::core::read_realtime_clock(incomplete); + return monotonic.has_value() && realtime.has_value() && !missing_monotonic.has_value() && + !missing_realtime.has_value() && + missing_monotonic.error().code() == laghu::core::ErrorCode::invalid_state && + missing_realtime.error().code() == laghu::core::ErrorCode::invalid_state; +} + +[[nodiscard]] bool check_realtime_conversion_boundaries() noexcept { + constexpr laghu::core::RealtimeInstant nanoseconds_per_second = 1000000000; + constexpr laghu::core::RealtimeInstant maximum = + std::numeric_limits::max(); + constexpr laghu::core::RealtimeInstant minimum = + std::numeric_limits::min(); + constexpr laghu::core::RealtimeInstant maximum_seconds = maximum / nanoseconds_per_second; + constexpr laghu::core::RealtimeInstant maximum_nanoseconds = maximum % nanoseconds_per_second; + constexpr laghu::core::RealtimeInstant minimum_seconds = + minimum / nanoseconds_per_second - 1; + constexpr laghu::core::RealtimeInstant minimum_nanoseconds = + nanoseconds_per_second + minimum % nanoseconds_per_second; + + const auto exact_maximum = laghu::core::internal::realtime_instant_from_parts( + maximum_seconds, maximum_nanoseconds); + const auto maximum_overflow = laghu::core::internal::realtime_instant_from_parts( + maximum_seconds, maximum_nanoseconds + 1); + const auto exact_minimum = laghu::core::internal::realtime_instant_from_parts( + minimum_seconds, minimum_nanoseconds); + const auto minimum_overflow = laghu::core::internal::realtime_instant_from_parts( + minimum_seconds, minimum_nanoseconds - 1); + const auto negative_nanoseconds = + laghu::core::internal::realtime_instant_from_parts(0, -1); + const auto excessive_nanoseconds = laghu::core::internal::realtime_instant_from_parts( + 0, nanoseconds_per_second); + return exact_maximum.has_value() && *exact_maximum == maximum && + !maximum_overflow.has_value() && + maximum_overflow.error().code() == laghu::core::ErrorCode::overflow && + exact_minimum.has_value() && *exact_minimum == minimum && + !minimum_overflow.has_value() && + minimum_overflow.error().code() == laghu::core::ErrorCode::overflow && + !negative_nanoseconds.has_value() && + negative_nanoseconds.error().code() == laghu::core::ErrorCode::corrupt_data && + !excessive_nanoseconds.has_value() && + excessive_nanoseconds.error().code() == laghu::core::ErrorCode::corrupt_data; +} + +[[nodiscard]] bool check_deterministic_entropy() noexcept { + std::array sequence{}; + for (std::size_t index = 0; index < sequence.size(); ++index) { + sequence[index] = static_cast(index); + } + std::array output{}; + laghu::test::DeterministicEntropy entropy{std::span{sequence}}; + const auto result = laghu::adapters::internal::fill_entropy_with( + mutable_view(output), entropy.call(), entropy.context()); + if (!result.has_value() || entropy.calls() != 3 || entropy.consumed() != output.size() || + output[0] != sequence[0] || output[255] != sequence[255] || + output[256] != sequence[256] || output.back() != sequence.back()) { + return false; + } + + laghu::test::DeterministicEntropy failing{std::span{sequence}}; + if (!failing.fail_on_call(2, EIO)) { + return false; + } + const auto failure = laghu::adapters::internal::fill_entropy_with( + mutable_view(output), failing.call(), failing.context()); + return !failure.has_value() && failing.calls() == 2 && failing.consumed() == 256 && + failure.error().domain() == laghu::core::ErrorDomain::posix && + failure.error().code() == laghu::core::ErrorCode::io && failure.error().native_code() == EIO; +} + +[[nodiscard]] bool check_os_entropy_only() noexcept { + std::array output{}; + return laghu::adapters::internal::fill_entropy(mutable_view(output)).has_value(); +} + +} // namespace + +int main() { + constexpr std::array tests{ + laghu::test::TestCase{"time_entropy.manual_clocks_and_deadlines", + check_manual_clocks_and_deadlines}, + laghu::test::TestCase{"time_entropy.clock_tables", check_clock_tables}, + laghu::test::TestCase{"time_entropy.realtime_conversion_boundaries", + check_realtime_conversion_boundaries}, + laghu::test::TestCase{"time_entropy.deterministic_entropy", check_deterministic_entropy}, + laghu::test::TestCase{"time_entropy.os_entropy_only", check_os_entropy_only}, + }; + return laghu::test::run_tests(tests); +} From 6279d4e93203b674b4c83c90aa2df9ba9cd25e6e Mon Sep 17 00:00:00 2001 From: Nitesh Purohit Date: Thu, 10 Sep 2026 18:27:33 -0400 Subject: [PATCH 2/3] enhance time entropy and cancellation logic --- CMakeLists.txt | 12 +-- .../laghu/core/deadlines_cancellation.hpp | 24 ++++++ tests/core/deadlines_cancellation.cpp | 76 +++++++++++++++++++ tests/support/laghu_test_time_entropy.cpp | 25 +++--- tests/support/laghu_test_time_entropy.hpp | 7 +- tests/support/time_entropy.cpp | 28 +++---- 6 files changed, 126 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 82fd410..447c55c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,17 +109,13 @@ add_library(laghu_test_time_entropy STATIC tests/support/laghu_test_time_entropy laghu_apply_first_party_contract(laghu_test_time_entropy) target_include_directories(laghu_test_time_entropy PUBLIC "${CMAKE_SOURCE_DIR}/tests/support" - "${CMAKE_SOURCE_DIR}/src/core/contract" - "${CMAKE_SOURCE_DIR}/src/adapters/private") + "${CMAKE_SOURCE_DIR}/src/core/contract") -add_executable(laghu_test_time_entropy_test - tests/support/time_entropy.cpp - src/adapters/entropy.cpp) +add_executable(laghu_test_time_entropy_test tests/support/time_entropy.cpp) laghu_apply_first_party_contract(laghu_test_time_entropy_test) -laghu_configure_api_consumer(laghu_test_time_entropy_test adapters) +laghu_configure_api_consumer(laghu_test_time_entropy_test core) target_include_directories(laghu_test_time_entropy_test PRIVATE - "${CMAKE_SOURCE_DIR}/src/core/private" - "${CMAKE_SOURCE_DIR}/src/adapters/private") + "${CMAKE_SOURCE_DIR}/src/core/private") target_link_libraries(laghu_test_time_entropy_test PRIVATE laghu_test_support laghu_test_time_entropy laghu_core) laghu_add_native_test(laghu.test_support.time_entropy laghu_test_time_entropy_test) diff --git a/src/core/contract/laghu/core/deadlines_cancellation.hpp b/src/core/contract/laghu/core/deadlines_cancellation.hpp index 843b03a..cbec66f 100644 --- a/src/core/contract/laghu/core/deadlines_cancellation.hpp +++ b/src/core/contract/laghu/core/deadlines_cancellation.hpp @@ -223,6 +223,14 @@ class CancellationToken final { return deadline.require_not_expired(clock); } + [[nodiscard]] Result require_active(const Deadline& deadline, + const ClockOperations& operations) const noexcept { + if (const Result active = require_active(); !active.has_value()) { + return std::unexpected{active.error()}; + } + return deadline.require_not_expired(operations); + } + [[nodiscard]] Result child(CancellationState& child_state) const noexcept { for (std::uint8_t index = 0; index < state_count_; ++index) { if (states_[index] == &child_state) { @@ -282,6 +290,22 @@ class CancellationSource final { return state_->finish(CancellationState::StoredTerminal::cancelled_deadline); } + [[nodiscard]] Result cancel_if_expired( + const Deadline& deadline, const ClockOperations& operations) noexcept { + const CancellationOutcome existing = state_->outcome(); + if (existing.is_terminal()) { + return existing; + } + const auto expired = deadline.expired(operations); + if (!expired.has_value()) { + return std::unexpected{expired.error()}; + } + if (!*expired) { + return state_->outcome(); + } + return state_->finish(CancellationState::StoredTerminal::cancelled_deadline); + } + private: CancellationState* state_; }; diff --git a/tests/core/deadlines_cancellation.cpp b/tests/core/deadlines_cancellation.cpp index 45d758a..ccdb71b 100644 --- a/tests/core/deadlines_cancellation.cpp +++ b/tests/core/deadlines_cancellation.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only #include #include +#include #include #include #include @@ -59,6 +60,30 @@ class DeterministicClock final { [[nodiscard]] constexpr bool check(bool condition) noexcept { return condition; } +class OperationClock final { + public: + constexpr explicit OperationClock(laghu::core::MonotonicInstant now) noexcept : now_(now) {} + + [[nodiscard]] constexpr laghu::core::ClockOperations operations() noexcept { + return laghu::core::ClockOperations{this, read}; + } + constexpr void advance_to(laghu::core::MonotonicInstant now) noexcept { now_ = now; } + + private: + [[nodiscard]] static laghu::core::Result read( + void* context) noexcept { + return static_cast(context)->now_; + } + + laghu::core::MonotonicInstant now_; +}; + +[[nodiscard]] laghu::core::Result failed_clock_read(void*) noexcept { + return std::unexpected{laghu::core::Error{laghu::core::ErrorDomain::posix, + laghu::core::ErrorCode::io, EIO, + "injected clock read failure"}}; +} + [[nodiscard]] bool check_deadline_boundaries() noexcept { DeterministicClock clock{100}; const auto after = laghu::core::Deadline::after(clock, 20); @@ -142,6 +167,54 @@ class DeterministicClock final { source.token().require_active().error().code() == laghu::core::ErrorCode::deadline); } +[[nodiscard]] bool check_clock_operation_deadline_cancellation() noexcept { + OperationClock clock{49}; + const auto operations = clock.operations(); + const auto deadline = laghu::core::Deadline::at(50); + + laghu::core::CancellationState active_state; + laghu::core::CancellationSource active_source{active_state}; + const auto active = active_source.token().require_active(deadline, operations); + const auto not_expired = active_source.cancel_if_expired(deadline, operations); + if (!check(active.has_value() && not_expired.has_value() && + not_expired->terminal == laghu::core::CancellationTerminal::pending && + active_source.token().outcome().terminal == laghu::core::CancellationTerminal::pending)) { + return false; + } + + clock.advance_to(50); + const auto expired = active_source.cancel_if_expired(deadline, operations); + if (!check(expired.has_value() && expired->is_cancelled() && + expired->cause == laghu::core::CancellationCause::deadline_expired && + !active_source.token().require_active(deadline, operations).has_value() && + active_source.token().outcome().is_cancelled())) { + return false; + } + + const laghu::core::ClockOperations failed_operations{nullptr, failed_clock_read, nullptr}; + laghu::core::CancellationState failed_state; + laghu::core::CancellationSource failed_source{failed_state}; + const auto token_failure = failed_source.token().require_active(deadline, failed_operations); + const auto source_failure = failed_source.cancel_if_expired(deadline, failed_operations); + if (!check(!token_failure.has_value() && token_failure.error().code() == laghu::core::ErrorCode::io && + !source_failure.has_value() && source_failure.error().code() == laghu::core::ErrorCode::io && + failed_source.token().outcome().terminal == laghu::core::CancellationTerminal::pending)) { + return false; + } + + laghu::core::CancellationState completed_state; + laghu::core::CancellationSource completed_source{completed_state}; + const auto completion = completed_source.complete(); + const auto completed_token = completed_source.token().require_active(deadline, failed_operations); + const auto already_completed = completed_source.cancel_if_expired(deadline, failed_operations); + return check(completion.terminal == laghu::core::CancellationTerminal::completed && + !completed_token.has_value() && + completed_token.error().code() == laghu::core::ErrorCode::invalid_state && + already_completed.has_value() && + already_completed->terminal == laghu::core::CancellationTerminal::completed && + already_completed->cause == laghu::core::CancellationCause::none); +} + [[nodiscard]] bool check_parent_observation_and_bounds() noexcept { laghu::core::CancellationState root_state; laghu::core::CancellationSource root_source{root_state}; @@ -250,6 +323,9 @@ int main() { if (!check(check_deadline_cancellation())) { return 4; } + if (!check(check_clock_operation_deadline_cancellation())) { + return 8; + } if (!check(check_parent_observation_and_bounds())) { return 5; } diff --git a/tests/support/laghu_test_time_entropy.cpp b/tests/support/laghu_test_time_entropy.cpp index 2ab69b2..49026d1 100644 --- a/tests/support/laghu_test_time_entropy.cpp +++ b/tests/support/laghu_test_time_entropy.cpp @@ -53,26 +53,19 @@ bool DeterministicEntropy::fail_on_call(std::size_t call, int native_error) noex return true; } -int DeterministicEntropy::fill(core::MutableByteView output, void* context) noexcept { - if (context == nullptr) { - errno = EINVAL; - return -1; - } - auto& entropy = *static_cast(context); - ++entropy.calls_; - if (entropy.failure_call_ == entropy.calls_) { - errno = entropy.failure_error_; - return -1; +core::Result DeterministicEntropy::fill(core::MutableByteView output) noexcept { + ++calls_; + if (failure_call_ == calls_) { + return std::unexpected{core::Error::from_errno(failure_error_, "deterministic entropy failed")}; } - if (output.size() > entropy.bytes_.size() - entropy.offset_) { - errno = ENOSPC; - return -1; + if (output.size() > bytes_.size() - offset_) { + return std::unexpected{core::Error::from_errno(ENOSPC, "deterministic entropy is exhausted")}; } for (std::size_t index = 0; index < output.size(); ++index) { - output.span()[index] = entropy.bytes_[entropy.offset_ + index]; + output.span()[index] = bytes_[offset_ + index]; } - entropy.offset_ += output.size(); - return 0; + offset_ += output.size(); + return {}; } } // namespace laghu::test diff --git a/tests/support/laghu_test_time_entropy.hpp b/tests/support/laghu_test_time_entropy.hpp index 100a51a..d4db42c 100644 --- a/tests/support/laghu_test_time_entropy.hpp +++ b/tests/support/laghu_test_time_entropy.hpp @@ -5,8 +5,8 @@ #include #include -#include #include +#include namespace laghu::test { @@ -38,14 +38,11 @@ class DeterministicEntropy final { : bytes_(bytes) {} [[nodiscard]] bool fail_on_call(std::size_t call, int native_error) noexcept; - [[nodiscard]] constexpr adapters::internal::EntropyCall call() const noexcept { return fill; } - [[nodiscard]] constexpr void* context() noexcept { return this; } + [[nodiscard]] core::Result fill(core::MutableByteView output) noexcept; [[nodiscard]] constexpr std::size_t calls() const noexcept { return calls_; } [[nodiscard]] constexpr std::size_t consumed() const noexcept { return offset_; } private: - [[nodiscard]] static int fill(core::MutableByteView output, void* context) noexcept; - std::span bytes_{}; std::size_t offset_{}; std::size_t calls_{}; diff --git a/tests/support/time_entropy.cpp b/tests/support/time_entropy.cpp index 0ef926c..aa84eb9 100644 --- a/tests/support/time_entropy.cpp +++ b/tests/support/time_entropy.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -101,17 +100,17 @@ template } [[nodiscard]] bool check_deterministic_entropy() noexcept { - std::array sequence{}; + std::array sequence{}; for (std::size_t index = 0; index < sequence.size(); ++index) { sequence[index] = static_cast(index); } - std::array output{}; + std::array output{}; laghu::test::DeterministicEntropy entropy{std::span{sequence}}; - const auto result = laghu::adapters::internal::fill_entropy_with( - mutable_view(output), entropy.call(), entropy.context()); - if (!result.has_value() || entropy.calls() != 3 || entropy.consumed() != output.size() || - output[0] != sequence[0] || output[255] != sequence[255] || - output[256] != sequence[256] || output.back() != sequence.back()) { + const auto first = entropy.fill(mutable_view(output)); + const auto second = entropy.fill(mutable_view(output)); + if (!first.has_value() || !second.has_value() || entropy.calls() != 2 || + entropy.consumed() != sequence.size() || output[0] != sequence[3] || + output[1] != sequence[4] || output[2] != sequence[5]) { return false; } @@ -119,18 +118,14 @@ template if (!failing.fail_on_call(2, EIO)) { return false; } - const auto failure = laghu::adapters::internal::fill_entropy_with( - mutable_view(output), failing.call(), failing.context()); - return !failure.has_value() && failing.calls() == 2 && failing.consumed() == 256 && + const auto successful = failing.fill(mutable_view(output)); + const auto failure = failing.fill(mutable_view(output)); + return successful.has_value() && !failure.has_value() && failing.calls() == 2 && + failing.consumed() == output.size() && failure.error().domain() == laghu::core::ErrorDomain::posix && failure.error().code() == laghu::core::ErrorCode::io && failure.error().native_code() == EIO; } -[[nodiscard]] bool check_os_entropy_only() noexcept { - std::array output{}; - return laghu::adapters::internal::fill_entropy(mutable_view(output)).has_value(); -} - } // namespace int main() { @@ -141,7 +136,6 @@ int main() { laghu::test::TestCase{"time_entropy.realtime_conversion_boundaries", check_realtime_conversion_boundaries}, laghu::test::TestCase{"time_entropy.deterministic_entropy", check_deterministic_entropy}, - laghu::test::TestCase{"time_entropy.os_entropy_only", check_os_entropy_only}, }; return laghu::test::run_tests(tests); } From db38f887ad2ae418d4e041e200a2f566e26643db Mon Sep 17 00:00:00 2001 From: Nitesh Purohit Date: Thu, 10 Sep 2026 18:40:49 -0400 Subject: [PATCH 3/3] feat: enhance time entropy and clock operations --- CMakeLists.txt | 15 +++++- src/core/clocks.cpp | 5 +- src/core/contract/laghu/core/clocks.hpp | 4 ++ .../laghu/core/internal/clock_operations.hpp | 1 - tests/adapters/entropy_seam.cpp | 53 +++++++++++++++++++ tests/core/deadlines_cancellation.cpp | 22 ++++++++ tests/support/laghu_test_time_entropy.cpp | 30 ++++++++--- tests/support/laghu_test_time_entropy.hpp | 7 +++ tests/support/time_entropy.cpp | 2 +- 9 files changed, 124 insertions(+), 15 deletions(-) create mode 100644 tests/adapters/entropy_seam.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 447c55c..e640a72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,8 @@ add_library(laghu_test_time_entropy STATIC tests/support/laghu_test_time_entropy laghu_apply_first_party_contract(laghu_test_time_entropy) target_include_directories(laghu_test_time_entropy PUBLIC "${CMAKE_SOURCE_DIR}/tests/support" - "${CMAKE_SOURCE_DIR}/src/core/contract") + "${CMAKE_SOURCE_DIR}/src/core/contract" + "${CMAKE_SOURCE_DIR}/src/adapters/private") add_executable(laghu_test_time_entropy_test tests/support/time_entropy.cpp) laghu_apply_first_party_contract(laghu_test_time_entropy_test) @@ -245,6 +246,15 @@ if(TARGET laghu_crypto) target_link_libraries(laghu_crypto_provider_test PRIVATE laghu_crypto) target_link_libraries(laghu_crypto_provider_test PRIVATE laghu_test_support) laghu_add_native_test(laghu.crypto.provider laghu_crypto_provider_test) + + add_executable(laghu_crypto_entropy_seam_test tests/adapters/entropy_seam.cpp) + laghu_apply_first_party_contract(laghu_crypto_entropy_seam_test) + laghu_configure_api_consumer(laghu_crypto_entropy_seam_test adapters) + target_include_directories(laghu_crypto_entropy_seam_test PRIVATE + "${CMAKE_SOURCE_DIR}/src/adapters/private") + target_link_libraries(laghu_crypto_entropy_seam_test PRIVATE + laghu_crypto laghu_test_support laghu_test_time_entropy) + laghu_add_native_test(laghu.crypto.entropy_seam laghu_crypto_entropy_seam_test) endif() if(LAGHU_ENABLE_TSAN) @@ -312,7 +322,8 @@ set(laghu_verify_targets laghu_test_support_runner_contract_test laghu_test_fault_injection_test laghu laghu_capability_header_parity laghu_visibility_probe visibility_negative_fixture) if(TARGET laghu_crypto_provider_test) - list(APPEND laghu_verify_targets laghu_crypto laghu_crypto_provider_test) + list(APPEND laghu_verify_targets + laghu_crypto laghu_crypto_provider_test laghu_crypto_entropy_seam_test) endif() add_custom_target(laghu_verify_toolchain diff --git a/src/core/clocks.cpp b/src/core/clocks.cpp index e75a8bf..924ec3d 100644 --- a/src/core/clocks.cpp +++ b/src/core/clocks.cpp @@ -6,7 +6,6 @@ #include #include - #include namespace laghu::core { @@ -62,9 +61,9 @@ Result read_realtime_clock(const ClockOperations& operations) n return operations.realtime_now(operations.context); } -namespace internal { +const ClockOperations& system_clock_operations() noexcept { return default_operations; } -const ClockOperations& default_clock_operations() noexcept { return default_operations; } +namespace internal { Result realtime_instant_from_parts(RealtimeInstant seconds, RealtimeInstant nanoseconds) noexcept { diff --git a/src/core/contract/laghu/core/clocks.hpp b/src/core/contract/laghu/core/clocks.hpp index c90a121..a8dd8d2 100644 --- a/src/core/contract/laghu/core/clocks.hpp +++ b/src/core/contract/laghu/core/clocks.hpp @@ -26,4 +26,8 @@ struct ClockOperations final { [[nodiscard]] Result read_realtime_clock( const ClockOperations& operations) noexcept; +// Returns a non-owning reference to Laghu's process-static POSIX clock table. +// It remains valid for the process lifetime and exposes no POSIX types. +[[nodiscard]] const ClockOperations& system_clock_operations() noexcept; + } // namespace laghu::core diff --git a/src/core/private/laghu/core/internal/clock_operations.hpp b/src/core/private/laghu/core/internal/clock_operations.hpp index 909a395..b74b24a 100644 --- a/src/core/private/laghu/core/internal/clock_operations.hpp +++ b/src/core/private/laghu/core/internal/clock_operations.hpp @@ -5,7 +5,6 @@ namespace laghu::core::internal { -[[nodiscard]] const ClockOperations& default_clock_operations() noexcept; [[nodiscard]] Result realtime_instant_from_parts( RealtimeInstant seconds, RealtimeInstant nanoseconds) noexcept; diff --git a/tests/adapters/entropy_seam.cpp b/tests/adapters/entropy_seam.cpp new file mode 100644 index 0000000..1f4df6b --- /dev/null +++ b/tests/adapters/entropy_seam.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +#include +#include +#include +#include + +#include "laghu_test_support.hpp" +#include "laghu_test_time_entropy.hpp" + +#include + +namespace { + +template +[[nodiscard]] laghu::core::MutableByteView mutable_view( + std::array& bytes) noexcept { + return *laghu::core::MutableByteView::from(std::span{bytes}); +} + +[[nodiscard]] bool check_deterministic_entropy_seam() noexcept { + std::array sequence{}; + for (std::size_t index = 0; index < sequence.size(); ++index) { + sequence[index] = static_cast(index); + } + std::array output{}; + laghu::test::DeterministicEntropy entropy{std::span{sequence}}; + const auto success = laghu::adapters::internal::fill_entropy_with( + mutable_view(output), entropy.call(), entropy.context()); + if (!success.has_value() || entropy.calls() != 3 || entropy.consumed() != output.size() || + output[0] != sequence[0] || output[255] != sequence[255] || + output[256] != sequence[256] || output.back() != sequence.back()) { + return false; + } + + laghu::test::DeterministicEntropy failing{std::span{sequence}}; + if (!failing.fail_on_call(2, EIO)) { + return false; + } + const auto failure = laghu::adapters::internal::fill_entropy_with( + mutable_view(output), failing.call(), failing.context()); + return !failure.has_value() && failing.calls() == 2 && failing.consumed() == 256 && + failure.error().domain() == laghu::core::ErrorDomain::posix && + failure.error().code() == laghu::core::ErrorCode::io && failure.error().native_code() == EIO; +} + +} // namespace + +int main() { + constexpr std::array tests{ + laghu::test::TestCase{"adapters.entropy.deterministic_seam", check_deterministic_entropy_seam}, + }; + return laghu::test::run_tests(tests); +} diff --git a/tests/core/deadlines_cancellation.cpp b/tests/core/deadlines_cancellation.cpp index ccdb71b..d8e3736 100644 --- a/tests/core/deadlines_cancellation.cpp +++ b/tests/core/deadlines_cancellation.cpp @@ -215,6 +215,25 @@ class OperationClock final { already_completed->cause == laghu::core::CancellationCause::none); } +[[nodiscard]] bool check_system_clock_contract() noexcept { + const auto& operations = laghu::core::system_clock_operations(); + const auto monotonic = laghu::core::read_monotonic_clock(operations); + const auto realtime = laghu::core::read_realtime_clock(operations); + if (!check(monotonic.has_value() && realtime.has_value())) { + return false; + } + + laghu::core::CancellationState state; + laghu::core::CancellationSource source{state}; + const auto active = source.token().require_active( + laghu::core::Deadline::at(std::numeric_limits::max()), + operations); + const auto expired = source.cancel_if_expired(laghu::core::Deadline::at(*monotonic), operations); + return check(active.has_value() && expired.has_value() && expired->is_cancelled() && + expired->cause == laghu::core::CancellationCause::deadline_expired && + source.token().outcome().is_cancelled()); +} + [[nodiscard]] bool check_parent_observation_and_bounds() noexcept { laghu::core::CancellationState root_state; laghu::core::CancellationSource root_source{root_state}; @@ -326,6 +345,9 @@ int main() { if (!check(check_clock_operation_deadline_cancellation())) { return 8; } + if (!check(check_system_clock_contract())) { + return 9; + } if (!check(check_parent_observation_and_bounds())) { return 5; } diff --git a/tests/support/laghu_test_time_entropy.cpp b/tests/support/laghu_test_time_entropy.cpp index 49026d1..02b62f5 100644 --- a/tests/support/laghu_test_time_entropy.cpp +++ b/tests/support/laghu_test_time_entropy.cpp @@ -54,18 +54,32 @@ bool DeterministicEntropy::fail_on_call(std::size_t call, int native_error) noex } core::Result DeterministicEntropy::fill(core::MutableByteView output) noexcept { - ++calls_; - if (failure_call_ == calls_) { - return std::unexpected{core::Error::from_errno(failure_error_, "deterministic entropy failed")}; + if (fill_call(output, this) != 0) { + return std::unexpected{core::Error::from_errno(errno, "deterministic entropy failed")}; } - if (output.size() > bytes_.size() - offset_) { - return std::unexpected{core::Error::from_errno(ENOSPC, "deterministic entropy is exhausted")}; + return {}; +} + +int DeterministicEntropy::fill_call(core::MutableByteView output, void* context) noexcept { + if (context == nullptr) { + errno = EINVAL; + return -1; + } + auto& entropy = *static_cast(context); + ++entropy.calls_; + if (entropy.failure_call_ == entropy.calls_) { + errno = entropy.failure_error_; + return -1; + } + if (output.size() > entropy.bytes_.size() - entropy.offset_) { + errno = ENOSPC; + return -1; } for (std::size_t index = 0; index < output.size(); ++index) { - output.span()[index] = bytes_[offset_ + index]; + output.span()[index] = entropy.bytes_[entropy.offset_ + index]; } - offset_ += output.size(); - return {}; + entropy.offset_ += output.size(); + return 0; } } // namespace laghu::test diff --git a/tests/support/laghu_test_time_entropy.hpp b/tests/support/laghu_test_time_entropy.hpp index d4db42c..c4c62b0 100644 --- a/tests/support/laghu_test_time_entropy.hpp +++ b/tests/support/laghu_test_time_entropy.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -39,10 +40,16 @@ class DeterministicEntropy final { [[nodiscard]] bool fail_on_call(std::size_t call, int native_error) noexcept; [[nodiscard]] core::Result fill(core::MutableByteView output) noexcept; + [[nodiscard]] constexpr adapters::internal::EntropyCall call() const noexcept { + return fill_call; + } + [[nodiscard]] constexpr void* context() noexcept { return this; } [[nodiscard]] constexpr std::size_t calls() const noexcept { return calls_; } [[nodiscard]] constexpr std::size_t consumed() const noexcept { return offset_; } private: + [[nodiscard]] static int fill_call(core::MutableByteView output, void* context) noexcept; + std::span bytes_{}; std::size_t offset_{}; std::size_t calls_{}; diff --git a/tests/support/time_entropy.cpp b/tests/support/time_entropy.cpp index aa84eb9..6e1d276 100644 --- a/tests/support/time_entropy.cpp +++ b/tests/support/time_entropy.cpp @@ -50,7 +50,7 @@ template } [[nodiscard]] bool check_clock_tables() noexcept { - const auto& operations = laghu::core::internal::default_clock_operations(); + const auto& operations = laghu::core::system_clock_operations(); const auto monotonic = laghu::core::read_monotonic_clock(operations); const auto realtime = laghu::core::read_realtime_clock(operations); const laghu::core::ClockOperations incomplete{};