diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..202e2c1 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: Webkit diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..8b058c7 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,2 @@ +Checks: '-*,google-*,cppcoreguidelines-*,clang-*,readability-*,modernize-*,-cppcoreguidelines-*' +FormatStyle: 'file' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 193da6a..1346eeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1038,3 +1038,6 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# Doxygen dir +docs/html/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2cb061b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,352 @@ +cmake_minimum_required(VERSION 3.15) + +# +# Project details +# + +project( + frameworkd + VERSION 0.1.0 + LANGUAGES CXX +) + +string(APPEND CMAKE_CXX_FLAGS " -pthread") +# +# Set project options +# + +include(cmake/StandardSettings.cmake) +include(cmake/StaticAnalyzers.cmake) +include(cmake/Utils.cmake) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() +message(STATUS "Started CMake for ${PROJECT_NAME} v${PROJECT_VERSION}...\n") + +if (UNIX) + add_compile_options("$<$:-D_DEBUG>") #this will allow to use same _DEBUG macro available in both Linux as well as Windows - MSCV environment. Easy to put Debug specific code. +endif (UNIX) + + +# +# Setup alternative names +# + +if(${PROJECT_NAME}_USE_ALT_NAMES) + string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWERCASE) + string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPERCASE) +else() + set(PROJECT_NAME_LOWERCASE ${PROJECT_NAME}) + set(PROJECT_NAME_UPPERCASE ${PROJECT_NAME}) +endif() + +# +# Prevent building in the source directory +# + +if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n") +endif() + +# +# Enable package managers +# + + +# +# Create library, setup header and source files +# + +# Find all headers and implementation files +include(cmake/SourcesAndHeaders.cmake) + +if(${PROJECT_NAME}_BUILD_EXECUTABLE) + add_executable(${PROJECT_NAME} ${exe_sources}) + + if(${PROJECT_NAME}_VERBOSE_OUTPUT) + verbose_message("Found the following sources:") + foreach(source IN LISTS exe_sources) + verbose_message("* ${source}") + endforeach() + endif() + + if(${PROJECT_NAME}_ENABLE_UNIT_TESTING) + add_library(${PROJECT_NAME}_LIB ${headers} ${sources}) + + if(${PROJECT_NAME}_VERBOSE_OUTPUT) + verbose_message("Found the following headers:") + foreach(header IN LISTS headers) + verbose_message("* ${header}") + endforeach() + endif() + endif() +elseif(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + add_library(${PROJECT_NAME} INTERFACE) + + if(${PROJECT_NAME}_VERBOSE_OUTPUT) + verbose_message("Found the following headers:") + foreach(header IN LIST headers) + verbose_message("* ${header}") + endforeach() + endif() +else() + add_library( + ${PROJECT_NAME} + ${headers} + ${sources} + ) + + if(${PROJECT_NAME}_VERBOSE_OUTPUT) + verbose_message("Found the following sources:") + foreach(source IN LISTS sources) + verbose_message("* ${source}") + endforeach() + verbose_message("Found the following headers:") + foreach(header IN LISTS headers) + verbose_message("* ${header}") + endforeach() + endif() +endif() + +set_target_properties( + ${PROJECT_NAME} + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}" +) +if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) + set_target_properties( + ${PROJECT_NAME}_LIB + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" + OUTPUT_NAME ${PROJECT_NAME} + ) +endif() + +message(STATUS "Added all header and implementation files.\n") + +# +# Set the project standard and warnings +# + +if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) +else() + target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + + if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) + target_compile_features(${PROJECT_NAME}_LIB PUBLIC cxx_std_17) + endif() +endif() +include(cmake/CompilerWarnings.cmake) +set_project_warnings(${PROJECT_NAME}) + +verbose_message("Applied compiler warnings. Using standard ${CMAKE_CXX_STANDARD}.\n") + +# +# Enable Doxygen +# + +include(cmake/Doxygen.cmake) + +# +# Model project dependencies +# + +# Identify and link with the specific "packages" the project uses +find_package(sdbus-c++ REQUIRED) +find_package(nlohmann_json REQUIRED) +target_link_libraries( + ${PROJECT_NAME} +# PUBLIC +# dependency1 ... + PRIVATE + SDBusCpp::sdbus-c++ + nlohmann_json::nlohmann_json +# ${PROJECT_NAME}_PROJECT_OPTIONS +# ${PROJECT_NAME}_PROJECT_WARNINGS +) +#if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) +# target_link_libraries( +# ${PROJECT_NAME}_LIB +# PUBLIC +# dependency1 ... +# ) +#endif() + +verbose_message("Successfully added all dependencies and linked against them.") + +# +# Set the build/user include directories +# + +# Allow usage of header files in the `src` directory, but only for utilities +if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + target_include_directories( + ${PROJECT_NAME} + INTERFACE + $ + $ + ) +else() + target_include_directories( + ${PROJECT_NAME} + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) + target_include_directories( + ${PROJECT_NAME}_LIB + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + endif() +endif() + +message(STATUS "Finished setting up include directories.") + +# +# Provide alias to library for +# + +if(${PROJECT_NAME}_BUILD_EXECUTABLE) + add_executable(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +else() + add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +endif() + +verbose_message("Project is now aliased as ${PROJECT_NAME}::${PROJECT_NAME}.\n") + +# +# Format the project using the `clang-format` target (i.e: cmake --build build --target clang-format) +# + +add_clang_format_target() + +# +# Install library for easy downstream inclusion +# + +include(GNUInstallDirs) +install( + TARGETS + ${PROJECT_NAME} + EXPORT + ${PROJECT_NAME}Targets + LIBRARY DESTINATION + ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION + ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION + ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION + include + PUBLIC_HEADER DESTINATION + include +) + +install( + EXPORT + ${PROJECT_NAME}Targets + FILE + ${PROJECT_NAME}Targets.cmake + NAMESPACE + ${PROJECT_NAME}:: + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) + +# +# Add version header +# + +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/version.hpp.in + include/${PROJECT_NAME_LOWERCASE}/version.hpp + @ONLY +) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME_LOWERCASE}/version.hpp + DESTINATION + include/${PROJECT_NAME_LOWERCASE} +) + +# +# Install the `include` directory +# + +install( + DIRECTORY + include/${PROJECT_NAME_LOWERCASE} + DESTINATION + include +) + +verbose_message("Install targets succesfully build. Install with `cmake --build --target install --config `.") + +# +# Quick `ConfigVersion.cmake` creation +# + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${PROJECT_NAME}ConfigVersion.cmake + VERSION + ${PROJECT_VERSION} + COMPATIBILITY + SameMajorVersion +) + +configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + INSTALL_DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) + +# +# Generate export header if specified +# + +if(${PROJECT_NAME}_GENERATE_EXPORT_HEADER) + include(GenerateExportHeader) + generate_export_header(${PROJECT_NAME}) + install( + FILES + ${PROJECT_BINARY_DIR}/${PROJECT_NAME_LOWERCASE}_export.h + DESTINATION + include + ) + + message(STATUS "Generated the export header `${PROJECT_NAME_LOWERCASE}_export.h` and installed it.") +endif() + +message(STATUS "Finished building requirements for installing the package.\n") + +# +# Unit testing setup +# + +if(${PROJECT_NAME}_ENABLE_UNIT_TESTING) + enable_testing() + message(STATUS "Build unit tests for the project. Tests should always be found in the test folder\n") + add_subdirectory(test) +endif() diff --git a/README.md b/README.md index 7725d8e..4078aaf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Daemons Framework

+

Frameworkd

A framework for creating daemons in our architecture

@@ -8,26 +8,26 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + +

diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..2b5d58e --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,99 @@ +# from here: +# +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Avai +# lable.md +# Courtesy of Jason Turner + +function(set_project_warnings project_name) + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss + # of data + /w14254 # 'operator': conversion from 'type1:field_bits' to + # 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class + # virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not + # virtual instances of this class may not be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable + # declared in the for-loop is used outside the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing + # an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected + # operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend + # 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may + # cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined + # conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a + # parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a + # non-virtual destructor. This helps catch hard to + # track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual + # function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output + # (ie printf) + ) + + if (${PROJECT_NAME}_WARNINGS_AS_ERRORS) + set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) + set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) + endif() + + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks + # do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were + # probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + ) + + if(MSVC) + set(PROJECT_WARNINGS ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") + endif() + + if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) + else() + target_compile_options(${project_name} PUBLIC ${PROJECT_WARNINGS}) + endif() + + if(NOT TARGET ${project_name}) + message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") + endif() +endfunction() diff --git a/cmake/Doxygen.cmake b/cmake/Doxygen.cmake new file mode 100644 index 0000000..bd6fe45 --- /dev/null +++ b/cmake/Doxygen.cmake @@ -0,0 +1,11 @@ +if(${PROJECT_NAME}_ENABLE_DOXYGEN) + set(DOXYGEN_CALLER_GRAPH YES) + set(DOXYGEN_CALL_GRAPH YES) + set(DOXYGEN_EXTRACT_ALL YES) + set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs) + + find_package(Doxygen REQUIRED dot) + doxygen_add_docs(doxygen-docs ${PROJECT_SOURCE_DIR}) + + verbose_message("Doxygen has been setup and documentation is now available.") +endif() diff --git a/cmake/SourcesAndHeaders.cmake b/cmake/SourcesAndHeaders.cmake new file mode 100644 index 0000000..7878be5 --- /dev/null +++ b/cmake/SourcesAndHeaders.cmake @@ -0,0 +1,30 @@ +set(sources + src/utilities/config-handler/config-handler.cpp + src/classes/daemon/daemon.cpp + src/classes/static-service-proxy/static-service-proxy.cpp + src/classes/locked-storage/locked-storage.cpp + src/classes/dbus-handler/dbus-handler.cpp + src/classes/service-proxy/service-proxy.cpp + src/classes/routine-service-proxy/routine-service-proxy.cpp + src/classes/service-handler/service-handler.cpp +) + +set(exe_sources + ${sources} +) + +set(headers + include/frameworkd/utilities/config-handler/config-handler.hpp + include/frameworkd/classes/daemon/daemon.hpp + include/frameworkd/classes/iservice/iservice.hpp + include/frameworkd/classes/static-service-proxy/static-service-proxy.hpp + include/frameworkd/classes/locked-storage/locked-storage.hpp + include/frameworkd/classes/dbus-handler/dbus-handler.hpp + include/frameworkd/classes/service-proxy/service-proxy.hpp + include/frameworkd/classes/routine-service-proxy/routine-service-proxy.hpp + include/frameworkd/classes/service-handler/service-handler.hpp +) + +set(test_sources + #src/daemon_test.cpp +) diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake new file mode 100644 index 0000000..7b7bedc --- /dev/null +++ b/cmake/StandardSettings.cmake @@ -0,0 +1,78 @@ +# +# Project settings +# + +option(${PROJECT_NAME}_BUILD_EXECUTABLE "Build the project as an executable, rather than a library." OFF) +option(${PROJECT_NAME}_BUILD_HEADERS_ONLY "Build the project as a header-only library." OFF) +option(${PROJECT_NAME}_USE_ALT_NAMES "Use alternative names for the project, such as naming the include directory all lowercase." ON) + +# +# Compiler options +# + +option(${PROJECT_NAME}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." OFF) + +# +# Unit testing +# +# Currently supporting: GoogleTest. + +option(${PROJECT_NAME}_ENABLE_UNIT_TESTING "Enable unit tests for the projects (from the `test` subfolder)." ON) + +option(${PROJECT_NAME}_USE_GTEST "Use the GoogleTest project for creating unit tests." ON) +option(${PROJECT_NAME}_USE_GOOGLE_MOCK "Use the GoogleMock project for extending the unit tests." OFF) + +# +# Static analyzers +# +# Currently supporting: Clang-Tidy. + +option(${PROJECT_NAME}_ENABLE_CLANG_TIDY "Enable static analysis with Clang-Tidy." OFF) + +# +# Code coverage +# + +option(${PROJECT_NAME}_ENABLE_CODE_COVERAGE "Enable code coverage through GCC." OFF) + +# +# Doxygen +# + +option(${PROJECT_NAME}_ENABLE_DOXYGEN "Enable Doxygen documentation builds of source." OFF) + +# +# Miscelanious options +# + +# Generate compile_commands.json for clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(${PROJECT_NAME}_VERBOSE_OUTPUT "Enable verbose output, allowing for a better understanding of each step taken." ON) +option(${PROJECT_NAME}_GENERATE_EXPORT_HEADER "Create a `project_export.h` file containing all exported symbols." OFF) + +# Export all symbols when building a shared library +if(BUILD_SHARED_LIBS) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) +endif() + +option(${PROJECT_NAME}_ENABLE_LTO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)." OFF) +if(${PROJECT_NAME}_ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(SEND_ERROR "IPO is not supported: ${output}.") + endif() +endif() + + +option(${PROJECT_NAME}_ENABLE_CCACHE "Enable the usage of Ccache, in order to speed up rebuild times." ON) +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 0000000..7943ab4 --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,9 @@ +if(${PROJECT_NAME}_ENABLE_CLANG_TIDY) + find_program(CLANGTIDY clang-tidy) + if(CLANGTIDY) + set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) + message("Clang-Tidy finished setting up.") + else() + message(SEND_ERROR "Clang-Tidy requested but executable not found.") + endif() +endif() diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake new file mode 100644 index 0000000..754c7cd --- /dev/null +++ b/cmake/Utils.cmake @@ -0,0 +1,40 @@ +# +# Print a message only if the `VERBOSE_OUTPUT` option is on +# + +function(verbose_message content) + if(${PROJECT_NAME}_VERBOSE_OUTPUT) + message(STATUS ${content}) + endif() +endfunction() + +# +# Add a target for formating the project using `clang-format` (i.e: cmake --build build --target clang-format) +# + +function(add_clang_format_target) + if(NOT ${PROJECT_NAME}_CLANG_FORMAT_BINARY) + find_program(${PROJECT_NAME}_CLANG_FORMAT_BINARY clang-format) + endif() + + if(${PROJECT_NAME}_CLANG_FORMAT_BINARY) + if(${PROJECT_NAME}_BUILD_EXECUTABLE) + add_custom_target(clang-format + COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} + -i ${exe_sources} ${headers} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + elseif(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + add_custom_target(clang-format + COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} + -i ${headers} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + else() + add_custom_target(clang-format + COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} + -i ${sources} ${headers} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + endif() + + message(STATUS "Format the project using the `clang-format` target (i.e: cmake --build build --target clang-format).\n") + endif() +endfunction() diff --git a/cmake/frameworkdConfig.cmake.in b/cmake/frameworkdConfig.cmake.in new file mode 100644 index 0000000..2ac739c --- /dev/null +++ b/cmake/frameworkdConfig.cmake.in @@ -0,0 +1,9 @@ +set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@) + +@PACKAGE_INIT@ + +set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@CMAKE_INSTALL_FULL_INCLUDEDIR@") + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") + +check_required_components(@PROJECT_NAME@) diff --git a/cmake/version.hpp.in b/cmake/version.hpp.in new file mode 100644 index 0000000..985bad6 --- /dev/null +++ b/cmake/version.hpp.in @@ -0,0 +1,11 @@ +#ifndef @PROJECT_NAME_UPPERCASE@_VERSION_H_ +#define @PROJECT_NAME_UPPERCASE@_VERSION_H_ + +#define @PROJECT_NAME_UPPERCASE@_VERSION "@PROJECT_VERSION@" + +#define @PROJECT_NAME_UPPERCASE@_MAJOR_VERSION @PROJECT_VERSION_MAJOR@ +#define @PROJECT_NAME_UPPERCASE@_MINOR_VERSION @PROJECT_VERSION_MINOR@ +#define @PROJECT_NAME_UPPERCASE@_PATCH_VERSION @PROJECT_VERSION_PATCH@ + +#endif // @PROJECT_NAME_UPPERCASE@_VERSION_H_ + diff --git a/examples/.gitkeep b/examples/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/ping.cpp b/examples/ping.cpp new file mode 100644 index 0000000..8c3a816 --- /dev/null +++ b/examples/ping.cpp @@ -0,0 +1,69 @@ +#include +#include + +class PingService : public RoutineService { + int pingCounter = 0; + std::chrono::nanoseconds pongDelay; + +public: + PingService(std::string serviceId) + : RoutineService { serviceId } + { + } + + const DBusHandler::Path pongMethodPath { + "zfkd.dbus.pong", + "/zfkd/dbus/pong", + "zfkd.dbus.pong", + "pong" + }; + + const DBusHandler::Path pongSignalPath { + "zfkd.dbus.pong", + "/zfkd/dbus/pong", + "zfkd.dbus.pong", + "pongDelay" + }; + + void setup() override + { + + DBusHandler::subscribeToSignal(pongSignalPath, [&](nlohmann::json pongWrapper) { + std::chrono::nanoseconds finalTime; + finalTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + pongDelay = finalTime - pongDelay; + std::cout << "Delay: " << pongDelay.count() << "ns" << std::endl; + }); + + } + + void routine() override + { + std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 2000 + 200)); + pongDelay = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + nlohmann::json pingWrapper; + pingWrapper["ping"] = pingCounter; + + nlohmann::json pongWrapper = DBusHandler::callMethod(pongMethodPath, pingWrapper); + pingCounter = pongWrapper["ping"]; + + pingCounter++; + + std::cout << "Ping: " << pingCounter << std::endl; + } + + void destroy() override + { + } +}; + +int main() +{ + Daemon newDaemon("ping"); + PingService pingService("ping"); + newDaemon.deploy(pingService); + newDaemon.run(); + return 0; +} diff --git a/examples/ping.json b/examples/ping.json new file mode 100644 index 0000000..c8b9de2 --- /dev/null +++ b/examples/ping.json @@ -0,0 +1,9 @@ +{ + "serviceId": "ping", + "proxys": { + "ping":{} + }, + "data": { + "anything": "anything" + } +} diff --git a/examples/pong.cpp b/examples/pong.cpp new file mode 100644 index 0000000..e5931e0 --- /dev/null +++ b/examples/pong.cpp @@ -0,0 +1,56 @@ +#include +#include + +class PongService : public StaticService { + int pongCounter = 0; + +public: + PongService(std::string serviceId) + : StaticService { serviceId } + { + } + + const DBusHandler::Path pongMethodPath { + "zfkd.dbus.pong", + "/zfkd/dbus/pong", + "zfkd.dbus.pong", + "pong" + }; + + const DBusHandler::Path pongSignalPath { + "zfkd.dbus.pong", + "/zfkd/dbus/pong", + "zfkd.dbus.pong", + "pongDelay" + }; + + void setup() override + { + DBusHandler::registerSignal(pongSignalPath); + + DBusHandler::registerMethod(pongMethodPath, [&](nlohmann::json pingWrapper) { + pongCounter = pingWrapper["ping"]; + pongCounter++; + pingWrapper["ping"] = pongCounter; + + std::cout << "Pong: " << pongCounter << std::endl; + + DBusHandler::emitSignal(pongSignalPath, pingWrapper); + + return pingWrapper; + }); + } + + void destroy() override + { + } +}; + +int main() +{ + Daemon newDaemon("pong"); + PongService pongService("pong"); + newDaemon.deploy(pongService); + newDaemon.run(); + return 0; +} diff --git a/examples/pong.json b/examples/pong.json new file mode 100644 index 0000000..a454221 --- /dev/null +++ b/examples/pong.json @@ -0,0 +1,9 @@ +{ + "serviceId": "pong", + "proxys": { + "pong":{} + }, + "data": { + "anything": "anything" + } +} diff --git a/examples/zfkd.dbus.ping.conf b/examples/zfkd.dbus.ping.conf new file mode 100644 index 0000000..c920fc7 --- /dev/null +++ b/examples/zfkd.dbus.ping.conf @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/examples/zfkd.dbus.pong.conf b/examples/zfkd.dbus.pong.conf new file mode 100644 index 0000000..be620f9 --- /dev/null +++ b/examples/zfkd.dbus.pong.conf @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/include/classes/daemon/daemon.hpp b/include/classes/daemon/daemon.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/include/classes/endpoint/endpoint.hpp b/include/classes/endpoint/endpoint.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/include/classes/routine/routine.hpp b/include/classes/routine/routine.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/include/frameworkd/classes/daemon/daemon.hpp b/include/frameworkd/classes/daemon/daemon.hpp new file mode 100644 index 0000000..546b058 --- /dev/null +++ b/include/frameworkd/classes/daemon/daemon.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../../utilities/config-handler/config-handler.hpp" +#include "../dbus-handler/dbus-handler.hpp" +#include "../service-handler/service-handler.hpp" + +class Daemon { +public: + explicit Daemon(const std::string& filePath); + + void deploy(StaticService& userService); + void deploy(RoutineService& userService); + + void run(); + + void getDaemonStatus(); + + auto getConfigHandler() -> ConfigHandler; + +private: + ConfigHandler m_configHandler; + ServiceHandler m_serviceHandler; + DBusHandler m_dbusHandler; +}; diff --git a/include/frameworkd/classes/dbus-handler/dbus-handler.hpp b/include/frameworkd/classes/dbus-handler/dbus-handler.hpp new file mode 100644 index 0000000..1642391 --- /dev/null +++ b/include/frameworkd/classes/dbus-handler/dbus-handler.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class DBusHandler { + +public: + using DBusObjectMap = std::map>; + using DBusProxyMap = std::map>; + using DBusVoidCallback = std::function; + using DBusCallback = std::function; + + struct Path { + + std::string service; + std::string objectPath; + std::string interface; + std::string functionality; + + Path() = default; + + Path(const std::string& serviceName, const std::string& serviceObjectPath, const std::string& serviceInterface, + const std::string& serviceFunctionality) + : service { std::move(serviceName) } + , objectPath { std::move(serviceObjectPath) } + , interface { std::move(serviceInterface) } + , functionality { std::move(serviceFunctionality) } + { + if (service.empty() || objectPath.empty() || interface.empty() || functionality.empty()) { + throw std::invalid_argument("Invalid path: empty string doesn't satisfies path format"); + } + }; + }; + +private: + static std::string s_serviceName; + static bool s_started; + static bool s_isServer; + + static std::unique_ptr s_connection; + + static DBusObjectMap s_DBusObjects; + static DBusProxyMap s_DBusProxys; + + static auto findProxy(const DBusHandler::Path& path) -> sdbus::IProxy*; + + static auto findObject(const DBusHandler::Path& path) -> sdbus::IObject*; + +public: + static void start(); + + static void start(const std::string& serviceName); + + static void start(const std::string& serviceName, DBusObjectMap DBusObjects, DBusProxyMap DBusProxys); + + static void start(const std::string& serviceName, DBusObjectMap DBusObjects, DBusProxyMap DBusProxys, + std::unique_ptr connection); + + static void registerMethod(const DBusHandler::Path& path, DBusCallback&& callback); + + static void subscribeToSignal(const DBusHandler::Path& path, DBusVoidCallback&& callback); + + static void registerSignal(const DBusHandler::Path& path); + + static auto callMethod(const DBusHandler::Path& path, nlohmann::json arg) -> nlohmann::json; + + static void callMethodAsync(const DBusHandler::Path& path, nlohmann::json arg, DBusVoidCallback&& callback); + + static void emitSignal(const DBusHandler::Path& path, nlohmann::json arg); + + static void exposeProperty(const DBusHandler::Path& path, std::function&& getter, + DBusVoidCallback&& setter); + + static auto getProperty(const DBusHandler::Path& path) -> nlohmann::json; + + static void getProperty(const DBusHandler::Path& path, DBusVoidCallback&& callback); + + static void setProperty(const DBusHandler::Path& path, nlohmann::json arg); + + static void finish(); +}; diff --git a/include/frameworkd/classes/iservice/iservice.hpp b/include/frameworkd/classes/iservice/iservice.hpp new file mode 100644 index 0000000..b6d8622 --- /dev/null +++ b/include/frameworkd/classes/iservice/iservice.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +class IService { +public: + std::string m_serviceId; + + /** + * @brief An interface function to be implemented by + * the user to make the "setup"/configuration + * of its class' parameter-members. + */ + virtual void setup() = 0; + + /** + * @brief An interface function to be implemented by + * the user if it wants to create a + * Routine Service. If it's implemented, + * this function is going to run in a loop, so its + * instructions are going to be executed "routinely". + */ + virtual void routine() = 0; + + /** + * @brief An interface function to be implemented by + * the user to execute the needed instructions + * to safely "destroy" the resources used by the + * class' previous functions. + */ + virtual void destroy() = 0; + + /** + * @brief The constructor of this interface. + * + * @param serviceId std::string that is the id related + * to the instantiated class. + */ + explicit IService(std::string serviceId) + : m_serviceId { serviceId } {}; + + /** + * @brief A virtual destructor to ensure + * inheritance compatibility. + */ + virtual ~IService() = default; +}; + +class StaticService : public IService { +public: + explicit StaticService(std::string serviceId) + : IService { serviceId } {}; + + void routine() final { } + + ~StaticService() override = default; +}; + +class RoutineService : public IService { +public: + explicit RoutineService(std::string serviceId) + : IService { serviceId } {}; + + ~RoutineService() override = default; +}; + diff --git a/src/classes/locked-storage/locked-storage.hpp b/include/frameworkd/classes/locked-storage/locked-storage.hpp similarity index 100% rename from src/classes/locked-storage/locked-storage.hpp rename to include/frameworkd/classes/locked-storage/locked-storage.hpp diff --git a/include/frameworkd/classes/routine-service-proxy/routine-service-proxy.hpp b/include/frameworkd/classes/routine-service-proxy/routine-service-proxy.hpp new file mode 100644 index 0000000..fd94797 --- /dev/null +++ b/include/frameworkd/classes/routine-service-proxy/routine-service-proxy.hpp @@ -0,0 +1,65 @@ +#pragma once +#include "../service-proxy/service-proxy.hpp" +#include + +class RoutineServiceProxy : public ServiceProxy { +public: + RoutineServiceProxy(RoutineService& realService, std::map depsMap); + ~RoutineServiceProxy() override = default; + +protected: + friend class ServiceHandler; + + std::thread m_thread; + std::mutex m_updateMtx; + + class RoutineState : public ServiceState { + + public: + RoutineServiceProxy& m_upperProxy; + + RoutineState(state_t state, RoutineServiceProxy& upperProxy) + : m_upperProxy { upperProxy } + , ServiceState { state } {}; + }; + + class MissingDependencies : public RoutineState { + public: + explicit MissingDependencies(RoutineServiceProxy& upperProxy) + : RoutineState { MISSING_DEPENDENCIES, upperProxy } {}; + + void allFine() override; + }; + + class StandBy : public RoutineState { + public: + explicit StandBy(RoutineServiceProxy& upperProxy) + : RoutineState { STAND_BY, upperProxy } {}; + }; + + class Running : public RoutineState { + public: + explicit Running(RoutineServiceProxy& upperProxy) + : RoutineState { RUNNING, upperProxy } {}; + + void somethingIsMissing() override; + }; + + class Stopped : public RoutineState { + public: + explicit Stopped(RoutineServiceProxy& upperProxy) + : RoutineState { STOPPED, upperProxy } {}; + }; + + class Finished : public RoutineState { + public: + explicit Finished(RoutineServiceProxy& upperProxy) + : RoutineState { FINISHED, upperProxy } {}; + }; + + void serviceCycle() override; + void changeState(ServiceState::state_t newState) override; + void weave(); + void cut(); +}; + diff --git a/include/frameworkd/classes/service-handler/service-handler.hpp b/include/frameworkd/classes/service-handler/service-handler.hpp new file mode 100644 index 0000000..1dc205c --- /dev/null +++ b/include/frameworkd/classes/service-handler/service-handler.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "../iservice/iservice.hpp" +#include "../routine-service-proxy/routine-service-proxy.hpp" +#include "../service-proxy/service-proxy.hpp" +#include "../static-service-proxy/static-service-proxy.hpp" +#include +#include +#include +#include +#include + +class ServiceHandler { +public: + explicit ServiceHandler(nlohmann::json servicesConfigs); + +protected: + std::map> m_serviceMap; + std::map> m_proxyDepsMap; + +public: + auto getProxyState(std::string serviceId) -> nlohmann::json; + auto getAllProxyState() -> nlohmann::json; + void buildServiceProxy(StaticService& userService); + void buildServiceProxy(RoutineService& userService); + void changeDependencyState(std::string serviceId, std::string dependencyId, ServiceProxy::ServiceState::state_t newState); + void updateServiceProxy(std::string serviceId); + void updateAllServiceProxys(); + void run(); +}; + diff --git a/include/frameworkd/classes/service-proxy/service-proxy.hpp b/include/frameworkd/classes/service-proxy/service-proxy.hpp new file mode 100644 index 0000000..bb49a3d --- /dev/null +++ b/include/frameworkd/classes/service-proxy/service-proxy.hpp @@ -0,0 +1,80 @@ +#pragma once +#include "../iservice/iservice.hpp" +#include +#include +#include +#include + +class ServiceProxy { +public: + friend class ServiceHandler; + + enum proxy_t { STATIC_SERVICE = 0, + ROUTINE_SERVICE }; + + class ServiceState { + public: + enum state_t { MISSING_DEPENDENCIES = 0, + UNINITIALIZED, + RUNNING, + STOPPED, + FINISHED, + STAND_BY, + UNKNOWN }; + + const state_t m_state; + + explicit ServiceState(state_t state) + : m_state { state } {}; + + virtual void somethingIsMissing() { } + virtual void allFine() { } + [[nodiscard]] auto getState() const -> state_t; + }; + +protected: + class ProxyConfigs { + public: + struct Dependency { + ServiceState::state_t m_reqrState; + ServiceState::state_t m_currState; + + Dependency(ServiceState::state_t reqrState, ServiceState::state_t currState) + : m_reqrState { reqrState } + , m_currState { currState } {}; + + Dependency() = default; + }; + + std::map m_depsMap; + + void changeDep(std::string dependencieId, ServiceState::state_t currState); + explicit ProxyConfigs(std::map depsMap); + }; + + proxy_t m_proxyType; + std::mutex m_stateMtx; + IService& m_realService; + ProxyConfigs m_proxyConfigs; + std::string m_realServiceId; + std::unique_ptr m_state; + + auto checkState() -> ServiceState::state_t; + void configure(); + + virtual void autoUpdate(); + virtual void serviceCycle() { } + virtual void changeState(ServiceState::state_t newState) { } + +public: + ServiceProxy(IService& realService, proxy_t proxyType, std::map depsMap) + : m_realService { realService } + , m_proxyType { proxyType } + , m_proxyConfigs { depsMap } + , m_realServiceId { realService.m_serviceId } {}; + + virtual auto reportState() -> nlohmann::json; + + virtual ~ServiceProxy() = default; +}; + diff --git a/include/frameworkd/classes/static-service-proxy/static-service-proxy.hpp b/include/frameworkd/classes/static-service-proxy/static-service-proxy.hpp new file mode 100644 index 0000000..e6ef6fb --- /dev/null +++ b/include/frameworkd/classes/static-service-proxy/static-service-proxy.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "../service-proxy/service-proxy.hpp" +#include +#include +#include + +class StaticServiceProxy : public ServiceProxy { +public: + StaticServiceProxy(StaticService& realService, std::map depsMap); + ~StaticServiceProxy() override = default; + +protected: + friend class ServiceHandler; + + bool m_runnedOnce; + + class StaticState : public ServiceState { + protected: + StaticServiceProxy& m_upperProxy; + + public: + StaticState(state_t state, StaticServiceProxy& upperProxy) + : ServiceState { state } + , m_upperProxy { upperProxy } {}; + }; + + class MissingDependencies : public StaticState { + public: + explicit MissingDependencies(StaticServiceProxy& upperProxy) + : StaticState { MISSING_DEPENDENCIES, upperProxy } {}; + void allFine() override; + }; + + class Uninitialized : public StaticState { + public: + explicit Uninitialized(StaticServiceProxy& upperProxy) + : StaticState { UNINITIALIZED, upperProxy } {}; + void somethingIsMissing() override; + }; + + class Running : public StaticState { + public: + explicit Running(StaticServiceProxy& upperProxy) + : StaticState { RUNNING, upperProxy } {}; + }; + + class StandBy : public StaticState { + public: + explicit StandBy(StaticServiceProxy& upperProxy) + : StaticState { STAND_BY, upperProxy } {}; + void somethingIsMissing() override; + }; + + void changeState(ServiceState::state_t newState) override; + void serviceCycle() override; +}; + diff --git a/include/frameworkd/utilities/config-handler/config-handler.hpp b/include/frameworkd/utilities/config-handler/config-handler.hpp new file mode 100644 index 0000000..b2d59ee --- /dev/null +++ b/include/frameworkd/utilities/config-handler/config-handler.hpp @@ -0,0 +1,22 @@ +#include +#include + +class ConfigHandler { +private: + static nlohmann::json m_config; + std::string m_fileName; + const std::vector m_requiredFields { + "data", "proxys", "serviceId" + }; + +public: + explicit ConfigHandler(const std::string& fileName); + void read(const std::string& fileName); + void read(); + auto operator[](const std::string& field) const -> const nlohmann::json; + auto getAllConfig() const -> const nlohmann::json; + +private: + [[nodiscard]] auto getConfig(const std::string& field) const -> const nlohmann::json; + void validateConfig(); +}; diff --git a/src/classes/config-handler/config-handler.cpp b/src/classes/config-handler/config-handler.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/config-handler/config-handler.hpp b/src/classes/config-handler/config-handler.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/daemon/daemon.cpp b/src/classes/daemon/daemon.cpp index e69de29..ee3ad42 100644 --- a/src/classes/daemon/daemon.cpp +++ b/src/classes/daemon/daemon.cpp @@ -0,0 +1,29 @@ +#include "frameworkd/classes/daemon/daemon.hpp" + +Daemon::Daemon(const std::string& name) + : m_configHandler("/etc/frameworkd/" + name + ".json") + , m_serviceHandler(m_configHandler.getAllConfig()) +{ + DBusHandler::start(m_configHandler["serviceId"]); +} + +void Daemon::deploy(StaticService& userService) +{ + m_serviceHandler.buildServiceProxy(userService); +} + +void Daemon::deploy(RoutineService& userService) +{ + m_serviceHandler.buildServiceProxy(userService); +} + +void Daemon::run() +{ + m_serviceHandler.run(); + DBusHandler::finish(); +} + +auto Daemon::getConfigHandler() -> ConfigHandler +{ + return m_configHandler; +} diff --git a/src/classes/daemon/daemon.hpp b/src/classes/daemon/daemon.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/dbus-controller/dbus-controller.cpp b/src/classes/dbus-controller/dbus-controller.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/dbus-controller/dbus-controller.hpp b/src/classes/dbus-controller/dbus-controller.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/dbus-handler/dbus-handler.cpp b/src/classes/dbus-handler/dbus-handler.cpp index e69de29..e032e74 100644 --- a/src/classes/dbus-handler/dbus-handler.cpp +++ b/src/classes/dbus-handler/dbus-handler.cpp @@ -0,0 +1,211 @@ +#include "frameworkd/classes/dbus-handler/dbus-handler.hpp" + +std::string DBusHandler::s_serviceName = ""; +bool DBusHandler::s_started = false; +bool DBusHandler::s_isServer = false; + +std::unique_ptr DBusHandler::s_connection; + +DBusHandler::DBusObjectMap DBusHandler::s_DBusObjects; +DBusHandler::DBusProxyMap DBusHandler::s_DBusProxys; + +void DBusHandler::start(const std::string& serviceName) { + s_isServer = true; + s_serviceName = "zfkd.dbus." + serviceName; + s_connection = sdbus::createSystemBusConnection(s_serviceName); +} + +void DBusHandler::start() { + s_isServer = false; +}; + +auto DBusHandler::findObject(const DBusHandler::Path& path) -> sdbus::IObject* +{ + try { + return s_DBusObjects.at(path.objectPath).get(); + } catch (std::out_of_range& e) { + s_DBusObjects[path.objectPath] = sdbus::createObject(*s_connection, path.objectPath); + return s_DBusObjects[path.objectPath].get(); + } +} + +auto DBusHandler::findProxy(const DBusHandler::Path& path) -> sdbus::IProxy* +{ + const std::string uniqueId = path.service + path.objectPath; + + try { + return s_DBusProxys.at(uniqueId).get(); + } catch (std::out_of_range& e) { + s_DBusProxys[uniqueId] = sdbus::createProxy(path.service, path.objectPath); + return s_DBusProxys[uniqueId].get(); + } +} + +void DBusHandler::registerMethod(const DBusHandler::Path& path, DBusCallback&& callback) +{ + if (!s_isServer) { + throw std::logic_error("Only servers can register methods"); + } + + if (s_started) { + throw std::logic_error("Methods should be register before finishing the handler"); + } + + sdbus::IObject* object = findObject(path); + + auto wrapper = [callback](sdbus::MethodCall call) { + std::vector bson; + call >> bson; + + nlohmann::json answer = callback(nlohmann::json::from_bson(bson)); + + auto reply = call.createReply(); + + reply << nlohmann::json::to_bson(answer); + + reply.send(); + }; + + object->registerMethod(path.interface, path.functionality, "ay", "ay", wrapper); +} + +void DBusHandler::subscribeToSignal(const DBusHandler::Path& path, DBusVoidCallback&& callback) +{ + sdbus::IProxy* proxy = findProxy(path); + + auto wrapper = [callback](sdbus::Signal& signal) { + std::vector bson; + signal >> bson; + + callback(nlohmann::json::from_bson(bson)); + }; + + proxy->registerSignalHandler(path.interface, path.functionality, wrapper); +} + +void DBusHandler::registerSignal(const DBusHandler::Path& path) +{ + if (!s_isServer) { + throw std::logic_error("Only servers can register signals"); + } + + if (s_started) { + throw std::logic_error("register signals is only possible before finishing the handler"); + } + + sdbus::IObject* object = findObject(path); + object->registerSignal(path.interface, path.functionality, "ay"); +} + +auto DBusHandler::callMethod(const DBusHandler::Path& path, nlohmann::json arg) -> nlohmann::json +{ + sdbus::IProxy* proxy = findProxy(path); + auto method = proxy->createMethodCall(path.interface, path.functionality); + + method << nlohmann::json::to_bson(arg); + + auto reply = proxy->callMethod(method); + + std::vector bson; + reply >> bson; + return nlohmann::json::from_bson(bson); +} + +void DBusHandler::callMethodAsync(const DBusHandler::Path& path, nlohmann::json arg, DBusVoidCallback&& callback) +{ + sdbus::IProxy* proxy = findProxy(path); + auto method = proxy->createMethodCall(path.interface, path.functionality); + + method << nlohmann::json::to_bson(arg); + + auto wrapper = [callback](sdbus::MethodReply& reply, const sdbus::Error* error) { + std::vector bson; + reply >> bson; + + callback(nlohmann::json::from_bson(bson)); + }; + + auto reply = proxy->callMethod(method, wrapper); +} + +void DBusHandler::emitSignal(const DBusHandler::Path& path, nlohmann::json arg) +{ + if (!s_isServer) { + throw std::logic_error("Only servers can emit signals"); + } + + if (!s_started) { + throw std::logic_error("emit a signal is only possible after finishing the handler"); + } + + sdbus::IObject* object = findObject(path); + + auto signal = object->createSignal(path.interface, path.functionality); + signal << nlohmann::json::to_bson(arg); + + object->emitSignal(signal); +} + +void DBusHandler::exposeProperty(const DBusHandler::Path& path, std::function&& getter, + DBusVoidCallback&& setter) +{ + + if (!s_isServer) { + throw std::logic_error("Only servers can expose properties"); + } + + if (s_started) { + throw std::logic_error("expose a property is only possible before finishing the handler"); + } + + sdbus::IObject* object = findObject(path); + + auto getterWrapper = [getter]() { return nlohmann::json::to_bson(getter()); }; + + auto setterWrapper = [setter](const std::vector& arg) { setter(nlohmann::json::from_bson(arg)); }; + + object->registerProperty(path.functionality) + .onInterface(path.interface) + .withGetter(getterWrapper) + .withSetter(setterWrapper); +} + +auto DBusHandler::getProperty(const DBusHandler::Path& path) -> nlohmann::json +{ + sdbus::IProxy* proxy = findProxy(path); + std::vector property = proxy->getProperty(path.functionality).onInterface(path.interface); + + return nlohmann::json::from_bson(property); +} + +void DBusHandler::getProperty(const DBusHandler::Path& path, DBusVoidCallback&& callback) +{ + sdbus::IProxy* proxy = findProxy(path); + std::vector property = proxy->getProperty(path.functionality).onInterface(path.interface); + + callback(nlohmann::json::from_bson(property)); +} + +void DBusHandler::setProperty(const DBusHandler::Path& path, nlohmann::json arg) +{ + sdbus::IProxy* proxy = findProxy(path); + proxy->setProperty(path.functionality).onInterface(path.interface).toValue(nlohmann::json::to_bson(arg)); +} + +void DBusHandler::finish() +{ + + s_started = true; + + for (auto const& object : s_DBusObjects) { + object.second->finishRegistration(); + } + + for (auto const& proxy : s_DBusProxys) { + proxy.second->finishRegistration(); + } + + if (s_isServer) { + s_connection->enterEventLoop(); + } +} diff --git a/src/classes/dbus-handler/dbus-handler.hpp b/src/classes/dbus-handler/dbus-handler.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/endpoint-handler/endpoint-handler.cpp b/src/classes/endpoint-handler/endpoint-handler.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/endpoint-handler/endpoint-handler.hpp b/src/classes/endpoint-handler/endpoint-handler.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/endpoint/endpoint.cpp b/src/classes/endpoint/endpoint.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/endpoint/endpoint.hpp b/src/classes/endpoint/endpoint.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/routine-handler/routine-handler.cpp b/src/classes/routine-handler/routine-handler.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/routine-handler/routine-handler.hpp b/src/classes/routine-handler/routine-handler.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/routine-service-proxy/routine-service-proxy.cpp b/src/classes/routine-service-proxy/routine-service-proxy.cpp new file mode 100644 index 0000000..5e3ca78 --- /dev/null +++ b/src/classes/routine-service-proxy/routine-service-proxy.cpp @@ -0,0 +1,83 @@ +#include "frameworkd/classes/routine-service-proxy/routine-service-proxy.hpp" + +RoutineServiceProxy::RoutineServiceProxy(RoutineService& realService, std::map depsMap) + : ServiceProxy { realService, ROUTINE_SERVICE, depsMap } +{ + changeState(ServiceState::MISSING_DEPENDENCIES); +} + +/** + * @brief: Exposes the routine-service from the DBUS and then + * calls m_upperProxy's method weave. + */ +void RoutineServiceProxy::MissingDependencies::allFine() +{ + m_upperProxy.weave(); +} + +/** + * @brief: Hides the routine-service from the DBUS and then calls + * the m_upperProxy's method cut. + */ +void RoutineServiceProxy::Running::somethingIsMissing() +{ + m_upperProxy.cut(); +} + +void RoutineServiceProxy::serviceCycle() +{ + changeState(ServiceState::RUNNING); + + while (checkState() != ServiceState::STOPPED) { + + m_realService.routine(); + } + + m_realService.destroy(); + + changeState(ServiceState::FINISHED); +} + +void RoutineServiceProxy::weave() +{ + const std::lock_guard lock { m_updateMtx }; + changeState(ServiceState::STAND_BY); + std::thread thread(&RoutineServiceProxy::serviceCycle, this); + std::swap(thread, m_thread); +} + +void RoutineServiceProxy::cut() +{ + const std::lock_guard lock { m_updateMtx }; + changeState(ServiceState::STOPPED); + m_thread.join(); + changeState(ServiceState::MISSING_DEPENDENCIES); +} + +void RoutineServiceProxy::changeState(ServiceState::state_t newState) +{ + const std::lock_guard lock { m_stateMtx }; + + switch (newState) { + case ServiceState::MISSING_DEPENDENCIES: + m_state = std::make_unique(*this); + break; + case ServiceState::RUNNING: + m_state = std::make_unique(*this); + break; + case ServiceState::STAND_BY: + m_state = std::make_unique(*this); + break; + case ServiceState::STOPPED: + m_state = std::make_unique(*this); + break; + case ServiceState::FINISHED: + m_state = std::make_unique(*this); + break; + default: + std::string errorMessage = "Unknown State! State Read = "; + errorMessage.append(std::to_string(newState)); + throw std::logic_error(errorMessage); + } +} + diff --git a/src/classes/routine/routine.cpp b/src/classes/routine/routine.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/routine/routine.hpp b/src/classes/routine/routine.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/service-handler/service-handler.cpp b/src/classes/service-handler/service-handler.cpp new file mode 100644 index 0000000..843f5a4 --- /dev/null +++ b/src/classes/service-handler/service-handler.cpp @@ -0,0 +1,91 @@ +#include "frameworkd/classes/service-handler/service-handler.hpp" + +ServiceHandler::ServiceHandler(nlohmann::json servicesConfigs) +{ + for (auto& [serviceId, dependencies] : servicesConfigs["proxys"].items()) { + std::map depsMap; + + for (auto& [dependencyId, currState] : dependencies.items()) { + depsMap.emplace(std::pair(dependencyId, currState)); + } + m_proxyDepsMap.emplace(std::pair>(serviceId, depsMap)); + } +} + +void ServiceHandler::buildServiceProxy(StaticService& userService) +{ + m_serviceMap.emplace(std::make_pair( + std::string(userService.m_serviceId), + std::make_unique(userService, m_proxyDepsMap.at(userService.m_serviceId)))); +} + +void ServiceHandler::buildServiceProxy(RoutineService& userService) +{ + m_serviceMap.emplace(std::make_pair( + std::string(userService.m_serviceId), + std::make_unique(userService, m_proxyDepsMap.at(userService.m_serviceId)))); +} + +auto ServiceHandler::getProxyState(std::string serviceId) -> nlohmann::json +{ + try { + return m_serviceMap.at(serviceId)->reportState(); + } catch (const std::out_of_range) { + std::string errorMessage = "Get dependency state request from a not built service! Unknown Service = " + serviceId; + + throw std::invalid_argument(errorMessage); + } +} + +auto ServiceHandler::getAllProxyState() -> nlohmann::json +{ + nlohmann::json allStates; + + for (auto& [serviceId, proxy] : m_serviceMap) { + allStates[serviceId] = proxy->reportState(); + } + + return allStates; +} + +void ServiceHandler::changeDependencyState(std::string serviceId, std::string dependencyId, ServiceProxy::ServiceState::state_t newState) +{ + if (dependencyId == "THIS") { + throw std::invalid_argument("THIS is a specially reserved dependency and don't accept outside changes of its state"); + } + + try { + m_serviceMap.at(serviceId)->m_proxyConfigs.changeDep(dependencyId, newState); + } catch (const std::out_of_range) { + std::string errorMessage = "Change dependency state request from a not built service! Unknown Service = " + serviceId; + + throw std::invalid_argument(errorMessage); + } +} + +void ServiceHandler::updateServiceProxy(std::string serviceId) +{ + try { + m_serviceMap.at(serviceId)->autoUpdate(); + } catch (const std::out_of_range) { + std::string errorMessage = "Update request from a not built service! Unknown Service = " + serviceId; + + throw std::invalid_argument(errorMessage); + } +} + +void ServiceHandler::updateAllServiceProxys() +{ + for (auto& [serviceId, proxy] : m_serviceMap) { + proxy->autoUpdate(); + } +} + +void ServiceHandler::run() +{ + for (auto& [serviceId, proxy] : m_serviceMap) { + proxy->m_proxyConfigs.changeDep("THIS", ServiceProxy::ServiceState::RUNNING); + proxy->configure(); + proxy->autoUpdate(); + } +} diff --git a/src/classes/service-proxy/service-proxy.cpp b/src/classes/service-proxy/service-proxy.cpp new file mode 100644 index 0000000..6f69831 --- /dev/null +++ b/src/classes/service-proxy/service-proxy.cpp @@ -0,0 +1,57 @@ +#include "frameworkd/classes/service-proxy/service-proxy.hpp" + +ServiceProxy::ProxyConfigs::ProxyConfigs(std::map depsMap) +{ + for (auto& [depId, depReqState] : depsMap) { + m_depsMap.emplace(std::piecewise_construct, + std::forward_as_tuple(depId), + std::forward_as_tuple(depReqState, ServiceState::UNKNOWN)); + } + + m_depsMap.emplace(std::piecewise_construct, + std::forward_as_tuple("THIS"), + std::forward_as_tuple(ServiceState::RUNNING, ServiceState::UNKNOWN)); +} + +auto ServiceProxy::ServiceState::getState() const -> state_t +{ + return m_state; +} + +void ServiceProxy::ServiceProxy::autoUpdate() +{ + bool noMissingDependencies = true; + + for (auto& [depId, dependency] : m_proxyConfigs.m_depsMap) { + if (dependency.m_currState != dependency.m_reqrState) { + noMissingDependencies = false; + } + } + + if (noMissingDependencies) { + m_state->allFine(); + } else { + m_state->somethingIsMissing(); + } +} + +auto ServiceProxy::ServiceProxy::checkState() -> ServiceState::state_t +{ + const std::lock_guard lock { m_stateMtx }; + return m_state->getState(); +} + +auto ServiceProxy::ServiceProxy::reportState() -> nlohmann::json +{ + return (nlohmann::json) { { "serviceId", m_realServiceId }, { "State", checkState() } }; +} + +void ServiceProxy::ProxyConfigs::changeDep(std::string dependencieId, ServiceState::state_t currState) +{ + m_depsMap.at(dependencieId).m_currState = currState; +} + +void ServiceProxy::configure() +{ + m_realService.setup(); +} diff --git a/src/classes/service/service.cpp b/src/classes/service/service.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/service/service.hpp b/src/classes/service/service.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/static-service-proxy/static-service-proxy.cpp b/src/classes/static-service-proxy/static-service-proxy.cpp new file mode 100644 index 0000000..4a207ce --- /dev/null +++ b/src/classes/static-service-proxy/static-service-proxy.cpp @@ -0,0 +1,73 @@ +#include "frameworkd/classes/static-service-proxy/static-service-proxy.hpp" + +StaticServiceProxy::StaticServiceProxy(StaticService& realService, std::map depsMap) + : m_runnedOnce { false } + , ServiceProxy { realService, STATIC_SERVICE, depsMap } +{ + changeState(ServiceState::MISSING_DEPENDENCIES); +} + +/* + * @brief: Exposes the static-service on the DBUS and then + * changes m_upperProxy's StaticState to state + * StandBy if the static-service already had run + * once, or to state Uninitialized if its doesn't. + */ +void StaticServiceProxy::MissingDependencies::allFine() +{ + state_t newState = (m_upperProxy.m_runnedOnce) ? STAND_BY : UNINITIALIZED; + + m_upperProxy.changeState(newState); +} + +/* + * @brief: Hides the static-service from the DBUS and then + * changes m_upperProxy's StaticState to state + * MissingDependencies. + */ +void StaticServiceProxy::Uninitialized::somethingIsMissing() +{ + m_upperProxy.changeState(ServiceState::MISSING_DEPENDENCIES); +} + +/* + * @brief: Hides the static-service from the DBUS and then + * changes m_upperProxy's StaticState to state + * MissingDependencies. + */ +void StaticServiceProxy::StandBy::somethingIsMissing() +{ + m_upperProxy.changeState(ServiceState::MISSING_DEPENDENCIES); +} + +void StaticServiceProxy::StaticServiceProxy::serviceCycle() +{ + changeState(ServiceState::RUNNING); + m_realService.destroy(); + changeState(ServiceState::STAND_BY); +} + +void StaticServiceProxy::StaticServiceProxy::changeState(ServiceState::state_t newState) +{ + const std::lock_guard lock { m_stateMtx }; + + switch (newState) { + case ServiceState::MISSING_DEPENDENCIES: + m_state = std::make_unique(*this); + break; + case ServiceState::UNINITIALIZED: + m_state = std::make_unique(*this); + break; + case ServiceState::RUNNING: + m_state = std::make_unique(*this); + break; + case ServiceState::STAND_BY: + m_state = std::make_unique(*this); + break; + default: + std::string errorMessage = "Unknown State! State Read = "; + errorMessage.append(std::to_string(newState)); + throw std::logic_error(errorMessage); + } +} + diff --git a/src/utilities/config-handler/config-handler.cpp b/src/utilities/config-handler/config-handler.cpp new file mode 100644 index 0000000..7c580db --- /dev/null +++ b/src/utilities/config-handler/config-handler.cpp @@ -0,0 +1,71 @@ +#include "frameworkd/utilities/config-handler/config-handler.hpp" +#include + +nlohmann::json ConfigHandler::m_config; + +ConfigHandler::ConfigHandler(const std::string& fileName) + : m_fileName(fileName) +{ + read(); +} + +/** + * @brief Validate the json + */ +void ConfigHandler::validateConfig() +{ + for (const auto& it : m_requiredFields) { + if (!ConfigHandler::m_config.contains(it)) { + throw std::runtime_error("Config file doesn't have all the required fields"); + } + } +} + +/** + * @brief Reads a file and turn them into a json object + */ +void ConfigHandler::read() +{ + std::ifstream file(m_fileName); + if (!file.is_open()) { + throw std::invalid_argument("Configuration file '"+ m_fileName + "' not found"); + } + + file >> ConfigHandler::m_config; + + validateConfig(); + + file.close(); +} + +/** + * @brief Reads a file and turn them into a json object + * @param fileName Name of the file to read + */ +void ConfigHandler::read(const std::string& fileName) +{ + m_fileName = fileName; + read(); +} + +/** + * @brief Gets a value from the json object + * @param field Field to get the value + */ +auto ConfigHandler::getConfig(const std::string& field) const -> const nlohmann::json +{ + return ConfigHandler::m_config[field]; +} + +/** + * @brief Operator overload that encapsulates the getConfig() + */ +auto ConfigHandler::operator[](const std::string& field) const -> const nlohmann::json +{ + return getConfig(field); +} + +auto ConfigHandler::getAllConfig() const -> const nlohmann::json +{ + return m_config; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..516aa59 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.15) + +# +# Project details +# + +project( + ${CMAKE_PROJECT_NAME}Tests + LANGUAGES CXX +) + +verbose_message("Adding tests under ${CMAKE_PROJECT_NAME}Tests...") + +foreach(file ${test_sources}) + string(REGEX REPLACE "(.*/)([a-zA-Z0-9_ ]+)(\.cpp)" "\\2" test_name ${file}) + add_executable(${test_name}_Tests ${file}) + + # + # Set the compiler standard + # + + target_compile_features(${test_name}_Tests PUBLIC cxx_std_17) + + # + # Setup code coverage if enabled + # + + if (${CMAKE_PROJECT_NAME}_ENABLE_CODE_COVERAGE) + target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC -O0 -g -fprofile-arcs -ftest-coverage) + target_link_options(${CMAKE_PROJECT_NAME} PUBLIC -fprofile-arcs -ftest-coverage) + verbose_message("Code coverage is enabled and provided with GCC.") + endif() + + # + # Load the desired unit testing framework + # + # Currently supported: GoogleTest (and GoogleMock). + + if(${CMAKE_PROJECT_NAME}_BUILD_EXECUTABLE) + set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}_LIB) + else() + set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}) + endif() + + if(${CMAKE_PROJECT_NAME}_USE_GTEST) + find_package(GTest REQUIRED) + + if(${CMAKE_PROJECT_NAME}_USE_GOOGLE_MOCK) + set(GOOGLE_MOCK_LIBRARIES GTest::gmock GTest::gmock_main) + endif() + + target_link_libraries( + ${test_name}_Tests + PUBLIC + GTest::GTest + GTest::Main + ${GOOGLE_MOCK_LIBRARIES} + ${${CMAKE_PROJECT_NAME}_TEST_LIB} + ) + else() + message(FATAL_ERROR "Unknown testing library. Please setup your desired unit testing library by using `target_link_libraries`.") + endif() + + # + # Add the unit tests + # + + add_test( + NAME + ${test_name} + COMMAND + ${test_name}_Tests + ) +endforeach() + +verbose_message("Finished adding unit tests for ${CMAKE_PROJECT_NAME}.") diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/XML-configurator/XML-configurator.cpp b/tools/XML-configurator/XML-configurator.cpp similarity index 100% rename from src/classes/XML-configurator/XML-configurator.cpp rename to tools/XML-configurator/XML-configurator.cpp diff --git a/src/classes/XML-configurator/XML-configurator.hpp b/tools/XML-configurator/XML-configurator.hpp similarity index 100% rename from src/classes/XML-configurator/XML-configurator.hpp rename to tools/XML-configurator/XML-configurator.hpp