Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,6 +103,24 @@ 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)
laghu_apply_first_party_contract(laghu_test_time_entropy_test)
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")
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
Expand Down Expand Up @@ -227,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)
Expand Down Expand Up @@ -289,11 +317,13 @@ 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)
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
Expand Down
1 change: 1 addition & 0 deletions cmake/LaghuApiBoundaries.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions cmake/LaghuBuildIdentity.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
98 changes: 98 additions & 0 deletions src/core/clocks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: AGPL-3.0-only
#include <cerrno>
#include <cstdint>
#include <limits>

#include <time.h>

#include <laghu/core/clocks.hpp>
#include <laghu/core/internal/clock_operations.hpp>

namespace laghu::core {
namespace {

inline constexpr std::int64_t nanoseconds_per_second = 1000000000;

[[nodiscard]] Result<MonotonicInstant> 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<MonotonicInstant>(value.tv_sec);
const auto nanoseconds = static_cast<MonotonicInstant>(value.tv_nsec);
if (seconds > (std::numeric_limits<MonotonicInstant>::max() - nanoseconds) /
static_cast<MonotonicInstant>(nanoseconds_per_second)) {
return std::unexpected{Error{ErrorDomain::core, ErrorCode::overflow, 0,
"CLOCK_MONOTONIC result overflows"}};
}
return seconds * static_cast<MonotonicInstant>(nanoseconds_per_second) + nanoseconds;
}

[[nodiscard]] Result<RealtimeInstant> 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<RealtimeInstant>(value.tv_sec),
static_cast<RealtimeInstant>(value.tv_nsec));
}

const ClockOperations default_operations{nullptr, posix_monotonic_now, posix_realtime_now};

} // namespace

Result<MonotonicInstant> 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<RealtimeInstant> 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);
}

const ClockOperations& system_clock_operations() noexcept { return default_operations; }

namespace internal {

Result<RealtimeInstant> 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<RealtimeInstant>::max();
constexpr RealtimeInstant minimum = std::numeric_limits<RealtimeInstant>::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
33 changes: 33 additions & 0 deletions src/core/contract/laghu/core/clocks.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: AGPL-3.0-only
#pragma once

#include <cstdint>

#include <laghu/core/contract.hpp>

namespace laghu::core {

using MonotonicInstant = std::uint64_t;
using RealtimeInstant = std::int64_t;

using MonotonicNowFunction = Result<MonotonicInstant> (*)(void* context) noexcept;
using RealtimeNowFunction = Result<RealtimeInstant> (*)(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<MonotonicInstant> read_monotonic_clock(
const ClockOperations& operations) noexcept;
[[nodiscard]] Result<RealtimeInstant> 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
60 changes: 57 additions & 3 deletions src/core/contract/laghu/core/deadlines_cancellation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
#include <limits>
#include <type_traits>

#include <laghu/core/contract.hpp>
#include <laghu/core/clocks.hpp>

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.
Expand All @@ -40,6 +38,19 @@ class Deadline final {
return Deadline{now + duration};
}

[[nodiscard]] static Result<Deadline> 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<MonotonicInstant>::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_};
}
Expand All @@ -51,6 +62,14 @@ class Deadline final {
return clock.now() >= instant_;
}

[[nodiscard]] Result<bool> 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 <MonotonicClock Clock>
[[nodiscard]] constexpr Result<void> require_not_expired(const Clock& clock) const noexcept {
if (expired(clock)) {
Expand All @@ -59,6 +78,17 @@ class Deadline final {
return {};
}

[[nodiscard]] Result<void> 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"};
}
Expand Down Expand Up @@ -193,6 +223,14 @@ class CancellationToken final {
return deadline.require_not_expired(clock);
}

[[nodiscard]] Result<void> require_active(const Deadline& deadline,
const ClockOperations& operations) const noexcept {
if (const Result<void> active = require_active(); !active.has_value()) {
return std::unexpected{active.error()};
}
return deadline.require_not_expired(operations);
}

[[nodiscard]] Result<CancellationToken> child(CancellationState& child_state) const noexcept {
for (std::uint8_t index = 0; index < state_count_; ++index) {
if (states_[index] == &child_state) {
Expand Down Expand Up @@ -252,6 +290,22 @@ class CancellationSource final {
return state_->finish(CancellationState::StoredTerminal::cancelled_deadline);
}

[[nodiscard]] Result<CancellationOutcome> 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_;
};
Expand Down
11 changes: 11 additions & 0 deletions src/core/private/laghu/core/internal/clock_operations.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
#pragma once

#include <laghu/core/clocks.hpp>

namespace laghu::core::internal {

[[nodiscard]] Result<RealtimeInstant> realtime_instant_from_parts(
RealtimeInstant seconds, RealtimeInstant nanoseconds) noexcept;

} // namespace laghu::core::internal
53 changes: 53 additions & 0 deletions tests/adapters/entropy_seam.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: AGPL-3.0-only
#include <array>
#include <cerrno>
#include <cstddef>
#include <span>

#include "laghu_test_support.hpp"
#include "laghu_test_time_entropy.hpp"

#include <laghu/adapters/internal/entropy.hpp>

namespace {

template <std::size_t Size>
[[nodiscard]] laghu::core::MutableByteView mutable_view(
std::array<std::byte, Size>& bytes) noexcept {
return *laghu::core::MutableByteView::from(std::span<std::byte>{bytes});
}

[[nodiscard]] bool check_deterministic_entropy_seam() noexcept {
std::array<std::byte, 600> sequence{};
for (std::size_t index = 0; index < sequence.size(); ++index) {
sequence[index] = static_cast<std::byte>(index);
}
std::array<std::byte, 600> output{};
laghu::test::DeterministicEntropy entropy{std::span<const std::byte>{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<const std::byte>{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);
}
Loading
Loading