From fcfb27c5202467e29daf1133dbfa0c520905be8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20J=C4=99drzejewski?= Date: Thu, 19 Feb 2026 10:15:09 +0100 Subject: [PATCH] hmon: rework error handling in FFI - Uniformly return `FFICode` in FFI functions. - Unit tests for FFI functions in Rust. - Rework comments for FFI functions. - Move `crate::common::ffi` to `crate::ffi`. --- src/health_monitoring_lib/BUILD | 1 - src/health_monitoring_lib/cpp/common.cpp | 9 + .../cpp/deadline_monitor.cpp | 61 +- src/health_monitoring_lib/cpp/ffi_helpers.h | 43 -- .../cpp/health_monitor.cpp | 84 ++- .../cpp/include/score/hm/common.h | 116 +-- .../score/hm/deadline/deadline_monitor.h | 2 +- .../cpp/include/score/hm/health_monitor.h | 4 +- .../cpp/tests/health_monitor_test.cpp | 2 - src/health_monitoring_lib/rust/common.rs | 41 -- .../rust/deadline/ffi.rs | 686 +++++++++++++++-- src/health_monitoring_lib/rust/ffi.rs | 687 +++++++++++++++--- src/health_monitoring_lib/rust/lib.rs | 99 ++- 13 files changed, 1433 insertions(+), 402 deletions(-) delete mode 100644 src/health_monitoring_lib/cpp/ffi_helpers.h diff --git a/src/health_monitoring_lib/BUILD b/src/health_monitoring_lib/BUILD index 2b25b7d0..70f2dbbb 100644 --- a/src/health_monitoring_lib/BUILD +++ b/src/health_monitoring_lib/BUILD @@ -28,7 +28,6 @@ PROC_MACRO_DEPS = [ CC_SOURCES = [ "cpp/common.cpp", "cpp/deadline_monitor.cpp", - "cpp/ffi_helpers.h", "cpp/health_monitor.cpp", ] diff --git a/src/health_monitoring_lib/cpp/common.cpp b/src/health_monitoring_lib/cpp/common.cpp index 5b7cf394..0be0c824 100644 --- a/src/health_monitoring_lib/cpp/common.cpp +++ b/src/health_monitoring_lib/cpp/common.cpp @@ -11,10 +11,19 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include +#include namespace score::hm::internal { +void abort_on_error(FFICode code) +{ + if (code != kSuccess) + { + std::abort(); + } +} + DroppableFFIHandle::DroppableFFIHandle(FFIHandle handle, DropFn drop_fn) : handle_(handle), drop_fn_(drop_fn) {} DroppableFFIHandle::DroppableFFIHandle(DroppableFFIHandle&& other) noexcept diff --git a/src/health_monitoring_lib/cpp/deadline_monitor.cpp b/src/health_monitoring_lib/cpp/deadline_monitor.cpp index 047a86b7..5426b84a 100644 --- a/src/health_monitoring_lib/cpp/deadline_monitor.cpp +++ b/src/health_monitoring_lib/cpp/deadline_monitor.cpp @@ -11,33 +11,47 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include "score/hm/deadline/deadline_monitor.h" -#include "ffi_helpers.h" +namespace +{ extern "C" { using namespace score::hm; using namespace score::hm::internal; using namespace score::hm::deadline; -// Deadline monitoring Foreign Function Interface Declarations that are exported by Rust implementation library -internal::FFIHandle deadline_monitor_builder_create(); -void deadline_monitor_builder_destroy(internal::FFIHandle handle); -void deadline_monitor_builder_add_deadline(internal::FFIHandle handler, - const IdentTag* tag, - uint32_t min, - uint32_t max); -int deadline_monitor_cpp_get_deadline(FFIHandle handler, const IdentTag* tag, FFIHandle* out); -void deadline_monitor_cpp_destroy(FFIHandle handler); -void deadline_destroy(FFIHandle deadline_handle); -int deadline_start(FFIHandle deadline_handle); -void deadline_stop(FFIHandle deadline_handle); +// Functions below must match functions defined in `crate::deadline::ffi`. + +FFICode deadline_monitor_builder_create(FFIHandle* deadline_monitor_builder_handle_out); +FFICode deadline_monitor_builder_destroy(FFIHandle deadline_monitor_builder_handle); +FFICode deadline_monitor_builder_add_deadline(FFIHandle deadline_monitor_builder_handle, + const IdentTag* deadline_tag, + uint32_t min_ms, + uint32_t max_ms); +FFICode deadline_monitor_get_deadline(FFIHandle deadline_monitor_handle, + const IdentTag* deadline_tag, + FFIHandle* deadline_handle_out); +FFICode deadline_monitor_destroy(FFIHandle deadline_monitor_handle); +FFICode deadline_destroy(FFIHandle deadline_handle); +FFICode deadline_start(FFIHandle deadline_handle); +FFICode deadline_stop(FFIHandle deadline_handle); +} + +FFIHandle deadline_monitor_builder_create_wrapper() +{ + FFIHandle handle{nullptr}; + auto result{deadline_monitor_builder_create(&handle)}; + abort_on_error(result); + return handle; } +} // namespace + // C++ wrapper for Rust library - the API implementation obeys the Rust API semantics and it's invariants namespace score::hm::deadline { DeadlineMonitorBuilder::DeadlineMonitorBuilder() - : monitor_builder_handler_(deadline_monitor_builder_create(), &deadline_monitor_builder_destroy) + : monitor_builder_handler_{deadline_monitor_builder_create_wrapper(), &deadline_monitor_builder_destroy} { } @@ -46,30 +60,30 @@ DeadlineMonitorBuilder DeadlineMonitorBuilder::add_deadline(const IdentTag& tag, auto handle = monitor_builder_handler_.as_rust_handle(); SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - deadline_monitor_builder_add_deadline(handle.value(), &tag, range.min_ms(), range.max_ms()); + auto result{deadline_monitor_builder_add_deadline(handle.value(), &tag, range.min_ms(), range.max_ms())}; + abort_on_error(result); return std::move(*this); } -DeadlineMonitor::DeadlineMonitor(FFIHandle handle) : monitor_handle_(handle, &deadline_monitor_cpp_destroy) {} +DeadlineMonitor::DeadlineMonitor(FFIHandle handle) : monitor_handle_(handle, &deadline_monitor_destroy) {} score::cpp::expected DeadlineMonitor::get_deadline(const IdentTag& tag) { auto handle = monitor_handle_.as_rust_handle(); SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - internal::FFIHandle ret = nullptr; - auto result = deadline_monitor_cpp_get_deadline(handle.value(), &tag, &ret); - + FFIHandle ret = nullptr; + auto result = deadline_monitor_get_deadline(handle.value(), &tag, &ret); if (result != kSuccess) { - return score::cpp::unexpected(::score::hm::ffi::fromRustError(result)); + return score::cpp::unexpected(static_cast(result)); } return score::cpp::expected(Deadline{ret}); } -Deadline::Deadline(internal::FFIHandle handle) : deadline_handle_(handle, &deadline_destroy), has_handle_(false) {} +Deadline::Deadline(FFIHandle handle) : deadline_handle_(handle, &deadline_destroy), has_handle_(false) {} Deadline::~Deadline() { @@ -90,7 +104,7 @@ score::cpp::expected Deadline::start() auto result = deadline_start(handle.value()); if (result != kSuccess) { - return score::cpp::unexpected(::score::hm::ffi::fromRustError(result)); + return score::cpp::unexpected(static_cast(result)); } has_handle_ = true; @@ -109,7 +123,8 @@ void DeadlineHandle::stop() auto handle = deadline_.value().get().deadline_handle_.as_rust_handle(); SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - deadline_stop(handle.value()); + auto result{deadline_stop(handle.value())}; + abort_on_error(result); } } diff --git a/src/health_monitoring_lib/cpp/ffi_helpers.h b/src/health_monitoring_lib/cpp/ffi_helpers.h deleted file mode 100644 index 35dd9188..00000000 --- a/src/health_monitoring_lib/cpp/ffi_helpers.h +++ /dev/null @@ -1,43 +0,0 @@ -/******************************************************************************** - * 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_HM_FFI_HELPERS_HPP -#define SCORE_HM_FFI_HELPERS_HPP - -#include - -namespace score::hm::ffi -{ - -inline Error fromRustError(int ffi_error_code) -{ - switch (ffi_error_code) - { - case 1: - return Error::NotFound; - case 2: - return Error::AlreadyExists; - case 3: - return Error::InvalidArgument; - case 4: - return Error::WrongState; - case 5: - return Error::Failed; - default: - assert(false && "Unknown FFI error code"); - return Error::InvalidArgument; // Fallback - } -} - -} // namespace score::hm::ffi - -#endif // SCORE_HM_FFI_HELPERS_HPP diff --git a/src/health_monitoring_lib/cpp/health_monitor.cpp b/src/health_monitoring_lib/cpp/health_monitor.cpp index 75f79b7e..8f9058a8 100644 --- a/src/health_monitoring_lib/cpp/health_monitor.cpp +++ b/src/health_monitoring_lib/cpp/health_monitor.cpp @@ -12,44 +12,62 @@ ********************************************************************************/ #include "score/hm/health_monitor.h" +namespace +{ extern "C" { using namespace score::hm; +using namespace score::hm::internal; +using namespace score::hm::deadline; + +// Functions below must match functions defined in `crate::ffi`. + +FFICode health_monitor_builder_create(FFIHandle* health_monitor_builder_handle_out); +FFICode health_monitor_builder_destroy(FFIHandle health_monitor_builder_handle); +FFICode health_monitor_builder_build(FFIHandle health_monitor_builder_handle, + uint32_t supervisor_cycle_ms, + uint32_t internal_cycle_ms, + FFIHandle* health_monitor_handle_out); +FFICode health_monitor_builder_add_deadline_monitor(FFIHandle health_monitor_builder_handle, + const IdentTag* monitor_tag, + FFIHandle deadline_monitor_builder_handle); +FFICode health_monitor_get_deadline_monitor(FFIHandle health_monitor_handle, + const IdentTag* monitor_tag, + FFIHandle* deadline_monitor_handle_out); +FFICode health_monitor_start(FFIHandle health_monitor_handle); +FFICode health_monitor_destroy(FFIHandle health_monitor_handle); +} -// Health Monitor Foreign Function Interface Declarations that are exported by Rust implementation library -internal::FFIHandle health_monitor_builder_create(); -void health_monitor_builder_destroy(internal::FFIHandle handler); - -internal::FFIHandle health_monitor_builder_build(internal::FFIHandle health_monitor_builder_handle, - uint32_t supervisor_cycle_ms, - uint32_t internal_cycle_ms); -void health_monitor_builder_add_deadline_monitor(internal::FFIHandle handle, - const IdentTag* tag, - internal::FFIHandle monitor_handle); - -internal::FFIHandle health_monitor_get_deadline_monitor(internal::FFIHandle health_monitor_handle, const IdentTag* tag); -void health_monitor_start(internal::FFIHandle health_monitor_handle); -void health_monitor_destroy(internal::FFIHandle handler); +FFIHandle health_monitor_builder_create_wrapper() +{ + FFIHandle handle{nullptr}; + auto result{health_monitor_builder_create(&handle)}; + abort_on_error(result); + return handle; } +} // namespace + // C++ wrapper for Rust library - the API implementation obeys the Rust API semantics and it's invariants namespace score::hm { HealthMonitorBuilder::HealthMonitorBuilder() - : health_monitor_builder_handle_{health_monitor_builder_create(), &health_monitor_builder_destroy} + : health_monitor_builder_handle_{health_monitor_builder_create_wrapper(), &health_monitor_builder_destroy} { } HealthMonitorBuilder HealthMonitorBuilder::add_deadline_monitor(const IdentTag& tag, - deadline::DeadlineMonitorBuilder&& monitor) && + DeadlineMonitorBuilder&& monitor) && { auto monitor_handle = monitor.drop_by_rust(); SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle_.as_rust_handle().has_value()); - health_monitor_builder_add_deadline_monitor( - health_monitor_builder_handle_.as_rust_handle().value(), &tag, monitor_handle.value()); + auto result{health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle_.as_rust_handle().value(), &tag, monitor_handle.value())}; + abort_on_error(result); + return std::move(*this); } @@ -67,16 +85,21 @@ HealthMonitorBuilder HealthMonitorBuilder::with_supervisor_api_cycle(std::chrono HealthMonitor HealthMonitorBuilder::build() && { - auto handle = health_monitor_builder_handle_.drop_by_rust(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + auto health_monitor_builder_handle = health_monitor_builder_handle_.drop_by_rust(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle.has_value()); uint32_t supervisor_duration_ms = static_cast(supervisor_api_cycle_duration_.count()); uint32_t internal_duration_ms = static_cast(internal_processing_cycle_duration_.count()); - return HealthMonitor(health_monitor_builder_build(handle.value(), supervisor_duration_ms, internal_duration_ms)); + FFIHandle health_monitor_handle{nullptr}; + auto result{health_monitor_builder_build( + health_monitor_builder_handle.value(), supervisor_duration_ms, internal_duration_ms, &health_monitor_handle)}; + abort_on_error(result); + + return HealthMonitor{health_monitor_handle}; } -HealthMonitor::HealthMonitor(internal::FFIHandle handle) : health_monitor_(handle) +HealthMonitor::HealthMonitor(FFIHandle handle) : health_monitor_(handle) { // Initialize health monitor } @@ -87,21 +110,22 @@ HealthMonitor::HealthMonitor(HealthMonitor&& other) other.health_monitor_ = nullptr; } -score::cpp::expected HealthMonitor::get_deadline_monitor(const IdentTag& tag) +score::cpp::expected HealthMonitor::get_deadline_monitor(const IdentTag& tag) { - auto maybe_monitor = health_monitor_get_deadline_monitor(health_monitor_, &tag); - - if (maybe_monitor != nullptr) + FFIHandle handle{nullptr}; + auto result{health_monitor_get_deadline_monitor(health_monitor_, &tag, &handle)}; + if (result != kSuccess) { - - return score::cpp::expected(deadline::DeadlineMonitor{maybe_monitor}); + return score::cpp::unexpected(static_cast(result)); } - return score::cpp::unexpected(Error::NotFound); + return score::cpp::expected(DeadlineMonitor{handle}); } + void HealthMonitor::start() { - health_monitor_start(health_monitor_); + auto result{health_monitor_start(health_monitor_)}; + abort_on_error(result); } HealthMonitor::~HealthMonitor() diff --git a/src/health_monitoring_lib/cpp/include/score/hm/common.h b/src/health_monitoring_lib/cpp/include/score/hm/common.h index ef934996..462ceb7f 100644 --- a/src/health_monitoring_lib/cpp/include/score/hm/common.h +++ b/src/health_monitoring_lib/cpp/include/score/hm/common.h @@ -13,6 +13,7 @@ #ifndef SCORE_HM_COMMON_H #define SCORE_HM_COMMON_H +#include #include #include #include @@ -20,61 +21,18 @@ namespace score::hm { -constexpr int kSuccess = 0; - -enum class Error -{ - NotFound = kSuccess + 1, - AlreadyExists = kSuccess + 2, - InvalidArgument = kSuccess + 3, - WrongState = kSuccess + 4, - Failed = kSuccess + 5 -}; - -/// -/// Identifier tag used to uniquely identify entities within the health monitoring system. -/// -class IdentTag -{ - public: - /// Create a new IdentTag from a C-style string. - template - explicit IdentTag(const char (&tag)[N]) : tag_(tag), len_(N - 1) - { - } - - private: - /// SAFETY: This has to be FFI compatible with the Rust side representation. - const char* const tag_; - size_t len_; -}; - -/// -/// Time range representation with minimum and maximum durations in milliseconds. -/// -class TimeRange +/// FFI internal helpers +namespace internal { - public: - TimeRange(std::chrono::milliseconds min_ms, std::chrono::milliseconds max_ms) : min_ms_(min_ms), max_ms_(max_ms) {} - - const uint32_t min_ms() const - { - return min_ms_.count(); - } - const uint32_t max_ms() const - { - return max_ms_.count(); - } +/// Internal success representation. +constexpr int kSuccess = 0; - private: - const std::chrono::milliseconds min_ms_; - const std::chrono::milliseconds max_ms_; -}; +/// Internal return code. +using FFICode = uint8_t; -/// FFI internal helpers -namespace internal -{ +/// Abort if provided code is not equal to `kSuccess`. +void abort_on_error(FFICode code); /// Opaque handle type for Rust managed object using FFIHandle = void*; @@ -98,7 +56,7 @@ class RustDroppable class DroppableFFIHandle { public: - using DropFn = void (*)(FFIHandle); + using DropFn = internal::FFICode (*)(FFIHandle); DroppableFFIHandle(FFIHandle handle, DropFn drop_fn); @@ -123,6 +81,60 @@ class DroppableFFIHandle } // namespace internal +enum class Error : internal::FFICode +{ + NullParameter = internal::kSuccess + 1, + NotFound, + AlreadyExists, + InvalidArgument, + WrongState, + Failed +}; + +/// +/// Identifier tag used to uniquely identify entities within the health monitoring system. +/// +class IdentTag +{ + public: + /// Create a new IdentTag from a C-style string. + template + explicit IdentTag(const char (&tag)[N]) : tag_(tag), len_(N - 1) + { + } + + private: + /// SAFETY: This has to be FFI compatible with the Rust side representation. + const char* const tag_; + size_t len_; +}; + +/// +/// Time range representation with minimum and maximum durations in milliseconds. +/// +class TimeRange +{ + public: + TimeRange(std::chrono::milliseconds min_ms, std::chrono::milliseconds max_ms) : min_ms_(min_ms), max_ms_(max_ms) + { + assert(min_ms_ <= max_ms_); + } + + const uint32_t min_ms() const + { + return min_ms_.count(); + } + + const uint32_t max_ms() const + { + return max_ms_.count(); + } + + private: + const std::chrono::milliseconds min_ms_; + const std::chrono::milliseconds max_ms_; +}; + } // namespace score::hm #endif // SCORE_HM_COMMON_H diff --git a/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h b/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h index 72d75c60..b46b8b36 100644 --- a/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h +++ b/src/health_monitoring_lib/cpp/include/score/hm/deadline/deadline_monitor.h @@ -37,7 +37,7 @@ class Deadline; class DeadlineMonitorBuilder final : public internal::RustDroppable { public: - /// Creates a new DeadlineMonitorBuilder + /// Create a new `DeadlineMonitorBuilder`. DeadlineMonitorBuilder(); DeadlineMonitorBuilder(const DeadlineMonitorBuilder&) = delete; diff --git a/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h b/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h index bf7dc1d7..efdae573 100644 --- a/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h +++ b/src/health_monitoring_lib/cpp/include/score/hm/health_monitor.h @@ -27,7 +27,7 @@ class HealthMonitor; class HealthMonitorBuilder final { public: - /// Creates a new HealthMonitorBuilder + /// Create a new `HealthMonitorBuilder`. HealthMonitorBuilder(); ~HealthMonitorBuilder() = default; @@ -48,7 +48,7 @@ class HealthMonitorBuilder final /// This duration determines how often the health monitor checks deadlines. HealthMonitorBuilder with_internal_processing_cycle(std::chrono::milliseconds cycle_duration) &&; - /// Builds and returns the HealthMonitor instance. + /// Build a new `HealthMonitor` instance based on provided parameters. HealthMonitor build() &&; private: diff --git a/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp index 32d76085..568d76d1 100644 --- a/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp +++ b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp @@ -14,10 +14,8 @@ #include "score/hm/common.h" #include #include -#include using namespace score::hm; -using ::testing::_; class HealthMonitorTest : public ::testing::Test { diff --git a/src/health_monitoring_lib/rust/common.rs b/src/health_monitoring_lib/rust/common.rs index 0f8f29bd..fa8cd141 100644 --- a/src/health_monitoring_lib/rust/common.rs +++ b/src/health_monitoring_lib/rust/common.rs @@ -131,44 +131,3 @@ impl MonitorEvaluator for MonitorEvalHandle { self.inner.evaluate(on_error) } } - -pub(crate) mod ffi { - use core::mem::ManuallyDrop; - use core::ops::{Deref, DerefMut}; - - pub(crate) type FFIHandle = *mut core::ffi::c_void; - - pub(crate) const HM_OK: i32 = 0; - pub(crate) const HM_NOT_FOUND: i32 = HM_OK + 1; - pub(crate) const HM_ALREADY_EXISTS: i32 = HM_OK + 2; - pub(crate) const _HM_INVALID_ARGS: i32 = HM_OK + 3; - pub(crate) const _HM_WRONG_STATE: i32 = HM_OK + 4; - pub(crate) const HM_FAILED: i32 = HM_OK + 5; - - /// A wrapper to represent borrowed data over FFI boundary without taking ownership. - pub(crate) struct FFIBorrowed { - data: ManuallyDrop, - } - - impl FFIBorrowed { - pub(crate) fn new(data: T) -> Self { - Self { - data: ManuallyDrop::new(data), - } - } - } - - impl Deref for FFIBorrowed { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.data - } - } - - impl DerefMut for FFIBorrowed { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.data - } - } -} diff --git a/src/health_monitoring_lib/rust/deadline/ffi.rs b/src/health_monitoring_lib/rust/deadline/ffi.rs index 99d86b81..8b45e67d 100644 --- a/src/health_monitoring_lib/rust/deadline/ffi.rs +++ b/src/health_monitoring_lib/rust/deadline/ffi.rs @@ -10,12 +10,11 @@ // // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use crate::common::ffi::*; use crate::deadline::deadline_monitor::Deadline; -use crate::deadline::*; -use crate::*; +use crate::deadline::{DeadlineMonitor, DeadlineMonitorBuilder, DeadlineMonitorError}; +use crate::ffi::{FFIBorrowed, FFICode, FFIHandle}; +use crate::{IdentTag, TimeRange}; use core::time::Duration; -use std::os::raw::c_int; pub(crate) struct DeadlineMonitorCpp { monitor: DeadlineMonitor, @@ -28,128 +27,655 @@ impl DeadlineMonitorCpp { Self { monitor } } - pub(crate) fn get_deadline(&self, tag: IdentTag) -> Result { + pub(crate) fn get_deadline(&self, tag: IdentTag) -> Result { match self.monitor.get_deadline(&tag) { Ok(deadline) => { // Now we allocate at runtime. As next step we will add a memory pool for deadlines into self and this way we will not need allocate anymore - let handle = Box::into_raw(Box::new(deadline)); - Ok(handle as FFIHandle) + Ok(Box::into_raw(Box::new(deadline)).cast()) }, - Err(DeadlineMonitorError::DeadlineInUse) => Err(HM_ALREADY_EXISTS), - Err(DeadlineMonitorError::DeadlineNotFound) => Err(HM_NOT_FOUND), + Err(DeadlineMonitorError::DeadlineInUse) => Err(FFICode::AlreadyExists), + Err(DeadlineMonitorError::DeadlineNotFound) => Err(FFICode::NotFound), } } } #[no_mangle] -pub extern "C" fn deadline_monitor_builder_create() -> FFIHandle { - let builder = DeadlineMonitorBuilder::new(); - let handle = Box::into_raw(Box::new(builder)); - handle as FFIHandle +pub extern "C" fn deadline_monitor_builder_create(deadline_monitor_builder_handle_out: *mut FFIHandle) -> FFICode { + if deadline_monitor_builder_handle_out.is_null() { + return FFICode::NullParameter; + } + + let deadline_monitor_builder = DeadlineMonitorBuilder::new(); + unsafe { + *deadline_monitor_builder_handle_out = Box::into_raw(Box::new(deadline_monitor_builder)).cast(); + } + + FFICode::Success } #[no_mangle] -pub extern "C" fn deadline_monitor_builder_destroy(handle: FFIHandle) { - assert!(!handle.is_null()); +pub extern "C" fn deadline_monitor_builder_destroy(deadline_monitor_builder_handle: FFIHandle) -> FFICode { + if deadline_monitor_builder_handle.is_null() { + return FFICode::NullParameter; + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` - // and this must be assured on other side of FFI. + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `deadline_monitor_builder_create`. unsafe { - let _ = Box::from_raw(handle as *mut DeadlineMonitorBuilder); + let _ = Box::from_raw(deadline_monitor_builder_handle as *mut DeadlineMonitorBuilder); } + + FFICode::Success } #[no_mangle] -pub extern "C" fn deadline_monitor_builder_add_deadline(handle: FFIHandle, tag: *const IdentTag, min: u32, max: u32) { - assert!(!handle.is_null()); - assert!(!tag.is_null()); +pub extern "C" fn deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle: FFIHandle, + deadline_tag: *const IdentTag, + min_ms: u32, + max_ms: u32, +) -> FFICode { + if deadline_monitor_builder_handle.is_null() || deadline_tag.is_null() { + return FFICode::NullParameter; + } + + if min_ms > max_ms { + return FFICode::InvalidArgument; + } - // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust - let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + // SAFETY: + // Validity of the pointer is ensured. + // `IdentTag` type must be compatible between C++ and Rust. + let deadline_tag = unsafe { *deadline_tag }; - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` - // and this must be assured on other side of FFI. - let mut monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut DeadlineMonitorBuilder) }); + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `deadline_monitor_builder_create`. + // It is assumed that the pointer was not consumed by a call to `deadline_monitor_builder_destroy`. + let mut deadline_monitor_builder = + FFIBorrowed::new(unsafe { Box::from_raw(deadline_monitor_builder_handle as *mut DeadlineMonitorBuilder) }); - monitor.add_deadline_internal( - &tag, - TimeRange::new(Duration::from_millis(min as u64), Duration::from_millis(max as u64)), + deadline_monitor_builder.add_deadline_internal( + &deadline_tag, + TimeRange::new( + Duration::from_millis(min_ms as u64), + Duration::from_millis(max_ms as u64), + ), ); + + FFICode::Success } #[no_mangle] -pub extern "C" fn deadline_monitor_cpp_destroy(handle: FFIHandle) { - assert!(!handle.is_null()); - - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` - // and this must be assured on other side of FFI. - unsafe { - let _ = Box::from_raw(handle as *mut DeadlineMonitorCpp); +pub extern "C" fn deadline_monitor_get_deadline( + deadline_monitor_handle: FFIHandle, + deadline_tag: *const IdentTag, + deadline_handle_out: *mut FFIHandle, +) -> FFICode { + if deadline_monitor_handle.is_null() || deadline_tag.is_null() || deadline_handle_out.is_null() { + return FFICode::NullParameter; } -} -#[no_mangle] -pub extern "C" fn deadline_monitor_cpp_get_deadline( - handle: FFIHandle, - tag: *const IdentTag, - out: *mut FFIHandle, -) -> c_int { - assert!(!handle.is_null()); - assert!(!tag.is_null()); - assert!(!out.is_null()); - - // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust - let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable - - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` - // and this must be assured on other side of FFI. - let monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut DeadlineMonitorCpp) }); - let deadline_handle = monitor.get_deadline(tag); - - deadline_handle.map_or_else( - |err_code| err_code, - |handle| { + // SAFETY: + // Validity of the pointer is ensured. + // `IdentTag` type must be compatible between C++ and Rust. + let deadline_tag = unsafe { *deadline_tag }; + + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_get_deadline_monitor`. + // It is assumed that the pointer was not consumed by a call to `deadline_monitor_destroy`. + let deadline_monitor = + FFIBorrowed::new(unsafe { Box::from_raw(deadline_monitor_handle as *mut DeadlineMonitorCpp) }); + + match deadline_monitor.get_deadline(deadline_tag) { + Ok(handle) => { unsafe { - *out = handle; + *deadline_handle_out = handle; } - HM_OK + FFICode::Success }, - ) + Err(e) => e, + } } #[no_mangle] -pub extern "C" fn deadline_start(handle: FFIHandle) -> c_int { - assert!(!handle.is_null()); +pub extern "C" fn deadline_monitor_destroy(deadline_monitor_handle: FFIHandle) -> FFICode { + if deadline_monitor_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_get_deadline_monitor`. + unsafe { + let _ = Box::from_raw(deadline_monitor_handle as *mut DeadlineMonitorCpp); + } + + FFICode::Success +} + +#[no_mangle] +pub extern "C" fn deadline_start(deadline_handle: FFIHandle) -> FFICode { + if deadline_handle.is_null() { + return FFICode::NullParameter; + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_cpp_get_deadline` - // and this must be assured on other side of FFI. - let mut deadline = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut Deadline) }); + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `deadline_monitor_get_deadline`. + // It is assumed that the pointer was not consumed by a call to `deadline_destroy`. + let mut deadline = FFIBorrowed::new(unsafe { Box::from_raw(deadline_handle as *mut Deadline) }); - // Safety: We ensure at CPP side that a Deadline has move only semantic to not end up in multiple owners of same deadline. - // We also check during start call that previous start/stop sequence was done correctly. + // SAFETY: `Deadline` has move-only semantic, as multiple owners are not allowed. match unsafe { deadline.start_internal() } { - Ok(()) => HM_OK, - Err(_err) => HM_FAILED, + Ok(()) => FFICode::Success, + Err(_err) => FFICode::Failed, } } #[no_mangle] -pub extern "C" fn deadline_stop(handle: FFIHandle) { - assert!(!handle.is_null()); +pub extern "C" fn deadline_stop(deadline_handle: FFIHandle) -> FFICode { + if deadline_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `deadline_monitor_get_deadline`. + // It is assumed that the pointer was not consumed by a call to `deadline_destroy`. + let mut deadline = FFIBorrowed::new(unsafe { Box::from_raw(deadline_handle as *mut Deadline) }); - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_cpp_get_deadline` - // and this must be assured on other side of FFI. - let mut deadline = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut Deadline) }); deadline.stop_internal(); + + FFICode::Success } #[no_mangle] -pub extern "C" fn deadline_destroy(handle: FFIHandle) { - assert!(!handle.is_null()); +pub extern "C" fn deadline_destroy(deadline_handle: FFIHandle) -> FFICode { + if deadline_handle.is_null() { + return FFICode::NullParameter; + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_cpp_get_deadline` - // and this must be assured on other side of FFI. + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `deadline_monitor_get_deadline`. unsafe { - let _ = Box::from_raw(handle as *mut Deadline); + let _ = Box::from_raw(deadline_handle as *mut Deadline); + } + + FFICode::Success +} + +#[score_testing_macros::test_mod_with_log] +#[cfg(test)] +mod tests { + use crate::deadline::ffi::{ + deadline_destroy, deadline_monitor_builder_add_deadline, deadline_monitor_builder_create, + deadline_monitor_builder_destroy, deadline_monitor_destroy, deadline_monitor_get_deadline, deadline_start, + deadline_stop, + }; + use crate::ffi::{ + health_monitor_builder_add_deadline_monitor, health_monitor_builder_build, health_monitor_builder_create, + health_monitor_destroy, health_monitor_get_deadline_monitor, FFICode, FFIHandle, + }; + use crate::IdentTag; + use core::ptr::null_mut; + + #[test] + fn deadline_monitor_builder_create_succeeds() { + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let deadline_monitor_builder_create_result = + deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + assert!(!deadline_monitor_builder_handle.is_null()); + assert_eq!(deadline_monitor_builder_create_result, FFICode::Success); + + // Clean-up. + // NOTE: `deadline_monitor_builder_destroy` positive path is already tested here. + let deadline_monitor_builder_destroy_result = deadline_monitor_builder_destroy(deadline_monitor_builder_handle); + assert_eq!(deadline_monitor_builder_destroy_result, FFICode::Success); + } + + #[test] + fn deadline_monitor_builder_create_null_builder() { + let deadline_monitor_builder_create_result = deadline_monitor_builder_create(null_mut()); + assert_eq!(deadline_monitor_builder_create_result, FFICode::NullParameter); + } + + #[test] + fn deadline_monitor_builder_destroy_null_builder() { + let deadline_monitor_builder_destroy_result = deadline_monitor_builder_destroy(null_mut()); + assert_eq!(deadline_monitor_builder_destroy_result, FFICode::NullParameter); + } + + #[test] + fn deadline_monitor_builder_add_deadline_succeeds() { + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let deadline_tag = IdentTag::new("deadline_1"); + + let deadline_monitor_builder_add_deadline_result = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + assert_eq!(deadline_monitor_builder_add_deadline_result, FFICode::Success); + + // Clean-up. + deadline_monitor_builder_destroy(deadline_monitor_builder_handle); + } + + #[test] + fn deadline_monitor_builder_add_deadline_invalid_range() { + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let deadline_tag = IdentTag::new("deadline_1"); + + let deadline_monitor_builder_add_deadline_result = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 10000, + 100, + ); + assert_eq!(deadline_monitor_builder_add_deadline_result, FFICode::InvalidArgument); + + // Clean-up. + deadline_monitor_builder_destroy(deadline_monitor_builder_handle); + } + + #[test] + fn deadline_monitor_builder_add_deadline_null_builder() { + let deadline_tag = IdentTag::new("deadline_1"); + + let deadline_monitor_builder_add_deadline_result = + deadline_monitor_builder_add_deadline(null_mut(), &deadline_tag as *const IdentTag, 100, 200); + assert_eq!(deadline_monitor_builder_add_deadline_result, FFICode::NullParameter); + } + + #[test] + fn deadline_monitor_builder_add_deadline_null_deadline_tag() { + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + + let deadline_monitor_builder_add_deadline_result = + deadline_monitor_builder_add_deadline(deadline_monitor_builder_handle, null_mut(), 100, 200); + assert_eq!(deadline_monitor_builder_add_deadline_result, FFICode::NullParameter); + + // Clean-up. + deadline_monitor_builder_destroy(deadline_monitor_builder_handle); + } + + #[test] + fn deadline_monitor_get_deadline_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let deadline_tag = IdentTag::new("deadline_1"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + + let deadline_monitor_get_deadline_result = deadline_monitor_get_deadline( + deadline_monitor_handle, + &deadline_tag as *const IdentTag, + &mut deadline_handle as *mut FFIHandle, + ); + assert!(!deadline_handle.is_null()); + assert_eq!(deadline_monitor_get_deadline_result, FFICode::Success); + + // Clean-up. + deadline_destroy(deadline_handle); + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_monitor_get_deadline_unknown_deadline() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let deadline_tag = IdentTag::new("deadline_1"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + + let unknown_deadline_tag = IdentTag::new("deadline_2"); + let deadline_monitor_get_deadline_result = deadline_monitor_get_deadline( + deadline_monitor_handle, + &unknown_deadline_tag as *const IdentTag, + &mut deadline_handle as *mut FFIHandle, + ); + assert!(deadline_handle.is_null()); + assert_eq!(deadline_monitor_get_deadline_result, FFICode::NotFound); + + // Clean-up. + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_monitor_get_deadline_null_monitor() { + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_tag = IdentTag::new("deadline_1"); + + let deadline_monitor_get_deadline_result = deadline_monitor_get_deadline( + null_mut(), + &deadline_tag as *const IdentTag, + &mut deadline_handle as *mut FFIHandle, + ); + assert_eq!(deadline_monitor_get_deadline_result, FFICode::NullParameter); + } + + #[test] + fn deadline_monitor_get_deadline_null_deadline_tag() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + + let deadline_monitor_get_deadline_result = deadline_monitor_get_deadline( + deadline_monitor_handle, + null_mut(), + &mut deadline_handle as *mut FFIHandle, + ); + assert_eq!(deadline_monitor_get_deadline_result, FFICode::NullParameter); + + // Clean-up. + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_monitor_get_deadline_null_deadline_handle() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let deadline_tag = IdentTag::new("deadline_1"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + + let deadline_monitor_get_deadline_result = + deadline_monitor_get_deadline(deadline_monitor_handle, &deadline_tag as *const IdentTag, null_mut()); + assert_eq!(deadline_monitor_get_deadline_result, FFICode::NullParameter); + + // Clean-up. + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_monitor_destroy_null_monitor() { + let deadline_monitor_destroy_result = deadline_monitor_destroy(null_mut()); + assert_eq!(deadline_monitor_destroy_result, FFICode::NullParameter); + } + + #[test] + fn deadline_start_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let deadline_tag = IdentTag::new("deadline_1"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + let _ = deadline_monitor_get_deadline( + deadline_monitor_handle, + &deadline_tag as *const IdentTag, + &mut deadline_handle as *mut FFIHandle, + ); + + let deadline_start_result = deadline_start(deadline_handle); + assert_eq!(deadline_start_result, FFICode::Success); + + // Clean-up. + deadline_destroy(deadline_handle); + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_start_already_started() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let deadline_tag = IdentTag::new("deadline_1"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + let _ = deadline_monitor_get_deadline( + deadline_monitor_handle, + &deadline_tag as *const IdentTag, + &mut deadline_handle as *mut FFIHandle, + ); + + let _ = deadline_start(deadline_handle); + let deadline_start_result = deadline_start(deadline_handle); + assert_eq!(deadline_start_result, FFICode::Failed); + + // Clean-up. + deadline_destroy(deadline_handle); + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_start_null_deadline() { + let deadline_start_result = deadline_start(null_mut()); + assert_eq!(deadline_start_result, FFICode::NullParameter); + } + + #[test] + fn deadline_stop_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + let mut deadline_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let deadline_tag = IdentTag::new("deadline_1"); + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_add_deadline( + deadline_monitor_builder_handle, + &deadline_tag as *const IdentTag, + 100, + 200, + ); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + let _ = deadline_monitor_get_deadline( + deadline_monitor_handle, + &deadline_tag as *const IdentTag, + &mut deadline_handle as *mut FFIHandle, + ); + let _ = deadline_start(deadline_handle); + + let deadline_stop_result = deadline_stop(deadline_handle); + assert_eq!(deadline_stop_result, FFICode::Success); + + // Clean-up. + deadline_destroy(deadline_handle); + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn deadline_stop_null_deadline() { + let deadline_stop_result = deadline_stop(null_mut()); + assert_eq!(deadline_stop_result, FFICode::NullParameter); + } + + #[test] + fn deadline_destroy_null_deadline() { + let deadline_destroy_result = deadline_destroy(null_mut()); + assert_eq!(deadline_destroy_result, FFICode::NullParameter); } } diff --git a/src/health_monitoring_lib/rust/ffi.rs b/src/health_monitoring_lib/rust/ffi.rs index f094ce67..37a8f679 100644 --- a/src/health_monitoring_lib/rust/ffi.rs +++ b/src/health_monitoring_lib/rust/ffi.rs @@ -10,197 +10,700 @@ // // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use crate::common::ffi::*; use crate::deadline::ffi::DeadlineMonitorCpp; -use crate::*; +use crate::deadline::DeadlineMonitorBuilder; +use crate::IdentTag; +use crate::{HealthMonitor, HealthMonitorBuilder}; +use core::mem::ManuallyDrop; +use core::ops::{Deref, DerefMut}; use core::time::Duration; +use score_log::ScoreDebug; + +pub type FFIHandle = *mut core::ffi::c_void; + +/// FFI return codes. +/// Must be aligned with `score::hm::Error` with additional success value. +#[repr(u8)] +#[allow(dead_code)] +#[derive(PartialEq, Eq, Debug, ScoreDebug)] +pub enum FFICode { + Success = 0, + NullParameter, + NotFound, + AlreadyExists, + InvalidArgument, + WrongState, + Failed, +} -#[no_mangle] -extern "C" fn health_monitor_builder_create() -> FFIHandle { - let builder = HealthMonitorBuilder::new(); - let handle = Box::into_raw(Box::new(builder)); - handle as FFIHandle +/// A wrapper to represent borrowed data over FFI boundary without taking ownership. +pub struct FFIBorrowed { + data: ManuallyDrop, } -#[no_mangle] -extern "C" fn health_monitor_builder_destroy(handle: FFIHandle) { - assert!(!handle.is_null()); - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` - // and this must be assured on other side of FFI. - unsafe { - let _ = Box::from_raw(handle as *mut HealthMonitorBuilder); +impl FFIBorrowed { + pub fn new(data: T) -> Self { + Self { + data: ManuallyDrop::new(data), + } + } +} + +impl Deref for FFIBorrowed { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for FFIBorrowed { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data } } #[no_mangle] -extern "C" fn health_monitor_builder_add_deadline_monitor(handle: FFIHandle, tag: *const IdentTag, monitor: FFIHandle) { - assert!(!handle.is_null()); - assert!(!tag.is_null()); - assert!(!monitor.is_null()); +pub extern "C" fn health_monitor_builder_create(health_monitor_builder_handle_out: *mut FFIHandle) -> FFICode { + if health_monitor_builder_handle_out.is_null() { + return FFICode::NullParameter; + } - // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust - let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + let health_monitor_builder = HealthMonitorBuilder::new(); + unsafe { + *health_monitor_builder_handle_out = Box::into_raw(Box::new(health_monitor_builder)).cast(); + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `deadline_monitor_builder_create` - let monitor = unsafe { Box::from_raw(monitor as *mut deadline::DeadlineMonitorBuilder) }; + FFICode::Success +} - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` - // and this must be assured on other side of FFI. - let mut health_monitor_builder = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut HealthMonitorBuilder) }); +#[no_mangle] +pub extern "C" fn health_monitor_builder_destroy(health_monitor_builder_handle: FFIHandle) -> FFICode { + if health_monitor_builder_handle.is_null() { + return FFICode::NullParameter; + } - health_monitor_builder.add_deadline_monitor_internal(&tag, *monitor); + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_create`. + // It is assumed that the pointer was not consumed by a call to `health_monitor_builder_build`. + unsafe { + let _ = Box::from_raw(health_monitor_builder_handle as *mut HealthMonitorBuilder); + } + + FFICode::Success } #[no_mangle] -extern "C" fn health_monitor_builder_build( - handle: FFIHandle, +pub extern "C" fn health_monitor_builder_build( + health_monitor_builder_handle: FFIHandle, supervisor_cycle_ms: u32, internal_cycle_ms: u32, -) -> FFIHandle { - assert!(!handle.is_null()); + health_monitor_handle_out: *mut FFIHandle, +) -> FFICode { + if health_monitor_builder_handle.is_null() || health_monitor_handle_out.is_null() { + return FFICode::NullParameter; + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` - // and this must be assured on other side of FFI. - let mut health_monitor_builder: Box = - unsafe { Box::from_raw(handle as *mut HealthMonitorBuilder) }; + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_create`. + // It is assumed that the pointer was not consumed by a call to `health_monitor_builder_destroy`. + let mut health_monitor_builder = + unsafe { Box::from_raw(health_monitor_builder_handle as *mut HealthMonitorBuilder) }; health_monitor_builder.with_internal_processing_cycle_internal(Duration::from_millis(internal_cycle_ms as u64)); health_monitor_builder.with_supervisor_api_cycle_internal(Duration::from_millis(supervisor_cycle_ms as u64)); - let health_monitor = health_monitor_builder.build(); - let health_monitor_handle = Box::into_raw(Box::new(health_monitor)); - health_monitor_handle as FFIHandle + // Check cycle interval args. + if !health_monitor_builder.check_cycle_args_internal() { + return FFICode::InvalidArgument; + } + + // Build instance. + let health_monitor = health_monitor_builder.build_internal(); + unsafe { + *health_monitor_handle_out = Box::into_raw(Box::new(health_monitor)).cast(); + } + + FFICode::Success } #[no_mangle] -extern "C" fn health_monitor_get_deadline_monitor(handle: FFIHandle, tag: *const IdentTag) -> FFIHandle { - assert!(!handle.is_null()); - assert!(!tag.is_null()); +pub extern "C" fn health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle: FFIHandle, + monitor_tag: *const IdentTag, + deadline_monitor_builder_handle: FFIHandle, +) -> FFICode { + if health_monitor_builder_handle.is_null() || monitor_tag.is_null() || deadline_monitor_builder_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // `IdentTag` type must be compatible between C++ and Rust. + let monitor_tag = unsafe { *monitor_tag }; + + // SAFETY: + // Validity of this pointer is ensured. + // It is assumed that the pointer was created by a call to `deadline_monitor_builder_create`. + // It is assumed that the pointer was not consumed by a call to `deadline_monitor_builder_destroy`. + let deadline_monitor_builder = + unsafe { Box::from_raw(deadline_monitor_builder_handle as *mut DeadlineMonitorBuilder) }; - // Safety: We ensure that the pointer is valid. `tag` ptr must be FFI data compatible with IdentTag in Rust - let tag: IdentTag = unsafe { *tag }; // Copy the IdentTag as this shall be trivially copyable + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_create`. + // It is assumed that the pointer was not consumed by calls to `health_monitor_builder_destroy` or `health_monitor_builder_build`. + let mut health_monitor_builder = + FFIBorrowed::new(unsafe { Box::from_raw(health_monitor_builder_handle as *mut HealthMonitorBuilder) }); - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_create` - // and this must be assured on other side of FFI. - let mut health_monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut HealthMonitor) }); + health_monitor_builder.add_deadline_monitor_internal(&monitor_tag, *deadline_monitor_builder); - if let Some(deadline_monitor) = health_monitor.get_deadline_monitor(&tag) { - let deadline_monitor_handle = Box::into_raw(Box::new(DeadlineMonitorCpp::new(deadline_monitor))); + FFICode::Success +} + +#[no_mangle] +pub extern "C" fn health_monitor_get_deadline_monitor( + health_monitor_handle: FFIHandle, + monitor_tag: *const IdentTag, + deadline_monitor_handle_out: *mut FFIHandle, +) -> FFICode { + if health_monitor_handle.is_null() || monitor_tag.is_null() || deadline_monitor_handle_out.is_null() { + return FFICode::NullParameter; + } - deadline_monitor_handle as FFIHandle + // SAFETY: + // Validity of the pointer is ensured. + // `IdentTag` type must be compatible between C++ and Rust. + let monitor_tag = unsafe { *monitor_tag }; + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_build`. + // It is assumed that the pointer was not consumed by a call to `health_monitor_destroy`. + let mut health_monitor = FFIBorrowed::new(unsafe { Box::from_raw(health_monitor_handle as *mut HealthMonitor) }); + + if let Some(deadline_monitor) = health_monitor.get_deadline_monitor(&monitor_tag) { + unsafe { + *deadline_monitor_handle_out = Box::into_raw(Box::new(DeadlineMonitorCpp::new(deadline_monitor))).cast(); + } + FFICode::Success } else { - core::ptr::null_mut() + FFICode::NotFound } } #[no_mangle] -extern "C" fn health_monitor_start(handle: FFIHandle) { - assert!(!handle.is_null()); +pub extern "C" fn health_monitor_start(health_monitor_handle: FFIHandle) -> FFICode { + if health_monitor_handle.is_null() { + return FFICode::NullParameter; + } + + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_build`. + // It is assumed that the pointer was not consumed by a call to `health_monitor_destroy`. + let mut health_monitor = FFIBorrowed::new(unsafe { Box::from_raw(health_monitor_handle as *mut HealthMonitor) }); + + // Check state, collect monitors and start. + if !health_monitor.check_monitors_exist_internal() { + return FFICode::WrongState; + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_build` - // and this must be assured on other side of FFI. - let mut monitor = FFIBorrowed::new(unsafe { Box::from_raw(handle as *mut HealthMonitor) }); - monitor.start(); + let monitors = match health_monitor.collect_monitors_internal() { + Ok(m) => m, + Err(_) => return FFICode::WrongState, + }; + + health_monitor.start_internal(monitors); + + FFICode::Success } #[no_mangle] -extern "C" fn health_monitor_destroy(handle: FFIHandle) { - assert!(!handle.is_null()); +pub extern "C" fn health_monitor_destroy(health_monitor_handle: FFIHandle) -> FFICode { + if health_monitor_handle.is_null() { + return FFICode::NullParameter; + } - // Safety: We ensure that the pointer is valid. We assume that pointer was created by call to `health_monitor_builder_build` - // and this must be assured on other side of FFI. + // SAFETY: + // Validity of the pointer is ensured. + // It is assumed that the pointer was created by a call to `health_monitor_builder_build`. unsafe { - let _ = Box::from_raw(handle as *mut HealthMonitor); + let _ = Box::from_raw(health_monitor_handle as *mut HealthMonitor); } + + FFICode::Success } +#[score_testing_macros::test_mod_with_log] #[cfg(test)] mod tests { - use crate::deadline::ffi::{deadline_monitor_builder_create, deadline_monitor_cpp_destroy}; + use crate::deadline::ffi::{ + deadline_monitor_builder_create, deadline_monitor_builder_destroy, deadline_monitor_destroy, + }; use crate::ffi::{ health_monitor_builder_add_deadline_monitor, health_monitor_builder_build, health_monitor_builder_create, health_monitor_builder_destroy, health_monitor_destroy, health_monitor_get_deadline_monitor, - health_monitor_start, + health_monitor_start, FFICode, FFIHandle, }; use crate::IdentTag; + use core::ptr::null_mut; #[test] - fn health_monitor_builder_create_ok() { - let health_monitor_builder_handle = health_monitor_builder_create(); + fn health_monitor_builder_create_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + + let health_monitor_builder_create_result = + health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); assert!(!health_monitor_builder_handle.is_null()); + assert_eq!(health_monitor_builder_create_result, FFICode::Success); // Clean-up. // NOTE: `health_monitor_builder_destroy` positive path is already tested here. + let health_monitor_builder_destroy_result = health_monitor_builder_destroy(health_monitor_builder_handle); + assert_eq!(health_monitor_builder_destroy_result, FFICode::Success); + } + + #[test] + fn health_monitor_builder_create_null_handle() { + let health_monitor_builder_create_result = health_monitor_builder_create(null_mut()); + assert_eq!(health_monitor_builder_create_result, FFICode::NullParameter); + } + + #[test] + fn health_monitor_builder_destroy_null_handle() { + let health_monitor_builder_destroy_result = health_monitor_builder_destroy(null_mut()); + assert_eq!(health_monitor_builder_destroy_result, FFICode::NullParameter); + } + + #[test] + fn health_monitor_builder_build_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_build_result = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + assert!(!health_monitor_handle.is_null()); + assert_eq!(health_monitor_builder_build_result, FFICode::Success); + + // Clean-up. + // NOTE: `health_monitor_destroy` positive path is already tested here. + let health_monitor_destroy_result = health_monitor_destroy(health_monitor_handle); + assert_eq!(health_monitor_destroy_result, FFICode::Success); + } + + #[test] + fn health_monitor_builder_build_invalid_cycle_intervals() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_build_result = health_monitor_builder_build( + health_monitor_builder_handle, + 123, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + assert!(health_monitor_handle.is_null()); + assert_eq!(health_monitor_builder_build_result, FFICode::InvalidArgument); + + // Clean-up not needed - health monitor builder was already consumed by the `build`. + } + + #[test] + fn health_monitor_builder_build_null_builder_handle() { + let mut health_monitor_handle: FFIHandle = null_mut(); + + let health_monitor_builder_build_result = + health_monitor_builder_build(null_mut(), 200, 100, &mut health_monitor_handle as *mut FFIHandle); + assert!(health_monitor_handle.is_null()); + assert_eq!(health_monitor_builder_build_result, FFICode::NullParameter); + } + + #[test] + fn health_monitor_builder_build_null_monitor_handle() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_build_result = + health_monitor_builder_build(health_monitor_builder_handle, 200, 100, null_mut()); + assert_eq!(health_monitor_builder_build_result, FFICode::NullParameter); + + // Clean-up. health_monitor_builder_destroy(health_monitor_builder_handle); } #[test] - fn health_monitor_builder_add_deadline_monitor_ok() { - let health_monitor_builder_handle = health_monitor_builder_create(); + fn health_monitor_builder_add_deadline_monitor_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); - let deadline_monitor_builder_handle = deadline_monitor_builder_create(); - health_monitor_builder_add_deadline_monitor( + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_add_deadline_monitor_result = health_monitor_builder_add_deadline_monitor( health_monitor_builder_handle, &deadline_monitor_tag as *const IdentTag, deadline_monitor_builder_handle, ); + assert!(!deadline_monitor_builder_handle.is_null()); + assert_eq!(health_monitor_builder_add_deadline_monitor_result, FFICode::Success); // Clean-up. health_monitor_builder_destroy(health_monitor_builder_handle); } #[test] - fn health_monitor_builder_build_ok() { - let health_monitor_builder_handle = health_monitor_builder_create(); - let health_monitor_handle = health_monitor_builder_build(health_monitor_builder_handle, 200, 100); - assert!(!health_monitor_handle.is_null()); + fn health_monitor_builder_add_deadline_monitor_null_hmon_builder() { + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_add_deadline_monitor_result = health_monitor_builder_add_deadline_monitor( + null_mut(), + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + assert_eq!( + health_monitor_builder_add_deadline_monitor_result, + FFICode::NullParameter + ); // Clean-up. - // NOTE: `health_monitor_destroy` positive path is already tested here. - health_monitor_destroy(health_monitor_handle); + deadline_monitor_builder_destroy(deadline_monitor_builder_handle); + } + + #[test] + fn health_monitor_builder_add_deadline_monitor_null_monitor_tag() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + + let health_monitor_builder_add_deadline_monitor_result = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + null_mut(), + deadline_monitor_builder_handle, + ); + assert_eq!( + health_monitor_builder_add_deadline_monitor_result, + FFICode::NullParameter + ); + + // Clean-up. + deadline_monitor_builder_destroy(deadline_monitor_builder_handle); + health_monitor_builder_destroy(health_monitor_builder_handle); } #[test] - fn health_monitor_get_deadline_monitor_ok() { - let health_monitor_builder_handle = health_monitor_builder_create(); + fn health_monitor_builder_add_deadline_monitor_null_deadline_monitor_builder() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); let deadline_monitor_tag = IdentTag::new("deadline_monitor"); - let deadline_monitor_builder_handle = deadline_monitor_builder_create(); - health_monitor_builder_add_deadline_monitor( + + let health_monitor_builder_add_deadline_monitor_result = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + null_mut(), + ); + assert_eq!( + health_monitor_builder_add_deadline_monitor_result, + FFICode::NullParameter + ); + + // Clean-up. + health_monitor_builder_destroy(health_monitor_builder_handle); + } + + #[test] + fn health_monitor_get_deadline_monitor_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( health_monitor_builder_handle, &deadline_monitor_tag as *const IdentTag, deadline_monitor_builder_handle, ); - let health_monitor_handle = health_monitor_builder_build(health_monitor_builder_handle, 200, 100); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); - let deadline_monitor_handle = - health_monitor_get_deadline_monitor(health_monitor_handle, &deadline_monitor_tag as *const IdentTag); + let health_monitor_get_deadline_monitor_result = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); assert!(!deadline_monitor_handle.is_null()); + assert_eq!(health_monitor_get_deadline_monitor_result, FFICode::Success); // Clean-up. - deadline_monitor_cpp_destroy(deadline_monitor_handle); + deadline_monitor_destroy(deadline_monitor_handle); health_monitor_destroy(health_monitor_handle); } #[test] - fn health_monitor_start_ok() { - let health_monitor_builder_handle = health_monitor_builder_create(); + fn health_monitor_get_deadline_monitor_already_taken() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_1_handle: FFIHandle = null_mut(); + let mut deadline_monitor_2_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); let deadline_monitor_tag = IdentTag::new("deadline_monitor"); - let deadline_monitor_builder_handle = deadline_monitor_builder_create(); - health_monitor_builder_add_deadline_monitor( + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( health_monitor_builder_handle, &deadline_monitor_tag as *const IdentTag, deadline_monitor_builder_handle, ); - let health_monitor_handle = health_monitor_builder_build(health_monitor_builder_handle, 200, 100); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); - let deadline_monitor_handle = - health_monitor_get_deadline_monitor(health_monitor_handle, &deadline_monitor_tag as *const IdentTag); - assert!(!deadline_monitor_handle.is_null()); + // First get. + let health_monitor_get_deadline_monitor_result_1 = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_1_handle as *mut FFIHandle, + ); + assert!(!deadline_monitor_1_handle.is_null()); + assert_eq!(health_monitor_get_deadline_monitor_result_1, FFICode::Success); + + // Second get. + let health_monitor_get_deadline_monitor_result_2 = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_2_handle as *mut FFIHandle, + ); + assert!(deadline_monitor_2_handle.is_null()); + assert_eq!(health_monitor_get_deadline_monitor_result_2, FFICode::NotFound); + + // Clean-up. + deadline_monitor_destroy(deadline_monitor_1_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_deadline_monitor_null_hmon() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_get_deadline_monitor_result = health_monitor_get_deadline_monitor( + null_mut(), + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + assert!(deadline_monitor_handle.is_null()); + assert_eq!(health_monitor_get_deadline_monitor_result, FFICode::NullParameter); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_deadline_monitor_null_monitor_tag() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_get_deadline_monitor_result = health_monitor_get_deadline_monitor( + health_monitor_handle, + null_mut(), + &mut deadline_monitor_handle as *mut FFIHandle, + ); + assert!(deadline_monitor_handle.is_null()); + assert_eq!(health_monitor_get_deadline_monitor_result, FFICode::NullParameter); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_get_deadline_monitor_null_deadline_monitor() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); - health_monitor_start(health_monitor_handle); + let health_monitor_get_deadline_monitor_result = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + null_mut(), + ); + assert_eq!(health_monitor_get_deadline_monitor_result, FFICode::NullParameter); // Clean-up. - deadline_monitor_cpp_destroy(deadline_monitor_handle); health_monitor_destroy(health_monitor_handle); } + + #[test] + fn health_monitor_start_succeeds() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + let mut deadline_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let _ = health_monitor_get_deadline_monitor( + health_monitor_handle, + &deadline_monitor_tag as *const IdentTag, + &mut deadline_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_start_result = health_monitor_start(health_monitor_handle); + assert_eq!(health_monitor_start_result, FFICode::Success); + + // Clean-up. + deadline_monitor_destroy(deadline_monitor_handle); + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_start_monitor_not_taken() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + let mut deadline_monitor_builder_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let deadline_monitor_tag = IdentTag::new("deadline_monitor"); + let _ = deadline_monitor_builder_create(&mut deadline_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_add_deadline_monitor( + health_monitor_builder_handle, + &deadline_monitor_tag as *const IdentTag, + deadline_monitor_builder_handle, + ); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_start_result = health_monitor_start(health_monitor_handle); + assert_eq!(health_monitor_start_result, FFICode::WrongState); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_start_no_monitors() { + let mut health_monitor_builder_handle: FFIHandle = null_mut(); + let mut health_monitor_handle: FFIHandle = null_mut(); + + let _ = health_monitor_builder_create(&mut health_monitor_builder_handle as *mut FFIHandle); + let _ = health_monitor_builder_build( + health_monitor_builder_handle, + 200, + 100, + &mut health_monitor_handle as *mut FFIHandle, + ); + + let health_monitor_start_result = health_monitor_start(health_monitor_handle); + assert_eq!(health_monitor_start_result, FFICode::WrongState); + + // Clean-up. + health_monitor_destroy(health_monitor_handle); + } + + #[test] + fn health_monitor_start_null_hmon() { + let health_monitor_start_result = health_monitor_start(null_mut()); + assert_eq!(health_monitor_start_result, FFICode::NullParameter); + } + + #[test] + fn health_monitor_destroy_null_hmon() { + let health_monitor_destroy_result = health_monitor_destroy(null_mut()); + assert_eq!(health_monitor_destroy_result, FFICode::NullParameter); + } } diff --git a/src/health_monitoring_lib/rust/lib.rs b/src/health_monitoring_lib/rust/lib.rs index 3d91682c..81d6f51b 100644 --- a/src/health_monitoring_lib/rust/lib.rs +++ b/src/health_monitoring_lib/rust/lib.rs @@ -20,7 +20,9 @@ mod protected_memory; mod worker; pub mod deadline; +use crate::common::MonitorEvalHandle; pub use common::{IdentTag, TimeRange}; +use containers::fixed_capacity::FixedCapacityVec; #[derive(Default)] pub struct HealthMonitorBuilder { @@ -30,6 +32,7 @@ pub struct HealthMonitorBuilder { } impl HealthMonitorBuilder { + /// Create a new [`HealthMonitorBuilder`] instance. pub fn new() -> Self { Self { deadlines: HashMap::new(), @@ -63,25 +66,14 @@ impl HealthMonitorBuilder { self } - /// Builds the HealthMonitor instance. + /// Build a new [`HealthMonitor`] instance based on provided parameters. pub fn build(self) -> HealthMonitor { assert!( - self.supervisor_api_cycle - .as_millis() - .is_multiple_of(self.internal_processing_cycle.as_millis()), + self.check_cycle_args_internal(), "supervisor API cycle must be multiple of internal processing cycle" ); - let allocator = protected_memory::ProtectedMemoryAllocator {}; - let mut monitors = HashMap::new(); - for (tag, builder) in self.deadlines { - monitors.insert(tag, Some(DeadlineMonitorState::Available(builder.build(&allocator)))); - } - HealthMonitor { - deadline_monitors: monitors, - worker: worker::UniqueThreadRunner::new(self.internal_processing_cycle), - supervisor_api_cycle: self.supervisor_api_cycle, - } + self.build_internal() } // Used by FFI and config parsing code which prefer not to move builder instance @@ -97,6 +89,25 @@ impl HealthMonitorBuilder { pub(crate) fn with_internal_processing_cycle_internal(&mut self, cycle_duration: core::time::Duration) { self.internal_processing_cycle = cycle_duration; } + + pub(crate) fn check_cycle_args_internal(&self) -> bool { + self.supervisor_api_cycle + .as_millis() + .is_multiple_of(self.internal_processing_cycle.as_millis()) + } + + pub(crate) fn build_internal(self) -> HealthMonitor { + let allocator = protected_memory::ProtectedMemoryAllocator {}; + let mut monitors = HashMap::new(); + for (tag, builder) in self.deadlines { + monitors.insert(tag, Some(DeadlineMonitorState::Available(builder.build(&allocator)))); + } + HealthMonitor { + deadline_monitors: monitors, + worker: worker::UniqueThreadRunner::new(self.internal_processing_cycle), + supervisor_api_cycle: self.supervisor_api_cycle, + } + } } enum DeadlineMonitorState { @@ -135,43 +146,38 @@ impl HealthMonitor { } } - /// Starts the health monitoring logic in a separate thread. - /// - /// From this point, the health monitor will periodically check monitors and notify the supervisor about system liveness. - /// - /// # Note - /// - This function shall be called before Lifecycle.running() otherwise the supervisor might consider the process not alive. - /// - Stops when the HealthMonitor instance is dropped. - /// - /// Panics if no monitors have been added. - pub fn start(&mut self) { - assert!( - !self.deadline_monitors.is_empty(), - "No deadline monitors have been added. HealthMonitor cannot start without any monitors." - ); + pub(crate) fn check_monitors_exist_internal(&self) -> bool { + !self.deadline_monitors.is_empty() + } - let mut monitors = containers::fixed_capacity::FixedCapacityVec::new(self.deadline_monitors.len()); + pub(crate) fn collect_monitors_internal(&mut self) -> Result, String> { + let mut monitors = FixedCapacityVec::new(self.deadline_monitors.len()); for (tag, monitor) in self.deadline_monitors.iter_mut() { match monitor.take() { Some(DeadlineMonitorState::Taken(handle)) => { - monitors.push(handle).expect("Failed to push monitor handle"); - // Should not fail since we preallocated enough capacity + if monitors.push(handle).is_err() { + // Should not fail since we preallocated enough capacity + return Err("Failed to push monitor handle".to_string()); + } }, Some(DeadlineMonitorState::Available(_)) => { - panic!( + return Err(format!( "All monitors must be taken before starting HealthMonitor but {:?} is not taken.", tag - ); + )); }, None => { - panic!( + return Err(format!( "Invalid monitor ({:?}) state encountered while starting HealthMonitor.", tag - ); + )); }, } } + Ok(monitors) + } + pub(crate) fn start_internal(&mut self, monitors: FixedCapacityVec) { let monitoring_logic = worker::MonitoringLogic::new( monitors, self.supervisor_api_cycle, @@ -186,6 +192,29 @@ impl HealthMonitor { self.worker.start(monitoring_logic) } + /// Starts the health monitoring logic in a separate thread. + /// + /// From this point, the health monitor will periodically check monitors and notify the supervisor about system liveness. + /// + /// # Note + /// - This function shall be called before Lifecycle.running() otherwise the supervisor might consider the process not alive. + /// - Stops when the HealthMonitor instance is dropped. + /// + /// Panics if no monitors have been added. + pub fn start(&mut self) { + assert!( + self.check_monitors_exist_internal(), + "No deadline monitors have been added. HealthMonitor cannot start without any monitors." + ); + + let monitors = match self.collect_monitors_internal() { + Ok(m) => m, + Err(e) => panic!("{}", e), + }; + + self.start_internal(monitors); + } + //TODO: Add possibility to run HM in the current thread - ie in main }