diff --git a/doc/website/release-notes/iceoryx-v2-0-0.md b/doc/website/release-notes/iceoryx-v2-0-0.md index 96a538832a..543c449642 100644 --- a/doc/website/release-notes/iceoryx-v2-0-0.md +++ b/doc/website/release-notes/iceoryx-v2-0-0.md @@ -36,6 +36,7 @@ - Replace IPC-channel-based `findService` with pub/sub-based on [#415](https://github.com/eclipse-iceoryx/iceoryx/issues/415) - Add `findService` method to `ServiceDiscovery` which applies a callable to all matching services [\#1105](https://github.com/eclipse-iceoryx/iceoryx/pull/1105) - Increase limits of `ServiceRegistry` to support the maximum number of publishers and servers that are configured in `iceoryx_posh_types.hpp` [\#1074](https://github.com/eclipse-iceoryx/iceoryx/issues/1074) +- C binding for service discovery [\#1142](https://github.com/eclipse-iceoryx/iceoryx/issues/1142) **Bugfixes:** diff --git a/iceoryx_binding_c/CMakeLists.txt b/iceoryx_binding_c/CMakeLists.txt index 3c8dbffdef..faa8d74eee 100644 --- a/iceoryx_binding_c/CMakeLists.txt +++ b/iceoryx_binding_c/CMakeLists.txt @@ -73,6 +73,7 @@ add_library(${PROJECT_NAME} source/cpp2c_publisher.cpp source/cpp2c_subscriber.cpp source/cpp2c_service_description_translation.cpp + source/c_service_discovery.cpp ) add_library(${PROJECT_NAMESPACE}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/api.h b/iceoryx_binding_c/include/iceoryx_binding_c/api.h index b236c9d0d6..a5291b2f0f 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/api.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/api.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ #include "runtime.h" #include "server.h" #include "service_description.h" +#include "service_discovery.h" #include "subscriber.h" #include "types.h" #include "user_trigger.h" diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/enums.h b/iceoryx_binding_c/include/iceoryx_binding_c/enums.h index 010c3f24a8..e5632a9531 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/enums.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/enums.h @@ -84,6 +84,12 @@ enum iox_ChunkReceiveResult ChunkReceiveResult_SUCCESS, }; +/// @brief describes events which can be triggered by a service discovery +enum iox_ServiceDiscoveryEvent +{ + ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED +}; + /// @brief Used by consumers to request a specific behavior from the producer; describes whether a producer blocks when /// consumer queue is full enum iox_QueueFullPolicy @@ -143,4 +149,11 @@ enum iox_ServerRequestResult ServerRequestResult_SUCCESS }; +/// @brief used to describe the messaging pattern +enum iox_MessagingPattern +{ + MessagingPattern_PUB_SUB, + MessagingPattern_REQ_RES +}; + #endif diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/internal/c2cpp_enum_translation.hpp b/iceoryx_binding_c/include/iceoryx_binding_c/internal/c2cpp_enum_translation.hpp index 64aacdcd5e..56f39ef155 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/internal/c2cpp_enum_translation.hpp +++ b/iceoryx_binding_c/include/iceoryx_binding_c/internal/c2cpp_enum_translation.hpp @@ -21,6 +21,7 @@ #include "iceoryx_binding_c/enums.h" #include "iceoryx_posh/popo/base_subscriber.hpp" #include "iceoryx_posh/popo/subscriber.hpp" +#include "iceoryx_posh/runtime/service_discovery.hpp" namespace c2cpp { @@ -33,6 +34,9 @@ iox::popo::ClientEvent clientEvent(const iox_ClientEvent value) noexcept; iox::popo::ClientState clientState(const iox_ClientState value) noexcept; iox::popo::ServerEvent serverEvent(const iox_ServerEvent value) noexcept; iox::popo::ServerState serverState(const iox_ServerState value) noexcept; + +iox::runtime::ServiceDiscoveryEvent serviceDiscoveryEvent(const iox_ServiceDiscoveryEvent value) noexcept; +iox::popo::MessagingPattern messagingPattern(const iox_MessagingPattern value) noexcept; } // namespace c2cpp #endif diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/listener.h b/iceoryx_binding_c/include/iceoryx_binding_c/listener.h index f427198a64..6aca47663d 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/listener.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/listener.h @@ -21,6 +21,7 @@ #include "iceoryx_binding_c/enums.h" #include "iceoryx_binding_c/internal/c2cpp_binding.h" #include "iceoryx_binding_c/server.h" +#include "iceoryx_binding_c/service_discovery.h" #include "iceoryx_binding_c/subscriber.h" #include "iceoryx_binding_c/types.h" #include "iceoryx_binding_c/user_trigger.h" @@ -174,4 +175,40 @@ ENUM iox_ListenerResult iox_listener_attach_server_event_with_context_data(iox_l void iox_listener_detach_server_event(iox_listener_t const self, iox_server_t const server, const ENUM iox_ServerEvent serverEvent); + +/// @brief Attaches a service discovery event to the listener +/// @param[in] self listener to which the event should be attached to +/// @param[in] serviceDiscovery service discovery which emits the event +/// @param[in] serviceDiscoveryEvent the event which should trigger the listener +/// @param[in] callback the callback which is called when an event triggers the listener +/// @return when successful iox_ListenerResult::ListenerResult_SUCCESS otherwise an enum which describes the error +ENUM iox_ListenerResult +iox_listener_attach_service_discovery_event(iox_listener_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + void (*callback)(iox_service_discovery_t)); + +/// @brief Attaches a service discovery event to the listener. The callback has an additional contextData argument to +/// provide access to user defined data. +/// @param[in] self listener to which the event should be attached to +/// @param[in] serviceDiscovery service discovery which emits the event +/// @param[in] serviceDiscoveryEvent the event which should trigger the listener +/// @param[in] callback the callback which is called when an event triggers the listener +/// @param[in] contextData a void pointer which is provided as second argument to the callback +/// @return when successful iox_ListenerResult::ListenerResult_SUCCESS otherwise an enum which describes the error +ENUM iox_ListenerResult iox_listener_attach_service_discovery_event_with_context_data( + iox_listener_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + void (*callback)(iox_service_discovery_t, void*), + void* const contextData); + +/// @brief Detaches a service discovery event from the listener +/// @param[in] self listener from which the event should be detached +/// @param[in] serviceDiscovery the service discovery which emits the event +/// @param[in] serviceDiscoveryEvent the service discovery event which should be removed from the listener +void iox_listener_detach_service_discovery_event(iox_listener_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent); + #endif diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/notification_info.h b/iceoryx_binding_c/include/iceoryx_binding_c/notification_info.h index 221f218c3d..9ad8a80735 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/notification_info.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/notification_info.h @@ -20,6 +20,7 @@ #include "iceoryx_binding_c/client.h" #include "iceoryx_binding_c/internal/c2cpp_binding.h" #include "iceoryx_binding_c/server.h" +#include "iceoryx_binding_c/service_discovery.h" #include "iceoryx_binding_c/subscriber.h" #include "iceoryx_binding_c/user_trigger.h" @@ -57,6 +58,13 @@ bool iox_notification_info_does_originate_from_client(iox_notification_info_t co /// @return true if the notification originates from the server, otherwise false bool iox_notification_info_does_originate_from_server(iox_notification_info_t const self, iox_server_t const server); +/// @brief does the notification originate from a certain service discovery +/// @param[in] self handle to notification info +/// @param[in] serviceDiscovery handle to serviceDiscovery in question +/// @return true if the notifiaction originates from the service discovery, otherwise false +bool iox_notification_info_does_originate_from_service_discovery(iox_notification_info_t const self, + iox_service_discovery_t const serviceDiscovery); + /// @brief acquires the handle of the subscriber origin /// @param[in] self handle to notification info /// @return the handle to the subscriber if the notification originated from a subscriber, otherwise NULL @@ -77,6 +85,11 @@ iox_client_t iox_notification_info_get_client_origin(iox_notification_info_t con /// @return the handle to the server if the notification originated from a server, otherwise NULL iox_server_t iox_notification_info_get_server_origin(iox_notification_info_t const self); +/// @brief acquires the handle of the service discovery origin +/// @param[in] self handle to the notification info +/// @return the handle to the service discovery if the notification originated from a service discovery, otherwise NULL +iox_service_discovery_t iox_notification_info_get_service_discovery_origin(iox_notification_info_t const self); + /// @brief calls the callback of the notification /// @param[in] self handle to notification info void iox_notification_info_call(iox_notification_info_t const self); diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/service_discovery.h b/iceoryx_binding_c/include/iceoryx_binding_c/service_discovery.h new file mode 100644 index 0000000000..b48d821143 --- /dev/null +++ b/iceoryx_binding_c/include/iceoryx_binding_c/service_discovery.h @@ -0,0 +1,91 @@ +// Copyright (c) 2022 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef IOX_BINDING_C_SERVICE_DISCOVERY_H +#define IOX_BINDING_C_SERVICE_DISCOVERY_H + +#include "iceoryx_binding_c/enums.h" +#include "iceoryx_binding_c/internal/c2cpp_binding.h" +#include "iceoryx_binding_c/types.h" +#include "service_description.h" + +/// @brief service discovery handle +typedef CLASS ServiceDiscovery* iox_service_discovery_t; + +/// @brief initializes a service discovery from a storage struct pointer +/// @param[in] self pointer to raw memory which can hold a service discovery +/// @return an initialized iox_service_discovery_t +iox_service_discovery_t iox_service_discovery_init(iox_service_discovery_storage_t* self); + +/// @brief after using an iox_service_discovery_t it must be cleaned up with this function +/// @param[in] self the service discovery which should be deinitialized +void iox_service_discovery_deinit(iox_service_discovery_t const self); + +/// @brief Searches all services with the given messaging pattern that match the provided service description +/// @param[in] self handle of the service discovery +/// @param[in] service service string to search for, a nullptr corresponds to a wildcard +/// @param[in] instance instance string to search for, a nullptr corresponds to a wildcard +/// @param[in] event event string to search for, a nullptr corresponds to a wildcard +/// @param[in] serviceContainer preallocated memory to an array of iox_service_description_t in which the matching +/// services can be written +/// @param[in] serviceContainerCapacity the capacity of the preallocated serviceContainer +/// @param[in] missedServices if the serviceContainer has insufficient size the number of missed services which could +/// not be written into the serviceContainer are stored here +/// @param[in] pattern messaging pattern of the service to search +/// @return the number of services which were written into the serviceContainer +uint64_t iox_service_discovery_find_service(iox_service_discovery_t const self, + const char* const service, + const char* const instance, + const char* const event, + iox_service_description_t* const serviceContainer, + const uint64_t serviceContainerCapacity, + uint64_t* missedServices, + const ENUM iox_MessagingPattern pattern); + +/// @brief Searches all services with the given messaging pattern that match the provided service description and +/// applies a function to each of them +/// @param[in] self handle of the service discovery +/// @param[in] service service string to search for, a nullptr corresponds to a wildcard +/// @param[in] instance instance string to search for, a nullptr corresponds to a wildcard +/// @param[in] event event string to search for, a nullptr corresponds to a wildcard +/// @param[in] callable to apply to all matching services +/// @param[in] pattern messaging pattern of the service to search +void iox_service_discovery_find_service_apply_callable(iox_service_discovery_t const self, + const char* const service, + const char* const instance, + const char* const event, + void (*callable)(const iox_service_description_t), + const ENUM iox_MessagingPattern pattern); + +/// @brief Searches all services with the given messaging pattern that match the provided service description and +/// applies a function to each of them. A second parameter for the function can be provided as contextData. +/// @param[in] self handle of the service discovery +/// @param[in] service service string to search for, a nullptr corresponds to a wildcard +/// @param[in] instance instance string to search for, a nullptr corresponds to a wildcard +/// @param[in] event event string to search for, a nullptr corresponds to a wildcard +/// @param[in] callable to apply to all matching services +/// @param[in] contextData a void pointer which is provided as second argument to the callback +/// @param[in] pattern messaging pattern of the service to search +void iox_service_discovery_find_service_apply_callable_with_context_data( + iox_service_discovery_t const self, + const char* const service, + const char* const instance, + const char* const event, + void (*callable)(const iox_service_description_t, void*), + void* const contextData, + const ENUM iox_MessagingPattern pattern); + +#endif diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/types.h b/iceoryx_binding_c/include/iceoryx_binding_c/types.h index 0d0d649394..c35fb86530 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/types.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/types.h @@ -120,5 +120,17 @@ typedef struct #endif } iox_server_storage_t; +/// @brief has exactly the size required to store the underlying object of iox_service_discovery_t +struct iox_service_discovery_storage_t +{ + // the value of the array size is the result of the following formula: + // sizeof(ServiceDiscovery) / 8 +#if defined(__APPLE__) + uint64_t do_not_touch_me[49175]; +#else + uint64_t do_not_touch_me[49172]; +#endif +}; +typedef struct iox_service_discovery_storage_t iox_service_discovery_storage_t; #endif diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/wait_set.h b/iceoryx_binding_c/include/iceoryx_binding_c/wait_set.h index e687c09948..3624235d40 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/wait_set.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/wait_set.h @@ -22,6 +22,7 @@ #include "iceoryx_binding_c/enums.h" #include "iceoryx_binding_c/internal/c2cpp_binding.h" #include "iceoryx_binding_c/notification_info.h" +#include "iceoryx_binding_c/service_discovery.h" #include "iceoryx_binding_c/subscriber.h" #include "iceoryx_binding_c/types.h" #include "iceoryx_binding_c/user_trigger.h" @@ -333,4 +334,44 @@ void iox_ws_detach_server_event(iox_ws_t const self, iox_server_t const server, /// @param[in] server the server which should be detached /// @param[in] serverState the state which should be detached from the server void iox_ws_detach_server_state(iox_ws_t const self, iox_server_t const server, const ENUM iox_ServerState serverState); + +/// @brief attaches a service discovery event to a waitset +/// @param[in] self handle to the waitset +/// @param[in] serviceDiscovery service discovery which emits the event +/// @param[in] serviceDiscoveryEvent the event which should be attached +/// @param[in] eventId an arbitrary id which will be tagged to the event +/// @param[in] callback a callback which is attached to the event +/// @return if the attaching was successfull it returns WaitSetResult_SUCCESS, otherwise +/// an enum which describes the error +ENUM iox_WaitSetResult iox_ws_attach_service_discovery_event(const iox_ws_t self, + const iox_service_discovery_t serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + const uint64_t eventId, + void (*callback)(iox_service_discovery_t)); + +/// @brief attaches a service discovery event to a waitset with additional context data for the callback +/// @param[in] self handle to the waitset +/// @param[in] serviceDiscovery service discovery which emits the event +/// @param[in] serviceDiscoveryEvent the event which should be attached +/// @param[in] eventId an arbitrary id which will be tagged to the event +/// @param[in] callback a callback which is attached to the event +/// @param[in] contextData a void pointer which is provided as second argument to the callback +/// @return if the attaching was successfull it returns WaitSetResult_SUCCESS, otherwise +/// an enum which describes the error +ENUM iox_WaitSetResult +iox_ws_attach_service_discovery_event_with_context_data(iox_ws_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + const uint64_t eventId, + void (*callback)(iox_service_discovery_t, void*), + void* const contextData); + +/// @brief detaches a service discovery event from a waitset +/// @param[in] self handle to the waitset +/// @param[in] serviceDiscovery the service discovery which should be detached +/// @param[in] serviceDiscoveryEvent the event which should be detached from the service discovery +void iox_ws_detach_service_discovery_event(iox_ws_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent); + #endif diff --git a/iceoryx_binding_c/source/c2cpp_enum_translation.cpp b/iceoryx_binding_c/source/c2cpp_enum_translation.cpp index 97b2a38f59..3192de8e08 100644 --- a/iceoryx_binding_c/source/c2cpp_enum_translation.cpp +++ b/iceoryx_binding_c/source/c2cpp_enum_translation.cpp @@ -126,4 +126,33 @@ iox::popo::ServerState serverState(const iox_ServerState value) noexcept errorHandler(iox::Error::kBINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_SERVER_STATE_VALUE); return iox::popo::ServerState::HAS_REQUEST; } + +iox::runtime::ServiceDiscoveryEvent serviceDiscoveryEvent(const iox_ServiceDiscoveryEvent value) noexcept +{ + switch (value) + { + case ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED: + return iox::runtime::ServiceDiscoveryEvent::SERVICE_REGISTRY_CHANGED; + } + + iox::LogFatal() << "invalid iox_ServiceDiscoveryEvent value"; + errorHandler(iox::Error::kBINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_SERVICE_DISCOVERY_EVENT_VALUE); + return iox::runtime::ServiceDiscoveryEvent::SERVICE_REGISTRY_CHANGED; +} + +iox::popo::MessagingPattern messagingPattern(const iox_MessagingPattern value) noexcept +{ + switch (value) + { + case MessagingPattern_PUB_SUB: + return iox::popo::MessagingPattern::PUB_SUB; + case MessagingPattern_REQ_RES: + return iox::popo::MessagingPattern::REQ_RES; + } + + iox::LogFatal() << "invalid iox_MessagingPattern value"; + errorHandler(iox::Error::kBINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_MESSAGING_PATTERN_VALUE); + return iox::popo::MessagingPattern::PUB_SUB; +} + } // namespace c2cpp diff --git a/iceoryx_binding_c/source/c_listener.cpp b/iceoryx_binding_c/source/c_listener.cpp index b489dc1173..dce9b7bedc 100644 --- a/iceoryx_binding_c/source/c_listener.cpp +++ b/iceoryx_binding_c/source/c_listener.cpp @@ -26,6 +26,7 @@ using namespace iox; using namespace iox::popo; +using namespace iox::runtime; extern "C" { #include "iceoryx_binding_c/listener.h" @@ -251,3 +252,50 @@ void iox_listener_detach_server_event(iox_listener_t const self, self->detachEvent(*server, c2cpp::serverEvent(serverEvent)); } + +iox_ListenerResult +iox_listener_attach_service_discovery_event(iox_listener_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + void (*callback)(iox_service_discovery_t)) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceDiscovery != nullptr); + iox::cxx::Expects(callback != nullptr); + + auto result = self->attachEvent(*serviceDiscovery, + c2cpp::serviceDiscoveryEvent(serviceDiscoveryEvent), + NotificationCallback{callback, nullptr}); + + return (result.has_error()) ? cpp2c::listenerResult(result.get_error()) + : iox_ListenerResult::ListenerResult_SUCCESS; +} + +iox_ListenerResult iox_listener_attach_service_discovery_event_with_context_data( + iox_listener_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + void (*callback)(iox_service_discovery_t, void*), + void* const contextData) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceDiscovery != nullptr); + iox::cxx::Expects(callback != nullptr); + + auto result = self->attachEvent(*serviceDiscovery, + c2cpp::serviceDiscoveryEvent(serviceDiscoveryEvent), + NotificationCallback{callback, contextData}); + + return (result.has_error()) ? cpp2c::listenerResult(result.get_error()) + : iox_ListenerResult::ListenerResult_SUCCESS; +} + +void iox_listener_detach_service_discovery_event(iox_listener_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceDiscovery != nullptr); + + self->detachEvent(*serviceDiscovery, c2cpp::serviceDiscoveryEvent(serviceDiscoveryEvent)); +} diff --git a/iceoryx_binding_c/source/c_notification_info.cpp b/iceoryx_binding_c/source/c_notification_info.cpp index 7f605ca3ef..29d2143c85 100644 --- a/iceoryx_binding_c/source/c_notification_info.cpp +++ b/iceoryx_binding_c/source/c_notification_info.cpp @@ -19,9 +19,11 @@ #include "iceoryx_posh/popo/untyped_client.hpp" #include "iceoryx_posh/popo/untyped_server.hpp" #include "iceoryx_posh/popo/user_trigger.hpp" +#include "iceoryx_posh/runtime/service_discovery.hpp" using namespace iox; using namespace iox::popo; +using namespace iox::runtime; extern "C" { #include "iceoryx_binding_c/notification_info.h" @@ -56,6 +58,12 @@ bool iox_notification_info_does_originate_from_server(iox_notification_info_t co return self->doesOriginateFrom(server); } +bool iox_notification_info_does_originate_from_service_discovery(iox_notification_info_t const self, + iox_service_discovery_t const serviceDiscovery) +{ + return self->doesOriginateFrom(serviceDiscovery); +} + iox_sub_t iox_notification_info_get_subscriber_origin(iox_notification_info_t const self) { return self->getOrigin(); @@ -76,6 +84,11 @@ iox_server_t iox_notification_info_get_server_origin(iox_notification_info_t con return self->getOrigin(); } +iox_service_discovery_t iox_notification_info_get_service_discovery_origin(iox_notification_info_t const self) +{ + return self->getOrigin(); +} + void iox_notification_info_call(iox_notification_info_t const self) { (*self)(); diff --git a/iceoryx_binding_c/source/c_service_discovery.cpp b/iceoryx_binding_c/source/c_service_discovery.cpp new file mode 100644 index 0000000000..8485e58de7 --- /dev/null +++ b/iceoryx_binding_c/source/c_service_discovery.cpp @@ -0,0 +1,151 @@ +// Copyright (c) 2022 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_binding_c/internal/c2cpp_enum_translation.hpp" +#include "iceoryx_binding_c/internal/cpp2c_service_description_translation.hpp" +#include "iceoryx_hoofs/cxx/optional.hpp" +#include "iceoryx_hoofs/cxx/requires.hpp" +#include "iceoryx_posh/runtime/service_discovery.hpp" + +using namespace iox; +using namespace iox::runtime; + +extern "C" { +#include "iceoryx_binding_c/service_discovery.h" +} + +iox_service_discovery_t iox_service_discovery_init(iox_service_discovery_storage_t* self) +{ + iox::cxx::Expects(self != nullptr); + + auto* me = new (self) ServiceDiscovery(); + return reinterpret_cast(me); +} + +void iox_service_discovery_deinit(iox_service_discovery_t const self) +{ + iox::cxx::Expects(self != nullptr); + + self->~ServiceDiscovery(); +} + +uint64_t iox_service_discovery_find_service(iox_service_discovery_t const self, + const char* const service, + const char* const instance, + const char* const event, + iox_service_description_t* const serviceContainer, + const uint64_t serviceContainerCapacity, + uint64_t* missedServices, + const ENUM iox_MessagingPattern pattern) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceContainer != nullptr); + iox::cxx::Expects(missedServices != nullptr); + + cxx::optional maybeService; + if (service != nullptr) + { + maybeService.emplace(cxx::TruncateToCapacity, service); + } + cxx::optional maybeInstance; + if (instance != nullptr) + { + maybeInstance.emplace(cxx::TruncateToCapacity, instance); + } + cxx::optional maybeEvent; + if (event != nullptr) + { + maybeEvent.emplace(cxx::TruncateToCapacity, event); + } + + uint64_t currentSize = 0U; + auto filter = [&](const capro::ServiceDescription& s) { + if (currentSize < serviceContainerCapacity) + { + serviceContainer[currentSize] = TranslateServiceDescription(s); + ++currentSize; + } + else + { + ++(*missedServices); + } + }; + self->findService(maybeService, maybeInstance, maybeEvent, filter, c2cpp::messagingPattern(pattern)); + + return currentSize; +} + +void iox_service_discovery_find_service_apply_callable(iox_service_discovery_t const self, + const char* const service, + const char* const instance, + const char* const event, + void (*callable)(const iox_service_description_t), + const ENUM iox_MessagingPattern pattern) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(callable != nullptr); + + cxx::optional maybeService; + if (service != nullptr) + { + maybeService.emplace(cxx::TruncateToCapacity, service); + } + cxx::optional maybeInstance; + if (instance != nullptr) + { + maybeInstance.emplace(cxx::TruncateToCapacity, instance); + } + cxx::optional maybeEvent; + if (event != nullptr) + { + maybeEvent.emplace(cxx::TruncateToCapacity, event); + } + + auto filter = [&](const capro::ServiceDescription& s) { callable(TranslateServiceDescription(s)); }; + self->findService(maybeService, maybeInstance, maybeEvent, filter, c2cpp::messagingPattern(pattern)); +} + +void iox_service_discovery_find_service_apply_callable_with_context_data( + iox_service_discovery_t const self, + const char* const service, + const char* const instance, + const char* const event, + void (*callable)(const iox_service_description_t, void*), + void* const contextData, + const ENUM iox_MessagingPattern pattern) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(callable != nullptr); + + cxx::optional maybeService; + if (service != nullptr) + { + maybeService.emplace(cxx::TruncateToCapacity, service); + } + cxx::optional maybeInstance; + if (instance != nullptr) + { + maybeInstance.emplace(cxx::TruncateToCapacity, instance); + } + cxx::optional maybeEvent; + if (event != nullptr) + { + maybeEvent.emplace(cxx::TruncateToCapacity, event); + } + + auto filter = [&](const capro::ServiceDescription& s) { callable(TranslateServiceDescription(s), contextData); }; + self->findService(maybeService, maybeInstance, maybeEvent, filter, c2cpp::messagingPattern(pattern)); +} diff --git a/iceoryx_binding_c/source/c_wait_set.cpp b/iceoryx_binding_c/source/c_wait_set.cpp index 9b2db6d61d..906452c460 100644 --- a/iceoryx_binding_c/source/c_wait_set.cpp +++ b/iceoryx_binding_c/source/c_wait_set.cpp @@ -29,6 +29,7 @@ using namespace iox; using namespace iox::popo; +using namespace iox::runtime; extern "C" { #include "iceoryx_binding_c/wait_set.h" @@ -395,3 +396,47 @@ void iox_ws_detach_server_state(iox_ws_t const self, iox_server_t const server, self->detachState(*server, c2cpp::serverState(serverState)); } + +iox_WaitSetResult iox_ws_attach_service_discovery_event(const iox_ws_t self, + const iox_service_discovery_t serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + const uint64_t eventId, + void (*callback)(iox_service_discovery_t)) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceDiscovery != nullptr); + + auto result = self->attachEvent( + *serviceDiscovery, c2cpp::serviceDiscoveryEvent(serviceDiscoveryEvent), eventId, {callback, nullptr}); + return (result.has_error()) ? cpp2c::waitSetResult(result.get_error()) : iox_WaitSetResult::WaitSetResult_SUCCESS; +} + +iox_WaitSetResult +iox_ws_attach_service_discovery_event_with_context_data(iox_ws_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent, + const uint64_t eventId, + void (*callback)(iox_service_discovery_t, void*), + void* const contextData) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceDiscovery != nullptr); + + NotificationCallback, void> notificationCallback; + notificationCallback.m_callback = callback; + notificationCallback.m_contextData = contextData; + + auto result = self->attachEvent( + *serviceDiscovery, c2cpp::serviceDiscoveryEvent(serviceDiscoveryEvent), eventId, notificationCallback); + return (result.has_error()) ? cpp2c::waitSetResult(result.get_error()) : iox_WaitSetResult::WaitSetResult_SUCCESS; +} + +void iox_ws_detach_service_discovery_event(iox_ws_t const self, + iox_service_discovery_t const serviceDiscovery, + const ENUM iox_ServiceDiscoveryEvent serviceDiscoveryEvent) +{ + iox::cxx::Expects(self != nullptr); + iox::cxx::Expects(serviceDiscovery != nullptr); + + self->detachEvent(*serviceDiscovery, c2cpp::serviceDiscoveryEvent(serviceDiscoveryEvent)); +} diff --git a/iceoryx_binding_c/test/moduletests/test_c2cpp_enum_translation.cpp b/iceoryx_binding_c/test/moduletests/test_c2cpp_enum_translation.cpp index d139d54461..d0947d96a2 100644 --- a/iceoryx_binding_c/test/moduletests/test_c2cpp_enum_translation.cpp +++ b/iceoryx_binding_c/test/moduletests/test_c2cpp_enum_translation.cpp @@ -327,4 +327,84 @@ TEST(c2cpp_enum_translation_test, ServerEvent) #endif #pragma GCC diagnostic pop } + +TEST(c2cpp_enum_translation_test, ServiceDiscoveryEvent) +{ + ::testing::Test::RecordProperty("TEST_ID", "9eb978fa-8fa9-452f-b0e3-cb26c0cecfbf"); + constexpr EnumMapping EVENTS[]{ + {iox::runtime::ServiceDiscoveryEvent::SERVICE_REGISTRY_CHANGED, + ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED}}; + + for (const auto event : EVENTS) + { + switch (event.cpp) + { + case iox::runtime::ServiceDiscoveryEvent::SERVICE_REGISTRY_CHANGED: + EXPECT_EQ(c2cpp::serviceDiscoveryEvent(event.c), event.cpp); + break; + // default intentionally left out in order to get a compiler warning if the enum gets extended and we forgot + // to extend the test + } + } + + // ignore the warning since we would like to test the behavior of an invalid enum value +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +// ignored for now since the undefined behavior sanitizer correctly detects the undefined behavior +// which is tested and handled here +// explicitly commented out since we are testing undefined behavior here and that we +// return the default value SERVICE_REGISTRY_CHANGED always in the undefined behavior case +// the clang sanitizer detects this successfully and this leads to termination, and with this the test fails +#if !defined(__clang__) + iox::Error errorValue = iox::Error::kNO_ERROR; + auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler( + [&](const iox::Error e, const std::function, const iox::ErrorLevel) { errorValue = e; }); + EXPECT_EQ(c2cpp::serviceDiscoveryEvent(static_cast(-1)), + iox::runtime::ServiceDiscoveryEvent::SERVICE_REGISTRY_CHANGED); + EXPECT_THAT(errorValue, Eq(iox::Error::kBINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_SERVICE_DISCOVERY_EVENT_VALUE)); +#endif +#pragma GCC diagnostic pop +} + +TEST(c2cpp_enum_translation_test, MessagingPattern) +{ + ::testing::Test::RecordProperty("TEST_ID", "934d1fa5-e345-4a3b-9730-5467ec03c281"); + constexpr EnumMapping VALUE[]{ + {iox::popo::MessagingPattern::PUB_SUB, MessagingPattern_PUB_SUB}, + {iox::popo::MessagingPattern::REQ_RES, MessagingPattern_REQ_RES}, + }; + + for (const auto value : VALUE) + { + switch (value.cpp) + { + case iox::popo::MessagingPattern::PUB_SUB: + EXPECT_EQ(c2cpp::messagingPattern(value.c), value.cpp); + break; + case iox::popo::MessagingPattern::REQ_RES: + EXPECT_EQ(c2cpp::messagingPattern(value.c), value.cpp); + break; + // default intentionally left out in order to get a compiler warning if the enum gets extended and we forgot + // to extend the test + } + } + + // ignore the warning since we would like to test the behavior of an invalid enum value +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +// ignored for now since the undefined behavior sanitizer correctly detects the undefined behavior +// which is tested and handled here +// explicitly commented out since we are testing undefined behavior here and that we +// return the default value PUB_SUB always in the undefined behavior case +// the clang sanitizer detects this successfully and this leads to termination, and with this the test fails +#if !defined(__clang__) + iox::Error errorValue = iox::Error::kNO_ERROR; + auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler( + [&](const iox::Error e, const std::function, const iox::ErrorLevel) { errorValue = e; }); + EXPECT_EQ(c2cpp::messagingPattern(static_cast(-1)), iox::popo::MessagingPattern::PUB_SUB); + EXPECT_THAT(errorValue, Eq(iox::Error::kBINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_MESSAGING_PATTERN_VALUE)); +#endif +#pragma GCC diagnostic pop +} + } // namespace diff --git a/iceoryx_binding_c/test/moduletests/test_listener.cpp b/iceoryx_binding_c/test/moduletests/test_listener.cpp index 400f1dcbd4..1362d14754 100644 --- a/iceoryx_binding_c/test/moduletests/test_listener.cpp +++ b/iceoryx_binding_c/test/moduletests/test_listener.cpp @@ -30,6 +30,7 @@ using namespace iox; using namespace iox::popo; using namespace iox::posix; using namespace iox::mepoo; +using namespace iox::runtime; extern "C" { #include "iceoryx_binding_c/client.h" @@ -52,6 +53,7 @@ iox_user_trigger_t g_userTriggerCallbackArgument = nullptr; iox_sub_t g_subscriberCallbackArgument = nullptr; iox_client_t g_clientCallbackArgument = nullptr; iox_server_t g_serverCallbackArgument = nullptr; +iox_service_discovery_t g_serviceDiscoveryCallbackArgument = nullptr; void* g_contextData = nullptr; void userTriggerCallback(iox_user_trigger_t userTrigger) @@ -98,6 +100,17 @@ void serverCallbackWithContextData(iox_server_t server, void* const contextData) g_contextData = contextData; } +void serviceDiscoveryCallback(iox_service_discovery_t serviceDiscovery) +{ + g_serviceDiscoveryCallbackArgument = serviceDiscovery; +} + +void serviceDiscoveryCallbackWithContextData(iox_service_discovery_t serviceDiscovery, void* const contextData) +{ + g_serviceDiscoveryCallbackArgument = serviceDiscovery; + g_contextData = contextData; +} + class iox_listener_test : public Test { public: @@ -115,6 +128,7 @@ class iox_listener_test : public Test g_userTriggerCallbackArgument = nullptr; g_subscriberCallbackArgument = nullptr; g_clientCallbackArgument = nullptr; + g_serviceDiscoveryCallbackArgument = nullptr; g_contextData = nullptr; m_mempoolconf.addMemPool({CHUNK_SIZE, NUM_CHUNKS_IN_POOL}); @@ -544,4 +558,96 @@ TIMING_TEST_F(iox_listener_test, NotifyingServerEventWithContextDataWorks, Repea /// END server tests ////////////////////// +TEST_F(iox_listener_test, AttachingServiceDiscoveryWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "75fd4e6f-ee2f-4e28-a2d8-8a0f01dbd91c"); + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_subscriberPortData[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + EXPECT_THAT(iox_listener_size(&m_sut), Eq(0)); + iox_listener_attach_service_discovery_event( + &m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, &serviceDiscoveryCallback); + EXPECT_THAT(iox_listener_size(&m_sut), Eq(1)); + + iox_listener_detach_service_discovery_event( + &m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + EXPECT_THAT(iox_listener_size(&m_sut), Eq(0)); + + iox_service_discovery_deinit(serviceDiscovery); +} + +TEST_F(iox_listener_test, AttachingServiceDiscoveryWithContextDataWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "2d7cbe60-bda1-4191-b2d5-d67c47312a48"); + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_subscriberPortData[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + EXPECT_THAT(iox_listener_size(&m_sut), Eq(0)); + iox_listener_attach_service_discovery_event_with_context_data(&m_sut, + serviceDiscovery, + ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, + &serviceDiscoveryCallbackWithContextData, + &serviceDiscoveryStorage); + EXPECT_THAT(iox_listener_size(&m_sut), Eq(1)); + + iox_listener_detach_service_discovery_event( + &m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + EXPECT_THAT(iox_listener_size(&m_sut), Eq(0)); + + iox_service_discovery_deinit(serviceDiscovery); +} + +void notifyServiceDiscovery(SubscriberPortData& portData) +{ + ConditionNotifier(*portData.m_chunkReceiverData.m_conditionVariableDataPtr, 0).notify(); +} + +TIMING_TEST_F(iox_listener_test, NotifyingServiceDiscoveryEventWorks, Repeat(5), [&] { + ::testing::Test::RecordProperty("TEST_ID", "538a50bc-60c8-4485-b70e-59d0c53f618b"); + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_subscriberPortData[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + iox_listener_attach_service_discovery_event( + &m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, &serviceDiscoveryCallback); + + notifyServiceDiscovery(m_subscriberPortData[0]); + std::this_thread::sleep_for(TIMEOUT); + TIMING_TEST_EXPECT_TRUE(g_serviceDiscoveryCallbackArgument == serviceDiscovery); + + iox_listener_detach_service_discovery_event( + &m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + + iox_service_discovery_deinit(serviceDiscovery); +}); + +TIMING_TEST_F(iox_listener_test, NotifyingServiceDiscoveryEventWithContextDataWorks, Repeat(5), [&] { + ::testing::Test::RecordProperty("TEST_ID", "257c27a5-95c6-489d-919f-125471b399e8"); + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_subscriberPortData[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + iox_listener_attach_service_discovery_event_with_context_data(&m_sut, + serviceDiscovery, + ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, + &serviceDiscoveryCallbackWithContextData, + &serviceDiscoveryStorage); + + notifyServiceDiscovery(m_subscriberPortData[0]); + std::this_thread::sleep_for(TIMEOUT); + TIMING_TEST_EXPECT_TRUE(g_serviceDiscoveryCallbackArgument == serviceDiscovery); + TIMING_TEST_EXPECT_TRUE(g_contextData == static_cast(&serviceDiscoveryStorage)); + + iox_listener_detach_service_discovery_event( + &m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + + iox_service_discovery_deinit(serviceDiscovery); +}); + } // namespace diff --git a/iceoryx_binding_c/test/moduletests/test_service_discovery.cpp b/iceoryx_binding_c/test/moduletests/test_service_discovery.cpp new file mode 100644 index 0000000000..ea3e256783 --- /dev/null +++ b/iceoryx_binding_c/test/moduletests/test_service_discovery.cpp @@ -0,0 +1,201 @@ +// Copyright (c) 2022 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_posh/runtime/service_discovery.hpp" +#include "iceoryx_posh/testing/roudi_gtest.hpp" + +using namespace iox; +using namespace iox::runtime; + +extern "C" { +#include "iceoryx_binding_c/publisher.h" +#include "iceoryx_binding_c/runtime.h" +#include "iceoryx_binding_c/service_discovery.h" +} + +namespace +{ +using namespace ::testing; +using description_vector = cxx::vector; + +class iox_service_discovery_test : public RouDi_GTest +{ + public: + void SetUp() override + { + iox_runtime_init("runtime"); + sut = iox_service_discovery_init(&sutStorage); + } + + void TearDown() override + { + searchResult.clear(); + iox_service_discovery_deinit(sut); + } + + iox_service_discovery_storage_t sutStorage; + iox_service_discovery_t sut; + + static description_vector searchResult; + static void findHandler(const iox_service_description_t s, void* contextData) + { + static_cast(contextData)->emplace_back(s); + } +}; + +description_vector iox_service_discovery_test::searchResult; + +TEST(iox_service_discovery_DeathTest, InitServiceDiscoveryWithNullptrForStorageTerminates) +{ + ::testing::Test::RecordProperty("TEST_ID", "be551a9e-7dcf-406a-a74c-7dcb1ee16c30"); + EXPECT_DEATH({ iox_service_discovery_init(nullptr); }, ".*"); +} + +/// @note We test only if the arguments of iox_service_discovery_find_service are correctly passed to +/// ServiceDiscovery::findService. +TEST_F(iox_service_discovery_test, + FindServiceWithCallableAndContextDataWithNullptrsForServiceInstanceEventReturnsAllServices) +{ + ::testing::Test::RecordProperty("TEST_ID", "09a2cd6c-fba9-4b9d-af96-c5a6cc168d98"); + iox_service_discovery_find_service_apply_callable_with_context_data( + sut, nullptr, nullptr, nullptr, findHandler, &searchResult, MessagingPattern_PUB_SUB); + for (const auto& service : searchResult) + { + EXPECT_THAT(service.instanceString, StrEq("RouDi_ID")); + } +} + +TEST_F(iox_service_discovery_test, FindServiceWithCallableAndContextDataReturnsOfferedService) +{ + ::testing::Test::RecordProperty("TEST_ID", "bb12e514-e7af-4946-b098-98b3cd0f43a5"); + iox_pub_options_t options; + iox_pub_options_init(&options); + iox_pub_storage_t storage; + auto* publisher = iox_pub_init(&storage, "service", "instance", "event", &options); + ASSERT_NE(publisher, nullptr); + const iox_service_description_t SERVICE_DESCRIPTION = iox_pub_get_service_description(publisher); + + iox_service_discovery_find_service_apply_callable_with_context_data(sut, + SERVICE_DESCRIPTION.serviceString, + SERVICE_DESCRIPTION.instanceString, + SERVICE_DESCRIPTION.eventString, + findHandler, + &searchResult, + MessagingPattern_PUB_SUB); + ASSERT_THAT(searchResult.size(), Eq(1U)); + EXPECT_THAT(*searchResult.begin()->serviceString, Eq(*SERVICE_DESCRIPTION.serviceString)); + EXPECT_THAT(*searchResult.begin()->instanceString, Eq(*SERVICE_DESCRIPTION.instanceString)); + EXPECT_THAT(*searchResult.begin()->eventString, Eq(*SERVICE_DESCRIPTION.eventString)); +} + +TEST_F(iox_service_discovery_test, FindServiceWithCallableWithNullptrsForServiceInstanceEventFindsCorrectServices) +{ + ::testing::Test::RecordProperty("TEST_ID", "ec565ca3-7494-42d7-9440-2000f1513759"); + auto findHandler = [](const iox_service_description_t s) { EXPECT_THAT(s.instanceString, StrEq("RouDi_ID")); }; + iox_service_discovery_find_service_apply_callable( + sut, nullptr, nullptr, nullptr, findHandler, MessagingPattern_PUB_SUB); +} + +TEST_F(iox_service_discovery_test, FindServiceWithCallableReturnsFindsCorrectService) +{ + ::testing::Test::RecordProperty("TEST_ID", "3ac1f029-3c05-4f95-90e9-e848ceb2d4d7"); + iox_pub_options_t options; + iox_pub_options_init(&options); + iox_pub_storage_t storage; + auto* publisher = iox_pub_init(&storage, "service", "instance", "event", &options); + ASSERT_NE(publisher, nullptr); + + auto findHandler = [](const iox_service_description_t s) { + EXPECT_THAT(s.serviceString, StrEq("service")); + EXPECT_THAT(s.instanceString, StrEq("instance")); + EXPECT_THAT(s.eventString, StrEq("event")); + }; + iox_service_discovery_find_service_apply_callable( + sut, "service", "instance", "event", findHandler, MessagingPattern_PUB_SUB); +} + +TEST_F(iox_service_discovery_test, FindServiceWithNullptrsForServiceInstanceEventReturnsAllServices) +{ + ::testing::Test::RecordProperty("TEST_ID", "75b411d7-b8c7-42d5-8acd-3916fd172081"); + const uint64_t SERVICE_CONTAINER_CAPACITY = 10U; + iox_service_description_t serviceContainer[SERVICE_CONTAINER_CAPACITY]; + uint64_t missedServices = 0U; + const auto numberFoundServices = iox_service_discovery_find_service(sut, + nullptr, + nullptr, + nullptr, + serviceContainer, + SERVICE_CONTAINER_CAPACITY, + &missedServices, + MessagingPattern_PUB_SUB); + + EXPECT_THAT(numberFoundServices, Eq(6U)); + EXPECT_THAT(missedServices, Eq(0U)); + for (uint64_t i = 0U; i < numberFoundServices; ++i) + { + EXPECT_THAT(serviceContainer[i].instanceString, StrEq("RouDi_ID")); + } +} + +TEST_F(iox_service_discovery_test, FindServiceReturnsOfferedService) +{ + ::testing::Test::RecordProperty("TEST_ID", "4bbd0b26-ed9d-4fcd-ae85-e7ea3783996d"); + iox_pub_options_t options; + iox_pub_options_init(&options); + iox_pub_storage_t storage; + auto* publisher = iox_pub_init(&storage, "service", "instance", "event", &options); + ASSERT_NE(publisher, nullptr); + const iox_service_description_t SERVICE_DESCRIPTION = iox_pub_get_service_description(publisher); + + const uint64_t SERVICE_CONTAINER_CAPACITY = 10U; + iox_service_description_t serviceContainer[SERVICE_CONTAINER_CAPACITY]; + uint64_t missedServices = 0U; + const auto numberFoundServices = iox_service_discovery_find_service(sut, + SERVICE_DESCRIPTION.serviceString, + SERVICE_DESCRIPTION.instanceString, + SERVICE_DESCRIPTION.eventString, + serviceContainer, + SERVICE_CONTAINER_CAPACITY, + &missedServices, + MessagingPattern_PUB_SUB); + + EXPECT_THAT(numberFoundServices, Eq(1U)); + EXPECT_THAT(missedServices, Eq(0U)); + EXPECT_THAT(serviceContainer[0U].serviceString, StrEq(SERVICE_DESCRIPTION.serviceString)); + EXPECT_THAT(serviceContainer[0U].instanceString, StrEq(SERVICE_DESCRIPTION.instanceString)); + EXPECT_THAT(serviceContainer[0U].eventString, StrEq(SERVICE_DESCRIPTION.eventString)); +} + +TEST_F(iox_service_discovery_test, FindServiceReturnsCorrectNumberOfServicesWhenServiceContainerTooSmall) +{ + ::testing::Test::RecordProperty("TEST_ID", "01047b88-f257-447c-8c5e-9bef7c358433"); + const uint64_t SERVICE_CONTAINER_CAPACITY = 3U; + iox_service_description_t serviceContainer[SERVICE_CONTAINER_CAPACITY]; + uint64_t missedServices = 0U; + const auto numberFoundServices = iox_service_discovery_find_service(sut, + nullptr, + nullptr, + nullptr, + serviceContainer, + SERVICE_CONTAINER_CAPACITY, + &missedServices, + MessagingPattern_PUB_SUB); + + EXPECT_THAT(numberFoundServices, Eq(SERVICE_CONTAINER_CAPACITY)); + EXPECT_THAT(missedServices, Eq(NUMBER_OF_INTERNAL_PUBLISHERS - SERVICE_CONTAINER_CAPACITY)); +} + +} // namespace diff --git a/iceoryx_binding_c/test/moduletests/test_types.cpp b/iceoryx_binding_c/test/moduletests/test_types.cpp index 5e755e6627..b49f647873 100644 --- a/iceoryx_binding_c/test/moduletests/test_types.cpp +++ b/iceoryx_binding_c/test/moduletests/test_types.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2020 - 2021 Apex.AI Inc. All rights reserved. +// Copyright (c) 2020 - 2022 Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,10 +22,12 @@ #include "iceoryx_posh/popo/untyped_server.hpp" #include "iceoryx_posh/popo/user_trigger.hpp" #include "iceoryx_posh/popo/wait_set.hpp" +#include "iceoryx_posh/runtime/service_discovery.hpp" using namespace iox; using namespace iox::popo; +using namespace iox::runtime; extern "C" { #include "iceoryx_binding_c/types.h" @@ -86,4 +88,10 @@ TEST(iox_types_test, ServerStorageSizeFits) EXPECT_THAT(alignof(UntypedServer), Le(alignof(iox_server_storage_t))); } +TEST(iox_types_test, ServiceDiscoveryStorageSizeFits) +{ + ::testing::Test::RecordProperty("TEST_ID", "8fd3af84-ca73-4e38-a061-5e4638f48b77"); + EXPECT_THAT(sizeof(ServiceDiscovery), Le(sizeof(iox_service_discovery_storage_t))); + EXPECT_THAT(alignof(ServiceDiscovery), Le(alignof(iox_service_discovery_storage_t))); +} } // namespace diff --git a/iceoryx_binding_c/test/moduletests/test_wait_set.cpp b/iceoryx_binding_c/test/moduletests/test_wait_set.cpp index 673fa378a3..b0f2159679 100644 --- a/iceoryx_binding_c/test/moduletests/test_wait_set.cpp +++ b/iceoryx_binding_c/test/moduletests/test_wait_set.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,11 +23,13 @@ #include "iceoryx_posh/popo/untyped_client.hpp" #include "iceoryx_posh/popo/untyped_server.hpp" #include "iceoryx_posh/popo/user_trigger.hpp" +#include "iceoryx_posh/runtime/service_discovery.hpp" #include "iceoryx_posh/testing/mocks/posh_runtime_mock.hpp" #include "mocks/wait_set_mock.hpp" using namespace iox; using namespace iox::popo; +using namespace iox::runtime; extern "C" { #include "iceoryx_binding_c/subscriber.h" @@ -156,6 +158,18 @@ void serverCallbackWithContextData(iox::popo::UntypedServer* server, void* const iox_ws_test::m_callbackOrigin = server; iox_ws_test::m_contextData = contextData; } + +void serviceDiscoveryCallback(iox_service_discovery_t serviceDiscovery) +{ + iox_ws_test::m_callbackOrigin = serviceDiscovery; +} + +void serviceDiscoveryCallbackWithContextData(iox_service_discovery_t serviceDiscovery, void* const contextData) +{ + iox_ws_test::m_callbackOrigin = serviceDiscovery; + iox_ws_test::m_contextData = contextData; +} + } // namespace /// @todo iox-#1106 will be enabled when worked on this issue @@ -1053,4 +1067,111 @@ TEST_F(iox_ws_test, NotifyingServerStateWithContextDataWorks) /// END server tests //////////////////// +TEST_F(iox_ws_test, AttachingServiceDiscoveryEventWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "a8be9cbd-d9b6-45a3-b34f-d58fb864d40d"); + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_portDataVector[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + EXPECT_THAT(iox_ws_size(m_sut), Eq(0)); + iox_ws_attach_service_discovery_event( + m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, 0, nullptr); + EXPECT_THAT(iox_ws_size(m_sut), Eq(1)); + + iox_ws_detach_service_discovery_event(m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + EXPECT_THAT(iox_ws_size(m_sut), Eq(0)); + + iox_service_discovery_deinit(serviceDiscovery); +} + +TEST_F(iox_ws_test, AttachingServiceDiscoveryEventWithContextDataWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "69515627-1590-4616-8502-975cd9256ecf"); + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_portDataVector[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + EXPECT_THAT(iox_ws_size(m_sut), Eq(0)); + iox_ws_attach_service_discovery_event_with_context_data( + m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, 0, nullptr, nullptr); + EXPECT_THAT(iox_ws_size(m_sut), Eq(1)); + + iox_ws_detach_service_discovery_event(m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + EXPECT_THAT(iox_ws_size(m_sut), Eq(0)); + + iox_service_discovery_deinit(serviceDiscovery); +} + +void notifyServiceDiscovery(SubscriberPortData& portData) +{ + iox::popo::ChunkQueuePusher pusher{&portData.m_chunkReceiverData}; + pusher.push(iox::mepoo::SharedChunk()); + EXPECT_FALSE(portData.m_chunkReceiverData.m_conditionVariableDataPtr->m_semaphore.post().has_error()); +} + +TEST_F(iox_ws_test, NotifyingServiceDiscoveryEventWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "945dcf94-4679-469f-aa47-1a87d536da72"); + constexpr uint64_t EVENT_ID = 13; + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_portDataVector[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + iox_ws_attach_service_discovery_event( + m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, EVENT_ID, &serviceDiscoveryCallback); + + notifyServiceDiscovery(m_portDataVector[0]); + + ASSERT_THAT(iox_ws_wait(m_sut, m_eventInfoStorage, MAX_NUMBER_OF_ATTACHMENTS_PER_WAITSET, &m_missedElements), + Eq(1)); + EXPECT_THAT(iox_notification_info_get_notification_id(m_eventInfoStorage[0]), Eq(EVENT_ID)); + EXPECT_THAT(iox_notification_info_get_service_discovery_origin(m_eventInfoStorage[0]), Eq(serviceDiscovery)); + EXPECT_TRUE(iox_notification_info_does_originate_from_service_discovery(m_eventInfoStorage[0], serviceDiscovery)); + iox_notification_info_call(m_eventInfoStorage[0]); + + EXPECT_THAT(m_callbackOrigin, Eq(static_cast(serviceDiscovery))); + EXPECT_THAT(m_contextData, Eq(nullptr)); + + iox_ws_detach_service_discovery_event(m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + + iox_service_discovery_deinit(serviceDiscovery); +} + +TEST_F(iox_ws_test, NotifyingServiceDiscoveryEventWithContextDataWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "510a0351-afeb-4c0f-a4b6-3032f1f3f831"); + constexpr uint64_t EVENT_ID = 31; + iox_service_discovery_storage_t serviceDiscoveryStorage; + EXPECT_CALL(*runtimeMock, getMiddlewareSubscriber(_, _, _)).WillOnce(Return(&m_portDataVector[0])); + + iox_service_discovery_t serviceDiscovery = iox_service_discovery_init(&serviceDiscoveryStorage); + + iox_ws_attach_service_discovery_event_with_context_data(m_sut, + serviceDiscovery, + ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED, + EVENT_ID, + &serviceDiscoveryCallbackWithContextData, + &serviceDiscoveryStorage); + + notifyServiceDiscovery(m_portDataVector[0]); + + ASSERT_THAT(iox_ws_wait(m_sut, m_eventInfoStorage, MAX_NUMBER_OF_ATTACHMENTS_PER_WAITSET, &m_missedElements), + Eq(1)); + EXPECT_THAT(iox_notification_info_get_notification_id(m_eventInfoStorage[0]), Eq(EVENT_ID)); + EXPECT_THAT(iox_notification_info_get_service_discovery_origin(m_eventInfoStorage[0]), Eq(serviceDiscovery)); + EXPECT_TRUE(iox_notification_info_does_originate_from_service_discovery(m_eventInfoStorage[0], serviceDiscovery)); + iox_notification_info_call(m_eventInfoStorage[0]); + + EXPECT_THAT(m_callbackOrigin, Eq(static_cast(serviceDiscovery))); + EXPECT_THAT(m_contextData, Eq(static_cast(&serviceDiscoveryStorage))); + + iox_ws_detach_service_discovery_event(m_sut, serviceDiscovery, ServiceDiscoveryEvent_SERVICE_REGISTRY_CHANGED); + + iox_service_discovery_deinit(serviceDiscovery); +} + } // namespace diff --git a/iceoryx_hoofs/include/iceoryx_hoofs/error_handling/error_handling.hpp b/iceoryx_hoofs/include/iceoryx_hoofs/error_handling/error_handling.hpp index 6a75a92bab..5b541aa7ca 100644 --- a/iceoryx_hoofs/include/iceoryx_hoofs/error_handling/error_handling.hpp +++ b/iceoryx_hoofs/include/iceoryx_hoofs/error_handling/error_handling.hpp @@ -195,6 +195,8 @@ namespace iox error(BINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_CLIENT_STATE_VALUE) \ error(BINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_SERVER_EVENT_VALUE) \ error(BINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_SERVER_STATE_VALUE) \ + error(BINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_SERVICE_DISCOVERY_EVENT_VALUE) \ + error(BINDING_C__C2CPP_ENUM_TRANSLATION_INVALID_MESSAGING_PATTERN_VALUE) \ error(EXPECTS_ENSURES_FAILED) // EXPECTS_ENSURES_FAILED is used as a temporary solution to make Expects/Ensures testable diff --git a/iceoryx_posh/include/iceoryx_posh/runtime/service_discovery.hpp b/iceoryx_posh/include/iceoryx_posh/runtime/service_discovery.hpp index 99f4f3b93b..cef7268d95 100644 --- a/iceoryx_posh/include/iceoryx_posh/runtime/service_discovery.hpp +++ b/iceoryx_posh/include/iceoryx_posh/runtime/service_discovery.hpp @@ -34,7 +34,7 @@ enum class MessagingPattern PUB_SUB, REQ_RES }; -} +} // namespace popo namespace runtime { enum class ServiceDiscoveryEvent : popo::EventEnumIdentifier @@ -52,11 +52,13 @@ class ServiceDiscovery ServiceDiscovery& operator=(ServiceDiscovery&&) = delete; ~ServiceDiscovery() noexcept = default; - /// @brief Searches all services that match the provided service description and applies a function to each of them + /// @brief Searches all services with the given messaging pattern that match the provided service description and + /// applies a function to each of them /// @param[in] service service string to search for, a nullopt corresponds to a wildcard /// @param[in] instance instance string to search for, a nullopt corresponds to a wildcard /// @param[in] event event string to search for, a nullopt corresponds to a wildcard /// @param[in] callableForEach callable to apply to all matching services + /// @param[in] pattern messaging pattern of the service to search void findService(const cxx::optional& service, const cxx::optional& instance, const cxx::optional& event,