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..df057978979 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/concurrency/LockStrategies.h @@ -0,0 +1,61 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +namespace middleware +{ +namespace concurrency +{ + +/** + * Suspend all interrupts. + * 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(); + +/** + * Scoped lock for single-core protection. + * 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 +{ + /** Acquires the single-core lock. */ + ScopedCoreLock(); + + /** Releases the single-core lock. */ + ~ScopedCoreLock(); + + ScopedCoreLock(ScopedCoreLock const&) = delete; + ScopedCoreLock& operator=(ScopedCoreLock const&) = delete; +}; + +/** + * Scoped lock for ECU-wide (multi-core) protection. + * 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 +{ + /** Acquires the ECU-wide lock using \p lock. */ + ScopedECULock(uint8_t volatile* lock); + + /** Releases the ECU-wide lock. */ + ~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..7976832cf33 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ClusterConnection.h @@ -0,0 +1,284 @@ +// 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; + +/** + * Cluster connection for proxy-only communication without timeout support. + * 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: + /** Constructs from \p configuration. */ + explicit ClusterConnectionNoTimeoutProxyOnly( + IClusterConnectionConfigurationProxyOnly& configuration); + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** Not supported: this is a proxy-only connection. */ + HRESULT subscribe(SkeletonBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** No-op: this is a proxy-only connection. */ + void unsubscribe(SkeletonBase&, uint16_t const) final {} +}; + +/** + * Cluster connection for skeleton-only communication without timeout support. + * 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: + /** Constructs from \p configuration. */ + explicit ClusterConnectionNoTimeoutSkeletonOnly( + IClusterConnectionConfigurationSkeletonOnly& configuration); + + /** Not supported: this is a skeleton-only connection. */ + HRESULT subscribe(ProxyBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) final; + + /** No-op: this is a skeleton-only connection. */ + void unsubscribe(ProxyBase&, uint16_t const) final {} + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) final; +}; + +/** + * Cluster connection for bidirectional communication without timeout support. + * 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: + /** Constructs from \p configuration. */ + explicit ClusterConnectionNoTimeoutBidirectional( + IClusterConnectionConfigurationBidirectional& configuration); + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) final; + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) final; +}; + +/** + * Cluster connection for bidirectional communication with timeout support. + * 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: + /** Constructs from \p configuration. */ + explicit ClusterConnectionBidirectionalWithTimeout( + IClusterConnectionConfigurationBidirectionalWithTimeout& configuration); + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) final; + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) final; +}; + +/** + * Cluster connection for proxy-only communication with timeout support. + * 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: + /** Constructs from \p configuration. */ + explicit ClusterConnectionProxyOnlyWithTimeout( + IClusterConnectionConfigurationProxyOnlyWithTimeout& configuration); + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) final; + + /** Not supported: this is a proxy-only connection. */ + HRESULT subscribe(SkeletonBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) final; + + /** No-op: this is a proxy-only connection. */ + void unsubscribe(SkeletonBase&, uint16_t const) final {} +}; + +/** + * Cluster connection for skeleton-only communication with timeout support. + * 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: + /** Constructs from \p configuration. */ + explicit ClusterConnectionSkeletonOnlyWithTimeout( + IClusterConnectionConfigurationSkeletonOnlyWithTimeout& configuration); + + /** Not supported: this is a skeleton-only connection. */ + HRESULT subscribe(ProxyBase&, uint16_t const) final { return HRESULT::NotImplemented; } + + /** \see IClusterConnection::subscribe() */ + HRESULT subscribe(SkeletonBase&, uint16_t const) final; + + /** No-op: this is a skeleton-only connection. */ + void + unsubscribe([[maybe_unused]] ProxyBase& proxy, [[maybe_unused]] uint16_t const serviceId) final + {} + + /** \see IClusterConnection::unsubscribe() */ + void unsubscribe(SkeletonBase&, [[maybe_unused]] uint16_t const) final; +}; + +/** + * Type selector for cluster connection implementations. + * 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; + +/** + * 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> +{ + /** The selected cluster connection type. */ + using type = ClusterConnectionNoTimeoutProxyOnly; +}; + +/** + * 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> +{ + /** The selected cluster connection type. */ + using type = ClusterConnectionNoTimeoutSkeletonOnly; +}; + +/** + * 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> +{ + /** The selected cluster connection type. */ + using type = ClusterConnectionNoTimeoutBidirectional; +}; + +/** + * 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> +{ + /** The selected cluster connection type. */ + using type = ClusterConnectionProxyOnlyWithTimeout; +}; + +/** + * 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> +{ + /** The selected cluster connection type. */ + using type = ClusterConnectionSkeletonOnlyWithTimeout; +}; + +/** + * 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> +{ + /** The selected cluster connection type. */ + using type = ClusterConnectionBidirectionalWithTimeout; +}; + +} // 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..54ed4aca095 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ClusterConnectionBase.h @@ -0,0 +1,111 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/IClusterConnectionConfigurationBase.h" +#include "middleware/core/ITimeoutHandler.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 +{ +/** + * Base class for cluster connection implementations. + * 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; + + /** + * Main message processing function. + * 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; + + /** \see IClusterConnection::registeredTransceiversCount() */ + size_t registeredTransceiversCount(uint16_t const serviceId) const override + { + return fConfiguration.registeredTransceiversCount(serviceId); + } + +protected: + /** Returns the configuration object. */ + IClusterConnectionConfigurationBase& getConfiguration() const { return fConfiguration; } + + /** Constructs from \p configuration. */ + explicit ClusterConnectionBase(IClusterConnectionConfigurationBase& configuration); + + /** \see IClusterConnection::getSourceClusterId() */ + uint8_t getSourceClusterId() const override { return fConfiguration.getSourceClusterId(); } + + /** \see IClusterConnection::getTargetClusterId() */ + uint8_t getTargetClusterId() const override { return fConfiguration.getTargetClusterId(); } + + /** \see IClusterConnection::sendMessage() */ + HRESULT sendMessage(Message const& msg) const override; + + /** \see IClusterConnection::dispatchMessage() */ + HRESULT dispatchMessage(Message const& msg) const override; + +private: + /** + * Respond with an error message. + * 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; + + /** Reference to the cluster connection configuration. */ + IClusterConnectionConfigurationBase& fConfiguration; +}; + +/** + * Base class for cluster connections with timeout support. + * 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: + /** \see IClusterConnection::registerTimeoutTransceiver() */ + void registerTimeoutTransceiver(ITimeoutHandler& transceiver) override; + + /** \see IClusterConnection::unregisterTimeoutTransceiver() */ + void unregisterTimeoutTransceiver(ITimeoutHandler& transceiver) override; + + /** + * Update all registered timeout transceivers. + * Processes timeout updates for all registered transceivers, checking for expired + * timeouts and triggering appropriate notifications. + */ + void updateTimeouts(); + +protected: + /** Constructs from \p 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..721d3599b9e --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/DatabaseManipulator.h @@ -0,0 +1,211 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include +#include + +#include "middleware/core/TransceiverBase.h" +#include "middleware/core/TransceiverContainer.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +class ProxyBase; +class SkeletonBase; + +namespace meta +{ + +namespace internal +{ +/** + * Dummy transceiver implementation for testing and placeholder purposes. + * 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 TransceiverBase +{ + using Base = TransceiverBase; + + /** No-op: always returns HRESULT::Ok. */ + virtual HRESULT onNewMessageReceived(Message const&) override { return HRESULT::Ok; } + + /** Returns INVALID_SERVICE_ID. */ + virtual uint16_t getServiceId() const override { return INVALID_SERVICE_ID; } + + /** Returns 0xFF. */ + virtual uint8_t getSourceClusterId() const override { return static_cast(0xFFU); } + + /** Returns false always. */ + virtual bool isInitialized() const override { return false; } + + /** No-op: always returns HRESULT::Ok. */ + virtual HRESULT sendMessage(Message&) const override { return HRESULT::Ok; } + + /** \see TransceiverBase::getAddressId() */ + virtual uint8_t getAddressId() const override { return fAddressId; } + + /** \see TransceiverBase::setAddressId() */ + virtual void setAddressId(uint8_t const addressId) override { fAddressId = addressId; } + + /** Constructs with \p instanceId and optional \p addressId. */ + explicit DummyTransceiver( + uint16_t const instanceId, uint8_t const addressId = INVALID_ADDRESS_ID) + : Base(instanceId), fAddressId(addressId) + {} + + virtual ~DummyTransceiver() = default; + +private: + /** The address ID of the dummy transceiver. */ + uint8_t fAddressId; +}; + +} // namespace internal + +/** + * Database manipulator for managing transceiver subscriptions and lookups. + * 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: + /** + * Subscribe a proxy to the transceiver database. + * 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 instanceId, + uint16_t maxServiceId); + + /** + * Subscribe a skeleton to the transceiver database. + * 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 instanceId, + uint16_t maxServiceId); + + /** + * Unsubscribe a transceiver from the database. + * 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, + TransceiverBase& transceiver, + uint16_t serviceId); + + /** Returns a pointer to the transceiver container for \p serviceId, or nullptr if not found. */ + static TransceiverContainer* getTransceiversByServiceId( + middleware::core::meta::TransceiverContainer* const start, + middleware::core::meta::TransceiverContainer* const end, + uint16_t serviceId); + + /** Returns a const pointer to the transceiver container for \p serviceId, 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 serviceId); + + /** + * Get transceivers by service ID and service instance ID. + * 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 serviceId, + uint16_t instanceId); + + /** Returns the skeleton matching \p serviceId and \p instanceId, or nullptr if not found. */ + static TransceiverBase* getSkeletonByServiceIdAndServiceInstanceId( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t serviceId, + uint16_t instanceId); + + /** Returns an iterator to \p transceiver in \p container, or end() if not found. */ + static etl::ivector::iterator + findTransceiver(TransceiverBase* const& transceiver, etl::ivector& container); + + /** Returns true if a skeleton with \p instanceId is registered in \p container. */ + static bool isSkeletonWithServiceInstanceIdRegistered( + etl::ivector const& container, uint16_t instanceId); + + /** + * Get a transceiver by service ID, instance ID, and address ID. + * 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 TransceiverBase* getTransceiver( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t serviceId, + uint16_t instanceId, + uint16_t addressId); + + /** Returns count of registered transceivers for \p serviceId. */ + static size_t registeredTransceiversCount( + middleware::core::meta::TransceiverContainer const* const start, + middleware::core::meta::TransceiverContainer const* const end, + uint16_t 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..89417b6d7ce --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/IClusterConnection.h @@ -0,0 +1,103 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include "middleware/core/ITimeoutHandler.h" +#include "middleware/core/Message.h" +#include "middleware/core/TransceiverBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +class SkeletonBase; +class ProxyBase; + +/** + * Interface for timeout cluster connection. + * This interface provides basic timeout management functionality for cluster connections. + * It allows transceivers to register and unregister for timeout notifications. + */ +class ITimeoutClusterConnection +{ +public: + /** Registers \p timeout as a timeout transceiver (no-op by default). */ + virtual void registerTimeoutTransceiver(ITimeoutHandler&) {} + + /** Unregisters \p timeout from timeout transceivers (no-op by default). */ + virtual void unregisterTimeoutTransceiver(ITimeoutHandler&) {} +}; + +/** + * Interface for cluster connection management. + * 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: + /** Returns the source cluster ID. */ + virtual uint8_t getSourceClusterId() const = 0; + + /** Returns the target cluster ID. */ + virtual uint8_t getTargetClusterId() const = 0; + + /** Subscribes \p proxy for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** Subscribes \p skeleton for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p proxy for \p serviceId. */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + + /** Unsubscribes \p skeleton for \p serviceId. */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + + /** + * Send a message through the cluster connection. + * 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; + + /** + * Process an incoming message. + * 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; + + /** Returns count of registered transceivers for a \p serviceId. */ + virtual size_t registeredTransceiversCount(uint16_t const serviceId) const = 0; + + /** + * Dispatch a message to its intended recipients. + * 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..bb64477fdb3 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/IClusterConnectionConfigurationBase.h @@ -0,0 +1,351 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include "middleware/core/ITimeoutHandler.h" +#include "middleware/core/Message.h" +#include "middleware/core/TransceiverBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +class ProxyBase; +class SkeletonBase; + +namespace meta +{ +struct TransceiverContainer; +} + +/** + * Base interface for cluster connection configurations. + * 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 +{ + /** Returns the source cluster ID. */ + virtual uint8_t getSourceClusterId() const = 0; + + /** Returns the target cluster ID. */ + virtual uint8_t getTargetClusterId() const = 0; + + /** Writes \p msg to the cluster connection, returns true on success. */ + virtual bool write(Message const& msg) const = 0; + + /** Returns count of registered transceivers for \p serviceId. */ + virtual size_t registeredTransceiversCount(uint16_t const serviceId) const = 0; + + /** + * Dispatch a message to its intended recipients. + * 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; + + /** + * Dispatch a message to proxy transceivers. + * 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); + + /** + * Dispatch a message to skeleton transceivers. + * 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); + + /** + * Dispatch a message to both proxy and skeleton transceivers. + * 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); +}; + +/** + * Configuration interface for cluster connections with timeout support. + * 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 +{ + /** Registers \p transceiver for timeout events. */ + virtual void registerTimeoutTransceiver(ITimeoutHandler& transceiver) = 0; + + /** Unregisters \p transceiver from timeout events. */ + virtual void unregisterTimeoutTransceiver(ITimeoutHandler& transceiver) = 0; + + /** + * Update all registered timeout transceivers. + * 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; + + /** Adds \p transceiver to \p timeoutTransceivers. */ + static void registerTimeoutTransceiver( + ITimeoutHandler& transceiver, ::etl::ivector& timeoutTransceivers); + + /** Removes \p transceiver from \p timeoutTransceivers. */ + static void unregisterTimeoutTransceiver( + ITimeoutHandler& transceiver, ::etl::ivector& timeoutTransceivers); + + /** Updates all timeouts in \p timeoutTransceivers. */ + static void updateTimeouts(::etl::ivector const& timeoutTransceivers); +}; + +/** + * Configuration interface for proxy-only cluster connections. + * 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; + + /** Subscribes \p proxy for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p proxy for \p serviceId. */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationProxyOnly() = default; + IClusterConnectionConfigurationProxyOnly() = default; +}; + +/** + * Configuration interface for skeleton-only cluster connections. + * 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; + + /** Subscribes \p skeleton for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p skeleton for \p serviceId. */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationSkeletonOnly() = default; + IClusterConnectionConfigurationSkeletonOnly() = default; +}; + +/** + * Configuration interface for bidirectional cluster connections. + * 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; + + /** Subscribes \p proxy for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p proxy for \p serviceId. */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + + /** Subscribes \p skeleton for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p skeleton for \p serviceId. */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationBidirectional() = default; + IClusterConnectionConfigurationBidirectional() = default; +}; + +/** + * Configuration interface for proxy-only cluster connections with timeout support. + * 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; + + /** Subscribes \p proxy for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p proxy for \p serviceId. */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + +protected: + virtual ~IClusterConnectionConfigurationProxyOnlyWithTimeout() = default; + IClusterConnectionConfigurationProxyOnlyWithTimeout() = default; +}; + +/** + * Configuration interface for bidirectional cluster connections with timeout support. + * 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; + + /** Subscribes \p proxy for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(ProxyBase& proxy, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p proxy for \p serviceId. */ + virtual void unsubscribe(ProxyBase& proxy, uint16_t const serviceId) = 0; + + /** Subscribes \p skeleton for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p skeleton for \p serviceId. */ + virtual void unsubscribe(SkeletonBase& skeleton, uint16_t const serviceId) = 0; + +protected: + IClusterConnectionConfigurationBidirectionalWithTimeout() = default; + virtual ~IClusterConnectionConfigurationBidirectionalWithTimeout() = default; +}; + +/** + * Configuration interface for skeleton-only cluster connections with timeout support. + * 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; + + /** Subscribes \p skeleton for \p serviceInstanceId, returns HRESULT. */ + virtual HRESULT subscribe(SkeletonBase& skeleton, uint16_t const serviceInstanceId) = 0; + + /** Unsubscribes \p skeleton for \p serviceId. */ + 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/ITimeoutHandler.h b/libs/bsw/middleware/include/middleware/core/ITimeoutHandler.h new file mode 100644 index 00000000000..b60456a4591 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ITimeoutHandler.h @@ -0,0 +1,30 @@ +// Copyright 2025 BMW AG + +#pragma once + +namespace middleware +{ +namespace core +{ +/** + * Abstract base class for timeout handling. + * Implementations can register with cluster connections to receive periodic timeout + * notifications and process expired timeouts accordingly. + */ +class ITimeoutHandler +{ +public: + /** + * Update all managed timeouts. + * 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; + + virtual ~ITimeoutHandler() = default; + ITimeoutHandler& operator=(ITimeoutHandler const&) = delete; + ITimeoutHandler& operator=(ITimeoutHandler&&) = delete; +}; +} // 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..6c4cd94c894 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/InstancesDatabase.h @@ -0,0 +1,38 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include "IClusterConnection.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +/** + * Interface for instance database management. + * 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 +{ + /** Returns the span of skeleton cluster connections. */ + virtual etl::span getSkeletonConnectionsRange() const = 0; + + /** Returns the span of proxy cluster connections. */ + virtual etl::span getProxyConnectionsRange() const = 0; + + /** Returns the span of registered 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..ad6f04ba8b9 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/LoggerApi.h @@ -0,0 +1,151 @@ +// 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 +{ + +/** Log buffer size for allocation failures. */ +ETL_INLINE_VAR constexpr uint8_t ALLOCATION_FAILURE_LOG_SIZE = 20U; +/** Log buffer size for initialization failures. */ +ETL_INLINE_VAR constexpr uint8_t INIT_FAILURE_LOG_SIZE = 11U; +/** Log buffer size for message sending failures. */ +ETL_INLINE_VAR constexpr uint8_t MSG_SEND_FAILURE_LOG_SIZE = 16U; +/** Log buffer size for cross-thread violations. */ +ETL_INLINE_VAR constexpr uint8_t CROSS_THREAD_VIOLATION_LOG_SIZE = 18U; + +/** + * Helper template to count the total size of types in bytes. + * 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; + +/** + * Specialization for a single type. + * \tparam T the type to count bytes for + */ +template +struct count_bytes +{ + /** The size of type T in bytes. */ + static constexpr size_t VALUE = sizeof(T); +}; + +/** + * Specialization for core::Message type. + * Counts only the bytes of the header fields actually serialized: + * srcClusterId, tgtClusterId, serviceId, serviceInstanceId, memberId, requestId. + */ +template<> +struct count_bytes +{ + using Header = core::Message::Header; + static constexpr size_t VALUE + = sizeof(decltype(Header::srcClusterId)) + sizeof(decltype(Header::tgtClusterId)) + + sizeof(decltype(Header::serviceId)) + sizeof(decltype(Header::serviceInstanceId)) + + sizeof(decltype(Header::memberId)) + sizeof(decltype(Header::requestId)); +}; + +/** + * Recursive template specialization for multiple types. + * \tparam T the first type + * \tparam Args the remaining types + */ +template +struct count_bytes +{ + /** Sum of all type sizes. */ + static constexpr size_t VALUE = count_bytes::VALUE + count_bytes::VALUE; +}; + +/** + * Logs an allocation failure event. + * 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 level, Error error, core::HRESULT res, core::Message const& msg, uint32_t size); + +/** + * Logs an initialization failure event for a service. + * 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 level, + Error error, + core::HRESULT res, + uint16_t serviceId, + uint16_t serviceInstanceId, + uint8_t sourceCluster); + +/** + * Logs a failure encountered while sending a message. + * 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 level, Error error, core::HRESULT res, core::Message const& msg); + +/** + * Logs a violation of cross-thread operations. + * 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 level, + Error error, + uint8_t sourceCluster, + uint16_t serviceId, + uint16_t serviceInstanceId, + uint32_t initId, + uint32_t currentTaskId); + +} // namespace logger +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/Message.h b/libs/bsw/middleware/include/middleware/core/Message.h index ad662818885..fbd31c98b66 100644 --- a/libs/bsw/middleware/include/middleware/core/Message.h +++ b/libs/bsw/middleware/include/middleware/core/Message.h @@ -16,8 +16,8 @@ namespace core { /** - * \brief Message object that is used for communication between proxies and skeletons. - * \details This object has 32 bytes in total which are distributed between its private members. + * Message object that is used for communication between proxies and skeletons. + * This object has 32 bytes in total which are distributed between its private members. * These consist of the following members: * - header, which is similar to the SOME/IP's message ID and request ID members; * - Additionally the header has some dispatching information like the source cluster from where the @@ -32,7 +32,7 @@ class Message static constexpr size_t MAX_PAYLOAD_SIZE = 32U; /** - * \brief The message's header. + * The message's header. * */ struct Header @@ -48,8 +48,8 @@ class Message }; /** - * \brief An object that contains information to where the data is stored. - * \details This object will be one of the possible types of the payload union. Whenever the + * An object that contains information to where the data is stored. + * This object will be one of the possible types of the payload union. Whenever the * data that needs to be sent is biffer than MAX_PAYLOAD_SIZE, the data must be allocated * externally and the message's payload set to this object. This object has an offset from * the beginning of the memory section where the data was stored, and the size of the data. @@ -57,21 +57,24 @@ class Message */ struct ExternalHandle { - ptrdiff_t offset; ///< The offset, from the beginning of the memory region dedicated for - ///< storing middleware data, where the message's payload is stored. - size_t size; ///< The size of the payload that is stored. + /** The offset, from the beginning of the memory region dedicated for storing middleware + * data, where the message's payload is stored. */ + ptrdiff_t offset; + /** The size of the payload that is stored. */ + size_t size; }; union PayloadType { constexpr PayloadType() : internalBuffer() {} - etl::array - internalBuffer; ///< A buffer that can store some data in place. - ExternalHandle externalHandle; ///< An object that contains information to where the data is - ///< located in memory. - ErrorState error; ///< An error value, which gives information of what error might have - ///< occurred during the communication. + /** A buffer that can store some data in place. */ + etl::array internalBuffer; + /** An object that contains information to where the data is located in memory. */ + ExternalHandle externalHandle; + /** An error value, which gives information of what error might have occurred during the + * communication. */ + ErrorState error; }; ~Message() = default; @@ -80,16 +83,12 @@ class Message Message(Message&& other) = default; Message& operator=(Message&& other) & = default; - /** - * \brief Get a constant reference to the message's header. - * - * \return const Header& - */ + /** Returns a const reference to the message header. */ Header const& getHeader() const { return _header; } /** - * \brief Set the target cluster id. - * \details This method can be useful to send the same message to several recipients, + * Set the target cluster id. + * This method can be useful to send the same message to several recipients, * by just changing the target cluster id. * * \param clusterId @@ -97,7 +96,7 @@ class Message void setTargetClusterId(uint8_t const clusterId) { _header.tgtClusterId = clusterId; } /** - * \brief Create a request message that originates from a proxy and is targetted to a specific + * Create a request message that originates from a proxy and is targetted to a specific * skeleton. * * \param header reference to the message header @@ -126,7 +125,7 @@ class Message } /** - * \brief Create a fireAndForget request message that originates from a proxy and is targetted + * Create a fireAndForget request message that originates from a proxy and is targetted * to a specific skeleton. * * \param serviceId service ID @@ -157,7 +156,7 @@ class Message } /** - * \brief Create a response message that originates from a skeleton and is targetted to a unique + * Create a response message that originates from a skeleton and is targetted to a unique * proxy. * * \param header reference to the message header @@ -186,7 +185,7 @@ class Message } /** - * \brief Create an event message without source cluster and target cluster IDs set. + * Create an event message without source cluster and target cluster IDs set. * \remark After calling this method, you need to manually set the target cluster ID. This * allows to use the same message to be sent to several clusters by just adapting the cluster * ID. @@ -216,7 +215,7 @@ class Message } /** - * \brief Create an error response message that originates from a skeleton and is targetted to a + * Create an error response message that originates from a skeleton and is targetted to a * unique proxy, and sets the payload with value \param error. * * \param header reference to the message header @@ -249,61 +248,28 @@ class Message return msg; } - /** - * \brief Check if message is a request. - * - * \return true if Flags::Request is active, otherwise returns false. - */ + /** Returns true if the Request flag is active. */ bool isRequest() const { return hasActiveFlag(Flags::Request); } - /** - * \brief Check if message is a fire and forget request. - * - * \return true if Flags::FireAndForgetRequest is active, otherwise returns false. - */ + /** Returns true if the FireAndForgetRequest flag is active. */ bool isFireAndForgetRequest() const { return hasActiveFlag(Flags::FireAndForgetRequest); } - /** - * \brief Check if message is a response. - * - * \return true if Flags::Response is active, otherwise returns false. - */ + /** Returns true if the Response flag is active. */ bool isResponse() const { return hasActiveFlag(Flags::Response); } - /** - * \brief Check if message contains an error. - * - * \return true if Flags::Error is active, otherwise returns false. - */ + /** Returns true if the Error flag is active. */ bool isError() const { return hasActiveFlag(Flags::Error); } - /** - * \brief Check if message is an event. - * - * \return true if Flags::Event is active, otherwise returns false. - */ + /** Returns true if the Event flag is active. */ bool isEvent() const { return hasActiveFlag(Flags::Event); } - /** - * \brief Check if message contains a reference to a unique external payload. - * - * \return true if Flags::UniqueExternalPayload is active, otherwise returns false. - */ + /** Returns true if the UniqueExternalPayload flag is active. */ bool hasUniqueExternalPayload() const { return hasActiveFlag(Flags::UniqueExternalPayload); } - /** - * \brief Check if message contains a reference to a shared external payload. - * - * \return true if Flags::SharedExternalPayload is active, otherwise returns false. - */ + /** Returns true if the SharedExternalPayload flag is active. */ bool hasSharedExternalPayload() const { return hasActiveFlag(Flags::SharedExternalPayload); } - /** - * \brief Get the ErrorState value of the message. - * - * \return the current ErrorState value if message has error, otherwise returns - * ErrorState::NoError. - */ + /** Returns the ErrorState if the message has an error, otherwise ErrorState::NoError. */ ErrorState getErrorState() const { return isError() ? _payload.error : ErrorState::NoError; } private: @@ -323,7 +289,7 @@ class Message }; /** - * \brief Gets a constant reference of the object that is stored inside the payload's internal + * Gets a constant reference of the object that is stored inside the payload's internal * buffer. \remark This method assumes that the user has checked that the message contains the * payload in its internal buffer, and not an ExternalHandle object or an error value. * @@ -344,7 +310,7 @@ class Message } /** - * \brief Constructs a copy of \param obj inside the payload's internal buffer. + * Constructs a copy of \param obj inside the payload's internal buffer. * * \tparam T the generic type which must be trivially copyable and have a size less than the * internal payload's size. \param obj @@ -363,7 +329,7 @@ class Message } /** - * \brief Gets a constant reference to the ExternalHandle object that is stored inside the + * Gets a constant reference to the ExternalHandle object that is stored inside the * message's payload. \remark This method assumes that the user has checked that the message * contains an ExternalHandle object, and not an error value or the actual payload stored inside * the internal buffer. @@ -373,7 +339,7 @@ class Message ExternalHandle const& getExternalHandle() const { return _payload.externalHandle; } /** - * \brief Set the payload as an ExternalHandle type which will contain information to where the + * Set the payload as an ExternalHandle type which will contain information to where the * actual message's payload is stored. * * \param offset @@ -387,36 +353,23 @@ class Message &_payload.externalHandle, ExternalHandle{offset, size}); } - /** - * \brief Checks if a flag is currently active in the flags bitmask. - * - * \param flag - * \return true if \param flag is active, otherwise false. - */ + /** Returns true if \p flag is active in the flags bitmask. */ constexpr bool hasActiveFlag(Flags const flag) const { return (_header.flags & static_cast(flag)) == static_cast(flag); } - /** - * \brief Sets the \param flag to active in the flags bitmask. - * - * \param flag - */ + /** Sets \p flag to active in the flags bitmask. */ constexpr void setFlag(Flags const flag) { _header.flags |= static_cast(flag); } - /** - * \brief Unsets the \param flag to active in the flags bitmask. - * - * \param flag - */ + /** Unsets \p flag in the flags bitmask. */ constexpr void unsetFlag(Flags const& flag) { _header.flags &= ~static_cast(flag); } - Header - _header; ///< The message header containing the service, method, request and instance ids. - PayloadType _payload; ///< The message payload which can either store some data - ///< constructed in place, and external handle pointing to the - ///< place where the actual data is stored or an error value. + /** The message header containing the service, method, request and instance ids. */ + Header _header; + /** The message payload which can either store some data constructed in place, an external + * handle pointing to where the actual data is stored, or an error value. */ + PayloadType _payload; }; } // namespace core 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..c9f0bf4fdf5 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/ProxyBase.h @@ -0,0 +1,115 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include + +#include "IClusterConnection.h" +#include "InstancesDatabase.h" +#include "TransceiverBase.h" + +namespace middleware +{ +namespace core +{ + +/** + * Base class for proxy implementations. + * 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 TransceiverBase +{ +public: + /** + * Set the address ID for this proxy. + * 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 addressId) final; + + /** \see TransceiverBase::getAddressId() */ + uint8_t getAddressId() const final; + + /** + * Check if the proxy is initialized. + * 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; + + /** + * Generate a message header for a request. + * 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 memberId, uint16_t requestId = INVALID_REQUEST_ID) const; + + /** + * Send a message through this proxy. + * 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() : TransceiverBase(), addressId_(INVALID_ADDRESS_ID) {} + + virtual ~ProxyBase() = default; + + /** \see TransceiverBase::getSourceClusterId() */ + uint8_t getSourceClusterId() const final; + + /** Unsubscribes this proxy for \p serviceId. */ + void unsubscribe(uint16_t serviceId); + + /** + * Check for cross-thread access violations. + * 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 initId) const; + + /** + * Initialize the proxy from the instances database. + * 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 instanceId, + uint8_t sourceCluster, + etl::span const& dbRange); + + /** Pointer to the cluster connection for this proxy. */ + IClusterConnection* fConnection{nullptr}; + +private: + /** The unique address ID for this proxy instance. */ + uint8_t addressId_; +}; + +} // 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..79b19f1ee60 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/SkeletonBase.h @@ -0,0 +1,100 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include + +#include "middleware/core/IClusterConnection.h" +#include "middleware/core/InstancesDatabase.h" +#include "middleware/core/TransceiverBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +class EventSender; + +/** + * Base class for skeleton implementations. + * 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 TransceiverBase +{ + friend class EventSender; + +public: + /** + * Check if the skeleton is initialized. + * 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; + + /** + * Send a message through this skeleton. + * 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; + + /** \see TransceiverBase::getSourceClusterId() */ + uint8_t getSourceClusterId() const final; + + /** Returns the span of cluster connections for this skeleton. */ + etl::span const& getClusterConnections() const; + +protected: + virtual ~SkeletonBase(); + + /** Unsubscribes this skeleton for \p serviceId. */ + void unsubscribe(uint16_t serviceId); + + /** + * Check for cross-thread access violations. + * 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 initId) const; + + /** + * Initialize the skeleton from the instances database. + * 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 instanceId, etl::span const& dbRange); + + /** Span of cluster connections for this skeleton. */ + etl::span connections_; + +private: + /** Returns INVALID_ADDRESS_ID (skeletons do not use address IDs). */ + uint8_t getAddressId() const final { return INVALID_ADDRESS_ID; } + + /** No-op: skeletons do not use address IDs. */ + void setAddressId(uint8_t const) final {} + + /** Returns the process/task ID; default returns INVALID_TASK_ID. */ + virtual uint32_t getProcessId() const { return INVALID_TASK_ID; } +}; + +} // namespace core +} // namespace middleware diff --git a/libs/bsw/middleware/include/middleware/core/TransceiverBase.h b/libs/bsw/middleware/include/middleware/core/TransceiverBase.h new file mode 100644 index 00000000000..d9914002298 --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/TransceiverBase.h @@ -0,0 +1,87 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include "middleware/core/Message.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +/** + * Abstract base class for message transceivers in the middleware. + * This class 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 TransceiverBase +{ +public: + /** Returns the service instance ID. */ + uint16_t getInstanceId() const { return instanceId_; } + + /** Returns the service ID. */ + virtual uint16_t getServiceId() const = 0; + + /** Returns the address ID used for routing messages to specific proxy instances. */ + virtual uint8_t getAddressId() const = 0; + + /** + * Handle reception of a new message. + * 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; + + /** Sets the service instance ID to \p instanceId. */ + void setInstanceId(uint16_t const instanceId) { instanceId_ = instanceId; } + + /** Sets the address ID to \p addressId. */ + virtual void setAddressId(uint8_t addressId) = 0; + + /** + * Check if the transceiver is initialized. + * 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; + + /** + * Send a message through this transceiver. + * 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; + + /** Returns the source cluster ID of this transceiver. */ + virtual uint8_t getSourceClusterId() const = 0; + + TransceiverBase(TransceiverBase const&) = delete; + TransceiverBase& operator=(TransceiverBase const&) = delete; + TransceiverBase(TransceiverBase&&) = delete; + TransceiverBase& operator=(TransceiverBase&&) = delete; + +protected: + /** The service instance ID for this transceiver. */ + uint16_t instanceId_; + + constexpr explicit TransceiverBase(uint16_t const instanceId = INVALID_INSTANCE_ID) + : instanceId_(instanceId) + {} + + virtual ~TransceiverBase() = default; +}; + +} // 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..a5066242bca --- /dev/null +++ b/libs/bsw/middleware/include/middleware/core/TransceiverContainer.h @@ -0,0 +1,101 @@ +// Copyright 2025 BMW AG + +#pragma once + +#include + +#include +#include + +#include "middleware/core/TransceiverBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ +namespace meta +{ +/** + * Container for managing transceiver collections. + * 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 +{ + /** + * Comparator for transceivers with instance ID and address ID. + * 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 + { + /** + * Compare two transceivers by instance ID and address ID. + * 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()(TransceiverBase const* const lhs, TransceiverBase const* const rhs) const + { + return ( + etl::make_pair(lhs->getInstanceId(), lhs->getAddressId()) + < etl::make_pair(rhs->getInstanceId(), rhs->getAddressId())); + } + }; + + /** + * Comparator for transceivers by instance ID only. + * 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 + { + /** + * Compare two transceivers by instance ID only. + * 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()(TransceiverBase const* const lhs, TransceiverBase 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; + + /** Pointer to the vector holding transceiver pointers. */ + etl::ivector* const fContainer{}; + /** The service ID associated with this container. */ + uint16_t const fServiceid; + /** The current/next available address ID for proxies. */ + uint16_t fActualAddress; +}; + +} // 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..161188be84e --- /dev/null +++ b/libs/bsw/middleware/include/middleware/logger/Logger.h @@ -0,0 +1,80 @@ +// 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 +{ + /** Memory allocation failure. */ + Allocation = 0U, + /** Memory deallocation failure. */ + Deallocation = 1U, + /** Proxy initialization failure. */ + ProxyInitialization = 2U, + /** Skeleton initialization failure. */ + SkeletonInitialization = 3U, + /** Message dispatch failure. */ + DispatchMessage = 4U, + /** Message sending failure. */ + SendMessage = 5U, + /** Proxy cross-thread access violation. */ + ProxyCrossThreadViolation = 6U, + /** Skeleton cross-thread access violation. */ + SkeletonCrossThreadViolation = 7U, +}; + +/** + * Generic logging function for formatted messages. + * 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, ...); + +/** + * Generic logging function for binary data. + * 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); + +/** + * Get the message ID associated with an error type. + * 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..ebefef0040a --- /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 +{ + +/** + * Get the current process/task ID. + * 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/queue/Queue.h b/libs/bsw/middleware/include/middleware/queue/Queue.h index d3990b49234..dc8c2636e3e 100644 --- a/libs/bsw/middleware/include/middleware/queue/Queue.h +++ b/libs/bsw/middleware/include/middleware/queue/Queue.h @@ -15,9 +15,9 @@ namespace queue { /** - * \brief A queue mutex wrapper which can accept an integer, which means that the mutex would live + * A queue mutex wrapper which can accept an integer, which means that the mutex would live * inside the queue, or a pointer, which would mean that the mutex would live outside the queue. - * \details Each specialization will provide a init method, a get method and an alias to the wrapped + * Each specialization will provide a init method, a get method and an alias to the wrapped * mutex type, which will be the same as T but with an additional volatile qualifier. * * \tparam T the mutex type which must be an integer or a pointer to an integer. @@ -27,7 +27,7 @@ template class QueueMutex; /** - * \brief Specialization for integer mutexes. + * Specialization for integer mutexes. * * \tparam T */ @@ -37,18 +37,10 @@ class QueueMutex::value>> public: using mutex_t = etl::add_volatile_t; - /** - * \brief Initializes the mutex's value to the one specified by \param initialValue. - * - * \param initialValue - */ + /** Initializes the mutex to \p initialValue. */ void init(mutex_t initialValue = 0U) { _mutex = initialValue; } - /** - * \brief Get a pointer to the internal mutex variable. - * - * \return mutex_t* - */ + /** Returns a pointer to the mutex variable. */ mutex_t* get() { return &_mutex; } private: @@ -56,8 +48,8 @@ class QueueMutex::value>> }; /** - * \brief Specialization for pointer to integer mutexes. - * \details In this specialization, when creating the mutex_t alias we first remove the pointer and + * Specialization for pointer to integer mutexes. + * In this specialization, when creating the mutex_t alias we first remove the pointer and * then add the volatile to T and finally add the pointer again. This is because (volatile uint8_t*) * != (uint8_t* volatile) and we want the first one. * @@ -78,11 +70,7 @@ class QueueMutex::value>> "Pointer's underlying type must be an integral"); } - /** - * \brief Initializes the mutex's value to the one specified by \param initialValue. - * - * \param initialValue - */ + /** Initializes the mutex to \p initialValue. */ void init(mutex_t initialValue = 0U) { ETL_ASSERT( @@ -92,11 +80,7 @@ class QueueMutex::value>> *_mutex = 0U; } - /** - * \brief Get a pointer to the internal mutex variable. - * - * \return mutex_t* - */ + /** Returns a pointer to the mutex variable. */ mutex_t get() { return _mutex; } private: @@ -104,7 +88,7 @@ class QueueMutex::value>> }; /** - * \brief Struct encapsulating features for the queue. + * Struct encapsulating features for the queue. * * \tparam Type the object type that the queue will contain. * \tparam Count the number of elements of the queue. @@ -122,8 +106,8 @@ struct QueueTraits }; /** - * \brief A queue object with two specializations: one where Traits::LockStrategy is different than - * void and another where it is void meaning that the queue doesn't need a lock mechanism. \details + * A queue object with two specializations: one where Traits::LockStrategy is different than + * void and another where it is void meaning that the queue doesn't need a lock mechanism. * Both specializations will provide two nested classes "Sender" and "Receiver", which are * constructed by receiving a reference to a queue instance, to be used for sending and receiving * elements. @@ -134,7 +118,7 @@ template class Queue; /** - * \brief Specialization of queue with a lock mechanism. + * Specialization of queue with a lock mechanism. * * \tparam Traits which will be of QueueTraits type. */ @@ -150,14 +134,14 @@ class Queue index = _queue.writeNext(); diff --git a/libs/bsw/middleware/include/middleware/queue/QueueBase.h b/libs/bsw/middleware/include/middleware/queue/QueueBase.h index 0cf3ad78d3b..ef1b879e908 100644 --- a/libs/bsw/middleware/include/middleware/queue/QueueBase.h +++ b/libs/bsw/middleware/include/middleware/queue/QueueBase.h @@ -13,7 +13,7 @@ namespace queue { /** - * \brief A struct that aggregates a series of statistics values related to queues. + * A struct that aggregates a series of statistics values related to queues. * */ struct QueueStats @@ -31,8 +31,8 @@ struct QueueStats }; /** - * \brief Base class for a multi-producer single-consumer queue, that is based on a circular buffer - * implementation. \details This class contains a sent_ and received_ attributes which represent + * Base class for a multi-producer single-consumer queue, that is based on a circular buffer + * implementation. This class contains a sent_ and received_ attributes which represent * writing and reading cursors, which will always be in the range [0, 2*maxSize[, where maxSize is * an attribute that is initialized by the init method, that represents the queue's maximum number * of elements. This way there are two distinct constellations where sent_ and received_ point to @@ -44,32 +44,20 @@ struct QueueStats class QueueBase { public: - /** - * \brief Get a constant reference to the current queue statistics. - * - * \return const QueueStats& - */ + /** Returns a const reference to the queue statistics. */ QueueStats const& getStats() const { return _stats; } - /** - * \brief Get a reference to the current queue statistics. - * - * \return QueueStats& - */ + /** Returns a reference to the queue statistics. */ QueueStats& getStats() { return _stats; } /** - * \brief Resets the queues statistics. + * Resets the queues statistics. * \remark Must be protected with ECU mutex from caller, to ensure concistency. * */ void resetStats() { _stats = QueueStats(); } - /** - * \brief Get the current size of the queue. - * - * \return constexpr uint32_t - */ + /** Returns the current number of elements in the queue. */ uint32_t size() const { // Volatile is needed to to make sure the read is not optimized away when new elements have @@ -78,11 +66,7 @@ class QueueBase return (txPos >= _received) ? (txPos - _received) : (txPos + (2U * _maxSize)) - _received; } - /** - * \brief Check if the queue is full. - * - * \return true if full, otherwise false. - */ + /** Returns true if the queue is full, false otherwise. */ bool isFull() const { // Volatile is needed to make sure the read is not optimized away when full() is called in a @@ -92,11 +76,7 @@ class QueueBase == ((static_cast(_received) + _maxSize) % (2U * _maxSize))); } - /** - * \brief Check if the queue is empty. - * - * \return true if empty, otherwise false. - */ + /** Returns true if the queue is empty, false otherwise. */ bool isEmpty() const { // Volatile is needed to prevent `isEmpty()` from returning `true` when in fact new elements @@ -105,8 +85,8 @@ class QueueBase } /** - * \brief Update some of the statistic values of the queue, like the max fill rate and the - * processingCounter. \details This method is useful to be called before processing the queue + * Update some of the statistic values of the queue, like the max fill rate and the + * processingCounter. This method is useful to be called before processing the queue * elements (reading and advancing). \remark A mutex is not needed here, since this will only be * called by a unique consumer. * @@ -144,7 +124,7 @@ class QueueBase QueueBase() {} /** - * \brief Init method which needs to be called before doing any work with the queue. + * Init method which needs to be called before doing any work with the queue. * * * \param maxSize the maximum number of elements inside the queue. @@ -166,24 +146,13 @@ class QueueBase _stats.maxFillRate = 0U; } - /** - * \brief Get the value of received_ attribute. - * - * \return uint32_t the value of the reading cursor. - */ + /** Returns the value of the reading cursor. */ uint32_t getReceived() const { return _received; } - /** - * \brief Get the value of sent_ attribute - * - * \return uint32_t the value of the writing cursor - */ + /** Returns the value of the writing cursor. */ uint32_t getSent() const { return _sent; } - /** - * \brief Advance the reading cursor. - * - */ + /** Advances the reading cursor. */ void advanceReceived() { _received = (_received + 1U) % (2U * _maxSize); @@ -191,7 +160,7 @@ class QueueBase } /** - * \brief Updates the writing cursor to the next element in the buffer. + * Updates the writing cursor to the next element in the buffer. * * \return etl::optional */ 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..95478da5639 --- /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 +{ +/** + * Get the current system time in milliseconds. + * 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(); + +/** + * Get the current system time in microseconds. + * 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..d896ce1d55a --- /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/ITimeoutHandler.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(ITimeoutHandler& transceiver) +{ + static_cast((ClusterConnectionBase::getConfiguration())) + .registerTimeoutTransceiver(transceiver); +} + +void ClusterConnectionTimeoutBase::unregisterTimeoutTransceiver(ITimeoutHandler& 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..296dd978791 --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/DatabaseManipulator.cpp @@ -0,0 +1,352 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/DatabaseManipulator.h" + +#include +#include + +#include +#include +#include + +#include "middleware/concurrency/LockStrategies.h" +#include "middleware/core/ProxyBase.h" +#include "middleware/core/SkeletonBase.h" +#include "middleware/core/TransceiverBase.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* containerIt = getTransceiversByServiceId(start, end, serviceId); + if (containerIt != end) + { + auto& container = *containerIt->fContainer; + auto* it = DbManipulator::findTransceiver(&proxy, container); + if (it != container.end()) + { + // order is important - vector must be reordered with new instance id! + container.erase(it); + // update instance id + proxy.setInstanceId(instanceId); + static_cast(container.emplace_back(&proxy)); + etl::sort( + container.begin(), + container.end(), + TransceiverContainer::TransceiverComparator()); + res = HRESULT::Ok; + } + else + { + if (container.full()) + { + res = HRESULT::TransceiverInitializationFailed; + } + else + { + auto const range = getTransceiversByServiceIdAndServiceInstanceId( + start, end, serviceId, instanceId); + bool addressNotFound = true; + while (addressNotFound) + { + auto const* it = etl::find_if( + range.first, + range.second, + [&containerIt](TransceiverBase const* const itrx) + { return (itrx->getAddressId() == containerIt->fActualAddress); }); + if (it == range.second) + { + addressNotFound = false; + proxy.setAddressId(containerIt->fActualAddress); + containerIt->fActualAddress++; + } + else + { + ++containerIt->fActualAddress; + } + } + proxy.setInstanceId(instanceId); + static_cast(container.emplace_back(&proxy)); + etl::sort( + container.begin(), + container.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, + TransceiverBase& transceiver, + uint16_t const serviceId) +{ + auto* containerIt = getTransceiversByServiceId(start, end, serviceId); + if (containerIt != end) + { + auto& container = *containerIt->fContainer; + auto const range = etl::equal_range( + container.cbegin(), + container.cend(), + &transceiver, + TransceiverContainer::TransceiverComparator()); + auto const* it = etl::find_if( + range.first, + range.second, + [&transceiver](TransceiverBase const* const itrx) + { return (itrx->getAddressId() == transceiver.getAddressId()); }); + if (it != container.cend()) + { + static_cast(container.erase(it)); + 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* containerIt = getTransceiversByServiceId(start, end, serviceId); + if (containerIt != end) + { + auto& container = *containerIt->fContainer; + auto* it = DbManipulator::findTransceiver(&skeleton, container); + if (it != container.end()) + { + // order is important - vector must be reordered with new instance id! + container.erase(it); + // update instance id + skeleton.setInstanceId(instanceId); + static_cast(container.emplace_back(&skeleton)); + etl::sort( + container.begin(), + container.end(), + TransceiverContainer::TransceiverComparator()); + res = HRESULT::InstanceAlreadyRegistered; + } + else + { + // if another skeleton with this serviceInstandId is registered fail + if (isSkeletonWithServiceInstanceIdRegistered(container, instanceId)) + { + res = HRESULT::SkeletonWithThisServiceIdAlreadyRegistered; + } + else + { + if (container.full()) + { + res = HRESULT::TransceiverInitializationFailed; + } + else + { + skeleton.setInstanceId(instanceId); + static_cast(container.emplace_back(&skeleton)); + etl::sort( + container.begin(), + container.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* it = etl::lower_bound( + start, + end, + TransceiverContainer{nullptr, serviceId, 0U}, + [](TransceiverContainer const& lhs, TransceiverContainer const& rhs) -> bool + { return lhs.fServiceid < rhs.fServiceid; }); + if ((it != end) && (it->fServiceid == serviceId)) + { + return it; + } + + return end; +} + +etl::pair< + etl::ivector::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* 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()); +} + +TransceiverBase* 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* 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( + TransceiverBase* const& transceiver, etl::ivector& container) +{ + auto* it = etl::lower_bound( + container.begin(), + container.end(), + transceiver, + TransceiverContainer::TransceiverComparator()); + + if ((it != container.cend()) && (*it)->getInstanceId() == transceiver->getInstanceId() + && (*it)->getAddressId() == transceiver->getAddressId()) + { + return it; + } + + 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); +} + +TransceiverBase* 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* containerIt = getTransceiversByServiceId(start, end, serviceId); + if (containerIt != end) + { + internal::DummyTransceiver const dummy(instanceId, addressId); + auto const* it = etl::lower_bound( + containerIt->fContainer->cbegin(), + containerIt->fContainer->cend(), + &dummy, + TransceiverContainer::TransceiverComparator()); + if ((it != containerIt->fContainer->cend()) + && (!TransceiverContainer::TransceiverComparator()(&dummy, *it))) + { + return *it; + } + } + 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* containerIt = getTransceiversByServiceId(start, end, serviceId); + if (containerIt != end) + { + return containerIt->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..654cf32124c --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/IClusterConnectionConfigurationBase.cpp @@ -0,0 +1,145 @@ +// 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/ITimeoutHandler.h" +#include "middleware/core/Message.h" +#include "middleware/core/TransceiverBase.h" +#include "middleware/core/types.h" + +namespace middleware +{ +namespace core +{ + +void ITimeoutConfiguration::registerTimeoutTransceiver( + ITimeoutHandler& transceiver, ::etl::ivector& timeoutTransceivers) +{ + auto const* it + = etl::find(timeoutTransceivers.cbegin(), timeoutTransceivers.cend(), &transceiver); + if (it == timeoutTransceivers.cend()) + { + if (!timeoutTransceivers.full()) + { + timeoutTransceivers.push_back(&transceiver); + } + } +} + +void ITimeoutConfiguration::unregisterTimeoutTransceiver( + ITimeoutHandler& transceiver, ::etl::ivector& timeoutTransceivers) +{ + using ETL_OR_STD::swap; + + auto* it = etl::find(timeoutTransceivers.begin(), timeoutTransceivers.end(), &transceiver); + if (it != timeoutTransceivers.cend()) + { + swap(*it, timeoutTransceivers.back()); + timeoutTransceivers.pop_back(); + } +} + +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(); + TransceiverBase* 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* 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..4cd1d7e2faf --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/ProxyBase.cpp @@ -0,0 +1,161 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/ProxyBase.h" + +#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* it = etl::find_if( + dbRange.begin(), + dbRange.end(), + [instanceId](IInstanceDatabase const* const dataBase) -> bool + { + auto const instances = dataBase->getInstanceIdsRange(); + const auto* instanceIdIt + = etl::lower_bound(instances.begin(), instances.end(), instanceId); + return ((instanceIdIt != instances.end()) && ((*instanceIdIt) == instanceId)); + }); + if (it != dbRange.end()) + { + auto const proxyCc = (*it)->getProxyConnectionsRange(); + auto 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); + + ETL_ASSERT_FAIL("Proxy cross thread violation detected."); + } + } +} + +} // 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..09ac95ef8af --- /dev/null +++ b/libs/bsw/middleware/src/middleware/core/SkeletonBase.cpp @@ -0,0 +1,191 @@ +// Copyright 2025 BMW AG + +#include "middleware/core/SkeletonBase.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 +SkeletonBase::sendMessage(Message& msg) const +{ + HRESULT res = HRESULT::ClusterIdNotFoundOrTransceiverNotRegistered; + auto 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* it = etl::find_if( + connections_.begin(), + connections_.end(), + [](IClusterConnection const* const clusConn) { return (clusConn != nullptr); }); + + if (it != connections_.end()) + { + clusterId = (*it)->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* it = etl::find_if( + dbRange.begin(), + dbRange.end(), + [instanceId](IInstanceDatabase const* const dataBase) -> bool + { + auto const instances = dataBase->getInstanceIdsRange(); + const auto* instanceIdIt + = etl::lower_bound(instances.begin(), instances.end(), instanceId); + return ((instanceIdIt != instances.end()) && ((*instanceIdIt) == instanceId)); + }); + HRESULT ret = HRESULT::TransceiverInitializationFailed; + if (it != dbRange.end()) + { + auto skeletonCc = (*it)->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::Critical, + logger::Error::SkeletonCrossThreadViolation, + getSourceClusterId(), + getServiceId(), + getInstanceId(), + initId, + currentTaskId); + + ETL_ASSERT_FAIL("Skeleton cross thread violation detected."); + } + } +} + +} // 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..e2681fe6468 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_cluster_configuration_unittest.cpp @@ -0,0 +1,526 @@ +#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::Exactly; +using testing::NiceMock; + +namespace middleware +{ +namespace core +{ +namespace test +{ + +struct MiddlewareMessageComparator +{ + 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 +, MiddlewareMessageComparator +{ + using ProxyMock::ProxyMock; + + ::middleware::core::HRESULT + onNewMessageReceived(::middleware::core::Message const& msg) override + { + return MiddlewareMessageComparator::msgReceived(msg); + } +}; + +struct SkeletonMockStoredMessage +: public SkeletonMock +, MiddlewareMessageComparator +{ + using SkeletonMock::SkeletonMock; + + ::middleware::core::HRESULT + onNewMessageReceived(::middleware::core::Message const& msg) override + { + return MiddlewareMessageComparator::msgReceived(msg); + } +}; + +struct TimeoutMock : middleware::core::ITimeoutHandler +{ + 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::TransceiverBase*, 1U> _proxyTransceiversAlloc{}; + etl::ivector<::middleware::core::TransceiverBase*>& _iProxyTransceivers{ + _proxyTransceiversAlloc}; + + etl::vector<::middleware::core::TransceiverBase*, 1U> _skeletonTransceiversAlloc{}; + etl::ivector<::middleware::core::TransceiverBase*>& _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(ITimeoutHandler& transceiver) override + { + ITimeoutConfiguration::registerTimeoutTransceiver(transceiver, _timeoutTransceiver); + } + + void unregisterTimeoutTransceiver(ITimeoutHandler& 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(ITimeoutHandler const& transceiver) + { + return _timeoutTransceiver.cend() + != etl::find(_timeoutTransceiver.cbegin(), _timeoutTransceiver.cend(), &transceiver); + } + +private: + etl::vector<::middleware::core::ITimeoutHandler*, MAX_TIMEOUT_RECEIVERS> + _timeoutTransceiverAlloc{}; + etl::ivector<::middleware::core::ITimeoutHandler*>& _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..95f3a52492e --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_connection_unittest.cpp @@ -0,0 +1,728 @@ +#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::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::ITimeoutHandler +{ + 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(ITimeoutHandler&) override { TimeoutTransceiverCounter::up(); } + + void unregisterTimeoutTransceiver(ITimeoutHandler&) 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(ITimeoutHandler&) override { TimeoutTransceiverCounter::up(); } + + void unregisterTimeoutTransceiver(ITimeoutHandler&) 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(ITimeoutHandler&) override { TimeoutTransceiverCounter::up(); } + + void unregisterTimeoutTransceiver(ITimeoutHandler&) 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..b2a5634ab8e --- /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::TransceiverBase; +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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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 + TransceiverBase 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..9ca93539d75 --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_proxy_base_unittest.cpp @@ -0,0 +1,250 @@ +#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::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_ANY_THROW({ + 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..ee8b0d6b85f --- /dev/null +++ b/libs/bsw/middleware/test/src/core/middleware_skeleton_base_unittest.cpp @@ -0,0 +1,360 @@ +#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::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_ANY_THROW({ + 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..1650be9ab3a --- /dev/null +++ b/libs/bsw/middleware/test/src/logger/DslLogger.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#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..f3f2b23ba63 --- /dev/null +++ b/libs/bsw/middleware/test/src/logger/mock/LoggerMock.cpp @@ -0,0 +1,98 @@ +#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}; +} // namespace + +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, ...) // NOLINT(cert-dcl50-cpp) +{ + va_list ap, ap_copy; + va_start(ap, format); + va_copy(ap_copy, ap); + vprintf(format, ap_copy); + va_end(ap_copy); + 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; + case 'u': + args.push_back(static_cast(va_arg(ap, unsigned int))); + continue; + default: break; + } + break; + default: 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..73b4c58b387 --- /dev/null +++ b/libs/bsw/middleware/test/src/time/mock/SystemTimerProviderMock.cpp @@ -0,0 +1,50 @@ +#include "SystemTimerProviderMock.h" + +#include "etl/error_handler.h" + +#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(); + } + + ETL_ASSERT_FAIL("SystemTimerProviderMock is not set."); +} + +uint32_t getCurrentTimeInUs() +{ + if (gSystemTimerProviderMockPtr != nullptr) + { + return gSystemTimerProviderMockPtr->getCurrentTimeInUs(); + } + + ETL_ASSERT_FAIL("SystemTimerProviderMock is not set."); +} + +} // 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