diff --git a/libs/bsw/middleware/CMakeLists.txt b/libs/bsw/middleware/CMakeLists.txt index 6279964de10..d86c44baa10 100644 --- a/libs/bsw/middleware/CMakeLists.txt +++ b/libs/bsw/middleware/CMakeLists.txt @@ -6,3 +6,17 @@ target_include_directories( $) target_link_libraries(middlewareHeaders INTERFACE etl) + +add_library( + middleware + src/middleware/core/ClusterConnection.cpp + src/middleware/core/ClusterConnectionBase.cpp + src/middleware/core/DatabaseManipulator.cpp + src/middleware/core/IClusterConnectionConfigurationBase.cpp + src/middleware/core/LoggerApi.cpp + src/middleware/core/ProxyBase.cpp + src/middleware/core/SkeletonBase.cpp) + +target_include_directories(middleware PUBLIC include) + +target_link_libraries(middleware PUBLIC etl) diff --git a/libs/bsw/middleware/doc/dd/core.rst b/libs/bsw/middleware/doc/dd/core.rst index 45d88d59532..c3fde372943 100644 --- a/libs/bsw/middleware/doc/dd/core.rst +++ b/libs/bsw/middleware/doc/dd/core.rst @@ -6,7 +6,7 @@ The core software unit of the middleware provides the key algorithms needed for Middleware Message ------------------ -Middleware is a service-oriented message passing system, and as such the ``MiddlewareMessage`` class is the fundamental unit of communication. +Middleware is a service-oriented message passing system, and as such the ``Message`` class is the fundamental unit of communication. This class consists of a header and a payload. The header has the following information: @@ -19,11 +19,11 @@ The header has the following information: * Address ID - identifies the recipient of the message within the destination cluster. The middleware services' nodes will be scattered across different clusters in the ECU. -This means that messages can travel between clusters, and as such the ``MiddlewareMessage`` needs to have information about its origin and destination. +This means that messages can travel between clusters, and as such the ``Message`` needs to have information about its origin and destination. Additionally, there may exist several possible recipients of a message, and as such, each recipient needs to have a unique identifier after system initialization. To this end, the header contains the source cluster ID, target cluster ID, and address ID fields. -Finally, the payload contains the actual data being transmitted. This payload can be up to MAX_PAYLOAD_SIZE (currently 20 bytes long) and is stored directly within the ``MiddlewareMessage`` object. -If the payload exceeds this size, the middleware employs its own memory management system and stores an external handle within the ``MiddlewareMessage`` object. +Finally, the payload contains the actual data being transmitted. This payload can be up to MAX_PAYLOAD_SIZE (currently 20 bytes long) and is stored directly within the ``Message`` object. +If the payload exceeds this size, the middleware employs its own memory management system and stores an external handle within the ``Message`` object. This handle contains information about the location and size of the payload in the middleware's memory region, as well as a flag indicating whether the payload is shared among multiple messages. In case of internal errors, the payload can store an error code instead, which is delivered to any recipient waiting for a response. diff --git a/libs/bsw/middleware/doc/dd/queue.rst b/libs/bsw/middleware/doc/dd/queue.rst index cc87381cd33..0eff4eb8693 100644 --- a/libs/bsw/middleware/doc/dd/queue.rst +++ b/libs/bsw/middleware/doc/dd/queue.rst @@ -2,7 +2,7 @@ Queue ===== The queue folder contains a multi-producer single-consumer queue implementation, which is composed of a non-templated base class and a generic templated class. -This queue will be unique to each cluster in the middleware system, and will be used to store ``MiddlewareMessage`` objects. +This queue will be unique to each cluster in the middleware system, and will be used to store ``Message`` objects. The queue must be declared with a ``QueueTraits`` type, which is a templated structure that contains the following members: * T - the type that will be contained in the queue diff --git a/libs/bsw/middleware/include/middleware/concurrency/LockStrategies.h b/libs/bsw/middleware/include/middleware/concurrency/LockStrategies.h new file mode 100644 index 00000000000..7f1938c8a83 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/concurrency/LockStrategies.h @@ -0,0 +1,78 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +namespace middleware +{ +namespace concurrency +{ + +/** + * \brief Suspend all interrupts. + * \details Platform-specific function that suspends all interrupts to ensure critical sections + * are protected. This is used in conjunction with lock strategies for thread-safe operations. + * The implementation must be provided for each platform integration. + */ +extern void suspendAllInterrupts(); + +/** + * \brief Scoped lock for single-core protection. + * \details RAII-style lock that protects critical sections within a single core by disabling + * interrupts or using other single-core synchronization mechanisms. The lock is acquired in the + * constructor and automatically released in the destructor, ensuring proper cleanup even in the + * presence of exceptions. + * The implementation must be provided for each platform integration. + */ +struct ScopedCoreLock +{ + /** + * \brief Constructor that acquires the core lock. + * \details Acquires the single-core lock by suspending interrupts or using other + * synchronization mechanisms. + */ + ScopedCoreLock(); + + /** + * \brief Destructor that releases the core lock. + * \details Releases the single-core lock by restoring interrupts or releasing the + * synchronization mechanism. + */ + ~ScopedCoreLock(); + + ScopedCoreLock(ScopedCoreLock const&) = delete; + ScopedCoreLock& operator=(ScopedCoreLock const&) = delete; +}; + +/** + * \brief Scoped lock for ECU-wide (multi-core) protection. + * \details RAII-style lock that protects critical sections across multiple cores in an ECU by + * using hardware-supported spinlocks or other multi-core synchronization mechanisms. The lock is + * acquired in the constructor and automatically released in the destructor, ensuring proper + * cleanup even in the presence of exceptions. + * The implementation must be provided for each platform integration. + */ +struct ScopedECULock +{ + /** + * \brief Constructor that acquires the ECU lock. + * \details Acquires the ECU-wide lock using the provided spinlock variable for multi-core + * synchronization. + * + * \param lock pointer to the volatile spinlock variable + */ + ScopedECULock(uint8_t volatile* lock); + + /** + * \brief Destructor that releases the ECU lock. + * \details Releases the ECU-wide lock, allowing other cores to acquire it. + */ + ~ScopedECULock(); + + ScopedECULock(ScopedECULock const&) = delete; + ScopedECULock& operator=(ScopedECULock const&) = delete; +}; + +} // namespace concurrency +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/ClusterConnection.h b/libs/bsw/middleware/include/middleware/core/ClusterConnection.h new file mode 100644 index 00000000000..abd7eb41bcc --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ClusterConnection.h @@ -0,0 +1,445 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include "middleware/core/ClusterConnectionBase.h" +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/Message.h" + +namespace middleware +{ +namespace core +{ +class ProxyBase; +class SkeletonBase; + +/** + * \brief Cluster connection for proxy-only communication without timeout support. + * \details This class provides a cluster connection that only supports proxy subscriptions, + * without timeout management. Skeleton subscriptions are not implemented and will return + * NotImplemented. + */ +class ClusterConnectionNoTimeoutProxyOnly final : public ClusterConnectionBase +{ + using Base = ClusterConnectionBase; + +public: + /** + * \brief Constructor for ClusterConnectionNoTimeoutProxyOnly. + * \details Initializes the proxy-only cluster connection with the provided configuration. + * + * \param configuration reference to the proxy-only cluster connection configuration + */ + explicit ClusterConnectionNoTimeoutProxyOnly( + IClusterConnectionConfigurationProxyOnly& configuration); + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers a proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** + * \brief Subscribe a skeleton (not implemented). + * \details This operation is not supported for proxy-only connections. + * + * \return HRESULT::NotImplemented + */ + HRESULT subscribe(SkeletonBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** + * \brief Unsubscribe a skeleton (no-op). + * \details This operation has no effect for proxy-only connections. + */ + void unsubscribe(SkeletonBase&, uint16_t const) final {} +}; + +/** + * \brief Cluster connection for skeleton-only communication without timeout support. + * \details This class provides a cluster connection that only supports skeleton subscriptions, + * without timeout management. Proxy subscriptions are not implemented and will return + * NotImplemented. + */ +class ClusterConnectionNoTimeoutSkeletonOnly final : public ClusterConnectionBase +{ + using Base = ClusterConnectionBase; + +public: + /** + * \brief Constructor for ClusterConnectionNoTimeoutSkeletonOnly. + * \details Initializes the skeleton-only cluster connection with the provided configuration. + * + * \param configuration reference to the skeleton-only cluster connection configuration + */ + explicit ClusterConnectionNoTimeoutSkeletonOnly( + IClusterConnectionConfigurationSkeletonOnly& configuration); + + /** + * \brief Subscribe a proxy (not implemented). + * \details This operation is not supported for skeleton-only connections. + * + * \return HRESULT::NotImplemented + */ + HRESULT subscribe(ProxyBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers a skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) final; + + /** + * \brief Unsubscribe a proxy (no-op). + * \details This operation has no effect for skeleton-only connections. + */ + void unsubscribe(ProxyBase&, uint16_t const) final {} + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) final; +}; + +/** + * \brief Cluster connection for bidirectional communication without timeout support. + * \details This class provides a cluster connection that supports both proxy and skeleton + * subscriptions, without timeout management. It enables full bidirectional communication + * between clusters. + */ +class ClusterConnectionNoTimeoutBidirectional final : public ClusterConnectionBase +{ + using Base = ClusterConnectionBase; + +public: + /** + * \brief Constructor for ClusterConnectionNoTimeoutBidirectional. + * \details Initializes the bidirectional cluster connection with the provided configuration. + * + * \param configuration reference to the bidirectional cluster connection configuration + */ + explicit ClusterConnectionNoTimeoutBidirectional( + IClusterConnectionConfigurationBidirectional& configuration); + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers a proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers a skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) final; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) final; +}; + +/** + * \brief Cluster connection for bidirectional communication with timeout support. + * \details This class provides a cluster connection that supports both proxy and skeleton + * subscriptions, with timeout management capabilities. It enables full bidirectional + * communication between clusters with timeout tracking. + */ +class ClusterConnectionBidirectionalWithTimeout final : public ClusterConnectionTimeoutBase +{ + using Base = ClusterConnectionTimeoutBase; + +public: + /** + * \brief Constructor for ClusterConnectionBidirectionalWithTimeout. + * \details Initializes the bidirectional cluster connection with timeout support. + * + * \param configuration reference to the bidirectional timeout configuration + */ + explicit ClusterConnectionBidirectionalWithTimeout( + IClusterConnectionConfigurationBidirectionalWithTimeout& configuration); + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers a proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers a skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) final; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) final; +}; + +/** + * \brief Cluster connection for proxy-only communication with timeout support. + * \details This class provides a cluster connection that only supports proxy subscriptions, + * with timeout management capabilities. Skeleton subscriptions are not implemented. + */ +class ClusterConnectionProxyOnlyWithTimeout final : public ClusterConnectionTimeoutBase +{ + using Base = ClusterConnectionTimeoutBase; + +public: + /** + * \brief Constructor for ClusterConnectionProxyOnlyWithTimeout. + * \details Initializes the proxy-only cluster connection with timeout support. + * + * \param configuration reference to the proxy-only timeout configuration + */ + explicit ClusterConnectionProxyOnlyWithTimeout( + IClusterConnectionConfigurationProxyOnlyWithTimeout& configuration); + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers a proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** + * \brief Subscribe a skeleton (not implemented). + * \details This operation is not supported for proxy-only connections. + * + * \return HRESULT::NotImplemented + */ + HRESULT subscribe(SkeletonBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** + * \brief Unsubscribe a skeleton (no-op). + * \details This operation has no effect for proxy-only connections. + */ + void unsubscribe(SkeletonBase&, uint16_t const) final {} +}; + +/** + * \brief Cluster connection for skeleton-only communication with timeout support. + * \details This class provides a cluster connection that only supports skeleton subscriptions, + * with timeout management capabilities. Proxy subscriptions are not implemented. + */ +class ClusterConnectionSkeletonOnlyWithTimeout final : public ClusterConnectionTimeoutBase +{ + using Base = ClusterConnectionTimeoutBase; + +public: + /** + * \brief Constructor for ClusterConnectionSkeletonOnlyWithTimeout. + * \details Initializes the skeleton-only cluster connection with timeout support. + * + * \param configuration reference to the skeleton-only timeout configuration + */ + explicit ClusterConnectionSkeletonOnlyWithTimeout( + IClusterConnectionConfigurationSkeletonOnlyWithTimeout& configuration); + + /** + * \brief Subscribe a proxy (not implemented). + * \details This operation is not supported for skeleton-only connections. + * + * \return HRESULT::NotImplemented + */ + HRESULT subscribe(ProxyBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers a skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + HRESULT subscribe(SkeletonBase&, uint16_t const) final; + + /** + * \brief Unsubscribe a proxy (no-op). + * \details This operation has no effect for skeleton-only connections. + */ + void + unsubscribe([[maybe_unused]] ProxyBase& proxy, [[maybe_unused]] uint16_t const serviceId) final + {} + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(SkeletonBase&, [[maybe_unused]] uint16_t const) final; +}; + +/** + * \brief Type selector for cluster connection implementations. + * \details This template struct selects the appropriate cluster connection type based on the + * configuration type provided. It uses SFINAE (Substitution Failure Is Not An Error) with + * enable_if to select the correct specialization. Instantiation without a valid configuration + * type will lead to a compilation error by design. + * + * \tparam T the configuration type + * \tparam Specialization SFINAE enabler parameter + */ +template +struct ClusterConnectionTypeSelector; + +/** + * \brief Type selector specialization for proxy-only configurations. + * \tparam T the proxy-only configuration type + */ +template +struct ClusterConnectionTypeSelector< + T, + typename etl::enable_if< + etl::is_base_of::value>::type> +{ + using type = ClusterConnectionNoTimeoutProxyOnly; ///< The selected cluster connection type +}; + +/** + * \brief Type selector specialization for skeleton-only configurations. + * \tparam T the skeleton-only configuration type + */ +template +struct ClusterConnectionTypeSelector< + T, + typename etl::enable_if< + etl::is_base_of::value>::type> +{ + using type = ClusterConnectionNoTimeoutSkeletonOnly; ///< The selected cluster connection type +}; + +/** + * \brief Type selector specialization for bidirectional configurations. + * \tparam T the bidirectional configuration type + */ +template +struct ClusterConnectionTypeSelector< + T, + typename etl::enable_if< + etl::is_base_of::value>::type> +{ + using type = ClusterConnectionNoTimeoutBidirectional; ///< The selected cluster connection type +}; + +/** + * \brief Type selector specialization for proxy-only configurations with timeout. + * \tparam T the proxy-only with timeout configuration type + */ +template +struct ClusterConnectionTypeSelector< + T, + typename etl::enable_if< + etl::is_base_of::value>::type> +{ + using type = ClusterConnectionProxyOnlyWithTimeout; ///< The selected cluster connection type +}; + +/** + * \brief Type selector specialization for skeleton-only configurations with timeout. + * \tparam T the skeleton-only with timeout configuration type + */ +template +struct ClusterConnectionTypeSelector< + T, + typename etl::enable_if< + etl::is_base_of::value>::type> +{ + using type = ClusterConnectionSkeletonOnlyWithTimeout; ///< The selected cluster connection type +}; + +/** + * \brief Type selector specialization for bidirectional configurations with timeout. + * \tparam T the bidirectional with timeout configuration type + */ +template +struct ClusterConnectionTypeSelector< + T, + typename etl::enable_if< + etl::is_base_of::value>::type> +{ + using type + = ClusterConnectionBidirectionalWithTimeout; ///< The selected cluster connection type +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/ClusterConnectionBase.h b/libs/bsw/middleware/include/middleware/core/ClusterConnectionBase.h new file mode 100644 index 00000000000..a3cea10e92e --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ClusterConnectionBase.h @@ -0,0 +1,149 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/ITimeout.h" +#include "middleware/core/LoggerApi.h" +#include "middleware/core/Message.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +/** + * \brief Base class for cluster connection implementations. + * \details This class provides a base implementation of the IClusterConnection interface, + * handling common functionality for cluster connections such as message processing, + * transceiver management, and message dispatching. It maintains a reference to the + * cluster connection configuration and implements core message routing logic. + */ +class ClusterConnectionBase : public IClusterConnection +{ +public: + ClusterConnectionBase(ClusterConnectionBase const&) = delete; + ClusterConnectionBase& operator=(ClusterConnectionBase const&) = delete; + ClusterConnectionBase(ClusterConnectionBase&&) = delete; + ClusterConnectionBase& operator=(ClusterConnectionBase&&) = delete; + + /** + * \brief Main message processing function. + * \details Processes incoming messages and releases message resources at the end of the + * function. This is the entry point for handling messages received from other clusters. + * + * \param msg constant reference to the message to process + */ + void processMessage(Message const& msg) const override; + + /** + * \brief Get the count of registered transceivers for a service. + * \param serviceId the service ID to query + * \return the number of registered transceivers + */ + size_t registeredTransceiversCount(uint16_t const serviceId) const override + { + return fConfiguration.registeredTransceiversCount(serviceId); + } + +protected: + /** + * \brief Get the configuration object. + * \return reference to the IClusterConnectionConfigurationBase configuration + */ + IClusterConnectionConfigurationBase& getConfiguration() const { return fConfiguration; } + + /** + * \brief Constructor for ClusterConnectionBase. + * \param configuration reference to the cluster connection configuration + */ + explicit ClusterConnectionBase(IClusterConnectionConfigurationBase& configuration); + + /** + * \brief Get the source cluster ID. + * \return the source cluster ID + */ + uint8_t getSourceClusterId() const override { return fConfiguration.getSourceClusterId(); } + + /** + * \brief Get the target cluster ID. + * \return the target cluster ID + */ + uint8_t getTargetClusterId() const override { return fConfiguration.getTargetClusterId(); } + + /** + * \brief Send a message through the cluster connection. + * \details Transmits the given message to the target cluster specified in the message header. + * + * \param msg constant reference to the message to send + * \return HRESULT indicating success or failure of the send operation + */ + HRESULT sendMessage(Message const& msg) const override; + + /** + * \brief Dispatch a message to its intended recipients. + * \details Routes the message to the appropriate proxy or skeleton based on the message header + * information. + * + * \param msg constant reference to the message to dispatch + * \return HRESULT indicating success or failure of the dispatch operation + */ + HRESULT dispatchMessage(Message const& msg) const override; + +private: + /** + * \brief Respond with an error message. + * \details Sends an error response back to the sender when an error occurs during message + * processing. + * + * \param error the error state to send + * \param msg constant reference to the original message + */ + void respondWithError(ErrorState const error, Message const& msg) const; + + IClusterConnectionConfigurationBase& + fConfiguration; ///< Reference to the cluster connection configuration +}; + +/** + * \brief Base class for cluster connections with timeout support. + * \details This class extends ClusterConnectionBase with timeout management capabilities, + * allowing transceivers to register for timeout notifications and handling periodic timeout + * updates. It is useful for cluster connections that need to track and handle communication + * timeouts. + */ +class ClusterConnectionTimeoutBase : public ClusterConnectionBase +{ +public: + /** + * \brief Register a timeout transceiver. + * \param transceiver reference to the ITimeout transceiver to register + */ + void registerTimeoutTransceiver(ITimeout& transceiver) override; + + /** + * \brief Unregister a timeout transceiver. + * \param transceiver reference to the ITimeout transceiver to unregister + */ + void unregisterTimeoutTransceiver(ITimeout& transceiver) override; + + /** + * \brief Update all registered timeout transceivers. + * \details Processes timeout updates for all registered transceivers, checking for expired + * timeouts and triggering appropriate notifications. + */ + void updateTimeouts(); + +protected: + /** + * \brief Constructor for ClusterConnectionTimeoutBase. + * \param configuration reference to the timeout configuration + */ + explicit ClusterConnectionTimeoutBase(ITimeoutConfiguration& configuration); +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/DatabaseManipulator.h b/libs/bsw/middleware/include/middleware/core/DatabaseManipulator.h new file mode 100644 index 00000000000..f6574c503cc --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/DatabaseManipulator.h @@ -0,0 +1,286 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include +#include + +#include "middleware/core/ITransceiver.h" +#include "middleware/core/TransceiverContainer.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +class ProxyBase; +class SkeletonBase; + +namespace meta +{ + +namespace internal +{ +/** + * \brief Dummy transceiver implementation for testing and placeholder purposes. + * \details This transceiver provides minimal functionality and is typically used as a + * placeholder in the transceiver database. It returns default values for most operations + * and does not perform actual message processing. + */ +struct DummyTransceiver final : public ITransceiver +{ + using Base = ITransceiver; + + /** + * \brief Handle reception of a new message (no-op). + * \param msg constant reference to the received message + * \return HRESULT::Ok always + */ + virtual HRESULT onNewMessageReceived(Message const&) override { return HRESULT::Ok; } + + /** + * \brief Get the service ID. + * \return INVALID_SERVICE_ID + */ + virtual uint16_t getServiceId() const override { return INVALID_SERVICE_ID; } + + /** + * \brief Get the source cluster ID. + * \return 0xFF + */ + virtual uint8_t getSourceClusterId() const override { return static_cast(0xFFU); } + + /** + * \brief Check if the transceiver is initialized. + * \return false always + */ + virtual bool isInitialized() const override { return false; } + + /** + * \brief Send a message (no-op). + * \return HRESULT::Ok always + */ + virtual HRESULT sendMessage(Message&) const override { return HRESULT::Ok; } + + /** + * \brief Get the address ID of the transceiver. + * \return the address ID + */ + virtual uint8_t getAddressId() const override { return fAddressId; } + + /** + * \brief Set the address ID of the transceiver. + * \param addressId the new address ID to set + */ + virtual void setAddressId(uint8_t const addressId) override { fAddressId = addressId; } + + /** + * \brief Constructor for DummyTransceiver. + * \param instanceId the service instance ID + * \param addressId the address ID (defaults to INVALID_ADDRESS_ID) + */ + explicit DummyTransceiver( + uint16_t const instanceId, uint16_t const addressId = INVALID_ADDRESS_ID) + : Base(instanceId), fAddressId(addressId) + {} + + virtual ~DummyTransceiver() = default; + +private: + uint16_t fAddressId; ///< The address ID of the dummy transceiver +}; + +} // namespace internal + +/** + * \brief Database manipulator for managing transceiver subscriptions and lookups. + * \details This class provides static utility methods for managing the transceiver database, + * including subscription management, transceiver lookups, and database queries. It operates on + * ranges of TransceiverContainer objects and provides efficient access to transceivers by + * service ID, instance ID, and address ID. + */ +class DbManipulator +{ +public: + /** + * \brief Subscribe a proxy to the transceiver database. + * \details Registers the proxy with the specified instance ID in the transceiver database, + * making it available to receive messages for the associated service. + * + * \param start pointer to the start of the transceiver container range + * \param end pointer to the end of the transceiver container range + * \param proxy reference to the proxy to subscribe + * \param instanceId the service instance ID for the subscription + * \param maxServiceId the maximum service ID in the database + * \return HRESULT indicating success or failure of the subscription + */ + static HRESULT subscribe( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + ProxyBase& proxy, + uint16_t const instanceId, + uint16_t const maxServiceId); + + /** + * \brief Subscribe a skeleton to the transceiver database. + * \details Registers the skeleton with the specified instance ID in the transceiver database, + * making it available to receive messages for the associated service. + * + * \param start pointer to the start of the transceiver container range + * \param end pointer to the end of the transceiver container range + * \param skeleton reference to the skeleton to subscribe + * \param instanceId the service instance ID for the subscription + * \param maxServiceId the maximum service ID in the database + * \return HRESULT indicating success or failure of the subscription + */ + static HRESULT subscribe( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + SkeletonBase& skeleton, + uint16_t const instanceId, + uint16_t const maxServiceId); + + /** + * \brief Unsubscribe a transceiver from the database. + * \details Removes the transceiver with the specified service ID from the transceiver + * database, stopping it from receiving further messages. + * + * \param start pointer to the start of the transceiver container range + * \param end pointer to the end of the transceiver container range + * \param transceiver reference to the transceiver to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + static void unsubscribe( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + ITransceiver& transceiver, + uint16_t const serviceId); + + /** + * \brief Get transceivers by service ID (mutable version). + * \details Returns a pointer to the transceiver container for the specified service ID, + * allowing modifications. + * + * \param start pointer to the start of the transceiver container range + * \param end pointer to the end of the transceiver container range + * \param serviceId the service ID to query + * \return pointer to the transceiver container, or nullptr if not found + */ + static TransceiverContainer* getTransceiversByServiceId( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + uint16_t const serviceId); + + /** + * \brief Get transceivers by service ID (const version). + * \details Returns a const pointer to the transceiver container for the specified service ID. + * + * \param start const pointer to the start of the transceiver container range + * \param end const pointer to the end of the transceiver container range + * \param serviceId the service ID to query + * \return const pointer to the transceiver container, or nullptr if not found + */ + static TransceiverContainer const* getTransceiversByServiceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId); + + /** + * \brief Get transceivers by service ID and service instance ID. + * \details Returns an iterator pair representing the range of transceivers that match both + * the service ID and service instance ID. + * + * \param start const pointer to the start of the transceiver container range + * \param end const pointer to the end of the transceiver container range + * \param serviceId the service ID to query + * \param instanceId the service instance ID to query + * \return pair of const iterators representing the begin and end of the matching range + */ + static etl::pair< + etl::ivector::const_iterator, + etl::ivector::const_iterator> + getTransceiversByServiceIdAndServiceInstanceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId, + uint16_t const instanceId); + + /** + * \brief Get a skeleton by service ID and service instance ID. + * \details Returns a pointer to the skeleton transceiver that matches both the service ID + * and service instance ID. + * + * \param start const pointer to the start of the transceiver container range + * \param end const pointer to the end of the transceiver container range + * \param serviceId the service ID to query + * \param instanceId the service instance ID to query + * \return pointer to the skeleton transceiver, or nullptr if not found + */ + static ITransceiver* getSkeletonByServiceIdAndServiceInstanceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId, + uint16_t const instanceId); + + /** + * \brief Find a transceiver in a container. + * \details Searches for the specified transceiver in the given container and returns an + * iterator to its position. + * + * \param transceiver pointer to the transceiver to find + * \param container reference to the vector of transceiver pointers to search + * \return iterator to the found transceiver, or end() if not found + */ + static etl::ivector::iterator + findTransceiver(ITransceiver* const& transceiver, etl::ivector& container); + + /** + * \brief Check if a skeleton with the given instance ID is registered. + * \details Checks whether a skeleton transceiver with the specified service instance ID + * exists in the container. + * + * \param container const reference to the vector of transceiver pointers to check + * \param instanceId the service instance ID to check for + * \return true if a skeleton with the instance ID is registered, false otherwise + */ + static bool isSkeletonWithServiceInstanceIdRegistered( + etl::ivector const& container, uint16_t const instanceId); + + /** + * \brief Get a transceiver by service ID, instance ID, and address ID. + * \details Returns a pointer to the transceiver that matches all three identifiers: service + * ID, service instance ID, and address ID. + * + * \param start const pointer to the start of the transceiver container range + * \param end const pointer to the end of the transceiver container range + * \param serviceId the service ID to query + * \param instanceId the service instance ID to query + * \param addressId the address ID to query + * \return pointer to the matching transceiver, or nullptr if not found + */ + static ITransceiver* getTransceiver( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId, + uint16_t const instanceId, + uint16_t const addressId); + + /** + * \brief Get the count of registered transceivers for a service. + * \details Returns the total number of transceivers registered for the specified service ID. + * + * \param start const pointer to the start of the transceiver container range + * \param end const pointer to the end of the transceiver container range + * \param serviceId the service ID to query + * \return the number of registered transceivers + */ + static size_t registeredTransceiversCount( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId); +}; +} // namespace meta +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/IClusterConnection.h b/libs/bsw/middleware/include/middleware/core/IClusterConnection.h new file mode 100644 index 00000000000..0b753e02b30 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/IClusterConnection.h @@ -0,0 +1,139 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include "middleware/core/ITimeout.h" +#include "middleware/core/ITransceiver.h" +#include "middleware/core/Message.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +class SkeletonBase; +class ProxyBase; + +/** + * \brief Interface for timeout cluster connection. + * \details This interface provides basic timeout management functionality for cluster connections. + * It allows transceivers to register and unregister for timeout notifications. + */ +class ITimeoutClusterConnection +{ +public: + /** + * \brief Register a timeout transceiver. + * + * \param timeout reference to the ITimeout transceiver to register + */ + virtual void registerTimeoutTransceiver(ITimeout&) {} + + /** + * \brief Unregister a timeout transceiver. + * + * \param timeout reference to the ITimeout transceiver to unregister + */ + virtual void unregisterTimeoutTransceiver(ITimeout&) {} +}; + +/** + * \brief Interface for cluster connection management. + * \details This interface provides the core functionality for managing connections between clusters + * in the middleware. It handles subscription management for proxies and skeletons, message routing, + * and cluster identification. A cluster connection is responsible for sending and receiving + * messages between different clusters, maintaining the registry of transceivers and dispatching + * messages to the appropriate recipients. + */ +class IClusterConnection : public ITimeoutClusterConnection +{ +public: + /** + * \brief Get the source cluster ID. + * \return the source cluster ID + */ + virtual uint8_t getSourceClusterId() const = 0; + + /** + * \brief Get the target cluster ID. + * \return the target cluster ID + */ + virtual uint8_t getTargetClusterId() const = 0; + + /** + * \brief Subscribe a proxy to the cluster connection. + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + + /** + * \brief Send a message through the cluster connection. + * \details Transmits the given message to the target cluster specified in the message header. + * + * \param msg constant reference to the message to send + * \return HRESULT indicating success or failure of the send operation + */ + virtual HRESULT sendMessage(Message const& msg) const = 0; + + /** + * \brief Process an incoming message. + * \details Processes a message received from another cluster, performing any necessary + * validation and routing operations. + * + * \param msg constant reference to the message to process + */ + virtual void processMessage(Message const& msg) const = 0; + + /** + * \brief Get the count of registered transceivers for a service. + * \param serviceId the service ID to query + * \return the number of registered transceivers + */ + virtual size_t registeredTransceiversCount(uint16_t const serviceId) const = 0; + + /** + * \brief Dispatch a message to its intended recipients. + * \details Routes the message to the appropriate proxy or skeleton based on the message header + * information. + * + * \param msg constant reference to the message to dispatch + * \return HRESULT indicating success or failure of the dispatch operation + */ + virtual HRESULT dispatchMessage(Message const& msg) const = 0; + + IClusterConnection(IClusterConnection const&) = delete; + IClusterConnection& operator=(IClusterConnection const&) = delete; + IClusterConnection(IClusterConnection&&) = delete; + IClusterConnection& operator=(IClusterConnection&&) = delete; + +protected: + IClusterConnection() = default; +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/IClusterConnectionConfigurationBase.h b/libs/bsw/middleware/include/middleware/core/IClusterConnectionConfigurationBase.h new file mode 100644 index 00000000000..6cac8fc32f7 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/IClusterConnectionConfigurationBase.h @@ -0,0 +1,497 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include "middleware/core/ITimeout.h" +#include "middleware/core/ITransceiver.h" +#include "middleware/core/Message.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +class ProxyBase; +class SkeletonBase; + +namespace meta +{ +struct TransceiverContainer; +} + +/** + * \brief Base interface for cluster connection configurations. + * \details This interface defines the core configuration methods for cluster connections, + * including cluster identification, message transmission, transceiver management, and message + * dispatching. Implementations provide the specific behavior for different cluster connection + * types. + */ +struct IClusterConnectionConfigurationBase +{ + /** + * \brief Get the source cluster ID. + * \return the source cluster ID + */ + virtual uint8_t getSourceClusterId() const = 0; + + /** + * \brief Get the target cluster ID. + * \return the target cluster ID + */ + virtual uint8_t getTargetClusterId() const = 0; + + /** + * \brief Write a message to the cluster connection. + * \param msg constant reference to the message to write + * \return true if the write was successful, false otherwise + */ + virtual bool write(Message const& msg) const = 0; + + /** + * \brief Get the count of registered transceivers for a service. + * \param serviceId the service ID to query + * \return the number of registered transceivers + */ + virtual size_t registeredTransceiversCount(uint16_t const serviceId) const = 0; + + /** + * \brief Dispatch a message to its intended recipients. + * \details Routes the message to the appropriate proxy or skeleton based on the message + * header information. + * + * \param msg constant reference to the message to dispatch + * \return HRESULT indicating success or failure of the dispatch operation + */ + virtual HRESULT dispatchMessage(Message const& msg) const = 0; + + IClusterConnectionConfigurationBase(IClusterConnectionConfigurationBase const&) = delete; + IClusterConnectionConfigurationBase& operator=(IClusterConnectionConfigurationBase const&) + = delete; + IClusterConnectionConfigurationBase(IClusterConnectionConfigurationBase&&) = delete; + IClusterConnectionConfigurationBase& operator=(IClusterConnectionConfigurationBase&&) = delete; + +protected: + virtual ~IClusterConnectionConfigurationBase() = default; + IClusterConnectionConfigurationBase() = default; + + /** + * \brief Dispatch a message to proxy transceivers. + * \details Routes the message to all proxy transceivers in the specified range. + * + * \param proxiesStart pointer to the start of the proxy transceiver container range + * \param proxiesEnd pointer to the end of the proxy transceiver container range + * \param msg constant reference to the message to dispatch + * \return HRESULT indicating success or failure of the dispatch operation + */ + static HRESULT dispatchMessageToProxy( + meta::TransceiverContainer const* const proxiesStart, + meta::TransceiverContainer const* const proxiesEnd, + Message const& msg); + + /** + * \brief Dispatch a message to skeleton transceivers. + * \details Routes the message to all skeleton transceivers in the specified range. + * + * \param skeletonsStart pointer to the start of the skeleton transceiver container range + * \param skeletonsEnd pointer to the end of the skeleton transceiver container range + * \param msg constant reference to the message to dispatch + * \return HRESULT indicating success or failure of the dispatch operation + */ + static HRESULT dispatchMessageToSkeleton( + meta::TransceiverContainer const* const skeletonsStart, + meta::TransceiverContainer const* const skeletonsEnd, + Message const& msg); + + /** + * \brief Dispatch a message to both proxy and skeleton transceivers. + * \details Routes the message to all transceivers (both proxies and skeletons) in the + * specified ranges. + * + * \param proxiesStart pointer to the start of the proxy transceiver container range + * \param proxiesEnd pointer to the end of the proxy transceiver container range + * \param skeletonsStart pointer to the start of the skeleton transceiver container range + * \param skeletonsEnd pointer to the end of the skeleton transceiver container range + * \param msg constant reference to the message to dispatch + * \return HRESULT indicating success or failure of the dispatch operation + */ + static HRESULT dispatchMessage( + meta::TransceiverContainer const* const proxiesStart, + meta::TransceiverContainer const* const proxiesEnd, + meta::TransceiverContainer const* const skeletonsStart, + meta::TransceiverContainer const* const skeletonsEnd, + Message const& msg); +}; + +/** + * \brief Configuration interface for cluster connections with timeout support. + * \details Extends the base configuration interface with timeout management capabilities, + * allowing registration and management of timeout transceivers. This interface is used by + * cluster connections that need to track and handle communication timeouts. + */ +struct ITimeoutConfiguration : public IClusterConnectionConfigurationBase +{ + /** + * \brief Register a timeout transceiver. + * \details Adds the given transceiver to the list of timeout transceivers that will be + * notified of timeout events. + * + * \param transceiver reference to the ITimeout transceiver to register + */ + virtual void registerTimeoutTransceiver(ITimeout& transceiver) = 0; + + /** + * \brief Unregister a timeout transceiver. + * \details Removes the given transceiver from the list of timeout transceivers. + * + * \param transceiver reference to the ITimeout transceiver to unregister + */ + virtual void unregisterTimeoutTransceiver(ITimeout& transceiver) = 0; + + /** + * \brief Update all registered timeout transceivers. + * \details Processes timeout updates for all registered transceivers, checking for expired + * timeouts and triggering appropriate notifications. + */ + virtual void updateTimeouts() = 0; + + ITimeoutConfiguration(ITimeoutConfiguration const&) = delete; + ITimeoutConfiguration& operator=(ITimeoutConfiguration const&) = delete; + ITimeoutConfiguration(ITimeoutConfiguration&&) = delete; + ITimeoutConfiguration& operator=(ITimeoutConfiguration&&) = delete; + +protected: + virtual ~ITimeoutConfiguration() = default; + ITimeoutConfiguration() = default; + + /** + * \brief Static helper to register a timeout transceiver. + * \details Adds the transceiver to the provided vector of timeout transceivers. + * + * \param transceiver reference to the ITimeout transceiver to register + * \param timeoutTransceivers reference to the vector of timeout transceivers + */ + static void registerTimeoutTransceiver( + ITimeout& transceiver, ::etl::ivector& timeoutTransceivers); + + /** + * \brief Static helper to unregister a timeout transceiver. + * \details Removes the transceiver from the provided vector of timeout transceivers. + * + * \param transceiver reference to the ITimeout transceiver to unregister + * \param timeoutTransceivers reference to the vector of timeout transceivers + */ + static void unregisterTimeoutTransceiver( + ITimeout& transceiver, ::etl::ivector& timeoutTransceivers); + + /** + * \brief Static helper to update timeouts for all transceivers. + * \details Processes timeout updates for all transceivers in the provided vector. + * + * \param timeoutTransceivers constant reference to the vector of timeout transceivers + */ + static void updateTimeouts(::etl::ivector const& timeoutTransceivers); +}; + +/** + * \brief Configuration interface for proxy-only cluster connections. + * \details Extends the base configuration interface with proxy subscription management. + * This interface is used by cluster connections that only support proxy communication. + */ +struct IClusterConnectionConfigurationProxyOnly : public IClusterConnectionConfigurationBase +{ + IClusterConnectionConfigurationProxyOnly(IClusterConnectionConfigurationProxyOnly const&) + = delete; + IClusterConnectionConfigurationProxyOnly& + operator=(IClusterConnectionConfigurationProxyOnly const&) + = delete; + IClusterConnectionConfigurationProxyOnly(IClusterConnectionConfigurationProxyOnly&&) = delete; + IClusterConnectionConfigurationProxyOnly& operator=(IClusterConnectionConfigurationProxyOnly&&) + = delete; + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers the proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationProxyOnly() = default; + IClusterConnectionConfigurationProxyOnly() = default; +}; + +/** + * \brief Configuration interface for skeleton-only cluster connections. + * \details Extends the base configuration interface with skeleton subscription management. + * This interface is used by cluster connections that only support skeleton communication. + */ +struct IClusterConnectionConfigurationSkeletonOnly : public IClusterConnectionConfigurationBase +{ + IClusterConnectionConfigurationSkeletonOnly(IClusterConnectionConfigurationSkeletonOnly const&) + = delete; + IClusterConnectionConfigurationSkeletonOnly& + operator=(IClusterConnectionConfigurationSkeletonOnly const&) + = delete; + IClusterConnectionConfigurationSkeletonOnly(IClusterConnectionConfigurationSkeletonOnly&&) + = delete; + IClusterConnectionConfigurationSkeletonOnly& + operator=(IClusterConnectionConfigurationSkeletonOnly&&) + = delete; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers the skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationSkeletonOnly() = default; + IClusterConnectionConfigurationSkeletonOnly() = default; +}; + +/** + * \brief Configuration interface for bidirectional cluster connections. + * \details Extends the base configuration interface with both proxy and skeleton subscription + * management. This interface is used by cluster connections that support full bidirectional + * communication. + */ +struct IClusterConnectionConfigurationBidirectional : public IClusterConnectionConfigurationBase +{ + IClusterConnectionConfigurationBidirectional( + IClusterConnectionConfigurationBidirectional const&) + = delete; + IClusterConnectionConfigurationBidirectional& + operator=(IClusterConnectionConfigurationBidirectional const&) + = delete; + IClusterConnectionConfigurationBidirectional(IClusterConnectionConfigurationBidirectional&&) + = delete; + IClusterConnectionConfigurationBidirectional& + operator=(IClusterConnectionConfigurationBidirectional&&) + = delete; + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers the proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers the skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationBidirectional() = default; + IClusterConnectionConfigurationBidirectional() = default; +}; + +/** + * \brief Configuration interface for proxy-only cluster connections with timeout support. + * \details Extends the timeout configuration interface with proxy subscription management. + * This interface is used by cluster connections that support proxy communication with timeout + * tracking. + */ +struct IClusterConnectionConfigurationProxyOnlyWithTimeout : public ITimeoutConfiguration +{ + IClusterConnectionConfigurationProxyOnlyWithTimeout( + IClusterConnectionConfigurationProxyOnlyWithTimeout const&) + = delete; + IClusterConnectionConfigurationProxyOnlyWithTimeout& + operator=(IClusterConnectionConfigurationProxyOnlyWithTimeout const&) + = delete; + IClusterConnectionConfigurationProxyOnlyWithTimeout( + IClusterConnectionConfigurationProxyOnlyWithTimeout&&) + = delete; + IClusterConnectionConfigurationProxyOnlyWithTimeout& + operator=(IClusterConnectionConfigurationProxyOnlyWithTimeout&&) + = delete; + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers the proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationProxyOnlyWithTimeout() = default; + IClusterConnectionConfigurationProxyOnlyWithTimeout() = default; +}; + +/** + * \brief Configuration interface for bidirectional cluster connections with timeout support. + * \details Extends the timeout configuration interface with both proxy and skeleton subscription + * management. This interface is used by cluster connections that support full bidirectional + * communication with timeout tracking. + */ +struct IClusterConnectionConfigurationBidirectionalWithTimeout : public ITimeoutConfiguration +{ + IClusterConnectionConfigurationBidirectionalWithTimeout( + IClusterConnectionConfigurationBidirectionalWithTimeout const&) + = delete; + IClusterConnectionConfigurationBidirectionalWithTimeout& + operator=(IClusterConnectionConfigurationBidirectionalWithTimeout const&) + = delete; + IClusterConnectionConfigurationBidirectionalWithTimeout( + IClusterConnectionConfigurationBidirectionalWithTimeout&&) + = delete; + IClusterConnectionConfigurationBidirectionalWithTimeout& + operator=(IClusterConnectionConfigurationBidirectionalWithTimeout&&) + = delete; + + /** + * \brief Subscribe a proxy to the cluster connection. + * \details Registers the proxy with the specified service instance ID to receive messages. + * + * \param proxy reference to the proxy to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a proxy from the cluster connection. + * \details Removes the proxy with the specified service ID from the cluster connection. + * + * \param proxy reference to the proxy to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers the skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + IClusterConnectionConfigurationBidirectionalWithTimeout() = default; + virtual ~IClusterConnectionConfigurationBidirectionalWithTimeout() = default; +}; + +/** + * \brief Configuration interface for skeleton-only cluster connections with timeout support. + * \details Extends the timeout configuration interface with skeleton subscription management. + * This interface is used by cluster connections that support skeleton communication with timeout + * tracking. + */ +struct IClusterConnectionConfigurationSkeletonOnlyWithTimeout : public ITimeoutConfiguration +{ + IClusterConnectionConfigurationSkeletonOnlyWithTimeout( + IClusterConnectionConfigurationSkeletonOnlyWithTimeout const&) + = delete; + IClusterConnectionConfigurationSkeletonOnlyWithTimeout& + operator=(IClusterConnectionConfigurationSkeletonOnlyWithTimeout const&) + = delete; + IClusterConnectionConfigurationSkeletonOnlyWithTimeout( + IClusterConnectionConfigurationSkeletonOnlyWithTimeout&&) + = delete; + IClusterConnectionConfigurationSkeletonOnlyWithTimeout& + operator=(IClusterConnectionConfigurationSkeletonOnlyWithTimeout&&) + = delete; + + /** + * \brief Subscribe a skeleton to the cluster connection. + * \details Registers the skeleton with the specified service instance ID to receive messages. + * + * \param skeleton reference to the skeleton to subscribe + * \param serviceInstanceId the service instance ID for the subscription + * \return HRESULT indicating success or failure of the subscription + */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** + * \brief Unsubscribe a skeleton from the cluster connection. + * \details Removes the skeleton with the specified service ID from the cluster connection. + * + * \param skeleton reference to the skeleton to unsubscribe + * \param serviceId the service ID for the unsubscription + */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationSkeletonOnlyWithTimeout() = default; + IClusterConnectionConfigurationSkeletonOnlyWithTimeout() = default; +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/ITimeout.h b/libs/bsw/middleware/include/middleware/core/ITimeout.h new file mode 100644 index 00000000000..2b649f289b9 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ITimeout.h @@ -0,0 +1,29 @@ +// Copyright 2025 BMW AG + +#pragma once + +namespace middleware +{ +namespace core +{ +/** + * \brief Interface for timeout management. + * \details This interface defines the contract for objects that need to handle timeout + * updates. Implementations of this interface can register with cluster connections to + * receive periodic timeout notifications and process expired timeouts accordingly. + */ +struct ITimeout +{ + /** + * \brief Update all managed timeouts. + * \details This method is called periodically to check for expired timeouts and trigger + * appropriate timeout handling. Implementations should check all managed timers and + * process any that have expired. + */ + virtual void updateTimeouts() = 0; + + ITimeout& operator=(ITimeout const&) = delete; + ITimeout& operator=(ITimeout&&) = delete; +}; +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/ITransceiver.h b/libs/bsw/middleware/include/middleware/core/ITransceiver.h new file mode 100644 index 00000000000..a1f4720e561 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ITransceiver.h @@ -0,0 +1,111 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include "middleware/core/Message.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +/** + * \brief Interface for message transceivers in the middleware. + * \details This interface defines the core functionality for objects that can send and receive + * messages in the middleware layer. Transceivers represent communication endpoints (proxies or + * skeletons) and are identified by service ID, instance ID, and address ID. They handle incoming + * messages and can send messages to other transceivers. + */ +class ITransceiver +{ +public: + /** + * \brief Get the service instance ID. + * \return the service instance ID + */ + uint16_t getInstanceId() const { return instanceId_; } + + /** + * \brief Get the service ID. + * \return the service ID + */ + virtual uint16_t getServiceId() const = 0; + + /** + * \brief Get the address ID. + * \details Returns the unique address identifier for this transceiver instance, used for + * routing messages to specific proxy instances. + * + * \return the address ID + */ + virtual uint8_t getAddressId() const = 0; + + /** + * \brief Handle reception of a new message. + * \details Called when a new message is received by this transceiver. Implementations should + * process the message according to their specific logic. + * + * \param msg constant reference to the received message + * \return HRESULT indicating success or failure of message processing + */ + virtual HRESULT onNewMessageReceived(Message const& msg) = 0; + + /** + * \brief Set the service instance ID. + * \param instanceId the new service instance ID to set + */ + void setInstanceId(uint16_t const instanceId) { instanceId_ = instanceId; } + + /** + * \brief Set the address ID. + * \details Updates the unique address identifier for this transceiver instance. + * + * \param addressId the new address ID to set + */ + virtual void setAddressId(uint8_t const addressId) = 0; + + /** + * \brief Check if the transceiver is initialized. + * \details Returns whether this transceiver has been properly initialized and is ready to + * send and receive messages. + * + * \return true if initialized, false otherwise + */ + virtual bool isInitialized() const = 0; + + /** + * \brief Send a message through this transceiver. + * \details Transmits the given message to the appropriate destination based on the message + * header information. + * + * \param msg reference to the message to send + * \return HRESULT indicating success or failure of the send operation + */ + virtual HRESULT sendMessage(Message& msg) const = 0; + + /** + * \brief Get the source cluster ID. + * \details Returns the identifier of the cluster where this transceiver resides. + * + * \return the source cluster ID + */ + virtual uint8_t getSourceClusterId() const = 0; + + ITransceiver(ITransceiver const&) = delete; + ITransceiver& operator=(ITransceiver const&) = delete; + ITransceiver(ITransceiver&&) = delete; + ITransceiver& operator=(ITransceiver&&) = delete; + +protected: + uint16_t instanceId_; ///< The service instance ID for this transceiver + + constexpr explicit ITransceiver(uint16_t const instanceId = INVALID_INSTANCE_ID) + : instanceId_(instanceId) + {} + + virtual ~ITransceiver() = default; +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/InstancesDatabase.h b/libs/bsw/middleware/include/middleware/core/InstancesDatabase.h new file mode 100644 index 00000000000..8aa14ca9651 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/InstancesDatabase.h @@ -0,0 +1,56 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include "IClusterConnection.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +/** + * \brief Interface for instance database management. + * \details This interface provides access to the database of service instances, including + * both skeleton and proxy cluster connections, as well as the registered instance IDs. + * Implementations of this interface maintain the mappings between service instances and + * their corresponding cluster connections. + */ +struct IInstanceDatabase +{ + /** + * \brief Get the range of skeleton cluster connections. + * \details Returns a span containing all skeleton cluster connections registered in the + * database. This allows iteration over all available skeleton connections. + * + * \return span of const pointers to IClusterConnection objects for skeletons + */ + virtual etl::span getSkeletonConnectionsRange() const = 0; + + /** + * \brief Get the range of proxy cluster connections. + * \details Returns a span containing all proxy cluster connections registered in the + * database. This allows iteration over all available proxy connections. + * + * \return span of const pointers to IClusterConnection objects for proxies + */ + virtual etl::span getProxyConnectionsRange() const = 0; + + /** + * \brief Get the range of registered instance IDs. + * \details Returns a span containing all service instance IDs registered in the database. + * This provides access to all active service instances. + * + * \return span of const uint16_t instance IDs + */ + virtual etl::span getInstanceIdsRange() const = 0; + + IInstanceDatabase& operator=(IInstanceDatabase const&) = delete; + IInstanceDatabase& operator=(IInstanceDatabase&&) = delete; +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/LoggerApi.h b/libs/bsw/middleware/include/middleware/core/LoggerApi.h new file mode 100644 index 00000000000..2def6f6e941 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/LoggerApi.h @@ -0,0 +1,149 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include + +#include "middleware/core/Message.h" +#include "middleware/core/types.h" +#include "middleware/logger/Logger.h" + +namespace middleware +{ +namespace logger +{ + +ETL_INLINE_VAR constexpr uint8_t ALLOCATION_FAILURE_LOG_SIZE + = 20U; ///< Log buffer size for allocation failures +ETL_INLINE_VAR constexpr uint8_t INIT_FAILURE_LOG_SIZE + = 11U; ///< Log buffer size for initialization failures +ETL_INLINE_VAR constexpr uint8_t MSG_SEND_FAILURE_LOG_SIZE + = 16U; ///< Log buffer size for message sending failures +ETL_INLINE_VAR constexpr uint8_t CROSS_THREAD_VIOLATION_LOG_SIZE + = 18U; ///< Log buffer size for cross-thread violations + +/** + * \brief Helper template to count the total size of types in bytes. + * \details This template struct recursively calculates the total byte size of multiple types, + * used for determining log buffer sizes. Specialized for core::Message to only count relevant + * header information. + * + * \tparam Args variadic template parameter pack of types to count + */ +template +struct count_bytes; + +/** + * \brief Specialization for a single type. + * \tparam T the type to count bytes for + */ +template +struct count_bytes +{ + static constexpr size_t VALUE = sizeof(T); ///< The size of type T in bytes +}; + +/** + * \brief Specialization for core::Message type. + * \details Only counts 10 bytes for relevant header information instead of the full message size. + */ +template<> +struct count_bytes +{ + static constexpr size_t VALUE = 10U; ///< Only relevant header information (10 bytes) +}; + +/** + * \brief Recursive template specialization for multiple types. + * \tparam T the first type + * \tparam Args the remaining types + */ +template +struct count_bytes +{ + static constexpr size_t VALUE + = count_bytes::VALUE + count_bytes::VALUE; ///< Sum of all type sizes +}; + +/** + * \brief Logs an allocation failure event. + * \details Records an allocation failure with the specified severity level, error code, result, + * message details, and the size of the failed allocation. This is typically called when memory + * allocation for message handling fails. + * + * \param level the severity level of the log message (e.g., INFO, WARN, ERROR) + * \param error the specific error that occurred during allocation + * \param res the HRESULT indicating the result of the allocation attempt + * \param msg constant reference to the message object containing middleware communication details + * \param size the size of the allocation that failed + */ +void logAllocationFailure( + LogLevel const level, + Error const error, + core::HRESULT const res, + core::Message const& msg, + uint32_t const size); + +/** + * \brief Logs an initialization failure event for a service. + * \details Records a service initialization failure with the specified severity level, error code, + * result, and service identification information. This is called when a service or service instance + * fails to initialize properly. + * + * \param level the severity level of the log message + * \param error the specific error encountered during the initialization process + * \param res the HRESULT indicating the result of the initialization attempt + * \param serviceId the identifier of the service that failed to initialize + * \param serviceInstanceId the instance identifier of the service that failed + * \param sourceCluster the cluster from which the initialization failure originated + */ +void logInitFailure( + LogLevel const level, + Error const error, + core::HRESULT const res, + uint16_t const serviceId, + uint16_t const serviceInstanceId, + uint8_t const sourceCluster); + +/** + * \brief Logs a failure encountered while sending a message. + * \details Records a message sending failure with the specified severity level, error code, + * result, and message details. This is typically called when message transmission fails due to + * queue full, service not found, or other transmission errors. + * + * \param level the severity level of the log message + * \param error the specific error that occurred during message sending + * \param res the HRESULT indicating the result of the sending attempt + * \param msg constant reference to the message object that failed to send + */ +void logMessageSendingFailure( + LogLevel const level, Error const error, core::HRESULT const res, core::Message const& msg); + +/** + * \brief Logs a violation of cross-thread operations. + * \details Records a cross-thread access violation with the specified severity level, error code, + * and detailed information about the violation. This is called when a service or proxy is accessed + * from a different thread than the one it was initialized on, which may violate thread-safety + * requirements. + * + * \param level the severity level of the log message + * \param error the specific error indicating the type of violation + * \param sourceCluster the cluster from which the violation originated + * \param serviceId the identifier of the service involved in the violation + * \param serviceInstanceId the instance identifier of the service involved + * \param initId the ID of the initialization task/thread + * \param currentTaskId the ID of the current task/thread that caused the violation + */ +void logCrossThreadViolation( + LogLevel const level, + Error const error, + uint8_t const sourceCluster, + uint16_t const serviceId, + uint16_t const serviceInstanceId, + uint32_t const initId, + uint32_t const currentTaskId); + +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/ProxyBase.h b/libs/bsw/middleware/include/middleware/core/ProxyBase.h new file mode 100644 index 00000000000..894b1bbec41 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ProxyBase.h @@ -0,0 +1,129 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include + +#include "IClusterConnection.h" +#include "ITransceiver.h" +#include "InstancesDatabase.h" + +namespace middleware +{ +namespace core +{ + +/** + * \brief Base class for proxy implementations. + * \details This class provides the common functionality for all proxy objects in the middleware. + * Proxies represent the client side of service communication, allowing applications to call + * methods on remote service instances (skeletons). The ProxyBase handles message generation, + * sending, and cluster connection management. + */ +class ProxyBase : public ITransceiver +{ +public: + /** + * \brief Set the address ID for this proxy. + * \details Updates the unique address identifier for this proxy instance, used for routing + * response messages back to this specific proxy. + * + * \param addressId the new address ID to set + */ + void setAddressId(uint8_t const addressId) final; + + /** + * \brief Get the address ID of this proxy. + * \details Returns the unique address identifier for this proxy instance. + * + * \return the address ID + */ + uint8_t getAddressId() const final; + + /** + * \brief Check if the proxy is initialized. + * \details Returns whether this proxy has been properly initialized and is ready to + * communicate with skeletons. + * + * \return true if initialized, false otherwise + */ + bool isInitialized() const override; + + /** + * \brief Generate a message header for a request. + * \details Creates a message header with the proxy's service information and the specified + * member ID and request ID. This is typically used when preparing to send a method call or + * request to a skeleton. + * + * \param memberId the member (method/event) ID within the service + * \param requestId the request ID for the message (defaults to INVALID_REQUEST_ID) + * \return the generated message with header populated + */ + [[nodiscard]] Message generateMessageHeader( + uint16_t const memberId, uint16_t const requestId = INVALID_REQUEST_ID) const; + + /** + * \brief Send a message through this proxy. + * \details Transmits the given message to the skeleton via the cluster connection. + * + * \param msg reference to the message to send + * \return HRESULT indicating success or failure of the send operation + */ + [[nodiscard]] HRESULT sendMessage(Message& msg) const override; + +protected: + constexpr ProxyBase() : ITransceiver(), addressId_(INVALID_ADDRESS_ID) {} + + virtual ~ProxyBase() = default; + + /** + * \brief Get the source cluster ID from the connection. + * \details Returns the identifier of the cluster where this proxy resides. + * + * \return the source cluster ID + */ + uint8_t getSourceClusterId() const final; + + /** + * \brief Unsubscribe this proxy from the cluster connection. + * \details Removes this proxy from the cluster connection for the specified service ID, + * stopping it from receiving further messages. + * + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(uint16_t const serviceId); + + /** + * \brief Check for cross-thread access violations. + * \details Verifies that the current thread matches the initialization thread and logs an + * error if a violation is detected. + * + * \param initId the ID of the thread that initialized this proxy + */ + void checkCrossThreadError(uint32_t const initId) const; + + /** + * \brief Initialize the proxy from the instances database. + * \details Looks up the cluster connection for the specified instance ID and source cluster + * in the given database range and initializes the proxy accordingly. + * + * \param instanceId the service instance ID to initialize for + * \param sourceCluster the source cluster ID + * \param dbRange span of instance database pointers to search + * \return HRESULT indicating success or failure of the initialization + */ + HRESULT initFromInstancesDatabase( + uint16_t const instanceId, + uint8_t const sourceCluster, + etl::span const& dbRange); + + IClusterConnection* fConnection{nullptr}; ///< Pointer to the cluster connection for this proxy + +private: + uint8_t addressId_; ///< The unique address ID for this proxy instance +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/SkeletonBase.h b/libs/bsw/middleware/include/middleware/core/SkeletonBase.h new file mode 100644 index 00000000000..e3a8d5a0211 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/SkeletonBase.h @@ -0,0 +1,133 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include + +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/ITransceiver.h" +#include "middleware/core/InstancesDatabase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +class EventSender; + +/** + * \brief Base class for skeleton implementations. + * \details This class provides the common functionality for all skeleton objects in the middleware. + * Skeletons represent the server side of service communication, receiving method calls from + * proxies and sending responses/events back. The SkeletonBase handles message processing, event + * sending, and manages connections to multiple clusters. + */ +class SkeletonBase : public ITransceiver +{ + friend class EventSender; + +public: + /** + * \brief Check if the skeleton is initialized. + * \details Returns whether this skeleton has been properly initialized and is ready to + * receive requests from proxies. + * + * \return true if initialized, false otherwise + */ + bool isInitialized() const override; + + /** + * \brief Send a message through this skeleton. + * \details Transmits the given message (typically a response or event) to proxies via the + * cluster connections. + * + * \param msg reference to the message to send + * \return HRESULT indicating success or failure of the send operation + */ + HRESULT sendMessage(Message& msg) const override; + + /** + * \brief Get the source cluster ID. + * \details Returns the identifier of the cluster where this skeleton resides. + * + * \return the source cluster ID + */ + uint8_t getSourceClusterId() const final; + + /** + * \brief Get the span of cluster connections. + * \details Returns a reference to the span containing all cluster connections associated with + * this skeleton, allowing the skeleton to communicate with proxies in multiple clusters. + * + * \return constant reference to the span of cluster connection pointers + */ + etl::span const& getClusterConnections() const; + +protected: + virtual ~SkeletonBase(); + + /** + * \brief Unsubscribe this skeleton from cluster connections. + * \details Removes this skeleton from all cluster connections for the specified service ID, + * stopping it from receiving further requests. + * + * \param serviceId the service ID for the unsubscription + */ + void unsubscribe(uint16_t const serviceId); + + /** + * \brief Check for cross-thread access violations. + * \details Verifies that the current thread matches the initialization thread and logs an + * error if a violation is detected. + * + * \param initId the ID of the thread that initialized this skeleton + */ + void checkCrossThreadError(uint32_t const initId) const; + + /** + * \brief Initialize the skeleton from the instances database. + * \details Looks up the cluster connections for the specified instance ID in the given + * database range and initializes the skeleton accordingly. + * + * \param instanceId the service instance ID to initialize for + * \param dbRange span of instance database pointers to search + * \return HRESULT indicating success or failure of the initialization + */ + HRESULT initFromInstancesDatabase( + uint16_t const instanceId, etl::span const& dbRange); + + etl::span + connections_; ///< Span of cluster connections for this skeleton + +private: + /** + * \brief Get the address ID (not used for skeletons). + * \details Skeletons do not use address IDs; always returns INVALID_ADDRESS_ID. + * + * \return INVALID_ADDRESS_ID + */ + uint8_t getAddressId() const final { return INVALID_ADDRESS_ID; } + + /** + * \brief Set the address ID (no-op for skeletons). + * \details Skeletons do not use address IDs; this operation has no effect. + * + * \param addressId the address ID (ignored) + */ + void setAddressId(uint8_t const) final {} + + /** + * \brief Get the process/task ID (virtual for derived classes). + * \details Returns the ID of the process/task that owns this skeleton, used for cross-thread + * violation checks. Default implementation returns INVALID_TASK_ID. + * + * \return the process/task ID + */ + virtual uint32_t getProcessId() const { return INVALID_TASK_ID; } +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/TransceiverContainer.h b/libs/bsw/middleware/include/middleware/core/TransceiverContainer.h new file mode 100644 index 00000000000..2082f1b61cb --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/TransceiverContainer.h @@ -0,0 +1,97 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include +#include + +#include "middleware/core/ITransceiver.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +namespace meta +{ +/** + * \brief Container for managing transceiver collections. + * \details This struct holds a collection of transceivers associated with a specific service ID. + * It provides comparator functors for sorting and searching transceivers by instance ID and + * address ID. The container is used by the middleware database to organize and manage proxies + * and skeletons. + */ +struct TransceiverContainer +{ + /** + * \brief Comparator for transceivers with instance ID and address ID. + * \details Compares transceivers based on a pair of (instance ID, address ID), providing + * a total ordering for transceivers. This is used when both instance and address + * identification are needed. + */ + struct TransceiverComparator + { + /** + * \brief Compare two transceivers by instance ID and address ID. + * \details Returns true if the left transceiver's (instanceId, addressId) pair is less + * than the right transceiver's pair using lexicographical comparison. + * + * \param lhs const pointer to the left-hand transceiver + * \param rhs const pointer to the right-hand transceiver + * \return true if lhs < rhs, false otherwise + */ + inline bool operator()(ITransceiver const* const lhs, ITransceiver const* const rhs) const + { + return ( + etl::make_pair(lhs->getInstanceId(), lhs->getAddressId()) + < etl::make_pair(rhs->getInstanceId(), rhs->getAddressId())); + } + }; + + /** + * \brief Comparator for transceivers by instance ID only. + * \details Compares transceivers based solely on their instance ID, ignoring address ID. + * This is used when only instance-level identification is needed, such as when searching + * for skeletons. + */ + struct TransceiverComparatorNoAddressId + { + /** + * \brief Compare two transceivers by instance ID only. + * \details Returns true if the left transceiver's instance ID is less than the right + * transceiver's instance ID. + * + * \param lhs const pointer to the left-hand transceiver + * \param rhs const pointer to the right-hand transceiver + * \return true if lhs instance ID < rhs instance ID, false otherwise + */ + inline bool operator()(ITransceiver const* const lhs, ITransceiver const* const rhs) const + { + return (lhs->getInstanceId() < rhs->getInstanceId()); + } + }; + + TransceiverContainer() = delete; + ~TransceiverContainer() = default; + + constexpr TransceiverContainer( + etl::ivector* container, uint16_t serviceid, uint16_t actualAddress) + : fContainer(container), fServiceid(serviceid), fActualAddress(actualAddress) + {} + + TransceiverContainer(TransceiverContainer const&) = delete; + TransceiverContainer& operator=(TransceiverContainer const&) = delete; + TransceiverContainer(TransceiverContainer&&) = delete; + TransceiverContainer& operator=(TransceiverContainer&&) = delete; + + etl::ivector* const + fContainer{}; ///< Pointer to the vector holding transceiver pointers + uint16_t const fServiceid; ///< The service ID associated with this container + uint16_t fActualAddress; ///< The current/next available address ID for proxies +}; + +} // namespace meta +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/traits.h b/libs/bsw/middleware/include/middleware/core/traits.h new file mode 100644 index 00000000000..fd05f1562f7 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/traits.h @@ -0,0 +1,40 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include +#include + +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +template +struct is_span : etl::false_type +{}; + +template +struct is_span<::etl::span> : etl::true_type +{}; + +template +struct enable_if_type +{ + using type = R; +}; + +template +struct GetCopyPolicyType +{ + using type = void; +}; + +template +struct GetCopyPolicyType::type> +{ + using type = typename E::CopyPolicy; +}; +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/types.h b/libs/bsw/middleware/include/middleware/core/types.h index 3d923cd4fcc..7202d9eb933 100644 --- a/libs/bsw/middleware/include/middleware/core/types.h +++ b/libs/bsw/middleware/include/middleware/core/types.h @@ -46,22 +46,24 @@ enum class HRESULT : uint8_t NotImplemented = 0xF7U, WrongTargetClusterId = 0xF6U, CannotAllocatePayload = 0xF5U, - TransceiverAllocationFailed = 0xF4U, - UnknownMessageType = 0xF3U, - ServiceNotFound = 0xF2U, - FutureAlreadyInUse = 0xF1U, - SkeletonWithThisServiceIdAlreadyRegistered = 0xF0U, - ResponseBufferFutureNotFound = 0xEEU, - NoClientsAvailable = 0xEDU, - ServiceBusy = 0xECU, - ServiceMemberIdNotFound = 0xEBU, - EventNotSendSuccessfully = 0xEAU, - RoutingError = 0xE9U, - InvalidPayload = 0xE8U, - InvalidRecipientCluster = 0xE7U, - UnchangedValueNotSent = 0xE6U, - DebouncedValueNotSent = 0xE5U, - TimingValueNotSent = 0xE4U, + CannotDeallocatePayload = 0xF4U, + TransceiverInitializationFailed = 0xF3U, + UnknownMessageType = 0xF2U, + ServiceNotFound = 0xF1U, + FutureAlreadyInUse = 0xF0U, + FutureNotFound = 0xEEU, + SkeletonWithThisServiceIdAlreadyRegistered = 0xEDU, + ResponseBufferFutureNotFound = 0xECU, + NoClientsAvailable = 0xEBU, + ServiceBusy = 0xEAU, + ServiceMemberIdNotFound = 0xE9U, + EventNotSendSuccessfully = 0xE8U, + RoutingError = 0xE7U, + InvalidPayload = 0xE6U, + InvalidRecipientCluster = 0xE5U, + UnchangedValueNotSent = 0xE4U, + DebouncedValueNotSent = 0xE3U, + TimingValueNotSent = 0xE2U, Ok = 0x00U }; diff --git a/libs/bsw/middleware/include/middleware/logger/Logger.h b/libs/bsw/middleware/include/middleware/logger/Logger.h new file mode 100644 index 00000000000..c980da512e6 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/logger/Logger.h @@ -0,0 +1,72 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include + +namespace middleware +{ +namespace logger +{ + +enum class LogLevel : uint8_t +{ + None = 0U, + Critical = 1U, + Error = 2U, + Warning = 3U, + Info = 4U, + Debug = 5U, + Trace = 6U +}; + +enum class Error : uint8_t +{ + Allocation = 0U, ///< Memory allocation failure + Deallocation = 1U, ///< Memory deallocation failure + ProxyInitialization = 2U, ///< Proxy initialization failure + SkeletonInitialization = 3U, ///< Skeleton initialization failure + DispatchMessage = 4U, ///< Message dispatch failure + SendMessage = 5U, ///< Message sending failure + ProxyCrossThreadViolation = 6U, ///< Proxy cross-thread access violation + SkeletonCrossThreadViolation = 7U, ///< Skeleton cross-thread access violation +}; + +/** + * \brief Generic logging function for formatted messages. + * \details Platform-specific implementation of the logging function that accepts printf-style + * format strings. This function must be implemented when integrating the middleware into a new + * platform to route log messages to the appropriate logging backend. + * + * \param level the log level for this message + * \param f the format string (printf-style) + * \param ... variadic arguments for the format string + */ +extern void log(LogLevel const level, char const* const f, ...); + +/** + * \brief Generic logging function for binary data. + * \details Platform-specific implementation of the logging function that logs binary data. + * This function must be implemented when integrating the middleware into a new platform to + * handle binary log data, which may be used for structured logging or diagnostic purposes. + * + * \param level the log level for this data + * \param data span containing the binary data to log + */ +extern void log_binary(LogLevel const level, etl::span const data); + +/** + * \brief Get the message ID associated with an error type. + * \details Returns a unique message identifier for the given error type. This is useful for + * extending logs to contain DLT (Diagnostic Log and Trace) information or other structured + * logging formats. + * + * \param id the error type + * \return the message ID associated with the error + */ +extern uint32_t getMessageId(Error const id); + +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/os/TaskIdProvider.h b/libs/bsw/middleware/include/middleware/os/TaskIdProvider.h new file mode 100644 index 00000000000..561c153444a --- /dev/null +++ b/libs/bsw/middleware/include/middleware/os/TaskIdProvider.h @@ -0,0 +1,24 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +namespace middleware +{ +namespace os +{ + +/** + * \brief Get the current process/task ID. + * \details Platform-specific function that returns the identifier of the currently executing + * process or task. This is used for thread-safety validation to detect cross-thread access + * violations in proxies and skeletons. The implementation must be provided for each platform + * integration. + * + * \return the current process/task ID + */ +extern uint32_t getProcessId(); + +} // namespace os +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/time/SystemTimerProvider.h b/libs/bsw/middleware/include/middleware/time/SystemTimerProvider.h new file mode 100644 index 00000000000..2095beb2b4b --- /dev/null +++ b/libs/bsw/middleware/include/middleware/time/SystemTimerProvider.h @@ -0,0 +1,34 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +namespace middleware +{ +namespace time +{ +/** + * \brief Get the current system time in milliseconds. + * \details Platform-specific function that returns the current system time in milliseconds. + * This is used for timeout management and timestamp operations in the middleware. The + * implementation must be provided for each platform integration and should return a monotonic + * timestamp. + * + * \return the current system time in milliseconds + */ +extern uint32_t getCurrentTimeInMs(); + +/** + * \brief Get the current system time in microseconds. + * \details Platform-specific function that returns the current system time in microseconds. + * This provides higher resolution timing for precise timeout management and timestamp operations + * in the middleware. The implementation must be provided for each platform integration and should + * return a monotonic timestamp. + * + * \return the current system time in microseconds + */ +extern uint32_t getCurrentTimeInUs(); + +} // namespace time +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/ClusterConnection.cpp b/libs/bsw/middleware/src/middleware/core/ClusterConnection.cpp new file mode 100644 index 00000000000..d24e9059206 --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/ClusterConnection.cpp @@ -0,0 +1,158 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/ClusterConnection.h" + +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +ClusterConnectionNoTimeoutProxyOnly::ClusterConnectionNoTimeoutProxyOnly( + IClusterConnectionConfigurationProxyOnly& configuration) +: Base(configuration) +{} + +HRESULT +ClusterConnectionNoTimeoutProxyOnly::subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) +{ + return static_cast(Base::getConfiguration()) + .subscribe(proxy, serviceInstanceId); +} + +void ClusterConnectionNoTimeoutProxyOnly::unsubscribe(ProxyBase& proxy, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(proxy, serviceId); +} + +ClusterConnectionNoTimeoutSkeletonOnly::ClusterConnectionNoTimeoutSkeletonOnly( + IClusterConnectionConfigurationSkeletonOnly& configuration) +: Base(configuration) +{} + +HRESULT ClusterConnectionNoTimeoutSkeletonOnly::subscribe( + SkeletonBase& skeleton, uint16_t const serviceInstanceId) +{ + return static_cast(Base::getConfiguration()) + .subscribe(skeleton, serviceInstanceId); +} + +void ClusterConnectionNoTimeoutSkeletonOnly::unsubscribe( + SkeletonBase& skeleton, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(skeleton, serviceId); +} + +ClusterConnectionNoTimeoutBidirectional::ClusterConnectionNoTimeoutBidirectional( + IClusterConnectionConfigurationBidirectional& configuration) +: Base(configuration) +{} + +HRESULT ClusterConnectionNoTimeoutBidirectional::subscribe( + ProxyBase& proxy, uint16_t const serviceInstanceId) +{ + return static_cast(Base::getConfiguration()) + .subscribe(proxy, serviceInstanceId); +} + +HRESULT ClusterConnectionNoTimeoutBidirectional::subscribe( + SkeletonBase& skeleton, uint16_t const serviceInstanceId) +{ + return static_cast(Base::getConfiguration()) + .subscribe(skeleton, serviceInstanceId); +} + +void ClusterConnectionNoTimeoutBidirectional::unsubscribe( + ProxyBase& proxy, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(proxy, serviceId); +} + +void ClusterConnectionNoTimeoutBidirectional::unsubscribe( + SkeletonBase& skeleton, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(skeleton, serviceId); +} + +ClusterConnectionBidirectionalWithTimeout::ClusterConnectionBidirectionalWithTimeout( + IClusterConnectionConfigurationBidirectionalWithTimeout& configuration) +: Base(configuration) +{} + +HRESULT ClusterConnectionBidirectionalWithTimeout::subscribe( + ProxyBase& proxy, uint16_t const serviceInstanceId) +{ + return static_cast( + Base::getConfiguration()) + .subscribe(proxy, serviceInstanceId); +} + +void ClusterConnectionBidirectionalWithTimeout::unsubscribe( + ProxyBase& proxy, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(proxy, serviceId); +} + +HRESULT ClusterConnectionBidirectionalWithTimeout::subscribe( + SkeletonBase& skeleton, uint16_t const serviceInstanceId) +{ + return static_cast( + Base::getConfiguration()) + .subscribe(skeleton, serviceInstanceId); +} + +void ClusterConnectionBidirectionalWithTimeout::unsubscribe( + SkeletonBase& skeleton, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(skeleton, serviceId); +} + +ClusterConnectionProxyOnlyWithTimeout::ClusterConnectionProxyOnlyWithTimeout( + IClusterConnectionConfigurationProxyOnlyWithTimeout& configuration) +: Base(configuration) +{} + +HRESULT +ClusterConnectionProxyOnlyWithTimeout::subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) +{ + return static_cast( + Base::getConfiguration()) + .subscribe(proxy, serviceInstanceId); +} + +void ClusterConnectionProxyOnlyWithTimeout::unsubscribe(ProxyBase& proxy, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(proxy, serviceId); +} + +ClusterConnectionSkeletonOnlyWithTimeout::ClusterConnectionSkeletonOnlyWithTimeout( + IClusterConnectionConfigurationSkeletonOnlyWithTimeout& configuration) +: Base(configuration) +{} + +HRESULT ClusterConnectionSkeletonOnlyWithTimeout::subscribe( + SkeletonBase& skeleton, uint16_t const serviceInstanceId) +{ + return static_cast( + Base::getConfiguration()) + .subscribe(skeleton, serviceInstanceId); +} + +void ClusterConnectionSkeletonOnlyWithTimeout::unsubscribe( + SkeletonBase& skeleton, uint16_t const serviceId) +{ + static_cast(Base::getConfiguration()) + .unsubscribe(skeleton, serviceId); +} + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/ClusterConnectionBase.cpp b/libs/bsw/middleware/src/middleware/core/ClusterConnectionBase.cpp new file mode 100644 index 00000000000..3b567d11fae --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/ClusterConnectionBase.cpp @@ -0,0 +1,125 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/ClusterConnectionBase.h" + +#include +#include + +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/ITimeout.h" +#include "middleware/core/LoggerApi.h" +#include "middleware/core/Message.h" +// #include "middleware/core/MessageAllocator.h" +#include "middleware/core/types.h" +#include "middleware/logger/Logger.h" + +namespace middleware +{ +namespace core +{ + +ClusterConnectionBase::ClusterConnectionBase(IClusterConnectionConfigurationBase& configuration) +: fConfiguration(configuration) +{} + +void ClusterConnectionBase::processMessage(Message const& msg) const +{ + static_cast(dispatchMessage(msg)); + // MessageAllocator::deallocate(msg); +} + +void ClusterConnectionBase::respondWithError(ErrorState const error, Message const& msg) const +{ + Message::Header const& header = msg.getHeader(); + if (header.requestId + != INVALID_REQUEST_ID) // not a fire/forget method so send error response back + { + Message const errorResponse = Message::createErrorResponse( + header.serviceId, + header.memberId, + header.requestId, + header.serviceInstanceId, + msg.getHeader().tgtClusterId, + msg.getHeader().srcClusterId, + msg.getHeader().addressId, + error); + static_cast(sendMessage(errorResponse)); + } +} + +HRESULT ClusterConnectionBase::sendMessage(Message const& msg) const +{ + auto res = HRESULT::QueueFull; + if ((msg.getHeader().srcClusterId == msg.getHeader().tgtClusterId)) + { + res = dispatchMessage(msg); + // MessageAllocator::deallocate(msg); + } + else + { + if (fConfiguration.write(msg)) + { + res = HRESULT::Ok; + } + else + { + logger::logMessageSendingFailure( + logger::LogLevel::Error, logger::Error::SendMessage, res, msg); + } + } + + return res; +} + +HRESULT ClusterConnectionBase::dispatchMessage(Message const& msg) const +{ + auto const res = fConfiguration.dispatchMessage(msg); + if (HRESULT::Ok != res) + { + if (HRESULT::ServiceBusy == res) + { + // ServiceBusy can only occur when dispatching a get request a message to the Skeleton + // side + respondWithError(ErrorState::ServiceBusy, msg); + } + else if (HRESULT::ServiceNotFound == res) + { + // ServiceNotFound can only occur when dispatching a message to the Skeleton side + respondWithError(ErrorState::ServiceNotFound, msg); + } + else + { + // other use cases are not relevant because they won't happen on the message dispatching + } + + logger::logMessageSendingFailure( + logger::LogLevel::Error, logger::Error::DispatchMessage, res, msg); + } + + return res; +} + +ClusterConnectionTimeoutBase::ClusterConnectionTimeoutBase(ITimeoutConfiguration& configuration) +: ClusterConnectionBase(configuration) +{} + +void ClusterConnectionTimeoutBase::registerTimeoutTransceiver(ITimeout& transceiver) +{ + static_cast((ClusterConnectionBase::getConfiguration())) + .registerTimeoutTransceiver(transceiver); +} + +void ClusterConnectionTimeoutBase::unregisterTimeoutTransceiver(ITimeout& transceiver) +{ + static_cast((ClusterConnectionBase::getConfiguration())) + .unregisterTimeoutTransceiver(transceiver); +} + +void ClusterConnectionTimeoutBase::updateTimeouts() +{ + static_cast((ClusterConnectionBase::getConfiguration())) + .updateTimeouts(); +} + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/DatabaseManipulator.cpp b/libs/bsw/middleware/src/middleware/core/DatabaseManipulator.cpp new file mode 100644 index 00000000000..a28f3b4ad1c --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/DatabaseManipulator.cpp @@ -0,0 +1,349 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/DatabaseManipulator.h" + +#include +#include + +#include +#include +#include + +#include "middleware/concurrency/LockStrategies.h" +#include "middleware/core/ITransceiver.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/TransceiverContainer.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +namespace meta +{ + +HRESULT +DbManipulator::subscribe( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + ProxyBase& proxy, + uint16_t const instanceId, + uint16_t const maxServiceId) +{ + auto res = HRESULT::ServiceNotFound; + if (proxy.getServiceId() > maxServiceId) + { + res = HRESULT::ServiceIdOutOfRange; + } + else + { + auto const serviceId = proxy.getServiceId(); + auto* containerIterator = getTransceiversByServiceId(start, end, serviceId); + if (containerIterator != end) + { + auto* iter = DbManipulator::findTransceiver(&proxy, *containerIterator->fContainer); + if (iter != containerIterator->fContainer->end()) + { + // order is important - vector must be reordered with new instance id! + containerIterator->fContainer->erase(iter); + // update instance id + proxy.setInstanceId(instanceId); + static_cast(containerIterator->fContainer->emplace_back(&proxy)); + etl::sort( + containerIterator->fContainer->begin(), + containerIterator->fContainer->end(), + TransceiverContainer::TransceiverComparator()); + res = HRESULT::Ok; + } + else + { + if (containerIterator->fContainer->full()) + { + res = HRESULT::TransceiverInitializationFailed; + } + else + { + auto const range = getTransceiversByServiceIdAndServiceInstanceId( + start, end, serviceId, instanceId); + bool addressNotFound = true; + while (addressNotFound) + { + auto const* const iter = etl::find_if( + range.first, + range.second, + [&containerIterator](ITransceiver const* const itrx) { + return (itrx->getAddressId() == containerIterator->fActualAddress); + }); + if (iter == range.second) + { + addressNotFound = false; + proxy.setAddressId(containerIterator->fActualAddress); + containerIterator->fActualAddress++; + } + else + { + ++containerIterator->fActualAddress; + } + } + proxy.setInstanceId(instanceId); + static_cast(containerIterator->fContainer->emplace_back(&proxy)); + etl::sort( + containerIterator->fContainer->begin(), + containerIterator->fContainer->end(), + TransceiverContainer::TransceiverComparator()); + res = HRESULT::Ok; + } + } + } + } + if (HRESULT::Ok != res) + { + proxy.setInstanceId(INVALID_INSTANCE_ID); + } + return res; +} + +void DbManipulator::unsubscribe( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + ITransceiver& transceiver, + uint16_t const serviceId) +{ + auto* const containerIterator = getTransceiversByServiceId(start, end, serviceId); + if (containerIterator != end) + { + auto const range = etl::equal_range( + containerIterator->fContainer->cbegin(), + containerIterator->fContainer->cend(), + &transceiver, + TransceiverContainer::TransceiverComparator()); + auto const* const iter = etl::find_if( + range.first, + range.second, + [&transceiver](ITransceiver const* const itrx) + { return (itrx->getAddressId() == transceiver.getAddressId()); }); + if (iter != containerIterator->fContainer->cend()) + { + static_cast(containerIterator->fContainer->erase(iter)); + transceiver.setAddressId(INVALID_ADDRESS_ID); + } + } +} + +HRESULT +DbManipulator::subscribe( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + SkeletonBase& skeleton, + uint16_t const instanceId, + uint16_t const maxServiceId) +{ + auto res = HRESULT::ServiceNotFound; + if (skeleton.getServiceId() > maxServiceId) + { + res = HRESULT::ServiceIdOutOfRange; + } + else + { + auto const serviceId = skeleton.getServiceId(); + auto* const containerIterator = getTransceiversByServiceId(start, end, serviceId); + if (containerIterator != end) + { + auto* iter = DbManipulator::findTransceiver(&skeleton, *containerIterator->fContainer); + if (iter != containerIterator->fContainer->end()) + { + // order is important - vector must be reordered with new instance id! + containerIterator->fContainer->erase(iter); + // update instance id + skeleton.setInstanceId(instanceId); + static_cast(containerIterator->fContainer->emplace_back(&skeleton)); + etl::sort( + containerIterator->fContainer->begin(), + containerIterator->fContainer->end(), + TransceiverContainer::TransceiverComparator()); + res = HRESULT::InstanceAlreadyRegistered; + } + else + { + // if another skeleton with this serviceInstandId is registered fail + if (isSkeletonWithServiceInstanceIdRegistered( + *containerIterator->fContainer, instanceId)) + { + res = HRESULT::SkeletonWithThisServiceIdAlreadyRegistered; + } + else + { + if (containerIterator->fContainer->full()) + { + res = HRESULT::TransceiverInitializationFailed; + } + else + { + skeleton.setInstanceId(instanceId); + static_cast(containerIterator->fContainer->emplace_back(&skeleton)); + etl::sort( + containerIterator->fContainer->begin(), + containerIterator->fContainer->end(), + TransceiverContainer::TransceiverComparator()); + res = HRESULT::Ok; + } + } + } + } + } + if ((HRESULT::Ok != res) && (HRESULT::InstanceAlreadyRegistered != res)) + { + skeleton.setInstanceId(INVALID_INSTANCE_ID); + } + return res; +} + +TransceiverContainer* DbManipulator::getTransceiversByServiceId( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + uint16_t const serviceId) +{ + // To avoid code duplication, call const version, then cast away constness + return const_cast( // NOLINT(cppcoreguidelines-pro-type-const-cast) + getTransceiversByServiceId( + static_cast(start), + static_cast(end), + serviceId)); +} + +TransceiverContainer const* DbManipulator::getTransceiversByServiceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId) +{ + auto const* const iter = etl::lower_bound( + start, + end, + TransceiverContainer{nullptr, serviceId, 0U}, + [](TransceiverContainer const& lhs, TransceiverContainer const& rhs) -> bool + { return lhs.fServiceid < rhs.fServiceid; }); + if ((iter != end) && (iter->fServiceid == serviceId)) + { + return iter; + } + + return end; +} + +etl::pair::const_iterator, etl::ivector::const_iterator> +DbManipulator::getTransceiversByServiceIdAndServiceInstanceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId, + uint16_t const instanceId) +{ + auto const* const transceiversById = getTransceiversByServiceId(start, end, serviceId); + if (transceiversById != end) + { + internal::DummyTransceiver const dummy(instanceId); + return etl::equal_range( + transceiversById->fContainer->cbegin(), + transceiversById->fContainer->cend(), + &dummy, + TransceiverContainer::TransceiverComparatorNoAddressId()); + } + + return etl::make_pair(start->fContainer->cbegin(), start->fContainer->cbegin()); +} + +ITransceiver* DbManipulator::getSkeletonByServiceIdAndServiceInstanceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId, + uint16_t const instanceId) +{ + auto const* const transceiversById = getTransceiversByServiceId(start, end, serviceId); + if (transceiversById != end) + { + internal::DummyTransceiver const dummy(instanceId); + auto const range = etl::equal_range( + transceiversById->fContainer->cbegin(), + transceiversById->fContainer->cend(), + &dummy, + TransceiverContainer::TransceiverComparator()); + // there can be only a single skeleton with the same instanceId + if (range.first != range.second) + { + return (*range.first); + } + } + return nullptr; +} + +etl::ivector::iterator DbManipulator::findTransceiver( + ITransceiver* const& transceiver, etl::ivector& container) +{ + auto* const iter = etl::lower_bound( + container.begin(), + container.end(), + transceiver, + TransceiverContainer::TransceiverComparator()); + + if ((iter != container.cend()) && (*iter)->getInstanceId() == transceiver->getInstanceId() + && (*iter)->getAddressId() == transceiver->getAddressId()) + { + return iter; + } + + return container.end(); +} + +bool DbManipulator::isSkeletonWithServiceInstanceIdRegistered( + etl::ivector const& container, uint16_t const instanceId) +{ + internal::DummyTransceiver const dummy(instanceId); + auto const range = etl::equal_range( + container.cbegin(), + container.cend(), + &dummy, + TransceiverContainer::TransceiverComparator()); + return (range.first != range.second); +} + +ITransceiver* DbManipulator::getTransceiver( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId, + uint16_t const instanceId, + uint16_t const addressId) +{ + auto const* const containerIterator = getTransceiversByServiceId(start, end, serviceId); + if (containerIterator != end) + { + internal::DummyTransceiver const dummy(instanceId, addressId); + auto const* const iter = etl::lower_bound( + containerIterator->fContainer->cbegin(), + containerIterator->fContainer->cend(), + &dummy, + TransceiverContainer::TransceiverComparator()); + if ((iter != containerIterator->fContainer->cend()) + && (!TransceiverContainer::TransceiverComparator()(&dummy, *iter))) + { + return *iter; + } + } + return nullptr; +} + +size_t DbManipulator::registeredTransceiversCount( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t const serviceId) +{ + auto const* const containerIterator = getTransceiversByServiceId(start, end, serviceId); + if (containerIterator != end) + { + return containerIterator->fContainer->size(); + } + return 0U; +} + +} // namespace meta +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/IClusterConnectionConfigurationBase.cpp b/libs/bsw/middleware/src/middleware/core/IClusterConnectionConfigurationBase.cpp new file mode 100644 index 00000000000..17ddff3ef3b --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/IClusterConnectionConfigurationBase.cpp @@ -0,0 +1,155 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/IClusterConnectionConfigurationBase.h" + +#include +#include +#include + +#include "middleware/concurrency/LockStrategies.h" +#include "middleware/core/DatabaseManipulator.h" +#include "middleware/core/ITimeout.h" +#include "middleware/core/ITransceiver.h" +#include "middleware/core/Message.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +void ITimeoutConfiguration::registerTimeoutTransceiver( + ITimeout& transceiver, ::etl::ivector& timeoutTransceivers) +{ + auto const* const iter + = etl::find(timeoutTransceivers.cbegin(), timeoutTransceivers.cend(), &transceiver); + if (iter == timeoutTransceivers.cend()) + { + if (timeoutTransceivers.full()) + { + // TODO: is it worth it to log this info? + // assert(); + } + else + { + timeoutTransceivers.push_back(&transceiver); + } + } +} + +void ITimeoutConfiguration::unregisterTimeoutTransceiver( + ITimeout& transceiver, ::etl::ivector& timeoutTransceivers) +{ + using ETL_OR_STD::swap; + + auto* const iter + = etl::find(timeoutTransceivers.begin(), timeoutTransceivers.end(), &transceiver); + if (iter != timeoutTransceivers.cend()) + { + swap(*iter, timeoutTransceivers.back()); + timeoutTransceivers.pop_back(); + } + else + { + // TODO: is it worth it to log this info? + // assert(); + } +} + +void ITimeoutConfiguration::updateTimeouts(::etl::ivector const& timeoutTransceivers) +{ + for (auto* const transceiver : timeoutTransceivers) + { + transceiver->updateTimeouts(); + } +} + +HRESULT +IClusterConnectionConfigurationBase::dispatchMessageToProxy( + meta::TransceiverContainer const* const proxiesStart, + meta::TransceiverContainer const* const proxiesEnd, + Message const& msg) +{ + HRESULT result = HRESULT::Ok; + + if (msg.isEvent()) + { + auto const range = meta::DbManipulator::getTransceiversByServiceIdAndServiceInstanceId( + proxiesStart, proxiesEnd, msg.getHeader().serviceId, msg.getHeader().serviceInstanceId); + for (auto const* it = range.first; it != range.second; it = etl::next(it)) + { + static_cast((*it)->onNewMessageReceived(msg)); + } + } + else if (msg.isResponse()) + { + Message::Header const& header = msg.getHeader(); + ITransceiver* const transceiver = meta::DbManipulator::getTransceiver( + proxiesStart, proxiesEnd, header.serviceId, header.serviceInstanceId, header.addressId); + if (transceiver != nullptr) + { + result = transceiver->onNewMessageReceived(msg); + } + } + else + { + result = HRESULT::RoutingError; + } + + return result; +} + +HRESULT +IClusterConnectionConfigurationBase::dispatchMessageToSkeleton( + meta::TransceiverContainer const* const skeletonsStart, + meta::TransceiverContainer const* const skeletonsEnd, + Message const& msg) +{ + HRESULT result = HRESULT::Ok; + + if (msg.isRequest() || msg.isFireAndForgetRequest()) + { + // message comes from proxy + // dispatch to specific transceiver, identified by serviceInstanceId + Message::Header const& header = msg.getHeader(); + auto* const skeleton = meta::DbManipulator::getSkeletonByServiceIdAndServiceInstanceId( + skeletonsStart, skeletonsEnd, header.serviceId, header.serviceInstanceId); + if (skeleton != nullptr) + { + result = skeleton->onNewMessageReceived(msg); + } + else + { + result = HRESULT::ServiceNotFound; + } + } + else + { + result = HRESULT::RoutingError; + } + + return result; +} + +HRESULT IClusterConnectionConfigurationBase::dispatchMessage( + meta::TransceiverContainer const* const proxiesStart, + meta::TransceiverContainer const* const proxiesEnd, + meta::TransceiverContainer const* const skeletonsStart, + meta::TransceiverContainer const* const skeletonsEnd, + Message const& msg) +{ + HRESULT result = HRESULT::Ok; + if (msg.isEvent() || msg.isResponse()) + { + result = dispatchMessageToProxy(proxiesStart, proxiesEnd, msg); + } + else + { + result = dispatchMessageToSkeleton(skeletonsStart, skeletonsEnd, msg); + } + + return result; +} + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/LoggerApi.cpp b/libs/bsw/middleware/src/middleware/core/LoggerApi.cpp new file mode 100644 index 00000000000..8ec0627090b --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/LoggerApi.cpp @@ -0,0 +1,188 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/LoggerApi.h" + +#include +#include + +#include +#include + +#include "middleware/core/Message.h" +#include "middleware/core/types.h" +#include "middleware/logger/Logger.h" + +namespace middleware +{ +namespace logger +{ +namespace +{ + +void serialize(etl::byte_stream_writer& writer, uint8_t const value) +{ + writer.write_unchecked(value); +} + +void serialize(etl::byte_stream_writer& writer, uint16_t const value) +{ + writer.write_unchecked(value); +} + +void serialize(etl::byte_stream_writer& writer, uint32_t const value) +{ + writer.write_unchecked(value); +} + +void serialize(etl::byte_stream_writer& writer, core::Message const& value) +{ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + static_assert( + count_bytes::VALUE == 10U, "Message log size in bytes exceeds the payload"); + + writer.write_unchecked(value.getHeader().srcClusterId); + writer.write_unchecked(value.getHeader().tgtClusterId); + writer.write_unchecked(value.getHeader().serviceId); + writer.write_unchecked(value.getHeader().serviceInstanceId); + writer.write_unchecked(value.getHeader().memberId); + writer.write_unchecked(value.getHeader().requestId); +} + +template +void serialize(etl::byte_stream_writer& writer, Value value, Values... values) +{ + serialize(writer, value); + serialize(writer, values...); +} + +template +void serialize(etl::byte_stream_writer& writer, Values... values) +{ + static_assert( + count_bytes::VALUE == MAX_SIZE, "Total size in bytes exceeds the payload"); + + serialize(writer, values...); +} +} // namespace + +void logAllocationFailure( + LogLevel const level, + Error const error, + core::HRESULT const res, + core::Message const& msg, + uint32_t const size) +{ + static char const* const kformat = "e:%d r:%d SC:%d TC:%d S:%d I:%d M:%d R:%d s:%d"; + + etl::array temp{}; + etl::byte_stream_writer writer{temp, etl::endian::native}; + serialize( + writer, + getMessageId(error), + static_cast(error), + static_cast(res), + msg, + size); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + middleware::logger::log( + level, + kformat, + error, + res, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId, + size); + middleware::logger::log_binary(level, temp); +} + +void logInitFailure( + LogLevel const level, + Error const error, + core::HRESULT const res, + uint16_t const serviceId, + uint16_t const serviceInstanceId, + uint8_t const sourceCluster) +{ + static char const* const kformat = "e:%d r:%d SC:%d S:%d I:%d"; + + etl::array + temp{}; // NOLINT(cppcoreguidelines-avoid-magic-numbers) + etl::byte_stream_writer writer{temp, etl::endian::native}; + serialize( + writer, + getMessageId(error), + static_cast(error), + static_cast(res), + static_cast(sourceCluster), + static_cast(serviceId), + static_cast(serviceInstanceId)); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + middleware::logger::log( + level, kformat, error, res, sourceCluster, serviceId, serviceInstanceId); + middleware::logger::log_binary(level, temp); +} + +void logMessageSendingFailure( + LogLevel const level, Error const error, core::HRESULT const res, core::Message const& msg) +{ + static char const* const kformat = "e:%d r:%d SC:%d TC:%d S:%d I:%d M:%d R:%d"; + + etl::array + temp{}; // NOLINT(cppcoreguidelines-avoid-magic-numbers) + etl::byte_stream_writer writer{temp, etl::endian::native}; + serialize( + writer, getMessageId(error), static_cast(error), static_cast(res), msg); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + middleware::logger::log( + level, + kformat, + error, + res, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId); + middleware::logger::log_binary(level, temp); +} + +void logCrossThreadViolation( + LogLevel const level, + Error const error, + uint8_t const sourceCluster, + uint16_t const serviceId, + uint16_t const serviceInstanceId, + uint32_t const initId, + uint32_t const currentTaskId) +{ + static char const* const kformat = "e:%d SC:%d S:%d I:%d T0:%d T1:%d"; + + etl::array + temp{}; // NOLINT(cppcoreguidelines-avoid-magic-numbers) + etl::byte_stream_writer writer{temp, etl::endian::native}; + serialize( + writer, + getMessageId(error), + static_cast(error), + static_cast(sourceCluster), + static_cast(serviceId), + static_cast(serviceInstanceId), + static_cast(initId), + static_cast(currentTaskId)); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + middleware::logger::log( + level, kformat, error, sourceCluster, serviceId, serviceInstanceId, initId, currentTaskId); + middleware::logger::log_binary(level, temp); +} + +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/ProxyBase.cpp b/libs/bsw/middleware/src/middleware/core/ProxyBase.cpp new file mode 100644 index 00000000000..122ed49b70f --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/ProxyBase.cpp @@ -0,0 +1,162 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/ProxyBase.h" + +#include +#include + +#include +#include + +#include "middleware/concurrency/LockStrategies.h" +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/InstancesDatabase.h" +#include "middleware/core/LoggerApi.h" +#include "middleware/core/Message.h" +#include "middleware/core/types.h" +#include "middleware/logger/Logger.h" +#include "middleware/os/TaskIdProvider.h" + +namespace middleware +{ +namespace core +{ + +HRESULT +ProxyBase::sendMessage(Message& msg) const +{ + HRESULT res = HRESULT::NotRegistered; + if (fConnection != nullptr) + { + res = fConnection->sendMessage(msg); + } + + return res; +} + +uint8_t ProxyBase::getSourceClusterId() const +{ + auto clusterId = static_cast(INVALID_CLUSTER_ID); + if (fConnection != nullptr) + { + clusterId = fConnection->getSourceClusterId(); + } + return clusterId; +} + +HRESULT +ProxyBase::initFromInstancesDatabase( + uint16_t const instanceId, + uint8_t const sourceCluster, + etl::span const& dbRange) +{ + HRESULT ret = HRESULT::TransceiverInitializationFailed; + unsubscribe(getServiceId()); + auto const* const iter = etl::find_if( + dbRange.begin(), + dbRange.end(), + [instanceId](IInstanceDatabase const* const dataBase) -> bool + { + auto const instances = dataBase->getInstanceIdsRange(); + auto const* const instanceIdIt + = etl::lower_bound(instances.begin(), instances.end(), instanceId); + return ((instanceIdIt != instances.end()) && ((*instanceIdIt) == instanceId)); + }); + if (iter != dbRange.end()) + { + auto const proxyCc = (*iter)->getProxyConnectionsRange(); + auto const* const ccIt = etl::find_if( + proxyCc.begin(), + proxyCc.end(), + [sourceCluster](IClusterConnection const* const clusConn) + { + if (clusConn != nullptr) + { + return (clusConn->getSourceClusterId() == sourceCluster); + } + return false; + }); + if (ccIt != proxyCc.end()) + { + ret = (*ccIt)->subscribe(*this, instanceId); + if ((HRESULT::Ok == ret) || (HRESULT::InstanceAlreadyRegistered == ret)) + { + fConnection = (*ccIt); + } + } + } + // only print error when configuration allows for it + if ((HRESULT::Ok != ret) && (!dbRange.empty())) + { + logger::logInitFailure( + logger::LogLevel::Critical, + logger::Error::ProxyInitialization, + ret, + getServiceId(), + instanceId, + sourceCluster); + } + return ret; +} + +void ProxyBase::unsubscribe(uint16_t const serviceId) +{ + if (fConnection != nullptr) + { + fConnection->unsubscribe(*this, serviceId); + fConnection = nullptr; + } +} + +Message ProxyBase::generateMessageHeader(uint16_t const memberId, uint16_t const requestId) const +{ + if (INVALID_REQUEST_ID != requestId) + { + return Message::createRequest( + getServiceId(), + memberId, + requestId, + getInstanceId(), + fConnection->getSourceClusterId(), + fConnection->getTargetClusterId(), + getAddressId()); + } + return Message::createFireAndForgetRequest( + getServiceId(), + memberId, + getInstanceId(), + fConnection->getSourceClusterId(), + fConnection->getTargetClusterId()); +} + +uint8_t ProxyBase::getAddressId() const { return addressId_; } + +bool ProxyBase::isInitialized() const { return (fConnection != nullptr); } + +void ProxyBase::setAddressId(uint8_t const addressId) { addressId_ = addressId; } + +void ProxyBase::checkCrossThreadError(uint32_t const initId) const +{ + if (ProxyBase::isInitialized()) + { + auto const currentTaskId = ::middleware::os::getProcessId(); + if (initId != currentTaskId) + { + ::middleware::concurrency::suspendAllInterrupts(); + + logger::logCrossThreadViolation( + logger::LogLevel::Critical, + logger::Error::ProxyCrossThreadViolation, + getSourceClusterId(), + getServiceId(), + getInstanceId(), + initId, + currentTaskId); + + std::abort(); + } + } +} + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/src/middleware/core/SkeletonBase.cpp b/libs/bsw/middleware/src/middleware/core/SkeletonBase.cpp new file mode 100644 index 00000000000..fa6c41777e1 --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/SkeletonBase.cpp @@ -0,0 +1,192 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/SkeletonBase.h" + +#include +#include +#include + +#include +#include + +#include "middleware/concurrency/LockStrategies.h" +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/InstancesDatabase.h" +#include "middleware/core/LoggerApi.h" +#include "middleware/core/Message.h" +#include "middleware/core/types.h" +#include "middleware/logger/Logger.h" +#include "middleware/os/TaskIdProvider.h" + +namespace middleware +{ +namespace core +{ + +HRESULT +SkeletonBase::sendMessage(Message& msg) const +{ + HRESULT res = HRESULT::ClusterIdNotFoundOrTransceiverNotRegistered; + auto const* const sender = etl::find_if( + connections_.begin(), + connections_.end(), + [&msg](IClusterConnection const* const clusConn) + { + if (clusConn != nullptr) + { + return (clusConn->getTargetClusterId() == msg.getHeader().tgtClusterId); + } + return false; + }); + + if (sender != connections_.end()) + { + res = (*sender)->sendMessage(msg); + } + else + { + logger::logMessageSendingFailure( + logger::LogLevel::Error, logger::Error::SendMessage, res, msg); + } + + return res; +} + +uint8_t SkeletonBase::getSourceClusterId() const +{ + auto clusterId = static_cast(INVALID_CLUSTER_ID); + if (!connections_.empty()) + { + auto const* const iter = etl::find_if( + connections_.begin(), + connections_.end(), + [](IClusterConnection const* const clusConn) { return (clusConn != nullptr); }); + + if (iter != connections_.end()) + { + clusterId = (*iter)->getSourceClusterId(); + } + } + return clusterId; +} + +void SkeletonBase::unsubscribe(uint16_t const serviceId) +{ + if (nullptr != connections_.data()) + { + for (auto* const connection : connections_) + { + if (connection != nullptr) + { + connection->unsubscribe(*this, serviceId); + } + } + } + connections_ = etl::span(); +} + +etl::span const& SkeletonBase::getClusterConnections() const +{ + return connections_; +} + +bool SkeletonBase::isInitialized() const { return (!connections_.empty()); } + +HRESULT +SkeletonBase::initFromInstancesDatabase( + uint16_t const instanceId, etl::span const& dbRange) +{ + unsubscribe(getServiceId()); + auto const* const iter = etl::find_if( + dbRange.begin(), + dbRange.end(), + [instanceId](IInstanceDatabase const* const dataBase) -> bool + { + auto const instances = dataBase->getInstanceIdsRange(); + auto const* const instanceIdIt + = etl::lower_bound(instances.begin(), instances.end(), instanceId); + return ((instanceIdIt != instances.end()) && ((*instanceIdIt) == instanceId)); + }); + HRESULT ret = HRESULT::TransceiverInitializationFailed; + if (iter != dbRange.end()) + { + auto skeletonCc = (*iter)->getSkeletonConnectionsRange(); + if (skeletonCc.empty()) + { + instanceId_ = INVALID_INSTANCE_ID; + ret = HRESULT::NoClientsAvailable; + } + else + { + bool isRegistered = true; + for (auto* const clusConn : skeletonCc) + { + if (nullptr != clusConn) + { + ret = clusConn->subscribe(*this, instanceId); + if ((ret == HRESULT::Ok) || (ret == HRESULT::InstanceAlreadyRegistered)) + { + continue; + } + + isRegistered = false; + break; + } + } + if (isRegistered) + { + connections_ = skeletonCc; + } + else + { + unsubscribe(getServiceId()); + instanceId_ = INVALID_INSTANCE_ID; + ret = HRESULT::TransceiverInitializationFailed; + } + } + } + else + { + ret = HRESULT::InstanceNotFound; + } + + if (HRESULT::Ok != ret) + { + logger::logInitFailure( + logger::LogLevel::Critical, + logger::Error::SkeletonInitialization, + ret, + getServiceId(), + instanceId, + INVALID_CLUSTER_ID); + } + return ret; +} + +SkeletonBase::~SkeletonBase() = default; + +void SkeletonBase::checkCrossThreadError(uint32_t const initId) const +{ + if (SkeletonBase::isInitialized()) + { + auto const currentTaskId = ::middleware::os::getProcessId(); + if (initId != currentTaskId) + { + ::middleware::concurrency::suspendAllInterrupts(); + + logger::logCrossThreadViolation( + logger::LogLevel::Error, + logger::Error::SkeletonCrossThreadViolation, + getSourceClusterId(), + getServiceId(), + getInstanceId(), + initId, + currentTaskId); + + std::abort(); + } + } +} + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/test/CMakeLists.txt b/libs/bsw/middleware/test/CMakeLists.txt index 959b8bdd84c..80cd7dca8c1 100644 --- a/libs/bsw/middleware/test/CMakeLists.txt +++ b/libs/bsw/middleware/test/CMakeLists.txt @@ -1,6 +1,20 @@ -add_executable(middlewareTest src/core/middleware_message_unittest.cpp - src/queue/middleware_queue_unittest.cpp) +add_executable( + middlewareTest + src/concurrency/ConcurrencyDefinitions.cpp + src/core/middleware_cluster_configuration_unittest.cpp + src/core/middleware_connection_unittest.cpp + src/core/middleware_db_manipulator_unittest.cpp + src/core/middleware_logger_api.cpp + src/core/middleware_message_unittest.cpp + src/core/middleware_proxy_base_unittest.cpp + src/core/middleware_skeleton_base_unittest.cpp + src/logger/mock/LoggerMock.cpp + src/os/OsDefinitions.cpp + src/queue/middleware_queue_unittest.cpp + src/time/mock/SystemTimerProviderMock.cpp) -target_link_libraries(middlewareTest PRIVATE middlewareHeaders gmock gtest_main) +target_include_directories(middlewareTest PUBLIC src) + +target_link_libraries(middlewareTest PRIVATE middleware gmock gtest_main) gtest_discover_tests(middlewareTest PROPERTIES LABELS "middlewareTest") diff --git a/libs/bsw/middleware/test/src/concurrency/ConcurrencyDefinitions.cpp b/libs/bsw/middleware/test/src/concurrency/ConcurrencyDefinitions.cpp new file mode 100644 index 00000000000..0b3847d05db --- /dev/null +++ b/libs/bsw/middleware/test/src/concurrency/ConcurrencyDefinitions.cpp @@ -0,0 +1,19 @@ +#include "middleware/concurrency/LockStrategies.h" + +namespace middleware +{ +namespace concurrency +{ + +void suspendAllInterrupts() {} + +ScopedCoreLock::ScopedCoreLock() {} + +ScopedCoreLock::~ScopedCoreLock() {} + +ScopedECULock::ScopedECULock(uint8_t volatile*) {} + +ScopedECULock::~ScopedECULock() {} + +} // namespace concurrency +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/core/middleware_cluster_configuration_unittest.cpp b/libs/bsw/middleware/test/src/core/middleware_cluster_configuration_unittest.cpp new file mode 100644 index 00000000000..27ff168ad93 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_cluster_configuration_unittest.cpp @@ -0,0 +1,524 @@ +#include + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "middleware/core/ClusterConnection.h" +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/Message.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/TransceiverContainer.h" +#include "middleware/core/types.h" +#include "middleware_instances_database.h" +#include "proxy.h" +#include "skeleton.h" + +using testing::_; +using testing::Exactly; +using testing::NiceMock; + +namespace middleware +{ +namespace core +{ +namespace test +{ + +struct MiddelwareMessageComparator +{ + using MsgType = ::middleware::core::Message; + + bool checkMsgHeader(MsgType const& other) const + { + Message::Header const& msgHeader = _msg.value().getHeader(); + Message::Header const& otherHeader = other.getHeader(); + return msgHeader.srcClusterId == otherHeader.srcClusterId + && msgHeader.tgtClusterId == otherHeader.tgtClusterId + && msgHeader.serviceId == otherHeader.serviceId + && msgHeader.memberId == otherHeader.memberId + && msgHeader.serviceInstanceId == otherHeader.serviceInstanceId + && msgHeader.addressId == otherHeader.addressId + && msgHeader.requestId == otherHeader.requestId + && msgHeader.flags == otherHeader.flags && _msg.value().isError() == other.isError() + && _msg.value().isEvent() == other.isEvent() + && _msg.value().isRequest() == other.isRequest() + && _msg.value().isResponse() == other.isResponse() + && _msg.value().isFireAndForgetRequest() == other.isFireAndForgetRequest(); + } + + void setReturnCode(::middleware::core::HRESULT ret) { _ret = ret; } + + ::middleware::core::HRESULT msgReceived(MsgType const& msg) + { + _msg = msg; + return _ret; + } + +private: + etl::optional _msg; + ::middleware::core::HRESULT _ret{::middleware::core::HRESULT::Ok}; +}; + +struct ProxyMockStoredMessage +: public ProxyMock +, MiddelwareMessageComparator +{ + using ProxyMock::ProxyMock; + + ::middleware::core::HRESULT + onNewMessageReceived(::middleware::core::Message const& msg) override + { + return MiddelwareMessageComparator::msgReceived(msg); + } +}; + +struct SkeletonMockStoredMessage +: public SkeletonMock +, MiddelwareMessageComparator +{ + using SkeletonMock::SkeletonMock; + + ::middleware::core::HRESULT + onNewMessageReceived(::middleware::core::Message const& msg) override + { + return MiddelwareMessageComparator::msgReceived(msg); + } +}; + +struct TimeoutMock : middleware::core::ITimeout +{ + void updateTimeouts() { _triggered = true; } + + bool hasBeenTriggered() { return _triggered; } + +private: + bool _triggered{false}; +}; + +struct ClusterConfigurationNoTimeout : public IClusterConnectionConfigurationBase +{ + static uint16_t const serviceId{12}; + static uint16_t const instanceId{1}; + static uint16_t const addressId{1}; + static uint8_t const sourceClusterId{1}; + static uint8_t const targetClusterId{2}; + + ClusterConfigurationNoTimeout() + { + // setup proxies + (_proxyTransceivers[0]).fContainer->emplace_back(&_proxy); + + etl::sort( + _proxyTransceivers[0].fContainer->begin(), + _proxyTransceivers[0].fContainer->end(), + meta::TransceiverContainer::TransceiverComparator()); + + // setup skeletons + _skeletonTransceivers[0].fContainer->emplace_back(&_skeleton); + + etl::sort( + _skeletonTransceivers[0].fContainer->begin(), + _skeletonTransceivers[0].fContainer->end(), + meta::TransceiverContainer::TransceiverComparator()); + } + + uint8_t getSourceClusterId() const override { return sourceClusterId; } + + uint8_t getTargetClusterId() const override { return targetClusterId; } + + bool write(Message const&) const override { return true; } + + std::size_t registeredTransceiversCount(uint16_t const) const override { return 0; } + + HRESULT dispatchMessage(Message const& msg) const override + { + return IClusterConnectionConfigurationBase::dispatchMessage( + std::begin(_proxyTransceivers), + std::end(_proxyTransceivers), + std::begin(_skeletonTransceivers), + std::end(_skeletonTransceivers), + msg); + } + + // relaying to protected base class methods + HRESULT dispatchMessageToProxy(Message const& msg) + { + return IClusterConnectionConfigurationBase::dispatchMessageToProxy( + std::begin(_proxyTransceivers), std::end(_proxyTransceivers), msg); + } + + // relaying to protected base class methods + HRESULT dispatchMessageToSkeleton(Message const& msg) + { + return IClusterConnectionConfigurationBase::dispatchMessageToSkeleton( + std::begin(_skeletonTransceivers), std::end(_skeletonTransceivers), msg); + } + + ProxyMockStoredMessage& getProxy() { return _proxy; } + + SkeletonMockStoredMessage& getSkeleton() { return _skeleton; } + +private: + etl::vector<::middleware::core::ITransceiver*, 1U> _proxyTransceiversAlloc{}; + etl::ivector<::middleware::core::ITransceiver*>& _iProxyTransceivers{_proxyTransceiversAlloc}; + + etl::vector<::middleware::core::ITransceiver*, 1U> _skeletonTransceiversAlloc{}; + etl::ivector<::middleware::core::ITransceiver*>& _iSkeletonTransceivers{ + _skeletonTransceiversAlloc}; + + ::middleware::core::meta::TransceiverContainer _proxyTransceivers[1]{ + {&_iProxyTransceivers, ClusterConfigurationNoTimeout::serviceId, 0U}}; + ::middleware::core::meta::TransceiverContainer _skeletonTransceivers[1]{ + {&_iSkeletonTransceivers, ClusterConfigurationNoTimeout::serviceId, 0U}}; + +protected: + ProxyMockStoredMessage _proxy{ + ClusterConfigurationNoTimeout::serviceId, + ClusterConfigurationNoTimeout::instanceId, + ClusterConfigurationNoTimeout::addressId}; + SkeletonMockStoredMessage _skeleton{ + ClusterConfigurationNoTimeout::serviceId, ClusterConfigurationNoTimeout::instanceId}; +}; + +struct ClusterConfigurationTimeout : ITimeoutConfiguration +{ + static size_t const MAX_TIMEOUT_RECEIVERS = 2; + + // implementing ITimeoutConfiguration` + uint8_t getSourceClusterId() const override { return static_cast(1); } + + uint8_t getTargetClusterId() const override { return static_cast(2); } + + bool write(Message const&) const override { return true; } + + std::size_t registeredTransceiversCount(uint16_t const) const override { return 0; } + + HRESULT dispatchMessage(Message const&) const override + { + return ::middleware::core::HRESULT::Ok; + } + + void registerTimeoutTransceiver(ITimeout& transceiver) override + { + ITimeoutConfiguration::registerTimeoutTransceiver(transceiver, _timeoutTransceiver); + } + + void unregisterTimeoutTransceiver(ITimeout& transceiver) override + { + ITimeoutConfiguration::unregisterTimeoutTransceiver(transceiver, _timeoutTransceiver); + } + + void updateTimeouts() override { ITimeoutConfiguration::updateTimeouts(_timeoutTransceiver); } + + // helper test functions accessing the container + size_t numTransceivers() { return _timeoutTransceiver.size(); } + + bool containsTransceiver(ITimeout const& transceiver) + { + return _timeoutTransceiver.cend() + != etl::find(_timeoutTransceiver.cbegin(), _timeoutTransceiver.cend(), &transceiver); + } + +private: + etl::vector<::middleware::core::ITimeout*, MAX_TIMEOUT_RECEIVERS> _timeoutTransceiverAlloc{}; + etl::ivector<::middleware::core::ITimeout*>& _timeoutTransceiver{_timeoutTransceiverAlloc}; +}; + +class ConfigurationBaseTest : public ::testing::Test +{ +public: + void SetUp() override {} + + void TearDown() override {} + + static Message createRequestMessage(uint16_t const memberId, uint16_t const requestId) + { + Message msg = Message::createRequest( + ClusterConfigurationNoTimeout::serviceId, + memberId, + requestId, + ClusterConfigurationNoTimeout::instanceId, + ClusterConfigurationNoTimeout::sourceClusterId, + ClusterConfigurationNoTimeout::targetClusterId, + ClusterConfigurationNoTimeout::addressId); + return msg; + } + + static Message createInvalidRequestMessage(uint16_t const memberId, uint16_t const requestId) + { + Message msg = Message::createRequest( + ClusterConfigurationNoTimeout::serviceId + + 1 /* offset ensuring there is no hit in the DB*/, + memberId, + requestId, + ClusterConfigurationNoTimeout::instanceId, + ClusterConfigurationNoTimeout::sourceClusterId, + ClusterConfigurationNoTimeout::targetClusterId, + ClusterConfigurationNoTimeout::addressId + + 1 /* offset ensuring there is no hit in the DB*/); + return msg; + } + + static Message createResponseMessage(uint16_t const memberId, uint16_t const requestId) + { + Message msg = Message::createResponse( + ClusterConfigurationNoTimeout::serviceId, + memberId, + requestId, + ClusterConfigurationNoTimeout::instanceId, + ClusterConfigurationNoTimeout::sourceClusterId, + ClusterConfigurationNoTimeout::targetClusterId, + ClusterConfigurationNoTimeout::addressId); + return msg; + } + + static Message createInvalidResponseMessage(uint16_t const memberId, uint16_t const requestId) + { + Message msg = Message::createResponse( + ClusterConfigurationNoTimeout::serviceId + + 1 /* offset ensuring there is no hit in the DB*/, + memberId, + requestId, + ClusterConfigurationNoTimeout::instanceId, + ClusterConfigurationNoTimeout::sourceClusterId, + ClusterConfigurationNoTimeout::targetClusterId, + ClusterConfigurationNoTimeout::addressId + + 1 /* offset ensuring there is no hit in the DB*/); + return msg; + } + + static Message createEvent(uint16_t const memberId) + { + Message msg = Message::createEvent( + ClusterConfigurationNoTimeout::serviceId, + memberId, + ClusterConfigurationNoTimeout::instanceId, + ClusterConfigurationNoTimeout::sourceClusterId); + msg.setTargetClusterId(ClusterConfigurationNoTimeout::targetClusterId); + + return msg; + } + +protected: + ClusterConfigurationNoTimeout _clusterConf; + ClusterConfigurationTimeout _clusterTimeoutConf; +}; + +TEST_F(ConfigurationBaseTest, ProxyTargetRouted) +{ + Message prxyTrgtMsg = createResponseMessage(123, 321); + + EXPECT_EQ(::middleware::core::HRESULT::Ok, _clusterConf.dispatchMessage(prxyTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, ProxyTargetRoutedEvent) +{ + Message eventMsg = createEvent(123); + + EXPECT_TRUE(eventMsg.isEvent()); + EXPECT_EQ(::middleware::core::HRESULT::Ok, _clusterConf.dispatchMessage(eventMsg)); + EXPECT_TRUE(_clusterConf.getProxy().checkMsgHeader(eventMsg)); +} + +TEST_F(ConfigurationBaseTest, ProxyTargetRoutedFailed) +{ + Message prxyTrgtMsg = createInvalidResponseMessage(123, 321); + + // expectation: Fall through, returning HRESULT::Ok if adressing params can't be matched to a + // registered proxy + EXPECT_EQ(::middleware::core::HRESULT::Ok, _clusterConf.dispatchMessage(prxyTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, ProxySkeletonWrongEndpoint) +{ + Message prxyTrgtMsg = createInvalidRequestMessage(123, 321); + + EXPECT_EQ( + ::middleware::core::HRESULT::RoutingError, + _clusterConf.dispatchMessageToProxy(prxyTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, SkeletonTargetRouted) +{ + Message skltnTrgtMsg = createRequestMessage(123, 321); + + EXPECT_EQ(::middleware::core::HRESULT::Ok, _clusterConf.dispatchMessage(skltnTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, SkeletonTargetRoutedMemberNotFound) +{ + Message skltnTrgtMsg = createRequestMessage(123, 321); + + _clusterConf.getSkeleton().setReturnCode(::middleware::core::HRESULT::ServiceMemberIdNotFound); + EXPECT_EQ( + ::middleware::core::HRESULT::ServiceMemberIdNotFound, + _clusterConf.dispatchMessage(skltnTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, SkeletonTargetRoutedServiceBusy) +{ + Message skltnTrgtMsg = createRequestMessage(123, 321); + + _clusterConf.getSkeleton().setReturnCode(::middleware::core::HRESULT::ServiceBusy); + EXPECT_EQ(::middleware::core::HRESULT::ServiceBusy, _clusterConf.dispatchMessage(skltnTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, SkeletonTargetRoutedArbitraryReturn) +{ + Message skltnTrgtMsg = createRequestMessage(123, 321); + + // setting any return code not created by the framework but the receiver. Expecting pass + // through. + _clusterConf.getSkeleton().setReturnCode(::middleware::core::HRESULT::NotImplemented); + EXPECT_EQ( + ::middleware::core::HRESULT::NotImplemented, _clusterConf.dispatchMessage(skltnTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, SkeletonTargetRoutedFailed) +{ + Message skltnTrgtMsg = createInvalidRequestMessage(123, 321); + + EXPECT_EQ( + ::middleware::core::HRESULT::ServiceNotFound, _clusterConf.dispatchMessage(skltnTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, SkeletonProxyWrongEndpoint) +{ + Message skltnTrgtMsg = createResponseMessage(123, 321); + + EXPECT_EQ( + ::middleware::core::HRESULT::RoutingError, + _clusterConf.dispatchMessageToSkeleton(skltnTrgtMsg)); +} + +TEST_F(ConfigurationBaseTest, TimeoutTransceiverAddRemove) +{ + TimeoutMock rec1; + TimeoutMock rec2; + + EXPECT_EQ(0, _clusterTimeoutConf.numTransceivers()); + + // add first receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec1); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + + // add second receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec2); + EXPECT_EQ(2, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec2)); + + // delete second receiver + _clusterTimeoutConf.unregisterTimeoutTransceiver(rec2); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + + // delete first receiver + _clusterTimeoutConf.unregisterTimeoutTransceiver(rec1); + EXPECT_EQ(0, _clusterTimeoutConf.numTransceivers()); +} + +TEST_F(ConfigurationBaseTest, TimeoutTransceiverAddButFull) +{ + TimeoutMock rec1; + TimeoutMock rec2; + TimeoutMock rec3; + + EXPECT_EQ(0, _clusterTimeoutConf.numTransceivers()); + + // add first receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec1); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + + // add second receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec2); + EXPECT_EQ(2, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec2)); + + // (try) add third receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec3); + EXPECT_EQ(2, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec2)); + + EXPECT_FALSE(_clusterTimeoutConf.containsTransceiver(rec3)); +} + +TEST_F(ConfigurationBaseTest, TimeoutTransceiverDoubleDelete) +{ + TimeoutMock rec1; + TimeoutMock rec2; + + EXPECT_EQ(0, _clusterTimeoutConf.numTransceivers()); + + // add first receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec1); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + + // add second receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec2); + EXPECT_EQ(2, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec2)); + + // delete second receiver + _clusterTimeoutConf.unregisterTimeoutTransceiver(rec2); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_FALSE(_clusterTimeoutConf.containsTransceiver(rec2)); + + // delete second receiver again + _clusterTimeoutConf.unregisterTimeoutTransceiver(rec2); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_FALSE(_clusterTimeoutConf.containsTransceiver(rec2)); +} + +TEST_F(ConfigurationBaseTest, TimeoutTransceiverDoubleInsert) +{ + TimeoutMock rec1; + + EXPECT_EQ(0, _clusterTimeoutConf.numTransceivers()); + + // add first receiver + _clusterTimeoutConf.registerTimeoutTransceiver(rec1); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + + // add first receiver again + _clusterTimeoutConf.registerTimeoutTransceiver(rec1); + EXPECT_EQ(1, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); +} + +TEST_F(ConfigurationBaseTest, TimeoutTransceiverTriggerTest) +{ + TimeoutMock rec1; + TimeoutMock rec2; + + // add receivers + _clusterTimeoutConf.registerTimeoutTransceiver(rec1); + _clusterTimeoutConf.registerTimeoutTransceiver(rec2); + EXPECT_EQ(2, _clusterTimeoutConf.numTransceivers()); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec1)); + EXPECT_TRUE(_clusterTimeoutConf.containsTransceiver(rec2)); + + // trigger all receivers + _clusterTimeoutConf.updateTimeouts(); + + EXPECT_TRUE(rec1.hasBeenTriggered()); + EXPECT_TRUE(rec2.hasBeenTriggered()); +} + +} // namespace test +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/core/middleware_connection_unittest.cpp b/libs/bsw/middleware/test/src/core/middleware_connection_unittest.cpp new file mode 100644 index 00000000000..3144e6012b9 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_connection_unittest.cpp @@ -0,0 +1,720 @@ +#include + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "logger/DslLogger.h" +#include "middleware/core/ClusterConnection.h" +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/Message.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/TransceiverContainer.h" +#include "middleware/core/types.h" +#include "middleware_instances_database.h" +#include "proxy.h" +#include "skeleton.h" + +using testing::_; +using testing::Exactly; +using testing::NiceMock; + +namespace middleware +{ +namespace core +{ +namespace test +{ + +struct ClusterConfigurationMockBase +{ + static uint8_t const sourceClusterId{1}; + static uint8_t const targetClusterId{2}; + + void setNextHRESULT(::middleware::core::HRESULT const& ret, std::uint8_t times = 1) + { + _mockReturnCounter = times; + returnHRESULT = ret; + } + + void setNextWriteResult(bool ret) { returnWrite = ret; } + + etl::optional<::middleware::core::Message> getLastReceivedMessage() { return messageReceived; } + + HRESULT dispatchMessage(::middleware::core::Message const& msg) const + { + messageReceived = msg; + return getResultInternal(); + } + + HRESULT subscribe(ProxyBase&, uint16_t const) { return getResultInternal(); } + + HRESULT subscribe(SkeletonBase&, uint16_t const) { return getResultInternal(); } + + uint8_t getSourceClusterId() const { return sourceClusterId; } + + uint8_t getTargetClusterId() const { return targetClusterId; } + + bool write(Message const&) const { return returnWrite; } + +private: + inline ::middleware::core::HRESULT getResultInternal() const + { + return (_mockReturnCounter-- >= 1) ? returnHRESULT : ::middleware::core::HRESULT::Ok; + } + + ::middleware::core::HRESULT returnHRESULT{::middleware::core::HRESULT::Ok}; + mutable std::uint8_t _mockReturnCounter{1}; + bool returnWrite{true}; + mutable etl::optional<::middleware::core::Message> messageReceived; +}; + +struct ProxyMockWithTimeout +: public ProxyMock +, public ::middleware::core::ITimeout +{ + using ProxyMock::ProxyMock; + + void updateTimeouts() override {} +}; + +struct TimeoutTransceiverCounter +{ + void up() { _cnt++; } + + void down() { (_cnt > 0) ? _cnt-- : _cnt = 0; } + + std::size_t getValue() const { return _cnt; } + + bool hasBeenTriggered() { return _triggered; } + + void updateTimeouts() { _triggered = true; } + +private: + std::size_t _cnt{0}; + bool _triggered{false}; +}; + +struct ClusterConnectionConfigurationProxyOnlyMock +: public IClusterConnectionConfigurationProxyOnly +, ClusterConfigurationMockBase +{ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(proxy, serviceInstanceId); + } + + void unsubscribe(ProxyBase&, uint16_t const) override {} + + HRESULT dispatchMessage(Message const& msg) const override + { + return ClusterConfigurationMockBase::dispatchMessage(msg); + } + + uint8_t getSourceClusterId() const override + { + return ClusterConfigurationMockBase::getSourceClusterId(); + } + + uint8_t getTargetClusterId() const override + { + return ClusterConfigurationMockBase::getTargetClusterId(); + } + + bool write(Message const& msg) const override + { + return ClusterConfigurationMockBase::write(msg); + } + + std::size_t registeredTransceiversCount(uint16_t const) const override { return 0; } +}; + +struct ClusterConnectionConfigurationSkeletonOnlyMock +: public IClusterConnectionConfigurationSkeletonOnly +, ClusterConfigurationMockBase +{ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(skeleton, serviceInstanceId); + } + + void unsubscribe(SkeletonBase&, uint16_t const) override {} + + HRESULT dispatchMessage(Message const& msg) const override + { + return ClusterConfigurationMockBase::dispatchMessage(msg); + } + + uint8_t getSourceClusterId() const override + { + return ClusterConfigurationMockBase::getSourceClusterId(); + } + + uint8_t getTargetClusterId() const override + { + return ClusterConfigurationMockBase::getTargetClusterId(); + } + + bool write(Message const& msg) const override + { + return ClusterConfigurationMockBase::write(msg); + } + + std::size_t registeredTransceiversCount(uint16_t const) const override { return 0; } +}; + +struct ClusterConnectionConfigurationBidirectionalMock +: public IClusterConnectionConfigurationBidirectional +, ClusterConfigurationMockBase +{ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(proxy, serviceInstanceId); + } + + void unsubscribe(ProxyBase&, uint16_t const) override {} + + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(skeleton, serviceInstanceId); + } + + void unsubscribe(SkeletonBase&, uint16_t const) override {} + + HRESULT dispatchMessage(Message const& msg) const override + { + return ClusterConfigurationMockBase::dispatchMessage(msg); + } + + uint8_t getSourceClusterId() const override + { + return ClusterConfigurationMockBase::getSourceClusterId(); + } + + uint8_t getTargetClusterId() const override + { + return ClusterConfigurationMockBase::getTargetClusterId(); + } + + bool write(Message const& msg) const override + { + return ClusterConfigurationMockBase::write(msg); + } + + std::size_t registeredTransceiversCount(uint16_t const) const override { return 0; } +}; + +struct ClusterConnectionConfigurationProxyOnlyTimeoutMock +: public IClusterConnectionConfigurationProxyOnlyWithTimeout +, ClusterConfigurationMockBase +, TimeoutTransceiverCounter +{ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(proxy, serviceInstanceId); + } + + void unsubscribe(ProxyBase&, uint16_t const) override {} + + HRESULT dispatchMessage(Message const& msg) const override + { + return ClusterConfigurationMockBase::dispatchMessage(msg); + } + + uint8_t getSourceClusterId() const override + { + return ClusterConfigurationMockBase::getSourceClusterId(); + } + + uint8_t getTargetClusterId() const override + { + return ClusterConfigurationMockBase::getTargetClusterId(); + } + + bool write(Message const& msg) const override + { + return ClusterConfigurationMockBase::write(msg); + } + + std::size_t registeredTransceiversCount(uint16_t const) const override { return 0; } + + void registerTimeoutTransceiver(ITimeout&) override { TimeoutTransceiverCounter::up(); } + + void unregisterTimeoutTransceiver(ITimeout&) override { TimeoutTransceiverCounter::down(); } + + void updateTimeouts() override { TimeoutTransceiverCounter::updateTimeouts(); } +}; + +struct ClusterConnectionConfigurationSkeletonOnlyTimeoutMock +: public IClusterConnectionConfigurationSkeletonOnlyWithTimeout +, ClusterConfigurationMockBase +, TimeoutTransceiverCounter +{ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(skeleton, serviceInstanceId); + } + + void unsubscribe(SkeletonBase&, uint16_t const) override {} + + HRESULT dispatchMessage(Message const& msg) const override + { + return ClusterConfigurationMockBase::dispatchMessage(msg); + } + + uint8_t getSourceClusterId() const override + { + return ClusterConfigurationMockBase::getSourceClusterId(); + } + + uint8_t getTargetClusterId() const override + { + return ClusterConfigurationMockBase::getTargetClusterId(); + } + + bool write(Message const& msg) const override + { + return ClusterConfigurationMockBase::write(msg); + } + + std::size_t registeredTransceiversCount(uint16_t const) const override + { + return TimeoutTransceiverCounter::getValue(); + } + + void registerTimeoutTransceiver(ITimeout&) override { TimeoutTransceiverCounter::up(); } + + void unregisterTimeoutTransceiver(ITimeout&) override { TimeoutTransceiverCounter::down(); } + + void updateTimeouts() override { TimeoutTransceiverCounter::updateTimeouts(); } +}; + +struct ClusterConnectionConfigurationBidirectionalTimeoutMock +: public IClusterConnectionConfigurationBidirectionalWithTimeout +, ClusterConfigurationMockBase +, TimeoutTransceiverCounter +{ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(proxy, serviceInstanceId); + } + + void unsubscribe(ProxyBase&, uint16_t const) override {} + + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) override + { + return ClusterConfigurationMockBase::subscribe(skeleton, serviceInstanceId); + } + + void unsubscribe(SkeletonBase&, uint16_t const) override {} + + HRESULT dispatchMessage(Message const& msg) const override + { + return ClusterConfigurationMockBase::dispatchMessage(msg); + } + + uint8_t getSourceClusterId() const override + { + return ClusterConfigurationMockBase::getSourceClusterId(); + } + + uint8_t getTargetClusterId() const override + { + return ClusterConfigurationMockBase::getTargetClusterId(); + } + + bool write(Message const& msg) const override + { + return ClusterConfigurationMockBase::write(msg); + } + + std::size_t registeredTransceiversCount(uint16_t const) const override + { + return TimeoutTransceiverCounter::getValue(); + } + + void registerTimeoutTransceiver(ITimeout&) override { TimeoutTransceiverCounter::up(); } + + void unregisterTimeoutTransceiver(ITimeout&) override { TimeoutTransceiverCounter::down(); } + + void updateTimeouts() override { TimeoutTransceiverCounter::updateTimeouts(); } +}; + +class ConnectionBaseTest : public ::testing::Test +{ +public: + void SetUp() override { logger_mock_.setup(); } + + void TearDown() override { logger_mock_.teardown(); } + + middleware::logger::test::DslLogger logger_mock_{}; +}; + +/* Testing final method implementations, implemented in the base classes, + * not meant to be supported on the public interfaces. + * + * For instance: subscribing with a skeleton on a Proxy-Only class + */ +TEST_F(ConnectionBaseTest, ConnectionsNotImplemented) +{ + ProxyMock proxyInstance(1, 2, 4); + SkeletonMock skeletonInstance(1, 2); + + ClusterConnectionConfigurationProxyOnlyMock confProxyOnly; + ClusterConnectionNoTimeoutProxyOnly connectionProxyOnly(confProxyOnly); + EXPECT_EQ( + ::middleware::core::HRESULT::NotImplemented, + connectionProxyOnly.subscribe(skeletonInstance, 123)); + EXPECT_NO_THROW(connectionProxyOnly.unsubscribe(skeletonInstance, 123)); + + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ClusterConnectionNoTimeoutSkeletonOnly connectionSkeletonOnly(confSkeletonOnly); + EXPECT_EQ( + ::middleware::core::HRESULT::NotImplemented, + connectionSkeletonOnly.subscribe(proxyInstance, 123)); + EXPECT_NO_THROW(connectionSkeletonOnly.unsubscribe(proxyInstance, 123)); + + ClusterConnectionConfigurationProxyOnlyTimeoutMock confProxyOnlyTimeout; + ClusterConnectionProxyOnlyWithTimeout connectionProxyOnlyTimeout(confProxyOnlyTimeout); + EXPECT_EQ( + ::middleware::core::HRESULT::NotImplemented, + connectionProxyOnlyTimeout.subscribe(skeletonInstance, 123)); + EXPECT_NO_THROW(connectionProxyOnlyTimeout.unsubscribe(skeletonInstance, 123)); + + ClusterConnectionConfigurationSkeletonOnlyTimeoutMock confSkeletonOnlyTimeout; + ClusterConnectionSkeletonOnlyWithTimeout connectionSkeletonOnlyTimeout(confSkeletonOnlyTimeout); + EXPECT_EQ( + ::middleware::core::HRESULT::NotImplemented, + connectionSkeletonOnlyTimeout.subscribe(proxyInstance, 123)); + EXPECT_NO_THROW(connectionSkeletonOnlyTimeout.unsubscribe(proxyInstance, 123)); +} + +TEST_F(ConnectionBaseTest, SubscribeUnsubscribeProxyOnly) +{ + ProxyMock proxyInstance(1, 2, 4); + ClusterConnectionConfigurationProxyOnlyMock confProxyOnly; + + ClusterConnectionNoTimeoutProxyOnly connectionProxyOnly(confProxyOnly); + EXPECT_EQ(::middleware::core::HRESULT::Ok, connectionProxyOnly.subscribe(proxyInstance, 1)); + EXPECT_NO_THROW(connectionProxyOnly.unsubscribe(proxyInstance, 1)); +} + +TEST_F(ConnectionBaseTest, SubscribeUnsubscribeSkeletonOnly) +{ + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + + ClusterConnectionNoTimeoutSkeletonOnly connectionSkeletonOnly(confSkeletonOnly); + EXPECT_EQ( + ::middleware::core::HRESULT::Ok, connectionSkeletonOnly.subscribe(skeletonInstance, 1)); + EXPECT_NO_THROW(connectionSkeletonOnly.unsubscribe(skeletonInstance, 1)); +} + +TEST_F(ConnectionBaseTest, SubscribeUnsubscribeBidirectional) +{ + ProxyMock proxyInstance(1, 2, 4); + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationBidirectionalMock confBidirectional; + + ClusterConnectionNoTimeoutBidirectional connectionBidirectional(confBidirectional); + EXPECT_EQ( + ::middleware::core::HRESULT::Ok, connectionBidirectional.subscribe(skeletonInstance, 1)); + EXPECT_EQ(::middleware::core::HRESULT::Ok, connectionBidirectional.subscribe(proxyInstance, 1)); + EXPECT_NO_THROW(connectionBidirectional.unsubscribe(skeletonInstance, 1)); + EXPECT_NO_THROW(connectionBidirectional.unsubscribe(proxyInstance, 1)); +} + +TEST_F(ConnectionBaseTest, SubscribeUnsubscribeProxyOnlyWithTimeout) +{ + ProxyMock proxyInstance(1, 2, 4); + ClusterConnectionConfigurationProxyOnlyTimeoutMock confProxyOnlyTimeout; + + ClusterConnectionProxyOnlyWithTimeout connectionProxyOnly(confProxyOnlyTimeout); + EXPECT_EQ(::middleware::core::HRESULT::Ok, connectionProxyOnly.subscribe(proxyInstance, 1)); + EXPECT_NO_THROW(connectionProxyOnly.unsubscribe(proxyInstance, 1)); +} + +TEST_F(ConnectionBaseTest, SubscribeUnsubscribeSkeletonOnlyWithTimeout) +{ + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyTimeoutMock confSkeletonOnlyTimeout; + + ClusterConnectionSkeletonOnlyWithTimeout connectionSkeletonOnly(confSkeletonOnlyTimeout); + EXPECT_EQ( + ::middleware::core::HRESULT::Ok, connectionSkeletonOnly.subscribe(skeletonInstance, 1)); + EXPECT_NO_THROW(connectionSkeletonOnly.unsubscribe(skeletonInstance, 1)); +} + +TEST_F(ConnectionBaseTest, SubscribeUnsubscribeBidirectionalWithTimeout) +{ + ProxyMock proxyInstance(1, 2, 4); + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationBidirectionalTimeoutMock confBidirectionalTimeout; + + ClusterConnectionBidirectionalWithTimeout connectionBidirectional(confBidirectionalTimeout); + EXPECT_EQ( + ::middleware::core::HRESULT::Ok, connectionBidirectional.subscribe(skeletonInstance, 1)); + EXPECT_EQ(::middleware::core::HRESULT::Ok, connectionBidirectional.subscribe(proxyInstance, 1)); + EXPECT_NO_THROW(connectionBidirectional.unsubscribe(skeletonInstance, 1)); + EXPECT_NO_THROW(connectionBidirectional.unsubscribe(proxyInstance, 1)); +} + +TEST_F(ConnectionBaseTest, SendMessageSameClusterNoError) +{ + Message msg = Message::createRequest( + 1, + 123, + 321, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::sourceClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + EXPECT_EQ(::middleware::core::HRESULT::Ok, ptrToBase->sendMessage(msg)); +} + +TEST_F(ConnectionBaseTest, SendMessageClusterToClusterNoError) +{ + Message msg = Message::createRequest( + 1, + 123, + 321, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::targetClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + EXPECT_EQ(::middleware::core::HRESULT::Ok, ptrToBase->sendMessage(msg)); +} + +TEST_F(ConnectionBaseTest, SendMessageClusterToClusterFailed) +{ + Message msg = Message::createRequest( + 1, + 123, + 321, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::targetClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + + // next invocation of write() on the cluster connection config will return false + confSkeletonOnly.setNextWriteResult(false); + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + logger::Error::SendMessage, + HRESULT::QueueFull, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId); + + // expecting fall-through, returning the HRESULT from the initialization + EXPECT_EQ(::middleware::core::HRESULT::QueueFull, ptrToBase->sendMessage(msg)); +} + +TEST_F(ConnectionBaseTest, SendMessageSameClusterServiceNotFound) +{ + Message msg = Message::createRequest( + 1, + 123, + 321, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::sourceClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + + // sendMessage: Setting error code ServiceNotFound exactly one time for the receiving side, but + // fall back to OK when sending back the error + confSkeletonOnly.setNextHRESULT(::middleware::core::HRESULT::ServiceNotFound, 1); + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + logger::Error::DispatchMessage, + HRESULT::ServiceNotFound, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId); + + EXPECT_EQ(::middleware::core::HRESULT::ServiceNotFound, ptrToBase->sendMessage(msg)); + + auto lastReceivedMsg = confSkeletonOnly.getLastReceivedMessage(); + EXPECT_EQ(lastReceivedMsg.value().getErrorState(), ErrorState::ServiceNotFound); +} + +TEST_F(ConnectionBaseTest, SendMessageSameClusterServiceBusy) +{ + Message msg = Message::createRequest( + 1, + 123, + 321, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::sourceClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + + // sendMessage: Setting error code ServiceNotFound exactly one time for the receiving side, but + // fall back to OK when sending back the error + confSkeletonOnly.setNextHRESULT(::middleware::core::HRESULT::ServiceBusy, 1); + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + logger::Error::DispatchMessage, + HRESULT::ServiceBusy, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId); + + EXPECT_EQ(::middleware::core::HRESULT::ServiceBusy, ptrToBase->sendMessage(msg)); + + auto lastReceivedMsg = confSkeletonOnly.getLastReceivedMessage(); + EXPECT_EQ(lastReceivedMsg.value().getErrorState(), ErrorState::ServiceBusy); +} + +TEST_F(ConnectionBaseTest, SendMessageSameClusterServiceBusyFireAndForget) +{ + Message msg = Message::createRequest( + 1, + 123, + INVALID_REQUEST_ID, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::sourceClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + + // sendMessage: Setting error code ServiceNotFound exactly one time for the receiving side, but + // fall back to OK when sending back the error + confSkeletonOnly.setNextHRESULT(::middleware::core::HRESULT::ServiceBusy, 1); + EXPECT_EQ(::middleware::core::HRESULT::ServiceBusy, ptrToBase->sendMessage(msg)); +} + +TEST_F(ConnectionBaseTest, processMessageFromReceivingSide) +{ + // generated code pops a single message from the queue + Message msg = Message::createRequest( + 1, + 123, + 321, + 2, + ClusterConfigurationMockBase::sourceClusterId, + ClusterConfigurationMockBase::sourceClusterId, + 4); + + SkeletonMock skeletonInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + EXPECT_EQ(::middleware::core::HRESULT::Ok, actualConnection.subscribe(skeletonInstance, 1)); + EXPECT_NO_THROW(ptrToBase->processMessage(msg)); + + auto lastReceivedMsg = confSkeletonOnly.getLastReceivedMessage(); + EXPECT_EQ(lastReceivedMsg.value().getErrorState(), ErrorState::NoError); +} + +TEST_F(ConnectionBaseTest, ProxyRegistersAsTimeoutTransceiver) +{ + ProxyMockWithTimeout proxyInstance(1, 2); + ClusterConnectionConfigurationSkeletonOnlyTimeoutMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyTimeoutMock>::type + actualConnection(confSkeletonOnly); + + EXPECT_NO_THROW(actualConnection.registerTimeoutTransceiver(proxyInstance)); + EXPECT_EQ(1, actualConnection.registeredTransceiversCount(1)); + EXPECT_NO_THROW(actualConnection.updateTimeouts()); + + EXPECT_TRUE(confSkeletonOnly.hasBeenTriggered()); + + EXPECT_NO_THROW(actualConnection.unregisterTimeoutTransceiver(proxyInstance)); + EXPECT_EQ(0, actualConnection.registeredTransceiversCount(1)); +} + +TEST_F(ConnectionBaseTest, SyntheticClusterIdGetter) +{ + ClusterConnectionConfigurationSkeletonOnlyMock confSkeletonOnly; + ::middleware::core::ClusterConnectionTypeSelector< + ClusterConnectionConfigurationSkeletonOnlyMock>::type actualConnection(confSkeletonOnly); + + // ptr to base class trick as observed in {Proxy/Skeleton}Base + ::middleware::core::IClusterConnection* ptrToBase = &actualConnection; + + // expectation: Connection getters are just relaying to the configuration getters + EXPECT_EQ(ptrToBase->getSourceClusterId(), confSkeletonOnly.getSourceClusterId()); + EXPECT_EQ(ptrToBase->getTargetClusterId(), confSkeletonOnly.getTargetClusterId()); +} + +} // namespace test +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/core/middleware_db_manipulator_unittest.cpp b/libs/bsw/middleware/test/src/core/middleware_db_manipulator_unittest.cpp new file mode 100644 index 00000000000..9b1e4ac1994 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_db_manipulator_unittest.cpp @@ -0,0 +1,561 @@ +#include +#include + +#include "gtest/gtest.h" +#include "middleware/core/DatabaseManipulator.h" +#include "proxy.h" +#include "skeleton.h" + +using ::middleware::core::HRESULT; +using ::middleware::core::ITransceiver; +using ::middleware::core::meta::DbManipulator; +using ::middleware::core::meta::TransceiverContainer; + +class DbManipulatorTest : public ::testing::Test +{ +public: + void SetUp() override + { + // setup proxies + (_proxyTransceivers[0]).fContainer->emplace_back(&_proxy1); + (_proxyTransceivers[0]).fContainer->emplace_back(&_proxy2); + (_proxyTransceivers[1]).fContainer->emplace_back(&_proxy3); + + etl::sort( + _proxyTransceivers[0].fContainer->begin(), + _proxyTransceivers[0].fContainer->end(), + TransceiverContainer::TransceiverComparator()); + etl::sort( + _proxyTransceivers[1].fContainer->begin(), + _proxyTransceivers[1].fContainer->end(), + TransceiverContainer::TransceiverComparator()); + + // setup skeletons + _skeletonTransceivers[0].fContainer->emplace_back(&_skeleton1); + + etl::sort( + _skeletonTransceivers[1].fContainer->begin(), + _skeletonTransceivers[1].fContainer->end(), + TransceiverContainer::TransceiverComparator()); + } + + void TearDown() override + { + // cleanup proxies + _proxyTransceivers[0].fContainer->clear(); + _proxyTransceivers[1].fContainer->clear(); + // cleanup skeletons + _skeletonTransceivers[0].fContainer->clear(); + _skeletonTransceivers[1].fContainer->clear(); + } + +private: + etl::vector _proxyTransceivers41{}; + etl::vector _proxyTransceivers54{}; + etl::ivector& _iProxyTransceivers41{_proxyTransceivers41}; + etl::ivector& _iProxyTransceivers54{_proxyTransceivers54}; + + etl::vector _skeletonTransceivers41{}; + etl::vector _skeletonTransceivers54{}; + etl::ivector& _iSkeletonTransceivers41{_skeletonTransceivers41}; + etl::ivector& _iSkeletonTransceivers54{_skeletonTransceivers54}; + +protected: + ProxyMock _proxy1{0x41, 0x01, 0x01}; + ProxyMock _proxy2{0x41, 0x01, 0x02}; + ProxyMock _proxy3{0x54, 0x02, 0x03}; + SkeletonMock _skeleton1{0x41, 0x01}; + + etl::array _proxyTransceivers{ + {{&_iProxyTransceivers41, 0x41, 0U}, {&_iProxyTransceivers54, 0x54, 0U}}}; + + etl::array _skeletonTransceivers{ + {{&_iSkeletonTransceivers41, 0x41, 0U}, {&_iSkeletonTransceivers54, 0x54, 0U}}}; +}; + +TEST_F(DbManipulatorTest, TestSubscribeNewProxy) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + ProxyMock proxy(0x41, instanceId, 0x04); + + // ACT + const HRESULT res = DbManipulator::subscribe( + _proxyTransceivers.begin(), + _proxyTransceivers.end(), + proxy, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_EQ(proxy.getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestSubscribeNewSkeleton) +{ + // ARRANGE + uint16_t const instanceId = 0x02; + SkeletonMock skeleton(0x54, instanceId); + + // ACT + const HRESULT res = DbManipulator::subscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + skeleton, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_EQ(skeleton.getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestSubscribeProxyWithOutOfRangeServiceId) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + ProxyMock proxy(0x40, instanceId, 0x01); + + // ACT + const HRESULT res = DbManipulator::subscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), proxy, instanceId, 0); + + // ASSERT + EXPECT_EQ(res, HRESULT::ServiceIdOutOfRange); + EXPECT_EQ(proxy.getInstanceId(), ::middleware::core::INVALID_INSTANCE_ID); +} + +TEST_F(DbManipulatorTest, TestSubscribeProxyWithUnknownServiceId) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + ProxyMock proxy(0x40, instanceId, 0x01); + + // ACT + const HRESULT res = DbManipulator::subscribe( + _proxyTransceivers.begin(), + _proxyTransceivers.end(), + proxy, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(res, HRESULT::ServiceNotFound); + EXPECT_EQ(proxy.getInstanceId(), ::middleware::core::INVALID_INSTANCE_ID); +} + +TEST_F(DbManipulatorTest, TestSubscribeSkeletonWithOutOfRangeServiceId) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + SkeletonMock skeleton(0x40, instanceId); + + // ACT + const HRESULT res = DbManipulator::subscribe( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), skeleton, instanceId, 0); + + // ASSERT + EXPECT_EQ(res, HRESULT::ServiceIdOutOfRange); + EXPECT_EQ(skeleton.getInstanceId(), ::middleware::core::INVALID_INSTANCE_ID); +} + +TEST_F(DbManipulatorTest, TestSubscribeSkeletonWithUnknownServiceId) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + SkeletonMock skeleton(0x40, instanceId); + + // ACT + const HRESULT res = DbManipulator::subscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + skeleton, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(res, HRESULT::ServiceNotFound); + EXPECT_EQ(skeleton.getInstanceId(), ::middleware::core::INVALID_INSTANCE_ID); +} + +TEST_F(DbManipulatorTest, TestSubscribeProxyWithTransceiverAlreadyRegistered) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + ProxyMock proxy(0x41, instanceId, 0x01); + + // ACT + const HRESULT resProxy = DbManipulator::subscribe( + _proxyTransceivers.begin(), + _proxyTransceivers.end(), + proxy, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(resProxy, HRESULT::Ok); + EXPECT_EQ(proxy.getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestSubscribeSkeletonWithTransceiverAlreadyRegistered) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + SkeletonMock skeleton(0x41, instanceId); + + // ACT + const HRESULT resSkeleton = DbManipulator::subscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + skeleton, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(resSkeleton, HRESULT::InstanceAlreadyRegistered); + EXPECT_EQ(skeleton.getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestProxySubscribeWithFullContainer) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + ProxyMock proxy1(0x41, instanceId, 0x04); + ProxyMock proxy2(0x41, instanceId, 0x05); + + // ACT + const HRESULT resProxy1 = DbManipulator::subscribe( + _proxyTransceivers.begin(), + _proxyTransceivers.end(), + proxy1, + instanceId, + etl::numeric_limits::max()); + const HRESULT resProxy2 = DbManipulator::subscribe( + _proxyTransceivers.begin(), + _proxyTransceivers.end(), + proxy2, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(resProxy1, HRESULT::Ok); + EXPECT_EQ(resProxy2, HRESULT::TransceiverInitializationFailed); +} + +TEST_F(DbManipulatorTest, TestSkeletonSubscribeWithFullContainer) +{ + // ARRANGE + uint16_t const instanceId = 0x02; + SkeletonMock skeleton(0x41, instanceId); + + // ACT + const HRESULT resSkeleton = DbManipulator::subscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + skeleton, + instanceId, + etl::numeric_limits::max()); + + // ASSERT + EXPECT_EQ(resSkeleton, HRESULT::TransceiverInitializationFailed); +} + +TEST_F(DbManipulatorTest, TestProxyUnsubscribe) +{ + // ARRANGE + + // ACT && ASSERT + + // container 0 + EXPECT_EQ(_proxyTransceivers[0].fContainer->size(), 2U); + + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), _proxy1, _proxy1.getServiceId()); + EXPECT_EQ(_proxyTransceivers[0].fContainer->size(), 1U); + + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), _proxy2, _proxy2.getServiceId()); + EXPECT_EQ(_proxyTransceivers[0].fContainer->size(), 0U); + + // nothing happens, unsubscribe was already done + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), _proxy1, _proxy1.getServiceId()); + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), _proxy2, _proxy2.getServiceId()); + EXPECT_EQ(_proxyTransceivers[0].fContainer->size(), 0U); + + // container 1 + EXPECT_EQ(_proxyTransceivers[1].fContainer->size(), 1U); + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), _proxy3, _proxy3.getServiceId()); + EXPECT_EQ(_proxyTransceivers[1].fContainer->size(), 0U); + + // nothing happens, unsubscribe was already done + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), _proxyTransceivers.end(), _proxy3, _proxy3.getServiceId()); + EXPECT_EQ(_proxyTransceivers[1].fContainer->size(), 0U); +} + +TEST_F(DbManipulatorTest, TestUnknownProxyUnsubscribe) +{ + // ARRANGE + uint16_t const instanceId = 0x01; + uint16_t const addressId = etl::numeric_limits::max(); + ProxyMock proxy(0xFF, instanceId, addressId); + + // ACT && ASSERT + EXPECT_EQ(_proxyTransceivers[0].fContainer->size(), 2U); + EXPECT_EQ(_proxyTransceivers[1].fContainer->size(), 1U); + DbManipulator::unsubscribe( + _proxyTransceivers.begin(), + _proxyTransceivers.end(), + proxy, + proxy.getServiceId()); // nothing happens + EXPECT_EQ(_proxyTransceivers[0].fContainer->size(), 2U); + EXPECT_EQ(_proxyTransceivers[1].fContainer->size(), 1U); +} + +TEST_F(DbManipulatorTest, TestSkeletonUnsubscribe) +{ + // ARRANGE + + // ACT && ASSERT + + // container 0 + EXPECT_EQ(_skeletonTransceivers[0].fContainer->size(), 1U); + DbManipulator::unsubscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + _skeleton1, + _skeleton1.getServiceId()); + EXPECT_EQ(_skeletonTransceivers[0].fContainer->size(), 0U); + + // nothing happens, unsubscribe was already done + DbManipulator::unsubscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + _skeleton1, + _skeleton1.getServiceId()); +} + +TEST_F(DbManipulatorTest, TestUnknownSkeletonUnsubscribe) +{ + // ARRANGE + uint16_t const service = etl::numeric_limits::max(); + uint16_t const instanceId = 0x01; + SkeletonMock skeleton(service, instanceId); + + // ACT && ASSERT + EXPECT_EQ(_skeletonTransceivers[0].fContainer->size(), 1U); + EXPECT_EQ(_skeletonTransceivers[1].fContainer->size(), 0U); + DbManipulator::unsubscribe( + _skeletonTransceivers.begin(), + _skeletonTransceivers.end(), + skeleton, + skeleton.getServiceId()); // nothing happens + EXPECT_EQ(_skeletonTransceivers[0].fContainer->size(), 1U); + EXPECT_EQ(_skeletonTransceivers[1].fContainer->size(), 0U); +} + +TEST_F(DbManipulatorTest, TestSkeletonValidSearchByServiceIdAndInstanceId) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = 0x1U; + + // ACT && ASSERT + ITransceiver const* const transceiver + = DbManipulator::getSkeletonByServiceIdAndServiceInstanceId( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId); + + EXPECT_EQ(transceiver->getServiceId(), service); + EXPECT_EQ(transceiver->getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestSkeletonSearchWithUnkownServiceId) +{ + // ARRANGE + uint16_t const service + = etl::numeric_limits::max(); // unknown service id to the database + uint16_t const instanceId = 0x1U; + + // ACT && ASSERT + ITransceiver const* const transceiver + = DbManipulator::getSkeletonByServiceIdAndServiceInstanceId( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestSkeletonSearchWithUnkownInstanceId) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = etl::numeric_limits::max(); + + // ACT && ASSERT + ITransceiver const* const transceiver + = DbManipulator::getSkeletonByServiceIdAndServiceInstanceId( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestProxyTransceiverValidSearch) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = 0x1U; + uint16_t const addressId = 0x1U; + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver->getServiceId(), service); + EXPECT_EQ(transceiver->getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestProxyTranceiverSearchWithUnkownServiceId) +{ + // ARRANGE + uint16_t const service + = etl::numeric_limits::max(); // unknown service id to the database + uint16_t const instanceId = 0x1U; + uint16_t const addressId = 0x1U; + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestProxyTranceiverSearchWithUnkownInstanceId) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId + = etl::numeric_limits::max(); // unknown instance id to the database + uint16_t const addressId = 0x1U; + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestProxyTranceiverSearchWithUnkownAddressId) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = 0x1U; + uint16_t const addressId + = etl::numeric_limits::max(); // unknown address id to the database + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestSkeletonTransceiverValidSearch) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = 0x1U; + uint16_t const addressId = etl::numeric_limits::max(); + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver->getServiceId(), service); + EXPECT_EQ(transceiver->getInstanceId(), instanceId); +} + +TEST_F(DbManipulatorTest, TestSkeletonTranceiverSearchWithUnkownServiceId) +{ + // ARRANGE + uint16_t const service + = etl::numeric_limits::max(); // unknown service id to the database + uint16_t const instanceId = 0x1U; + uint16_t const addressId = etl::numeric_limits::max(); + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestSkeletonTranceiverSearchWithUnkownInstanceId) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = 0xFFFFU; // unknown instance id to the database + uint16_t const addressId = etl::numeric_limits::max(); + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestSkeletonTranceiverSearchWithUnkownAdressId) +{ + // ARRANGE + uint16_t const service = 0x41U; + uint16_t const instanceId = 0x1U; + uint16_t const addressId = 0x1U; // unknown address id to the database + + // ACT && ASSERT + ITransceiver const* const transceiver = DbManipulator::getTransceiver( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service, instanceId, addressId); + + EXPECT_EQ(transceiver, nullptr); +} + +TEST_F(DbManipulatorTest, TestRegisteredProxyTransceiverCount) +{ + // ARRANGE + uint16_t const service1 = 0x41U; + uint16_t const service2 = 0x54U; + uint16_t const service3 = 0x10U; // unknown service id to the database + + // ACT && ASSERT + std::size_t const proxyCount1 = DbManipulator::registeredTransceiversCount( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service1); + std::size_t const proxyCount2 = DbManipulator::registeredTransceiversCount( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service2); + std::size_t const proxyCount3 = DbManipulator::registeredTransceiversCount( + _proxyTransceivers.begin(), _proxyTransceivers.end(), service3); + + EXPECT_EQ(proxyCount1, 2U); + EXPECT_EQ(proxyCount2, 1U); + EXPECT_EQ(proxyCount3, 0U); +} + +TEST_F(DbManipulatorTest, TestRegisteredSkeletonTransceiverCount) +{ + // ARRANGE + uint16_t const service1 = 0x41U; + uint16_t const service2 = 0x54U; // know service id, but no instance registered on the database + uint16_t const service3 = 0x10U; // unknown service id to the database + + // ACT && ASSERT + std::size_t const skeletonCount1 = DbManipulator::registeredTransceiversCount( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service1); + std::size_t const skeletonCount2 = DbManipulator::registeredTransceiversCount( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service2); + std::size_t const skeletonCount3 = DbManipulator::registeredTransceiversCount( + _skeletonTransceivers.begin(), _skeletonTransceivers.end(), service3); + + EXPECT_EQ(skeletonCount1, 1U); + EXPECT_EQ(skeletonCount2, 0U); + EXPECT_EQ(skeletonCount3, 0U); +} diff --git a/libs/bsw/middleware/test/src/core/middleware_instances_database.h b/libs/bsw/middleware/test/src/core/middleware_instances_database.h new file mode 100644 index 00000000000..3d5e64d929f --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_instances_database.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include + +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/InstancesDatabase.h" +#include "middleware/core/types.h" + +using namespace middleware::core; + +class ClusterConnection : public IClusterConnection +{ +public: + uint8_t getSourceClusterId() const override { return static_cast(1U); } + + uint8_t getTargetClusterId() const override { return static_cast(2U); } + + HRESULT subscribe(ProxyBase&, uint16_t const) override { return HRESULT::Ok; } + + HRESULT subscribe(SkeletonBase&, uint16_t const) override { return HRESULT::Ok; } + + void unsubscribe(ProxyBase&, uint16_t const) override {} + + void unsubscribe(SkeletonBase&, uint16_t const) override {} + + HRESULT sendMessage(Message const&) const override { return HRESULT::Ok; } + + void processMessage(Message const&) const override {} + + size_t registeredTransceiversCount(uint16_t const) const override { return 1U; } + + HRESULT dispatchMessage(Message const&) const override { return HRESULT::Ok; } +}; + +class BadClusterConnection : public IClusterConnection +{ +public: + uint8_t getSourceClusterId() const override { return static_cast(1U); } + + uint8_t getTargetClusterId() const override { return static_cast(2U); } + + HRESULT subscribe(ProxyBase&, uint16_t const) override { return HRESULT::NotRegistered; } + + HRESULT subscribe(SkeletonBase&, uint16_t const) override { return HRESULT::NotRegistered; } + + void unsubscribe(ProxyBase&, uint16_t const) override {} + + void unsubscribe(SkeletonBase&, uint16_t const) override {} + + HRESULT sendMessage(Message const&) const override { return HRESULT::NotRegistered; } + + void processMessage(Message const&) const override {} + + size_t registeredTransceiversCount(uint16_t const) const override { return 0U; } + + HRESULT dispatchMessage(Message const&) const override + { + return HRESULT::CannotAllocatePayload; + } +}; + +class InstancesDatabase : public ::IInstanceDatabase +{ +public: + constexpr InstancesDatabase() = default; + + etl::span getSkeletonConnectionsRange() const override + { + return etl::span(fSkeletonConnections); + } + + etl::span getProxyConnectionsRange() const override + { + return etl::span(fProxyConnections); + } + + etl::span<::uint16_t const> getInstanceIdsRange() const override + { + return etl::span(instanceIds_); + } + +private: + etl::array const instanceIds_ = {{1}}; + ClusterConnection clustConn_; + etl::array const fProxyConnections = {{&clustConn_, nullptr}}; + etl::array const fSkeletonConnections = {{&clustConn_, nullptr}}; +}; + +class BadInstancesDatabase : public ::IInstanceDatabase +{ +public: + constexpr BadInstancesDatabase() = default; + + etl::span getSkeletonConnectionsRange() const override + { + return etl::span(fSkeletonConnections); + } + + etl::span getProxyConnectionsRange() const override + { + return etl::span(fProxyConnections); + } + + etl::span<::uint16_t const> getInstanceIdsRange() const override + { + return etl::span(instanceIds_); + } + +private: + etl::array const instanceIds_ = {{1}}; + BadClusterConnection clustConn_; + etl::array const fProxyConnections = {{&clustConn_, nullptr}}; + etl::array const fSkeletonConnections = {{&clustConn_, nullptr}}; +}; + +class EmptyInstancesDatabase : public ::IInstanceDatabase +{ +public: + constexpr EmptyInstancesDatabase() = default; + + etl::span getSkeletonConnectionsRange() const override + { + return etl::span(fSkeletonConnections); + } + + etl::span getProxyConnectionsRange() const override + { + return etl::span(fProxyConnections); + } + + etl::span<::uint16_t const> getInstanceIdsRange() const override + { + return etl::span(instanceIds_); + } + +private: + etl::array const instanceIds_ = {{1}}; + ClusterConnection clustConn_; + etl::array const fProxyConnections{}; + etl::array const fSkeletonConnections{}; +}; + +constexpr InstancesDatabase _InstancesDatabase; +constexpr EmptyInstancesDatabase _EmptyInstancesDatabase; +constexpr BadInstancesDatabase _BadInstancesDatabase; + +constexpr etl::array<::IInstanceDatabase const* const, 1U> INSTANCESDATABASE{&_InstancesDatabase}; +constexpr etl::array<::IInstanceDatabase const* const, 1U> EMPTYINSTANCESDATABASE{ + &_EmptyInstancesDatabase}; +constexpr etl::array<::IInstanceDatabase const* const, 1U> BADINSTANCESDATABASE{ + &_BadInstancesDatabase}; diff --git a/libs/bsw/middleware/test/src/core/middleware_logger_api.cpp b/libs/bsw/middleware/test/src/core/middleware_logger_api.cpp new file mode 100644 index 00000000000..cee28571883 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_logger_api.cpp @@ -0,0 +1,149 @@ +#include +#include +#include + +#include "logger/DslLogger.h" +#include "middleware/core/LoggerApi.h" + +namespace middleware +{ +namespace core +{ +namespace test +{ + +class LoggerApiTest : public ::testing::Test +{ +public: + void SetUp() override { logger_mock_.setup(); } + + void TearDown() override { logger_mock_.teardown(); } + +protected: + middleware::logger::test::DslLogger logger_mock_{}; +}; + +TEST_F(LoggerApiTest, TestLogAllocationFailure) +{ + // ARRANGE + logger::LogLevel const level = logger::LogLevel::Error; + logger::Error const error = logger::Error::Allocation; + HRESULT const res = HRESULT::CannotAllocatePayload; + core::Message const msg + = core::Message::createRequest(0x1000U, 0x2000U, 0x3000U, 0x4000U, 1U, 2U, 3U); + + // ACT && ASSERT + logger_mock_.EXPECT_LOG( + level, + "e:%d r:%d SC:%d TC:%d S:%d I:%d M:%d R:%d s:%d", + error, + res, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId, + static_cast(sizeof(msg))); + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + error, + res, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId, + static_cast(sizeof(msg))); + middleware::logger::logAllocationFailure(level, error, res, msg, sizeof(msg)); +} + +TEST_F(LoggerApiTest, TestLogInitFailure) +{ + // ARRANGE + logger::LogLevel const level = logger::LogLevel::Critical; + logger::Error const error = logger::Error::ProxyInitialization; + const HRESULT res = HRESULT::TransceiverInitializationFailed; + uint16_t const serviceId = etl::numeric_limits::max(); + uint16_t const serviceInstanceId = etl::numeric_limits::max(); + uint8_t const sourceCluster = etl::numeric_limits::max(); + + // ACT && ASSERT + logger_mock_.EXPECT_LOG( + level, + "e:%d r:%d SC:%d S:%d I:%d", + error, + res, + sourceCluster, + serviceId, + serviceInstanceId); + logger_mock_.EXPECT_EVENT_LOG(level, error, res, sourceCluster, serviceId, serviceInstanceId); + middleware::logger::logInitFailure( + level, error, res, serviceId, serviceInstanceId, sourceCluster); +} + +TEST_F(LoggerApiTest, TestLogMessageSendingFailure) +{ + // ARRANGE + logger::LogLevel const level = logger::LogLevel::Error; + logger::Error const error = logger::Error::DispatchMessage; + HRESULT const res = HRESULT::ServiceNotFound; + core::Message const msg + = core::Message::createRequest(0x1000U, 0x2000U, 0x3000U, 0x4000U, 1U, 2U, 3U); + + // ACT && ASSERT + logger_mock_.EXPECT_LOG( + level, + "e:%d r:%d SC:%d TC:%d S:%d I:%d M:%d R:%d", + error, + res, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId); + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + error, + res, + msg.getHeader().srcClusterId, + msg.getHeader().tgtClusterId, + msg.getHeader().serviceId, + msg.getHeader().serviceInstanceId, + msg.getHeader().memberId, + msg.getHeader().requestId); + middleware::logger::logMessageSendingFailure(level, error, res, msg); +} + +TEST_F(LoggerApiTest, TestLogCrossThreadViolation) +{ + // ARRANGE + logger::LogLevel const level = logger::LogLevel::Critical; + logger::Error const error = logger::Error::ProxyCrossThreadViolation; + uint16_t const serviceId = etl::numeric_limits::max(); + uint16_t const serviceInstanceId = etl::numeric_limits::max(); + uint8_t const sourceCluster = etl::numeric_limits::max(); + uint32_t const initId = etl::numeric_limits::max(); + uint32_t const currentTaskId = etl::numeric_limits::max(); + + // ACT && ASSERT + logger_mock_.EXPECT_LOG( + level, + "e:%d SC:%d S:%d I:%d T0:%d T1:%d", + error, + sourceCluster, + serviceId, + serviceInstanceId, + initId, + currentTaskId); + logger_mock_.EXPECT_EVENT_LOG( + level, error, sourceCluster, serviceId, serviceInstanceId, initId, currentTaskId); + middleware::logger::logCrossThreadViolation( + level, error, sourceCluster, serviceId, serviceInstanceId, initId, currentTaskId); +} + +} // namespace test +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/core/middleware_proxy_base_unittest.cpp b/libs/bsw/middleware/test/src/core/middleware_proxy_base_unittest.cpp new file mode 100644 index 00000000000..8827a1b34cc --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_proxy_base_unittest.cpp @@ -0,0 +1,253 @@ +#include +#include + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "logger/DslLogger.h" +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/LoggerApi.h" +#include "middleware/core/Message.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/types.h" +#include "middleware_instances_database.h" + +using testing::_; +using testing::Exactly; +using testing::NiceMock; + +namespace middleware +{ +namespace core +{ +namespace test +{ + +class Proxy : public ProxyBase +{ +public: + HRESULT init(uint16_t instanceId, uint8_t clusterId) + { + return ProxyBase::initFromInstancesDatabase( + instanceId, clusterId, etl::span(INSTANCESDATABASE)); + } + + uint8_t getProxySourceClusterId() { return ProxyBase::getSourceClusterId(); } + + void checkCrossThreadError(uint32_t const initId) + { + return ProxyBase::checkCrossThreadError(initId); + } + + uint16_t getServiceId() const override { return serviceId_; } + + HRESULT onNewMessageReceived(Message const&) override { return HRESULT::NotImplemented; } + +private: + uint16_t serviceId_{0x10U}; +}; + +class ProxyBaseTest : public ::testing::Test +{ +public: + void SetUp() override + { + logger_mock_.setup(); + const HRESULT res = proxy_.init(kValidinstanceid, kValidclustid); + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_TRUE(proxy_.isInitialized()); + } + + void TearDown() override { logger_mock_.teardown(); } + +protected: + uint16_t const kValidinstanceid{1U}; + uint8_t const kValidclustid{static_cast(1U)}; + uint16_t const kInvalidinstanceid{100U}; + uint8_t const kInvalidclustid{static_cast(100U)}; + + Proxy proxy_; + middleware::logger::test::DslLogger logger_mock_{}; +}; + +using ProxyBaseDeathTest = ProxyBaseTest; + +TEST_F(ProxyBaseTest, TestInitFromDatabase) +{ + // ARRANGE + Proxy proxy; + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + HRESULT res = proxy.init(kValidinstanceid, kValidclustid); + EXPECT_EQ(res, HRESULT::Ok); + + EXPECT_TRUE(proxy.isInitialized()); + + // re-init + res = proxy.init(kValidinstanceid, kValidclustid); + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_TRUE(proxy.isInitialized()); +} + +TEST_F(ProxyBaseTest, TestInitFromDatabaseWithInvalidInstanceId) +{ + // ARRANGE + Proxy proxy; + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::ProxyInitialization, + HRESULT::TransceiverInitializationFailed, + kValidclustid, + proxy.getServiceId(), + kInvalidinstanceid); + + // ACT & ASSERT + const HRESULT res = proxy.init(kInvalidinstanceid, kValidclustid); + + EXPECT_EQ(res, HRESULT::TransceiverInitializationFailed); + EXPECT_FALSE(proxy.isInitialized()); +} + +TEST_F(ProxyBaseTest, TestInitFromDatabaseWithInvalidClusterId) +{ + // ARRANGE + Proxy proxy; + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::ProxyInitialization, + HRESULT::TransceiverInitializationFailed, + kInvalidclustid, + proxy.getServiceId(), + kValidinstanceid); + + // ACT & ASSERT + const HRESULT res = proxy.init(kValidinstanceid, kInvalidclustid); + + EXPECT_EQ(res, HRESULT::TransceiverInitializationFailed); + EXPECT_FALSE(proxy.isInitialized()); +} + +TEST_F(ProxyBaseTest, TestGenerateMessageHeaderWithRequestId) +{ + // ARRANGE + uint16_t const memberId{0x15U}; + uint16_t const requestId{0x05U}; + + // ACT + Message const msg = proxy_.generateMessageHeader(memberId, requestId); + + // ASSERT + EXPECT_EQ(msg.getHeader().srcClusterId, kValidclustid); + EXPECT_EQ(msg.getHeader().tgtClusterId, static_cast(2U)); // Hardcoded for now + EXPECT_EQ(msg.getHeader().serviceId, proxy_.getServiceId()); + EXPECT_EQ(msg.getHeader().memberId, memberId); + EXPECT_EQ(msg.getHeader().serviceInstanceId, proxy_.getInstanceId()); + EXPECT_EQ(msg.getHeader().addressId, proxy_.getAddressId()); + EXPECT_EQ(msg.getHeader().requestId, requestId); + EXPECT_TRUE(msg.isRequest()); + EXPECT_FALSE(msg.isEvent()); +} + +TEST_F(ProxyBaseTest, TestGenerateMessageHeaderWithInvalidRequestId) +{ + // ARRANGE + uint16_t const memberId{0x15U}; + + // ACT + Message msg = proxy_.generateMessageHeader(memberId, INVALID_REQUEST_ID); + + // ASSERT + EXPECT_EQ(msg.getHeader().srcClusterId, kValidclustid); + EXPECT_EQ(msg.getHeader().tgtClusterId, static_cast(2U)); // Hardcoded for now + EXPECT_EQ(msg.getHeader().serviceId, proxy_.getServiceId()); + EXPECT_EQ(msg.getHeader().memberId, memberId); + EXPECT_EQ(msg.getHeader().serviceInstanceId, proxy_.getInstanceId()); + EXPECT_EQ(msg.getHeader().addressId, proxy_.getAddressId()); + EXPECT_EQ(msg.getHeader().requestId, INVALID_REQUEST_ID); + EXPECT_TRUE(msg.isFireAndForgetRequest()); + EXPECT_FALSE(msg.isEvent()); +} + +/** + * @brief Test generation and message sending + * Test cases: + * - Successful message sent + * - Not initialized + * - [MISSING] Too big of a payload + * - [MISSING] Queue Full + * + */ + +TEST_F(ProxyBaseTest, TestGenerateAndSendMessage) +{ + // ARRANGE + uint16_t const memberId{0x15U}; + uint16_t const requestId{0x05U}; + + // ACT + Message msg = proxy_.generateMessageHeader(memberId, requestId); + + // ASSERT + EXPECT_EQ(proxy_.sendMessage(msg), HRESULT::Ok); +} + +TEST_F(ProxyBaseTest, TestSendMessageWithNotInitProxy) +{ + // ARRANGE + Proxy proxy; + Message msg + = Message::createRequest(proxy.getServiceId(), 0x01U, 0x01U, 0x01U, 0x01U, 0x02U, 0x01U); + + // ASSERT + EXPECT_EQ(proxy.sendMessage(msg), HRESULT::NotRegistered); +} + +TEST_F(ProxyBaseDeathTest, TestCheckCrossThreadErrorWithSameTaskId) +{ + // ARRANGE + uint32_t const goodProcess = 0U; + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + proxy_.checkCrossThreadError(goodProcess); +} + +TEST_F(ProxyBaseDeathTest, TestCheckCrossThreadErrorWithNotInitProxy) +{ + // ARRANGE + Proxy proxy; + uint32_t const goodProcess = 0U; + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + proxy.checkCrossThreadError(goodProcess); +} + +TEST_F(ProxyBaseDeathTest, TestCheckCrossThreadErrorAbort) +{ + // ARRANGE + uint32_t const wrongProcess = 1234U; + + // ACT & ASSERT + EXPECT_DEATH( + { + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::ProxyCrossThreadViolation, + proxy_.getProxySourceClusterId(), + proxy_.getServiceId(), + proxy_.getInstanceId(), + wrongProcess, + 0U); + proxy_.checkCrossThreadError(wrongProcess); + }, + ""); +} + +} // namespace test +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/core/middleware_skeleton_base_unittest.cpp b/libs/bsw/middleware/test/src/core/middleware_skeleton_base_unittest.cpp new file mode 100644 index 00000000000..5f6fd94cc5c --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_skeleton_base_unittest.cpp @@ -0,0 +1,363 @@ +#include + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "logger/DslLogger.h" +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/Message.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/types.h" +#include "middleware_instances_database.h" + +using testing::_; +using testing::Exactly; +using testing::NiceMock; + +namespace middleware +{ +namespace core +{ +namespace test +{ + +class Skeleton : public SkeletonBase +{ +public: + HRESULT init(uint16_t instanceId) + { + return SkeletonBase::initFromInstancesDatabase( + instanceId, + etl::span(INSTANCESDATABASE)); + } + + HRESULT initEmptyDatabase(uint16_t instanceId) + { + return SkeletonBase::initFromInstancesDatabase( + instanceId, + etl::span(EMPTYINSTANCESDATABASE)); + } + + HRESULT initBadDatabase(uint16_t instanceId) + { + return SkeletonBase::initFromInstancesDatabase( + instanceId, + etl::span(BADINSTANCESDATABASE)); + } + + void checkCrossThreadError(uint32_t const initId) + { + return SkeletonBase::checkCrossThreadError(initId); + } + + uint16_t getServiceId() const override { return serviceId_; } + + HRESULT onNewMessageReceived(Message const&) override { return HRESULT::NotImplemented; } + +private: + uint16_t serviceId_{0x10U}; +}; + +class SkeletonBaseTest : public ::testing::Test +{ +public: + void SetUp() override + { + logger_mock_.setup(); + + const HRESULT res = skeleton_.init(kValidinstanceid); + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_TRUE(skeleton_.isInitialized()); + } + + void TearDown() override { logger_mock_.teardown(); } + +protected: + uint16_t const kValidinstanceid{1U}; + uint16_t const kInvalidinstanceid{100U}; + + Skeleton skeleton_; + middleware::logger::test::DslLogger logger_mock_{}; +}; + +using SkeletonBaseDeathTest = SkeletonBaseTest; + +/** + * @brief Test initialization from database + * Test cases: + * - A valid init + * - An init with an invalidInstanceId + * - [MISSING] An init with an already used instanceId + * - Init from an empty Instances Database + * - Init from a bad Instances Database + * - Reinit skeleton + */ +TEST_F(SkeletonBaseTest, TestInitFromDatabase) +{ + // ARRANGE + Skeleton skeleton; + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + HRESULT res = skeleton.init(kValidinstanceid); + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_TRUE(skeleton.isInitialized()); + + res = skeleton.init(kValidinstanceid); + EXPECT_EQ(res, HRESULT::Ok); + EXPECT_TRUE(skeleton.isInitialized()); +} + +TEST_F(SkeletonBaseTest, TestInitFromDatabaseWithInvalidInstanceId) +{ + // ARRANGE + Skeleton skeleton; + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::SkeletonInitialization, + HRESULT::InstanceNotFound, + core::INVALID_CLUSTER_ID, + skeleton.getServiceId(), + kInvalidinstanceid); + + // ACT & ASSERT + const HRESULT res = skeleton.init(kInvalidinstanceid); + + EXPECT_EQ(res, HRESULT::InstanceNotFound); + EXPECT_FALSE(skeleton.isInitialized()); +} + +TEST_F(SkeletonBaseTest, TestInitWithEmptyDatabase) +{ + // ARRANGE + Skeleton skeleton; + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::SkeletonInitialization, + HRESULT::NoClientsAvailable, + core::INVALID_CLUSTER_ID, + skeleton.getServiceId(), + kValidinstanceid); + + // ACT & ASSERT + const HRESULT res = skeleton.initEmptyDatabase(kValidinstanceid); + + EXPECT_EQ(res, HRESULT::NoClientsAvailable); + EXPECT_FALSE(skeleton.isInitialized()); +} + +TEST_F(SkeletonBaseTest, TestInitFromWrongDatabase) +{ + // ARRANGE + Skeleton skeleton; + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::SkeletonInitialization, + HRESULT::TransceiverInitializationFailed, + core::INVALID_CLUSTER_ID, + skeleton.getServiceId(), + kValidinstanceid); + + // ACT & ASSERT + const HRESULT res = skeleton.initBadDatabase(kValidinstanceid); + + EXPECT_EQ(res, HRESULT::TransceiverInitializationFailed); + EXPECT_FALSE(skeleton.isInitialized()); +} + +/** + * @brief Test sendMessage + * Test cases: + * - Valid Target Cluster + * - Invalid Target Cluster + * + */ +TEST_F(SkeletonBaseTest, TestSendMessage) +{ + // ARRANGE + auto const tgtClusterId = static_cast(2U); + Message validMsg = Message::createResponse( + skeleton_.getServiceId(), + 0x8001, + INVALID_REQUEST_ID, + skeleton_.getInstanceId(), + skeleton_.getSourceClusterId(), + tgtClusterId, + INVALID_ADDRESS_ID); + + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + const HRESULT res = skeleton_.sendMessage(validMsg); + EXPECT_EQ(res, HRESULT::Ok); +} + +TEST_F(SkeletonBaseTest, TestSendInvalidMessage) +{ + // ARRANGE + const HRESULT expectedResult = HRESULT::ClusterIdNotFoundOrTransceiverNotRegistered; + Message invalidMsg = Message::createResponse( + skeleton_.getServiceId(), + 0x8001, + INVALID_REQUEST_ID, + skeleton_.getInstanceId(), + skeleton_.getSourceClusterId(), + skeleton_.getSourceClusterId(), + INVALID_ADDRESS_ID); + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + logger::Error::SendMessage, + expectedResult, + invalidMsg.getHeader().srcClusterId, + invalidMsg.getHeader().tgtClusterId, + invalidMsg.getHeader().serviceId, + invalidMsg.getHeader().serviceInstanceId, + invalidMsg.getHeader().memberId, + invalidMsg.getHeader().requestId); + + // ACT & ASSERT + const HRESULT res = skeleton_.sendMessage(invalidMsg); + EXPECT_EQ(res, expectedResult); +} + +TEST_F(SkeletonBaseTest, TestSendMessageFromUnknownSkeleton) +{ + // ARRANGE + const HRESULT expectedResult = HRESULT::ClusterIdNotFoundOrTransceiverNotRegistered; + Skeleton skeleton; + uint8_t const tgtClusterId = static_cast(2U); + Message validMsg = Message::createResponse( + skeleton_.getServiceId(), + 0x8001, + INVALID_REQUEST_ID, + skeleton_.getInstanceId(), + skeleton_.getSourceClusterId(), + tgtClusterId, + INVALID_ADDRESS_ID); + + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Error, + logger::Error::SendMessage, + expectedResult, + validMsg.getHeader().srcClusterId, + validMsg.getHeader().tgtClusterId, + validMsg.getHeader().serviceId, + validMsg.getHeader().serviceInstanceId, + validMsg.getHeader().memberId, + validMsg.getHeader().requestId); + + // ACT & ASSERT + const HRESULT res = skeleton.sendMessage(validMsg); + EXPECT_EQ(res, HRESULT::ClusterIdNotFoundOrTransceiverNotRegistered); +} + +/** + * @brief Test getSourceClusterId + * Test cases: + * - Inited skeleton + * - Not inited skeleton + * + */ + +TEST_F(SkeletonBaseTest, TestGetSourceClusterId) +{ + // ARRANGE + + // ACT + uint8_t const clusterId = skeleton_.getSourceClusterId(); + + // ASSERT + EXPECT_EQ(clusterId, static_cast(1U)); +} + +TEST_F(SkeletonBaseTest, TestGetSourceClusterIdFromNotInitSkeleton) +{ + // ARRANGE + Skeleton skeleton; + + // ACT + uint8_t const clusterId = skeleton.getSourceClusterId(); + + // ASSERT + EXPECT_EQ(clusterId, static_cast(INVALID_CLUSTER_ID)); +} + +/** + * @brief Test CheckCrossThreadError + * Test cases: + * - Process is the same + * - Process is NOT the same + * + */ + +TEST_F(SkeletonBaseDeathTest, TestCheckCrossThreadError) +{ + // ARRANGE + uint32_t const goodProcess = 0U; + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + skeleton_.checkCrossThreadError(goodProcess); +} + +TEST_F(SkeletonBaseDeathTest, TestCheckCrossThreadErrorWithNotInitSkeleton) +{ + // ARRANGE + Skeleton skeleton; + uint32_t const goodProcess = 0U; + logger_mock_.EXPECT_NO_LOGGING(); + + // ACT & ASSERT + skeleton.checkCrossThreadError(goodProcess); +} + +TEST_F(SkeletonBaseDeathTest, TestCheckCrossThreadErrorAssert) +{ + // ARRANGE + uint32_t const wrongProcess = 1234U; + + // ACT & ASSERT + EXPECT_DEATH( + { + logger_mock_.EXPECT_EVENT_LOG( + logger::LogLevel::Critical, + logger::Error::SkeletonCrossThreadViolation, + skeleton_.getSourceClusterId(), + skeleton_.getServiceId(), + skeleton_.getInstanceId(), + wrongProcess, + 0U); + skeleton_.checkCrossThreadError(wrongProcess); + }, + ""); +} + +/** + * @brief Test getClusterConnections + * Test cases: + * - Inited skeleton + * - Not inited skeleton + */ +TEST_F(SkeletonBaseTest, TestGetClusterConnections) +{ + // ARRANGE + // ACT & ASSERT + EXPECT_FALSE(skeleton_.getClusterConnections().empty()); +} + +TEST_F(SkeletonBaseTest, TestGetClusterConnectionsFromNotInitSkeleton) +{ + // ARRANGE + Skeleton skeleton; + // ACT & ASSERT + EXPECT_TRUE(skeleton.getClusterConnections().empty()); +} + +} // namespace test +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/core/proxy.h b/libs/bsw/middleware/test/src/core/proxy.h new file mode 100644 index 00000000000..c60f083bca8 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/proxy.h @@ -0,0 +1,27 @@ +#include "gtest/gtest.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/types.h" + +class ProxyMock : public ::middleware::core::ProxyBase +{ +public: + ProxyMock( + uint16_t serviceId, + uint16_t instanceId, + uint16_t addressId = etl::numeric_limits::max()) + : ::middleware::core::ProxyBase(), serviceId_(serviceId) + { + this->setAddressId(addressId); + this->setInstanceId(instanceId); + } + + uint16_t getServiceId() const final { return serviceId_; } + + virtual ::middleware::core::HRESULT onNewMessageReceived(::middleware::core::Message const&) + { + return ::middleware::core::HRESULT::NotImplemented; + } + +private: + uint16_t serviceId_; +}; diff --git a/libs/bsw/middleware/test/src/core/skeleton.h b/libs/bsw/middleware/test/src/core/skeleton.h new file mode 100644 index 00000000000..aecc6e85e36 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/skeleton.h @@ -0,0 +1,23 @@ +#include "gtest/gtest.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/types.h" + +class SkeletonMock : public ::middleware::core::SkeletonBase +{ +public: + SkeletonMock(uint16_t serviceId, uint16_t instanceId) + : middleware::core::SkeletonBase(), serviceId_(serviceId) + { + this->setInstanceId(instanceId); + } + + uint16_t getServiceId() const final { return serviceId_; } + + virtual ::middleware::core::HRESULT onNewMessageReceived(::middleware::core::Message const&) + { + return ::middleware::core::HRESULT::NotImplemented; + } + +private: + uint16_t serviceId_; +}; diff --git a/libs/bsw/middleware/test/src/logger/DslLogger.h b/libs/bsw/middleware/test/src/logger/DslLogger.h new file mode 100644 index 00000000000..5356327a83e --- /dev/null +++ b/libs/bsw/middleware/test/src/logger/DslLogger.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include "middleware/core/LoggerApi.h" +#include "middleware/logger/Logger.h" +#include "mock/LoggerMock.h" + +namespace middleware +{ +namespace logger +{ +namespace test +{ + +using ::testing::_; +using ::testing::Cardinality; +using ::testing::ElementsAreArray; +using ::testing::Exactly; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::StrEq; + +class DslLogger +{ +public: + NiceMock _mock; + + void setup() {} + + void teardown() {} + + template + void EXPECT_LOG(LogLevel const level, std::string const& format, Args... args) + { + etl::vector vec; + push_all(vec, args...); + + EXPECT_CALL(_mock, log(level, StrEq(format.c_str()), ElementsAreArray(vec))) + .Times(Exactly(1U)) + .WillRepeatedly(Return()); + } + + template + void EXPECT_EVENT_LOG(LogLevel const level, Error const error, Args... args) + { + static etl::array::VALUE> + buffer{}; + + uint32_t const messageId = logger::getMessageId(error); + + uint32_t index = 0U; + copy_to_buffer(buffer.data(), index, messageId); + copy_to_buffer(buffer.data(), index, error); + copy_to_buffer(buffer.data(), index, args...); + + EXPECT_CALL(_mock, log_binary(level, ElementsAreArray(buffer))) + .Times(Exactly(1U)) + .WillRepeatedly(Return()); + } + + void EXPECT_NO_LOG() { EXPECT_CALL(_mock, log(_, _, _)).Times(0); } + + void EXPECT_NO_BINARY_LOG() { EXPECT_CALL(_mock, log_binary(_, _)).Times(0); } + + void EXPECT_NO_LOGGING() + { + EXPECT_NO_LOG(); + EXPECT_NO_BINARY_LOG(); + } + +private: + template + void push_all(etl::ivector& vec, T arg) + { + vec.push_back(static_cast(arg)); + } + + template + void push_all(etl::ivector& vec, T arg, Args... args) + { + vec.push_back(static_cast(arg)); + push_all(vec, args...); + } + + template + void copy_to_buffer(uint8_t* const buffer, uint32_t& index, T arg) + { + memcpy(&buffer[index], &arg, sizeof(arg)); + index += sizeof(arg); + } + + template + void copy_to_buffer(uint8_t* const buffer, uint32_t& index, T arg, Args... args) + { + memcpy(&buffer[index], &arg, sizeof(arg)); + index += sizeof(arg); + copy_to_buffer(buffer, index, args...); + } +}; + +} // namespace test +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/logger/mock/LoggerMock.cpp b/libs/bsw/middleware/test/src/logger/mock/LoggerMock.cpp new file mode 100644 index 00000000000..089fda763b2 --- /dev/null +++ b/libs/bsw/middleware/test/src/logger/mock/LoggerMock.cpp @@ -0,0 +1,91 @@ +#include "LoggerMock.h" + +#include +#include +#include +#include + +#include "middleware/logger/Logger.h" + +namespace middleware +{ +namespace logger +{ +namespace test +{ +namespace mock +{ +namespace +{ +middleware::logger::test::mock::LoggerMock* _loggerMockPtr{nullptr}; +} + +LoggerMock::LoggerMock() { _loggerMockPtr = this; } + +LoggerMock::~LoggerMock() { _loggerMockPtr = nullptr; } + +} // namespace mock +} // namespace test +} // namespace logger +} // namespace middleware + +namespace middleware +{ +namespace logger +{ + +void log(LogLevel const level, char const* const format, ...) +{ + va_list ap; + va_start(ap, format); + printf(format, ap); + printf("\n"); + + std::vector args; + for (char const* p = format; *p != '\0'; ++p) + { + switch (*p) + { + case '%': + switch (*++p) + { + case 'd': args.push_back(static_cast(va_arg(ap, int))); continue; + } + break; + } + } + + if (test::mock::_loggerMockPtr != nullptr) + { + test::mock::_loggerMockPtr->log(level, format, args); + } + + va_end(ap); +} + +void log_binary(LogLevel const level, etl::span const data) +{ + for (size_t i = 0; i < data.size(); ++i) + { + printf("%d ", data[i]); + } + printf("\n"); + + if (test::mock::_loggerMockPtr != nullptr) + { + test::mock::_loggerMockPtr->log_binary(level, data); + } +} + +uint32_t getMessageId(Error const id) +{ + if (test::mock::_loggerMockPtr != nullptr) + { + return test::mock::_loggerMockPtr->getMessageId(id); + } + + return etl::numeric_limits::max(); +} + +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/logger/mock/LoggerMock.h b/libs/bsw/middleware/test/src/logger/mock/LoggerMock.h new file mode 100644 index 00000000000..824a2248585 --- /dev/null +++ b/libs/bsw/middleware/test/src/logger/mock/LoggerMock.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +#include "middleware/logger/Logger.h" + +namespace middleware +{ +namespace logger +{ +namespace test +{ +namespace mock +{ + +class LoggerMock +{ +public: + LoggerMock(); + ~LoggerMock(); + + MOCK_METHOD(void, log, (LogLevel const, char const* const, std::vector const&)); + MOCK_METHOD(void, log_binary, (LogLevel const, etl::span const)); + MOCK_METHOD(uint32_t, getMessageId, (Error const)); +}; + +} // namespace mock +} // namespace test +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/os/OsDefinitions.cpp b/libs/bsw/middleware/test/src/os/OsDefinitions.cpp new file mode 100644 index 00000000000..f6976cd598d --- /dev/null +++ b/libs/bsw/middleware/test/src/os/OsDefinitions.cpp @@ -0,0 +1,13 @@ +#include + +#include "middleware/os/TaskIdProvider.h" + +namespace middleware +{ +namespace os +{ + +uint32_t getProcessId() { return 0; } + +} // namespace os +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.cpp b/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.cpp new file mode 100644 index 00000000000..8324bdd0031 --- /dev/null +++ b/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.cpp @@ -0,0 +1,50 @@ +#include "SystemTimerProviderMock.h" + +#include + +#include "middleware/time/SystemTimerProvider.h" + +namespace middleware +{ +namespace time +{ + +namespace +{ +test::SystemTimerProviderMock* gSystemTimerProviderMockPtr = nullptr; +} // namespace + +namespace test +{ + +void setSystemTimerProviderMock(SystemTimerProviderMock* const ptr) +{ + gSystemTimerProviderMockPtr = ptr; +} + +void unsetSystemTimerProviderMock() { gSystemTimerProviderMockPtr = nullptr; } + +} // namespace test + +uint32_t getCurrentTimeInMs() +{ + if (gSystemTimerProviderMockPtr != nullptr) + { + return gSystemTimerProviderMockPtr->getCurrentTimeInMs(); + } + + std::abort(); +} + +uint32_t getCurrentTimeInUs() +{ + if (gSystemTimerProviderMockPtr != nullptr) + { + return gSystemTimerProviderMockPtr->getCurrentTimeInUs(); + } + + std::abort(); +} + +} // namespace time +} // namespace middleware diff --git a/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.h b/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.h new file mode 100644 index 00000000000..f500734dc15 --- /dev/null +++ b/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +namespace middleware +{ +namespace time +{ +namespace test +{ + +class SystemTimerProviderMock +{ +public: + MOCK_METHOD(uint32_t, getCurrentTimeInMs, ()); + MOCK_METHOD(uint32_t, getCurrentTimeInUs, ()); +}; + +void setSystemTimerProviderMock(SystemTimerProviderMock* const ptr); +void unsetSystemTimerProviderMock(); + +} // namespace test +} // namespace time +} // namespace middleware