diff --git a/CMLibStorage.cmake b/CMLibStorage.cmake index 5719639a..9d07653c 100644 --- a/CMLibStorage.cmake +++ b/CMLibStorage.cmake @@ -1,4 +1,3 @@ SET(STORAGE_LIST DEP) -SET(STORAGE_LIST_DEP "https://github.com/bacpack-system/package-tracker.git") -SET(STORAGE_LIST_DEP_REVISION "v0.0.0" CACHE STRING "" FORCE) +SET(STORAGE_LIST_DEP "https://github.com/bacpack-system/package-tracker.git") \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 264788f5..18ee03cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,35 +1,38 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.25 FATAL_ERROR) PROJECT(ModuleGateway) +SET(BRINGAUTO_MODULE_GATEWAY_VERSION 1.4.0) + +SET(BRINGAUTO_MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY "DEBUG" CACHE STRING "Minimum logger verbosity level for module-gateway") +SET_PROPERTY(CACHE BRINGAUTO_MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY PROPERTY STRINGS "DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL") + +OPTION(BRINGAUTO_TESTS "Enable tests" OFF) +OPTION(BRINGAUTO_PACKAGE "Package creation" OFF) +OPTION(BRINGAUTO_INSTALL "Enable install" OFF) +OPTION(BRINGAUTO_SYSTEM_DEP "Enable system dependencies" OFF) +OPTION(BRINGAUTO_SAMPLES "Enable build of sample app, not used in project" OFF) +OPTION(BRINGAUTO_GET_PACKAGES_ONLY "Only download packages for this project" OFF) + +FIND_PACKAGE(CMLIB COMPONENTS CMCONF REQUIRED) +CMCONF_INIT_SYSTEM(FLEET_PROTOCOL) + FIND_PACKAGE(CMLIB COMPONENTS CMDEF CMUTIL STORAGE REQUIRED ) -SET(BRINGAUTO_MODULE_GATEWAY_VERSION 1.3.5) - -SET(BRINGAUTO_MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY "DEBUG" CACHE STRING "Minimum logger verbosity level for module-gateway") -SET_PROPERTY(CACHE BRINGAUTO_MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY PROPERTY STRINGS "DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL") - CMDEF_COMPILE_DEFINITIONS( ALL "MODULE_GATEWAY_VERSION=\"${BRINGAUTO_MODULE_GATEWAY_VERSION}\"" "BRINGAUTO_MODULE_GATEWAY_MINIMUM_LOGGER_VERBOSITY=\"${BRINGAUTO_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() SET(CMAKE_POSITION_INDEPENDENT_CODE ON) -OPTION(BRINGAUTO_TESTS "Disable tests" OFF) -OPTION(BRINGAUTO_PACKAGE "Package creation" OFF) -OPTION(BRINGAUTO_INSTALL "Disable install" OFF) -OPTION(BRINGAUTO_SYSTEM_DEP "Enable system dependencies" OFF) -OPTION(BRINGAUTO_SAMPLES "Enable build of sample app, not used in project" OFF) -OPTION(BRINGAUTO_GET_PACKAGES_ONLY "Only download packages for this project" OFF) - IF(BRINGAUTO_PACKAGE) IF(NOT BRINGAUTO_INSTALL) SET(BRINGAUTO_INSTALL ON CACHE BOOL "Forced install due to BRINGAUTO_PACKAGE=ON" FORCE) @@ -55,6 +58,9 @@ 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) FILE(GLOB_RECURSE source_files "source/*") ADD_LIBRARY(module-gateway-lib STATIC "${source_files}") @@ -72,6 +78,8 @@ 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 ${CMAKE_DL_LIBS} ) diff --git a/README.md b/README.md index 4270f41c..df905c25 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,18 @@ 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 -- g++ >= 10 or other compiler with c++20 support +- [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.2.0 +- [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++23 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 diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index c953a203..ea369eff 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -5,11 +5,13 @@ 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.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/external_client/ErrorAggregator.hpp b/include/bringauto/external_client/ErrorAggregator.hpp index dfa47cd9..e72fd82c 100644 --- a/include/bringauto/external_client/ErrorAggregator.hpp +++ b/include/bringauto/external_client/ErrorAggregator.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -25,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. @@ -111,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/modules/Buffer.hpp b/include/bringauto/modules/Buffer.hpp index a6c5e8ea..015d0587 100644 --- a/include/bringauto/modules/Buffer.hpp +++ b/include/bringauto/modules/Buffer.hpp @@ -9,6 +9,8 @@ namespace bringauto::modules { +class IModuleManagerLibraryHandler; + /** * @brief Buffer structure used to simplify buffer management. The reason for this class is to provide * a way to manage buffer memory in a safe way and make it easier to pass buffers between objects. @@ -18,7 +20,7 @@ namespace bringauto::modules { */ struct Buffer final { - friend class ModuleManagerLibraryHandler; + friend class IModuleManagerLibraryHandler; Buffer() = default; Buffer(const Buffer& buff) = default; diff --git a/include/bringauto/modules/IModuleManagerLibraryHandler.hpp b/include/bringauto/modules/IModuleManagerLibraryHandler.hpp new file mode 100644 index 00000000..ba2233ec --- /dev/null +++ b/include/bringauto/modules/IModuleManagerLibraryHandler.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include + +#include +#include + + + +namespace bringauto::modules { + +/** + * @brief Class used to load and handle library created by module maintainer + */ +class IModuleManagerLibraryHandler { +public: + 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; + + /** + * @brief Get the module number + * + * @return module number + */ + virtual int getModuleNumber() = 0; + + /** + * @brief Check if device type is supported by the module + * + * @param device_type device type to check + * @return OK if supported, NOT_OK otherwise + */ + virtual int isDeviceTypeSupported(unsigned int device_type) = 0; + + /** + * @brief Evaluate the condition for aggregating a new status into the current status + * + * @param current_status current aggregated status + * @param new_status newly received status + * @param device_type device type + * @return OK if the new status should be aggregated, NOT_OK otherwise + */ + virtual int sendStatusCondition(const Buffer ¤t_status, const Buffer &new_status, unsigned int device_type) = 0; + + /** + * @brief 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; + + /** + * @brief 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; + + /** + * @brief 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; + + /** + * @brief 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; + + /** + * @brief Check if the status data is valid + * + * @param status status buffer to validate + * @param device_type device type + * @return OK if valid, NOT_OK otherwise + */ + virtual int statusDataValid(const Buffer &status, unsigned int device_type) = 0; + + /** + * @brief Check if the command data is valid + * + * @param command command buffer to validate + * @param device_type device type + * @return OK if valid, NOT_OK otherwise + */ + 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 + */ + Buffer constructBuffer(std::size_t size = 0); + +protected: + + std::function deallocate_ {}; + + virtual int allocate(struct buffer* buffer_pointer, size_t size_in_bytes) const = 0; + + /** + * @brief Constructs a buffer by taking ownership of the given raw C buffer + * + * @param buffer raw C buffer whose memory ownership is transferred + * @return a new Buffer object + */ + Buffer constructBufferByTakeOwnership(struct ::buffer& buffer); +}; + +} diff --git a/include/bringauto/modules/ModuleManagerLibraryHandler.hpp b/include/bringauto/modules/ModuleManagerLibraryHandler.hpp deleted file mode 100644 index fbae8958..00000000 --- a/include/bringauto/modules/ModuleManagerLibraryHandler.hpp +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - - - -namespace bringauto::modules { - -/** - * @brief Class used to load and handle library created by module maintainer - */ -class ModuleManagerLibraryHandler { -public: - ModuleManagerLibraryHandler() = default; - - ~ModuleManagerLibraryHandler(); - - /** - * @brief Load library created by a module maintainer - * - * @param path path to the library - */ - void loadLibrary(const std::filesystem::path &path); - - int getModuleNumber() const; - - int isDeviceTypeSupported(unsigned int device_type) const; - - int sendStatusCondition(const Buffer ¤t_status, const Buffer &new_status, unsigned int device_type) const; - - /** - * @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); - - /** - * @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); - - /** - * @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); - - /** - * @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 statusDataValid(const Buffer &status, unsigned int device_type) const; - - int commandDataValid(const Buffer &command, unsigned int device_type) const; - - /** - * @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); - -private: - - int allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const; - - void deallocate(struct buffer *buffer) const; - - void *checkFunction(const char *functionName) 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); - - void *module_ {}; - - std::function getModuleNumber_ {}; - std::function isDeviceTypeSupported_ {}; - std::function generateFirstCommand_ {}; - std::function statusDataValid_ {}; - std::function commandDataValid_ {}; - std::function sendStatusCondition_ {}; - std::function aggregateStatus_ {}; - std::function aggregateError_ {}; - std::function generateCommand_ {}; - std::function allocate_ {}; - std::function deallocate_ {}; -}; - -} diff --git a/include/bringauto/modules/ModuleManagerLibraryHandlerAsync.hpp b/include/bringauto/modules/ModuleManagerLibraryHandlerAsync.hpp new file mode 100644 index 00000000..c1171d95 --- /dev/null +++ b/include/bringauto/modules/ModuleManagerLibraryHandlerAsync.hpp @@ -0,0 +1,88 @@ +#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; + + ModuleManagerLibraryHandlerAsync(const ModuleManagerLibraryHandlerAsync &) = delete; + ModuleManagerLibraryHandlerAsync(ModuleManagerLibraryHandlerAsync &&) = delete; + ModuleManagerLibraryHandlerAsync &operator=(const ModuleManagerLibraryHandlerAsync &) = delete; + ModuleManagerLibraryHandlerAsync &operator=(ModuleManagerLibraryHandlerAsync &&) = delete; + + /** + * @brief Spawns the module binary process and waits for it to be ready via Aeron IPC. + */ + 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; + + int generateCommand(Buffer &generated_command, const Buffer &new_status, + const Buffer ¤t_status, const Buffer ¤t_command, + unsigned int device_type) override; + + int aggregateStatus(Buffer &aggregated_status, const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) override; + + int aggregateError(Buffer &error_message, const Buffer ¤t_error_message, const Buffer &status, + unsigned int device_type) override; + + 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; + +private: + + int allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const override; + + void deallocate(struct buffer *buffer) const; + + /// 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::async_function_execution_definitions::ModuleFunctionExecutor aeronClient { + async_function_execution::Config { + .isProducer = true, + .defaultTimeout = settings::AeronClientConstants::aeron_client_default_timeout, + }, + fleet_protocol::async_function_execution_definitions::moduleFunctionList + }; +}; + +} diff --git a/include/bringauto/modules/ModuleManagerLibraryHandlerLocal.hpp b/include/bringauto/modules/ModuleManagerLibraryHandlerLocal.hpp new file mode 100644 index 00000000..4b20ff8e --- /dev/null +++ b/include/bringauto/modules/ModuleManagerLibraryHandlerLocal.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + + + +namespace bringauto::modules { + +/** + * @brief Class used to load and handle library created by module maintainer + */ +class ModuleManagerLibraryHandlerLocal : public IModuleManagerLibraryHandler { +public: + ModuleManagerLibraryHandlerLocal() = default; + + ~ModuleManagerLibraryHandlerLocal() override; + + /** + * @brief Loads the module shared library from the given path using dlmopen. + */ + 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; + + int generateCommand(Buffer &generated_command, const Buffer &new_status, + const Buffer ¤t_status, const Buffer ¤t_command, + unsigned int device_type) override; + + int aggregateStatus(Buffer &aggregated_status, const Buffer ¤t_status, + const Buffer &new_status, unsigned int device_type) override; + + int aggregateError(Buffer &error_message, const Buffer ¤t_error_message, const Buffer &status, + unsigned int device_type) override; + + 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; + +private: + + int allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const override; + + void deallocate(struct buffer *buffer) const; + + void *checkFunction(const char *functionName) const; + + void *module_ {}; + + std::function getModuleNumber_ {}; + std::function isDeviceTypeSupported_ {}; + std::function generateFirstCommand_ {}; + std::function statusDataValid_ {}; + std::function commandDataValid_ {}; + std::function sendStatusCondition_ {}; + std::function aggregateStatus_ {}; + std::function aggregateError_ {}; + std::function generateCommand_ {}; + std::function allocate_ {}; +}; + +} \ No newline at end of file diff --git a/include/bringauto/modules/StatusAggregator.hpp b/include/bringauto/modules/StatusAggregator.hpp index e7c1e1d9..5043df77 100644 --- a/include/bringauto/modules/StatusAggregator.hpp +++ b/include/bringauto/modules/StatusAggregator.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -20,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; @@ -186,7 +186,7 @@ class StatusAggregator { 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 306eb9c5..349108e3 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -88,35 +88,57 @@ 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 maximum time to wait for the module binary to become ready after launch + */ + static constexpr std::chrono::seconds aeron_client_startup_timeout { 10 }; + /** + * @brief Aeron IPC channel URI used for local module communication + */ + static constexpr std::string_view aeron_connection { "aeron:ipc" }; + /** + * @brief separator used in Aeron channel/stream identifier strings + */ + static constexpr std::string_view separator { ":::" }; +}; + /** * @brief Constant string views */ @@ -142,6 +164,7 @@ class Constants { 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" }; diff --git a/include/bringauto/settings/Settings.hpp b/include/bringauto/settings/Settings.hpp index aad2db84..9c3c13a6 100644 --- a/include/bringauto/settings/Settings.hpp +++ b/include/bringauto/settings/Settings.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include @@ -31,7 +33,12 @@ struct Settings { /** * @brief paths to shared module libraries */ - std::unordered_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/structures/ModuleLibrary.hpp b/include/bringauto/structures/ModuleLibrary.hpp index 5f37de37..30aed8f7 100644 --- a/include/bringauto/structures/ModuleLibrary.hpp +++ b/include/bringauto/structures/ModuleLibrary.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -13,6 +13,7 @@ namespace bringauto::structures { * @brief Library with library handlers and status aggregators */ struct ModuleLibrary { + ModuleLibrary() = default; ~ModuleLibrary(); @@ -21,7 +22,15 @@ struct ModuleLibrary { * * @param libPaths paths to the libraries */ - void loadLibraries(const std::unordered_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 @@ -30,9 +39,9 @@ struct ModuleLibrary { */ void initStatusAggregators(std::shared_ptr &context); /// Map of module handlers, key is module id - std::unordered_map> moduleLibraryHandlers {}; + std::unordered_map> moduleLibraryHandlers {}; /// Map of status aggregators, key is module id - std::unordered_map> statusAggregators {}; + std::unordered_map> statusAggregators {}; }; } diff --git a/main.cpp b/main.cpp index de95f30a..5494b2ae 100644 --- a/main.cpp +++ b/main.cpp @@ -58,9 +58,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..2889f140 100644 --- a/resources/config/README.md +++ b/resources/config/README.md @@ -22,6 +22,8 @@ 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) 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/source/bringauto/external_client/ErrorAggregator.cpp b/source/bringauto/external_client/ErrorAggregator.cpp index b0f650fe..4f83b6c0 100644 --- a/source/bringauto/external_client/ErrorAggregator.cpp +++ b/source/bringauto/external_client/ErrorAggregator.cpp @@ -8,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; } diff --git a/source/bringauto/external_client/connection/ExternalConnection.cpp b/source/bringauto/external_client/connection/ExternalConnection.cpp index 3925e57f..3d4871e8 100644 --- a/source/bringauto/external_client/connection/ExternalConnection.cpp +++ b/source/bringauto/external_client/connection/ExternalConnection.cpp @@ -421,7 +421,16 @@ std::vector ExternalConnection::getAllConnecte std::vector devices {}; for(const auto &moduleNumber: settings_.modules) { std::list unique_devices {}; - const 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; diff --git a/source/bringauto/modules/IModuleManagerLibraryHandler.cpp b/source/bringauto/modules/IModuleManagerLibraryHandler.cpp new file mode 100644 index 00000000..2306ee2a --- /dev/null +++ b/source/bringauto/modules/IModuleManagerLibraryHandler.cpp @@ -0,0 +1,28 @@ +#include + +#include + + + +namespace bringauto::modules { + +Buffer IModuleManagerLibraryHandler::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 IModuleManagerLibraryHandler::constructBufferByTakeOwnership(struct ::buffer &buffer) { + if (buffer.data == nullptr || buffer.size_in_bytes == 0) { + throw Buffer::BufferNotAllocated { "Buffer not allocated - cannot take ownership" }; + } + return { buffer, deallocate_ }; +} + +} diff --git a/source/bringauto/modules/ModuleHandler.cpp b/source/bringauto/modules/ModuleHandler.cpp index bae697cb..a05e34a9 100644 --- a/source/bringauto/modules/ModuleHandler.cpp +++ b/source/bringauto/modules/ModuleHandler.cpp @@ -188,7 +188,7 @@ void ModuleHandler::handleStatus(const ip::DeviceStatus &status) const { 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) { diff --git a/source/bringauto/modules/ModuleManagerLibraryHandlerAsync.cpp b/source/bringauto/modules/ModuleManagerLibraryHandlerAsync.cpp new file mode 100644 index 00000000..0670aa3b --- /dev/null +++ b/source/bringauto/modules/ModuleManagerLibraryHandlerAsync.cpp @@ -0,0 +1,240 @@ +#include +#include + +#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); + try + { + moduleBinaryProcess_.wait(); + } + catch (const std::exception& e) + { + settings::Logger::logError("Failed to wait for module binary process: {}", e.what()); + } + } +} + +void ModuleManagerLibraryHandlerAsync::loadLibrary(const std::filesystem::path &path) { + if(moduleBinaryProcess_.valid()) { + ::kill(moduleBinaryProcess_.id(), SIGTERM); + try { + moduleBinaryProcess_.wait(); + } catch(const std::system_error &e) { + settings::Logger::logError("Failed to wait for previous module binary process: {}", e.what()); + } + } + // boost::process::child constructor throws boost::process::process_error on spawn failure. + moduleBinaryProcess_ = boost::process::child { moduleBinaryPath_.string(), "-m", path.string() }; + const auto deadline = std::chrono::steady_clock::now() + settings::AeronClientConstants::aeron_client_startup_timeout; + while(std::chrono::steady_clock::now() < deadline) { + if(aeronClient.callFunc(fleet_protocol::async_function_execution_definitions::getModuleNumberAsync).has_value()) { + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds { 10 }); + } + throw std::runtime_error { "Module binary " + moduleBinaryPath_.string() + " did not become ready within startup timeout" }; +} + +int ModuleManagerLibraryHandlerAsync::getModuleNumber() { + std::lock_guard lock { getModuleNumberMutex_ }; + return aeronClient.callFunc(fleet_protocol::async_function_execution_definitions::getModuleNumberAsync).value_or(NOT_OK); +} + +int ModuleManagerLibraryHandlerAsync::isDeviceTypeSupported(unsigned int device_type) { + std::lock_guard lock { isDeviceTypeSupportedMutex_ }; + return aeronClient.callFunc(fleet_protocol::async_function_execution_definitions::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 { sendStatusConditionMutex_ }; + fleet_protocol::async_function_execution_definitions::ConvertibleBuffer current_status_raw_buffer; + fleet_protocol::async_function_execution_definitions::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::async_function_execution_definitions::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::async_function_execution_definitions::ConvertibleBuffer new_status_raw_buffer; + fleet_protocol::async_function_execution_definitions::ConvertibleBuffer current_status_raw_buffer; + fleet_protocol::async_function_execution_definitions::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::async_function_execution_definitions::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::async_function_execution_definitions::ConvertibleBuffer current_status_raw_buffer; + fleet_protocol::async_function_execution_definitions::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::async_function_execution_definitions::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 + if (ret.value().buffer.data != nullptr) { + 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::async_function_execution_definitions::ConvertibleBuffer current_error_raw_buffer; + fleet_protocol::async_function_execution_definitions::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::async_function_execution_definitions::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::async_function_execution_definitions::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::async_function_execution_definitions::ConvertibleBuffer status_raw_buffer; + if (status.isAllocated()) { + status_raw_buffer = status.getStructBuffer(); + } + + return aeronClient.callFunc(fleet_protocol::async_function_execution_definitions::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::async_function_execution_definitions::ConvertibleBuffer command_raw_buffer; + if (command.isAllocated()) { + command_raw_buffer = command.getStructBuffer(); + } + + return aeronClient.callFunc(fleet_protocol::async_function_execution_definitions::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; +} + +} diff --git a/source/bringauto/modules/ModuleManagerLibraryHandler.cpp b/source/bringauto/modules/ModuleManagerLibraryHandlerLocal.cpp similarity index 70% rename from source/bringauto/modules/ModuleManagerLibraryHandler.cpp rename to source/bringauto/modules/ModuleManagerLibraryHandlerLocal.cpp index b075a5e7..69f4eb78 100644 --- a/source/bringauto/modules/ModuleManagerLibraryHandler.cpp +++ b/source/bringauto/modules/ModuleManagerLibraryHandlerLocal.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -20,17 +20,17 @@ struct FunctionTypeDeducer> { 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()}; + throw std::runtime_error {"Unable to load library " + path.string() + ": " + dlerror()}; } isDeviceTypeSupported_ = reinterpret_cast::fncptr>(checkFunction( "is_device_type_supported")); @@ -57,7 +57,7 @@ void ModuleManagerLibraryHandler::loadLibrary(const std::filesystem::path &path) log::logDebug("Library " + path.string() + " was successfully loaded"); } -void *ModuleManagerLibraryHandler::checkFunction(const char *functionName) const { +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"}; @@ -65,17 +65,17 @@ void *ModuleManagerLibraryHandler::checkFunction(const char *functionName) const return function; } -int ModuleManagerLibraryHandler::getModuleNumber() const { +int ModuleManagerLibraryHandlerLocal::getModuleNumber() { return getModuleNumber_(); } -int ModuleManagerLibraryHandler::isDeviceTypeSupported(unsigned int device_type) const { +int ModuleManagerLibraryHandlerLocal::isDeviceTypeSupported(unsigned int device_type) { return isDeviceTypeSupported_(device_type); } -int ModuleManagerLibraryHandler::sendStatusCondition(const Buffer ¤t_status, - const Buffer &new_status, - unsigned int device_type) const { +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 {}; @@ -89,10 +89,10 @@ int ModuleManagerLibraryHandler::sendStatusCondition(const Buffer ¤t_statu return sendStatusCondition_(current_status_raw_buffer, new_status_raw_buffer, device_type); } -int ModuleManagerLibraryHandler::generateCommand(Buffer &generated_command, - const Buffer &new_status, - const Buffer ¤t_status, - const Buffer ¤t_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 {}; @@ -118,9 +118,9 @@ int ModuleManagerLibraryHandler::generateCommand(Buffer &generated_command, return ret; } -int ModuleManagerLibraryHandler::aggregateStatus(Buffer &aggregated_status, - const Buffer ¤t_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 {}; @@ -141,9 +141,9 @@ int ModuleManagerLibraryHandler::aggregateStatus(Buffer &aggregated_status, return ret; } -int ModuleManagerLibraryHandler::aggregateError(Buffer &error_message, - const Buffer ¤t_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 {}; @@ -165,7 +165,7 @@ 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 {}; const int ret = generateFirstCommand_(&raw_buffer, device_type); if (ret == OK) { @@ -176,7 +176,7 @@ int ModuleManagerLibraryHandler::generateFirstCommand(Buffer &default_command, u return ret; } -int ModuleManagerLibraryHandler::statusDataValid(const Buffer &status, unsigned int device_type) const { +int ModuleManagerLibraryHandlerLocal::statusDataValid(const Buffer &status, unsigned int device_type) { struct ::buffer raw_buffer {}; if (status.isAllocated()) { raw_buffer = status.getStructBuffer(); @@ -184,7 +184,7 @@ int ModuleManagerLibraryHandler::statusDataValid(const Buffer &status, unsigned return statusDataValid_(raw_buffer, device_type); } -int ModuleManagerLibraryHandler::commandDataValid(const Buffer &command, unsigned int device_type) const { +int ModuleManagerLibraryHandlerLocal::commandDataValid(const Buffer &command, unsigned int device_type) { struct ::buffer raw_buffer {}; if (command.isAllocated()) { raw_buffer = command.getStructBuffer(); @@ -192,31 +192,12 @@ int ModuleManagerLibraryHandler::commandDataValid(const Buffer &command, unsigne return commandDataValid_(raw_buffer, device_type); } -int ModuleManagerLibraryHandler::allocate(struct buffer *buffer_pointer, size_t size_in_bytes) const { +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) const { +void ModuleManagerLibraryHandlerLocal::deallocate(struct buffer *buffer) const { deallocate_(buffer); } -Buffer ModuleManagerLibraryHandler::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 ModuleManagerLibraryHandler::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/settings/SettingsParser.cpp b/source/bringauto/settings/SettingsParser.cpp index 18d78021..17c2171d 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); @@ -55,8 +54,7 @@ bool SettingsParser::areCmdArgumentsCorrect() const { }; 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()); @@ -98,6 +96,10 @@ bool SettingsParser::areSettingsCorrect() const { 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,16 @@ bool SettingsParser::areSettingsCorrect() const { isCorrect = false; } + for(auto& externalConnectionSettings: settings_->externalConnectionSettingsList) { + for(auto const& externalModuleId: externalConnectionSettings.modules) { + if(!settings_->modulePaths.contains(externalModuleId)) { + std::cerr << "Module " << externalModuleId << + " is defined in external-connection endpoint modules but is not specified in module-paths" << std::endl; + isCorrect = false; + } + } + } + return isCorrect; } @@ -151,14 +163,24 @@ void SettingsParser::fillInternalServerSettings(const nlohmann::json &file) cons void SettingsParser::fillModulePathsSettings(const nlohmann::json &file) const { for(auto &[key, val]: file[std::string(Constants::MODULE_PATHS)].items()) { - settings_->modulePaths[stoi(key)] = val; + try { + val.get_to(settings_->modulePaths[stoi(key)]); + } catch(const std::invalid_argument &) { + throw std::invalid_argument { "Module path key '" + key + "' is not a valid integer module number" }; + } catch(const std::out_of_range &) { + throw std::out_of_range { "Module path key '" + key + "' is out of integer range" }; + } + } + if(file.contains(std::string(Constants::MODULE_BINARY_PATH))) { + file.at(std::string(Constants::MODULE_BINARY_PATH)).get_to(settings_->moduleBinaryPath); } } void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) const { - 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)]; + 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)]) { @@ -178,7 +200,7 @@ void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) 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>(); diff --git a/source/bringauto/structures/ModuleLibrary.cpp b/source/bringauto/structures/ModuleLibrary.cpp index c6d61c8b..f5b331cb 100644 --- a/source/bringauto/structures/ModuleLibrary.cpp +++ b/source/bringauto/structures/ModuleLibrary.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include @@ -11,15 +13,35 @@ ModuleLibrary::~ModuleLibrary() { [](auto &pair) { pair.second->destroy_status_aggregator(); }); } -void ModuleLibrary::loadLibraries(const std::unordered_map &libPaths) { +void ModuleLibrary::loadLibraries(const std::unordered_map &libPaths) { + std::shared_ptr handler; for(auto const &[key, path]: libPaths) { - auto handler = std::make_shared(); + 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, 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); + auto [it, inserted] = moduleLibraryHandlers.try_emplace(key, handler); + if(!inserted) { + settings::Logger::logWarning("Module with number: {} is already registered, skipping duplicate", key); + } + } +} + +void ModuleLibrary::loadLibraries(const std::unordered_map &libPaths, const std::filesystem::path &moduleBinaryPath) { + std::shared_ptr handler; + for(auto const &[key, path]: libPaths) { + 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.string(), key, handler->getModuleNumber()); + throw std::runtime_error {"Module numbers from config are not corresponding to binaries. Unable to continue. Fix configuration file."}; + } + auto [it, inserted] = moduleLibraryHandlers.try_emplace(key, handler); + if(!inserted) { + settings::Logger::logWarning("Module with number: {} is already registered, skipping duplicate", key); + } } } @@ -38,7 +60,7 @@ void ModuleLibrary::initStatusAggregators(std::shared_ptr &contex for(const auto &connection: context->settings->externalConnectionSettingsList) { const auto &modules = connection.modules; if(std::find(modules.cbegin(), modules.cend(), moduleNumber) != modules.cend()) { - statusAggregators.emplace(moduleNumber, statusAggregator); + statusAggregators.try_emplace(moduleNumber, statusAggregator); settings::Logger::logInfo("Module with number: {} started", moduleNumber); found = true; break; 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 8c204e36..1d16d3cc 100644 --- a/test/include/ErrorAggregatorTests.hpp +++ b/test/include/ErrorAggregatorTests.hpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -27,7 +27,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 2ed62f5c..b72c9a9a 100644 --- a/test/include/ExternalConnectionTests.hpp +++ b/test/include/ExternalConnectionTests.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/test/include/StatusAggregatorTests.hpp b/test/include/StatusAggregatorTests.hpp index 0a878c16..84f710ad 100644 --- a/test/include/StatusAggregatorTests.hpp +++ b/test/include/StatusAggregatorTests.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include @@ -39,7 +39,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 859f836a..a115554a 100644 --- a/test/include/testing_utils/ConfigMock.hpp +++ b/test/include/testing_utils/ConfigMock.hpp @@ -27,11 +27,11 @@ class ConfigMock { int port { 1636 }; } internal_server_settings; - std::unordered_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); + for (const auto& [key, value] : module_paths) { + result += std::format("\"{}\": \"{}\",\n", key, value.string()); } if (!result.empty()) { result.pop_back(); @@ -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/ErrorAggregatorTests.cpp b/test/source/ErrorAggregatorTests.cpp index 12d2dc4c..340de7fa 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); @@ -83,3 +85,45 @@ TEST_F(ErrorAggregatorTests, is_device_type_supported) { const int ret = errorAggregator_.is_device_type_supported(SUPPORTED_DEVICE_TYPE); EXPECT_EQ(ret, OK); } + +TEST_F(ErrorAggregatorTests, get_error_device_not_registered) { + bringauto::modules::Buffer error {}; + const auto deviceId = testing_utils::DeviceIdentificationHelper::createDeviceIdentification(MODULE, + SUPPORTED_DEVICE_TYPE, "button", "name", 10); + const int ret = errorAggregator_.get_error(error, deviceId); + EXPECT_EQ(ret, DEVICE_NOT_REGISTERED); +} + +TEST_F(ErrorAggregatorTests, get_error_after_add_status_device_registered) { + const auto status = init_status_buffer(); + const auto deviceId = testing_utils::DeviceIdentificationHelper::createDeviceIdentification(MODULE, + SUPPORTED_DEVICE_TYPE, "button", "name", 10); + int ret = errorAggregator_.add_status_to_error_aggregator(status, deviceId); + ASSERT_EQ(ret, OK); + bringauto::modules::Buffer error {}; + ret = errorAggregator_.get_error(error, deviceId); + EXPECT_NE(ret, DEVICE_NOT_REGISTERED); +} + +TEST_F(ErrorAggregatorTests, clear_error_aggregator_ok) { + const auto status = init_status_buffer(); + const auto deviceId = testing_utils::DeviceIdentificationHelper::createDeviceIdentification(MODULE, + SUPPORTED_DEVICE_TYPE, "button", "name", 10); + int ret = errorAggregator_.add_status_to_error_aggregator(status, deviceId); + ASSERT_EQ(ret, OK); + ret = errorAggregator_.clear_error_aggregator(); + EXPECT_EQ(ret, OK); +} + +TEST_F(ErrorAggregatorTests, get_error_not_available_after_clear) { + const auto status = init_status_buffer(); + const auto deviceId = testing_utils::DeviceIdentificationHelper::createDeviceIdentification(MODULE, + SUPPORTED_DEVICE_TYPE, "button", "name", 10); + int ret = errorAggregator_.add_status_to_error_aggregator(status, deviceId); + ASSERT_EQ(ret, OK); + ret = errorAggregator_.clear_error_aggregator(); + ASSERT_EQ(ret, OK); + bringauto::modules::Buffer error {}; + ret = errorAggregator_.get_error(error, deviceId); + EXPECT_EQ(ret, NO_MESSAGE_AVAILABLE); +} 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 81ea7c92..813218c3 100644 --- a/test/source/StatusAggregatorTests.cpp +++ b/test/source/StatusAggregatorTests.cpp @@ -1,5 +1,7 @@ #include #include +#include + #include @@ -41,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(); @@ -59,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); } @@ -94,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();