From fb7e64d396323361dec42a41997b5938607496ea Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Sun, 20 Apr 2025 16:36:31 +0200 Subject: [PATCH 01/22] [#264] Implement missing functions in AttributeSet --- iceoryx2-ffi/cxx/src/attribute_set.cpp | 16 +++-- .../cxx/tests/src/attribute_tests.cpp | 38 +++++++++++ iceoryx2-ffi/ffi/src/api/attribute_set.rs | 67 +++++++++++++++++++ 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/iceoryx2-ffi/cxx/src/attribute_set.cpp b/iceoryx2-ffi/cxx/src/attribute_set.cpp index f2e71e37b..eb7f9743d 100644 --- a/iceoryx2-ffi/cxx/src/attribute_set.cpp +++ b/iceoryx2-ffi/cxx/src/attribute_set.cpp @@ -12,6 +12,7 @@ #include "iox2/attribute_set.hpp" #include "iox/assertions_addendum.hpp" +#include "iox/uninitialized_array.hpp" #include "iox2/internal/callback_context.hpp" namespace iox2 { @@ -35,13 +36,20 @@ auto AttributeSetView::at(const uint64_t index) const -> AttributeView { return AttributeView(iox2_attribute_set_at(m_handle, index)); } -auto AttributeSetView::get_key_value_len([[maybe_unused]] const Attribute::Key& key) const -> uint64_t { - IOX_TODO(); +auto AttributeSetView::get_key_value_len(const Attribute::Key& key) const -> uint64_t { + return iox2_attribute_set_get_key_value_len(m_handle, key.c_str()); } -auto AttributeSetView::get_key_value_at([[maybe_unused]] const Attribute::Key& key, [[maybe_unused]] const uint64_t idx) +auto AttributeSetView::get_key_value_at(const Attribute::Key& key, const uint64_t idx) -> iox::optional { - IOX_TODO(); + iox::UninitializedArray buffer; + iox2_attribute_set_get_key_value_at(m_handle, key.c_str(), idx, &buffer[0], Attribute::Value::capacity()); + + if (buffer[0] == 0) { + return iox::nullopt; + } + + return Attribute::Value(iox::TruncateToCapacity, &buffer[0]); } void AttributeSetView::get_key_values( diff --git a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp index f96ec6e1e..c5d4b26cd 100644 --- a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp @@ -118,4 +118,42 @@ TEST(AttributeSet, all_key_values_can_be_acquired) { return CallbackProgression::Continue; }); } + +TEST(AttributeSet, get_key_value_len_works) { + auto empty_key = Attribute::Key("fuu"); + auto key = Attribute::Key("whatever"); + auto value_1 = Attribute::Value("you"); + auto value_2 = Attribute::Value("want"); + + auto attribute_specifer = AttributeSpecifier().define(key, value_1).define(key, value_2); + auto attributes = attribute_specifer.attributes(); + + ASSERT_THAT(attributes.get_key_value_len(key), Eq(2)); + ASSERT_THAT(attributes.get_key_value_len(empty_key), Eq(0)); +} + +TEST(AttributeSet, get_key_value_at_works) { + auto key = Attribute::Key("schmu whatever"); + auto value_1 = Attribute::Value("fuu you"); + auto value_2 = Attribute::Value("blue want"); + + auto attribute_specifer = AttributeSpecifier().define(key, value_1).define(key, value_2); + auto attributes = attribute_specifer.attributes(); + + auto v_1 = attributes.get_key_value_at(key, 0); + auto v_2 = attributes.get_key_value_at(key, 1); + auto v_3 = attributes.get_key_value_at(key, 2); + + ASSERT_THAT(v_1.has_value(), Eq(true)); + ASSERT_THAT(v_2.has_value(), Eq(true)); + ASSERT_THAT(v_3.has_value(), Eq(true)); + + if (v_1->size() == value_1.size()) { + ASSERT_THAT(v_1.value().c_str(), StrEq(value_1.c_str())); + ASSERT_THAT(v_2.value().c_str(), StrEq(value_2.c_str())); + } else { + ASSERT_THAT(v_2.value().c_str(), StrEq(value_1.c_str())); + ASSERT_THAT(v_1.value().c_str(), StrEq(value_2.c_str())); + } +} } // namespace diff --git a/iceoryx2-ffi/ffi/src/api/attribute_set.rs b/iceoryx2-ffi/ffi/src/api/attribute_set.rs index e2e25ed7e..04b83755e 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_set.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_set.rs @@ -71,6 +71,73 @@ pub unsafe extern "C" fn iox2_attribute_set_at( (&attribute_set[index] as *const Attribute).cast() } +/// Returns the number of values stored under a specific key. If the key does not exist it +/// returns 0. +/// +/// # Safety +/// +/// * The `handle` must be a valid handle. +/// * `key` must be non-zero and contain a null-terminated string +#[no_mangle] +pub unsafe extern "C" fn iox2_attribute_set_get_key_value_len( + handle: iox2_attribute_set_h_ref, + key: *const c_char, +) -> usize { + debug_assert!(!handle.is_null()); + debug_assert!(!key.is_null()); + + let key = CStr::from_ptr(key); + let key = key.to_str(); + if key.is_err() { + return 0; + } + + let attribute_set = (*handle).underlying_type(); + attribute_set.get_key_value_len(key.unwrap()) +} + +/// Returns a value of a key at a specific index. The index enumerates the values of the key +/// if the key has multiple values. The values are always stored at the same position during +/// the lifetime of the service but they can change when the process is recreated by another +/// process when the system restarts. +/// +/// # Safety +/// +/// * The `handle` must be a valid handle. +/// * `key` must be non-zero and contain a null-terminated string +/// * `buffer` must point to a valid memory location +/// * `buffer_len` must define the length of the memory pointed by `buffer` +#[no_mangle] +pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( + handle: iox2_attribute_set_h_ref, + key: *const c_char, + index: usize, + buffer: *mut c_char, + buffer_len: usize, +) { + debug_assert!(!handle.is_null()); + debug_assert!(!key.is_null()); + debug_assert!(!buffer.is_null()); + debug_assert!(0 < buffer_len); + + let key = CStr::from_ptr(key).to_str(); + if key.is_err() { + buffer.add(0).write(0); + return; + } + + let attribute_set = (*handle).underlying_type(); + if let Some(v) = attribute_set.get_key_value_at(key.unwrap(), index) { + if let Ok(value) = CString::new(v) { + core::ptr::copy_nonoverlapping( + value.as_ptr(), + buffer, + buffer_len.min(value.count_bytes() + 1 /* null terminator */), + ); + } + } +} + /// Calls the provided callback for every value that is owned by the provided key. /// /// # Safety From 18699cfd9aa1484f4636201a62e8164da1ab00dd Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Sun, 20 Apr 2025 16:40:51 +0200 Subject: [PATCH 02/22] [#264] Implement missing functions in Subscriber --- iceoryx2-ffi/cxx/include/iox2/subscriber.hpp | 2 +- .../cxx/tests/src/service_publish_subscribe_tests.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/subscriber.hpp b/iceoryx2-ffi/cxx/include/iox2/subscriber.hpp index 9a3da0b9b..6065a4ee3 100644 --- a/iceoryx2-ffi/cxx/include/iox2/subscriber.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/subscriber.hpp @@ -123,7 +123,7 @@ inline auto Subscriber::id() const -> UniqueSubscriberId template inline auto Subscriber::buffer_size() const -> uint64_t { - IOX_TODO(); + return iox2_subscriber_buffer_size(&m_handle); } template diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 087f9df29..3c7e99703 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -510,6 +510,12 @@ TYPED_TEST(ServicePublishSubscribeTest, setting_service_properties_works) { ASSERT_THAT(static_config.message_type_details().payload().size(), Eq(sizeof(uint64_t))); ASSERT_THAT(static_config.message_type_details().payload().alignment(), Eq(alignof(uint64_t))); ASSERT_THAT(static_config.message_type_details().payload().type_name(), StrEq("u64")); + + auto subscriber = service.subscriber_builder().create().expect(""); + ASSERT_THAT(subscriber.buffer_size(), Eq(SUBSCRIBER_MAX_BUFFER_SIZE)); + + auto subscriber_2 = service.subscriber_builder().buffer_size(1).create().expect(""); + ASSERT_THAT(subscriber_2.buffer_size(), Eq(1)); } TYPED_TEST(ServicePublishSubscribeTest, safe_overflow_can_be_set) { From da8db11437281ded4c45806a0112b0ca579cdcd3 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Sun, 20 Apr 2025 22:34:48 +0200 Subject: [PATCH 03/22] [#264] Add DynamicConfig implementation --- iceoryx2-ffi/cxx/CMakeLists.txt | 1 + .../iox2/dynamic_config_publish_subscribe.hpp | 31 +++++++--- .../iox2/port_factory_publish_subscribe.hpp | 6 +- .../src/dynamic_config_publish_subscribe.cpp | 27 ++++++++ .../src/service_publish_subscribe_tests.cpp | 26 ++++++++ .../ffi/src/api/port_factory_pub_sub.rs | 62 +++++++++++++++++++ .../dynamic_config/publish_subscribe.rs | 3 +- 7 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 iceoryx2-ffi/cxx/src/dynamic_config_publish_subscribe.cpp diff --git a/iceoryx2-ffi/cxx/CMakeLists.txt b/iceoryx2-ffi/cxx/CMakeLists.txt index 34b96e72c..7b05c44d5 100644 --- a/iceoryx2-ffi/cxx/CMakeLists.txt +++ b/iceoryx2-ffi/cxx/CMakeLists.txt @@ -41,6 +41,7 @@ add_library(iceoryx2-cxx-object-lib OBJECT src/attribute_specifier.cpp src/attribute_verifier.cpp src/config.cpp + src/dynamic_config_publish_subscribe.cpp src/event_id.cpp src/file_descriptor.cpp src/header_publish_subscribe.cpp diff --git a/iceoryx2-ffi/cxx/include/iox2/dynamic_config_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/dynamic_config_publish_subscribe.hpp index f7b2e5013..29b85f58f 100644 --- a/iceoryx2-ffi/cxx/include/iox2/dynamic_config_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/dynamic_config_publish_subscribe.hpp @@ -13,19 +13,36 @@ #ifndef IOX2_DYNAMIC_CONFIG_PUBLISH_SUBSCRIBE_HPP #define IOX2_DYNAMIC_CONFIG_PUBLISH_SUBSCRIBE_HPP -#include "iox/assertions_addendum.hpp" +#include "iox2/internal/iceoryx2.hpp" #include namespace iox2 { + +/// The dynamic configuration of an +/// [`MessagingPattern::PublishSubscribe`] based service. Contains dynamic +/// parameters like the connected endpoints etc.. class DynamicConfigPublishSubscribe { public: - auto number_of_publishers() const -> uint64_t { - IOX_TODO(); - } - auto number_of_subscribers() const -> uint64_t { - IOX_TODO(); - } + DynamicConfigPublishSubscribe(const DynamicConfigPublishSubscribe&) = delete; + DynamicConfigPublishSubscribe(DynamicConfigPublishSubscribe&&) = delete; + auto operator=(const DynamicConfigPublishSubscribe&) -> DynamicConfigPublishSubscribe& = delete; + auto operator=(DynamicConfigPublishSubscribe&&) -> DynamicConfigPublishSubscribe& = delete; + ~DynamicConfigPublishSubscribe() = default; + + /// Returns how many [`Publisher`] ports are currently connected. + auto number_of_publishers() const -> uint64_t; + + /// Returns how many [`Subscriber`] ports are currently connected. + auto number_of_subscribers() const -> uint64_t; + + private: + template + friend class PortFactoryPublishSubscribe; + + explicit DynamicConfigPublishSubscribe(iox2_port_factory_pub_sub_h handle); + + iox2_port_factory_pub_sub_h m_handle = nullptr; }; } // namespace iox2 diff --git a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp index 431c566de..c980009f3 100644 --- a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp @@ -58,7 +58,7 @@ class PortFactoryPublishSubscribe { /// Returns the DynamicConfig of the [`Service`]. /// Contains all dynamic settings, like the current participants etc.. - auto dynamic_config() const -> const DynamicConfigPublishSubscribe&; + auto dynamic_config() const -> DynamicConfigPublishSubscribe; /// Iterates over all [`Node`]s of the [`Service`] /// and calls for every [`Node`] the provided callback. If an error occurs @@ -145,8 +145,8 @@ inline auto PortFactoryPublishSubscribe::static_config() template inline auto PortFactoryPublishSubscribe::dynamic_config() const - -> const DynamicConfigPublishSubscribe& { - IOX_TODO(); + -> DynamicConfigPublishSubscribe { + return DynamicConfigPublishSubscribe(m_handle); } template diff --git a/iceoryx2-ffi/cxx/src/dynamic_config_publish_subscribe.cpp b/iceoryx2-ffi/cxx/src/dynamic_config_publish_subscribe.cpp new file mode 100644 index 000000000..d2717665b --- /dev/null +++ b/iceoryx2-ffi/cxx/src/dynamic_config_publish_subscribe.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2025 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 Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include "iox2/dynamic_config_publish_subscribe.hpp" + +namespace iox2 { +auto DynamicConfigPublishSubscribe::number_of_publishers() const -> uint64_t { + return iox2_port_factory_pub_sub_dynamic_config_number_of_publishers(&m_handle); +} + +auto DynamicConfigPublishSubscribe::number_of_subscribers() const -> uint64_t { + return iox2_port_factory_pub_sub_dynamic_config_number_of_subscribers(&m_handle); +} + +DynamicConfigPublishSubscribe::DynamicConfigPublishSubscribe(iox2_port_factory_pub_sub_h handle) + : m_handle { handle } { +} +} // namespace iox2 diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 3c7e99703..265cf2a59 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -300,6 +300,32 @@ TYPED_TEST(ServicePublishSubscribeTest, loan_slice_send_receive_works) { ASSERT_THAT(iterations, Eq(SLICE_MAX_LENGTH)); } +TYPED_TEST(ServicePublishSubscribeTest, number_of_publishers_subscribers_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + const auto service_name = iox2_testing::generate_service_name(); + + auto node = NodeBuilder().create().expect(""); + auto service = node.service_builder(service_name).template publish_subscribe().create().expect(""); + + ASSERT_THAT(service.dynamic_config().number_of_publishers(), Eq(0)); + ASSERT_THAT(service.dynamic_config().number_of_subscribers(), Eq(0)); + + { + auto sut_publisher = service.publisher_builder().create().expect(""); + ASSERT_THAT(service.dynamic_config().number_of_publishers(), Eq(1)); + ASSERT_THAT(service.dynamic_config().number_of_subscribers(), Eq(0)); + + auto sut_subscriber = service.subscriber_builder().create().expect(""); + ASSERT_THAT(service.dynamic_config().number_of_publishers(), Eq(1)); + ASSERT_THAT(service.dynamic_config().number_of_subscribers(), Eq(1)); + } + + ASSERT_THAT(service.dynamic_config().number_of_publishers(), Eq(0)); + ASSERT_THAT(service.dynamic_config().number_of_subscribers(), Eq(0)); +} + + // NOLINTBEGIN(readability-function-cognitive-complexity) : Cognitive complexity of 26 (+1) is OK. Test case is complex. TYPED_TEST(ServicePublishSubscribeTest, loan_slice_uninit_send_receive_works) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index 001859e97..15a0b3e9d 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -277,6 +277,68 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_static_config( *static_config = config.into(); } +/// Returns how many publisher ports are currently connected. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or +/// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_pub_sub_dynamic_config_number_of_publishers( + handle: iox2_port_factory_pub_sub_h_ref, +) -> usize { + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + + use iceoryx2::prelude::PortFactory; + match port_factory.service_type { + iox2_service_type_e::IPC => port_factory + .value + .as_ref() + .ipc + .dynamic_config() + .number_of_publishers(), + iox2_service_type_e::LOCAL => port_factory + .value + .as_ref() + .local + .dynamic_config() + .number_of_publishers(), + } +} + +/// Returns how many subscriber ports are currently connected. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or +/// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_pub_sub_dynamic_config_number_of_subscribers( + handle: iox2_port_factory_pub_sub_h_ref, +) -> usize { + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + + use iceoryx2::prelude::PortFactory; + match port_factory.service_type { + iox2_service_type_e::IPC => port_factory + .value + .as_ref() + .ipc + .dynamic_config() + .number_of_subscribers(), + iox2_service_type_e::LOCAL => port_factory + .value + .as_ref() + .local + .dynamic_config() + .number_of_subscribers(), + } +} + /// This function needs to be called to destroy the port factory! /// /// # Arguments diff --git a/iceoryx2/src/service/dynamic_config/publish_subscribe.rs b/iceoryx2/src/service/dynamic_config/publish_subscribe.rs index 8aadf60a6..f2fec7ca5 100644 --- a/iceoryx2/src/service/dynamic_config/publish_subscribe.rs +++ b/iceoryx2/src/service/dynamic_config/publish_subscribe.rs @@ -69,7 +69,8 @@ pub struct SubscriberDetails { pub buffer_size: usize, } -/// The dynamic configuration of an [`crate::service::messaging_pattern::MessagingPattern::Event`] +/// The dynamic configuration of an +/// [`crate::service::messaging_pattern::MessagingPattern::PublishSubscribe`] /// based service. Contains dynamic parameters like the connected endpoints etc.. #[repr(C)] #[derive(Debug)] From 43c733ada161c5488820ce7e3c2940615ad58142 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Sun, 20 Apr 2025 22:56:41 +0200 Subject: [PATCH 04/22] [#264] Add DynamicConfig implementation for Events --- iceoryx2-ffi/cxx/CMakeLists.txt | 1 + .../cxx/include/iox2/dynamic_config_event.hpp | 27 +++++--- .../cxx/include/iox2/port_factory_event.hpp | 2 +- iceoryx2-ffi/cxx/src/dynamic_config_event.cpp | 29 +++++++++ iceoryx2-ffi/cxx/src/port_factory_event.cpp | 4 +- .../cxx/tests/src/service_event_tests.cpp | 24 +++++++ .../ffi/src/api/port_factory_event.rs | 62 +++++++++++++++++++ 7 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 iceoryx2-ffi/cxx/src/dynamic_config_event.cpp diff --git a/iceoryx2-ffi/cxx/CMakeLists.txt b/iceoryx2-ffi/cxx/CMakeLists.txt index 7b05c44d5..f3c25f3d0 100644 --- a/iceoryx2-ffi/cxx/CMakeLists.txt +++ b/iceoryx2-ffi/cxx/CMakeLists.txt @@ -41,6 +41,7 @@ add_library(iceoryx2-cxx-object-lib OBJECT src/attribute_specifier.cpp src/attribute_verifier.cpp src/config.cpp + src/dynamic_config_event.cpp src/dynamic_config_publish_subscribe.cpp src/event_id.cpp src/file_descriptor.cpp diff --git a/iceoryx2-ffi/cxx/include/iox2/dynamic_config_event.hpp b/iceoryx2-ffi/cxx/include/iox2/dynamic_config_event.hpp index 92280aa1d..3d01c8b6d 100644 --- a/iceoryx2-ffi/cxx/include/iox2/dynamic_config_event.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/dynamic_config_event.hpp @@ -13,19 +13,32 @@ #ifndef IOX2_DYNAMIC_CONFIG_EVENT_HPP #define IOX2_DYNAMIC_CONFIG_EVENT_HPP -#include "iox/assertions_addendum.hpp" +#include "iox2/internal/iceoryx2.hpp" #include namespace iox2 { class DynamicConfigEvent { public: - auto number_of_listeners() const -> uint64_t { - IOX_TODO(); - } - auto number_of_notifiers() const -> uint64_t { - IOX_TODO(); - } + DynamicConfigEvent(const DynamicConfigEvent&) = delete; + DynamicConfigEvent(DynamicConfigEvent&&) = delete; + auto operator=(const DynamicConfigEvent&) -> DynamicConfigEvent& = delete; + auto operator=(DynamicConfigEvent&&) -> DynamicConfigEvent& = delete; + ~DynamicConfigEvent() = default; + + /// Returns how many [`Listener`] ports are currently connected. + auto number_of_listeners() const -> uint64_t; + + /// Returns how many [`Notifier`] ports are currently connected. + auto number_of_notifiers() const -> uint64_t; + + private: + template + friend class PortFactoryEvent; + + explicit DynamicConfigEvent(iox2_port_factory_event_h handle); + + iox2_port_factory_event_h m_handle = nullptr; }; } // namespace iox2 diff --git a/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp b/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp index 56f24987f..65397927f 100644 --- a/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp @@ -55,7 +55,7 @@ class PortFactoryEvent { /// Returns the DynamicConfig of the [`Service`]. /// Contains all dynamic settings, like the current participants etc.. - auto dynamic_config() const -> const DynamicConfigEvent&; + auto dynamic_config() const -> DynamicConfigEvent; /// Iterates over all [`Node`]s of the [`Service`] /// and calls for every [`Node`] the provided callback. If an error occurs diff --git a/iceoryx2-ffi/cxx/src/dynamic_config_event.cpp b/iceoryx2-ffi/cxx/src/dynamic_config_event.cpp new file mode 100644 index 000000000..cda357567 --- /dev/null +++ b/iceoryx2-ffi/cxx/src/dynamic_config_event.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2025 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 Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include "iox2/dynamic_config_event.hpp" + +namespace iox2 { +auto DynamicConfigEvent::number_of_listeners() const -> uint64_t { + return iox2_port_factory_event_dynamic_config_number_of_listeners(&m_handle); +} + +auto DynamicConfigEvent::number_of_notifiers() const -> uint64_t { + return iox2_port_factory_event_dynamic_config_number_of_notifiers(&m_handle); +} + +DynamicConfigEvent::DynamicConfigEvent(iox2_port_factory_event_h handle) + : m_handle { handle } { +} + + +} // namespace iox2 diff --git a/iceoryx2-ffi/cxx/src/port_factory_event.cpp b/iceoryx2-ffi/cxx/src/port_factory_event.cpp index be7e2fd03..bf434d477 100644 --- a/iceoryx2-ffi/cxx/src/port_factory_event.cpp +++ b/iceoryx2-ffi/cxx/src/port_factory_event.cpp @@ -74,8 +74,8 @@ auto PortFactoryEvent::static_config() const -> StaticConfigEvent { } template -auto PortFactoryEvent::dynamic_config() const -> const DynamicConfigEvent& { - IOX_TODO(); +auto PortFactoryEvent::dynamic_config() const -> DynamicConfigEvent { + return DynamicConfigEvent(m_handle); } template diff --git a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp index 02f5cfa81..40b56e961 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp @@ -570,4 +570,28 @@ TYPED_TEST(ServiceEventTest, when_deadline_is_not_missed_notification_works) { ASSERT_THAT(result.has_value(), Eq(true)); ASSERT_THAT(listener.try_wait_one().expect("").has_value(), Eq(true)); } + +TYPED_TEST(ServiceEventTest, number_of_listener_notifier_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + const auto service_name = iox2_testing::generate_service_name(); + auto node = NodeBuilder().create().expect(""); + + auto service = node.service_builder(service_name).event().create().expect(""); + + ASSERT_THAT(service.dynamic_config().number_of_listeners(), Eq(0)); + ASSERT_THAT(service.dynamic_config().number_of_notifiers(), Eq(0)); + { + auto listener = service.listener_builder().create().expect(""); + ASSERT_THAT(service.dynamic_config().number_of_listeners(), Eq(1)); + ASSERT_THAT(service.dynamic_config().number_of_notifiers(), Eq(0)); + + auto notifier = service.notifier_builder().create().expect(""); + ASSERT_THAT(service.dynamic_config().number_of_listeners(), Eq(1)); + ASSERT_THAT(service.dynamic_config().number_of_notifiers(), Eq(1)); + } + ASSERT_THAT(service.dynamic_config().number_of_listeners(), Eq(0)); + ASSERT_THAT(service.dynamic_config().number_of_notifiers(), Eq(0)); +} + + } // namespace diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs index f8bc66d23..df11da60c 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs @@ -294,6 +294,68 @@ pub unsafe extern "C" fn iox2_port_factory_event_attributes( } } +/// Returns how many listener ports are currently connected. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_event_open`](crate::iox2_service_builder_event_open) or +/// [`iox2_service_builder_event_open_or_create`](crate::iox2_service_builder_event_open_or_create)! +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_event_dynamic_config_number_of_listeners( + handle: iox2_port_factory_event_h_ref, +) -> usize { + use iceoryx2::prelude::PortFactory; + + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + match port_factory.service_type { + iox2_service_type_e::IPC => port_factory + .value + .as_ref() + .ipc + .dynamic_config() + .number_of_listeners(), + iox2_service_type_e::LOCAL => port_factory + .value + .as_ref() + .local + .dynamic_config() + .number_of_listeners(), + } +} + +/// Returns how many notifier ports are currently connected. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_event_open`](crate::iox2_service_builder_event_open) or +/// [`iox2_service_builder_event_open_or_create`](crate::iox2_service_builder_event_open_or_create)! +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_event_dynamic_config_number_of_notifiers( + handle: iox2_port_factory_event_h_ref, +) -> usize { + use iceoryx2::prelude::PortFactory; + + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + match port_factory.service_type { + iox2_service_type_e::IPC => port_factory + .value + .as_ref() + .ipc + .dynamic_config() + .number_of_notifiers(), + iox2_service_type_e::LOCAL => port_factory + .value + .as_ref() + .local + .dynamic_config() + .number_of_notifiers(), + } +} + /// This function needs to be called to destroy the port factory! /// /// # Arguments From 8da0d3d1ee81d1fce96e686b430fe210fb7cd4c4 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Sun, 20 Apr 2025 23:36:28 +0200 Subject: [PATCH 05/22] [#264] Add ServiceId getter for event --- iceoryx2-ffi/cxx/CMakeLists.txt | 1 + .../cxx/include/iox2/port_factory_event.hpp | 2 +- iceoryx2-ffi/cxx/include/iox2/service_id.hpp | 20 +++++++----- iceoryx2-ffi/cxx/src/port_factory_event.cpp | 9 ++++-- iceoryx2-ffi/cxx/src/service_id.cpp | 27 ++++++++++++++++ .../cxx/tests/src/service_event_tests.cpp | 12 +++++++ .../ffi/src/api/port_factory_event.rs | 31 +++++++++++++++++++ 7 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 iceoryx2-ffi/cxx/src/service_id.cpp diff --git a/iceoryx2-ffi/cxx/CMakeLists.txt b/iceoryx2-ffi/cxx/CMakeLists.txt index f3c25f3d0..dd3c6af20 100644 --- a/iceoryx2-ffi/cxx/CMakeLists.txt +++ b/iceoryx2-ffi/cxx/CMakeLists.txt @@ -61,6 +61,7 @@ add_library(iceoryx2-cxx-object-lib OBJECT src/port_factory_notifier.cpp src/service.cpp src/service_builder_event.cpp + src/service_id.cpp src/service_name.cpp src/static_config.cpp src/static_config_event.cpp diff --git a/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp b/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp index 65397927f..b2fef8d1f 100644 --- a/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/port_factory_event.hpp @@ -44,7 +44,7 @@ class PortFactoryEvent { auto name() const -> ServiceNameView; /// Returns the [`ServiceId`] of the [`Service`] - auto service_id() const -> const ServiceId&; + auto service_id() const -> ServiceId; /// Returns the attributes defined in the [`Service`] auto attributes() const -> AttributeSetView; diff --git a/iceoryx2-ffi/cxx/include/iox2/service_id.hpp b/iceoryx2-ffi/cxx/include/iox2/service_id.hpp index c38eb07b0..bf053f635 100644 --- a/iceoryx2-ffi/cxx/include/iox2/service_id.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/service_id.hpp @@ -13,22 +13,28 @@ #ifndef IOX2_SERVICE_ID_HPP #define IOX2_SERVICE_ID_HPP -#include "iox/assertions_addendum.hpp" +#include "iox/string.hpp" +#include "iox2/internal/iceoryx2.hpp" namespace iox2 { /// Represents the unique if of a [`Service`]. class ServiceId { public: /// Returns the maximum string length of a [`ServiceId`] - auto max_len() -> uint64_t { - IOX_TODO(); - } + auto max_len() -> uint64_t; /// Returns the string value of the [`ServiceId`] - auto as_str() const -> const char* { - IOX_TODO(); - } + auto as_str() const -> const char*; + + private: + explicit ServiceId(const iox::string& value); + + template + friend class PortFactoryEvent; + + iox::string m_value; }; + } // namespace iox2 #endif diff --git a/iceoryx2-ffi/cxx/src/port_factory_event.cpp b/iceoryx2-ffi/cxx/src/port_factory_event.cpp index bf434d477..8ec10d655 100644 --- a/iceoryx2-ffi/cxx/src/port_factory_event.cpp +++ b/iceoryx2-ffi/cxx/src/port_factory_event.cpp @@ -12,6 +12,7 @@ #include "iox2/port_factory_event.hpp" #include "iox/assertions_addendum.hpp" +#include "iox/uninitialized_array.hpp" #include "iox2/iceoryx2.h" namespace iox2 { @@ -56,8 +57,12 @@ auto PortFactoryEvent::name() const -> ServiceNameView { } template -auto PortFactoryEvent::service_id() const -> const ServiceId& { - IOX_TODO(); +auto PortFactoryEvent::service_id() const -> ServiceId { + iox::UninitializedArray buffer; + + iox2_port_factory_event_service_id(&m_handle, &buffer[0], IOX2_SERVICE_ID_LENGTH); + + return ServiceId(iox::string(iox::TruncateToCapacity, &buffer[0])); } template diff --git a/iceoryx2-ffi/cxx/src/service_id.cpp b/iceoryx2-ffi/cxx/src/service_id.cpp new file mode 100644 index 000000000..2f0977d59 --- /dev/null +++ b/iceoryx2-ffi/cxx/src/service_id.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2025 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 Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include "iox2/service_id.hpp" + +namespace iox2 { +auto ServiceId::max_len() -> uint64_t { + return IOX2_SERVICE_ID_LENGTH; +} + +auto ServiceId::as_str() const -> const char* { + return m_value.c_str(); +} + +ServiceId::ServiceId(const iox::string& value) + : m_value { value } { +} +} // namespace iox2 diff --git a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp index 40b56e961..da333e1a6 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp @@ -593,5 +593,17 @@ TYPED_TEST(ServiceEventTest, number_of_listener_notifier_works) { ASSERT_THAT(service.dynamic_config().number_of_notifiers(), Eq(0)); } +TYPED_TEST(ServiceEventTest, service_id_is_unique_per_service) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + const auto service_name_1 = iox2_testing::generate_service_name(); + const auto service_name_2 = iox2_testing::generate_service_name(); + auto node = NodeBuilder().create().expect(""); + auto service_1_create = node.service_builder(service_name_1).event().create().expect(""); + auto service_1_open = node.service_builder(service_name_1).event().open().expect(""); + auto service_2 = node.service_builder(service_name_2).event().create().expect(""); + + ASSERT_THAT(service_1_create.service_id().as_str(), StrEq(service_1_open.service_id().as_str())); + ASSERT_THAT(service_1_create.service_id().as_str(), Not(StrEq(service_2.service_id().as_str()))); +} } // namespace diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs index df11da60c..c873aafff 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs @@ -24,6 +24,7 @@ use iceoryx2::service::port_factory::{event::PortFactory as PortFactoryEvent, Po use iceoryx2_bb_elementary::static_assert::*; use iceoryx2_ffi_macros::iceoryx2_ffi; +use core::ffi::c_char; use core::mem::ManuallyDrop; use super::{iox2_attribute_set_h_ref, iox2_static_config_event_t}; @@ -356,6 +357,36 @@ pub unsafe extern "C" fn iox2_port_factory_event_dynamic_config_number_of_notifi } } +/// Stores the service id in the provided buffer +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_event_open`](crate::iox2_service_builder_event_open) or +/// [`iox2_service_builder_event_open_or_create`](crate::iox2_service_builder_event_open_or_create)! +/// * `buffer` must be non-zero and point to a valid memory location +/// * `buffer_len` must define the actual size of the memory location `buffer` is pointing to +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_event_service_id( + handle: iox2_port_factory_event_h_ref, + buffer: *mut c_char, + buffer_len: usize, +) { + use iceoryx2::prelude::PortFactory; + + debug_assert!(!buffer.is_null()); + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + let service_id = match port_factory.service_type { + iox2_service_type_e::IPC => port_factory.value.as_ref().ipc.service_id(), + iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.service_id(), + }; + + let len = buffer_len.min(service_id.as_str().len()) - 1; + core::ptr::copy_nonoverlapping(service_id.as_str().as_ptr(), buffer.cast(), len); + buffer.add(len).write(0); +} + /// This function needs to be called to destroy the port factory! /// /// # Arguments From 1960803fcfca00fdf7fa908e84fba15620a098ec Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 00:23:27 +0200 Subject: [PATCH 06/22] [#264] name available for pub sub --- .../iox2/port_factory_publish_subscribe.hpp | 7 +++--- .../cxx/include/iox2/service_name.hpp | 2 ++ .../src/service_publish_subscribe_tests.cpp | 11 +++++++++ .../ffi/src/api/port_factory_pub_sub.rs | 24 ++++++++++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp index c980009f3..e901ee8cd 100644 --- a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp @@ -44,7 +44,7 @@ class PortFactoryPublishSubscribe { auto operator=(const PortFactoryPublishSubscribe&) -> PortFactoryPublishSubscribe& = delete; /// Returns the [`ServiceName`] of the service - auto name() const -> const ServiceName&; + auto name() const -> ServiceNameView; /// Returns the [`ServiceId`] of the [`Service`] auto service_id() const -> const ServiceId&; @@ -121,8 +121,9 @@ inline PortFactoryPublishSubscribe::~PortFactoryPublishS } template -inline auto PortFactoryPublishSubscribe::name() const -> const ServiceName& { - IOX_TODO(); +inline auto PortFactoryPublishSubscribe::name() const -> ServiceNameView { + const auto* service_name_ptr = iox2_port_factory_pub_sub_service_name(&m_handle); + return ServiceNameView(service_name_ptr); } template diff --git a/iceoryx2-ffi/cxx/include/iox2/service_name.hpp b/iceoryx2-ffi/cxx/include/iox2/service_name.hpp index 698ad99e3..e90c2749c 100644 --- a/iceoryx2-ffi/cxx/include/iox2/service_name.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/service_name.hpp @@ -44,6 +44,8 @@ class ServiceNameView { friend class Node; template friend class PortFactoryEvent; + template + friend class PortFactoryPublishSubscribe; explicit ServiceNameView(iox2_service_name_ptr ptr); iox2_service_name_ptr m_ptr = nullptr; diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 265cf2a59..92e54a703 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -56,6 +56,17 @@ TYPED_TEST(ServicePublishSubscribeTest, created_service_does_exist) { Service::does_exist(service_name, Config::global_config(), MessagingPattern::Event).expect("")); } +TYPED_TEST(ServicePublishSubscribeTest, service_name_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + const auto service_name = iox2_testing::generate_service_name(); + + auto node = NodeBuilder().create().expect(""); + auto sut = node.service_builder(service_name).template publish_subscribe().create().expect(""); + + ASSERT_THAT(sut.name().to_string().c_str(), StrEq(service_name.to_string().c_str())); +} + TYPED_TEST(ServicePublishSubscribeTest, creating_existing_service_fails) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index 15a0b3e9d..71a63c716 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -26,7 +26,7 @@ use iceoryx2_ffi_macros::iceoryx2_ffi; use core::mem::ManuallyDrop; -use super::iox2_attribute_set_h_ref; +use super::{iox2_attribute_set_h_ref, iox2_service_name_ptr}; // BEGIN types definition @@ -308,6 +308,28 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_dynamic_config_number_of_publ } } +/// Returns the [`iox2_service_name_ptr`], an immutable pointer to the service name. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or +/// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_pub_sub_service_name( + handle: iox2_port_factory_pub_sub_h_ref, +) -> iox2_service_name_ptr { + use iceoryx2::prelude::PortFactory; + + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + + match port_factory.service_type { + iox2_service_type_e::IPC => port_factory.value.as_ref().ipc.name(), + iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.name(), + } +} + /// Returns how many subscriber ports are currently connected. /// /// # Safety From 75d1b5ea35811b4ec12b1002f51544c1417bba34 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 00:28:41 +0200 Subject: [PATCH 07/22] [#264] ServiceId available for pubsub port factory --- .../iox2/port_factory_publish_subscribe.hpp | 9 ++++-- iceoryx2-ffi/cxx/include/iox2/service_id.hpp | 2 ++ iceoryx2-ffi/cxx/src/port_factory_event.cpp | 1 - .../src/service_publish_subscribe_tests.cpp | 15 +++++++++ .../ffi/src/api/port_factory_pub_sub.rs | 32 ++++++++++++++++++- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp index e901ee8cd..faaba7bf7 100644 --- a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp @@ -47,7 +47,7 @@ class PortFactoryPublishSubscribe { auto name() const -> ServiceNameView; /// Returns the [`ServiceId`] of the [`Service`] - auto service_id() const -> const ServiceId&; + auto service_id() const -> ServiceId; /// Returns the attributes defined in the [`Service`] auto attributes() const -> AttributeSetView; @@ -127,8 +127,11 @@ inline auto PortFactoryPublishSubscribe::name() const -> } template -inline auto PortFactoryPublishSubscribe::service_id() const -> const ServiceId& { - IOX_TODO(); +inline auto PortFactoryPublishSubscribe::service_id() const -> ServiceId { + iox::UninitializedArray buffer; + iox2_port_factory_pub_sub_service_id(&m_handle, &buffer[0], IOX2_SERVICE_ID_LENGTH); + + return ServiceId(iox::string(iox::TruncateToCapacity, &buffer[0])); } template diff --git a/iceoryx2-ffi/cxx/include/iox2/service_id.hpp b/iceoryx2-ffi/cxx/include/iox2/service_id.hpp index bf053f635..985c81258 100644 --- a/iceoryx2-ffi/cxx/include/iox2/service_id.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/service_id.hpp @@ -31,6 +31,8 @@ class ServiceId { template friend class PortFactoryEvent; + template + friend class PortFactoryPublishSubscribe; iox::string m_value; }; diff --git a/iceoryx2-ffi/cxx/src/port_factory_event.cpp b/iceoryx2-ffi/cxx/src/port_factory_event.cpp index 8ec10d655..18a86bb05 100644 --- a/iceoryx2-ffi/cxx/src/port_factory_event.cpp +++ b/iceoryx2-ffi/cxx/src/port_factory_event.cpp @@ -59,7 +59,6 @@ auto PortFactoryEvent::name() const -> ServiceNameView { template auto PortFactoryEvent::service_id() const -> ServiceId { iox::UninitializedArray buffer; - iox2_port_factory_event_service_id(&m_handle, &buffer[0], IOX2_SERVICE_ID_LENGTH); return ServiceId(iox::string(iox::TruncateToCapacity, &buffer[0])); diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 92e54a703..3cf3dce05 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -1318,5 +1318,20 @@ TYPED_TEST(ServicePublishSubscribeTest, PayloadTypeNameIsSetToInnerTypeNameIfPro auto static_config = service.static_config(); ASSERT_THAT(static_config.message_type_details().payload().type_name(), StrEq("Payload")); } + +TYPED_TEST(ServicePublishSubscribeTest, service_id_is_unique_per_service) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + const auto service_name_1 = iox2_testing::generate_service_name(); + const auto service_name_2 = iox2_testing::generate_service_name(); + auto node = NodeBuilder().create().expect(""); + + auto service_1_create = + node.service_builder(service_name_1).template publish_subscribe().create().expect(""); + auto service_1_open = node.service_builder(service_name_1).template publish_subscribe().open().expect(""); + auto service_2 = node.service_builder(service_name_2).template publish_subscribe().create().expect(""); + + ASSERT_THAT(service_1_create.service_id().as_str(), StrEq(service_1_open.service_id().as_str())); + ASSERT_THAT(service_1_create.service_id().as_str(), Not(StrEq(service_2.service_id().as_str()))); +} // END tests for customizable payload and user header type name } // namespace diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index 71a63c716..9eb39d521 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -24,7 +24,7 @@ use iceoryx2::service::port_factory::publish_subscribe::PortFactory; use iceoryx2_bb_elementary::static_assert::*; use iceoryx2_ffi_macros::iceoryx2_ffi; -use core::mem::ManuallyDrop; +use core::{ffi::c_char, mem::ManuallyDrop}; use super::{iox2_attribute_set_h_ref, iox2_service_name_ptr}; @@ -330,6 +330,36 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_service_name( } } +/// Stores the service id in the provided buffer +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or +/// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! +/// * `buffer` must be non-zero and point to a valid memory location +/// * `buffer_len` must define the actual size of the memory location `buffer` is pointing to +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_pub_sub_service_id( + handle: iox2_port_factory_pub_sub_h_ref, + buffer: *mut c_char, + buffer_len: usize, +) { + use iceoryx2::prelude::PortFactory; + + debug_assert!(!buffer.is_null()); + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + let service_id = match port_factory.service_type { + iox2_service_type_e::IPC => port_factory.value.as_ref().ipc.service_id(), + iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.service_id(), + }; + + let len = buffer_len.min(service_id.as_str().len()) - 1; + core::ptr::copy_nonoverlapping(service_id.as_str().as_ptr(), buffer.cast(), len); + buffer.add(len).write(0); +} + /// Returns how many subscriber ports are currently connected. /// /// # Safety From 14a7bbfe5a5ca8ae9a0fe7dd6c36de89efdf32e2 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 11:05:48 +0200 Subject: [PATCH 08/22] [#264] List all nodes connected to a pubsub service --- .../iox2/internal/callback_context.hpp | 57 +++++++++++++++- .../cxx/include/iox2/node_details.hpp | 12 ++-- iceoryx2-ffi/cxx/include/iox2/node_id.hpp | 10 ++- iceoryx2-ffi/cxx/include/iox2/node_name.hpp | 21 ++++-- iceoryx2-ffi/cxx/include/iox2/node_state.hpp | 2 +- .../iox2/port_factory_publish_subscribe.hpp | 15 ++++- iceoryx2-ffi/cxx/src/node.cpp | 48 +------------- .../src/service_publish_subscribe_tests.cpp | 38 +++++++++++ iceoryx2-ffi/ffi/src/api/node.rs | 2 +- .../ffi/src/api/port_factory_pub_sub.rs | 66 +++++++++++++++++-- 10 files changed, 197 insertions(+), 74 deletions(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp b/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp index 99bee02c2..29d72abc2 100644 --- a/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp @@ -13,6 +13,14 @@ #ifndef IOX2_INTERNAL_CALLBACK_CONTEXT_HPP #define IOX2_INTERNAL_CALLBACK_CONTEXT_HPP +#include "iox/optional.hpp" +#include "iox2/internal/iceoryx2.hpp" +#include "iox2/node_details.hpp" +#include "iox2/node_id.hpp" +#include "iox2/node_name.hpp" +#include "iox2/node_state.hpp" +#include "iox2/service_type.hpp" + namespace iox2::internal { template class CallbackContext { @@ -30,14 +38,59 @@ class CallbackContext { }; template -auto ctx(const T& ptr) -> CallbackContext { +inline auto ctx(const T& ptr) -> CallbackContext { return CallbackContext(ptr); } template -auto ctx_cast(void* ptr) -> CallbackContext* { +inline auto ctx_cast(void* ptr) -> CallbackContext* { return static_cast*>(ptr); } + +template +// NOLINTBEGIN(readability-function-size) +auto list_callback(iox2_node_state_e node_state, + iox2_node_id_ptr node_id_ptr, + const char* executable, + iox2_node_name_ptr node_name, + iox2_config_ptr config, + iox2_callback_context context) -> iox2_callback_progression_e { + auto node_details = [&] { + if (node_id_ptr == nullptr || config == nullptr) { + return iox::optional(); + } + + return iox::optional(NodeDetails { + iox::FileName::create(iox::string(iox::TruncateToCapacity, executable)) + .expect("The executable file name is always valid."), + NodeNameView { node_name }.to_owned(), + Config {} }); + }(); + + iox2_node_id_h node_id_handle = nullptr; + iox2_node_id_clone_from_ptr(nullptr, node_id_ptr, &node_id_handle); + NodeId node_id { node_id_handle }; + + auto node_state_object = [&] { + switch (node_state) { + case iox2_node_state_e_ALIVE: + return NodeState { AliveNodeView { node_id, node_details } }; + case iox2_node_state_e_DEAD: + return NodeState { DeadNodeView { AliveNodeView { node_id, node_details } } }; + case iox2_node_state_e_UNDEFINED: + return NodeState { iox2_node_state_e_UNDEFINED, node_id }; + case iox2_node_state_e_INACCESSIBLE: + return NodeState { iox2_node_state_e_INACCESSIBLE, node_id }; + } + + IOX_UNREACHABLE(); + }(); + + auto* callback = internal::ctx_cast)>>(context); + return iox::into(callback->value()(node_state_object)); +} +// NOLINTEND(readability-function-size) + } // namespace iox2::internal #endif diff --git a/iceoryx2-ffi/cxx/include/iox2/node_details.hpp b/iceoryx2-ffi/cxx/include/iox2/node_details.hpp index b0fb3c817..a6aa4e2a8 100644 --- a/iceoryx2-ffi/cxx/include/iox2/node_details.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/node_details.hpp @@ -30,12 +30,12 @@ class NodeDetails { private: template - friend auto list_callback(iox2_node_state_e, - iox2_node_id_ptr, - const char* executable, - iox2_node_name_ptr, - iox2_config_ptr, - iox2_callback_context) -> iox2_callback_progression_e; + friend auto internal::list_callback(iox2_node_state_e, + iox2_node_id_ptr, + const char* executable, + iox2_node_name_ptr, + iox2_config_ptr, + iox2_callback_context) -> iox2_callback_progression_e; NodeDetails(iox::FileName executable, NodeName name, Config config); diff --git a/iceoryx2-ffi/cxx/include/iox2/node_id.hpp b/iceoryx2-ffi/cxx/include/iox2/node_id.hpp index 26f5e7271..2a647b171 100644 --- a/iceoryx2-ffi/cxx/include/iox2/node_id.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/node_id.hpp @@ -21,6 +21,13 @@ #include namespace iox2 { +namespace internal { +template +auto list_callback( + iox2_node_state_e, iox2_node_id_ptr, const char*, iox2_node_name_ptr, iox2_config_ptr, iox2_callback_context) + -> iox2_callback_progression_e; +} + /// The system-wide unique id of a [`Node`] class NodeId { public: @@ -48,11 +55,10 @@ class NodeId { template friend class DeadNodeView; template - friend auto list_callback( + friend auto internal::list_callback( iox2_node_state_e, iox2_node_id_ptr, const char*, iox2_node_name_ptr, iox2_config_ptr, iox2_callback_context) -> iox2_callback_progression_e; - explicit NodeId(iox2_node_id_h handle); void drop(); diff --git a/iceoryx2-ffi/cxx/include/iox2/node_name.hpp b/iceoryx2-ffi/cxx/include/iox2/node_name.hpp index f92f436e1..c8480d94a 100644 --- a/iceoryx2-ffi/cxx/include/iox2/node_name.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/node_name.hpp @@ -19,6 +19,15 @@ #include "iox2/semantic_string.hpp" namespace iox2 { +namespace internal { +template +auto list_callback(iox2_node_state_e, + iox2_node_id_ptr, + const char* executable, + iox2_node_name_ptr, + iox2_config_ptr, + iox2_callback_context) -> iox2_callback_progression_e; +} class NodeName; /// Non-owning view of a [`NodeName`]. @@ -41,12 +50,12 @@ class NodeNameView { friend class Node; friend class NodeName; template - friend auto list_callback(iox2_node_state_e, - iox2_node_id_ptr, - const char* executable, - iox2_node_name_ptr, - iox2_config_ptr, - iox2_callback_context) -> iox2_callback_progression_e; + friend auto internal::list_callback(iox2_node_state_e, + iox2_node_id_ptr, + const char* executable, + iox2_node_name_ptr, + iox2_config_ptr, + iox2_callback_context) -> iox2_callback_progression_e; explicit NodeNameView(iox2_node_name_ptr ptr); iox2_node_name_ptr m_ptr = nullptr; diff --git a/iceoryx2-ffi/cxx/include/iox2/node_state.hpp b/iceoryx2-ffi/cxx/include/iox2/node_state.hpp index 672876130..401be4d12 100644 --- a/iceoryx2-ffi/cxx/include/iox2/node_state.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/node_state.hpp @@ -102,7 +102,7 @@ class NodeState { private: template - friend auto list_callback( + friend auto internal::list_callback( iox2_node_state_e, iox2_node_id_ptr, const char*, iox2_node_name_ptr, iox2_config_ptr, iox2_callback_context) -> iox2_callback_progression_e; diff --git a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp index faaba7bf7..f0d2d7cef 100644 --- a/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/port_factory_publish_subscribe.hpp @@ -19,6 +19,7 @@ #include "iox2/attribute_set.hpp" #include "iox2/callback_progression.hpp" #include "iox2/dynamic_config_publish_subscribe.hpp" +#include "iox2/internal/callback_context.hpp" #include "iox2/internal/iceoryx2.hpp" #include "iox2/node_failure_enums.hpp" #include "iox2/node_state.hpp" @@ -155,9 +156,17 @@ inline auto PortFactoryPublishSubscribe::dynamic_config( template inline auto PortFactoryPublishSubscribe::nodes( - [[maybe_unused]] const iox::function)>& callback) const - -> iox::expected { - IOX_TODO(); + const iox::function)>& callback) const -> iox::expected { + auto ctx = internal::ctx(callback); + + const auto ret_val = + iox2_port_factory_pub_sub_nodes(&m_handle, internal::list_callback, static_cast(&ctx)); + + if (ret_val == IOX2_OK) { + return iox::ok(); + } + + return iox::err(iox::into(ret_val)); } template diff --git a/iceoryx2-ffi/cxx/src/node.cpp b/iceoryx2-ffi/cxx/src/node.cpp index af60eb44e..e81c09fd0 100644 --- a/iceoryx2-ffi/cxx/src/node.cpp +++ b/iceoryx2-ffi/cxx/src/node.cpp @@ -83,57 +83,13 @@ auto Node::service_builder(const ServiceName& name) const -> ServiceBuilder { &m_handle, name.as_view().m_ptr }; } -template -// NOLINTBEGIN(readability-function-size) -auto list_callback(iox2_node_state_e node_state, - iox2_node_id_ptr node_id_ptr, - const char* executable, - iox2_node_name_ptr node_name, - iox2_config_ptr config, - iox2_callback_context context) -> iox2_callback_progression_e { - auto node_details = [&] { - if (node_id_ptr == nullptr || config == nullptr) { - return iox::optional(); - } - - return iox::optional(NodeDetails { - iox::FileName::create(iox::string(iox::TruncateToCapacity, executable)) - .expect("The executable file name is always valid."), - NodeNameView { node_name }.to_owned(), - Config {} }); - }(); - - iox2_node_id_h node_id_handle = nullptr; - iox2_node_id_clone_from_ptr(nullptr, node_id_ptr, &node_id_handle); - NodeId node_id { node_id_handle }; - - auto node_state_object = [&] { - switch (node_state) { - case iox2_node_state_e_ALIVE: - return NodeState { AliveNodeView { node_id, node_details } }; - case iox2_node_state_e_DEAD: - return NodeState { DeadNodeView { AliveNodeView { node_id, node_details } } }; - case iox2_node_state_e_UNDEFINED: - return NodeState { iox2_node_state_e_UNDEFINED, node_id }; - case iox2_node_state_e_INACCESSIBLE: - return NodeState { iox2_node_state_e_INACCESSIBLE, node_id }; - } - - IOX_UNREACHABLE(); - }(); - - auto* callback = internal::ctx_cast)>>(context); - return iox::into(callback->value()(node_state_object)); -} -// NOLINTEND(readability-function-size) - template auto Node::list(ConfigView config, const iox::function)>& callback) -> iox::expected { auto ctx = internal::ctx(callback); - const auto ret_val = - iox2_node_list(iox::into(T), config.m_ptr, list_callback, static_cast(&ctx)); + const auto ret_val = iox2_node_list( + iox::into(T), config.m_ptr, internal::list_callback, static_cast(&ctx)); if (ret_val == IOX2_OK) { return iox::ok(); diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 3cf3dce05..5d3aba2f5 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -67,6 +67,44 @@ TYPED_TEST(ServicePublishSubscribeTest, service_name_works) { ASSERT_THAT(sut.name().to_string().c_str(), StrEq(service_name.to_string().c_str())); } +TYPED_TEST(ServicePublishSubscribeTest, list_service_nodes_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + const auto node_name_1 = NodeName::create("nala is hungry").expect(""); + const auto node_name_2 = NodeName::create("maybe octo-wolf can help?").expect(""); + const auto service_name = iox2_testing::generate_service_name(); + + auto node_1 = NodeBuilder().name(node_name_1).create().expect(""); + auto node_2 = NodeBuilder().name(node_name_2).create().expect(""); + + auto sut_1 = node_1.service_builder(service_name).template publish_subscribe().create().expect(""); + auto sut_2 = node_2.service_builder(service_name).template publish_subscribe().open().expect(""); + + auto counter = 0; + auto verify_node = [&](const AliveNodeView& node_view) { + counter++; + if (node_view.id() == node_1.id()) { + ASSERT_THAT(node_view.details()->name().to_string().c_str(), StrEq(node_1.name().to_string().c_str())); + } else { + ASSERT_THAT(node_view.details()->name().to_string().c_str(), StrEq(node_2.name().to_string().c_str())); + } + }; + + auto result = sut_1.nodes([&](auto node_state) -> CallbackProgression { + node_state.alive(verify_node); + + node_state.dead([](auto) { ASSERT_TRUE(false); }); + node_state.inaccessible([](auto) { ASSERT_TRUE(false); }); + node_state.undefined([](auto) { ASSERT_TRUE(false); }); + + return CallbackProgression::Continue; + }); + + ASSERT_THAT(result.has_value(), Eq(true)); + ASSERT_THAT(counter, Eq(2)); +} + + TYPED_TEST(ServicePublishSubscribeTest, creating_existing_service_fails) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; diff --git a/iceoryx2-ffi/ffi/src/api/node.rs b/iceoryx2-ffi/ffi/src/api/node.rs index a562b8933..c1ebf7afc 100644 --- a/iceoryx2-ffi/ffi/src/api/node.rs +++ b/iceoryx2-ffi/ffi/src/api/node.rs @@ -410,7 +410,7 @@ pub unsafe extern "C" fn iox2_dead_node_remove_stale_resources( } } -fn iox2_node_list_impl( +pub(crate) fn iox2_node_list_impl( node_state: &NodeState, callback: iox2_node_list_callback, callback_ctx: iox2_callback_context, diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index 9eb39d521..0ce789c1e 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -12,11 +12,15 @@ #![allow(non_camel_case_types)] -use crate::api::{ - iox2_port_factory_publisher_builder_h, iox2_port_factory_publisher_builder_t, - iox2_port_factory_subscriber_builder_h, iox2_port_factory_subscriber_builder_t, - iox2_service_type_e, iox2_static_config_publish_subscribe_t, AssertNonNullHandle, HandleToType, - PayloadFfi, PortFactoryPublisherBuilderUnion, PortFactorySubscriberBuilderUnion, UserHeaderFfi, +use crate::{ + api::{ + iox2_port_factory_publisher_builder_h, iox2_port_factory_publisher_builder_t, + iox2_port_factory_subscriber_builder_h, iox2_port_factory_subscriber_builder_t, + iox2_service_type_e, iox2_static_config_publish_subscribe_t, AssertNonNullHandle, + HandleToType, IntoCInt, PayloadFfi, PortFactoryPublisherBuilderUnion, + PortFactorySubscriberBuilderUnion, UserHeaderFfi, + }, + iox2_node_list_impl, IOX2_OK, }; use iceoryx2::prelude::*; @@ -24,9 +28,14 @@ use iceoryx2::service::port_factory::publish_subscribe::PortFactory; use iceoryx2_bb_elementary::static_assert::*; use iceoryx2_ffi_macros::iceoryx2_ffi; -use core::{ffi::c_char, mem::ManuallyDrop}; +use core::{ + ffi::{c_char, c_int}, + mem::ManuallyDrop, +}; -use super::{iox2_attribute_set_h_ref, iox2_service_name_ptr}; +use super::{ + iox2_attribute_set_h_ref, iox2_callback_context, iox2_node_list_callback, iox2_service_name_ptr, +}; // BEGIN types definition @@ -308,6 +317,49 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_dynamic_config_number_of_publ } } +/// Calls the callback repeatedly with an [`iox2_node_state_e`], [`iox2_node_id_ptr`], +/// [´iox2_node_name_ptr´] and [`iox2_config_ptr`] for all [`Node`](iceoryx2::node::Node)s that +/// have opened the service. +/// +/// Returns IOX2_OK on success, an [`iox2_node_list_failure_e`] otherwise. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or +/// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! +/// * `callback` - A valid callback with [`iox2_node_list_callback`} signature +/// * `callback_ctx` - An optional callback context [`iox2_callback_context`} to e.g. store information across callback iterations +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_pub_sub_nodes( + handle: iox2_port_factory_pub_sub_h_ref, + callback: iox2_node_list_callback, + callback_ctx: iox2_callback_context, +) -> c_int { + use iceoryx2::prelude::PortFactory; + + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + + let list_result = match port_factory.service_type { + iox2_service_type_e::IPC => port_factory + .value + .as_ref() + .ipc + .nodes(|node_state| iox2_node_list_impl(&node_state, callback, callback_ctx)), + iox2_service_type_e::LOCAL => port_factory + .value + .as_ref() + .local + .nodes(|node_state| iox2_node_list_impl(&node_state, callback, callback_ctx)), + }; + + match list_result { + Ok(_) => IOX2_OK, + Err(e) => e.into_c_int(), + } +} + /// Returns the [`iox2_service_name_ptr`], an immutable pointer to the service name. /// /// # Safety From a8aee3716bad4bead681f3b0f96c2abc2e31dcd4 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 11:14:54 +0200 Subject: [PATCH 09/22] [#264] List all nodes connected to an event service --- iceoryx2-ffi/cxx/src/port_factory_event.cpp | 14 +++-- .../cxx/tests/src/service_event_tests.cpp | 37 +++++++++++++ .../src/service_publish_subscribe_tests.cpp | 1 - .../ffi/src/api/port_factory_event.rs | 53 +++++++++++++++++-- 4 files changed, 98 insertions(+), 7 deletions(-) diff --git a/iceoryx2-ffi/cxx/src/port_factory_event.cpp b/iceoryx2-ffi/cxx/src/port_factory_event.cpp index 18a86bb05..a304be2c7 100644 --- a/iceoryx2-ffi/cxx/src/port_factory_event.cpp +++ b/iceoryx2-ffi/cxx/src/port_factory_event.cpp @@ -11,9 +11,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/port_factory_event.hpp" -#include "iox/assertions_addendum.hpp" #include "iox/uninitialized_array.hpp" #include "iox2/iceoryx2.h" +#include "iox2/internal/callback_context.hpp" namespace iox2 { template @@ -83,9 +83,17 @@ auto PortFactoryEvent::dynamic_config() const -> DynamicConfigEvent { } template -auto PortFactoryEvent::nodes([[maybe_unused]] const iox::function)>& callback) const +auto PortFactoryEvent::nodes(const iox::function)>& callback) const -> iox::expected { - IOX_TODO(); + auto ctx = internal::ctx(callback); + + const auto ret_val = iox2_port_factory_event_nodes(&m_handle, internal::list_callback, static_cast(&ctx)); + + if (ret_val == IOX2_OK) { + return iox::ok(); + } + + return iox::err(iox::into(ret_val)); } template diff --git a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp index da333e1a6..8f1c54bb1 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp @@ -606,4 +606,41 @@ TYPED_TEST(ServiceEventTest, service_id_is_unique_per_service) { ASSERT_THAT(service_1_create.service_id().as_str(), StrEq(service_1_open.service_id().as_str())); ASSERT_THAT(service_1_create.service_id().as_str(), Not(StrEq(service_2.service_id().as_str()))); } + +TYPED_TEST(ServiceEventTest, list_service_nodes_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + const auto node_name_1 = NodeName::create("Nala and The HypnoToad").expect(""); + const auto node_name_2 = NodeName::create("Can they be friends?").expect(""); + const auto service_name = iox2_testing::generate_service_name(); + + auto node_1 = NodeBuilder().name(node_name_1).create().expect(""); + auto node_2 = NodeBuilder().name(node_name_2).create().expect(""); + + auto sut_1 = node_1.service_builder(service_name).event().create().expect(""); + auto sut_2 = node_2.service_builder(service_name).event().open().expect(""); + + auto counter = 0; + auto verify_node = [&](const AliveNodeView& node_view) { + counter++; + if (node_view.id() == node_1.id()) { + ASSERT_THAT(node_view.details()->name().to_string().c_str(), StrEq(node_1.name().to_string().c_str())); + } else { + ASSERT_THAT(node_view.details()->name().to_string().c_str(), StrEq(node_2.name().to_string().c_str())); + } + }; + + auto result = sut_1.nodes([&](auto node_state) -> CallbackProgression { + node_state.alive(verify_node); + + node_state.dead([](auto) { ASSERT_TRUE(false); }); + node_state.inaccessible([](auto) { ASSERT_TRUE(false); }); + node_state.undefined([](auto) { ASSERT_TRUE(false); }); + + return CallbackProgression::Continue; + }); + + ASSERT_THAT(result.has_value(), Eq(true)); + ASSERT_THAT(counter, Eq(2)); +} } // namespace diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 5d3aba2f5..405c0f275 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -104,7 +104,6 @@ TYPED_TEST(ServicePublishSubscribeTest, list_service_nodes_works) { ASSERT_THAT(counter, Eq(2)); } - TYPED_TEST(ServicePublishSubscribeTest, creating_existing_service_fails) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs index c873aafff..6f84d54ab 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs @@ -15,19 +15,23 @@ use crate::api::{ iox2_port_factory_listener_builder_h, iox2_port_factory_listener_builder_t, iox2_port_factory_notifier_builder_h, iox2_port_factory_notifier_builder_t, - iox2_service_name_ptr, iox2_service_type_e, AssertNonNullHandle, HandleToType, + iox2_service_name_ptr, iox2_service_type_e, AssertNonNullHandle, HandleToType, IntoCInt, PortFactoryListenerBuilderUnion, PortFactoryNotifierBuilderUnion, }; +use crate::{iox2_node_list_impl, IOX2_OK}; use iceoryx2::prelude::*; use iceoryx2::service::port_factory::{event::PortFactory as PortFactoryEvent, PortFactory}; use iceoryx2_bb_elementary::static_assert::*; use iceoryx2_ffi_macros::iceoryx2_ffi; -use core::ffi::c_char; +use core::ffi::{c_char, c_int}; use core::mem::ManuallyDrop; -use super::{iox2_attribute_set_h_ref, iox2_static_config_event_t}; +use super::{ + iox2_attribute_set_h_ref, iox2_callback_context, iox2_node_list_callback, + iox2_static_config_event_t, +}; // BEGIN types definition @@ -387,6 +391,49 @@ pub unsafe extern "C" fn iox2_port_factory_event_service_id( buffer.add(len).write(0); } +/// Calls the callback repeatedly with an [`iox2_node_state_e`], [`iox2_node_id_ptr`], +/// [´iox2_node_name_ptr´] and [`iox2_config_ptr`] for all [`Node`](iceoryx2::node::Node)s that +/// have opened the service. +/// +/// Returns IOX2_OK on success, an [`iox2_node_list_failure_e`] otherwise. +/// +/// # Safety +/// +/// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or +/// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! +/// * `callback` - A valid callback with [`iox2_node_list_callback`} signature +/// * `callback_ctx` - An optional callback context [`iox2_callback_context`} to e.g. store information across callback iterations +#[no_mangle] +pub unsafe extern "C" fn iox2_port_factory_event_nodes( + handle: iox2_port_factory_event_h_ref, + callback: iox2_node_list_callback, + callback_ctx: iox2_callback_context, +) -> c_int { + use iceoryx2::prelude::PortFactory; + + handle.assert_non_null(); + + let port_factory = &mut *handle.as_type(); + + let list_result = match port_factory.service_type { + iox2_service_type_e::IPC => port_factory + .value + .as_ref() + .ipc + .nodes(|node_state| iox2_node_list_impl(&node_state, callback, callback_ctx)), + iox2_service_type_e::LOCAL => port_factory + .value + .as_ref() + .local + .nodes(|node_state| iox2_node_list_impl(&node_state, callback, callback_ctx)), + }; + + match list_result { + Ok(_) => IOX2_OK, + Err(e) => e.into_c_int(), + } +} + /// This function needs to be called to destroy the port factory! /// /// # Arguments From 0ae50b9e2abd74594df9843c224dea00f158758f Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 11:26:54 +0200 Subject: [PATCH 10/22] [#264] Max nodes and event id value can be configured for event services --- .../cxx/src/service_builder_event.cpp | 6 +- .../cxx/tests/src/service_event_tests.cpp | 6 ++ .../ffi/src/api/service_builder_event.rs | 84 +++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/iceoryx2-ffi/cxx/src/service_builder_event.cpp b/iceoryx2-ffi/cxx/src/service_builder_event.cpp index b098a5512..1ddacea99 100644 --- a/iceoryx2-ffi/cxx/src/service_builder_event.cpp +++ b/iceoryx2-ffi/cxx/src/service_builder_event.cpp @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/service_builder_event.hpp" -#include "iox/assertions_addendum.hpp" namespace iox2 { template @@ -56,8 +55,9 @@ void ServiceBuilderEvent::set_parameters() { .or_else([&]() { iox2_service_builder_event_disable_deadline(&m_handle); }); } - m_max_nodes.and_then([](auto) { IOX_TODO(); }); - m_event_id_max_value.and_then([](auto) { IOX_TODO(); }); + m_max_nodes.and_then([&](auto value) { iox2_service_builder_event_set_max_nodes(&m_handle, value); }); + m_event_id_max_value.and_then( + [&](auto value) { iox2_service_builder_event_set_event_id_max_value(&m_handle, value); }); } template diff --git a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp index 8f1c54bb1..75a00598d 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp @@ -92,6 +92,8 @@ TYPED_TEST(ServiceEventTest, service_settings_are_applied) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; constexpr uint64_t NUMBER_OF_NOTIFIERS = 5; constexpr uint64_t NUMBER_OF_LISTENERS = 7; + constexpr uint64_t NUMBER_OF_NODES = 8; + constexpr uint64_t MAX_EVENT_ID_VALUE = 9; const auto create_event_id = EventId(12); const auto dropped_event_id = EventId(13); const auto dead_event_id = EventId(14); @@ -103,6 +105,8 @@ TYPED_TEST(ServiceEventTest, service_settings_are_applied) { .event() .max_notifiers(NUMBER_OF_NOTIFIERS) .max_listeners(NUMBER_OF_LISTENERS) + .max_nodes(NUMBER_OF_NODES) + .event_id_max_value(MAX_EVENT_ID_VALUE) .notifier_created_event(create_event_id) .notifier_dropped_event(dropped_event_id) .notifier_dead_event(dead_event_id) @@ -113,6 +117,8 @@ TYPED_TEST(ServiceEventTest, service_settings_are_applied) { ASSERT_THAT(static_config.max_notifiers(), Eq(NUMBER_OF_NOTIFIERS)); ASSERT_THAT(static_config.max_listeners(), Eq(NUMBER_OF_LISTENERS)); + ASSERT_THAT(static_config.max_nodes(), Eq(NUMBER_OF_NODES)); + ASSERT_THAT(static_config.event_id_max_value(), Eq(MAX_EVENT_ID_VALUE)); ASSERT_THAT(static_config.notifier_created_event(), Eq(iox::optional(create_event_id))); ASSERT_THAT(static_config.notifier_dropped_event(), Eq(iox::optional(dropped_event_id))); ASSERT_THAT(static_config.notifier_dead_event(), Eq(iox::optional(dead_event_id))); diff --git a/iceoryx2-ffi/ffi/src/api/service_builder_event.rs b/iceoryx2-ffi/ffi/src/api/service_builder_event.rs index 690a8af8c..21ad63c5c 100644 --- a/iceoryx2-ffi/ffi/src/api/service_builder_event.rs +++ b/iceoryx2-ffi/ffi/src/api/service_builder_event.rs @@ -532,6 +532,90 @@ pub unsafe extern "C" fn iox2_service_builder_event_set_max_notifiers( } } +/// Sets the max nodes for the builder +/// +/// # Arguments +/// +/// * `service_builder_handle` - Must be a valid [`iox2_service_builder_event_h_ref`] +/// obtained by [`iox2_service_builder_event`](crate::iox2_service_builder_event). +/// * `value` - The value to set the max notifiers to +/// +/// # Safety +/// +/// * `service_builder_handle` must be valid handles +#[no_mangle] +pub unsafe extern "C" fn iox2_service_builder_event_set_max_nodes( + service_builder_handle: iox2_service_builder_event_h_ref, + value: c_size_t, +) { + service_builder_handle.assert_non_null(); + + let service_builder_struct = unsafe { &mut *service_builder_handle.as_type() }; + + match service_builder_struct.service_type { + iox2_service_type_e::IPC => { + let service_builder = + ManuallyDrop::take(&mut service_builder_struct.value.as_mut().ipc); + + let service_builder = ManuallyDrop::into_inner(service_builder.event); + service_builder_struct.set(ServiceBuilderUnion::new_ipc_event( + service_builder.max_nodes(value), + )); + } + iox2_service_type_e::LOCAL => { + let service_builder = + ManuallyDrop::take(&mut service_builder_struct.value.as_mut().local); + + let service_builder = ManuallyDrop::into_inner(service_builder.event); + service_builder_struct.set(ServiceBuilderUnion::new_local_event( + service_builder.max_nodes(value), + )); + } + } +} + +/// Sets the max event id value for the builder +/// +/// # Arguments +/// +/// * `service_builder_handle` - Must be a valid [`iox2_service_builder_event_h_ref`] +/// obtained by [`iox2_service_builder_event`](crate::iox2_service_builder_event). +/// * `value` - The value to set the max notifiers to +/// +/// # Safety +/// +/// * `service_builder_handle` must be valid handles +#[no_mangle] +pub unsafe extern "C" fn iox2_service_builder_event_set_event_id_max_value( + service_builder_handle: iox2_service_builder_event_h_ref, + value: c_size_t, +) { + service_builder_handle.assert_non_null(); + + let service_builder_struct = unsafe { &mut *service_builder_handle.as_type() }; + + match service_builder_struct.service_type { + iox2_service_type_e::IPC => { + let service_builder = + ManuallyDrop::take(&mut service_builder_struct.value.as_mut().ipc); + + let service_builder = ManuallyDrop::into_inner(service_builder.event); + service_builder_struct.set(ServiceBuilderUnion::new_ipc_event( + service_builder.event_id_max_value(value), + )); + } + iox2_service_type_e::LOCAL => { + let service_builder = + ManuallyDrop::take(&mut service_builder_struct.value.as_mut().local); + + let service_builder = ManuallyDrop::into_inner(service_builder.event); + service_builder_struct.set(ServiceBuilderUnion::new_local_event( + service_builder.event_id_max_value(value), + )); + } + } +} + /// Sets the max listeners for the builder /// /// # Arguments From 4471229d27d4354b2df8f0f3b420149eb96da0e5 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 11:59:13 +0200 Subject: [PATCH 11/22] [#264] Address clang-tidy warnings --- iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp | 2 ++ iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp | 8 +++++--- .../cxx/tests/src/service_publish_subscribe_tests.cpp | 8 +++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp index c5d4b26cd..baecff28e 100644 --- a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp @@ -132,6 +132,7 @@ TEST(AttributeSet, get_key_value_len_works) { ASSERT_THAT(attributes.get_key_value_len(empty_key), Eq(0)); } +//NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by ASSERT_THAT TEST(AttributeSet, get_key_value_at_works) { auto key = Attribute::Key("schmu whatever"); auto value_1 = Attribute::Value("fuu you"); @@ -156,4 +157,5 @@ TEST(AttributeSet, get_key_value_at_works) { ASSERT_THAT(v_1.value().c_str(), StrEq(value_2.c_str())); } } +//NOLINTEND(readability-function-cognitive-complexity) } // namespace diff --git a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp index 75a00598d..e4e574f70 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp @@ -613,6 +613,7 @@ TYPED_TEST(ServiceEventTest, service_id_is_unique_per_service) { ASSERT_THAT(service_1_create.service_id().as_str(), Not(StrEq(service_2.service_id().as_str()))); } +//NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by ASSERT_THAT TYPED_TEST(ServiceEventTest, list_service_nodes_works) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; @@ -639,9 +640,9 @@ TYPED_TEST(ServiceEventTest, list_service_nodes_works) { auto result = sut_1.nodes([&](auto node_state) -> CallbackProgression { node_state.alive(verify_node); - node_state.dead([](auto) { ASSERT_TRUE(false); }); - node_state.inaccessible([](auto) { ASSERT_TRUE(false); }); - node_state.undefined([](auto) { ASSERT_TRUE(false); }); + node_state.dead([](const auto&) { ASSERT_TRUE(false); }); + node_state.inaccessible([](const auto&) { ASSERT_TRUE(false); }); + node_state.undefined([](const auto&) { ASSERT_TRUE(false); }); return CallbackProgression::Continue; }); @@ -649,4 +650,5 @@ TYPED_TEST(ServiceEventTest, list_service_nodes_works) { ASSERT_THAT(result.has_value(), Eq(true)); ASSERT_THAT(counter, Eq(2)); } +//NOLINTEND(readability-function-cognitive-complexity) } // namespace diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 405c0f275..383edff2b 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -67,6 +67,7 @@ TYPED_TEST(ServicePublishSubscribeTest, service_name_works) { ASSERT_THAT(sut.name().to_string().c_str(), StrEq(service_name.to_string().c_str())); } +//NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by ASSERT_THAT TYPED_TEST(ServicePublishSubscribeTest, list_service_nodes_works) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; @@ -93,9 +94,9 @@ TYPED_TEST(ServicePublishSubscribeTest, list_service_nodes_works) { auto result = sut_1.nodes([&](auto node_state) -> CallbackProgression { node_state.alive(verify_node); - node_state.dead([](auto) { ASSERT_TRUE(false); }); - node_state.inaccessible([](auto) { ASSERT_TRUE(false); }); - node_state.undefined([](auto) { ASSERT_TRUE(false); }); + node_state.dead([](const auto&) { ASSERT_TRUE(false); }); + node_state.inaccessible([](const auto&) { ASSERT_TRUE(false); }); + node_state.undefined([](const auto&) { ASSERT_TRUE(false); }); return CallbackProgression::Continue; }); @@ -103,6 +104,7 @@ TYPED_TEST(ServicePublishSubscribeTest, list_service_nodes_works) { ASSERT_THAT(result.has_value(), Eq(true)); ASSERT_THAT(counter, Eq(2)); } +//NOLINTEND(readability-function-cognitive-complexity) TYPED_TEST(ServicePublishSubscribeTest, creating_existing_service_fails) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; From c63cd17e2b4c52a83e65f413093084b3a7c15d56 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 21 Apr 2025 19:37:36 +0200 Subject: [PATCH 12/22] [#264] Add attributes to service and test service --- .../cxx/include/iox2/attribute_set.hpp | 1 + .../cxx/include/iox2/static_config_event.hpp | 4 + .../iox2/static_config_publish_subscribe.hpp | 4 + iceoryx2-ffi/cxx/src/static_config.cpp | 3 +- iceoryx2-ffi/cxx/src/static_config_event.cpp | 5 ++ .../src/static_config_publish_subscribe.cpp | 5 ++ iceoryx2-ffi/cxx/tests/src/service_tests.cpp | 88 +++++++++++++++++++ .../ffi/src/api/port_factory_event.rs | 2 +- .../ffi/src/api/port_factory_pub_sub.rs | 2 +- iceoryx2-ffi/ffi/src/api/static_config.rs | 10 ++- 10 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 iceoryx2-ffi/cxx/tests/src/service_tests.cpp diff --git a/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp b/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp index 8ee50714f..e819e634d 100644 --- a/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp @@ -56,6 +56,7 @@ class AttributeSetView { friend class PortFactoryEvent; friend class AttributeVerifier; friend class AttributeSpecifier; + friend class StaticConfig; explicit AttributeSetView(iox2_attribute_set_h_ref handle); diff --git a/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp b/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp index e6a44a2e3..115af6710 100644 --- a/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp @@ -15,6 +15,7 @@ #include "iox/duration.hpp" #include "iox/optional.hpp" +#include "iox2/attribute_set.hpp" #include "iox2/event_id.hpp" #include "iox2/iceoryx2.h" #include "iox2/internal/iceoryx2.hpp" @@ -47,6 +48,9 @@ class StaticConfigEvent { /// Returns the emitted [`EventId`] when a notifier is identified as dead. auto notifier_dead_event() const -> iox::optional; + /// Returns the attributes defined in the [`Service`] + auto attributes() const -> AttributeSetView; + /// Returns the deadline of the service. If no new notification is signaled from any /// [`Notifier`] after the given deadline, it is rated /// as an error and all [`Listener`] that are attached diff --git a/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp index 888336f14..eb8522f5d 100644 --- a/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp @@ -13,6 +13,7 @@ #ifndef IOX2_STATIC_CONFIG_PUBLISH_SUBSCRIBE_HPP #define IOX2_STATIC_CONFIG_PUBLISH_SUBSCRIBE_HPP +#include "iox2/attribute_set.hpp" #include "iox2/internal/iceoryx2.hpp" #include "iox2/message_type_details.hpp" @@ -43,6 +44,9 @@ class StaticConfigPublishSubscribe { /// Returns how many [`Sample`]s a [`Subscriber`] port can borrow in parallel at most. auto subscriber_max_borrowed_samples() const -> uint64_t; + /// Returns the attributes defined in the [`Service`] + auto attributes() const -> AttributeSetView; + /// Returns true if the [`Service`] safely overflows, otherwise false. Safe /// overflow means that the [`Publisher`] will recycle the oldest /// [`Sample`] from the [`Subscriber`] when its buffer is full. diff --git a/iceoryx2-ffi/cxx/src/static_config.cpp b/iceoryx2-ffi/cxx/src/static_config.cpp index 19b82137d..139bab9eb 100644 --- a/iceoryx2-ffi/cxx/src/static_config.cpp +++ b/iceoryx2-ffi/cxx/src/static_config.cpp @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/static_config.hpp" -#include "iox/assertions_addendum.hpp" #include "iox/into.hpp" namespace iox2 { @@ -20,7 +19,7 @@ StaticConfig::StaticConfig(iox2_static_config_t value) } auto StaticConfig::attributes() const -> AttributeSetView { - IOX_TODO(); + return AttributeSetView(m_value.attributes); } auto StaticConfig::id() const -> const char* { diff --git a/iceoryx2-ffi/cxx/src/static_config_event.cpp b/iceoryx2-ffi/cxx/src/static_config_event.cpp index 70894cdd1..c3f8b52c0 100644 --- a/iceoryx2-ffi/cxx/src/static_config_event.cpp +++ b/iceoryx2-ffi/cxx/src/static_config_event.cpp @@ -11,6 +11,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/static_config_event.hpp" +#include "iox/assertions_addendum.hpp" namespace iox2 { StaticConfigEvent::StaticConfigEvent(iox2_static_config_event_t value) @@ -33,6 +34,10 @@ auto StaticConfigEvent::event_id_max_value() const -> size_t { return m_value.event_id_max_value; } +auto StaticConfigEvent::attributes() const -> AttributeSetView { + IOX_TODO(); +} + auto StaticConfigEvent::notifier_created_event() const -> iox::optional { if (!m_value.has_notifier_created_event) { return iox::nullopt; diff --git a/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp b/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp index a4dd9ff0e..92671161e 100644 --- a/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp +++ b/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp @@ -11,12 +11,17 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/static_config_publish_subscribe.hpp" +#include "iox/assertions_addendum.hpp" namespace iox2 { StaticConfigPublishSubscribe::StaticConfigPublishSubscribe(iox2_static_config_publish_subscribe_t value) : m_value { value } { } +auto StaticConfigPublishSubscribe::attributes() const -> AttributeSetView { + IOX_TODO(); +} + auto StaticConfigPublishSubscribe::max_nodes() const -> uint64_t { return m_value.max_nodes; } diff --git a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp new file mode 100644 index 000000000..30a5477c6 --- /dev/null +++ b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2025 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 Software License 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license +// which is available at https://opensource.org/licenses/MIT. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#include "iox2/node.hpp" +#include "iox2/service.hpp" + +#include "test.hpp" + +namespace { +using namespace iox2; + +template +class ServiceTest : public ::testing::Test { + public: + static constexpr ServiceType TYPE = T::TYPE; +}; + +TYPED_TEST_SUITE(ServiceTest, iox2_testing::ServiceTypes, ); + +TYPED_TEST(ServiceTest, does_exist_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + const auto service_name = iox2_testing::generate_service_name(); + + ASSERT_FALSE( + Service::does_exist(service_name, Config::global_config(), MessagingPattern::PublishSubscribe) + .expect("")); + ASSERT_FALSE( + Service::does_exist(service_name, Config::global_config(), MessagingPattern::Event).expect("")); + + auto node = NodeBuilder().create().expect(""); + + { + auto sut = node.service_builder(service_name).template publish_subscribe().create().expect(""); + + ASSERT_TRUE( + Service::does_exist(service_name, Config::global_config(), MessagingPattern::PublishSubscribe) + .expect("")); + ASSERT_FALSE(Service::does_exist(service_name, Config::global_config(), MessagingPattern::Event) + .expect("")); + } + + ASSERT_FALSE( + Service::does_exist(service_name, Config::global_config(), MessagingPattern::PublishSubscribe) + .expect("")); + ASSERT_FALSE( + Service::does_exist(service_name, Config::global_config(), MessagingPattern::Event).expect("")); +} + +TYPED_TEST(ServiceTest, list_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + const auto service_name_1 = iox2_testing::generate_service_name(); + const auto service_name_2 = iox2_testing::generate_service_name(); + + auto node = NodeBuilder().create().expect(""); + + auto sut_1 = node.service_builder(service_name_1).template publish_subscribe().create().expect(""); + auto sut_2 = node.service_builder(service_name_2).event().create().expect(""); + + //NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by EXPECT_THAT + auto verify = [&](auto details) -> CallbackProgression { + if (details.static_details.messaging_pattern() == MessagingPattern::PublishSubscribe) { + EXPECT_THAT(details.static_details.name(), StrEq(service_name_1.to_string().c_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_1.service_id().as_str())); + } else { + EXPECT_THAT(details.static_details.name(), StrEq(service_name_2.to_string().c_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_2.service_id().as_str())); + } + + return CallbackProgression::Continue; + }; + //NOLINTEND(readability-function-cognitive-complexity) + + auto result = Service::list(Config::global_config(), verify); + + ASSERT_THAT(result.has_value(), Eq(true)); +} +} // namespace diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs index 6f84d54ab..1c64f00e3 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs @@ -386,7 +386,7 @@ pub unsafe extern "C" fn iox2_port_factory_event_service_id( iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.service_id(), }; - let len = buffer_len.min(service_id.as_str().len()) - 1; + let len = buffer_len.min(service_id.as_str().len()); core::ptr::copy_nonoverlapping(service_id.as_str().as_ptr(), buffer.cast(), len); buffer.add(len).write(0); } diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index 0ce789c1e..0d7ef484c 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -407,7 +407,7 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_service_id( iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.service_id(), }; - let len = buffer_len.min(service_id.as_str().len()) - 1; + let len = buffer_len.min(service_id.as_str().len()); core::ptr::copy_nonoverlapping(service_id.as_str().as_ptr(), buffer.cast(), len); buffer.add(len).write(0); } diff --git a/iceoryx2-ffi/ffi/src/api/static_config.rs b/iceoryx2-ffi/ffi/src/api/static_config.rs index 86d7ee184..fd47bb23e 100644 --- a/iceoryx2-ffi/ffi/src/api/static_config.rs +++ b/iceoryx2-ffi/ffi/src/api/static_config.rs @@ -14,8 +14,10 @@ use core::ffi::c_char; -use iceoryx2::service::static_config::messaging_pattern::MessagingPattern; use iceoryx2::service::static_config::StaticConfig; +use iceoryx2::{ + prelude::AttributeSet, service::static_config::messaging_pattern::MessagingPattern, +}; use iceoryx2_bb_log::fatal_panic; use crate::{ @@ -23,6 +25,8 @@ use crate::{ IOX2_SERVICE_ID_LENGTH, IOX2_SERVICE_NAME_LENGTH, }; +use super::iox2_attribute_set_h_ref; + #[derive(Clone, Copy)] #[repr(C)] pub union iox2_static_config_details_t { @@ -37,12 +41,15 @@ pub struct iox2_static_config_t { pub name: [c_char; IOX2_SERVICE_NAME_LENGTH], pub messaging_pattern: iox2_messaging_pattern_e, pub details: iox2_static_config_details_t, + pub attributes: iox2_attribute_set_h_ref, } impl From<&StaticConfig> for iox2_static_config_t { fn from(value: &StaticConfig) -> Self { Self { id: core::array::from_fn(|n| { + debug_assert!(value.service_id().as_str().len() + 1 < IOX2_SERVICE_ID_LENGTH); + let raw_service_id = value.service_id().as_str().as_bytes(); if n < raw_service_id.len() { raw_service_id[n] as _ @@ -59,6 +66,7 @@ impl From<&StaticConfig> for iox2_static_config_t { 0 } }), + attributes: (value.attributes() as *const AttributeSet).cast(), messaging_pattern: value.messaging_pattern().into(), details: { match value.messaging_pattern() { From 772abbbca498be5491fb12529841cb985d93b65e Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 22 Apr 2025 14:18:20 +0200 Subject: [PATCH 13/22] [#264] Add service list tests with attributes --- iceoryx2-ffi/cxx/tests/src/service_tests.cpp | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp index 30a5477c6..8dbdaaf39 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp @@ -10,6 +10,7 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT +#include "iox2/attribute_specifier.hpp" #include "iox2/node.hpp" #include "iox2/service.hpp" @@ -85,4 +86,59 @@ TYPED_TEST(ServiceTest, list_works) { ASSERT_THAT(result.has_value(), Eq(true)); } + +TYPED_TEST(ServiceTest, list_works_with_attributes) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + auto key_1 = Attribute::Key("do elephants like strawberries?"); + auto value_1 = Attribute::Value("do strawberries like elephants?"); + auto key_2 = Attribute::Key("the berry of the straw"); + auto value_2 = Attribute::Value("has left the field!"); + + + const auto service_name_1 = iox2_testing::generate_service_name(); + const auto service_name_2 = iox2_testing::generate_service_name(); + + auto node = NodeBuilder().create().expect(""); + + auto sut_1 = node.service_builder(service_name_1) + .template publish_subscribe() + .create_with_attributes(AttributeSpecifier().define(key_1, value_1).define(key_2, value_2)) + .expect(""); + auto sut_2 = node.service_builder(service_name_2).event().create().expect(""); + + //NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by EXPECT_THAT + auto verify = [&](auto details) -> CallbackProgression { + if (details.static_details.messaging_pattern() == MessagingPattern::PublishSubscribe) { + EXPECT_THAT(details.static_details.name(), StrEq(service_name_1.to_string().c_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_1.service_id().as_str())); + + auto counter = 0; + details.static_details.attributes().get_key_values(key_1, [&](auto& value) -> CallbackProgression { + EXPECT_THAT(value.c_str(), StrEq(value_1.c_str())); + counter++; + return CallbackProgression::Continue; + }); + EXPECT_THAT(counter, Eq(1)); + + counter = 0; + details.static_details.attributes().get_key_values(key_2, [&](auto& value) -> CallbackProgression { + EXPECT_THAT(value.c_str(), StrEq(value_2.c_str())); + counter++; + return CallbackProgression::Continue; + }); + EXPECT_THAT(counter, Eq(1)); + } else { + EXPECT_THAT(details.static_details.name(), StrEq(service_name_2.to_string().c_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_2.service_id().as_str())); + } + + return CallbackProgression::Continue; + }; + //NOLINTEND(readability-function-cognitive-complexity) + + auto result = Service::list(Config::global_config(), verify); + + ASSERT_THAT(result.has_value(), Eq(true)); +} } // namespace From 9e399dbf66aa68d828eb5577900f5bdc9b84c0de Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 22 Apr 2025 15:33:08 +0200 Subject: [PATCH 14/22] [#264] Remove attributes from messaging port factories --- iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp | 3 --- .../cxx/include/iox2/static_config_publish_subscribe.hpp | 3 --- iceoryx2-ffi/cxx/src/static_config_event.cpp | 5 ----- iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp | 5 ----- 4 files changed, 16 deletions(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp b/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp index 115af6710..ff97b274e 100644 --- a/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/static_config_event.hpp @@ -48,9 +48,6 @@ class StaticConfigEvent { /// Returns the emitted [`EventId`] when a notifier is identified as dead. auto notifier_dead_event() const -> iox::optional; - /// Returns the attributes defined in the [`Service`] - auto attributes() const -> AttributeSetView; - /// Returns the deadline of the service. If no new notification is signaled from any /// [`Notifier`] after the given deadline, it is rated /// as an error and all [`Listener`] that are attached diff --git a/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp b/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp index eb8522f5d..11542ce5a 100644 --- a/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/static_config_publish_subscribe.hpp @@ -44,9 +44,6 @@ class StaticConfigPublishSubscribe { /// Returns how many [`Sample`]s a [`Subscriber`] port can borrow in parallel at most. auto subscriber_max_borrowed_samples() const -> uint64_t; - /// Returns the attributes defined in the [`Service`] - auto attributes() const -> AttributeSetView; - /// Returns true if the [`Service`] safely overflows, otherwise false. Safe /// overflow means that the [`Publisher`] will recycle the oldest /// [`Sample`] from the [`Subscriber`] when its buffer is full. diff --git a/iceoryx2-ffi/cxx/src/static_config_event.cpp b/iceoryx2-ffi/cxx/src/static_config_event.cpp index c3f8b52c0..70894cdd1 100644 --- a/iceoryx2-ffi/cxx/src/static_config_event.cpp +++ b/iceoryx2-ffi/cxx/src/static_config_event.cpp @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/static_config_event.hpp" -#include "iox/assertions_addendum.hpp" namespace iox2 { StaticConfigEvent::StaticConfigEvent(iox2_static_config_event_t value) @@ -34,10 +33,6 @@ auto StaticConfigEvent::event_id_max_value() const -> size_t { return m_value.event_id_max_value; } -auto StaticConfigEvent::attributes() const -> AttributeSetView { - IOX_TODO(); -} - auto StaticConfigEvent::notifier_created_event() const -> iox::optional { if (!m_value.has_notifier_created_event) { return iox::nullopt; diff --git a/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp b/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp index 92671161e..a4dd9ff0e 100644 --- a/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp +++ b/iceoryx2-ffi/cxx/src/static_config_publish_subscribe.cpp @@ -11,17 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/static_config_publish_subscribe.hpp" -#include "iox/assertions_addendum.hpp" namespace iox2 { StaticConfigPublishSubscribe::StaticConfigPublishSubscribe(iox2_static_config_publish_subscribe_t value) : m_value { value } { } -auto StaticConfigPublishSubscribe::attributes() const -> AttributeSetView { - IOX_TODO(); -} - auto StaticConfigPublishSubscribe::max_nodes() const -> uint64_t { return m_value.max_nodes; } From 6912586e283b26d31de8380750d82eb7ceacdf7e Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 22 Apr 2025 16:10:26 +0200 Subject: [PATCH 15/22] [#264] Implement Service::details() --- .../cxx/include/iox2/static_config.hpp | 2 + iceoryx2-ffi/cxx/src/service.cpp | 28 ++++++++-- iceoryx2-ffi/cxx/tests/src/service_tests.cpp | 54 +++++++++++++++++++ iceoryx2-ffi/ffi/src/api/service.rs | 46 ++++++++++++++-- 4 files changed, 123 insertions(+), 7 deletions(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/static_config.hpp b/iceoryx2-ffi/cxx/include/iox2/static_config.hpp index 50e224d7a..da0243745 100644 --- a/iceoryx2-ffi/cxx/include/iox2/static_config.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/static_config.hpp @@ -33,6 +33,8 @@ class StaticConfig { auto messaging_pattern() const -> MessagingPattern; private: + template + friend class Service; template friend auto list_callback(const iox2_static_config_t*, void*) -> iox2_callback_progression_e; explicit StaticConfig(iox2_static_config_t value); diff --git a/iceoryx2-ffi/cxx/src/service.cpp b/iceoryx2-ffi/cxx/src/service.cpp index 9df8a59d0..2aa451a29 100644 --- a/iceoryx2-ffi/cxx/src/service.cpp +++ b/iceoryx2-ffi/cxx/src/service.cpp @@ -38,11 +38,31 @@ auto Service::does_exist(const ServiceName& service_name, } template -auto Service::details([[maybe_unused]] const ServiceName& service_name, - [[maybe_unused]] const ConfigView config, - [[maybe_unused]] const MessagingPattern messaging_pattern) +auto Service::details(const ServiceName& service_name, + const ConfigView config, + const MessagingPattern messaging_pattern) -> iox::expected>, ServiceDetailsError> { - IOX_TODO(); + iox2_static_config_t raw_static_config; + bool does_exist = false; + + auto result = iox2_service_details(iox::into(S), + service_name.as_view().m_ptr, + config.m_ptr, + iox::into(messaging_pattern), + &raw_static_config, + &does_exist); + + StaticConfig static_config(raw_static_config); + + if (result != IOX2_OK) { + return iox::err(iox::into(result)); + } + + if (!does_exist) { + return iox::ok(iox::optional>()); + } + + return iox::ok(iox::optional(ServiceDetails { static_config })); } template diff --git a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp index 8dbdaaf39..895ecaf32 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp @@ -141,4 +141,58 @@ TYPED_TEST(ServiceTest, list_works_with_attributes) { ASSERT_THAT(result.has_value(), Eq(true)); } + +TYPED_TEST(ServiceTest, details_works) { + constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; + + auto key_1 = Attribute::Key("gimme a strawberries?"); + auto value_1 = Attribute::Value("i want a strawberry!"); + auto key_2 = Attribute::Key("it makes me immortal"); + auto value_2 = Attribute::Value("or at least sticky"); + + + const auto service_name_1 = iox2_testing::generate_service_name(); + const auto service_name_2 = iox2_testing::generate_service_name(); + + auto node = NodeBuilder().create().expect(""); + + auto sut = node.service_builder(service_name_1) + .template publish_subscribe() + .create_with_attributes(AttributeSpecifier().define(key_1, value_1).define(key_2, value_2)) + .expect(""); + + auto result = + Service::details(service_name_1, Config::global_config(), MessagingPattern::PublishSubscribe); + + ASSERT_THAT(result.has_value(), Eq(true)); + ASSERT_THAT(result->has_value(), Eq(true)); + + ASSERT_THAT(result.value()->static_details.name(), StrEq(service_name_1.to_string().c_str())); + ASSERT_THAT(result.value()->static_details.name(), StrEq(service_name_1.to_string().c_str())); + + // auto counter = 0; + // result.value()->static_details.attributes().get_key_values(key_1, [&](auto& value) -> CallbackProgression { + // EXPECT_THAT(value.c_str(), StrEq(value_1.c_str())); + // counter++; + // return CallbackProgression::Continue; + // }); + // EXPECT_THAT(counter, Eq(1)); + + // counter = 0; + // result.value()->static_details.attributes().get_key_values(key_2, [&](auto& value) -> CallbackProgression { + // EXPECT_THAT(value.c_str(), StrEq(value_2.c_str())); + // counter++; + // return CallbackProgression::Continue; + // }); + // EXPECT_THAT(counter, Eq(1)); + + result = Service::details(service_name_1, Config::global_config(), MessagingPattern::Event); + ASSERT_THAT(result.has_value(), Eq(true)); + ASSERT_THAT(result->has_value(), Eq(false)); + + result = + Service::details(service_name_2, Config::global_config(), MessagingPattern::PublishSubscribe); + ASSERT_THAT(result.has_value(), Eq(true)); + ASSERT_THAT(result->has_value(), Eq(false)); +} } // namespace diff --git a/iceoryx2-ffi/ffi/src/api/service.rs b/iceoryx2-ffi/ffi/src/api/service.rs index e8d6212a7..5974c6f34 100644 --- a/iceoryx2-ffi/ffi/src/api/service.rs +++ b/iceoryx2-ffi/ffi/src/api/service.rs @@ -228,19 +228,59 @@ pub unsafe extern "C" fn iox2_service_does_exist( /// * The `service_name` must be valid and non-null /// * The `config` must be valid and non-null /// * The `service_details` must be valid and non-null +/// * The `does_exist` must be valid and non-null #[no_mangle] pub unsafe extern "C" fn iox2_service_details( - _service_type: iox2_service_type_e, + service_type: iox2_service_type_e, service_name: iox2_service_name_ptr, config: iox2_config_ptr, - _messaging_pattern: iox2_messaging_pattern_e, + messaging_pattern: iox2_messaging_pattern_e, service_details: *mut iox2_static_config_t, + does_exist: *mut bool, ) -> c_int { debug_assert!(!service_name.is_null()); debug_assert!(!config.is_null()); debug_assert!(!service_details.is_null()); + debug_assert!(!does_exist.is_null()); + + let config = &*config; + let service_name = &*service_name; + let messaging_pattern = messaging_pattern.into(); - todo!() + match service_type { + iox2_service_type_e::IPC => { + match ipc::Service::details(service_name, config, messaging_pattern) { + Ok(None) => { + does_exist.write(false); + return IOX2_OK; + } + Err(e) => { + return e.into_c_int(); + } + Ok(Some(v)) => { + service_details.write((&v.static_details).into()); + does_exist.write(true); + return IOX2_OK; + } + } + } + iox2_service_type_e::LOCAL => { + match local::Service::details(service_name, config, messaging_pattern) { + Ok(None) => { + does_exist.write(false); + return IOX2_OK; + } + Err(e) => { + return e.into_c_int(); + } + Ok(Some(v)) => { + service_details.write((&v.static_details).into()); + does_exist.write(true); + return IOX2_OK; + } + } + } + }; } fn list_callback( From f45d7388f03313b91e2bc8e8a258f869b7644f92 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 14:31:30 +0200 Subject: [PATCH 16/22] [#264] Add owning AttributeSet --- .../cxx/include/iox2/attribute_set.hpp | 47 +++++- .../cxx/include/iox2/static_config.hpp | 8 + iceoryx2-ffi/cxx/src/attribute_set.cpp | 85 +++++++++- iceoryx2-ffi/cxx/src/service.cpp | 4 +- iceoryx2-ffi/cxx/src/static_config.cpp | 27 +++- .../cxx/tests/src/attribute_tests.cpp | 2 +- iceoryx2-ffi/cxx/tests/src/service_tests.cpp | 30 ++-- iceoryx2-ffi/ffi/cbindgen.toml | 2 + iceoryx2-ffi/ffi/src/api/attribute_set.rs | 152 +++++++++++++++--- .../ffi/src/api/attribute_specifier.rs | 6 +- .../ffi/src/api/attribute_verifier.rs | 10 +- .../ffi/src/api/port_factory_event.rs | 12 +- .../ffi/src/api/port_factory_pub_sub.rs | 12 +- iceoryx2-ffi/ffi/src/api/static_config.rs | 21 ++- 14 files changed, 344 insertions(+), 74 deletions(-) diff --git a/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp b/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp index e819e634d..912e863be 100644 --- a/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp @@ -57,13 +57,56 @@ class AttributeSetView { friend class AttributeVerifier; friend class AttributeSpecifier; friend class StaticConfig; + friend class AttributeSet; - explicit AttributeSetView(iox2_attribute_set_h_ref handle); + explicit AttributeSetView(iox2_attribute_set_ptr handle); - iox2_attribute_set_h_ref m_handle = nullptr; + iox2_attribute_set_ptr m_handle = nullptr; }; + +/// Represents all service attributes. They can be set when the service is created. +class AttributeSet { + public: + AttributeSet(const AttributeSet&) = delete; + AttributeSet(AttributeSet&& rhs) noexcept; + auto operator=(const AttributeSet&) -> AttributeSet& = delete; + auto operator=(AttributeSet&& rhs) noexcept -> AttributeSet&; + ~AttributeSet(); + + /// Returns the number of [`Attribute`]s stored inside the [`AttributeSet`]. + auto len() const -> uint64_t; + + /// Returns a [`AttributeView`] at a specific index. The number of indices is returned via + /// [`AttributeSet::len()`]. + auto at(uint64_t index) const -> AttributeView; + + /// Returns the number of values stored under a specific key. If the key does not exist it + /// returns 0. + auto get_key_value_len(const Attribute::Key& key) const -> uint64_t; + + /// Returns a value of a key at a specific index. The index enumerates the values of the key + /// if the key has multiple values. The values are always stored at the same position during + /// the lifetime of the service but they can change when the process is recreated by another + /// process when the system restarts. + /// If the key does not exist or it does not have a value at the specified index, it returns + /// [`None`]. + auto get_key_value_at(const Attribute::Key& key, uint64_t idx) -> iox::optional; + + /// Returns all values to a specific key + void get_key_values(const Attribute::Key& key, + const iox::function& callback) const; + + private: + explicit AttributeSet(iox2_attribute_set_h handle); + void drop(); + + iox2_attribute_set_h m_handle = nullptr; + AttributeSetView m_view; +}; + } // namespace iox2 auto operator<<(std::ostream& stream, const iox2::AttributeSetView& value) -> std::ostream&; +auto operator<<(std::ostream& stream, const iox2::AttributeSet& value) -> std::ostream&; #endif diff --git a/iceoryx2-ffi/cxx/include/iox2/static_config.hpp b/iceoryx2-ffi/cxx/include/iox2/static_config.hpp index da0243745..3434a3660 100644 --- a/iceoryx2-ffi/cxx/include/iox2/static_config.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/static_config.hpp @@ -20,6 +20,13 @@ namespace iox2 { /// Defines a common set of static service configuration details every service shares. class StaticConfig { public: + StaticConfig(const StaticConfig&) = delete; + StaticConfig(StaticConfig&& rhs) noexcept; + ~StaticConfig(); + + auto operator=(const StaticConfig&) -> StaticConfig& = delete; + auto operator=(StaticConfig&& rhs) noexcept -> StaticConfig&; + /// Returns the attributes of the [`Service`] auto attributes() const -> AttributeSetView; @@ -38,6 +45,7 @@ class StaticConfig { template friend auto list_callback(const iox2_static_config_t*, void*) -> iox2_callback_progression_e; explicit StaticConfig(iox2_static_config_t value); + void drop(); iox2_static_config_t m_value; }; diff --git a/iceoryx2-ffi/cxx/src/attribute_set.cpp b/iceoryx2-ffi/cxx/src/attribute_set.cpp index eb7f9743d..26b7ea7e3 100644 --- a/iceoryx2-ffi/cxx/src/attribute_set.cpp +++ b/iceoryx2-ffi/cxx/src/attribute_set.cpp @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #include "iox2/attribute_set.hpp" -#include "iox/assertions_addendum.hpp" #include "iox/uninitialized_array.hpp" #include "iox2/internal/callback_context.hpp" @@ -24,7 +23,11 @@ auto get_key_values_callback(const char* value, iox2_callback_context context) - } } // namespace -AttributeSetView::AttributeSetView(iox2_attribute_set_h_ref handle) +///////////////////////////// +/// BEGIN: AttributeSetView +///////////////////////////// + +AttributeSetView::AttributeSetView(iox2_attribute_set_ptr handle) : m_handle { handle } { } @@ -57,9 +60,87 @@ void AttributeSetView::get_key_values( auto ctx = internal::ctx(callback); iox2_attribute_set_get_key_values(m_handle, key.c_str(), get_key_values_callback, static_cast(&ctx)); } + +///////////////////////////// +/// END: AttributeSetView +///////////////////////////// + +///////////////////////////// +/// BEGIN: AttributeSet +///////////////////////////// +AttributeSet::AttributeSet(iox2_attribute_set_h handle) + : m_handle { handle } + , m_view { AttributeSetView(iox2_cast_attribute_set_ptr(handle)) } { +} + +AttributeSet::AttributeSet(AttributeSet&& rhs) noexcept + : m_handle { std::move(rhs.m_handle) } + , m_view { std::move(rhs.m_view) } { + rhs.m_handle = nullptr; + rhs.m_view.m_handle = nullptr; +} + +auto AttributeSet::operator=(AttributeSet&& rhs) noexcept -> AttributeSet& { + if (this != &rhs) { + drop(); + m_handle = std::move(rhs.m_handle); + m_view = std::move(rhs.m_view); + + rhs.m_handle = nullptr; + rhs.m_view.m_handle = nullptr; + } + + return *this; +} + +AttributeSet::~AttributeSet() { + drop(); +} + +void AttributeSet::drop() { + if (m_handle != nullptr) { + iox2_attribute_set_drop(m_handle); + + m_handle = nullptr; + m_view.m_handle = nullptr; + } +} + +auto AttributeSet::len() const -> uint64_t { + return m_view.len(); +} + +auto AttributeSet::at(const uint64_t index) const -> AttributeView { + return m_view.at(index); +} + +auto AttributeSet::get_key_value_len(const Attribute::Key& key) const -> uint64_t { + return m_view.get_key_value_len(key); +} + +auto AttributeSet::get_key_value_at(const Attribute::Key& key, const uint64_t idx) -> iox::optional { + return m_view.get_key_value_at(key, idx); +} + +void AttributeSet::get_key_values(const Attribute::Key& key, + const iox::function& callback) const { + m_view.get_key_values(key, callback); +} +///////////////////////////// +/// END: AttributeSet +///////////////////////////// } // namespace iox2 auto operator<<(std::ostream& stream, const iox2::AttributeSetView& value) -> std::ostream& { + stream << "AttributeSetView { "; + for (uint64_t idx = 0; idx < value.len(); ++idx) { + auto attribute = value.at(idx); + stream << attribute; + } + return stream; +} + +auto operator<<(std::ostream& stream, const iox2::AttributeSet& value) -> std::ostream& { stream << "AttributeSet { "; for (uint64_t idx = 0; idx < value.len(); ++idx) { auto attribute = value.at(idx); diff --git a/iceoryx2-ffi/cxx/src/service.cpp b/iceoryx2-ffi/cxx/src/service.cpp index 2aa451a29..ed95dc618 100644 --- a/iceoryx2-ffi/cxx/src/service.cpp +++ b/iceoryx2-ffi/cxx/src/service.cpp @@ -52,8 +52,6 @@ auto Service::details(const ServiceName& service_name, &raw_static_config, &does_exist); - StaticConfig static_config(raw_static_config); - if (result != IOX2_OK) { return iox::err(iox::into(result)); } @@ -62,7 +60,7 @@ auto Service::details(const ServiceName& service_name, return iox::ok(iox::optional>()); } - return iox::ok(iox::optional(ServiceDetails { static_config })); + return iox::ok(iox::optional(ServiceDetails { StaticConfig(raw_static_config) })); } template diff --git a/iceoryx2-ffi/cxx/src/static_config.cpp b/iceoryx2-ffi/cxx/src/static_config.cpp index 139bab9eb..344650f99 100644 --- a/iceoryx2-ffi/cxx/src/static_config.cpp +++ b/iceoryx2-ffi/cxx/src/static_config.cpp @@ -18,8 +18,33 @@ StaticConfig::StaticConfig(iox2_static_config_t value) : m_value { value } { } +StaticConfig::StaticConfig(StaticConfig&& rhs) noexcept + : m_value { std::move(rhs.m_value) } { + rhs.m_value.attributes = nullptr; +} + +auto StaticConfig::operator=(StaticConfig&& rhs) noexcept -> StaticConfig& { + if (this != &rhs) { + drop(); + m_value = std::move(rhs.m_value); + rhs.m_value.attributes = nullptr; + } + return *this; +} + +StaticConfig::~StaticConfig() { + drop(); +} + +void StaticConfig::drop() { + if (m_value.attributes != nullptr) { + iox2_attribute_set_drop(m_value.attributes); + m_value.attributes = nullptr; + } +} + auto StaticConfig::attributes() const -> AttributeSetView { - return AttributeSetView(m_value.attributes); + return AttributeSetView(iox2_cast_attribute_set_ptr(m_value.attributes)); } auto StaticConfig::id() const -> const char* { diff --git a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp index baecff28e..6b0f9cdf5 100644 --- a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp @@ -147,7 +147,7 @@ TEST(AttributeSet, get_key_value_at_works) { ASSERT_THAT(v_1.has_value(), Eq(true)); ASSERT_THAT(v_2.has_value(), Eq(true)); - ASSERT_THAT(v_3.has_value(), Eq(true)); + ASSERT_THAT(v_3.has_value(), Eq(false)); if (v_1->size() == value_1.size()) { ASSERT_THAT(v_1.value().c_str(), StrEq(value_1.c_str())); diff --git a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp index 895ecaf32..afbde4083 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp @@ -170,21 +170,21 @@ TYPED_TEST(ServiceTest, details_works) { ASSERT_THAT(result.value()->static_details.name(), StrEq(service_name_1.to_string().c_str())); ASSERT_THAT(result.value()->static_details.name(), StrEq(service_name_1.to_string().c_str())); - // auto counter = 0; - // result.value()->static_details.attributes().get_key_values(key_1, [&](auto& value) -> CallbackProgression { - // EXPECT_THAT(value.c_str(), StrEq(value_1.c_str())); - // counter++; - // return CallbackProgression::Continue; - // }); - // EXPECT_THAT(counter, Eq(1)); - - // counter = 0; - // result.value()->static_details.attributes().get_key_values(key_2, [&](auto& value) -> CallbackProgression { - // EXPECT_THAT(value.c_str(), StrEq(value_2.c_str())); - // counter++; - // return CallbackProgression::Continue; - // }); - // EXPECT_THAT(counter, Eq(1)); + auto counter = 0; + result.value()->static_details.attributes().get_key_values(key_1, [&](auto& value) -> CallbackProgression { + EXPECT_THAT(value.c_str(), StrEq(value_1.c_str())); + counter++; + return CallbackProgression::Continue; + }); + EXPECT_THAT(counter, Eq(1)); + + counter = 0; + result.value()->static_details.attributes().get_key_values(key_2, [&](auto& value) -> CallbackProgression { + EXPECT_THAT(value.c_str(), StrEq(value_2.c_str())); + counter++; + return CallbackProgression::Continue; + }); + EXPECT_THAT(counter, Eq(1)); result = Service::details(service_name_1, Config::global_config(), MessagingPattern::Event); ASSERT_THAT(result.has_value(), Eq(true)); diff --git a/iceoryx2-ffi/ffi/cbindgen.toml b/iceoryx2-ffi/ffi/cbindgen.toml index a682fa831..558836aa1 100644 --- a/iceoryx2-ffi/ffi/cbindgen.toml +++ b/iceoryx2-ffi/ffi/cbindgen.toml @@ -81,6 +81,7 @@ after_includes = """ #endif // forward declarations of renamed types +typedef struct iox2_attribute_set_ptr_t iox2_attribute_set_ptr_t; typedef struct iox2_config_ptr_t iox2_config_ptr_t; typedef struct iox2_node_id_ptr_t iox2_node_id_ptr_t; typedef struct iox2_node_name_ptr_t iox2_node_name_ptr_t; @@ -148,6 +149,7 @@ renaming_overrides_prefixing = false # Table of name conversions to apply to item names (lhs becomes rhs) [export.rename] +"AttributeSet" = "iox2_attribute_set_ptr_t" "Config" = "iox2_config_ptr_t" "NodeId" = "iox2_node_id_ptr_t" "NodeName" = "iox2_node_name_ptr_t" diff --git a/iceoryx2-ffi/ffi/src/api/attribute_set.rs b/iceoryx2-ffi/ffi/src/api/attribute_set.rs index 04b83755e..13d94b378 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_set.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_set.rs @@ -13,25 +13,77 @@ #![allow(non_camel_case_types)] use core::ffi::{c_char, CStr}; +use iceoryx2_bb_elementary::static_assert::*; extern crate alloc; use alloc::ffi::CString; use iceoryx2::service::attribute::{Attribute, AttributeSet}; use iceoryx2_bb_elementary::CallbackProgression; +use iceoryx2_ffi_macros::iceoryx2_ffi; -use super::{iox2_attribute_h_ref, iox2_callback_context, iox2_callback_progression_e}; +use super::{ + iox2_attribute_h_ref, iox2_callback_context, iox2_callback_progression_e, AssertNonNullHandle, + HandleToType, +}; // BEGIN types definition +#[repr(C)] +#[repr(align(8))] // alignment of Option +pub struct iox2_attribute_set_storage_t { + internal: [u8; 24], // magic number obtained with size_of::>() +} + +#[repr(C)] +#[iceoryx2_ffi(AttributeSet)] +pub struct iox2_attribute_set_t { + pub value: iox2_attribute_set_storage_t, + deleter: fn(*mut iox2_attribute_set_t), +} + pub struct iox2_attribute_set_h_t; +/// The owning handle for `iox2_attribute_set_t`. Passing the handle to an function transfers the ownership. +pub type iox2_attribute_set_h = *mut iox2_attribute_set_h_t; + +/// The non-owning handle for `iox2_attribute_set_t`. Passing the handle to an function does not transfers the ownership. +pub type iox2_attribute_set_h_ref = *const iox2_attribute_set_h; + +// NOTE check the README.md for using opaque types with renaming +/// The immutable pointer to the underlying `AttributeSet` +pub type iox2_attribute_set_ptr = *const AttributeSet; +/// The mutable pointer to the underlying `AttributeSet` +pub type iox2_attribute_set_ptr_mut = *mut AttributeSet; + +impl AssertNonNullHandle for iox2_attribute_set_h { + fn assert_non_null(self) { + debug_assert!(!self.is_null()); + } +} + +impl AssertNonNullHandle for iox2_attribute_set_h_ref { + fn assert_non_null(self) { + debug_assert!(!self.is_null()); + unsafe { + debug_assert!(!(*self).is_null()); + } + } +} + +impl HandleToType for iox2_attribute_set_h { + type Target = *mut iox2_attribute_set_t; -impl iox2_attribute_set_h_t { - pub(crate) unsafe fn underlying_type(&self) -> &AttributeSet { - &*(self as *const iox2_attribute_set_h_t).cast() + fn as_type(self) -> Self::Target { + self as *mut _ as _ } } -pub type iox2_attribute_set_h_ref = *const iox2_attribute_set_h_t; +impl HandleToType for iox2_attribute_set_h_ref { + type Target = *mut iox2_attribute_set_t; + + fn as_type(self) -> Self::Target { + unsafe { *self as *mut _ as _ } + } +} pub type iox2_attribute_set_get_callback = extern "C" fn(*const c_char, iox2_callback_context) -> iox2_callback_progression_e; @@ -40,17 +92,81 @@ pub type iox2_attribute_set_get_callback = // BEGIN C API +/// This function create a new attribute_set by cloning an already existing one! +/// +/// # Safety +/// +/// * `struct_ptr` - Must be either a NULL pointer or a pointer to a valid [`iox2_attribute_set_t`]. If it is a NULL pointer, the storage will be allocated on the heap. +/// * `source_ptr` - Must be valid pointer to a [`iox2_attribute_set_ptr`]. +/// * `handle_ptr` - An uninitialized or dangling [`iox2_attribute_set_h`] handle which will be initialized by this function call. +/// +#[no_mangle] +pub unsafe extern "C" fn iox2_attribute_set_new_clone( + struct_ptr: *mut iox2_attribute_set_t, + source_ptr: iox2_attribute_set_ptr, + handle_ptr: *mut iox2_attribute_set_h, +) { + debug_assert!(!handle_ptr.is_null()); + debug_assert!(!source_ptr.is_null()); + + *handle_ptr = core::ptr::null_mut(); + + let mut struct_ptr = struct_ptr; + fn no_op(_: *mut iox2_attribute_set_t) {} + let mut deleter: fn(*mut iox2_attribute_set_t) = no_op; + if struct_ptr.is_null() { + struct_ptr = iox2_attribute_set_t::alloc(); + deleter = iox2_attribute_set_t::dealloc; + } + debug_assert!(!struct_ptr.is_null()); + + unsafe { + (*struct_ptr).deleter = deleter; + } + + unsafe { + (*struct_ptr).value.init((&*source_ptr).clone()); + } + + *handle_ptr = (*struct_ptr).as_handle(); +} + +/// This function needs to be called to destroy the attribute set! +/// +/// # Safety +/// +/// * `handle` - A valid [`iox2_attribute_set_h`] created with [`iox2_attribute_set_new_clone()`]. +/// * The `handle` is invalid after the return of this function and leads to undefined behavior if used in another function call! +/// * The corresponding [`iox2_attribute_set_t`] can be re-used with a call to [`iox2_attribute_set_new_clone()`]! +#[no_mangle] +pub unsafe extern "C" fn iox2_attribute_set_drop(handle: iox2_attribute_set_h) { + debug_assert!(!handle.is_null()); + + let attribute_set = &mut *handle.as_type(); + + core::ptr::drop_in_place(attribute_set.value.as_option_mut()); + (attribute_set.deleter)(attribute_set); +} + +#[no_mangle] +pub unsafe extern "C" fn iox2_cast_attribute_set_ptr( + handle: iox2_attribute_set_h, +) -> iox2_attribute_set_ptr { + debug_assert!(!handle.is_null()); + + (*handle.as_type()).value.as_ref() +} + /// Returns the length of the attribute set. /// /// # Safety /// /// * The `handle` must be a valid handle. #[no_mangle] -pub unsafe extern "C" fn iox2_attribute_set_len(handle: iox2_attribute_set_h_ref) -> usize { +pub unsafe extern "C" fn iox2_attribute_set_len(handle: iox2_attribute_set_ptr) -> usize { debug_assert!(!handle.is_null()); - let attribute_set = (*handle).underlying_type(); - attribute_set.iter().len() + (*handle).iter().len() } /// Returns a [`iox2_attribute_h_ref`] to the attribute stored at the provided index. @@ -61,14 +177,13 @@ pub unsafe extern "C" fn iox2_attribute_set_len(handle: iox2_attribute_set_h_ref /// * The `index` < [`iox2_attribute_set_len()`]. #[no_mangle] pub unsafe extern "C" fn iox2_attribute_set_at( - handle: iox2_attribute_set_h_ref, + handle: iox2_attribute_set_ptr, index: usize, ) -> iox2_attribute_h_ref { debug_assert!(!handle.is_null()); debug_assert!(index < iox2_attribute_set_len(handle)); - let attribute_set = (*handle).underlying_type(); - (&attribute_set[index] as *const Attribute).cast() + (&(*handle)[index] as *const Attribute).cast() } /// Returns the number of values stored under a specific key. If the key does not exist it @@ -80,7 +195,7 @@ pub unsafe extern "C" fn iox2_attribute_set_at( /// * `key` must be non-zero and contain a null-terminated string #[no_mangle] pub unsafe extern "C" fn iox2_attribute_set_get_key_value_len( - handle: iox2_attribute_set_h_ref, + handle: iox2_attribute_set_ptr, key: *const c_char, ) -> usize { debug_assert!(!handle.is_null()); @@ -92,8 +207,7 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_len( return 0; } - let attribute_set = (*handle).underlying_type(); - attribute_set.get_key_value_len(key.unwrap()) + (*handle).get_key_value_len(key.unwrap()) } /// Returns a value of a key at a specific index. The index enumerates the values of the key @@ -109,7 +223,7 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_len( /// * `buffer_len` must define the length of the memory pointed by `buffer` #[no_mangle] pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( - handle: iox2_attribute_set_h_ref, + handle: iox2_attribute_set_ptr, key: *const c_char, index: usize, buffer: *mut c_char, @@ -126,8 +240,7 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( return; } - let attribute_set = (*handle).underlying_type(); - if let Some(v) = attribute_set.get_key_value_at(key.unwrap(), index) { + if let Some(v) = (*handle).get_key_value_at(key.unwrap(), index) { if let Ok(value) = CString::new(v) { core::ptr::copy_nonoverlapping( value.as_ptr(), @@ -147,14 +260,13 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( /// * The `callback` must point to a function with the required signature. #[no_mangle] pub unsafe extern "C" fn iox2_attribute_set_get_key_values( - handle: iox2_attribute_set_h_ref, + handle: iox2_attribute_set_ptr, key: *const c_char, callback: iox2_attribute_set_get_callback, callback_ctx: iox2_callback_context, ) { debug_assert!(!handle.is_null()); - let attribute_set = (*handle).underlying_type(); let key = CStr::from_ptr(key); let c_str = key.to_str(); if c_str.is_err() { @@ -163,7 +275,7 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_values( let c_str = c_str.unwrap(); - attribute_set.get_key_values(c_str, |value| { + (*handle).get_key_values(c_str, |value| { if let Ok(value) = CString::new(value) { callback(value.as_ptr(), callback_ctx).into() } else { diff --git a/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs b/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs index 6b14402b8..d1d2ce8a5 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs @@ -24,7 +24,7 @@ use core::{ mem::ManuallyDrop, }; -use super::iox2_attribute_set_h_ref; +use super::iox2_attribute_set_ptr; // BEGIN type definition @@ -189,10 +189,10 @@ pub unsafe extern "C" fn iox2_attribute_specifier_define( #[no_mangle] pub unsafe extern "C" fn iox2_attribute_specifier_attributes( handle: iox2_attribute_specifier_h_ref, -) -> iox2_attribute_set_h_ref { +) -> iox2_attribute_set_ptr { debug_assert!(!handle.is_null()); let attribute_specifier_struct = &mut *handle.as_type(); - (attribute_specifier_struct.value.as_ref().0.attributes() as *const AttributeSet).cast() + attribute_specifier_struct.value.as_ref().0.attributes() } // END C API diff --git a/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs b/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs index d3eeb8165..0bc22b1a9 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs @@ -27,7 +27,7 @@ use core::{ extern crate alloc; use alloc::ffi::CString; -use super::iox2_attribute_set_h_ref; +use super::iox2_attribute_set_ptr; // BEGIN type definition @@ -217,11 +217,11 @@ pub unsafe extern "C" fn iox2_attribute_verifier_require_key( #[no_mangle] pub unsafe extern "C" fn iox2_attribute_verifier_attributes( handle: iox2_attribute_verifier_h_ref, -) -> iox2_attribute_set_h_ref { +) -> iox2_attribute_set_ptr { debug_assert!(!handle.is_null()); let attribute_verifier_struct = &mut *handle.as_type(); - (attribute_verifier_struct.value.as_ref().0.attributes() as *const AttributeSet).cast() + attribute_verifier_struct.value.as_ref().0.attributes() } /// Verifies if the [`iox2_attribute_set_h_ref`] contains all required keys and key-value pairs. @@ -235,7 +235,7 @@ pub unsafe extern "C" fn iox2_attribute_verifier_attributes( #[no_mangle] pub unsafe extern "C" fn iox2_attribute_verifier_verify_requirements( handle: iox2_attribute_verifier_h_ref, - rhs: iox2_attribute_set_h_ref, + rhs: iox2_attribute_set_ptr, incompatible_key_buffer: *mut c_char, incompatible_key_buffer_len: usize, ) -> bool { @@ -245,7 +245,7 @@ pub unsafe extern "C" fn iox2_attribute_verifier_verify_requirements( let attribute_verifier_struct = &mut *handle.as_type(); let attribute_verifier = &attribute_verifier_struct.value.as_ref().0; - match attribute_verifier.verify_requirements((*rhs).underlying_type()) { + match attribute_verifier.verify_requirements(&*rhs) { Ok(()) => true, Err(incompatible_key) => { if let Ok(incompatible_key) = CString::new(incompatible_key) { diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs index 1c64f00e3..1bd754a37 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs @@ -29,7 +29,7 @@ use core::ffi::{c_char, c_int}; use core::mem::ManuallyDrop; use super::{ - iox2_attribute_set_h_ref, iox2_callback_context, iox2_node_list_callback, + iox2_attribute_set_ptr, iox2_callback_context, iox2_node_list_callback, iox2_static_config_event_t, }; @@ -283,19 +283,15 @@ pub unsafe extern "C" fn iox2_port_factory_event_listener_builder( #[no_mangle] pub unsafe extern "C" fn iox2_port_factory_event_attributes( port_factory_handle: iox2_port_factory_event_h_ref, -) -> iox2_attribute_set_h_ref { +) -> iox2_attribute_set_ptr { use iceoryx2::prelude::PortFactory; port_factory_handle.assert_non_null(); let port_factory = &mut *port_factory_handle.as_type(); match port_factory.service_type { - iox2_service_type_e::IPC => { - (port_factory.value.as_ref().ipc.attributes() as *const AttributeSet).cast() - } - iox2_service_type_e::LOCAL => { - (port_factory.value.as_ref().local.attributes() as *const AttributeSet).cast() - } + iox2_service_type_e::IPC => port_factory.value.as_ref().ipc.attributes(), + iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.attributes(), } } diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index 0d7ef484c..fd86105f9 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -34,7 +34,7 @@ use core::{ }; use super::{ - iox2_attribute_set_h_ref, iox2_callback_context, iox2_node_list_callback, iox2_service_name_ptr, + iox2_attribute_set_ptr, iox2_callback_context, iox2_node_list_callback, iox2_service_name_ptr, }; // BEGIN types definition @@ -244,19 +244,15 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_subscriber_builder( #[no_mangle] pub unsafe extern "C" fn iox2_port_factory_pub_sub_attributes( port_factory_handle: iox2_port_factory_pub_sub_h_ref, -) -> iox2_attribute_set_h_ref { +) -> iox2_attribute_set_ptr { use iceoryx2::prelude::PortFactory; port_factory_handle.assert_non_null(); let port_factory = &mut *port_factory_handle.as_type(); match port_factory.service_type { - iox2_service_type_e::IPC => { - (port_factory.value.as_ref().ipc.attributes() as *const AttributeSet).cast() - } - iox2_service_type_e::LOCAL => { - (port_factory.value.as_ref().local.attributes() as *const AttributeSet).cast() - } + iox2_service_type_e::IPC => port_factory.value.as_ref().ipc.attributes(), + iox2_service_type_e::LOCAL => port_factory.value.as_ref().local.attributes(), } } diff --git a/iceoryx2-ffi/ffi/src/api/static_config.rs b/iceoryx2-ffi/ffi/src/api/static_config.rs index fd47bb23e..cb9586d86 100644 --- a/iceoryx2-ffi/ffi/src/api/static_config.rs +++ b/iceoryx2-ffi/ffi/src/api/static_config.rs @@ -14,10 +14,8 @@ use core::ffi::c_char; +use iceoryx2::service::static_config::messaging_pattern::MessagingPattern; use iceoryx2::service::static_config::StaticConfig; -use iceoryx2::{ - prelude::AttributeSet, service::static_config::messaging_pattern::MessagingPattern, -}; use iceoryx2_bb_log::fatal_panic; use crate::{ @@ -25,7 +23,7 @@ use crate::{ IOX2_SERVICE_ID_LENGTH, IOX2_SERVICE_NAME_LENGTH, }; -use super::iox2_attribute_set_h_ref; +use super::{iox2_attribute_set_h, iox2_attribute_set_new_clone}; #[derive(Clone, Copy)] #[repr(C)] @@ -41,11 +39,22 @@ pub struct iox2_static_config_t { pub name: [c_char; IOX2_SERVICE_NAME_LENGTH], pub messaging_pattern: iox2_messaging_pattern_e, pub details: iox2_static_config_details_t, - pub attributes: iox2_attribute_set_h_ref, + pub attributes: iox2_attribute_set_h, } impl From<&StaticConfig> for iox2_static_config_t { fn from(value: &StaticConfig) -> Self { + let mut attribute_handle_ptr: iox2_attribute_set_h = core::ptr::null_mut(); + + unsafe { + iox2_attribute_set_new_clone( + core::ptr::null_mut(), + value.attributes(), + &mut attribute_handle_ptr, + ) + }; + debug_assert!(!attribute_handle_ptr.is_null()); + Self { id: core::array::from_fn(|n| { debug_assert!(value.service_id().as_str().len() + 1 < IOX2_SERVICE_ID_LENGTH); @@ -66,7 +75,7 @@ impl From<&StaticConfig> for iox2_static_config_t { 0 } }), - attributes: (value.attributes() as *const AttributeSet).cast(), + attributes: attribute_handle_ptr, messaging_pattern: value.messaging_pattern().into(), details: { match value.messaging_pattern() { From 37fdc5074789270e10fca592fc915c05565757b4 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 14:40:50 +0200 Subject: [PATCH 17/22] [#264] AttributeSetView can be converted to owned version --- iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp | 7 +++++++ iceoryx2-ffi/cxx/src/attribute_set.cpp | 6 ++++++ iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp | 16 ++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp b/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp index 912e863be..14df21363 100644 --- a/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/attribute_set.hpp @@ -20,6 +20,8 @@ #include namespace iox2 { +class AttributeSet; + /// Represents all service attributes. They can be set when the service is created. /// /// @attention The parent from which the view was extracted MUST live longer than the @@ -49,6 +51,9 @@ class AttributeSetView { void get_key_values(const Attribute::Key& key, const iox::function& callback) const; + /// Creates a copy of the [`AttributeSetView`] that owns the attributes. + auto to_owned() const -> AttributeSet; + private: template friend class PortFactoryPublishSubscribe; @@ -97,6 +102,8 @@ class AttributeSet { const iox::function& callback) const; private: + friend class AttributeSetView; + explicit AttributeSet(iox2_attribute_set_h handle); void drop(); diff --git a/iceoryx2-ffi/cxx/src/attribute_set.cpp b/iceoryx2-ffi/cxx/src/attribute_set.cpp index 26b7ea7e3..63ba1dfc3 100644 --- a/iceoryx2-ffi/cxx/src/attribute_set.cpp +++ b/iceoryx2-ffi/cxx/src/attribute_set.cpp @@ -61,6 +61,12 @@ void AttributeSetView::get_key_values( iox2_attribute_set_get_key_values(m_handle, key.c_str(), get_key_values_callback, static_cast(&ctx)); } +auto AttributeSetView::to_owned() const -> AttributeSet { + iox2_attribute_set_h handle = nullptr; + iox2_attribute_set_new_clone(nullptr, m_handle, &handle); + return AttributeSet(handle); +} + ///////////////////////////// /// END: AttributeSetView ///////////////////////////// diff --git a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp index 6b0f9cdf5..6c3b0c413 100644 --- a/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/attribute_tests.cpp @@ -158,4 +158,20 @@ TEST(AttributeSet, get_key_value_at_works) { } } //NOLINTEND(readability-function-cognitive-complexity) + +TEST(AttributeSet, to_owned_works) { + auto key = Attribute::Key("your mind becomes a galaxy"); + auto value_1 = Attribute::Value("shiny and bright"); + auto value_2 = Attribute::Value("with spice aroma"); + + auto attribute_specifer = AttributeSpecifier().define(key, value_1).define(key, value_2); + auto attributes = attribute_specifer.attributes(); + auto attributes_owned = attributes.to_owned(); + + ASSERT_THAT(attributes_owned.len(), Eq(2)); + ASSERT_THAT(attributes_owned.at(0).key(), Eq(key)); + ASSERT_THAT(attributes_owned.at(1).key(), Eq(key)); + ASSERT_THAT(attributes_owned.at(0).value(), Eq(value_1)); + ASSERT_THAT(attributes_owned.at(1).value(), Eq(value_2)); +} } // namespace From 928b22cde58891c5b3f4e5503e67a40c0db35cbe Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 14:50:02 +0200 Subject: [PATCH 18/22] [#264] Address static code analysis warnings and add release note --- doc/release-notes/iceoryx2-unreleased.md | 2 ++ iceoryx2-ffi/cxx/tests/src/service_tests.cpp | 2 ++ iceoryx2-ffi/ffi/src/api/attribute_set.rs | 11 ++++++++++- .../ffi/src/api/attribute_specifier.rs | 4 ++-- iceoryx2-ffi/ffi/src/api/attribute_verifier.rs | 6 +++--- iceoryx2-ffi/ffi/src/api/port_factory_event.rs | 13 ++++++++----- .../ffi/src/api/port_factory_pub_sub.rs | 10 +++++++--- iceoryx2-ffi/ffi/src/api/service.rs | 18 +++++++----------- 8 files changed, 41 insertions(+), 25 deletions(-) diff --git a/doc/release-notes/iceoryx2-unreleased.md b/doc/release-notes/iceoryx2-unreleased.md index 86f758753..1488fb322 100644 --- a/doc/release-notes/iceoryx2-unreleased.md +++ b/doc/release-notes/iceoryx2-unreleased.md @@ -11,6 +11,8 @@ conflicts when merging. --> +* Full C/C++ Language bindings for all features + [#264](https://github.com/eclipse-iceoryx/iceoryx2/issues/264) * Read LogLevel from environment variable [#396](https://github.com/eclipse-iceoryx/iceoryx2/issues/396) * Lookup config file in default locations diff --git a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp index afbde4083..de16c2f62 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp @@ -142,6 +142,7 @@ TYPED_TEST(ServiceTest, list_works_with_attributes) { ASSERT_THAT(result.has_value(), Eq(true)); } +//NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by ASSERT_THAT TYPED_TEST(ServiceTest, details_works) { constexpr ServiceType SERVICE_TYPE = TestFixture::TYPE; @@ -195,4 +196,5 @@ TYPED_TEST(ServiceTest, details_works) { ASSERT_THAT(result.has_value(), Eq(true)); ASSERT_THAT(result->has_value(), Eq(false)); } +//NOLINTEND(readability-function-cognitive-complexity) } // namespace diff --git a/iceoryx2-ffi/ffi/src/api/attribute_set.rs b/iceoryx2-ffi/ffi/src/api/attribute_set.rs index 13d94b378..73cc0dba3 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_set.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_set.rs @@ -125,7 +125,7 @@ pub unsafe extern "C" fn iox2_attribute_set_new_clone( } unsafe { - (*struct_ptr).value.init((&*source_ptr).clone()); + (*struct_ptr).value.init((*source_ptr).clone()); } *handle_ptr = (*struct_ptr).as_handle(); @@ -148,6 +148,15 @@ pub unsafe extern "C" fn iox2_attribute_set_drop(handle: iox2_attribute_set_h) { (attribute_set.deleter)(attribute_set); } +/// This function casts a [`iox2_attribute_set_h`] into a [`iox2_attribute_set_ptr`] +/// +/// Returns a [`iox2_attribute_set_ptr`] +/// +/// # Safety +/// +/// * `handle` obtained by [`iox2_attribute_set_new_clone()`] +/// * The `handle` must be a valid handle. +/// * The `handle` is still valid after the call to this function. #[no_mangle] pub unsafe extern "C" fn iox2_cast_attribute_set_ptr( handle: iox2_attribute_set_h, diff --git a/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs b/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs index d1d2ce8a5..a0700a08c 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_specifier.rs @@ -180,12 +180,12 @@ pub unsafe extern "C" fn iox2_attribute_specifier_define( )); } -/// Returnes a [`iox2_attribute_set_h_ref`] to the underlying attribute set. +/// Returnes a [`iox2_attribute_set_ptr`] to the underlying attribute set. /// /// # Safety /// /// * The `handle` must point to an initialized [`iox2_attribute_specifier_h`]. -/// * The `handle` must live at least as long as the returned [`iox2_attribute_set_h_ref`]. +/// * The `handle` must live at least as long as the returned [`iox2_attribute_set_ptr`]. #[no_mangle] pub unsafe extern "C" fn iox2_attribute_specifier_attributes( handle: iox2_attribute_specifier_h_ref, diff --git a/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs b/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs index 0bc22b1a9..7c9f12980 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_verifier.rs @@ -208,12 +208,12 @@ pub unsafe extern "C" fn iox2_attribute_verifier_require_key( )); } -/// Returnes a [`iox2_attribute_set_h_ref`] to the underlying attribute set. +/// Returnes a [`iox2_attribute_set_ptr`] to the underlying attribute set. /// /// # Safety /// /// * The `handle` must point to an initialized [`iox2_attribute_verifier_h`]. -/// * The `handle` must live at least as long as the returned [`iox2_attribute_set_h_ref`]. +/// * The `handle` must live at least as long as the returned [`iox2_attribute_set_ptr`]. #[no_mangle] pub unsafe extern "C" fn iox2_attribute_verifier_attributes( handle: iox2_attribute_verifier_h_ref, @@ -224,7 +224,7 @@ pub unsafe extern "C" fn iox2_attribute_verifier_attributes( attribute_verifier_struct.value.as_ref().0.attributes() } -/// Verifies if the [`iox2_attribute_set_h_ref`] contains all required keys and key-value pairs. +/// Verifies if the [`iox2_attribute_set_ptr`] contains all required keys and key-value pairs. /// /// # Safety /// diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs index 1bd754a37..77bd0d6d6 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_event.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_event.rs @@ -387,18 +387,21 @@ pub unsafe extern "C" fn iox2_port_factory_event_service_id( buffer.add(len).write(0); } -/// Calls the callback repeatedly with an [`iox2_node_state_e`], [`iox2_node_id_ptr`], -/// [´iox2_node_name_ptr´] and [`iox2_config_ptr`] for all [`Node`](iceoryx2::node::Node)s that +/// Calls the callback repeatedly with an [`iox2_node_state_e`](crate::api::iox2_node_state_e), +/// [`iox2_node_id_ptr`](crate::api::iox2_node_id_ptr), +/// [´iox2_node_name_ptr´](crate::api::iox2_node_name_ptr) and +/// [`iox2_config_ptr`](crate::api::iox2_config_ptr) for all [`Node`](iceoryx2::node::Node)s that /// have opened the service. /// -/// Returns IOX2_OK on success, an [`iox2_node_list_failure_e`] otherwise. +/// Returns IOX2_OK on success, an +/// [`iox2_node_list_failure_e`](crate::api::iox2_node_list_failure_e) otherwise. /// /// # Safety /// /// * The `handle` must be valid and obtained by [`iox2_service_builder_pub_sub_open`](crate::iox2_service_builder_pub_sub_open) or /// [`iox2_service_builder_pub_sub_open_or_create`](crate::iox2_service_builder_pub_sub_open_or_create)! -/// * `callback` - A valid callback with [`iox2_node_list_callback`} signature -/// * `callback_ctx` - An optional callback context [`iox2_callback_context`} to e.g. store information across callback iterations +/// * `callback` - A valid callback with [`iox2_node_list_callback`] signature +/// * `callback_ctx` - An optional callback context [`iox2_callback_context`] to e.g. store information across callback iterations #[no_mangle] pub unsafe extern "C" fn iox2_port_factory_event_nodes( handle: iox2_port_factory_event_h_ref, diff --git a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs index fd86105f9..147122f19 100644 --- a/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs +++ b/iceoryx2-ffi/ffi/src/api/port_factory_pub_sub.rs @@ -313,11 +313,15 @@ pub unsafe extern "C" fn iox2_port_factory_pub_sub_dynamic_config_number_of_publ } } -/// Calls the callback repeatedly with an [`iox2_node_state_e`], [`iox2_node_id_ptr`], -/// [´iox2_node_name_ptr´] and [`iox2_config_ptr`] for all [`Node`](iceoryx2::node::Node)s that +/// Calls the callback repeatedly with an [`iox2_node_state_e`](crate::api::iox2_node_state_e), +/// [`iox2_node_id_ptr`](crate::api::iox2_node_id_ptr), +/// [´iox2_node_name_ptr´](crate::api::iox2_node_name_ptr) and +/// [`iox2_config_ptr`](crate::api::iox2_config_ptr) for all +/// [`Node`](iceoryx2::node::Node)s that /// have opened the service. /// -/// Returns IOX2_OK on success, an [`iox2_node_list_failure_e`] otherwise. +/// Returns IOX2_OK on success, an +/// [`iox2_node_list_failure_e`](crate::api::iox2_node_list_failure_e) otherwise. /// /// # Safety /// diff --git a/iceoryx2-ffi/ffi/src/api/service.rs b/iceoryx2-ffi/ffi/src/api/service.rs index 5974c6f34..89bfa94fb 100644 --- a/iceoryx2-ffi/ffi/src/api/service.rs +++ b/iceoryx2-ffi/ffi/src/api/service.rs @@ -252,15 +252,13 @@ pub unsafe extern "C" fn iox2_service_details( match ipc::Service::details(service_name, config, messaging_pattern) { Ok(None) => { does_exist.write(false); - return IOX2_OK; - } - Err(e) => { - return e.into_c_int(); + IOX2_OK } + Err(e) => e.into_c_int(), Ok(Some(v)) => { service_details.write((&v.static_details).into()); does_exist.write(true); - return IOX2_OK; + IOX2_OK } } } @@ -268,19 +266,17 @@ pub unsafe extern "C" fn iox2_service_details( match local::Service::details(service_name, config, messaging_pattern) { Ok(None) => { does_exist.write(false); - return IOX2_OK; - } - Err(e) => { - return e.into_c_int(); + IOX2_OK } + Err(e) => e.into_c_int(), Ok(Some(v)) => { service_details.write((&v.static_details).into()); does_exist.write(true); - return IOX2_OK; + IOX2_OK } } } - }; + } } fn list_callback( From f978318790dbd7ac9b5ad42d07129e52ff1c66d0 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 18:13:24 +0200 Subject: [PATCH 19/22] [#264] C++ idiomatic naming; increase clang-tidy CI timeout; add dev docu --- .github/workflows/build-test.yml | 2 +- .../include/iox2/internal/callback_context.hpp | 18 ++++++++++++++++++ iceoryx2-ffi/cxx/include/iox2/service_id.hpp | 2 +- iceoryx2-ffi/cxx/src/service_id.cpp | 2 +- .../cxx/tests/src/service_event_tests.cpp | 4 ++-- .../src/service_publish_subscribe_tests.cpp | 4 ++-- iceoryx2-ffi/cxx/tests/src/service_tests.cpp | 8 ++++---- 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4bad92489..33098a4cf 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -121,7 +121,7 @@ jobs: static-code-analysis-cpp: needs: preflight-check if: ${{ needs.changes.outputs.source-code == 'true' }} - timeout-minutes: 10 + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Checkout sources diff --git a/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp b/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp index 29d72abc2..af395f487 100644 --- a/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/internal/callback_context.hpp @@ -22,6 +22,24 @@ #include "iox2/service_type.hpp" namespace iox2::internal { + +/// Building block to provide a type-safe context pointer to a C callback +/// that has a `void*` context argument. +/// The context could be hereby a user provided clojure with capture or +/// any other C++ object. +/// +/// # Example +/// +/// ``` +/// void some_c_callback(void* context) { +/// auto ctx = internal::ctx_cast(context); +/// ctx->value(); // access underlying my_context_object +/// } +/// +/// SomeType my_context_object; +/// auto ctx = internal::ctx(my_context_object); +/// some_c_callback(static_cast(&ctx)); +/// ``` template class CallbackContext { public: diff --git a/iceoryx2-ffi/cxx/include/iox2/service_id.hpp b/iceoryx2-ffi/cxx/include/iox2/service_id.hpp index 985c81258..cbe88f0cb 100644 --- a/iceoryx2-ffi/cxx/include/iox2/service_id.hpp +++ b/iceoryx2-ffi/cxx/include/iox2/service_id.hpp @@ -24,7 +24,7 @@ class ServiceId { auto max_len() -> uint64_t; /// Returns the string value of the [`ServiceId`] - auto as_str() const -> const char*; + auto c_str() const -> const char*; private: explicit ServiceId(const iox::string& value); diff --git a/iceoryx2-ffi/cxx/src/service_id.cpp b/iceoryx2-ffi/cxx/src/service_id.cpp index 2f0977d59..b63230f8c 100644 --- a/iceoryx2-ffi/cxx/src/service_id.cpp +++ b/iceoryx2-ffi/cxx/src/service_id.cpp @@ -17,7 +17,7 @@ auto ServiceId::max_len() -> uint64_t { return IOX2_SERVICE_ID_LENGTH; } -auto ServiceId::as_str() const -> const char* { +auto ServiceId::c_str() const -> const char* { return m_value.c_str(); } diff --git a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp index e4e574f70..2c6705800 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_event_tests.cpp @@ -609,8 +609,8 @@ TYPED_TEST(ServiceEventTest, service_id_is_unique_per_service) { auto service_1_open = node.service_builder(service_name_1).event().open().expect(""); auto service_2 = node.service_builder(service_name_2).event().create().expect(""); - ASSERT_THAT(service_1_create.service_id().as_str(), StrEq(service_1_open.service_id().as_str())); - ASSERT_THAT(service_1_create.service_id().as_str(), Not(StrEq(service_2.service_id().as_str()))); + ASSERT_THAT(service_1_create.service_id().c_str(), StrEq(service_1_open.service_id().c_str())); + ASSERT_THAT(service_1_create.service_id().c_str(), Not(StrEq(service_2.service_id().c_str()))); } //NOLINTBEGIN(readability-function-cognitive-complexity), false positive caused by ASSERT_THAT diff --git a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp index 383edff2b..385f546c0 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_publish_subscribe_tests.cpp @@ -1369,8 +1369,8 @@ TYPED_TEST(ServicePublishSubscribeTest, service_id_is_unique_per_service) { auto service_1_open = node.service_builder(service_name_1).template publish_subscribe().open().expect(""); auto service_2 = node.service_builder(service_name_2).template publish_subscribe().create().expect(""); - ASSERT_THAT(service_1_create.service_id().as_str(), StrEq(service_1_open.service_id().as_str())); - ASSERT_THAT(service_1_create.service_id().as_str(), Not(StrEq(service_2.service_id().as_str()))); + ASSERT_THAT(service_1_create.service_id().c_str(), StrEq(service_1_open.service_id().c_str())); + ASSERT_THAT(service_1_create.service_id().c_str(), Not(StrEq(service_2.service_id().c_str()))); } // END tests for customizable payload and user header type name } // namespace diff --git a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp index de16c2f62..af5355a5b 100644 --- a/iceoryx2-ffi/cxx/tests/src/service_tests.cpp +++ b/iceoryx2-ffi/cxx/tests/src/service_tests.cpp @@ -72,10 +72,10 @@ TYPED_TEST(ServiceTest, list_works) { auto verify = [&](auto details) -> CallbackProgression { if (details.static_details.messaging_pattern() == MessagingPattern::PublishSubscribe) { EXPECT_THAT(details.static_details.name(), StrEq(service_name_1.to_string().c_str())); - EXPECT_THAT(details.static_details.id(), StrEq(sut_1.service_id().as_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_1.service_id().c_str())); } else { EXPECT_THAT(details.static_details.name(), StrEq(service_name_2.to_string().c_str())); - EXPECT_THAT(details.static_details.id(), StrEq(sut_2.service_id().as_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_2.service_id().c_str())); } return CallbackProgression::Continue; @@ -111,7 +111,7 @@ TYPED_TEST(ServiceTest, list_works_with_attributes) { auto verify = [&](auto details) -> CallbackProgression { if (details.static_details.messaging_pattern() == MessagingPattern::PublishSubscribe) { EXPECT_THAT(details.static_details.name(), StrEq(service_name_1.to_string().c_str())); - EXPECT_THAT(details.static_details.id(), StrEq(sut_1.service_id().as_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_1.service_id().c_str())); auto counter = 0; details.static_details.attributes().get_key_values(key_1, [&](auto& value) -> CallbackProgression { @@ -130,7 +130,7 @@ TYPED_TEST(ServiceTest, list_works_with_attributes) { EXPECT_THAT(counter, Eq(1)); } else { EXPECT_THAT(details.static_details.name(), StrEq(service_name_2.to_string().c_str())); - EXPECT_THAT(details.static_details.id(), StrEq(sut_2.service_id().as_str())); + EXPECT_THAT(details.static_details.id(), StrEq(sut_2.service_id().c_str())); } return CallbackProgression::Continue; From 9abff52cca4ee1f65cfb13c5932e93ff87acbdde Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 18:40:13 +0200 Subject: [PATCH 20/22] [#264] Fix undefined behavior in attribute set list --- iceoryx2-ffi/ffi/src/api/attribute_set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iceoryx2-ffi/ffi/src/api/attribute_set.rs b/iceoryx2-ffi/ffi/src/api/attribute_set.rs index 73cc0dba3..e3a828edf 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_set.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_set.rs @@ -243,9 +243,9 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( debug_assert!(!buffer.is_null()); debug_assert!(0 < buffer_len); + buffer.add(0).write(0); let key = CStr::from_ptr(key).to_str(); if key.is_err() { - buffer.add(0).write(0); return; } From ad96149f06ae79b27ecad0616e0ba346492a84f6 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 19:44:48 +0200 Subject: [PATCH 21/22] [#264] Add Windows bigobj fix --- iceoryx2-ffi/cxx/tests/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/iceoryx2-ffi/cxx/tests/CMakeLists.txt b/iceoryx2-ffi/cxx/tests/CMakeLists.txt index 6fadc0284..9fcc759a6 100644 --- a/iceoryx2-ffi/cxx/tests/CMakeLists.txt +++ b/iceoryx2-ffi/cxx/tests/CMakeLists.txt @@ -23,6 +23,7 @@ file(GLOB TEST_FILES src/*.cpp) add_executable(${PROJECT_NAME} ${TEST_FILES}) target_link_libraries(${PROJECT_NAME} iceoryx2-cxx::static-lib-cxx GTest::gtest GTest::gmock) +target_compile_options(${PROJECT_NAME} PRIVATE $<$:/bigobj>) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17 From 17bdee97543ab824659dada58c2ead4e17fbf8c9 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Wed, 23 Apr 2025 20:02:46 +0200 Subject: [PATCH 22/22] [#264] Allow empty values for attribute keys in C/C++ binding --- iceoryx2-ffi/cxx/src/attribute_set.cpp | 6 ++++-- iceoryx2-ffi/ffi/src/api/attribute_set.rs | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/iceoryx2-ffi/cxx/src/attribute_set.cpp b/iceoryx2-ffi/cxx/src/attribute_set.cpp index 63ba1dfc3..b1c1af984 100644 --- a/iceoryx2-ffi/cxx/src/attribute_set.cpp +++ b/iceoryx2-ffi/cxx/src/attribute_set.cpp @@ -46,9 +46,11 @@ auto AttributeSetView::get_key_value_len(const Attribute::Key& key) const -> uin auto AttributeSetView::get_key_value_at(const Attribute::Key& key, const uint64_t idx) -> iox::optional { iox::UninitializedArray buffer; - iox2_attribute_set_get_key_value_at(m_handle, key.c_str(), idx, &buffer[0], Attribute::Value::capacity()); + bool has_value = false; + iox2_attribute_set_get_key_value_at( + m_handle, key.c_str(), idx, &buffer[0], Attribute::Value::capacity(), &has_value); - if (buffer[0] == 0) { + if (!has_value) { return iox::nullopt; } diff --git a/iceoryx2-ffi/ffi/src/api/attribute_set.rs b/iceoryx2-ffi/ffi/src/api/attribute_set.rs index e3a828edf..1a0b9bb4c 100644 --- a/iceoryx2-ffi/ffi/src/api/attribute_set.rs +++ b/iceoryx2-ffi/ffi/src/api/attribute_set.rs @@ -237,13 +237,14 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( index: usize, buffer: *mut c_char, buffer_len: usize, + has_value: *mut bool, ) { debug_assert!(!handle.is_null()); debug_assert!(!key.is_null()); debug_assert!(!buffer.is_null()); debug_assert!(0 < buffer_len); - buffer.add(0).write(0); + *has_value = false; let key = CStr::from_ptr(key).to_str(); if key.is_err() { return; @@ -256,6 +257,7 @@ pub unsafe extern "C" fn iox2_attribute_set_get_key_value_at( buffer, buffer_len.min(value.count_bytes() + 1 /* null terminator */), ); + *has_value = true; } } }