diff --git a/CMLibStorage.cmake b/CMLibStorage.cmake index 7e7142da..36796bad 100644 --- a/CMLibStorage.cmake +++ b/CMLibStorage.cmake @@ -1,3 +1,7 @@ +FIND_PACKAGE(CMLIB REQUIRED COMPONENTS CMCONF) + +CMCONF_INIT_SYSTEM(FLEET_PROTOCOL) + SET(STORAGE_LIST DEP) SET(STORAGE_LIST_DEP "https://github.com/bacpack-system/package-tracker.git") diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cfa7803..4ff6e19b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,13 +6,17 @@ FIND_PACKAGE(CMLIB REQUIRED ) -SET(BRINGAUTO_MODULE_GATEWAY_VERSION 1.3.4) +SET(BRINGAUTO_MODULE_GATEWAY_VERSION 1.4.0) + +SET(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY "DEBUG" CACHE STRING "Minimum logger verbosity level for module-gateway") CMDEF_COMPILE_DEFINITIONS( - ALL "MODULE_GATEWAY_VERSION=\"${BRINGAUTO_MODULE_GATEWAY_VERSION}\"" + ALL + "MODULE_GATEWAY_VERSION=\"${BRINGAUTO_MODULE_GATEWAY_VERSION}\"" + "MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY=\"${MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY}\"" ) SET(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMDEF_LIBRARY_INSTALL_DIR}") -SET(CMAKE_CXX_STANDARD 20) +SET(CMAKE_CXX_STANDARD 23) INCLUDE(CheckPIESupported) CHECK_PIE_SUPPORTED() @@ -43,13 +47,17 @@ SET(Protobuf_USE_STATIC_LIBS ON) FIND_PACKAGE(Boost 1.74 REQUIRED CONFIG) FIND_PACKAGE(Protobuf 3.21.12 REQUIRED) -FIND_PACKAGE(cxxopts 3.0.0 REQUIRED) -FIND_PACKAGE(nlohmann_json 3.2.0 REQUIRED) +FIND_PACKAGE(cxxopts 3.1.1 REQUIRED) +FIND_PACKAGE(nlohmann_json 3.10.5 REQUIRED) FIND_PACKAGE(PahoMqttCpp REQUIRED) FIND_PACKAGE(eclipse-paho-mqtt-c REQUIRED) FIND_PACKAGE(libbringauto_logger 2.0.0 REQUIRED) FIND_PACKAGE(fleet-protocol-interface 2.0.0 REQUIRED) FIND_PACKAGE(ZLIB 1.2.11 REQUIRED) +FIND_PACKAGE(fleet-protocol-cxx-helpers-static 1.2.0 REQUIRED) +FIND_PACKAGE(aeron 1.48.6 REQUIRED) +FIND_PACKAGE(async-function-execution-shared 1.0.0 REQUIRED) +FIND_PACKAGE(msquic CONFIG REQUIRED) FILE(GLOB_RECURSE source_files "source/*") ADD_LIBRARY(module-gateway-lib STATIC "${source_files}") @@ -67,6 +75,9 @@ TARGET_LINK_LIBRARIES(module-gateway-lib PUBLIC eclipse-paho-mqtt-c::paho-mqtt3as PahoMqttCpp::paho-mqttpp3 ZLIB::ZLIB + fleet-protocol-cxx-helpers-static::fleet-protocol-cxx-helpers-static + async-function-execution-shared::async-function-execution-shared + msquic ${CMAKE_DL_LIBS} ) diff --git a/Dockerfile b/Dockerfile index c2c9b0d1..d6cb4db2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN cmake .. -DCMAKE_BUILD_TYPE=Release -DBRINGAUTO_GET_PACKAGES_ONLY=ON FROM bringauto/cpp-build-environment:latest AS mission_module_builder -ARG MISSION_MODULE_VERSION=v1.2.12 +ARG MISSION_MODULE_VERSION=v1.2.13 # Install mission module dependencies WORKDIR /home/bringauto/modules/ diff --git a/README.md b/README.md index 3610eb2d..72904256 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,17 @@ connection is broken and as soon as the connection is up, then error aggregated - [cmlib](https://github.com/cmakelib/cmakelib) - [protobuf](https://github.com/protocolbuffers/protobuf/tree/main/src) >= v3.21.12 -- [cxxopts](https://github.com/jarro2783/cxxopts) >= v3.0.0 -- [boost](https://github.com/boostorg/boost) >= v1.74.0 -- [nlohmann-json](https://github.com/nlohmann/json) >= v3.2.0 -- [ba-logger](https://github.com/bringauto/ba-logger) >= v1.2.0 +- [cxxopts](https://github.com/jarro2783/cxxopts) >= v3.1.1 +- [boost](https://github.com/boostorg/boost) >= v1.86.0 +- [nlohmann-json](https://github.com/nlohmann/json) >= v3.10.5/ +- [pahomqtt](https://github.com/eclipse-paho/paho.mqtt.c) >= v1.3.9 +- [pahomqttcpp](https://github.com/eclipse-paho/paho.mqtt.cpp) >= v1.3.2 +- [zlib](https://github.com/madler/zlib) >= v1.2.11 +- [ba-logger](https://github.com/bringauto/ba-logger) >= v2.0.0 +- [fleet-protocol-interface](https://github.com/bringauto/fleet-protocol) >= v2.0.0 +- [fleet-protocol-cpp](https://github.com/bringauto/fleet-protocol-cpp) >= v1.1.1 +- [aeron](https://github.com/aeron-io/aeron) >= v1.48.6 +- [async-function-execution](https://github.com/bringauto/async-function-execution) >= 0.1.0 - g++ >= 10 or other compiler with c++20 support ## Build @@ -50,7 +57,7 @@ make ### Arguments -* required arguments: +* Required arguments: * `-c | --config-path=`path to json configuration file ([Configs Readme](./configs/README.md)) * All arguments: * `-h | --help` print help @@ -77,6 +84,10 @@ make * BRINGAUTO_SYSTEM_DEP=ON/OFF - DEFAULT: OFF +* MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY=DEBUG/INFO/WARNING/ERROR/CRITICAL + - DEFAULT: DEBUG + - sets the minimum logger verbosity on compile level to improve performance + * CURRENTLY UNUSED * BRINGAUTO_SAMPLES=ON/OFF diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index e004024d..ea369eff 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -3,13 +3,15 @@ SET(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH FALSE) BA_PACKAGE_LIBRARY(protobuf v4.21.12) BA_PACKAGE_LIBRARY(fleet-protocol-interface v2.0.0 NO_DEBUG ON) BA_PACKAGE_LIBRARY(nlohmann-json v3.10.5 NO_DEBUG ON) -BA_PACKAGE_LIBRARY(cxxopts v3.0.5 NO_DEBUG ON) +BA_PACKAGE_LIBRARY(cxxopts v3.1.1 NO_DEBUG ON) BA_PACKAGE_LIBRARY(boost v1.86.0) -BA_PACKAGE_LIBRARY(ba-logger v2.0.0 -) +BA_PACKAGE_LIBRARY(ba-logger v2.0.0) BA_PACKAGE_LIBRARY(pahomqttc v1.3.9) BA_PACKAGE_LIBRARY(pahomqttcpp v1.3.2) BA_PACKAGE_LIBRARY(zlib v1.2.11 OUTPUT_PATH_VAR ZLIB_DIR) +BA_PACKAGE_LIBRARY(fleet-protocol-cpp v1.2.0) +BA_PACKAGE_LIBRARY(aeron v1.48.6) +BA_PACKAGE_LIBRARY(async-function-execution v1.0.0) IF (BRINGAUTO_TESTS) BA_PACKAGE_LIBRARY(gtest v1.12.1) diff --git a/include/bringauto/common_utils/EnumUtils.hpp b/include/bringauto/common_utils/EnumUtils.hpp index f0f1fc1d..56994d61 100644 --- a/include/bringauto/common_utils/EnumUtils.hpp +++ b/include/bringauto/common_utils/EnumUtils.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include @@ -24,10 +26,22 @@ class EnumUtils { /** * @brief Converts protocol type to string * - * @param toEnum structures::ProtocolType - * @return std::string + * @param toString structures::ProtocolType + * @return std::string_view */ - static std::string protocolTypeToString(structures::ProtocolType toString); + static constexpr std::string_view protocolTypeToString(structures::ProtocolType toString) { + switch(toString) { + case structures::ProtocolType::MQTT: + return settings::Constants::MQTT; + case structures::ProtocolType::QUIC: + return settings::Constants::QUIC; + case structures::ProtocolType::DUMMY: + return settings::Constants::DUMMY; + case structures::ProtocolType::INVALID: + default: + return ""; + } + }; /** * @brief Converts string to logger verbosity @@ -41,10 +55,50 @@ class EnumUtils { * @brief Converts logger verbosity to string * * @param verbosity logging::LoggerVerbosity - * @return std::string + * @return std::string_view */ - static std::string loggerVerbosityToString(logging::LoggerVerbosity verbosity); + static constexpr std::string_view loggerVerbosityToString(logging::LoggerVerbosity verbosity) { + switch(verbosity) { + case logging::LoggerVerbosity::Debug: + return settings::Constants::LOG_LEVEL_DEBUG; + case logging::LoggerVerbosity::Info: + return settings::Constants::LOG_LEVEL_INFO; + case logging::LoggerVerbosity::Warning: + return settings::Constants::LOG_LEVEL_WARNING; + case logging::LoggerVerbosity::Error: + return settings::Constants::LOG_LEVEL_ERROR; + case logging::LoggerVerbosity::Critical: + return settings::Constants::LOG_LEVEL_CRITICAL; + default: + return settings::Constants::LOG_LEVEL_INVALID; + } + }; -}; + /** + * @brief Converts connection state to string + * + * @param state external_client::connection::ConnectionState + * @return std::string_view + */ + static constexpr std::string_view connectionStateToString( + external_client::connection::ConnectionState state + ) { + using enum external_client::connection::ConnectionState; + switch (state) { + case NOT_INITIALIZED: + return settings::Constants::LOG_CONNECTION_STATE_NOT_INITIALIZED; + case NOT_CONNECTED: + return settings::Constants::LOG_CONNECTION_STATE_NOT_CONNECTED; + case CONNECTING: + return settings::Constants::LOG_CONNECTION_STATE_CONNECTING; + case CONNECTED: + return settings::Constants::LOG_CONNECTION_STATE_CONNECTED; + case CLOSING: + return settings::Constants::LOG_CONNECTION_STATE_CLOSING; + default: + return settings::Constants::LOG_UNKNOWN; + } + } + }; } diff --git a/include/bringauto/common_utils/ProtobufUtils.hpp b/include/bringauto/common_utils/ProtobufUtils.hpp index 5c709b5d..c124689a 100644 --- a/include/bringauto/common_utils/ProtobufUtils.hpp +++ b/include/bringauto/common_utils/ProtobufUtils.hpp @@ -5,8 +5,6 @@ #include #include -#include -#include @@ -37,7 +35,7 @@ class ProtobufUtils { const InternalProtocol::DeviceConnectResponse_ResponseType &resType); /** - * @brief Create a Internal Server Command message + * @brief Create an Internal Server Command message * * @param device Protobuf message Device * @param command command data @@ -47,7 +45,7 @@ class ProtobufUtils { const modules::Buffer &command); /** - * @brief Create a Internal Client Status message + * @brief Create an Internal Client Status message * * @param device Protobuf message Device * @param status status data @@ -67,7 +65,7 @@ class ProtobufUtils { const modules::Buffer &status); /** - * @brief Create a External Client Connect message + * @brief Create an External Client Connect message * * @param sessionId session identification * @param company name of the company @@ -81,7 +79,7 @@ class ProtobufUtils { const std::vector &devices); /** - * @brief Create a External Client Status message + * @brief Create an External Client Status message * * @param sessionId session identification * @param deviceState state of the device @@ -97,7 +95,7 @@ class ProtobufUtils { const modules::Buffer &errorMessage = modules::Buffer {}); /** - * @brief Create a External Client Command Response object + * @brief Create an External Client Command Response object * * @param sessionId session identification * @param type command response message type @@ -114,7 +112,7 @@ class ProtobufUtils { * @param status status to be copied * @param buffer buffer to copy to */ - static void copyStatusToBuffer(const InternalProtocol::DeviceStatus &status, modules::Buffer &buffer); + static void copyStatusToBuffer(const InternalProtocol::DeviceStatus &status, const modules::Buffer &buffer); /** * @brief Copy command data from DeviceCommand to a Buffer @@ -122,7 +120,7 @@ class ProtobufUtils { * @param command command to be copied * @param buffer buffer to copy to */ - static void copyCommandToBuffer(const InternalProtocol::DeviceCommand &command, modules::Buffer &buffer); + static void copyCommandToBuffer(const InternalProtocol::DeviceCommand &command, const modules::Buffer &buffer); }; } diff --git a/include/bringauto/external_client/ErrorAggregator.hpp b/include/bringauto/external_client/ErrorAggregator.hpp index e913d325..e72fd82c 100644 --- a/include/bringauto/external_client/ErrorAggregator.hpp +++ b/include/bringauto/external_client/ErrorAggregator.hpp @@ -1,13 +1,8 @@ #pragma once -#include - -#include -#include - +#include #include -#include #include #include @@ -30,7 +25,7 @@ class ErrorAggregator { * @return OK if initialization was successful * @return NOT_OK if an error occurred */ - int init_error_aggregator(const std::shared_ptr &library); + int init_error_aggregator(const std::shared_ptr &library); /** * @short Clean up. @@ -108,7 +103,7 @@ class ErrorAggregator { * * @see fleet-protocol/lib/common_headers/include/device_management.h */ - int is_device_type_supported(unsigned int device_type); + int is_device_type_supported(unsigned int device_type) const; private: struct DeviceState { @@ -116,7 +111,7 @@ class ErrorAggregator { modules::Buffer lastStatus {}; }; - std::shared_ptr module_ {}; + std::shared_ptr module_ {}; /** * @brief Map of devices states, key is device identification converted to string diff --git a/include/bringauto/external_client/ExternalClient.hpp b/include/bringauto/external_client/ExternalClient.hpp index 0879964d..fb827d7a 100644 --- a/include/bringauto/external_client/ExternalClient.hpp +++ b/include/bringauto/external_client/ExternalClient.hpp @@ -20,9 +20,9 @@ namespace bringauto::external_client { class ExternalClient { public: - ExternalClient(std::shared_ptr &context, + ExternalClient(const std::shared_ptr &context, structures::ModuleLibrary &moduleLibrary, - std::shared_ptr> &toExternalQueue); + const std::shared_ptr> &toExternalQueue); /** * @brief Initialize connections, error aggregators @@ -54,7 +54,7 @@ class ExternalClient { void handleAggregatedMessages(); /** - * @brief Handle commands messages from from an external server + * @brief Handle commands messages from an external server */ void handleCommands(); @@ -68,10 +68,10 @@ class ExternalClient { /** * @brief Send aggregated status message to the external server * - * @param deviceStatus aggregated status message ready to send + * @param internalMessage aggregated status message ready to send * @return reconnect expected if true, reconnect not expected if false */ - bool sendStatus(const structures::InternalClientMessage &deviceStatus); + bool sendStatus(const structures::InternalClientMessage &internalMessage); bool insideConnectSequence_ { false }; @@ -79,7 +79,7 @@ class ExternalClient { * @brief Map of external connections, key is number from settings * - map is needed because of the possibility of multiple modules connected to one external server */ - std::map> externalConnectionMap_ {}; + std::unordered_map> externalConnectionMap_ {}; /// List of external connections, each device can have its own connection or multiple devices can share one connection std::list externalConnectionsList_ {}; /// Queue for messages from module handler to external client to be sent to external server diff --git a/include/bringauto/external_client/connection/ConnectionState.hpp b/include/bringauto/external_client/connection/ConnectionState.hpp index 91ec13f1..375ca30d 100644 --- a/include/bringauto/external_client/connection/ConnectionState.hpp +++ b/include/bringauto/external_client/connection/ConnectionState.hpp @@ -18,6 +18,10 @@ enum class ConnectionState { /** * CONNECTED - Client is connected to the External server */ - CONNECTED + CONNECTED, + /** + * CLOSING - Client closing connection to the External server + */ + CLOSING }; } diff --git a/include/bringauto/external_client/connection/ExternalConnection.hpp b/include/bringauto/external_client/connection/ExternalConnection.hpp index 0b19a4df..66c108b6 100644 --- a/include/bringauto/external_client/connection/ExternalConnection.hpp +++ b/include/bringauto/external_client/connection/ExternalConnection.hpp @@ -11,8 +11,6 @@ #include #include -#include - #include #include #include @@ -72,9 +70,10 @@ class ExternalConnection { * @brief Force aggregation on all devices in all modules that the connection services. * Is used before the connect sequence to assure that every device has an available status to be sent * + * @param connectedDevices * @return number of devices */ - std::vector forceAggregationOnAllDevices(std::vector connectedDevices); + std::vector forceAggregationOnAllDevices(const std::vector &connectedDevices); /** * @brief Fill error aggregator with not acknowledged status messages @@ -104,11 +103,11 @@ class ExternalConnection { * @brief Check if module type is supported * * @param moduleNum module type number - * @return true if moudle type is supported otherwise false + * @return true if module type is supported otherwise false */ - bool isModuleSupported(int moduleNum); + bool isModuleSupported(int moduleNum) const; - std::vector getAllConnectedDevices(); + std::vector getAllConnectedDevices() const; private: @@ -172,7 +171,7 @@ class ExternalConnection { /// Class handling sent messages - timers, not acknowledged statuses etc. std::unique_ptr sentMessagesHandler_ {}; /// @brief Map of error aggregators, key is module number - std::map errorAggregators_ {}; + std::unordered_map errorAggregators_ {}; /// Queue of commands received from external server, commands are processed by aggregator std::shared_ptr > commandQueue_ {}; diff --git a/include/bringauto/external_client/connection/communication/DummyCommunication.hpp b/include/bringauto/external_client/connection/communication/DummyCommunication.hpp new file mode 100644 index 00000000..83bf3923 --- /dev/null +++ b/include/bringauto/external_client/connection/communication/DummyCommunication.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + + + +namespace bringauto::external_client::connection::communication { + +/** + * @brief Dummy communication channel for testing purposes. + * Does not establish any real connection, just simulates it. + * receiveMessage always returns nullptr. + * Initialization and closing connection only changes the debug logs of sendMessage. + */ +class DummyCommunication: public ICommunicationChannel { +public: + explicit DummyCommunication(const structures::ExternalConnectionSettings &settings); + + ~DummyCommunication() override; + + void initializeConnection() override; + + bool sendMessage(ExternalProtocol::ExternalClient *message) override; + + std::shared_ptr receiveMessage() override; + + void closeConnection() override; + +private: + /// Flag to indicate if the fake connection is established + bool isConnected_ { false }; +}; + +} diff --git a/include/bringauto/external_client/connection/communication/ICommunicationChannel.hpp b/include/bringauto/external_client/connection/communication/ICommunicationChannel.hpp index 1d952c05..1c54ca4c 100644 --- a/include/bringauto/external_client/connection/communication/ICommunicationChannel.hpp +++ b/include/bringauto/external_client/connection/communication/ICommunicationChannel.hpp @@ -1,11 +1,10 @@ #pragma once #include +#include #include -#include - namespace bringauto::external_client::connection::communication { @@ -16,7 +15,7 @@ namespace bringauto::external_client::connection::communication { */ class ICommunicationChannel { public: - explicit ICommunicationChannel(const structures::ExternalConnectionSettings &settings): settings_ { settings } {}; + explicit ICommunicationChannel(structures::ExternalConnectionSettings settings): settings_ {std::move( settings )} {}; virtual ~ICommunicationChannel() = default; diff --git a/include/bringauto/external_client/connection/communication/MqttCommunication.hpp b/include/bringauto/external_client/connection/communication/MqttCommunication.hpp index 5b4ecca4..31c76650 100644 --- a/include/bringauto/external_client/connection/communication/MqttCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/MqttCommunication.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp new file mode 100644 index 00000000..aa286835 --- /dev/null +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -0,0 +1,338 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include + + +namespace bringauto::external_client::connection::communication { + class QuicCommunication : public ICommunicationChannel { + public: + explicit QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, + const std::string &vehicleName); + + ~QuicCommunication() override; + + /** + * @brief Initializes a QUIC connection to the server. + * + * Attempts to establish a new QUIC connection. + * It first atomically verifies that the current connection state is + * NOT_CONNECTED and transitions it to CONNECTING in order to prevent + * concurrent connection attempts. + * + * After the state transition, it opens a QUIC connection handle and + * starts the connection using the configured server address, port, + * and QUIC configuration. + * + * Any failures during the connection open or start process are logged. + */ + void initializeConnection() override; + + /** + * @brief Enqueues an outgoing message to be sent over the QUIC connection. + * + * Creates a shared copy of the provided ExternalClient message + * and pushes it into the outbound message queue in a thread-safe manner. + * After enqueuing, it notifies the sender thread via a condition variable + * that a new message is available for sending. + * + * @param message Pointer to the message that should be sent. + * @return true Always returns true to indicate the message was successfully enqueued. + */ + bool sendMessage(ExternalProtocol::ExternalClient *message) override; + + /** + * @brief Receives an incoming message from the QUIC connection. + * + * Waits for an incoming message to appear in the inbound + * queue or for the connection state to change from CONNECTED. + * The wait is bounded by a configurable timeout. + * + * If the wait times out, the connection is no longer in the CONNECTED + * state, or no message is available, the function returns nullptr. + * Otherwise, it retrieves and removes the next message from the inbound + * queue and returns it. + * + * @return A shared pointer to the received ExternalServer message, + * or nullptr if no message is available or the connection is not active. + */ + std::shared_ptr receiveMessage() override; + + /** + * @brief Initiates a graceful shutdown of the QUIC connection. + * + * Requests an orderly shutdown of the active QUIC connection. + * If no connection is currently established, the function returns immediately. + * + * The shutdown is performed asynchronously. Completion is signaled via the + * QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE event, which is handled in + * the connectionCallback. + */ + void closeConnection() override; + + private: + /// Pointer to the MsQuic API function table + const QUIC_API_TABLE *quic_{nullptr}; + /// QUIC registration handle associated with the application + HQUIC registration_{nullptr}; + /// QUIC configuration handle (ALPN, credentials, transport settings) + HQUIC config_{nullptr}; + /// Active QUIC connection handle + HQUIC connection_{nullptr}; + /// Application-Layer Protocol Negotiation (ALPN) string + std::string alpn_; + /// QUIC buffer wrapping the ALPN string + QUIC_BUFFER alpnBuffer_{}; + + /// Path to the client certificate file + std::string certFile_; + /// Path to the client private key file + std::string keyFile_; + /// Path to the CA certificate file + std::string caFile_; + + /// Atomic state of the connection used for synchronization across threads + std::atomic connectionState_{ConnectionState::NOT_CONNECTED}; + + /// @name Inbound (peer → this) + /// @{ + /// Queue of incoming messages received from the peer + std::queue > inboundQueue_; + /// Mutex protecting access to the inbound message queue + std::mutex inboundMutex_; + /// Condition variable for signaling inbound message availability + std::condition_variable inboundCv_; + /// @} + + /// @name Outbound (this → peer) + /// @{ + /// Queue of outgoing messages to be sent to the peer + std::queue > outboundQueue_; + /// Mutex protecting access to the outbound message queue + std::mutex outboundMutex_; + /// Condition variable for signaling outbound message availability + std::condition_variable outboundCv_; + /// Dedicated sender thread responsible for transmitting outbound messages + std::jthread senderThread_; + /// @} + + /** + * @brief Owns memory for a single MsQuic StreamSend operation. + * + * SendBuffer wraps a QUIC_BUFFER together with its backing storage. + * The memory must remain valid until MsQuic signals + * QUIC_STREAM_EVENT_SEND_COMPLETE, at which point it can be safely freed. + * + * Instances of this struct are typically allocated on the heap and passed + * to MsQuic via the StreamSend ClientContext pointer. + */ + struct SendBuffer { + QUIC_BUFFER buffer{}; + std::string storage; + + /** + * @brief Constructs a SendBuffer with zero-initialized storage. + * + * Allocates storage of the given size, fills it with zero bytes, + * and initializes the QUIC_BUFFER to point to this storage. + * + * @param size Number of bytes to allocate for the send buffer. + */ + explicit SendBuffer(size_t size) + : storage(size, '\0') { + buffer.Length = static_cast(storage.size()); + buffer.Buffer = reinterpret_cast(storage.data()); + } + }; + + /** + * @brief Loads and initializes the MsQuic API. + * + * Initializes the MsQuic library and retrieves the + * QUIC API function table. The resulting table is stored for later + * use when creating registrations, configurations, and connections. + * + * If the initialization fails, an error is logged. + */ + void loadMsQuic(); + + /** + * @brief Initializes a QUIC registration. + * + * Creates a QUIC registration with the specified application name and + * a low-latency execution profile. The registration is required for + * creating QUIC configurations and connections. + * + * If registration creation fails, an error is logged. + */ + void initRegistration(); + + /** + * @brief Initializes the QUIC configuration and loads client credentials. + * + * Opens a QUIC configuration using the configured ALPN and default QUIC + * transport settings. Client TLS credentials are then set up using a + * certificate file, private key file, and CA certificate file. + * + * If configuration creation or credential loading fails, an error is logged. + */ + void initConfiguration(); + + /** + * @brief Opens a QUIC configuration. + * + * Creates a QUIC configuration associated with the current registration, + * configured ALPN, and optional transport settings. + * + * If settings are not provided, default QUIC settings are used. + * On failure, an error is logged. + */ + void configurationOpen(); + + /** + * @brief Loads TLS credentials into the QUIC configuration. + * + * Loads client-side TLS credentials into the active QUIC configuration. + * The credentials define the certificate, private key, and CA certificate + * used for secure communication. + * + * If credential loading fails, an error is logged. + * + * @param credential Pointer to the QUIC credential configuration. + */ + void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; + + /** + * @brief Handles a successfully decoded incoming message. + * + * Pushes the decoded ExternalServer message into the inbound queue + * in a thread-safe manner and notifies the receiver thread that a + * new message is available. + * + * @param msg Decoded message received from the peer. + */ + void onMessageDecoded(std::shared_ptr msg); + + /** + * @brief Sends a message to the peer using a QUIC stream. + * + * Opens a new QUIC stream on the active connection and serializes the + * provided ExternalClient message into a byte buffer. + * The message is sent using a single StreamSend call with START and FIN + * flags, effectively opening, sending, and closing the stream. + * + * The allocated send buffer is released asynchronously in the + * QUIC_STREAM_EVENT_SEND_COMPLETE callback. + * + * @param message Message to be sent to the peer. + */ + void sendViaQuicStream(const ExternalProtocol::ExternalClient& message); + + /** + * @brief Closes the active QUIC configuration. + */ + void closeConfiguration(); + + /** + * @brief Closes the QUIC registration. + */ + void closeRegistration(); + + /** + * @brief Closes the MsQuic API and releases associated resources. + */ + void closeMsQuic(); + + /** + * @brief Stops the QUIC communication and releases all resources. + * + * Initiates connection shutdown and closes the QUIC configuration, + * registration, and MsQuic API in the correct order. + * All waiting sender and receiver threads are unblocked by notifying + * the associated condition variables. + */ + void stop(); + + /** + * @brief Handles QUIC connection-level events. + * + * Processes connection lifecycle events reported by MsQuic, including + * successful connection establishment, peer-initiated shutdown, and + * shutdown completion. + * + * All QUIC_CONNECTION_EVENT cases are documented at + * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_CONNECTION_EVENT.html + * + * @param connection QUIC connection handle. + * @param context User-defined context pointer (QuicCommunication instance). + * @param event Connection event information provided by MsQuic. + * @return QUIC_STATUS_SUCCESS to indicate successful event handling. + */ + static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); + + /** + * @brief Handles QUIC stream-level events. + * + * Processes stream events reported by MsQuic, including data reception, + * send completion, stream startup, and shutdown notifications. + * + * All QUIC_STREAM_EVENT cases are documented at + * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_STREAM_EVENT.html + * + * @param stream QUIC stream handle associated with the event. + * @param context User-defined context pointer (QuicCommunication instance). + * @param event Stream event information provided by MsQuic. + * @return QUIC_STATUS_SUCCESS to indicate successful event handling. + */ + static QUIC_STATUS QUIC_API streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); + + /** + * @brief Sender thread main loop for outbound messages. + * + * Waits for outbound messages while the connection is in the CONNECTED state. + * Messages are dequeued and sent over individual QUIC streams. + * + * If sending fails, the message is re-enqueued for a later retry. + * The loop terminates when the connection leaves the CONNECTED state. + */ + void senderLoop(); + + /** + * @brief Retrieves the QUIC stream identifier for the given stream handle. + * + * Queries MsQuic for the stream ID associated with the provided HQUIC stream. + * If the parameter query fails, an empty optional is returned. + * + * @param stream Valid QUIC stream handle. + * @return Stream identifier on success, or std::nullopt if the query fails. + */ + std::optional getStreamId(HQUIC stream); + + /** + * @brief Retrieves a protocol setting value as a plain string. + * + * Extracts a value from ExternalConnectionSettings::protocolSettings and + * transparently handles values stored as JSON-encoded strings. + * + * Allows uniform access to protocol settings regardless of whether + * they were stored as plain strings or JSON-serialized values. + * + * @param settings External connection settings containing protocolSettings. + * @param key Key identifying the protocol setting. + * @return Plain string value suitable for direct use (e.g. file paths). + * + * @throws std::out_of_range if the key is not present in protocolSettings. + */ + static std::string getProtocolSettingsString( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ); + }; +} diff --git a/include/bringauto/external_client/connection/messages/NotAckedStatus.hpp b/include/bringauto/external_client/connection/messages/NotAckedStatus.hpp index b3fef09f..f2706878 100644 --- a/include/bringauto/external_client/connection/messages/NotAckedStatus.hpp +++ b/include/bringauto/external_client/connection/messages/NotAckedStatus.hpp @@ -2,7 +2,6 @@ #include #include - #include @@ -14,8 +13,8 @@ namespace bringauto::external_client::connection::messages { */ class NotAckedStatus { public: - NotAckedStatus(const ExternalProtocol::Status &status, boost::asio::io_context &timerContext, - std::atomic &responseHandled, std::mutex &responseHandledMutex): status_ { status }, + NotAckedStatus(ExternalProtocol::Status status, boost::asio::io_context &timerContext, + std::atomic &responseHandled, std::mutex &responseHandledMutex): status_ {std::move( status )}, timer_ { timerContext }, responseHandled_ { responseHandled }, @@ -54,7 +53,7 @@ class NotAckedStatus { * * @param endConnectionFunc function which is called when status does not get response */ - void timeoutHandler(const std::function &endConnectionFunc); + void timeoutHandler(const std::function &endConnectionFunc) const; /// Status message that was not acknowledged yet ExternalProtocol::Status status_ {}; /// Timer for checking if status got response diff --git a/include/bringauto/external_client/connection/messages/SentMessagesHandler.hpp b/include/bringauto/external_client/connection/messages/SentMessagesHandler.hpp index 7ac7cdc0..cf12b59d 100644 --- a/include/bringauto/external_client/connection/messages/SentMessagesHandler.hpp +++ b/include/bringauto/external_client/connection/messages/SentMessagesHandler.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include @@ -91,7 +90,7 @@ class SentMessagesHandler { /** * @brief returns message counter of status_response - * @param status + * @param statusResponse * @return counter */ [[nodiscard]] static u_int32_t getStatusResponseCounter(const ExternalProtocol::StatusResponse &statusResponse); diff --git a/include/bringauto/internal_server/InternalServer.hpp b/include/bringauto/internal_server/InternalServer.hpp index d0d299b5..08b1aeaf 100644 --- a/include/bringauto/internal_server/InternalServer.hpp +++ b/include/bringauto/internal_server/InternalServer.hpp @@ -15,19 +15,19 @@ namespace bringauto::internal_server { /** - * Server implements internal protocol. Serves as link between Module handler and Internal client.. + * Server implements internal protocol. Serves as link between Module handler and Internal client. * It accepts connections from multiple Internal clients. * Receives messages on these connections. The message needs to begin with 4 bytes header. - * Header's format is 32 bit unsigned int with little endian endianity. + * Header's format is 32 bit unsigned int with little endian endianness. * Header represents size of the remaining part of the message. - * Messaged is send thru queue to ModuleHandler, and when answer is given resends it to Internal client. + * Messaged is send through queue to ModuleHandler, and when answer is given resends it to Internal client. */ class InternalServer { public: /** * @brief Constructs Internal Server. - * @param settings shares context with Internal Server + * @param context shares context with Internal Server * @param fromInternalQueue queue for sending data from Server to Module Handler * @param toInternalQueue queue for sending data from Module Handler to Server */ @@ -64,7 +64,7 @@ class InternalServer { /** * @brief Asynchronously receives data. - * @param connection connection that data are being sent thru + * @param connection connection that data are being sent through */ void addAsyncReceive(const std::shared_ptr &connection); @@ -79,7 +79,7 @@ class InternalServer { /** * @brief Processes buffer data and once message is complete calls handleMessage(...) - * @param connection conneection with context holding received and processed data + * @param connection connection with context holding received and processed data * @param bytesTransferred size of received data * @param bufferOffset offset of buffer where data starts * @return true if data and whole message is correct in context to fleet protocol @@ -97,16 +97,16 @@ class InternalServer { bool handleMessage(const std::shared_ptr &connection); /** - * @brief Checks if status is valid, if it is sends message to Module Handler. + * @brief Checks if status is valid. If it is, the message is sent to Module Handler. * @param connection connection with information about validity * @param client message to be checked and sent * @return true if status is valid. */ bool handleStatus(const std::shared_ptr &connection, - const InternalProtocol::InternalClient &client); + const InternalProtocol::InternalClient &client) const; /** - * @brief Checks for existance of this device and possibly its priority and calls matching method. + * @brief Checks for existence of this device and possibly its priority and calls matching method. * @param connection connection holding data * @param client message to be checked * @return true if connect is valid. @@ -132,7 +132,7 @@ class InternalServer { /** * @brief Sends response to InternalClient, that device is already connected and with higher priority. - * @param connection connection response will be sent thru + * @param connection connection response will be sent through * @param connect message containing data for response message * @param deviceId unique device identification */ @@ -142,7 +142,7 @@ class InternalServer { /** * @brief Sends response to InternalClient, that device is already connected and with same priority. - * @param connection connection response will be sent thru + * @param connection connection response will be sent through * @param connect message containing data for response message * @param deviceId unique device identification */ @@ -153,18 +153,18 @@ class InternalServer { /** * Ends all operations of previous connection using same device, * closes its socket, then replaces it in map with new connection. - * Afterwards sends new connection message to Module Handler. - * @param connection new connection to replace the old one + * Afterward sends new connection message to Module Handler. + * @param newConnection new connection to replace the old one * @param connect message to be sent * @param deviceId unique device identification */ - void changeConnection(const std::shared_ptr &connection, + void changeConnection(const std::shared_ptr &newConnection, const InternalProtocol::InternalClient &connect, const structures::DeviceIdentification &deviceId); /** * @brief Writes messages to Internal client. - * @param connection connection message will be sent thru + * @param connection connection message will be sent through * @param message message to be sent * @return true if writes are successful */ @@ -178,15 +178,15 @@ class InternalServer { void removeConnFromMap(const std::shared_ptr &connection); /** - * Periodicly checks for new messages received from module handler thru queue. + * Periodically checks for new messages received from module handler through queue. * If message is received calls validatesResponse(...). * Runs until stop() is called. */ void listenToQueue(); /** - * Validates if message belongs to any active connection, if it does resends the message to InternalClient, - * then notifies waiting connection. + * Validates if message belongs to any active connection. If it does, the message is resent to InternalClient, + * then the waiting connection is notified. * @param message message to be validated */ void validateResponse(const InternalProtocol::InternalServer &message); diff --git a/include/bringauto/modules/Buffer.hpp b/include/bringauto/modules/Buffer.hpp index cd0edfd4..235bf26e 100644 --- a/include/bringauto/modules/Buffer.hpp +++ b/include/bringauto/modules/Buffer.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include #include -#include #include @@ -20,7 +18,8 @@ namespace bringauto::modules { */ struct Buffer final { - friend class ModuleManagerLibraryHandler; + friend class ModuleManagerLibraryHandlerLocal; + friend class ModuleManagerLibraryHandlerAsync; Buffer() = default; Buffer(const Buffer& buff) = default; @@ -94,7 +93,7 @@ struct Buffer final { } /** - * Underlyig data type used to hold information by shared_ptr. + * Underlying data type used to hold information by shared_ptr. * Data type in ::buffer struct is a type void*. It is not viable * to use void* in C++ --> use 1-byte data type. */ diff --git a/include/bringauto/modules/IModuleManagerLibraryHandler.hpp b/include/bringauto/modules/IModuleManagerLibraryHandler.hpp new file mode 100644 index 00000000..c1254063 --- /dev/null +++ b/include/bringauto/modules/IModuleManagerLibraryHandler.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include + + + +namespace bringauto::modules { + +/** + * @brief Class used to load and handle library created by module maintainer + */ +class IModuleManagerLibraryHandler { +public: + explicit IModuleManagerLibraryHandler() = default; + + virtual ~IModuleManagerLibraryHandler() = default; + + /** + * @brief Load library created by a module maintainer + * + * @param path path to the library + */ + virtual void loadLibrary(const std::filesystem::path &path) = 0; + + virtual int getModuleNumber() = 0; + + virtual int isDeviceTypeSupported(unsigned int device_type) = 0; + + virtual int sendStatusCondition(const Buffer ¤t_status, const Buffer &new_status, unsigned int device_type) = 0; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + virtual int generateCommand(Buffer &generated_command, const Buffer &new_status, + const Buffer ¤t_status, const Buffer ¤t_command, + unsigned int device_type) = 0; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + virtual int aggregateStatus(Buffer &aggregated_status, const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) = 0; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + virtual int aggregateError(Buffer &error_message, const Buffer ¤t_error_message, const Buffer &status, + unsigned int device_type) = 0; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + virtual int generateFirstCommand(Buffer &default_command, unsigned int device_type) = 0; + + virtual int statusDataValid(const Buffer &status, unsigned int device_type) = 0; + + virtual int commandDataValid(const Buffer &command, unsigned int device_type) = 0; + + /** + * @brief Constructs a buffer with the given size + * + * @param size size of the buffer + * @return a new Buffer object + */ + virtual Buffer constructBuffer(std::size_t size = 0) = 0; +}; + +} diff --git a/include/bringauto/modules/ModuleHandler.hpp b/include/bringauto/modules/ModuleHandler.hpp index ea70aeca..1ae711d3 100644 --- a/include/bringauto/modules/ModuleHandler.hpp +++ b/include/bringauto/modules/ModuleHandler.hpp @@ -3,13 +3,10 @@ #include #include #include -#include #include #include #include -#include - #include @@ -19,11 +16,11 @@ namespace bringauto::modules { class ModuleHandler { public: ModuleHandler( - std::shared_ptr &context, + const std::shared_ptr &context, structures::ModuleLibrary &moduleLibrary, - std::shared_ptr > &fromInternalQueue, - std::shared_ptr > &toInternalQueue, - std::shared_ptr > &toExternalQueue) + const std::shared_ptr > &fromInternalQueue, + const std::shared_ptr > &toInternalQueue, + const std::shared_ptr > &toExternalQueue) : context_ { context }, moduleLibrary_ { moduleLibrary }, fromInternalQueue_ { fromInternalQueue }, toInternalQueue_ { toInternalQueue }, toExternalQueue_ { toExternalQueue } {} @@ -34,13 +31,13 @@ class ModuleHandler { * Initializes all modules and handles incoming messages from Internal Server through queues * */ - void run(); + void run() const; /** * @brief Stop Module handler and clean all initialized modules * */ - void destroy(); + void destroy() const; private: @@ -48,20 +45,20 @@ class ModuleHandler { * @brief Process all incoming messages from internal server * */ - void handleMessages(); + void handleMessages() const; /** * @brief Check if there are any timeouted messages * */ - void checkTimeoutedMessages(); + void checkTimeoutedMessages() const; /** * @brief Process disconnect device * - * @param device device identification + * @param deviceId device identification */ - void handleDisconnect(const structures::DeviceIdentification& deviceId); + void handleDisconnect(const structures::DeviceIdentification& deviceId) const; /** * @brief Send aggregated status to external server @@ -70,14 +67,14 @@ class ModuleHandler { * @param device protobuf device * @param disconnected true if device is disconnected otherwise false */ - void sendAggregatedStatus(const structures::DeviceIdentification &deviceId, const InternalProtocol::Device &device, bool disconnected); + void sendAggregatedStatus(const structures::DeviceIdentification &deviceId, const InternalProtocol::Device &device, bool disconnected) const; /** * @brief Process connect message * * @param connect Connect message */ - void handleConnect(const InternalProtocol::DeviceConnect &connect); + void handleConnect(const InternalProtocol::DeviceConnect &connect) const; /** * @brief Send connect response message to internal client @@ -85,19 +82,19 @@ class ModuleHandler { * @param device internal client * @param response_type type of the response */ - void sendConnectResponse(const InternalProtocol::Device &device, InternalProtocol::DeviceConnectResponse_ResponseType response_type); + void sendConnectResponse(const InternalProtocol::Device &device, InternalProtocol::DeviceConnectResponse_ResponseType response_type) const; /** * @brief Process status message * * @param status Status message */ - void handleStatus(const InternalProtocol::DeviceStatus &status); + void handleStatus(const InternalProtocol::DeviceStatus &status) const; /** * @brief Throws an error if external queue size is too big */ - void checkExternalQueueSize(); + void checkExternalQueueSize() const; std::shared_ptr context_ {}; diff --git a/include/bringauto/modules/ModuleManagerLibraryHandlerAsync.hpp b/include/bringauto/modules/ModuleManagerLibraryHandlerAsync.hpp new file mode 100644 index 00000000..558da78c --- /dev/null +++ b/include/bringauto/modules/ModuleManagerLibraryHandlerAsync.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + + + +namespace bringauto::modules { + +/** + * @brief Class used to load and handle library created by module maintainer + */ +class ModuleManagerLibraryHandlerAsync : public IModuleManagerLibraryHandler { +public: + explicit ModuleManagerLibraryHandlerAsync(const std::filesystem::path &moduleBinaryPath, const int moduleNumber); + + ~ModuleManagerLibraryHandlerAsync() override; + + /** + * @brief Load library created by a module maintainer + * + * @param path path to the library + */ + void loadLibrary(const std::filesystem::path &path) override; + + int getModuleNumber() override; + + int isDeviceTypeSupported(unsigned int device_type) override; + + int sendStatusCondition(const Buffer ¤t_status, const Buffer &new_status, unsigned int device_type) override; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + int generateCommand(Buffer &generated_command, const Buffer &new_status, + const Buffer ¤t_status, const Buffer ¤t_command, + unsigned int device_type) override; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + int aggregateStatus(Buffer &aggregated_status, const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) override; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + int aggregateError(Buffer &error_message, const Buffer ¤t_error_message, const Buffer &status, + unsigned int device_type) override; + + /** + * @short After executing the respective module function, an error might be thrown when allocating the buffer. + * + * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h + */ + int generateFirstCommand(Buffer &default_command, unsigned int device_type) override; + + int statusDataValid(const Buffer &status, unsigned int device_type) override; + + int commandDataValid(const Buffer &command, unsigned int device_type) override; + + /** + * @brief Constructs a buffer with the given size + * + * @param size size of the buffer + * @return a new Buffer object + */ + Buffer constructBuffer(std::size_t size = 0) override; + +private: + + int allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const; + + void deallocate(struct buffer *buffer) const; + + /** + * @brief Constructs a buffer with the same raw c buffer as provided + * + * @param buffer c buffer to be used + * @return a new Buffer object + */ + Buffer constructBufferByTakeOwnership(struct ::buffer& buffer); + + std::function deallocate_ {}; + + /// Path to the module binary + std::filesystem::path moduleBinaryPath_ {}; + /// Process of the module binary + boost::process::child moduleBinaryProcess_ {}; + + /// TODO find a way to not need this + std::mutex getModuleNumberMutex_ {}; + std::mutex isDeviceTypeSupportedMutex_ {}; + std::mutex sendStatusConditionMutex_ {}; + std::mutex generateCommandMutex_ {}; + std::mutex aggregateStatusMutex_ {}; + std::mutex aggregateErrorMutex_ {}; + std::mutex generateFirstCommandMutex_ {}; + std::mutex statusDataValidMutex_ {}; + std::mutex commandDataValidMutex_ {}; + + fleet_protocol::cxx::ModuleFunctionExecutor aeronClient { + async_function_execution::Config { + .isProducer = true, + .defaultTimeout = settings::AeronClientConstants::aeron_client_default_timeout, + }, + fleet_protocol::cxx::moduleFunctionList + }; +}; + +} diff --git a/include/bringauto/modules/ModuleManagerLibraryHandler.hpp b/include/bringauto/modules/ModuleManagerLibraryHandlerLocal.hpp similarity index 65% rename from include/bringauto/modules/ModuleManagerLibraryHandler.hpp rename to include/bringauto/modules/ModuleManagerLibraryHandlerLocal.hpp index cf937027..bd897d51 100644 --- a/include/bringauto/modules/ModuleManagerLibraryHandler.hpp +++ b/include/bringauto/modules/ModuleManagerLibraryHandlerLocal.hpp @@ -1,13 +1,6 @@ #pragma once -#include - -#include -#include -#include - -#include -#include +#include @@ -16,60 +9,60 @@ namespace bringauto::modules { /** * @brief Class used to load and handle library created by module maintainer */ -class ModuleManagerLibraryHandler { +class ModuleManagerLibraryHandlerLocal : public IModuleManagerLibraryHandler { public: - ModuleManagerLibraryHandler() = default; + explicit ModuleManagerLibraryHandlerLocal() = default; - ~ModuleManagerLibraryHandler(); + ~ModuleManagerLibraryHandlerLocal() override; /** * @brief Load library created by a module maintainer * * @param path path to the library */ - void loadLibrary(const std::filesystem::path &path); + void loadLibrary(const std::filesystem::path &path) override; - int getModuleNumber(); + int getModuleNumber() override; - int isDeviceTypeSupported(unsigned int device_type); + int isDeviceTypeSupported(unsigned int device_type) override; - int sendStatusCondition(const Buffer current_status, const Buffer new_status, unsigned int device_type); + int sendStatusCondition(const Buffer ¤t_status, const Buffer &new_status, unsigned int device_type) override; /** * @short After executing the respective module function, an error might be thrown when allocating the buffer. * * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h */ - int generateCommand(Buffer &generated_command, const Buffer new_status, - const Buffer current_status, const Buffer current_command, - unsigned int device_type); + int generateCommand(Buffer &generated_command, const Buffer &new_status, + const Buffer ¤t_status, const Buffer ¤t_command, + unsigned int device_type) override; /** * @short After executing the respective module function, an error might be thrown when allocating the buffer. * * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h */ - int aggregateStatus(Buffer &aggregated_status, const Buffer current_status, - const Buffer new_status, unsigned int device_type); + int aggregateStatus(Buffer &aggregated_status, const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) override; /** * @short After executing the respective module function, an error might be thrown when allocating the buffer. * * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h */ - int aggregateError(Buffer &error_message, const Buffer current_error_message, const Buffer status, - unsigned int device_type); + int aggregateError(Buffer &error_message, const Buffer ¤t_error_message, const Buffer &status, + unsigned int device_type) override; /** * @short After executing the respective module function, an error might be thrown when allocating the buffer. * * @see fleet-protocol/lib/module_maintainer/module_gateway/include/module_manager.h */ - int generateFirstCommand(Buffer &default_command, unsigned int device_type); + int generateFirstCommand(Buffer &default_command, unsigned int device_type) override; - int statusDataValid(const Buffer status, unsigned int device_type); + int statusDataValid(const Buffer &status, unsigned int device_type) override; - int commandDataValid(const Buffer command, unsigned int device_type); + int commandDataValid(const Buffer &command, unsigned int device_type) override; /** * @brief Constructs a buffer with the given size @@ -77,15 +70,15 @@ class ModuleManagerLibraryHandler { * @param size size of the buffer * @return a new Buffer object */ - Buffer constructBuffer(std::size_t size = 0); + Buffer constructBuffer(std::size_t size = 0) override; private: - int allocate(struct buffer *buffer_pointer, size_t size_in_bytes); + int allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const; - void deallocate(struct buffer *buffer); + void deallocate(struct buffer *buffer) const; - void *checkFunction(const char *functionName); + void *checkFunction(const char *functionName) const; /** * @brief Constructs a buffer with the same raw c buffer as provided @@ -113,4 +106,4 @@ class ModuleManagerLibraryHandler { std::function deallocate_ {}; }; -} +} \ No newline at end of file diff --git a/include/bringauto/modules/StatusAggregator.hpp b/include/bringauto/modules/StatusAggregator.hpp index 05d034b0..33580bf2 100644 --- a/include/bringauto/modules/StatusAggregator.hpp +++ b/include/bringauto/modules/StatusAggregator.hpp @@ -1,16 +1,10 @@ #pragma once -#include +#include #include #include -#include -#include - -#include #include -#include -#include #include @@ -26,9 +20,9 @@ class StatusAggregator { public: explicit StatusAggregator(const std::shared_ptr &context, - const std::shared_ptr &libraryHandler): context_ { context }, - module_ { - libraryHandler } {}; + const std::shared_ptr &libraryHandler): context_ { context }, + module_ { + libraryHandler } {}; StatusAggregator() = default; @@ -151,10 +145,10 @@ class StatusAggregator { /** * @brief Get the device timeout count * - * @param key device unique key, obtained from getId function in ProtobufUtils + * @param device device unique key, obtained from getId function in ProtobufUtils * @return number of timeouts */ - int getDeviceTimeoutCount(const structures::DeviceIdentification& ); + int getDeviceTimeoutCount(const structures::DeviceIdentification& device); private: @@ -166,9 +160,9 @@ class StatusAggregator { * @param device_type device type * @return struct buffer with aggregated status message */ - Buffer aggregateStatus(structures::StatusAggregatorDeviceState &deviceState, + Buffer aggregateStatus(const structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, - const unsigned int &device_type); + const unsigned int &device_type) const; /** * @brief Aggregate and set status message @@ -178,7 +172,7 @@ class StatusAggregator { * @param device_type device type */ void aggregateSetStatus(structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, - const unsigned int &device_type); + const unsigned int &device_type) const; /** * @brief Aggregate, set and send status message @@ -188,11 +182,11 @@ class StatusAggregator { * @param device_type device type */ void aggregateSetSendStatus(structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, - const unsigned int &device_type); + const unsigned int &device_type) const; std::shared_ptr context_ {}; - const std::shared_ptr module_ {}; + const std::shared_ptr module_ {}; /** * @brief Map of devices states, key is device identification diff --git a/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index 730ec38d..e9628b56 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -88,35 +88,45 @@ constexpr unsigned int max_external_queue_size { 500 }; /** * @brief Constants for Mqtt communication -*/ + */ struct MqttConstants { /** * @brief keep alive interval in seconds; * value reasoning: keepalive is half of the default timeout in Fleet protocol * The value is chosen based on empiric measurement. - */ + */ static constexpr std::chrono::seconds keepalive { status_response_timeout / 2U }; /** * @brief automatic reconnection of mqtt client option - */ + */ static constexpr bool automatic_reconnect { true }; /** * @brief max time that the mqtt client will wait for a connection before failing; * value reasoning: TCP timeout for retransmission when TCP packet is dropped is 200ms, * this value is multiple of three of this value - */ + */ static constexpr std::chrono::milliseconds connect_timeout { 600 }; /** * @brief max messages that can be in the process of transmission simultaneously; * value reasoning: How many MQTT inflight messages can be open at one time. * The value is chosen as a recommendation from a MQTT community. - */ + */ static constexpr size_t max_inflight { 20 }; }; +/** + * @brief Constants for Aeron client communication + */ +struct AeronClientConstants { + /** + * @brief default timeout for Aeron client function calls + */ + static constexpr std::chrono::milliseconds aeron_client_default_timeout { 1000 }; +}; + /** * @brief Constant string views */ @@ -130,6 +140,7 @@ class Constants { inline static constexpr std::string_view LOG_LEVEL { "level" }; inline static constexpr std::string_view LOG_USE { "use" }; inline static constexpr std::string_view LOG_PATH { "path" }; + inline static constexpr std::string_view LOG_UNKNOWN { "unknown" }; inline static constexpr std::string_view LOG_LEVEL_DEBUG { "DEBUG" }; inline static constexpr std::string_view LOG_LEVEL_INFO { "INFO" }; @@ -138,10 +149,17 @@ class Constants { inline static constexpr std::string_view LOG_LEVEL_CRITICAL { "CRITICAL" }; inline static constexpr std::string_view LOG_LEVEL_INVALID { "INVALID" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_NOT_INITIALIZED { "not initialized" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_NOT_CONNECTED { "not connected" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_CONNECTING { "connecting" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_CONNECTED { "connected" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_CLOSING { "closing" }; + inline static constexpr std::string_view HELP { "help" }; inline static constexpr std::string_view PORT { "port" }; inline static constexpr std::string_view MODULE_PATHS { "module-paths" }; + inline static constexpr std::string_view MODULE_BINARY_PATH { "module-binary-path" }; inline static constexpr std::string_view INTERNAL_SERVER_SETTINGS { "internal-server-settings" }; @@ -153,13 +171,19 @@ class Constants { inline static constexpr std::string_view PROTOCOL_TYPE { "protocol-type" }; inline static constexpr std::string_view MQTT { "MQTT" }; + inline static constexpr std::string_view QUIC { "QUIC" }; + inline static constexpr std::string_view DUMMY { "DUMMY" }; inline static constexpr std::string_view MQTT_SETTINGS { "mqtt-settings" }; + inline static constexpr std::string_view QUIC_SETTINGS { "quic-settings" }; inline static constexpr std::string_view SSL { "ssl" }; inline static constexpr std::string_view CA_FILE { "ca-file" }; inline static constexpr std::string_view CLIENT_CERT { "client-cert" }; inline static constexpr std::string_view CLIENT_KEY { "client-key" }; + inline static constexpr std::string_view ALPN { "alpn" }; inline static constexpr std::string_view MODULES { "modules" }; + inline static constexpr std::string_view AERON_CONNECTION { "aeron:ipc"}; + inline static constexpr std::string_view SEPARATOR { ":::" }; }; } diff --git a/include/bringauto/settings/LoggerId.hpp b/include/bringauto/settings/LoggerId.hpp index 062fa601..5ba0c1bd 100644 --- a/include/bringauto/settings/LoggerId.hpp +++ b/include/bringauto/settings/LoggerId.hpp @@ -1,10 +1,35 @@ #pragma once -#include +#include + +#ifndef MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY +#define MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY "DEBUG" +#endif + + namespace bringauto::settings { -constexpr bringauto::logging::LoggerId logId = {.id = "ModuleGateway"}; -using Logger = bringauto::logging::Logger; +/** + * @brief Converts the minimum logger verbosity defined in CMake to LoggerVerbosity enum. + * @param verbosityString The minimum logger verbosity defined in CMake. + */ +constexpr bringauto::logging::LoggerVerbosity toLoggerVerbosity(std::string_view verbosityString) { + if (verbosityString == "INFO") { + return bringauto::logging::LoggerVerbosity::Info; + } else if (verbosityString == "WARNING") { + return bringauto::logging::LoggerVerbosity::Warning; + } else if (verbosityString == "ERROR") { + return bringauto::logging::LoggerVerbosity::Error; + } else if (verbosityString == "CRITICAL") { + return bringauto::logging::LoggerVerbosity::Critical; + } + return bringauto::logging::LoggerVerbosity::Debug; +} + +constexpr logging::LoggerId logId = {.id = "ModuleGateway"}; +using BaseLogger = logging::Logger; + +using Logger = LoggerWrapper; } diff --git a/include/bringauto/settings/LoggerWrapper.hpp b/include/bringauto/settings/LoggerWrapper.hpp new file mode 100644 index 00000000..38b39d38 --- /dev/null +++ b/include/bringauto/settings/LoggerWrapper.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + + + +namespace bringauto::settings { + +/** + * @brief Custom Logger wrapper, which optimises out logging calls bellow the set verbosity level at compile time. + * + */ +template +class LoggerWrapper { +public: + // Forward addSink to the base logger + template + static void addSink(Args &&...args) { + BaseLogger::template addSink(std::forward(args)...); + } + + static void init(const logging::LoggerSettings &settings) { + if (settings.verbosity != verbosity) { + throw std::runtime_error("LoggerWrapper verbosity is different than the provided settings verbosity."); + } + BaseLogger::init(settings); + } + + static void destroy() { + BaseLogger::destroy(); + } + + // wrappers for common log levels + template + static void logDebug(LogArgs &&...args) { + if constexpr (verbosity > logging::LoggerVerbosity::Debug) { + return; // Skip logging if verbosity is lower than Debug + } + BaseLogger::log(logging::LoggerVerbosity::Debug, std::forward(args)...); + } + + template + static void logInfo(LogArgs &&...args) { + if constexpr (verbosity > logging::LoggerVerbosity::Info) { + return; // Skip logging if verbosity is lower than Info + } + BaseLogger::log(logging::LoggerVerbosity::Info, std::forward(args)...); + } + + template + static void logWarning(LogArgs &&...args) { + if constexpr (verbosity > logging::LoggerVerbosity::Warning) { + return; // Skip logging if verbosity is lower than Warning + } + BaseLogger::log(logging::LoggerVerbosity::Warning, std::forward(args)...); + } + + template + static void logError(LogArgs &&...args) { + if constexpr (verbosity > logging::LoggerVerbosity::Error) { + return; // Skip logging if verbosity is lower than Error + } + BaseLogger::log(logging::LoggerVerbosity::Error, std::forward(args)...); + } + + template + static void logCritical(LogArgs &&...args) { + if constexpr (verbosity > logging::LoggerVerbosity::Critical) { + return; // Skip logging if verbosity is lower than Critical + } + BaseLogger::log(logging::LoggerVerbosity::Critical, std::forward(args)...); + } + + template + static void log(logging::LoggerVerbosity dynVerbosity, LogArgs &&...args) { + if (verbosity > dynVerbosity) { + return; // Skip logging if verbosity is lower than Critical + } + BaseLogger::log(dynVerbosity, std::forward(args)...); + } +}; + +} diff --git a/include/bringauto/settings/QuicSettingsParser.hpp b/include/bringauto/settings/QuicSettingsParser.hpp new file mode 100644 index 00000000..b1df8005 --- /dev/null +++ b/include/bringauto/settings/QuicSettingsParser.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +#include + + +namespace bringauto::settings { + class QuicSettingsParser { + public: + static QUIC_SETTINGS parse(const structures::ExternalConnectionSettings &settings); + + private: + static std::optional getOptionalUint( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ); + }; +} diff --git a/include/bringauto/settings/Settings.hpp b/include/bringauto/settings/Settings.hpp index cf687f78..b105dda6 100644 --- a/include/bringauto/settings/Settings.hpp +++ b/include/bringauto/settings/Settings.hpp @@ -3,10 +3,8 @@ #include #include -#include #include #include -#include @@ -33,7 +31,12 @@ struct Settings { /** * @brief paths to shared module libraries */ - std::map modulePaths {}; + std::unordered_map modulePaths {}; + + /** + * @brief path to module binary + */ + std::filesystem::path moduleBinaryPath {}; /** * @brief Setting of external connection endpoints and protocols diff --git a/include/bringauto/settings/SettingsParser.hpp b/include/bringauto/settings/SettingsParser.hpp index 910c261a..2a82f3f0 100644 --- a/include/bringauto/settings/SettingsParser.hpp +++ b/include/bringauto/settings/SettingsParser.hpp @@ -36,7 +36,7 @@ class SettingsParser { * @brief Serializes settings to json * @return string with json representation of settings */ - std::string serializeToJson(); + std::string serializeToJson() const; private: cxxopts::ParseResult cmdArguments_ {}; @@ -44,18 +44,18 @@ class SettingsParser { void parseCmdArguments(int argc, char **argv); - bool areCmdArgumentsCorrect(); + bool areCmdArgumentsCorrect() const; - bool areSettingsCorrect(); + bool areSettingsCorrect() const; void fillSettings(); - void fillLoggingSettings(const nlohmann::json &file); + void fillLoggingSettings(const nlohmann::json &file) const; - void fillInternalServerSettings(const nlohmann::json &file); + void fillInternalServerSettings(const nlohmann::json &file) const; - void fillModulePathsSettings(const nlohmann::json &file); + void fillModulePathsSettings(const nlohmann::json &file) const; - void fillExternalConnectionSettings(const nlohmann::json &file); + void fillExternalConnectionSettings(const nlohmann::json &file) const; }; } diff --git a/include/bringauto/structures/AtomicQueue.hpp b/include/bringauto/structures/AtomicQueue.hpp index 40e58232..ec9149ed 100644 --- a/include/bringauto/structures/AtomicQueue.hpp +++ b/include/bringauto/structures/AtomicQueue.hpp @@ -30,7 +30,7 @@ class AtomicQueue { } /** - * @brief Waits for timeout or till being notifed that queue is not empty. + * @brief Waits for timeout or till being notified that queue is not empty. * @param timeout length of timeout * @return true if the queue is empty */ diff --git a/include/bringauto/structures/Connection.hpp b/include/bringauto/structures/Connection.hpp index 01dbe790..a1fe1aec 100644 --- a/include/bringauto/structures/Connection.hpp +++ b/include/bringauto/structures/Connection.hpp @@ -5,7 +5,6 @@ #include #include -#include @@ -21,6 +20,18 @@ struct Connection { */ explicit Connection(boost::asio::io_context &io_context_): socket(io_context_) {} + /** + * @brief Get the remote endpoint address as a string. + * @return remote endpoint address as a string + */ + [[nodiscard]] + std::string remoteEndpointAddress() const { + if (!socket.is_open()) { + return "(N/A, socket is not open)"; + } + return socket.remote_endpoint().address().to_string(); + } + /** * @brief socket endpoint in communication between server and client */ diff --git a/include/bringauto/structures/DeviceIdentification.hpp b/include/bringauto/structures/DeviceIdentification.hpp index d8308ceb..619244ff 100644 --- a/include/bringauto/structures/DeviceIdentification.hpp +++ b/include/bringauto/structures/DeviceIdentification.hpp @@ -59,7 +59,7 @@ class DeviceIdentification { /** * @brief get priority value - * @return priorirt value + * @return priority value */ [[nodiscard]] uint32_t getPriority() const; diff --git a/include/bringauto/structures/ExternalConnectionSettings.hpp b/include/bringauto/structures/ExternalConnectionSettings.hpp index b1fd0a15..5aee40bb 100644 --- a/include/bringauto/structures/ExternalConnectionSettings.hpp +++ b/include/bringauto/structures/ExternalConnectionSettings.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include @@ -13,14 +13,16 @@ namespace bringauto::structures { */ enum class ProtocolType { INVALID = -1, - MQTT + MQTT, + QUIC, + DUMMY }; struct ExternalConnectionSettings { /// Communication protocol ProtocolType protocolType {}; - /// Map of protocol specific settings, taken from config, pair of key and value - std::map protocolSettings {}; + /// Map of protocol specific settings, taken from config, a pair of key and value + std::unordered_map protocolSettings {}; /// Ip address of the external server std::string serverIp {}; /// Port of the external server diff --git a/include/bringauto/structures/InternalClientMessage.hpp b/include/bringauto/structures/InternalClientMessage.hpp index 20f4ca38..06442535 100644 --- a/include/bringauto/structures/InternalClientMessage.hpp +++ b/include/bringauto/structures/InternalClientMessage.hpp @@ -2,7 +2,6 @@ #include -#include #include diff --git a/include/bringauto/structures/ModuleHandlerMessage.hpp b/include/bringauto/structures/ModuleHandlerMessage.hpp index 87f30ab7..4d9c0c73 100644 --- a/include/bringauto/structures/ModuleHandlerMessage.hpp +++ b/include/bringauto/structures/ModuleHandlerMessage.hpp @@ -2,7 +2,6 @@ #include -#include #include diff --git a/include/bringauto/structures/ModuleLibrary.hpp b/include/bringauto/structures/ModuleLibrary.hpp index 79761e2d..d6b3a0db 100644 --- a/include/bringauto/structures/ModuleLibrary.hpp +++ b/include/bringauto/structures/ModuleLibrary.hpp @@ -1,9 +1,8 @@ #pragma once -#include +#include #include -#include #include @@ -14,6 +13,7 @@ namespace bringauto::structures { * @brief Library with library handlers and status aggregators */ struct ModuleLibrary { + ModuleLibrary() = default; ~ModuleLibrary(); @@ -22,7 +22,15 @@ struct ModuleLibrary { * * @param libPaths paths to the libraries */ - void loadLibraries(const std::map &libPaths); + void loadLibraries(const std::unordered_map &libPaths); + + /** + * @brief Load libraries from paths + * + * @param libPaths paths to the libraries + * @param moduleBinaryPath path to module binary for async function execution over shared memory + */ + void loadLibraries(const std::unordered_map &libPaths, const std::filesystem::path &moduleBinaryPath); /** * @brief Initialize status aggregators with context @@ -31,9 +39,9 @@ struct ModuleLibrary { */ void initStatusAggregators(std::shared_ptr &context); /// Map of module handlers, key is module id - std::map> moduleLibraryHandlers {}; + std::unordered_map> moduleLibraryHandlers {}; /// Map of status aggregators, key is module id - std::map> statusAggregators {}; + std::unordered_map> statusAggregators {}; }; } diff --git a/include/bringauto/structures/ReconnectQueueItem.hpp b/include/bringauto/structures/ReconnectQueueItem.hpp index fb87c71a..0687b5b8 100644 --- a/include/bringauto/structures/ReconnectQueueItem.hpp +++ b/include/bringauto/structures/ReconnectQueueItem.hpp @@ -14,7 +14,6 @@ namespace bringauto::structures { * @brief Item in reconnect queue in external client */ struct ReconnectQueueItem { -public: explicit ReconnectQueueItem( const std::reference_wrapper &connection, bool reconnect): connection_ { connection }, reconnect { reconnect } {} diff --git a/include/bringauto/structures/StatusAggregatorDeviceState.hpp b/include/bringauto/structures/StatusAggregatorDeviceState.hpp index 97a198bc..13c80d2d 100644 --- a/include/bringauto/structures/StatusAggregatorDeviceState.hpp +++ b/include/bringauto/structures/StatusAggregatorDeviceState.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -73,7 +72,7 @@ class StatusAggregatorDeviceState { /** * @brief Add a command to the queue of received commands from the external server. - * Commands are being retreived by the getCommand method, which also removes them from the queue. + * Commands are being retrieved by the getCommand method, which also removes them from the queue. * If the queue is full, the oldest command will be removed. * * @param commandBuffer command Buffer diff --git a/include/bringauto/structures/ThreadTimer.hpp b/include/bringauto/structures/ThreadTimer.hpp index b9e340b7..671ff259 100644 --- a/include/bringauto/structures/ThreadTimer.hpp +++ b/include/bringauto/structures/ThreadTimer.hpp @@ -4,11 +4,8 @@ #include #include -#include #include -#include #include -#include #include #include @@ -27,13 +24,12 @@ class ThreadTimer final { /** * @brief Constructor * - * @param context Global context - * @param func Function which should be executed - * @param interval Interval of time in which function will be executed - * (in seconds) + * @param context Global context + * @param function Function which should be executed + * @param deviceId DeviceIdentification */ - explicit ThreadTimer(std::shared_ptr &context, - std::function &function, + explicit ThreadTimer(const std::shared_ptr &context, + const std::function &function, const DeviceIdentification& deviceId): timer_ { context->ioContext }, fun_ { function }, deviceId_ { deviceId } {} diff --git a/main.cpp b/main.cpp index a607e9f1..07536448 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -36,8 +37,10 @@ void initLogger(const bringauto::structures::LoggingSettings &settings) { bringauto::settings::Logger::addSink(paramFileSink); } - bringauto::logging::LoggerSettings params { "ModuleGateway", - bringauto::logging::LoggerVerbosity::Debug }; + const bringauto::logging::LoggerSettings params { + "ModuleGateway", + bringauto::settings::toLoggerVerbosity(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY) + }; bringauto::settings::Logger::init(params); } @@ -46,6 +49,11 @@ int main(int argc, char **argv) { namespace bas = bringauto::structures; namespace baset = bringauto::settings; auto context = std::make_shared(); + + // Disable synchronization with stdio to improve performance + std::ios_base::sync_with_stdio(false); + std::cin.tie(nullptr); + try { baset::SettingsParser settingsParser; if(!settingsParser.parseSettings(argc, argv)) { @@ -59,9 +67,15 @@ int main(int argc, char **argv) { std::cerr << "[ERROR] Error occurred during reading configuration: " << e.what() << std::endl; return 1; } + bas::ModuleLibrary moduleLibrary {}; + try { - moduleLibrary.loadLibraries(context->settings->modulePaths); + if(context->settings->moduleBinaryPath.empty()) { + moduleLibrary.loadLibraries(context->settings->modulePaths); + } else { + moduleLibrary.loadLibraries(context->settings->modulePaths, context->settings->moduleBinaryPath); + } moduleLibrary.initStatusAggregators(context); } catch(std::exception &e) { std::cerr << "[ERROR] Error occurred during module initialization: " << e.what() << std::endl; diff --git a/resources/config/README.md b/resources/config/README.md index 8451e279..a67581a1 100644 --- a/resources/config/README.md +++ b/resources/config/README.md @@ -22,22 +22,33 @@ Note: at least one logging sink needs to be used ### module-paths: * key : number that corresponds to the module being loaded * value : path to the module shared library file +### module-binary-path: + - path to the module binary for async function execution over shared memory. If none is provided, the module will be loaded as a shared library ### external-connection: * company : company name used as identification in external connection (string) * vehicle-name : vehicle name used as identification in external connection (string) * endpoints : array of objects listing possible ways to connect to external server - - protocol-type : string (only mqtt is supported) + - protocol-type : string (only mqtt and quic are supported) - server-ip : ip of the external connection (string) - port : port of the external connection (int) - modules : array of integers that represent module numbers to be used on this connection - - mqtt-settings : **only for mqtt** - - ssl : if connection requires ssl, bool - - ca-file : public trusted certificate file name (string) - - client-cert : public certificate chain file name (string) - - client-key : private key file name (string) -## Example +#### mqtt-settings (only for MQTT) +* ssl : if connection requires ssl, bool +* ca-file : public trusted certificate file name (string) +* client-cert : public certificate chain file name (string) +* client-key : private key file name (string) -[Example](./example.json) +#### quic-settings (only for QUIC) +* ca-file : path to the trusted CA certificate file (string) +* client-cert : path to the client certificate file (string) +* client-key : path to the client private key file (string) +* alpn : Application-Layer Protocol Negotiation identifier (string), must match the ALPN configured on the server + +Note: QUIC uses TLS 1.3 internally. All certificate files must be provided in a format supported by MsQuic/OpenSSL. +## Examples + +[MQTT Example](./example.json) +[QUIC Example](./quic_example.json) diff --git a/resources/config/default.json b/resources/config/default.json index 3fc59eb0..0402e352 100644 --- a/resources/config/default.json +++ b/resources/config/default.json @@ -14,6 +14,7 @@ "port": 8888 }, "module-paths": { }, + "module-binary-path": "", "external-connection" : { "company": "", "vehicle-name": "", diff --git a/resources/config/example.json b/resources/config/example.json index 3bb8d46f..2a5bc024 100644 --- a/resources/config/example.json +++ b/resources/config/example.json @@ -20,6 +20,7 @@ "1000": "./libmission-module-gateway-shared.so" }, + "module-binary-path": "", "external-connection" : { "company" : "bringauto", "vehicle-name" : "virtual_vehicle", diff --git a/resources/config/for_docker.json b/resources/config/for_docker.json index c28bea57..68cbcb4d 100644 --- a/resources/config/for_docker.json +++ b/resources/config/for_docker.json @@ -18,6 +18,7 @@ "2": "/home/bringauto/modules/io_module/lib/libio-module-gateway-shared.so", "3": "/home/bringauto/modules/transparent_module/lib/libtransparent-module-gateway-shared.so" }, + "module-binary-path": "", "external-connection" : { "company" : "bringauto", "vehicle-name" : "virtual_vehicle", diff --git a/resources/config/quic_example.json b/resources/config/quic_example.json new file mode 100644 index 00000000..640b3648 --- /dev/null +++ b/resources/config/quic_example.json @@ -0,0 +1,36 @@ +{ + "logging": { + "console": { "level": "DEBUG", "use": true }, + "file": { "level": "DEBUG", "use": false, "path": "./log" } + }, + + "internal-server-settings": { "port": 1636 }, + + "module-paths": { + "1": "/home/bringauto/modules/mission_module/lib/libmission-module-gateway-shared.so", + "2": "/home/bringauto/modules/io_module/lib/libio-module-gateway-shared.so", + "3": "/home/bringauto/modules/transparent_module/lib/libtransparent-module-gateway-shared.so" + }, + + "module-binary-path": "", + + "external-connection" : { + "company" : "bringauto", + "vehicle-name" : "virtual_vehicle", + "endpoints" : [ + { + "protocol-type" : "QUIC", + "server-ip": "127.0.0.1", + "port": 6121, + "quic-settings": { + "ca-file" : "build/certs/ca.pem", + "client-cert" : "build/certs/client.pem", + "client-key" : "build/certs/client-key.pem", + "alpn" : "sample", + "DisconnectTimeoutMs" : 800 + }, + "modules": [1,2,3] + } + ] + } +} diff --git a/resources/systemd/module-gateway.service.in b/resources/systemd/module-gateway.service.in index 35df8cf0..68e55858 100644 --- a/resources/systemd/module-gateway.service.in +++ b/resources/systemd/module-gateway.service.in @@ -4,10 +4,11 @@ After=network.target nss-lookup.target [Service] Type=simple +User=root Restart=always RestartSec=20 -WorkingDirectory=@CMAKE_INSTALL_SYSCONFDIR@ -ExecStart=@CMAKE_INSTALL_PREFIX@/bin/module-gateway-app --config-path=@CMAKE_INSTALL_SYSCONFDIR@/bringauto/module-gateway/config.json +WorkingDirectory=@CMAKE_INSTALL_PREFIX@ +ExecStart=@CMAKE_INSTALL_PREFIX@/bin/module-gateway-app --config-path=/@CMAKE_INSTALL_SYSCONFDIR@/bringauto/module-gateway/config.json [Install] WantedBy=multi-user.target diff --git a/source/bringauto/common_utils/EnumUtils.cpp b/source/bringauto/common_utils/EnumUtils.cpp index 614e7eca..ae8976d0 100644 --- a/source/bringauto/common_utils/EnumUtils.cpp +++ b/source/bringauto/common_utils/EnumUtils.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -11,19 +10,14 @@ structures::ProtocolType EnumUtils::stringToProtocolType(std::string toEnum) { std::transform(toEnum.begin(), toEnum.end(), toEnum.begin(), ::toupper); if(toEnum == settings::Constants::MQTT) { return structures::ProtocolType::MQTT; - } + } else if(toEnum == settings::Constants::QUIC) { + return structures::ProtocolType::QUIC; + } else if(toEnum == settings::Constants::DUMMY) { + return structures::ProtocolType::DUMMY; + } return structures::ProtocolType::INVALID; } -std::string EnumUtils::protocolTypeToString(structures::ProtocolType toString) { - std::string result {}; - if(toString == structures::ProtocolType::MQTT) { - result = settings::Constants::MQTT; - } - std::transform(result.begin(), result.end(), result.begin(), ::tolower); - return result; -} - logging::LoggerVerbosity EnumUtils::stringToLoggerVerbosity(std::string toEnum) { std::transform(toEnum.begin(), toEnum.end(), toEnum.begin(), ::toupper); if(toEnum == settings::Constants::LOG_LEVEL_DEBUG) { @@ -41,28 +35,4 @@ logging::LoggerVerbosity EnumUtils::stringToLoggerVerbosity(std::string toEnum) return logging::LoggerVerbosity::Debug; } -std::string EnumUtils::loggerVerbosityToString(logging::LoggerVerbosity verbosity) { - std::string result {}; - switch(verbosity) { - case logging::LoggerVerbosity::Debug: - result = settings::Constants::LOG_LEVEL_DEBUG; - break; - case logging::LoggerVerbosity::Info: - result = settings::Constants::LOG_LEVEL_INFO; - break; - case logging::LoggerVerbosity::Warning: - result = settings::Constants::LOG_LEVEL_WARNING; - break; - case logging::LoggerVerbosity::Error: - result = settings::Constants::LOG_LEVEL_ERROR; - break; - case logging::LoggerVerbosity::Critical: - result = settings::Constants::LOG_LEVEL_CRITICAL; - break; - default: - result = settings::Constants::LOG_LEVEL_INVALID; - break; - } - return result; -} } diff --git a/source/bringauto/common_utils/ProtobufUtils.cpp b/source/bringauto/common_utils/ProtobufUtils.cpp index 3e1fd762..a68a1493 100644 --- a/source/bringauto/common_utils/ProtobufUtils.cpp +++ b/source/bringauto/common_utils/ProtobufUtils.cpp @@ -1,9 +1,5 @@ #include -#include - -#include - namespace bringauto::common_utils { @@ -12,9 +8,9 @@ InternalProtocol::InternalServer ProtobufUtils::createInternalServerConnectResponseMessage(const InternalProtocol::Device &device, const InternalProtocol::DeviceConnectResponse_ResponseType &resType) { InternalProtocol::InternalServer message {}; - auto response = message.mutable_deviceconnectresponse(); + const auto response = message.mutable_deviceconnectresponse(); response->set_responsetype(resType); - auto device_ = response->mutable_device(); + const auto device_ = response->mutable_device(); device_->CopyFrom(device); return message; } @@ -23,11 +19,11 @@ InternalProtocol::InternalServer ProtobufUtils::createInternalServerCommandMessage(const InternalProtocol::Device &device, const modules::Buffer &command) { InternalProtocol::InternalServer message {}; - auto deviceCommand = message.mutable_devicecommand(); + const auto deviceCommand = message.mutable_devicecommand(); if (command.isAllocated()) { deviceCommand->set_commanddata(command.getStructBuffer().data, command.getStructBuffer().size_in_bytes); } - auto device_ = deviceCommand->mutable_device(); + const auto device_ = deviceCommand->mutable_device(); device_->CopyFrom(device); return message; } @@ -36,11 +32,11 @@ InternalProtocol::InternalClient ProtobufUtils::createInternalClientStatusMessage(const InternalProtocol::Device &device, const modules::Buffer &status) { InternalProtocol::InternalClient message {}; - auto deviceStatus = message.mutable_devicestatus(); + const auto deviceStatus = message.mutable_devicestatus(); if (status.isAllocated()) { deviceStatus->set_statusdata(status.getStructBuffer().data, status.getStructBuffer().size_in_bytes); } - auto device_ = deviceStatus->mutable_device(); + const auto device_ = deviceStatus->mutable_device(); device_->CopyFrom(device); return message; } @@ -60,14 +56,15 @@ ExternalProtocol::ExternalClient ProtobufUtils::createExternalClientConnect(cons const std::string &vehicleName, const std::vector &devices) { ExternalProtocol::ExternalClient externalMessage {}; - auto connectMessage = externalMessage.mutable_connect(); + const auto connectMessage = externalMessage.mutable_connect(); connectMessage->set_sessionid(sessionId); connectMessage->set_company(company); connectMessage->set_vehiclename(vehicleName); + connectMessage->mutable_devices()->Reserve(devices.size()); for(const auto &tmpDevice: devices) { - auto devicePtr = connectMessage->add_devices(); + const auto devicePtr = connectMessage->add_devices(); devicePtr->CopyFrom(tmpDevice.convertToIPDevice()); } @@ -102,24 +99,26 @@ ExternalProtocol::ExternalClient ProtobufUtils::createExternalClientCommandRespo return externalMessage; } -void ProtobufUtils::copyStatusToBuffer(const InternalProtocol::DeviceStatus &status, modules::Buffer &buffer) { +void ProtobufUtils::copyStatusToBuffer(const InternalProtocol::DeviceStatus &status, const modules::Buffer &buffer) { if (!buffer.isAllocated()) { throw BufferNotAllocated { "Buffer is not allocated" }; } - if (status.statusdata().size() > buffer.getStructBuffer().size_in_bytes) { + const auto statusSize = status.statusdata().size(); + if (statusSize > buffer.getStructBuffer().size_in_bytes) { throw BufferTooSmall { "Buffer does not have enough space allocated for status" }; } - std::memcpy(buffer.getStructBuffer().data, status.statusdata().c_str(), status.statusdata().size()); + std::memcpy(buffer.getStructBuffer().data, status.statusdata().c_str(), statusSize); } -void ProtobufUtils::copyCommandToBuffer(const InternalProtocol::DeviceCommand &command, modules::Buffer &buffer) { +void ProtobufUtils::copyCommandToBuffer(const InternalProtocol::DeviceCommand &command, const modules::Buffer &buffer) { if (!buffer.isAllocated()) { throw BufferNotAllocated { "Buffer is not allocated" }; } - if (command.commanddata().size() > buffer.getStructBuffer().size_in_bytes) { + const auto commandSize = command.commanddata().size(); + if (commandSize > buffer.getStructBuffer().size_in_bytes) { throw BufferTooSmall { "Buffer does not have enough space allocated for command" }; } - std::memcpy(buffer.getStructBuffer().data, command.commanddata().c_str(), command.commanddata().size()); + std::memcpy(buffer.getStructBuffer().data, command.commanddata().c_str(), commandSize); } } diff --git a/source/bringauto/external_client/ErrorAggregator.cpp b/source/bringauto/external_client/ErrorAggregator.cpp index 02afaf99..4f83b6c0 100644 --- a/source/bringauto/external_client/ErrorAggregator.cpp +++ b/source/bringauto/external_client/ErrorAggregator.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -9,7 +8,7 @@ namespace bringauto::external_client { -int ErrorAggregator::init_error_aggregator(const std::shared_ptr &library) { +int ErrorAggregator::init_error_aggregator(const std::shared_ptr &library) { module_ = library; return OK; } @@ -58,7 +57,7 @@ int ErrorAggregator::get_error(modules::Buffer &error, const structures::DeviceI return DEVICE_NOT_REGISTERED; } - auto ¤tError = devices_[device].errorMessage; + const auto ¤tError = devices_[device].errorMessage; if(!currentError.isAllocated()) { return NO_MESSAGE_AVAILABLE; @@ -79,7 +78,7 @@ int ErrorAggregator::get_module_number() const { return module_->getModuleNumber(); } -int ErrorAggregator::is_device_type_supported(unsigned int device_type) { +int ErrorAggregator::is_device_type_supported(unsigned int device_type) const { return module_->isDeviceTypeSupported(device_type); } diff --git a/source/bringauto/external_client/ExternalClient.cpp b/source/bringauto/external_client/ExternalClient.cpp index b7e2b617..c21b1457 100644 --- a/source/bringauto/external_client/ExternalClient.cpp +++ b/source/bringauto/external_client/ExternalClient.cpp @@ -3,9 +3,12 @@ #include #include #include - +#include +#include #include +#include + #include @@ -14,9 +17,9 @@ namespace bringauto::external_client { namespace ip = InternalProtocol; -ExternalClient::ExternalClient(std::shared_ptr &context, +ExternalClient::ExternalClient(const std::shared_ptr &context, structures::ModuleLibrary &moduleLibrary, - std::shared_ptr> &toExternalQueue): + const std::shared_ptr> &toExternalQueue): toExternalQueue_ { toExternalQueue }, context_ { context }, moduleLibrary_ { moduleLibrary }, @@ -44,7 +47,8 @@ void ExternalClient::handleCommand(const InternalProtocol::DeviceCommand &device const auto &device = deviceCommand.device(); const auto &moduleNumber = device.module(); auto &statusAggregators = moduleLibrary_.statusAggregators; - if(not statusAggregators.contains(moduleNumber)) { + const auto it = statusAggregators.find(moduleNumber); + if (it == statusAggregators.end()) { settings::Logger::logWarning("Module with module number {} does no exists", static_cast(moduleNumber)); return; } @@ -54,13 +58,13 @@ void ExternalClient::handleCommand(const InternalProtocol::DeviceCommand &device settings::Logger::logWarning("Received empty command for device: {} {}", device.devicerole(), device.devicename()); return; } - auto &moduleLibraryHandler = moduleLibrary_.moduleLibraryHandlers.at(moduleNumber); - auto commandBuffer = moduleLibraryHandler->constructBuffer(commandData.size()); + const auto &moduleLibraryHandler = moduleLibrary_.moduleLibraryHandlers.at(moduleNumber); + const auto commandBuffer = moduleLibraryHandler->constructBuffer(commandData.size()); common_utils::ProtobufUtils::copyCommandToBuffer(deviceCommand, commandBuffer); - auto deviceId = structures::DeviceIdentification(device); - int ret = statusAggregators.at(moduleNumber)->update_command(commandBuffer, deviceId); + const auto deviceId = structures::DeviceIdentification(device); + int ret = it->second->update_command(commandBuffer, deviceId); if (ret == OK) { settings::Logger::logInfo("Command for device {} was added to queue", device.devicename()); } @@ -84,16 +88,26 @@ void ExternalClient::run() { } void ExternalClient::initConnections() { - for(auto const &connection: context_->settings->externalConnectionSettingsList) { - externalConnectionsList_.emplace_back(context_, moduleLibrary_, connection, fromExternalQueue_, + for(auto const &connectionSettings: context_->settings->externalConnectionSettingsList) { + externalConnectionsList_.emplace_back(context_, moduleLibrary_, connectionSettings, fromExternalQueue_, reconnectQueue_); auto &newConnection = externalConnectionsList_.back(); std::shared_ptr communicationChannel; - switch(connection.protocolType) { + switch(connectionSettings.protocolType) { case structures::ProtocolType::MQTT: communicationChannel = std::make_shared( - connection, context_->settings->company, context_->settings->vehicleName + connectionSettings, context_->settings->company, context_->settings->vehicleName + ); + break; + case structures::ProtocolType::QUIC: + communicationChannel = std::make_shared( + connectionSettings, context_->settings->company, context_->settings->vehicleName + ); + break; + case structures::ProtocolType::DUMMY: + communicationChannel = std::make_shared( + connectionSettings ); break; case structures::ProtocolType::INVALID: @@ -103,7 +117,7 @@ void ExternalClient::initConnections() { } newConnection.init(communicationChannel); - for(auto const &moduleNumber: connection.modules) { + for(auto const &moduleNumber: connectionSettings.modules) { externalConnectionMap_.emplace(moduleNumber, newConnection); } } @@ -139,7 +153,7 @@ void ExternalClient::handleAggregatedMessages() { bool ExternalClient::sendStatus(const structures::InternalClientMessage &internalMessage) { auto &deviceStatus = internalMessage.getMessage().devicestatus(); const auto &moduleNumber = deviceStatus.device().module(); - auto it = externalConnectionMap_.find(moduleNumber); + const auto it = externalConnectionMap_.find(moduleNumber); if(it == externalConnectionMap_.end()) { settings::Logger::logError("Module number {} not found in the map\n", static_cast(moduleNumber)); return true; diff --git a/source/bringauto/external_client/connection/ExternalConnection.cpp b/source/bringauto/external_client/connection/ExternalConnection.cpp index dc6f08c9..4051818f 100644 --- a/source/bringauto/external_client/connection/ExternalConnection.cpp +++ b/source/bringauto/external_client/connection/ExternalConnection.cpp @@ -1,15 +1,16 @@ #include #include #include - #include +#include + #include namespace bringauto::external_client::connection { -using log = bringauto::settings::Logger; +using log = settings::Logger; ExternalConnection::ExternalConnection(const std::shared_ptr &context, structures::ModuleLibrary &moduleLibrary, @@ -41,23 +42,24 @@ void ExternalConnection::sendStatus(const InternalProtocol::DeviceStatus &status const auto &device = status.device(); const auto &deviceModule = device.module(); - if(!errorAggregators_.contains(deviceModule)){ + const auto it = errorAggregators_.find(deviceModule); + if(it == errorAggregators_.end()) { log::logError( "Status with module number ({}) was passed to external connection, that doesn't support this module", static_cast(device.module())); return; } - auto &errorAggregator = errorAggregators_.at(deviceModule); - auto deviceId = structures::DeviceIdentification(device); - auto moduleLibraryHandler = moduleLibrary_.moduleLibraryHandlers.at(deviceModule); + auto &errorAggregator = it->second; + const auto deviceId = structures::DeviceIdentification(device); + const auto moduleLibraryHandler = moduleLibrary_.moduleLibraryHandlers.at(deviceModule); modules::Buffer lastStatus {}; - auto isRegistered = errorAggregator.get_last_status(lastStatus, deviceId); + const auto isRegistered = errorAggregator.get_last_status(lastStatus, deviceId); if (isRegistered == DEVICE_NOT_REGISTERED){ deviceState = ExternalProtocol::Status_DeviceState_CONNECTING; const auto &statusData = status.statusdata(); - auto statusBuffer = moduleLibraryHandler->constructBuffer(statusData.size()); + const auto statusBuffer = moduleLibraryHandler->constructBuffer(statusData.size()); if (!statusBuffer.isEmpty()) { common_utils::ProtobufUtils::copyStatusToBuffer(status, statusBuffer); } @@ -186,7 +188,7 @@ int ExternalConnection::statusMessageHandle(const std::vectorsecond; if(sentMessagesHandler_->isDeviceConnected(deviceId)) { responseType = ExternalProtocol::CommandResponse_Type_OK; commandQueue_->pushAndNotify(commandMessage.devicecommand()); @@ -378,28 +381,25 @@ void ExternalConnection::fillErrorAggregatorWithNotAckedStatuses() { void ExternalConnection::fillErrorAggregator(const InternalProtocol::DeviceStatus &deviceStatus) { int moduleNum = deviceStatus.device().module(); - if(not errorAggregators_.contains(moduleNum)){ + const auto it = errorAggregators_.find(moduleNum); + if(it == errorAggregators_.end()) { log::logError("Module with module number {} does no exists", moduleNum); return; } fillErrorAggregatorWithNotAckedStatuses(); - if(errorAggregators_.find(moduleNum) != errorAggregators_.end()) { - const auto &statusData = deviceStatus.statusdata(); - auto statusBuffer = moduleLibrary_.moduleLibraryHandlers.at(moduleNum)->constructBuffer( - statusData.size()); - if (!statusBuffer.isEmpty()) { - common_utils::ProtobufUtils::copyStatusToBuffer(deviceStatus, statusBuffer); - } - - auto deviceId = structures::DeviceIdentification(deviceStatus.device()); - auto &errorAggregator = errorAggregators_.at(moduleNum); - errorAggregator.add_status_to_error_aggregator(statusBuffer, deviceId); - } else { - log::logError("Device status with unsupported module was passed to fillErrorAggregator()"); + const auto &statusData = deviceStatus.statusdata(); + const auto statusBuffer = moduleLibrary_.moduleLibraryHandlers.at(moduleNum)->constructBuffer( + statusData.size()); + if (!statusBuffer.isEmpty()) { + common_utils::ProtobufUtils::copyStatusToBuffer(deviceStatus, statusBuffer); } + + const auto deviceId = structures::DeviceIdentification(deviceStatus.device()); + auto &errorAggregator = it->second; + errorAggregator.add_status_to_error_aggregator(statusBuffer, deviceId); } -std::vector ExternalConnection::forceAggregationOnAllDevices(std::vector connectedDevices) { +std::vector ExternalConnection::forceAggregationOnAllDevices(const std::vector &connectedDevices) { std::vector forcedDevices {}; for(const auto &device: connectedDevices) { modules::Buffer last_status {}; @@ -412,11 +412,20 @@ std::vector ExternalConnection::forceAggregati return forcedDevices; } -std::vector ExternalConnection::getAllConnectedDevices() { +std::vector ExternalConnection::getAllConnectedDevices() const { std::vector devices {}; for(const auto &moduleNumber: settings_.modules) { std::list unique_devices {}; - int ret = moduleLibrary_.statusAggregators.at(moduleNumber)->get_unique_devices(unique_devices); + auto statusAggregatorItr = moduleLibrary_.statusAggregators.find(moduleNumber); + + if (statusAggregatorItr == moduleLibrary_.statusAggregators.end()) + { + log::logWarning("Module {} is defined in external-connection endpoint but is not specified in module-paths", + moduleNumber); + continue; + } + + const int ret = (*statusAggregatorItr).second->get_unique_devices(unique_devices); if(ret <= 0) { log::logWarning("Module {} does not have any connected devices", moduleNumber); continue; @@ -436,8 +445,8 @@ void ExternalConnection::setNotInitialized() { state_.exchange(ConnectionState::NOT_INITIALIZED); } -bool ExternalConnection::isModuleSupported(int moduleNum) { - return errorAggregators_.find(moduleNum) != errorAggregators_.end(); +bool ExternalConnection::isModuleSupported(int moduleNum) const { + return errorAggregators_.contains(moduleNum); } diff --git a/source/bringauto/external_client/connection/communication/DummyCommunication.cpp b/source/bringauto/external_client/connection/communication/DummyCommunication.cpp new file mode 100644 index 00000000..0a770fee --- /dev/null +++ b/source/bringauto/external_client/connection/communication/DummyCommunication.cpp @@ -0,0 +1,42 @@ +#include +#include + + + +namespace bringauto::external_client::connection::communication { + +DummyCommunication::DummyCommunication(const structures::ExternalConnectionSettings &settings) : ICommunicationChannel(settings) { + settings::Logger::logDebug("Creating DummyCommunication"); +} + +DummyCommunication::~DummyCommunication() { + closeConnection(); +} + +void DummyCommunication::initializeConnection() { + settings::Logger::logDebug("Initializing DummyCommunication connection"); + isConnected_ = true; +} + +bool DummyCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { + if (!isConnected_) { + settings::Logger::logDebug("Failed sending message, DummyCommunication is not connected"); + return false; + } + + settings::Logger::logDebug("Sending message in DummyCommunication"); + return true; +} + +std::shared_ptr DummyCommunication::receiveMessage() { + settings::Logger::logDebug("Receiving message in DummyCommunication"); + return nullptr; +} + +void DummyCommunication::closeConnection() { + settings::Logger::logDebug("Closing DummyCommunication connection"); + isConnected_ = false; +} + + +} diff --git a/source/bringauto/external_client/connection/communication/MqttCommunication.cpp b/source/bringauto/external_client/connection/communication/MqttCommunication.cpp index a9e380d1..969da2ca 100644 --- a/source/bringauto/external_client/connection/communication/MqttCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/MqttCommunication.cpp @@ -64,7 +64,7 @@ void MqttCommunication::initializeConnection() { if (client_ != nullptr && client_->is_connected()) { return; } - else if (client_ != nullptr) { + if (client_ != nullptr) { closeConnection(); } connect(); diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp new file mode 100644 index 00000000..f356681a --- /dev/null +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -0,0 +1,502 @@ +#include +#include +#include +#include +#include + + +namespace bringauto::external_client::connection::communication { + QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, + const std::string &company, + const std::string &vehicleName) : ICommunicationChannel(settings), + alpn_(getProtocolSettingsString( + settings, + settings::Constants::ALPN)), + certFile_(getProtocolSettingsString( + settings, + settings::Constants::CLIENT_CERT)), + keyFile_(getProtocolSettingsString( + settings, + settings::Constants::CLIENT_KEY)), + caFile_(getProtocolSettingsString( + settings, + settings::Constants::CA_FILE)) { + alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); + alpnBuffer_.Length = static_cast(alpn_.size()); + + loadMsQuic(); + initRegistration(); + initConfiguration(); + + settings::Logger::logInfo("[quic] Initialize QUIC communication to {}:{} for {}/{}", settings.serverIp, + settings.port, company, vehicleName); + } + + QuicCommunication::~QuicCommunication() { + stop(); + } + + void QuicCommunication::initializeConnection() { + settings::Logger::logDebug("[quic] Connecting to server when {}", + common_utils::EnumUtils::connectionStateToString(connectionState_)); + + ConnectionState expected = ConnectionState::NOT_CONNECTED; + if (!connectionState_. + compare_exchange_strong(expected, ConnectionState::CONNECTING, std::memory_order_acq_rel)) { + settings::Logger::logError("Connection already in progress or established"); + return; + } + + QUIC_STATUS status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); + if (QUIC_FAILED(status)) { + settings::Logger::logError("ConnectionOpen failed (status=0x{:x})", status); + return; + } + + status = quic_->ConnectionStart( + connection_, + config_, + QUIC_ADDRESS_FAMILY_INET, + settings_.serverIp.c_str(), + settings_.port + ); + + if (QUIC_FAILED(status)) { + settings::Logger::logError("ConnectionOpen failed (status=0x{:x})", status); + return; + } + + connectionState_ = ConnectionState::CONNECTING; + } + + bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { + if (connectionState_.load() == ConnectionState::NOT_CONNECTED) { + settings::Logger::logWarning("[quic] Connection not established, cannot send message"); + return false; + } + + { + auto copy = std::make_unique(*message); + std::lock_guard lock(outboundMutex_); + outboundQueue_.push(std::move(copy)); + } + settings::Logger::logDebug("[quic] Notifying sender thread about enqueued message"); + outboundCv_.notify_one(); + return true; + } + + std::shared_ptr QuicCommunication::receiveMessage() { + std::unique_lock lock(inboundMutex_); + + if (!inboundCv_.wait_for( + lock, + settings::receive_message_timeout, + [this] { return !inboundQueue_.empty() || connectionState_.load() != ConnectionState::CONNECTED; } + )) { + return nullptr; + } + + if (connectionState_.load() != ConnectionState::CONNECTED || inboundQueue_.empty()) { + return nullptr; + } + + auto msg = inboundQueue_.front(); + inboundQueue_.pop(); + return msg; + } + + void QuicCommunication::loadMsQuic() { + QUIC_STATUS status = MsQuicOpen2(&quic_); + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to open QUIC; QUIC_STATUS => {:x}", status); + return; + } + } + + constexpr auto quicRegistrationAppName = "module-gateway-quic-client"; + + void QuicCommunication::initRegistration() { + QUIC_REGISTRATION_CONFIG config{}; + config.AppName = quicRegistrationAppName; + config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; + + QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status); + return; + } + } + + void QuicCommunication::initConfiguration() { + configurationOpen(); + + QUIC_CERTIFICATE_FILE certificate{}; + certificate.CertificateFile = certFile_.c_str(); + certificate.PrivateKeyFile = keyFile_.c_str(); + + QUIC_CREDENTIAL_CONFIG credential{}; + credential.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; + credential.Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE; + credential.CertificateFile = &certificate; + credential.CaCertificateFile = caFile_.c_str(); + + configurationLoadCredential(&credential); + } + + void QuicCommunication::configurationOpen() { + const QUIC_SETTINGS settings = settings::QuicSettingsParser::parse(settings_); + const uint32_t settingsSize = sizeof(QUIC_SETTINGS); + + QUIC_STATUS status = quic_->ConfigurationOpen( + registration_, + &alpnBuffer_, + 1, + &settings, + settingsSize, + nullptr, + &config_ + ); + + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", status); + return; + } + } + + void QuicCommunication::configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const { + const QUIC_STATUS status = quic_->ConfigurationLoadCredential(config_, credential); + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status); + return; + } + } + + void QuicCommunication::closeConnection() { + if (!connection_) { + return; + } + + quic_->ConnectionShutdown(connection_, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + + /// Asynchronously waiting for QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE event, then continue in connectionCallback + } + + void QuicCommunication::closeConfiguration() { + if (config_) { + quic_->ConfigurationClose(config_); + } + + config_ = nullptr; + } + + void QuicCommunication::closeRegistration() { + if (registration_) { + quic_->RegistrationClose(registration_); + } + + registration_ = nullptr; + } + + void QuicCommunication::closeMsQuic() { + if (quic_) { + MsQuicClose(quic_); + } + + quic_ = nullptr; + } + + void QuicCommunication::stop() { + closeConnection(); + closeConfiguration(); + closeRegistration(); + closeMsQuic(); + + inboundCv_.notify_all(); + outboundCv_.notify_all(); + + settings::Logger::logInfo("[quic] Connection stopped"); + } + + void QuicCommunication::onMessageDecoded( + std::shared_ptr msg + ) { + { + std::scoped_lock lock(inboundMutex_); + inboundQueue_.push(std::move(msg)); + } + settings::Logger::logDebug("[quic] Notifying receiver thread about dequeued message"); + inboundCv_.notify_one(); + } + + void QuicCommunication::sendViaQuicStream(const ExternalProtocol::ExternalClient& message) { + HQUIC stream{nullptr}; + if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { + settings::Logger::logError("[quic] StreamOpen failed"); + return; + } + + const size_t size = message.ByteSizeLong(); + auto sendBuffer = std::make_unique(size); + + if (!message.SerializeToArray(sendBuffer->storage.data(), static_cast(size))) { + settings::Logger::logError("[quic] Message serialization failed"); + return; + } + + const SendBuffer *raw = sendBuffer.get(); + const QUIC_BUFFER *quicBuf = &raw->buffer; + SendBuffer *quicBufContext = sendBuffer.release(); + + const QUIC_STATUS status = quic_->StreamSend( + stream, + quicBuf, + 1, + /** + * START => Simulates quic_->StreamStart before send + * FIN => Simulates quic_->StreamShutdown after send + */ + QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, + quicBufContext + ); + + if (QUIC_FAILED(status)) { + std::unique_ptr reclaim{quicBufContext}; + + quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); + settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); + return; + } + + auto streamId = getStreamId(stream); + settings::Logger::logDebug("[quic] [stream {}] Message sent", streamId ? *streamId : 0); + } + + QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void *context, + QUIC_CONNECTION_EVENT *event) { + auto *self = static_cast(context); + + switch (event->Type) { + /// Fired when the QUIC handshake is complete and the connection is ready + /// for stream creation and data transfer. + case QUIC_CONNECTION_EVENT_CONNECTED: { + settings::Logger::logInfo("[quic] Connected to server"); + + auto expected = ConnectionState::CONNECTING; + if (self->connectionState_.compare_exchange_strong(expected, ConnectionState::CONNECTED)) { + /// Start sender thread only after connection is fully established + self->senderThread_ = std::jthread(&QuicCommunication::senderLoop, self); + self->outboundCv_.notify_all(); + } + break; + } + + /// Final notification that the connection has been fully shut down. + /// This is the last event delivered for the connection handle. + case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: { + settings::Logger::logInfo("[quic] Connection shutdown complete"); + + self->connectionState_ = ConnectionState::NOT_CONNECTED; + self->outboundCv_.notify_all(); + + if (self->senderThread_.joinable()) { + self->senderThread_.request_stop(); + } + + self->quic_->ConnectionClose(connection); + self->connection_ = nullptr; + break; + } + + /// Peer or transport initiated connection shutdown (error or graceful close). + /// Further sends may fail after this event. + case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: { + settings::Logger::logWarning("[quic] Connection shutdown initiated by peer"); + self->connectionState_ = ConnectionState::CLOSING; + break; + } + + default: { + settings::Logger::logDebug("[quic] Unhandled connection event 0x{:x}", + static_cast(event->Type)); + break; + } + } + + return QUIC_STATUS_SUCCESS; + } + + QUIC_STATUS QUIC_API QuicCommunication::streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event) { + auto *self = static_cast(context); + auto streamId = self->getStreamId(stream); + + switch (event->Type) { + /// Raised when the peer sends stream data and MsQuic delivers received bytes to the application. + case QUIC_STREAM_EVENT_RECEIVE: { + if (event->RECEIVE.BufferCount == 0) { + settings::Logger::logDebug( + "[quic] [stream {}] End of stream received", + streamId ? *streamId : 0 + ); + break; + } + + settings::Logger::logDebug( + "[quic] [stream {}] Received {:d} bytes in {:d} buffers", + streamId ? *streamId : 0, + event->RECEIVE.TotalBufferLength, + event->RECEIVE.BufferCount + ); + + std::vector data; + data.reserve(event->RECEIVE.TotalBufferLength); + + for (uint32_t i = 0; i < event->RECEIVE.BufferCount; ++i) { + const auto &b = event->RECEIVE.Buffers[i]; + data.insert(data.end(), b.Buffer, b.Buffer + b.Length); + } + + auto msg = std::make_shared(); + if (!msg->ParseFromArray(data.data(), static_cast(data.size()))) { + settings::Logger::logError("[quic] Failed to parse ExternalServer message"); + } else { + self->onMessageDecoded(std::move(msg)); + } + + break; + } + + /// Raised when the peer has finished sending on this stream + /// (peer's FIN has been fully received and processed). + case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { + settings::Logger::logDebug("[quic] [stream {}] Peer stream send shutdown", streamId ? *streamId : 0); + break; + } + + /// Raised when the local send direction is fully shut down + /// and the peer has acknowledged the FIN. + case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { + settings::Logger::logDebug("[quic] [stream {}] Stream send shutdown complete", + streamId ? *streamId : 0); + break; + } + + /// Raised after StreamStart completes successfully + /// and the stream becomes active with a valid stream ID. + case QUIC_STREAM_EVENT_START_COMPLETE: { + settings::Logger::logDebug("[quic] [stream {}] Stream start completed", streamId ? *streamId : 0); + break; + } + + /// Raised when a single StreamSend operation completes + /// (data was accepted, acknowledged, or the send was canceled). + case QUIC_STREAM_EVENT_SEND_COMPLETE: { + /** + * This event is raised when MsQuic has finished processing + * a single StreamSend request. + * + * Meaning: + * - MsQuic no longer needs the application-provided buffer: + * - the data has been acknowledged (ACKed) by the peer + * at the QUIC transport level and will not be retransmitted + * - OR the send was canceled (Canceled == TRUE), e.g. due to + * stream or connection shutdown + * + * Reliability semantics: + * - the ACK is strictly a QUIC transport-level acknowledgment + * - it does NOT mean the peer application has read or processed + * the data + * + * Practical consequence: + * - this is the only correct place to safely free the memory + * passed to StreamSend (via ClientContext) + */ + if (event->SEND_COMPLETE.Canceled) { + settings::Logger::logDebug("[quic] [stream {}] Stream send canceled", + streamId ? *streamId : 0); + } else { + settings::Logger::logDebug("[quic] [stream {}] Stream send completed", + streamId ? *streamId : 0); + } + + std::unique_ptr sendBuf{ + static_cast(event->SEND_COMPLETE.ClientContext) + }; + + break; + } + + /// Raised when both send and receive directions are closed + /// and the stream lifecycle is fully complete. + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { + settings::Logger::logDebug("[quic] [stream {}] Stream shutdown complete", streamId ? *streamId : 0); + self->quic_->StreamClose(stream); + break; + } + + default: { + settings::Logger::logDebug("[quic] [stream {}] Unhandled stream event 0x{:x}", streamId ? *streamId : 0, + static_cast(event->Type)); + break; + } + } + + return QUIC_STATUS_SUCCESS; + } + + void QuicCommunication::senderLoop() { + settings::Logger::logDebug("[quic] Sender thread loop started"); + + while (connectionState_.load() == ConnectionState::CONNECTED) { + std::unique_ptr msg; + + std::unique_lock lock(outboundMutex_); + + settings::Logger::logDebug("[quic] Sender thread loop waiting for outbound queue"); + outboundCv_.wait(lock, [this] { + return !outboundQueue_.empty() || + connectionState_.load() != ConnectionState::CONNECTED; + }); + + if (connectionState_.load() != ConnectionState::CONNECTED) { + break; + } + + settings::Logger::logDebug("[quic] Sender thread loop sending outbound queue"); + msg = std::move(outboundQueue_.front()); + outboundQueue_.pop(); + lock.unlock(); + + sendViaQuicStream(*msg); + } + } + + std::optional QuicCommunication::getStreamId(HQUIC stream) { + uint64_t streamId = 0; + uint32_t streamIdLen = sizeof(streamId); + + if (QUIC_FAILED(quic_->GetParam( + stream, + QUIC_PARAM_STREAM_ID, + &streamIdLen, + &streamId))) { + return std::nullopt; + } + + return streamId; + } + + std::string QuicCommunication::getProtocolSettingsString( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ) { + const auto &raw = settings.protocolSettings.at(std::string(key)); + + if (nlohmann::json::accept(raw)) { + auto j = nlohmann::json::parse(raw); + if (j.is_string()) { + return j.get(); + } + } + return raw; + } +} diff --git a/source/bringauto/external_client/connection/messages/NotAckedStatus.cpp b/source/bringauto/external_client/connection/messages/NotAckedStatus.cpp index 354297b8..1f0fb137 100644 --- a/source/bringauto/external_client/connection/messages/NotAckedStatus.cpp +++ b/source/bringauto/external_client/connection/messages/NotAckedStatus.cpp @@ -5,8 +5,6 @@ #include #include -#include - namespace bringauto::external_client::connection::messages { @@ -25,7 +23,7 @@ void NotAckedStatus::cancelTimer() { timer_.cancel(); } -void NotAckedStatus::timeoutHandler(const std::function &endConnectionFunc) { +void NotAckedStatus::timeoutHandler(const std::function &endConnectionFunc) const { std::string loggingStr("Status response Timeout (" + std::to_string(status_.messagecounter()) + "):"); std::unique_lock lock(responseHandledMutex_); if(responseHandled_.load()) { diff --git a/source/bringauto/external_client/connection/messages/SentMessagesHandler.cpp b/source/bringauto/external_client/connection/messages/SentMessagesHandler.cpp index c50ef638..9bbb4fec 100644 --- a/source/bringauto/external_client/connection/messages/SentMessagesHandler.cpp +++ b/source/bringauto/external_client/connection/messages/SentMessagesHandler.cpp @@ -23,7 +23,7 @@ void SentMessagesHandler::addNotAckedStatus(const ExternalProtocol::Status &stat int SentMessagesHandler::acknowledgeStatus(const ExternalProtocol::StatusResponse &statusResponse) { std::scoped_lock lock {ackMutex_}; - auto responseCounter = getStatusResponseCounter(statusResponse); + const auto responseCounter = getStatusResponseCounter(statusResponse); for(auto i = 0u; i < notAckedStatuses_.size(); ++i) { if(getStatusCounter(notAckedStatuses_[i]->getStatus()) == responseCounter) { notAckedStatuses_[i]->cancelTimer(); @@ -55,7 +55,7 @@ void SentMessagesHandler::addDeviceAsConnected(const structures::DeviceIdentific } void SentMessagesHandler::deleteConnectedDevice(const structures::DeviceIdentification &device) { - auto it = std::find(connectedDevices_.begin(), connectedDevices_.end(), device); + const auto it = std::find(connectedDevices_.begin(), connectedDevices_.end(), device); if(it != connectedDevices_.end()) { connectedDevices_.erase(it); } else { @@ -74,7 +74,7 @@ bool SentMessagesHandler::isAnyDeviceConnected() const { } void SentMessagesHandler::clearAllTimers() { - for(auto ¬AckedStatus: notAckedStatuses_) { + for(const auto ¬AckedStatus: notAckedStatuses_) { notAckedStatus->cancelTimer(); } responseHandled_ = false; diff --git a/source/bringauto/internal_server/InternalServer.cpp b/source/bringauto/internal_server/InternalServer.cpp index dd1aa924..54282f32 100644 --- a/source/bringauto/internal_server/InternalServer.cpp +++ b/source/bringauto/internal_server/InternalServer.cpp @@ -7,14 +7,14 @@ namespace bringauto::internal_server { -using log = bringauto::settings::Logger; +using log = settings::Logger; void InternalServer::run() { log::logInfo("Internal server started, constants used: fleet_protocol_timeout_length: {}, queue_timeout_length: {}", settings::fleet_protocol_timeout_length.count(), settings::queue_timeout_length.count()); - boost::asio::ip::tcp::endpoint endpoint { boost::asio::ip::tcp::v4(), context_->settings->port }; + const boost::asio::ip::tcp::endpoint endpoint { boost::asio::ip::tcp::v4(), context_->settings->port }; acceptor_.open(endpoint.protocol()); acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); acceptor_.bind(endpoint); @@ -33,11 +33,15 @@ void InternalServer::addAsyncAccept() { log::logError("Error in addAsyncAccept(): {}", error.message()); return; } - boost::asio::socket_base::keep_alive option(true); - connection->socket.set_option(option); + + const boost::asio::socket_base::keep_alive keepAliveOption(true); + connection->socket.set_option(keepAliveOption); + const boost::asio::ip::tcp::no_delay noDelayOption(true); + connection->socket.set_option(noDelayOption); + log::logInfo("Accepted connection with Internal Client, " "connection's ip address is {}", - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); addAsyncReceive(connection); addAsyncAccept(); }); @@ -74,14 +78,14 @@ void InternalServer::asyncReceiveHandler( } else { log::logWarning( "Internal Client with ip address {} has been disconnected. Reason: {}", - connection->socket.remote_endpoint().address().to_string(), error.message()); + connection->remoteEndpointAddress(), error.message()); } std::lock_guard lock(serverMutex_); removeConnFromMap(connection); return; } - bool result = processBufferData(connection, bytesTransferred); + const bool result = processBufferData(connection, bytesTransferred); if(result) { addAsyncReceive(connection); } else { @@ -98,19 +102,19 @@ bool InternalServer::processBufferData( "Error in processBufferData(...): bufferOffset: {} is greater than bytesTransferred: {}, " "Invalid bufferOffset: {} received from Internal Client, " "connection's ip address is {}", bufferOffset, bytesTransferred, - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } auto &completeMessageSize = connection->connContext.completeMessageSize; auto &completeMessage = connection->connContext.completeMessage; auto &buffer = connection->connContext.buffer; - const uint8_t headerSize = settings::header; + constexpr uint8_t headerSize = settings::header; if(bytesTransferred < headerSize && completeMessageSize == 0) { log::logError( "Error in processBufferData(...): Incomplete header received from Internal Client, " - "connection's ip address is {}", connection->socket.remote_endpoint().address().to_string()); + "connection's ip address is {}", connection->remoteEndpointAddress()); return false; } @@ -121,7 +125,7 @@ bool InternalServer::processBufferData( uint32_t size { 0 }; - std::copy(dataBegin, dataBegin + headerSize, (uint8_t *)&size); + std::copy_n(dataBegin, headerSize, reinterpret_cast(&size)); completeMessageSize = size; dataBegin = dataBegin + headerSize; @@ -129,13 +133,13 @@ bool InternalServer::processBufferData( if(bytesLeft < headerSize) { log::logError( "Error in processBufferData(...): Incomplete header received from Internal Client, " - "connection's ip address is {}", connection->socket.remote_endpoint().address().to_string()); + "connection's ip address is {}", connection->remoteEndpointAddress()); return false; } bytesLeft -= headerSize; } const auto messageBytesLeft = std::min(completeMessageSize - completeMessage.size(), bytesLeft); - auto dataEnd = dataBegin + messageBytesLeft; + const auto dataEnd = dataBegin + messageBytesLeft; std::copy(dataBegin, dataEnd, std::back_inserter(completeMessage)); @@ -150,7 +154,7 @@ bool InternalServer::processBufferData( if(bytesLeft < messageBytesLeft) { log::logError("Error in processBufferData(...): messageBytesLeft: {} is greater than bytesLeft: {}, " "connection's ip address is {}", messageBytesLeft, bytesLeft, - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } bytesLeft -= messageBytesLeft; @@ -158,14 +162,14 @@ bool InternalServer::processBufferData( if(bytesTransferred < bytesLeft) { log::logError("Error in processBufferData(...): bytesLeft: {} is greater than bytesTransferred: {}, " "connection's ip address is {}", bytesLeft, bytesTransferred, - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } if(bytesLeft && !processBufferData(connection, bytesLeft, bytesTransferred - bytesLeft)) { log::logError("Error in processBufferData(...): " "Received extra invalid bytes of data: {} from Internal Client, " "connection's ip address is {}", bytesLeft, - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } return true; @@ -178,7 +182,7 @@ bool InternalServer::handleMessage(const std::shared_ptr connection->connContext.completeMessage.size())) { log::logError( "Error in handleMessage(...): message received from Internal Client cannot be parsed, " - "connection's ip address is {}", connection->socket.remote_endpoint().address().to_string()); + "connection's ip address is {}", connection->remoteEndpointAddress()); return false; } if(client.has_devicestatus()) { @@ -192,7 +196,7 @@ bool InternalServer::handleMessage(const std::shared_ptr } else { log::logError( "Error in handleMessage(...): message received from Internal Client cannot be parsed, " - "connection's ip address is {}", connection->socket.remote_endpoint().address().to_string()); + "connection's ip address is {}", connection->remoteEndpointAddress()); return false; } std::unique_lock lk(connection->connectionMutex); @@ -204,19 +208,19 @@ bool InternalServer::handleMessage(const std::shared_ptr log::logError("Error in handleMessage(...): " "Module Handler did not respond to a message in time, " "connection's ip address is {}", - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } return !context_->ioContext.stopped(); } bool InternalServer::handleStatus(const std::shared_ptr &connection, - const InternalProtocol::InternalClient &client) { + const InternalProtocol::InternalClient &client) const { if(!connection->ready) { log::logError("Error in handleStatus(...): " "received status from Internal Client without being connected, " "connection's ip address is {}", - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } std::lock_guard lk(connection->connectionMutex); @@ -232,12 +236,12 @@ bool InternalServer::handleConnection(const std::shared_ptrsocket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } - structures::DeviceIdentification deviceId { client.deviceconnect().device() }; - auto existingConnection = findConnection(deviceId); + const structures::DeviceIdentification deviceId { client.deviceconnect().device() }; + const auto existingConnection = findConnection(deviceId); if(not existingConnection) { connectNewDevice(connection, client, deviceId); @@ -258,7 +262,7 @@ bool InternalServer::handleConnection(const std::shared_ptr &connection, const InternalProtocol::InternalClient &connect, const structures::DeviceIdentification &deviceId) { - auto message = common_utils::ProtobufUtils::createInternalServerConnectResponseMessage( + const auto message = common_utils::ProtobufUtils::createInternalServerConnectResponseMessage( connect.deviceconnect().device(), InternalProtocol::DeviceConnectResponse_ResponseType_HIGHER_PRIORITY_ALREADY_CONNECTED); log::logInfo( @@ -294,7 +298,7 @@ void InternalServer::respondWithHigherPriorityConnected(const std::shared_ptr &connection, const InternalProtocol::InternalClient &connect, const structures::DeviceIdentification &deviceId) { - auto message = common_utils::ProtobufUtils::createInternalServerConnectResponseMessage( + const auto message = common_utils::ProtobufUtils::createInternalServerConnectResponseMessage( connect.deviceconnect().device(), InternalProtocol::DeviceConnectResponse_ResponseType_ALREADY_CONNECTED); log::logInfo( @@ -309,7 +313,7 @@ void InternalServer::respondWithAlreadyConnected(const std::shared_ptr &newConnection, const InternalProtocol::InternalClient &connect, const structures::DeviceIdentification &deviceId) { - auto oldConnection = findConnection(deviceId); + const auto oldConnection = findConnection(deviceId); if(oldConnection) { removeConnFromMap(oldConnection); } @@ -335,22 +339,22 @@ bool InternalServer::sendResponse(const std::shared_ptr log::logError("Error in sendResponse(...): " "Cannot write message header to Internal Client, " "connection's ip address is {}", - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } try { log::logDebug("Sending response to Internal Client, " "connection's ip address is {}", - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); const auto dataWSize = connection->socket.write_some(boost::asio::buffer(data)); if(dataWSize != header) { log::logError("Error in sendResponse(...): " "Cannot write data to Internal Client, " "connection's ip address is {}", - connection->socket.remote_endpoint().address().to_string()); + connection->remoteEndpointAddress()); return false; } - } catch(const boost::exception &e) { + } catch(const boost::exception &) { log::logError("Error in sendResponse(...): " "Cannot write data to Internal Client"); return false; @@ -381,7 +385,7 @@ void InternalServer::validateResponse(const InternalProtocol::InternalServer &me deviceId = message.deviceconnectresponse().device(); } std::lock_guard lock(serverMutex_); - auto connection = findConnection(deviceId); + const auto connection = findConnection(deviceId); if(connection && connection->deviceId->getPriority() == deviceId.getPriority()) { sendResponse(connection, message); @@ -400,10 +404,10 @@ void InternalServer::removeConnFromMap(const std::shared_ptrdeviceId == nullptr) { return; } - auto it = std::find_if(connectedDevices_.begin(), connectedDevices_.end(), - [&connection](const std::shared_ptr &toCompare) { - return connection->deviceId->isSame(toCompare->deviceId); - }); + const auto it = std::find_if(connectedDevices_.begin(), connectedDevices_.end(), + [&connection](const std::shared_ptr &toCompare) { + return connection->deviceId->isSame(toCompare->deviceId); + }); if(it != connectedDevices_.end() && (*it)->deviceId->getPriority() == connection->deviceId->getPriority()) { connectedDevices_.erase(it); @@ -420,7 +424,7 @@ void InternalServer::removeConnFromMap(const std::shared_ptr InternalServer::findConnection(const structures::DeviceIdentification &deviceId) { std::shared_ptr connectionFound {}; - auto it = std::find_if(connectedDevices_.begin(), connectedDevices_.end(), + const auto it = std::find_if(connectedDevices_.begin(), connectedDevices_.end(), [&deviceId](const auto& toCompare) { return deviceId.isSame(toCompare->deviceId); }); diff --git a/source/bringauto/modules/ModuleHandler.cpp b/source/bringauto/modules/ModuleHandler.cpp index 0b23e2e5..5059cdb5 100644 --- a/source/bringauto/modules/ModuleHandler.cpp +++ b/source/bringauto/modules/ModuleHandler.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include @@ -11,7 +11,7 @@ namespace bringauto::modules { namespace ip = InternalProtocol; -void ModuleHandler::destroy() { +void ModuleHandler::destroy() const { while(not fromInternalQueue_->empty()) { auto &message = fromInternalQueue_->front(); if(message.disconnected()) { @@ -22,13 +22,13 @@ void ModuleHandler::destroy() { settings::Logger::logInfo("Module handler stopped"); } -void ModuleHandler::run() { +void ModuleHandler::run() const { settings::Logger::logInfo("Module handler started, constants used: queue_timeout_length: {}, status_aggregation_timeout: {}", settings::queue_timeout_length.count(), settings::status_aggregation_timeout.count()); handleMessages(); } -void ModuleHandler::handleMessages() { +void ModuleHandler::handleMessages() const { while(not context_->ioContext.stopped()) { if(fromInternalQueue_->waitForValueWithTimeout(settings::queue_timeout_length)) { checkTimeoutedMessages(); @@ -48,12 +48,12 @@ void ModuleHandler::handleMessages() { } } -void ModuleHandler::checkTimeoutedMessages(){ +void ModuleHandler::checkTimeoutedMessages() const { for (const auto& [key, statusAggregator] : moduleLibrary_.statusAggregators) { auto moduleLibraryHandler = moduleLibrary_.moduleLibraryHandlers.at(key); if(statusAggregator->getTimeoutedMessageReady()){ std::list unique_devices {}; - int ret = statusAggregator->get_unique_devices(unique_devices); + const int ret = statusAggregator->get_unique_devices(unique_devices); if (ret == NOT_OK) { settings::Logger::logError("Could not get unique devices in checkTimeoutedMessages"); return; @@ -62,7 +62,7 @@ void ModuleHandler::checkTimeoutedMessages(){ for (auto &device: unique_devices) { while(true) { Buffer aggregatedStatusBuffer {}; - int remainingMessages = statusAggregator->get_aggregated_status(aggregatedStatusBuffer, device); + const int remainingMessages = statusAggregator->get_aggregated_status(aggregatedStatusBuffer, device); if(remainingMessages < 0) { break; } @@ -85,10 +85,10 @@ void ModuleHandler::checkTimeoutedMessages(){ } } -void ModuleHandler::handleDisconnect(const structures::DeviceIdentification& deviceId) { +void ModuleHandler::handleDisconnect(const structures::DeviceIdentification& deviceId) const { const auto &moduleNumber = deviceId.getModule(); const std::string& deviceName { deviceId.getDeviceName() }; - auto &statusAggregators = moduleLibrary_.statusAggregators; + const auto &statusAggregators = moduleLibrary_.statusAggregators; if(not statusAggregators.contains(moduleNumber)) { settings::Logger::logWarning("Module number: {} is not supported", moduleNumber); @@ -106,8 +106,8 @@ void ModuleHandler::handleDisconnect(const structures::DeviceIdentification& dev settings::Logger::logWarning("Force aggregation failed on device: {} with error code: {}", deviceName, ret); return; } - auto device = structures::DeviceIdentification(deviceId); - auto internalProtocolDevice = device.convertToIPDevice(); + const auto device = structures::DeviceIdentification(deviceId); + const auto internalProtocolDevice = device.convertToIPDevice(); sendAggregatedStatus(deviceId, internalProtocolDevice, true); statusAggregator->remove_device(deviceId); @@ -116,7 +116,7 @@ void ModuleHandler::handleDisconnect(const structures::DeviceIdentification& dev } void ModuleHandler::sendAggregatedStatus(const structures::DeviceIdentification &deviceId, const ip::Device &device, - bool disconnected) { + bool disconnected) const { const auto &statusAggregator = moduleLibrary_.statusAggregators.at(deviceId.getModule()); Buffer aggregatedStatusBuffer {}; statusAggregator->get_aggregated_status(aggregatedStatusBuffer, deviceId); @@ -127,11 +127,11 @@ void ModuleHandler::sendAggregatedStatus(const structures::DeviceIdentification checkExternalQueueSize(); } -void ModuleHandler::handleConnect(const ip::DeviceConnect &connect) { +void ModuleHandler::handleConnect(const ip::DeviceConnect &connect) const { const auto &device = connect.device(); const auto &moduleNumber = device.module(); const auto &deviceName = device.devicename(); - auto &statusAggregators = moduleLibrary_.statusAggregators; + const auto &statusAggregators = moduleLibrary_.statusAggregators; settings::Logger::logInfo("Module handler received Connect message from device: {}", deviceName); if(not statusAggregators.contains(moduleNumber)) { @@ -147,7 +147,7 @@ void ModuleHandler::handleConnect(const ip::DeviceConnect &connect) { return; } - auto deviceId = structures::DeviceIdentification(device); + const auto deviceId = structures::DeviceIdentification(device); if(statusAggregator->is_device_valid(deviceId) == OK) { settings::Logger::logInfo("Device {} is replaced by device with higher priority", deviceName); } @@ -156,13 +156,13 @@ void ModuleHandler::handleConnect(const ip::DeviceConnect &connect) { } void -ModuleHandler::sendConnectResponse(const ip::Device &device, ip::DeviceConnectResponse_ResponseType response_type) { - auto response = common_utils::ProtobufUtils::createInternalServerConnectResponseMessage(device, response_type); +ModuleHandler::sendConnectResponse(const ip::Device &device, ip::DeviceConnectResponse_ResponseType response_type) const { + const auto response = common_utils::ProtobufUtils::createInternalServerConnectResponseMessage(device, response_type); toInternalQueue_->pushAndNotify(structures::ModuleHandlerMessage(false,response)); settings::Logger::logInfo("New device {} is trying to connect, sending response {}", device.devicename(), static_cast(response_type)); } -void ModuleHandler::handleStatus(const ip::DeviceStatus &status) { +void ModuleHandler::handleStatus(const ip::DeviceStatus &status) const { const auto &device = status.device(); const auto &moduleNumber = device.module(); const auto &deviceName = device.devicename(); @@ -173,11 +173,11 @@ void ModuleHandler::handleStatus(const ip::DeviceStatus &status) { settings::Logger::logWarning("Module number: {} is not supported", static_cast(moduleNumber)); return; } - auto statusAggregator = statusAggregators[moduleNumber]; + const auto statusAggregator = statusAggregators[moduleNumber]; const auto moduleHandler = moduleLibrary_.moduleLibraryHandlers.at(moduleNumber); const auto &statusData = status.statusdata(); - auto statusBuffer = moduleHandler->constructBuffer(statusData.size()); + const auto statusBuffer = moduleHandler->constructBuffer(statusData.size()); if (!statusBuffer.isEmpty()) { common_utils::ProtobufUtils::copyStatusToBuffer(status, statusBuffer); } @@ -194,11 +194,11 @@ void ModuleHandler::handleStatus(const ip::DeviceStatus &status) { settings::Logger::logWarning("Add status to aggregator failed with return code: {}", addStatusToAggregatorRc); return; } - + Buffer commandBuffer {}; int getCommandRc = statusAggregator->get_command(statusBuffer, deviceId, commandBuffer); if(getCommandRc == OK) { - auto deviceCommandMessage = common_utils::ProtobufUtils::createInternalServerCommandMessage(device, + const auto deviceCommandMessage = common_utils::ProtobufUtils::createInternalServerCommandMessage(device, commandBuffer); toInternalQueue_->pushAndNotify(structures::ModuleHandlerMessage(false, deviceCommandMessage)); settings::Logger::logDebug("Module handler successfully retrieved command and sent it to device: {}", deviceName); @@ -213,7 +213,7 @@ void ModuleHandler::handleStatus(const ip::DeviceStatus &status) { } } -void ModuleHandler::checkExternalQueueSize() { +void ModuleHandler::checkExternalQueueSize() const { if(toExternalQueue_->size() > settings::max_external_queue_size) { settings::Logger::logError("External queue size is too big, external client is not handling messages"); //temporarily disabled to verify if the bug related to deadlocks is fixed diff --git a/source/bringauto/modules/ModuleManagerLibraryHandlerAsync.cpp b/source/bringauto/modules/ModuleManagerLibraryHandlerAsync.cpp new file mode 100644 index 00000000..ffea062c --- /dev/null +++ b/source/bringauto/modules/ModuleManagerLibraryHandlerAsync.cpp @@ -0,0 +1,235 @@ +#include + +#include + +#include + + + +namespace bringauto::modules { + +ModuleManagerLibraryHandlerAsync::ModuleManagerLibraryHandlerAsync(const std::filesystem::path &moduleBinaryPath, const int moduleNumber) : + moduleBinaryPath_ { moduleBinaryPath } { + aeronClient.connect(moduleNumber); + deallocate_ = [this](struct buffer *buffer) { + this->deallocate(buffer); + }; +} + +ModuleManagerLibraryHandlerAsync::~ModuleManagerLibraryHandlerAsync() { + if (moduleBinaryProcess_.valid()) { + ::kill(moduleBinaryProcess_.id(), SIGTERM); + moduleBinaryProcess_.wait(); + } +} + +void ModuleManagerLibraryHandlerAsync::loadLibrary(const std::filesystem::path &path) { + moduleBinaryProcess_ = boost::process::child { moduleBinaryPath_.string(), "-m", path.string() }; + if (!moduleBinaryProcess_.valid()) { + throw std::runtime_error { "Failed to start module binary " + moduleBinaryPath_.string() }; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); // TODO Not sure how much time is needed. +} + +int ModuleManagerLibraryHandlerAsync::getModuleNumber() { + std::lock_guard lock { getModuleNumberMutex_ }; + return aeronClient.callFunc(fleet_protocol::cxx::getModuleNumberAsync).value_or(NOT_OK); +} + +int ModuleManagerLibraryHandlerAsync::isDeviceTypeSupported(unsigned int device_type) { + std::lock_guard lock { isDeviceTypeSupportedMutex_ }; + return aeronClient.callFunc(fleet_protocol::cxx::isDeviceTypeSupportedAsync, + device_type).value_or(NOT_OK); +} + +int ModuleManagerLibraryHandlerAsync::sendStatusCondition(const Buffer ¤t_status, + const Buffer &new_status, + unsigned int device_type) { + std::lock_guard lock { isDeviceTypeSupportedMutex_ }; + fleet_protocol::cxx::ConvertibleBuffer current_status_raw_buffer; + fleet_protocol::cxx::ConvertibleBuffer new_status_raw_buffer; + + if (current_status.isAllocated()) { + current_status_raw_buffer = current_status.getStructBuffer(); + } + if (new_status.isAllocated()) { + new_status_raw_buffer = new_status.getStructBuffer(); + } + + return aeronClient.callFunc(fleet_protocol::cxx::sendStatusConditionAsync, + current_status_raw_buffer, + new_status_raw_buffer, + device_type).value_or(NOT_OK); +} + +int ModuleManagerLibraryHandlerAsync::generateCommand(Buffer &generated_command, + const Buffer &new_status, + const Buffer ¤t_status, + const Buffer ¤t_command, unsigned int device_type) { + std::lock_guard lock { generateCommandMutex_ }; + fleet_protocol::cxx::ConvertibleBuffer new_status_raw_buffer; + fleet_protocol::cxx::ConvertibleBuffer current_status_raw_buffer; + fleet_protocol::cxx::ConvertibleBuffer current_command_raw_buffer; + + if (new_status.isAllocated()) { + new_status_raw_buffer = new_status.getStructBuffer(); + } + if (current_status.isAllocated()) { + current_status_raw_buffer = current_status.getStructBuffer(); + } + if (current_command.isAllocated()) { + current_command_raw_buffer = current_command.getStructBuffer(); + } + + auto ret = aeronClient.callFunc(fleet_protocol::cxx::generateCommandAsync, + new_status_raw_buffer, + current_status_raw_buffer, + current_command_raw_buffer, + device_type); + + if (!ret.has_value()) { + return NOT_OK; + } + + if (ret.value().returnCode == OK) { + generated_command = constructBufferByTakeOwnership(ret.value().buffer); + } else { + generated_command = constructBuffer(); + } + return ret.value().returnCode; +} + +int ModuleManagerLibraryHandlerAsync::aggregateStatus(Buffer &aggregated_status, + const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) { + std::lock_guard lock { aggregateStatusMutex_ }; + fleet_protocol::cxx::ConvertibleBuffer current_status_raw_buffer; + fleet_protocol::cxx::ConvertibleBuffer new_status_raw_buffer; + + if (current_status.isAllocated()) { + current_status_raw_buffer = current_status.getStructBuffer(); + } + if (new_status.isAllocated()) { + new_status_raw_buffer = new_status.getStructBuffer(); + } + + auto ret = aeronClient.callFunc(fleet_protocol::cxx::aggregateStatusAsync, + current_status_raw_buffer, + new_status_raw_buffer, + device_type); + if (!ret.has_value()) { + return NOT_OK; + } + if (ret.value().returnCode == OK) { + aggregated_status = constructBufferByTakeOwnership(ret.value().buffer); + } else { + // Needed to properly free the allocated buffer memory + auto invalid_buffer = constructBufferByTakeOwnership(ret.value().buffer); + aggregated_status = current_status; + } + return ret.value().returnCode; +} + +int ModuleManagerLibraryHandlerAsync::aggregateError(Buffer &error_message, + const Buffer ¤t_error_message, + const Buffer &status, unsigned int device_type) { + std::lock_guard lock { aggregateErrorMutex_ }; + fleet_protocol::cxx::ConvertibleBuffer current_error_raw_buffer; + fleet_protocol::cxx::ConvertibleBuffer status_raw_buffer; + + if (current_error_message.isAllocated()) { + current_error_raw_buffer = current_error_message.getStructBuffer(); + } + if (status.isAllocated()) { + status_raw_buffer = status.getStructBuffer(); + } + + auto ret = aeronClient.callFunc(fleet_protocol::cxx::aggregateErrorAsync, + current_error_raw_buffer, + status_raw_buffer, + device_type); + if (!ret.has_value()) { + return NOT_OK; + } + if (ret.value().returnCode == OK) { + error_message = constructBufferByTakeOwnership(ret.value().buffer); + } else { + error_message = constructBuffer(); + } + return ret.value().returnCode; +} + +int ModuleManagerLibraryHandlerAsync::generateFirstCommand(Buffer &default_command, unsigned int device_type) { + std::lock_guard lock { generateFirstCommandMutex_ }; + auto ret = aeronClient.callFunc(fleet_protocol::cxx::generateFirstCommandAsync, device_type); + if (!ret.has_value()) { + return NOT_OK; + } + if (ret.value().returnCode == OK) { + default_command = constructBufferByTakeOwnership(ret.value().buffer); + } else { + default_command = constructBuffer(); + } + return ret.value().returnCode; +} + +int ModuleManagerLibraryHandlerAsync::statusDataValid(const Buffer &status, unsigned int device_type) { + std::lock_guard lock { statusDataValidMutex_ }; + fleet_protocol::cxx::ConvertibleBuffer status_raw_buffer; + if (status.isAllocated()) { + status_raw_buffer = status.getStructBuffer(); + } + + return aeronClient.callFunc(fleet_protocol::cxx::statusDataValidAsync, + status_raw_buffer, + device_type).value_or(NOT_OK); +} + +int ModuleManagerLibraryHandlerAsync::commandDataValid(const Buffer &command, unsigned int device_type) { + std::lock_guard lock { commandDataValidMutex_ }; + fleet_protocol::cxx::ConvertibleBuffer command_raw_buffer; + if (command.isAllocated()) { + command_raw_buffer = command.getStructBuffer(); + } + + return aeronClient.callFunc(fleet_protocol::cxx::commandDataValidAsync, + command_raw_buffer, + device_type).value_or(NOT_OK); +} + +int ModuleManagerLibraryHandlerAsync::allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const { + try{ + buffer_pointer->data = new char[size_in_bytes](); + } catch(std::bad_alloc&){ + return NOT_OK; + } + buffer_pointer->size_in_bytes = size_in_bytes; + return OK; +} + +void ModuleManagerLibraryHandlerAsync::deallocate(struct buffer *buffer) const { + delete[] static_cast(buffer->data); + buffer->data = nullptr; + buffer->size_in_bytes = 0; +} + +Buffer ModuleManagerLibraryHandlerAsync::constructBuffer(std::size_t size) { + if (size == 0) { + return Buffer {}; + } + struct ::buffer buff {}; + buff.size_in_bytes = size; + if(allocate(&buff, size) != OK) { + throw std::bad_alloc {}; + } + return { buff, deallocate_ }; +} + +Buffer ModuleManagerLibraryHandlerAsync::constructBufferByTakeOwnership(struct ::buffer &buffer) { + if (buffer.data == nullptr) { + throw Buffer::BufferNotAllocated { "Buffer not allocated - cannot take ownership" }; + } + return { buffer, deallocate_ }; +} + +} diff --git a/source/bringauto/modules/ModuleManagerLibraryHandler.cpp b/source/bringauto/modules/ModuleManagerLibraryHandlerLocal.cpp similarity index 69% rename from source/bringauto/modules/ModuleManagerLibraryHandler.cpp rename to source/bringauto/modules/ModuleManagerLibraryHandlerLocal.cpp index 8296e556..193dc5cd 100644 --- a/source/bringauto/modules/ModuleManagerLibraryHandler.cpp +++ b/source/bringauto/modules/ModuleManagerLibraryHandlerLocal.cpp @@ -1,7 +1,8 @@ -#include - +#include #include +#include + #include #include @@ -17,16 +18,16 @@ struct FunctionTypeDeducer> { using fncptr = R (*)(Args...); }; -using log = bringauto::settings::Logger; +using log = settings::Logger; -ModuleManagerLibraryHandler::~ModuleManagerLibraryHandler() { +ModuleManagerLibraryHandlerLocal::~ModuleManagerLibraryHandlerLocal() { if(module_ != nullptr) { dlclose(module_); module_ = nullptr; } } -void ModuleManagerLibraryHandler::loadLibrary(const std::filesystem::path &path) { +void ModuleManagerLibraryHandlerLocal::loadLibrary(const std::filesystem::path &path) { module_ = dlmopen(LM_ID_NEWLM, path.c_str(), RTLD_LAZY); if(module_ == nullptr) { throw std::runtime_error {"Unable to load library " + path.string() + dlerror()}; @@ -56,25 +57,25 @@ void ModuleManagerLibraryHandler::loadLibrary(const std::filesystem::path &path) log::logDebug("Library " + path.string() + " was successfully loaded"); } -void *ModuleManagerLibraryHandler::checkFunction(const char *functionName) { - auto function = dlsym(module_, functionName); +void *ModuleManagerLibraryHandlerLocal::checkFunction(const char *functionName) const { + const auto function = dlsym(module_, functionName); if(not function) { throw std::runtime_error {"Function " + std::string(functionName) + " is not included in library"}; } return function; } -int ModuleManagerLibraryHandler::getModuleNumber() { +int ModuleManagerLibraryHandlerLocal::getModuleNumber() { return getModuleNumber_(); } -int ModuleManagerLibraryHandler::isDeviceTypeSupported(unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::isDeviceTypeSupported(unsigned int device_type) { return isDeviceTypeSupported_(device_type); } -int ModuleManagerLibraryHandler::sendStatusCondition(const Buffer current_status, - const Buffer new_status, - unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::sendStatusCondition(const Buffer ¤t_status, + const Buffer &new_status, + unsigned int device_type) { struct ::buffer current_status_raw_buffer {}; struct ::buffer new_status_raw_buffer {}; @@ -88,10 +89,10 @@ int ModuleManagerLibraryHandler::sendStatusCondition(const Buffer current_status return sendStatusCondition_(current_status_raw_buffer, new_status_raw_buffer, device_type); } -int ModuleManagerLibraryHandler::generateCommand(Buffer &generated_command, - const Buffer new_status, - const Buffer current_status, - const Buffer current_command, unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::generateCommand(Buffer &generated_command, + const Buffer &new_status, + const Buffer ¤t_status, + const Buffer ¤t_command, unsigned int device_type) { struct ::buffer raw_buffer {}; struct ::buffer new_status_raw_buffer {}; struct ::buffer current_status_raw_buffer {}; @@ -117,9 +118,9 @@ int ModuleManagerLibraryHandler::generateCommand(Buffer &generated_command, return ret; } -int ModuleManagerLibraryHandler::aggregateStatus(Buffer &aggregated_status, - const Buffer current_status, - const Buffer new_status, unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::aggregateStatus(Buffer &aggregated_status, + const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) { struct ::buffer raw_buffer {}; struct ::buffer current_status_raw_buffer {}; struct ::buffer new_status_raw_buffer {}; @@ -131,7 +132,7 @@ int ModuleManagerLibraryHandler::aggregateStatus(Buffer &aggregated_status, new_status_raw_buffer = new_status.getStructBuffer(); } - int ret = aggregateStatus_(&raw_buffer, current_status_raw_buffer, new_status_raw_buffer, device_type); + const int ret = aggregateStatus_(&raw_buffer, current_status_raw_buffer, new_status_raw_buffer, device_type); if (ret == OK) { aggregated_status = constructBufferByTakeOwnership(raw_buffer); } else { @@ -140,9 +141,9 @@ int ModuleManagerLibraryHandler::aggregateStatus(Buffer &aggregated_status, return ret; } -int ModuleManagerLibraryHandler::aggregateError(Buffer &error_message, - const Buffer current_error_message, - const Buffer status, unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::aggregateError(Buffer &error_message, + const Buffer ¤t_error_message, + const Buffer &status, unsigned int device_type) { struct ::buffer raw_buffer {}; struct ::buffer current_error_raw_buffer {}; @@ -155,7 +156,7 @@ int ModuleManagerLibraryHandler::aggregateError(Buffer &error_message, status_raw_buffer = status.getStructBuffer(); } - int ret = aggregateError_(&raw_buffer, current_error_raw_buffer, status_raw_buffer, device_type); + const int ret = aggregateError_(&raw_buffer, current_error_raw_buffer, status_raw_buffer, device_type); if (ret == OK) { error_message = constructBufferByTakeOwnership(raw_buffer); } else { @@ -164,9 +165,9 @@ int ModuleManagerLibraryHandler::aggregateError(Buffer &error_message, return ret; } -int ModuleManagerLibraryHandler::generateFirstCommand(Buffer &default_command, unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::generateFirstCommand(Buffer &default_command, unsigned int device_type) { struct ::buffer raw_buffer {}; - int ret = generateFirstCommand_(&raw_buffer, device_type); + const int ret = generateFirstCommand_(&raw_buffer, device_type); if (ret == OK) { default_command = constructBufferByTakeOwnership(raw_buffer); } else { @@ -175,7 +176,7 @@ int ModuleManagerLibraryHandler::generateFirstCommand(Buffer &default_command, u return ret; } -int ModuleManagerLibraryHandler::statusDataValid(const Buffer status, unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::statusDataValid(const Buffer &status, unsigned int device_type) { struct ::buffer raw_buffer {}; if (status.isAllocated()) { raw_buffer = status.getStructBuffer(); @@ -183,7 +184,7 @@ int ModuleManagerLibraryHandler::statusDataValid(const Buffer status, unsigned i return statusDataValid_(raw_buffer, device_type); } -int ModuleManagerLibraryHandler::commandDataValid(const Buffer command, unsigned int device_type) { +int ModuleManagerLibraryHandlerLocal::commandDataValid(const Buffer &command, unsigned int device_type) { struct ::buffer raw_buffer {}; if (command.isAllocated()) { raw_buffer = command.getStructBuffer(); @@ -191,15 +192,15 @@ int ModuleManagerLibraryHandler::commandDataValid(const Buffer command, unsigned return commandDataValid_(raw_buffer, device_type); } -int ModuleManagerLibraryHandler::allocate(struct buffer *buffer_pointer, size_t size_in_bytes){ +int ModuleManagerLibraryHandlerLocal::allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const { return allocate_(buffer_pointer, size_in_bytes); } -void ModuleManagerLibraryHandler::deallocate(struct buffer *buffer){ +void ModuleManagerLibraryHandlerLocal::deallocate(struct buffer *buffer) const { deallocate_(buffer); } -Buffer ModuleManagerLibraryHandler::constructBuffer(std::size_t size) { +Buffer ModuleManagerLibraryHandlerLocal::constructBuffer(std::size_t size) { if (size == 0) { return Buffer {}; } @@ -211,11 +212,11 @@ Buffer ModuleManagerLibraryHandler::constructBuffer(std::size_t size) { return { buff, deallocate_ }; } -Buffer ModuleManagerLibraryHandler::constructBufferByTakeOwnership(struct ::buffer &buffer) { +Buffer ModuleManagerLibraryHandlerLocal::constructBufferByTakeOwnership(struct ::buffer &buffer) { if (buffer.data == nullptr) { throw Buffer::BufferNotAllocated { "Buffer not allocated - cannot take ownership" }; } return { buffer, deallocate_ }; } -} +} \ No newline at end of file diff --git a/source/bringauto/modules/StatusAggregator.cpp b/source/bringauto/modules/StatusAggregator.cpp index daa95bca..ee7c11df 100644 --- a/source/bringauto/modules/StatusAggregator.cpp +++ b/source/bringauto/modules/StatusAggregator.cpp @@ -2,19 +2,21 @@ #include #include +#include + namespace bringauto::modules { -using log = bringauto::settings::Logger; +using log = settings::Logger; -int StatusAggregator::clear_device(const structures::DeviceIdentification &key) { - if(is_device_valid(key) == NOT_OK) { +int StatusAggregator::clear_device(const structures::DeviceIdentification &device) { + if(is_device_valid(device) == NOT_OK) { return DEVICE_NOT_REGISTERED; } - if (not devices.contains(key)) { + if (not devices.contains(device)) { return NOT_OK; } - auto &deviceState = devices.at(key); + auto &deviceState = devices.at(device); auto &aggregatedMessages = deviceState.aggregatedMessages(); while(not aggregatedMessages.empty()) { aggregatedMessages.pop(); @@ -23,8 +25,8 @@ int StatusAggregator::clear_device(const structures::DeviceIdentification &key) } Buffer -StatusAggregator::aggregateStatus(structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, - const unsigned int &device_type) { +StatusAggregator::aggregateStatus(const structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, + const unsigned int &device_type) const { auto &currStatus = deviceState.getStatus(); Buffer aggregatedStatusBuff {}; if (module_->aggregateStatus(aggregatedStatusBuff, currStatus, status, device_type) != OK) { @@ -34,14 +36,14 @@ StatusAggregator::aggregateStatus(structures::StatusAggregatorDeviceState &devic } void StatusAggregator::aggregateSetStatus(structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, - const unsigned int &device_type) { + const unsigned int &device_type) const { const auto aggregatedStatusBuff = aggregateStatus(deviceState, status, device_type); deviceState.setStatus(aggregatedStatusBuff); } void StatusAggregator::aggregateSetSendStatus(structures::StatusAggregatorDeviceState &deviceState, const Buffer &status, - const unsigned int &device_type) { + const unsigned int &device_type) const { const auto aggregatedStatusBuff = aggregateStatus(deviceState, status, device_type); deviceState.setStatusAndResetTimer(aggregatedStatusBuff); @@ -92,14 +94,14 @@ int StatusAggregator::add_status_to_aggregator(const Buffer& status, return COMMAND_INVALID; } - std::function timeouted_force_aggregation = [device, this]( + const std::function timeouted_force_aggregation = [device, this]( const structures::DeviceIdentification& deviceId) { timeoutedMessageReady_.store(true); deviceTimeouts_[device]++; return force_aggregation_on_device(deviceId); }; - devices.insert( - { device, structures::StatusAggregatorDeviceState(context_, timeouted_force_aggregation, device, commandBuffer, status) }); + devices.emplace( + device, structures::StatusAggregatorDeviceState(context_, timeouted_force_aggregation, device, commandBuffer, status)); force_aggregation_on_device(device); return 1; @@ -107,7 +109,7 @@ int StatusAggregator::add_status_to_aggregator(const Buffer& status, auto &deviceState = devices.at(device); auto &currStatus = deviceState.getStatus(); - auto &aggregatedMessages = deviceState.aggregatedMessages(); + const auto &aggregatedMessages = deviceState.aggregatedMessages(); if(module_->sendStatusCondition(currStatus, status, device_type) == OK) { aggregateSetSendStatus(deviceState, status, device_type); } else { @@ -222,8 +224,8 @@ bool StatusAggregator::getTimeoutedMessageReady() const { return timeoutedMessageReady_.load(); } -int StatusAggregator::getDeviceTimeoutCount(const structures::DeviceIdentification &key) { - return deviceTimeouts_[key]; +int StatusAggregator::getDeviceTimeoutCount(const structures::DeviceIdentification &device) { + return deviceTimeouts_[device]; } } diff --git a/source/bringauto/settings/LoggerId.cpp b/source/bringauto/settings/LoggerId.cpp new file mode 100644 index 00000000..41480047 --- /dev/null +++ b/source/bringauto/settings/LoggerId.cpp @@ -0,0 +1,22 @@ +#include + +namespace { + +/** + * @brief Compile time string comparison + * @param str1 First string to compare + * @param str2 Second string to compare + * @return true if both strings are equal, false otherwise + */ +constexpr bool stringsEqual(char const *str1, char const *str2) { + return std::string_view(str1) == str2; +} + +static_assert(stringsEqual(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY, "DEBUG") || + stringsEqual(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY, "INFO") || + stringsEqual(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY, "WARNING") || + stringsEqual(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY, "ERROR") || + stringsEqual(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY, "CRITICAL"), + "Invalid MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY defined. Use DEBUG, INFO, WARNING, ERROR, or CRITICAL."); + +} \ No newline at end of file diff --git a/source/bringauto/settings/QuicSettingsParser.cpp b/source/bringauto/settings/QuicSettingsParser.cpp new file mode 100644 index 00000000..4c3dab74 --- /dev/null +++ b/source/bringauto/settings/QuicSettingsParser.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +#include + + +namespace bringauto::settings { + QUIC_SETTINGS QuicSettingsParser::parse( + const structures::ExternalConnectionSettings &settings + ) { + QUIC_SETTINGS quic{}; + + if (auto value = getOptionalUint(settings, "IdleTimeoutMs")) { + settings::Logger::logDebug("[quic] [settings] IdleTimeoutMs settings loaded"); + quic.IdleTimeoutMs = *value; + quic.IsSet.IdleTimeoutMs = TRUE; + } + + if (auto value = getOptionalUint(settings, "HandshakeIdleTimeoutMs")) { + settings::Logger::logDebug("[quic] [settings] HandshakeIdleTimeoutMs settings loaded"); + quic.HandshakeIdleTimeoutMs = *value; + quic.IsSet.HandshakeIdleTimeoutMs = TRUE; + } + + if (auto value = getOptionalUint(settings, "DisconnectTimeoutMs")) { + settings::Logger::logDebug("[quic] [settings] DisconnectTimeoutMs settings loaded"); + quic.DisconnectTimeoutMs = *value; + quic.IsSet.DisconnectTimeoutMs = TRUE; + } + + return quic; + } + + std::optional QuicSettingsParser::getOptionalUint( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ) { + const auto it = settings.protocolSettings.find(std::string(key)); + if (it == settings.protocolSettings.end()) { + return std::nullopt; + } + + const auto &raw = it->second; + + if (!nlohmann::json::accept(raw)) { + settings::Logger::logWarning( + "[quic] QUIC setting '{}' is not valid JSON", key + ); + return std::nullopt; + } + + auto j = nlohmann::json::parse(raw); + + if (!j.is_number_unsigned()) { + settings::Logger::logWarning( + "[quic] QUIC setting '{}' must be an unsigned integer", key + ); + return std::nullopt; + } + + return j.get(); + } +} diff --git a/source/bringauto/settings/SettingsParser.cpp b/source/bringauto/settings/SettingsParser.cpp index aa3df832..ce60789a 100644 --- a/source/bringauto/settings/SettingsParser.cpp +++ b/source/bringauto/settings/SettingsParser.cpp @@ -5,6 +5,7 @@ #include #include +#include @@ -37,8 +38,6 @@ void SettingsParser::parseCmdArguments(int argc, char **argv) { cxxopts::value()); options.add_options("Internal Server")(std::string(Constants::PORT), "Port on which Server listens", cxxopts::value()); - options.add_options("Module Handler")(std::string(Constants::MODULE_PATHS), "Paths to shared module libraries", - cxxopts::value>()); options.allow_unrecognised_options(); cmdArguments_ = options.parse(argc, argv); @@ -48,15 +47,14 @@ void SettingsParser::parseCmdArguments(int argc, char **argv) { } } -bool SettingsParser::areCmdArgumentsCorrect() { +bool SettingsParser::areCmdArgumentsCorrect() const { bool isCorrect = true; std::vector requiredParams { std::string(Constants::CONFIG_PATH) }; std::vector allParameters = { std::string(Constants::CONFIG_PATH), - std::string(Constants::PORT), - std::string(Constants::MODULE_PATHS) + std::string(Constants::PORT) }; allParameters.insert(allParameters.end(), requiredParams.begin(), requiredParams.end()); @@ -87,7 +85,7 @@ bool SettingsParser::areCmdArgumentsCorrect() { return isCorrect; } -bool SettingsParser::areSettingsCorrect() { +bool SettingsParser::areSettingsCorrect() const { bool isCorrect = true; if(settings_->loggingSettings.file.use && !std::filesystem::exists(settings_->loggingSettings.file.path)) { @@ -98,6 +96,10 @@ bool SettingsParser::areSettingsCorrect() { std::cerr << "No shared module library provided." << std::endl; isCorrect = false; } + if(!settings_->moduleBinaryPath.empty() && !std::filesystem::exists(settings_->moduleBinaryPath)) { + std::cerr << "Given module binary path (" << settings_->moduleBinaryPath << ") does not exist." << std::endl; + isCorrect = false; + } if(!std::regex_match(settings_->company, std::regex("^[a-z0-9_]+$"))) { std::cerr << "Company name (" << settings_->company << ") is not valid." << std::endl; isCorrect = false; @@ -107,6 +109,18 @@ bool SettingsParser::areSettingsCorrect() { isCorrect = false; } + isCorrect &= !std::ranges::any_of(settings_->externalConnectionSettingsList, [&](auto& externalConnectionSettings){ + return std::ranges::any_of(externalConnectionSettings.modules, [&](auto const& externalModuleId) { + bool isMissing = !settings_->modulePaths.contains(externalModuleId); + if (isMissing) + { + std::cerr << "Module " << externalModuleId << + " is defined in external-connection endpoint modules but is not specified in module-paths" << std::endl; + } + return isMissing; + }); + }); + return isCorrect; } @@ -127,7 +141,7 @@ void SettingsParser::fillSettings() { fillExternalConnectionSettings(file); } -void SettingsParser::fillLoggingSettings(const nlohmann::json &file) { +void SettingsParser::fillLoggingSettings(const nlohmann::json &file) const { const auto &consoleLogging = file[std::string(Constants::LOGGING)][std::string(Constants::LOGGING_CONSOLE)]; const auto &fileLogging = file[std::string(Constants::LOGGING)][std::string(Constants::LOGGING_FILE)]; @@ -141,7 +155,7 @@ void SettingsParser::fillLoggingSettings(const nlohmann::json &file) { settings_->loggingSettings.file.path = std::filesystem::path(fileLogging[std::string(Constants::LOG_PATH)]); } -void SettingsParser::fillInternalServerSettings(const nlohmann::json &file) { +void SettingsParser::fillInternalServerSettings(const nlohmann::json &file) const { if(cmdArguments_.count(std::string(Constants::PORT))) { settings_->port = cmdArguments_[std::string(Constants::PORT)].as(); } else { @@ -149,16 +163,18 @@ void SettingsParser::fillInternalServerSettings(const nlohmann::json &file) { } } -void SettingsParser::fillModulePathsSettings(const nlohmann::json &file) { +void SettingsParser::fillModulePathsSettings(const nlohmann::json &file) const { for(auto &[key, val]: file[std::string(Constants::MODULE_PATHS)].items()) { - settings_->modulePaths[stoi(key)] = val; + val.get_to(settings_->modulePaths[stoi(key)]); } + file.at(std::string(Constants::MODULE_BINARY_PATH)).get_to(settings_->moduleBinaryPath); } -void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) { - settings_->vehicleName = file[std::string(Constants::EXTERNAL_CONNECTION)][std::string( - Constants::VEHICLE_NAME)]; - settings_->company = file[std::string(Constants::EXTERNAL_CONNECTION)][std::string(Constants::COMPANY)]; +void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) const { + file.at(std::string(Constants::EXTERNAL_CONNECTION)).at(std::string(Constants::VEHICLE_NAME)).get_to( + settings_->vehicleName); + file.at(std::string(Constants::EXTERNAL_CONNECTION)).at(std::string(Constants::COMPANY)).get_to( + settings_->company); for(const auto &endpoint: file[std::string(Constants::EXTERNAL_CONNECTION)][std::string( Constants::EXTERNAL_ENDPOINTS)]) { @@ -170,17 +186,22 @@ void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) case structures::ProtocolType::MQTT: settingsName = std::string(Constants::MQTT_SETTINGS); break; + case structures::ProtocolType::QUIC: + settingsName = std::string(Constants::QUIC_SETTINGS); + break; + case structures::ProtocolType::DUMMY: + break; case structures::ProtocolType::INVALID: default: std::cerr << "Invalid protocol type: " << endpoint[std::string(Constants::PROTOCOL_TYPE)] << std::endl; continue; } - externalConnectionSettings.serverIp = endpoint[std::string(Constants::SERVER_IP)]; + endpoint.at(std::string(Constants::SERVER_IP)).get_to(externalConnectionSettings.serverIp); externalConnectionSettings.port = endpoint[std::string(Constants::PORT)]; externalConnectionSettings.modules = endpoint[std::string(Constants::MODULES)].get>(); - if(endpoint.find(settingsName) != endpoint.end()) { + if(!settingsName.empty() && endpoint.find(settingsName) != endpoint.end()) { for(auto &[key, val]: endpoint[settingsName].items()) { externalConnectionSettings.protocolSettings[key] = to_string(val); } @@ -190,7 +211,7 @@ void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) } } -std::string SettingsParser::serializeToJson() { +std::string SettingsParser::serializeToJson() const { nlohmann::json settingsAsJson {}; settingsAsJson[std::string(Constants::LOGGING)][std::string(Constants::LOGGING_CONSOLE)] @@ -223,6 +244,12 @@ std::string SettingsParser::serializeToJson() { case structures::ProtocolType::MQTT: settingsName = std::string(Constants::MQTT_SETTINGS); break; + case structures::ProtocolType::QUIC: + settingsName = std::string(Constants::QUIC_SETTINGS); + break; + case structures::ProtocolType::DUMMY: + settingsName = std::string(Constants::DUMMY); + break; case structures::ProtocolType::INVALID: settingsName = "INVALID"; break; diff --git a/source/bringauto/structures/ModuleLibrary.cpp b/source/bringauto/structures/ModuleLibrary.cpp index b5d69428..b8fa6b31 100644 --- a/source/bringauto/structures/ModuleLibrary.cpp +++ b/source/bringauto/structures/ModuleLibrary.cpp @@ -1,9 +1,9 @@ #include +#include +#include #include -#include - namespace bringauto::structures { @@ -13,12 +13,26 @@ ModuleLibrary::~ModuleLibrary() { [](auto &pair) { pair.second->destroy_status_aggregator(); }); } -void ModuleLibrary::loadLibraries(const std::map &libPaths) { +void ModuleLibrary::loadLibraries(const std::unordered_map &libPaths) { + std::shared_ptr handler; + for(auto const &[key, path]: libPaths) { + handler = std::make_shared(); + handler->loadLibrary(path); + if(handler->getModuleNumber() != key) { + settings::Logger::logError("Module number from shared library {} does not match the module number from config. Config: {}, binary: {}.", path.string(), key, handler->getModuleNumber()); + throw std::runtime_error {"Module numbers from config are not corresponding to binaries. Unable to continue. Fix configuration file."}; + } + moduleLibraryHandlers.emplace(key, handler); + } +} + +void ModuleLibrary::loadLibraries(const std::unordered_map &libPaths, const std::filesystem::path &moduleBinaryPath) { + std::shared_ptr handler; for(auto const &[key, path]: libPaths) { - auto handler = std::make_shared(); + handler = std::make_shared(moduleBinaryPath, key); handler->loadLibrary(path); if(handler->getModuleNumber() != key) { - settings::Logger::logError("Module number from shared library {} does not match the module number from config. Config: {}, binary: {}.", path, key, handler->getModuleNumber()); + settings::Logger::logError("Module number from shared library {} does not match the module number from config. Config: {}, binary: {}.", path.string(), key, handler->getModuleNumber()); throw std::runtime_error {"Module numbers from config are not corresponding to binaries. Unable to continue. Fix configuration file."}; } moduleLibraryHandlers.emplace(key, handler); @@ -30,7 +44,7 @@ void ModuleLibrary::initStatusAggregators(std::shared_ptr &contex auto statusAggregator = std::make_shared(context, libraryHandler); statusAggregator->init_status_aggregator(); auto moduleNumber = statusAggregator->get_module_number(); - if(statusAggregators.find(moduleNumber) != statusAggregators.end()) { + if(statusAggregators.contains(moduleNumber)) { settings::Logger::logWarning("Module with number: {} is already initialized, so skipping this module", moduleNumber); continue; diff --git a/source/bringauto/structures/StatusAggregatorDeviceState.cpp b/source/bringauto/structures/StatusAggregatorDeviceState.cpp index 92f6067a..6da352f9 100644 --- a/source/bringauto/structures/StatusAggregatorDeviceState.cpp +++ b/source/bringauto/structures/StatusAggregatorDeviceState.cpp @@ -1,6 +1,6 @@ #include -#include +#include @@ -45,8 +45,8 @@ std::queue &StatusAggregatorDeviceState::aggregatedMessa return aggregatedMessages_; } -int StatusAggregatorDeviceState::addExternalCommand(const modules::Buffer &command) { - externalCommandQueue_.push(command); +int StatusAggregatorDeviceState::addExternalCommand(const modules::Buffer &commandBuffer) { + externalCommandQueue_.push(commandBuffer); if (externalCommandQueue_.size() > settings::max_external_commands) { externalCommandQueue_.pop(); return NOT_OK; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 54f4550a..dbf05a6f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.25) PROJECT(ModuleGateway) -SET(CMAKE_CXX_STANDARD 20) +SET(CMAKE_CXX_STANDARD 23) ADD_SUBDIRECTORY("${CMAKE_CURRENT_LIST_DIR}/lib/example-module") diff --git a/test/include/ErrorAggregatorTests.hpp b/test/include/ErrorAggregatorTests.hpp index 37bc1402..64214d01 100644 --- a/test/include/ErrorAggregatorTests.hpp +++ b/test/include/ErrorAggregatorTests.hpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -23,7 +23,7 @@ class ErrorAggregatorTests: public ::testing::Test { bringauto::settings::Logger::addSink(); bringauto::logging::LoggerSettings settings { "ErrorAggregatorTests", - bringauto::logging::LoggerVerbosity::Critical + bringauto::settings::toLoggerVerbosity(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY) }; bringauto::settings::Logger::init(settings); } @@ -31,7 +31,7 @@ class ErrorAggregatorTests: public ::testing::Test { bringauto::modules::Buffer init_status_buffer(); bringauto::external_client::ErrorAggregator errorAggregator_ {}; - std::shared_ptr libHandler_ {}; + std::shared_ptr libHandler_ {}; #ifdef DEBUG static constexpr const char* PATH_TO_MODULE { "./test/lib/example-module/libexample-module-gateway-sharedd.so" }; #else diff --git a/test/include/ExternalConnectionTests.hpp b/test/include/ExternalConnectionTests.hpp index 15dd09e8..ea7e09fc 100644 --- a/test/include/ExternalConnectionTests.hpp +++ b/test/include/ExternalConnectionTests.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -80,7 +79,7 @@ class ExternalConnectionTests: public ::testing::Test { bringauto::settings::Logger::addSink(); bringauto::logging::LoggerSettings settings { "ExternalConnectionTests", - bringauto::logging::LoggerVerbosity::Error + bringauto::settings::toLoggerVerbosity(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY) }; bringauto::settings::Logger::init(settings); }; diff --git a/test/include/InternalServerTests.hpp b/test/include/InternalServerTests.hpp index 2d888b56..6054d359 100644 --- a/test/include/InternalServerTests.hpp +++ b/test/include/InternalServerTests.hpp @@ -24,7 +24,7 @@ class InternalServerTests: public ::testing::Test { bringauto::settings::Logger::addSink(); bringauto::logging::LoggerSettings params { "InternalServerTests", - bringauto::logging::LoggerVerbosity::Debug + bringauto::settings::toLoggerVerbosity(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY), }; bringauto::settings::Logger::init(params); } diff --git a/test/include/StatusAggregatorTests.hpp b/test/include/StatusAggregatorTests.hpp index 88cb1bb3..848208e1 100644 --- a/test/include/StatusAggregatorTests.hpp +++ b/test/include/StatusAggregatorTests.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include @@ -23,7 +23,7 @@ class StatusAggregatorTests: public ::testing::Test { bringauto::settings::Logger::addSink(); bringauto::logging::LoggerSettings settings { "StatusAggregatorTests", - bringauto::logging::LoggerVerbosity::Critical + bringauto::settings::toLoggerVerbosity(MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY) }; bringauto::settings::Logger::init(settings); } @@ -43,7 +43,7 @@ class StatusAggregatorTests: public ::testing::Test { std::unique_ptr statusAggregator_ {}; - std::shared_ptr libHandler_ {}; + std::shared_ptr libHandler_ {}; #ifdef DEBUG static constexpr const char* PATH_TO_MODULE { "./test/lib/example-module/libexample-module-gateway-sharedd.so" }; diff --git a/test/include/testing_utils/ConfigMock.hpp b/test/include/testing_utils/ConfigMock.hpp index c4e0309b..97514f5a 100644 --- a/test/include/testing_utils/ConfigMock.hpp +++ b/test/include/testing_utils/ConfigMock.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include @@ -27,11 +27,11 @@ class ConfigMock { int port { 1636 }; } internal_server_settings; - std::map module_paths { {1, "/path/to/lib1.so"}, {2, "/path/to/lib2.so"}, {3, "/path/to/lib3.so"} }; + std::unordered_map module_paths { {1, "/path/to/lib1.so"}, {2, "/path/to/lib2.so"}, {3, "/path/to/lib3.so"} }; std::string modulePathsToString() const { std::string result = ""; for (auto [key, value] : module_paths) { - result += std::format("\"{}\": \"{}\",\n", key, value); + result += std::format("\"{}\": \"{}\",\n", key, value.string()); } if (!result.empty()) { result.pop_back(); @@ -48,7 +48,7 @@ class ConfigMock { std::string server_ip { "localhost" }; int port { 1883 }; - std::map mqtt_settings { + std::unordered_map mqtt_settings { { "ssl", "\"false\"" }, { "ca-file", "\"ca.pem\"" }, { "client-cert", "\"client.pem\"" }, @@ -105,6 +105,7 @@ class ConfigMock { "\"module-paths\": {{\n" "{}\n" "}},\n" + "\"module-binary-path\": \"\",\n" "\"external-connection\": {{\n" "\"company\": \"{}\",\n" "\"vehicle-name\": \"{}\",\n" diff --git a/test/source/EnumUtilsTests.cpp b/test/source/EnumUtilsTests.cpp index f56ee9fb..5194b0f3 100644 --- a/test/source/EnumUtilsTests.cpp +++ b/test/source/EnumUtilsTests.cpp @@ -14,9 +14,11 @@ TEST(EnumUtilsTests, EnumUtils){ namespace balog = bringauto::logging; EXPECT_EQ(bacu::EnumUtils::stringToProtocolType(std::string(baset::Constants::MQTT)), bas::ProtocolType::MQTT); + EXPECT_EQ(bacu::EnumUtils::stringToProtocolType(std::string(baset::Constants::DUMMY)), bas::ProtocolType::DUMMY); EXPECT_EQ(bacu::EnumUtils::stringToProtocolType("INVALID"), bas::ProtocolType::INVALID); - EXPECT_EQ(bacu::EnumUtils::protocolTypeToString(bas::ProtocolType::MQTT), "mqtt"); + EXPECT_EQ(bacu::EnumUtils::protocolTypeToString(bas::ProtocolType::MQTT), "MQTT"); + EXPECT_EQ(bacu::EnumUtils::protocolTypeToString(bas::ProtocolType::DUMMY), "DUMMY"); EXPECT_EQ(bacu::EnumUtils::protocolTypeToString(bas::ProtocolType::INVALID), ""); EXPECT_EQ(bacu::EnumUtils::stringToLoggerVerbosity(std::string(baset::Constants::LOG_LEVEL_DEBUG)), balog::LoggerVerbosity::Debug); diff --git a/test/source/ErrorAggregatorTests.cpp b/test/source/ErrorAggregatorTests.cpp index 12d2dc4c..258b3fc9 100644 --- a/test/source/ErrorAggregatorTests.cpp +++ b/test/source/ErrorAggregatorTests.cpp @@ -1,5 +1,7 @@ #include #include +#include + #include @@ -15,7 +17,7 @@ bam::Buffer ErrorAggregatorTests::init_status_buffer() { } void ErrorAggregatorTests::SetUp(){ - libHandler_ = std::make_shared(); + libHandler_ = std::make_shared(); libHandler_->loadLibrary(PATH_TO_MODULE); errorAggregator_.init_error_aggregator(libHandler_); } @@ -26,14 +28,14 @@ void ErrorAggregatorTests::TearDown(){ TEST_F(ErrorAggregatorTests, init_error_aggregator_ok) { external_client::ErrorAggregator errorAggregatorTest {}; - const auto libHandler = std::make_shared(); + const auto libHandler = std::make_shared(); const int ret = errorAggregatorTest.init_error_aggregator(libHandler); EXPECT_EQ(ret, OK); } TEST_F(ErrorAggregatorTests, destroy_error_aggregator_ok) { external_client::ErrorAggregator errorAggregatorTest {}; - const auto libHandler = std::make_shared(); + const auto libHandler = std::make_shared(); errorAggregatorTest.init_error_aggregator(libHandler); const int ret = errorAggregatorTest.destroy_error_aggregator(); EXPECT_EQ(ret, OK); diff --git a/test/source/InternalServerTests.cpp b/test/source/InternalServerTests.cpp index 44b745d1..0de845e3 100644 --- a/test/source/InternalServerTests.cpp +++ b/test/source/InternalServerTests.cpp @@ -31,6 +31,7 @@ TEST_F(InternalServerTests, OneClient) { testing_utils::TestHandler testedData(devices, data); testedData.runTestsParallelConnections(); } + /** * @brief Tests Connection of 2 clients, and communication between clients-server-handler */ @@ -50,6 +51,7 @@ TEST_F(InternalServerTests, TwoClients) { testing_utils::TestHandler testedData(devices, data); testedData.runTestsParallelConnections(); } + /** * @brief Tests Connection of 5 clients, and communication between clients-server-handler */ @@ -69,6 +71,7 @@ TEST_F(InternalServerTests, FiveClients) { testing_utils::TestHandler testedData(devices, data); testedData.runTestsParallelConnections(); } + /** * @brief Tests Connection of 50 clients, and communication between clients-server-handler */ @@ -88,6 +91,7 @@ TEST_F(InternalServerTests, FiftyClients) { testing_utils::TestHandler testedData(devices, data); testedData.runTestsParallelConnections(); } + ///TESTS FOR RESPONSES TO DIFFERENT PRIORITES /** * @brief tests if server responds to each client with correct response and running communication is not broken @@ -115,6 +119,7 @@ TEST_F(InternalServerTests, SameRolePriority000) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response and running communication is not broken */ @@ -142,6 +147,7 @@ TEST_F(InternalServerTests, SameRolePriority001) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response, lower priority connection is disconnected correctly and no communication is broken */ @@ -168,6 +174,7 @@ TEST_F(InternalServerTests, SameRolePriority110) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response and running communication is not broken */ @@ -194,6 +201,7 @@ TEST_F(InternalServerTests, SameRolePriority121) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response, lower priority connection is disconnected correctly and no communication is broken */ @@ -220,6 +228,7 @@ TEST_F(InternalServerTests, SameRolePriority101) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response and running communication is not broken */ @@ -246,6 +255,7 @@ TEST_F(InternalServerTests, SameRolePriority122) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response, lower priority connection is disconnected correctly and no communication is broken */ @@ -272,6 +282,7 @@ TEST_F(InternalServerTests, SameRolePriority120) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response, lower priority connection is disconnected correctly and no communication is broken */ @@ -298,6 +309,7 @@ TEST_F(InternalServerTests, SameRolePriority210) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if server responds to each client with correct response, lower priority connection is disconnected correctly and no communication is broken */ @@ -324,6 +336,7 @@ TEST_F(InternalServerTests, SameRolePriority211) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsSerialConnections(); } + /** * @brief tests if connection is rejected when we send less than 4 bytes of a message and other communication is not broken */ @@ -350,6 +363,7 @@ TEST_F(InternalServerTests, RejectMessageSmallerThan4Bytes) { auto connectStr = testedData.getConnect(0).SerializeAsString(); testedData.runTestsWithWrongMessage(1, connectStr.size(), "", false, true); } + /** * @brief tests if connection is rejected when we send 4 bytes of the message (as the header) defining size of the rest of message as 0 and other communication is not broken */ @@ -375,10 +389,9 @@ TEST_F(InternalServerTests, RejectMesseageComposedOfOnlyHeaderWithNumber0) { testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsWithWrongMessage(1, 0, "", true); } + /** - * this is timeout from client side - * test is not working and the feature is not implemented - * should test for if connection is rejected when we send only 4 bytes of the message (as the header) defining message size larger than 0 and other communication is not broken + * this test will only make sense when processBufferData returns false if bytesTransferred is 0 and header value is not 0 (not intended as of v1.3.5) */ TEST_F(InternalServerTests, RejectMessageComposedOfOnlyHeader) { GTEST_SKIP(); @@ -404,6 +417,7 @@ TEST_F(InternalServerTests, RejectMessageComposedOfOnlyHeader) { auto connectStr = testedData.getConnect(0).SerializeAsString(); testedData.runTestsWithWrongMessage(0, connectStr.size(), "", true); } + /** * @brief tests if connection is rejected when we send message that is not matching InternalProtocol::InternalClient message and other communication is not broken */ @@ -430,11 +444,9 @@ TEST_F(InternalServerTests, RejectMessageWithGarbageDataMatchingHeaderSize) { auto connectStr = testedData.getConnect(0).SerializeAsString(); testedData.runTestsWithWrongMessage(1, connectStr.size() + 5, connectStr + "12345", true); } -/** - * this is timeout from client side - * test is not working and the feature is not implemented - * should tests for if connection is rejected when we send less data than header defines as size of the message and other communication is not broken +/** + * this test will only make sense if processBufferData returns false if bytesTransferred is less than header size and header value is not 0 (not intended as of v1.3.5) */ TEST_F(InternalServerTests, RejectMessageWithLessDataThanHeaderSays) { GTEST_SKIP(); @@ -488,6 +500,7 @@ TEST_F(InternalServerTests, RejectMessageWhereStatusIsSentBeforeConnection) { auto connectStr = testedData.getConnect(1).SerializeAsString(); testedData.runTestsWithWrongMessage(1, connectStr.size(), connectStr, true); } + /** * @brief tests for disconnection when client receives connect message on connection that is already connected */ @@ -515,11 +528,11 @@ TEST_F(InternalServerTests, RejectMessageWhereConnectionIsSentAfterAlreadyBeingC auto connectStr = testedData.getConnect(0).SerializeAsString(); testedData.runTestsWithWrongMessage(0, connectStr.size(), connectStr, false); } + /** - * @brief tests if server correctly timeouts then disconnects if it does not receive response to connect from module hanlder in itme + * @brief tests if server correctly disconnects if it does not receive response to connect from module hanlder in itme */ TEST_F(InternalServerTests, TestForBehaviorWhereModuleHandlerDoesntRespondToConnect) { - GTEST_SKIP(); std::vector responseType { InternalProtocol::DeviceConnectResponse_ResponseType_OK, InternalProtocol::DeviceConnectResponse_ResponseType_OK, @@ -541,11 +554,11 @@ TEST_F(InternalServerTests, TestForBehaviorWhereModuleHandlerDoesntRespondToConn testing_utils::TestHandler testedData(devices, responseType, data); testedData.runTestsWithModuleHandlerTimeout(true, 2); } + /** - * @brief tests if server correctly timeouts then disconnects if it does not receive command to status from module hanlder in time + * @brief tests if server correctly disconnects if it does not receive command to status from module hanlder in time */ TEST_F(InternalServerTests, TestForBehaviorWhereModuleHandlerDoesntRespondToStatus) { - GTEST_SKIP(); std::vector responseType { InternalProtocol::DeviceConnectResponse_ResponseType_OK, InternalProtocol::DeviceConnectResponse_ResponseType_OK, diff --git a/test/source/SettingsParserTests.cpp b/test/source/SettingsParserTests.cpp index 42af8b08..cd25d550 100644 --- a/test/source/SettingsParserTests.cpp +++ b/test/source/SettingsParserTests.cpp @@ -185,3 +185,21 @@ TEST_F(SettingsParserTests, InvalidProtocol){ EXPECT_TRUE(result); EXPECT_TRUE(settingsParser.getSettings()->externalConnectionSettingsList.empty()); } + +/** + * @brief Test if modules specified in endpoint missing in module-paths are handled correctly + */ +TEST_F(SettingsParserTests, MissingModules){ + testing_utils::ConfigMock::Config config {}; + config.module_paths = { {1, "/path/to/lib1.so"}, {2, "/path/to/lib2.so"}, {3, "/path/to/lib3.so"} }; + config.external_connection.endpoint.modules = { 1, 2, 3, 4}; + + bool failed = false; + try { + parseConfig(config); + }catch (std::invalid_argument &e){ + EXPECT_STREQ(e.what(), "Arguments are not correct."); + failed = true; + } + EXPECT_TRUE(failed); +} \ No newline at end of file diff --git a/test/source/StatusAggregatorTests.cpp b/test/source/StatusAggregatorTests.cpp index 9d92866a..813218c3 100644 --- a/test/source/StatusAggregatorTests.cpp +++ b/test/source/StatusAggregatorTests.cpp @@ -1,5 +1,8 @@ #include #include +#include + +#include namespace modules = bringauto::modules; @@ -40,7 +43,7 @@ void StatusAggregatorTests::remove_device_from_status_aggregator(){ void StatusAggregatorTests::SetUp(){ context_ = std::make_shared(); - libHandler_ = std::make_shared(); + libHandler_ = std::make_shared(); libHandler_->loadLibrary(PATH_TO_MODULE); statusAggregator_ = std::make_unique(context_, libHandler_); statusAggregator_->init_status_aggregator(); @@ -58,7 +61,7 @@ TEST_F(StatusAggregatorTests, init_status_aggregator_ok) { } TEST_F(StatusAggregatorTests, init_status_aggregator_bad_path) { - auto libHandler = std::make_shared(); + auto libHandler = std::make_shared(); EXPECT_THROW(libHandler->loadLibrary(WRONG_PATH_TO_MODULE), std::runtime_error); } @@ -93,7 +96,7 @@ TEST_F(StatusAggregatorTests, add_status_to_aggregator_status_register_device){ } TEST_F(StatusAggregatorTests, add_status_to_aggregator_without_aggregation){ - auto libHandler = std::make_shared(); + auto libHandler = std::make_shared(); libHandler->loadLibrary(PATH_TO_MODULE); add_status_to_aggregator(); auto size = std::string(BUTTON_PRESSED).size(); diff --git a/test/source/testing_utils/TestHandler.cpp b/test/source/testing_utils/TestHandler.cpp index 2ea29b7a..1f7188a9 100644 --- a/test/source/testing_utils/TestHandler.cpp +++ b/test/source/testing_utils/TestHandler.cpp @@ -302,7 +302,7 @@ void TestHandler::runConnects(size_t numberOfErrors) { if(i < numberOfErrors) { clients[i].connectSocket(); clients[i].sendMessage(connects[i]); - clients[i].insteadOfMessageExpectTimeoutThenError(); + clients[i].insteadOfMessageExpectError(); clients[i].disconnectSocket(); } else { clients[i].connectSocket(); @@ -332,7 +332,7 @@ void TestHandler::runStatuses(size_t numberOfErrors) { for(size_t i = 0; i < clients.size()*(numberOfMessages - 1); ++i) { if(i < numberOfErrors) { clients[i].sendMessage(statuses[i]); - clients[i].insteadOfMessageExpectTimeoutThenError(); + clients[i].insteadOfMessageExpectError(); clients[i].disconnectSocket(); } else if(clients[i%clients.size()].isOpen()) { clients[i%clients.size()].sendMessage(statuses[i%clients.size()]);