diff --git a/.gitignore b/.gitignore index 4a0b530..d0b2fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ CMakeLists.txt.user* *.dll *.exe +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index e64a794..6b80ace 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(ADC LANGUAGES CXX) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}") -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) # set(CMAKE_BUILD_TYPE Release) @@ -28,11 +28,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang and GCC.") # endif() - - set(ADC_COMMON_HEADERS common/adc_traits.h common/adc_utils.h + common/adc_serialization.h # common/adc_value_holder.h # common/adc_value.h # common/adc_valholder.h @@ -40,12 +39,11 @@ set(ADC_COMMON_HEADERS set(ADC_DEVICE_HEADERS device/adc_device_attribute.h - device/adc_device_command.h + device/adc_device_command.h device/adc_device.h device/adc_device_concepts.h ) - set(ADC_NETWORK_HEADERS # net/adc_netmsg.h # net/adc_netmessage.h @@ -60,76 +58,82 @@ set(ADC_NETWORK_HEADERS net/adc_device_netclient.h ) - option(ASIO_LIBRARY "Use of ASIO library for networking implementation" ON) -if (ASIO_LIBRARY) +if(ASIO_LIBRARY) find_package(ASIO REQUIRED) - set(ADC_NETWORK_HEADERS ${ADC_NETWORK_HEADERS} + set(ADC_NETWORK_HEADERS + ${ADC_NETWORK_HEADERS} net/asio/adc_netservice_asio.h net/asio/adc_device_netserver_asio.h ) add_compile_definitions(PUBLIC USE_ASIO_LIBRARY) - option(OPENSSL_LIBRARY "Use openssl library for related ASIO-based implementation" ON) - if (OPENSSL_LIBRARY) + option( + OPENSSL_LIBRARY + "Use openssl library for related ASIO-based implementation" + ON + ) + if(OPENSSL_LIBRARY) find_package(OpenSSL REQUIRED) add_compile_definitions(PUBLIC USE_OPENSSL_WITH_ASIO) endif() endif() - - option(SPDLOG_LIBRARY "Use of SPDLOG library for logging" ON) -if (SPDLOG_LIBRARY) +if(SPDLOG_LIBRARY) find_package(spdlog REQUIRED) find_package(fmt REQUIRED) - set(ADC_COMMON_HEADERS ${ADC_COMMON_HEADERS} - common/adc_spdlog.h - ) + set(ADC_COMMON_HEADERS ${ADC_COMMON_HEADERS} common/adc_spdlog.h) - set(ADC_NETWORK_HEADERS ${ADC_NETWORK_HEADERS} - net/adc_netserver_spdlog.h - ) + set(ADC_NETWORK_HEADERS ${ADC_NETWORK_HEADERS} net/adc_netserver_spdlog.h) add_compile_definitions(PUBLIC USE_SPDLOG_LIBRARY) endif() +option( + USE_UWEBSOCKET + "Use of uWebsocket library for websocket-related staff" + ON +) - -option(USE_UWEBSOCKET "Use of uWebsocket library for websocket-related staff" ON) - -if (USE_UWEBSOCKET) +if(USE_UWEBSOCKET) include(FetchContent) include(ExternalProject) - FetchContent_Declare(uwebsockets - SOURCE_DIR ${CMAKE_BINARY_DIR}/uWebsockets - BINARY_DIR ${CMAKE_BINARY_DIR} + FetchContent_Declare( + uwebsockets + SOURCE_DIR + ${CMAKE_BINARY_DIR}/uWebsockets + BINARY_DIR + ${CMAKE_BINARY_DIR} GIT_REPOSITORY "https://github.com/uNetworking/uWebSockets.git" GIT_TAG "v20.67.0" GIT_SHALLOW TRUE GIT_SUBMODULES "" GIT_PROGRESS TRUE - ) + ) FetchContent_MakeAvailable(uwebsockets) FetchContent_GetProperties(uwebsockets SOURCE_DIR uwebsockets_SOURCE_DIR) set(UWEBSOCKETS_INCLUDE_DIR ${uwebsockets_SOURCE_DIR}/src) - FetchContent_Declare(usockets - SOURCE_DIR ${uwebsockets_SOURCE_DIR}/uSockets - BINARY_DIR ${CMAKE_BINARY_DIR} + FetchContent_Declare( + usockets + SOURCE_DIR + ${uwebsockets_SOURCE_DIR}/uSockets + BINARY_DIR + ${CMAKE_BINARY_DIR} GIT_REPOSITORY "https://github.com/uNetworking/uSockets.git" GIT_TAG "v0.8.8" GIT_SHALLOW TRUE GIT_SUBMODULES "" GIT_PROGRESS TRUE - ) + ) FetchContent_MakeAvailable(usockets) FetchContent_GetProperties(usockets SOURCE_DIR usockets_SOURCE_DIR) @@ -137,7 +141,8 @@ if (USE_UWEBSOCKET) include(ExternalProject) - ExternalProject_Add(LIBUS + ExternalProject_Add( + LIBUS SOURCE_DIR ${usockets_SOURCE_DIR} PREFIX ${CMAKE_BINARY_DIR}/_deps BUILD_IN_SOURCE TRUE @@ -148,17 +153,19 @@ if (USE_UWEBSOCKET) ) add_library(LIBUS_LIB STATIC IMPORTED) - set_target_properties(LIBUS_LIB PROPERTIES IMPORTED_LOCATION ${usockets_SOURCE_DIR}/uSockets.a) + set_target_properties( + LIBUS_LIB + PROPERTIES IMPORTED_LOCATION ${usockets_SOURCE_DIR}/uSockets.a + ) get_target_property(LIBUS_LIB_PATH LIBUS_LIB IMPORTED_LOCATION) include_directories(${USOCKETS_INCLUDE_DIR}) include_directories(${UWEBSOCKETS_INCLUDE_DIR}) - endif(USE_UWEBSOCKET) option(BUILD_TESTS "Build tests" ON) -if (BUILD_TESTS) +if(BUILD_TESTS) find_package(doctest) # set(VALUEHOLDER_TEST_APP adc_valueholder_test) @@ -175,21 +182,34 @@ if (BUILD_TESTS) set(NETSERVICE_TEST_APP adc_netservice_test) add_executable(${NETSERVICE_TEST_APP} tests/adc_netservice_test.cpp) - if (OPENSSL_LIBRARY) - target_link_libraries(${NETSERVICE_TEST_APP} OpenSSL::SSL OpenSSL::Crypto) + if(OPENSSL_LIBRARY) + target_link_libraries( + ${NETSERVICE_TEST_APP} + OpenSSL::SSL + OpenSSL::Crypto + ) endif() - if (ASIO_LIBRARY) + if(ASIO_LIBRARY) find_package(cxxopts CONFIG) set(ASIO_NETSERVER_TEST_APP adc_asio_netserver_test) - add_executable(${ASIO_NETSERVER_TEST_APP} tests/adc_asio_netserver_test.cpp) - target_link_libraries(${ASIO_NETSERVER_TEST_APP} PUBLIC Threads::Threads cxxopts::cxxopts) - if (OPENSSL_LIBRARY) - target_link_libraries(${ASIO_NETSERVER_TEST_APP} PUBLIC OpenSSL::SSL OpenSSL::Crypto) + add_executable( + ${ASIO_NETSERVER_TEST_APP} + tests/adc_asio_netserver_test.cpp + ) + target_link_libraries( + ${ASIO_NETSERVER_TEST_APP} + PUBLIC Threads::Threads cxxopts::cxxopts + ) + if(OPENSSL_LIBRARY) + target_link_libraries( + ${ASIO_NETSERVER_TEST_APP} + PUBLIC OpenSSL::SSL OpenSSL::Crypto + ) endif() endif() - if (NOT doctest_FOUND) + if(NOT doctest_FOUND) include(FetchContent) FetchContent_Declare( doctest @@ -207,7 +227,7 @@ if (BUILD_TESTS) add_test(NETMSG_TEST ${NETMSG_TEST_APP}) add_test(ASIO_NETSRV_TEST ${ASIO_NETSERVER_TEST_APP}) - if (SPDLOG_LIBRARY) + if(SPDLOG_LIBRARY) target_link_libraries(${ASIO_NETSERVER_TEST_APP} PRIVATE fmt::fmt) endif() @@ -216,7 +236,13 @@ endif(BUILD_TESTS) include(GNUInstallDirs) -add_library(${PROJECT_NAME} INTERFACE ${ADC_COMMON_HEADERS} ${ADC_DEVICE_HEADERS} ${ADC_NETWORK_HEADERS}) +add_library( + ${PROJECT_NAME} + INTERFACE + ${ADC_COMMON_HEADERS} + ${ADC_DEVICE_HEADERS} + ${ADC_NETWORK_HEADERS} +) target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_20) # target_link_libraries(${PROJECT_NAME} INTERFACE ASIO::ASIO) target_include_directories( @@ -225,4 +251,3 @@ target_include_directories( $ $ ) - diff --git a/common/adc_serialization.h b/common/adc_serialization.h new file mode 100644 index 0000000..c611460 --- /dev/null +++ b/common/adc_serialization.h @@ -0,0 +1,518 @@ +#pragma once + + +/* + +ABSTRACT DEVICE COMPONENTS LIBRARY + + */ + + +#include +#include +#include + +#include "adc_traits.h" +#include "adc_utils.h" + +namespace adc +{ + +/* SERIALIZATION/DESERIALIZATION PROCESS ERROR */ + +template +concept adc_serialization_error_c = std::default_initializable && (std::convertible_to || requires { + (bool)T() == false; // default constucted value must be a "non-error"! + }); + +// default +typedef std::error_code adc_serialization_error_t; + +/* SERIALIZATION/DESERIALIZATION PROCESS TUNING PARAMETERS */ + + +// delimiter between items of serializing values sequence +static constexpr std::string_view adc_SERIALIZING_DEFAULT_SEQ_DELIMITER{";"}; + +// delimiter between items of aggregative (multi-element) serializing value +static constexpr std::string_view adc_SERIALIZING_DEFAULT_ELEM_DELIMITER{","}; + + +template +concept adc_serialization_params_c = std::copyable && requires(T t) { + // delimiter between items of serializing values sequence + requires traits::adc_output_char_range; + + // delimiter between items of aggregative (multi-element) serializing value + requires traits::adc_output_char_range; + + // a format string for std::chrono::time_point types + requires traits::adc_output_char_range; +}; + + +// default serializatio/deserialization process parameters type +struct adc_serialization_params_t { + std::string elem_delim{adc_SERIALIZING_DEFAULT_ELEM_DELIMITER}; + std::string seq_delim{adc_SERIALIZING_DEFAULT_SEQ_DELIMITER}; + std::string timepoint_format{"{:%FT%T}"}; +}; + + + +/* SERIALIZER/DESERIALIZER CONCEPTS */ + +template +struct adc_serializer_interface_t { + virtual ~adc_serializer_interface_t() = default; + + typedef RetT error_t; + + template SelfT, traits::adc_output_char_range R, typename ValueT> + RetT operator()(this SelfT&& self, R& output, ValueT const& value) + { + return std::forward(self)(output, value); + } + + template SelfT, traits::adc_output_char_range R, typename ValueT> + RetT operator()(this SelfT&& self, R& output, ValueT const& value, adc_serialization_params_c auto const& params) + { + return std::forward(self)(output, value, params); + } + +protected: + adc_serializer_interface_t() = default; +}; + + +template +concept adc_serializer_c = + std::derived_from> && requires(T t, const T t_const) { + // static const variable with name of the serializer + requires std::formattable && std::is_const_v; + }; + + + +template +struct adc_deserializer_interface_t { + virtual ~adc_deserializer_interface_t() = default; + + typedef RetT error_t; + + template SelfT, traits::adc_input_char_range R, typename ValueT> + RetT operator()(this SelfT&& self, R const& input, ValueT& value) + { + return std::forward(self)(input, value); + } + + template SelfT, traits::adc_input_char_range R, typename ValueT> + RetT operator()(this SelfT&& self, R const& input, ValueT& value, adc_serialization_params_c auto& params) + { + return std::forward(self)(input, value, params); + } + +protected: + adc_deserializer_interface_t() = default; +}; + + +template +concept adc_deserializer_c = + std::derived_from> && requires(T t, const T t_const) { + // static const variable with name of the deserializer + requires std::formattable && + std::is_const_v; + }; + +} // namespace adc + + + +/* BASIC IMPLEMENTATION OF SERIALIZER AND DESERIALIZER CLASSES */ + +namespace adc +{ + +enum class AdcSerializerErrorCode : int { ERROR_OK, ERROR_UNDERLYING_SERIALIZER }; + +enum class AdcDeserializerErrorCode : int { ERROR_OK, ERROR_UNDERLYING_DESERIALIZER, ERROR_INVALID_SERIALIZED_VALUE }; + +} // namespace adc + + +namespace std +{ + +template <> +class is_error_code_enum : public true_type +{ +}; + +template <> +class is_error_code_enum : public true_type +{ +}; + +} // namespace std + + +namespace adc +{ + +// error category +struct AdcSerializerCategory : public std::error_category { + AdcSerializerCategory() : std::error_category() {} + + const char* name() const noexcept + { + return "Adc-SERIALIZER-ERR-CATEGORY"; + } + + std::string message(int ec) const + { + AdcSerializerErrorCode err = static_cast(ec); + + switch (err) { + case AdcSerializerErrorCode::ERROR_OK: + return "OK"; + case AdcSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER: + return "error returned by underlying serializer"; + default: + return "UNKNOWN"; + } + } + + static const AdcSerializerCategory& get() + { + static const AdcSerializerCategory constInst; + return constInst; + } +}; + + +// error category +struct AdcDeserializerCategory : public std::error_category { + AdcDeserializerCategory() : std::error_category() {} + + const char* name() const noexcept + { + return "Adc-DESERIALIZER-ERR-CATEGORY"; + } + + std::string message(int ec) const + { + AdcDeserializerErrorCode err = static_cast(ec); + + switch (err) { + case AdcDeserializerErrorCode::ERROR_OK: + return "OK"; + case AdcDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER: + return "error returned by underlying deserializer"; + case AdcDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE: + return "invalid serialized value"; + default: + return "UNKNOWN"; + } + } + + static const AdcDeserializerCategory& get() + { + static const AdcDeserializerCategory constInst; + return constInst; + } +}; + + + +/* BASE SERIALIZER CLASS (FOR IMPLEMENTATIONS BELOW) */ + +struct AdcSerializerBase : adc_serializer_interface_t { + virtual ~AdcSerializerBase() = default; + + using typename adc_serializer_interface_t::error_t; + +protected: + AdcSerializerBase() = default; + + static void addElemDelimiter(traits::adc_output_char_range auto& output, + adc_serialization_params_c auto const& params) + { + std::format_to(std::back_inserter(output), "{}", params.elem_delim); + } + + + static void addSeqDelimiter(traits::adc_output_char_range auto& output, + adc_serialization_params_c auto const& params) + { + std::format_to(std::back_inserter(output), "{}", params.seq_delim); + } + + + template + requires(std::ranges::input_range && std::same_as>) + static error_t serializeRange(adc_serializer_c auto& sr, + R const& r, + traits::adc_output_char_range auto& output, + adc_serialization_params_c auto const& params) + { + size_t i = 0, N = std::ranges::size(r); + + for (auto const& el : r) { + auto err = sr(output, el, params); + if (err) { + return adc_deduced_err(err, AdcSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER); + } + if (++i < N) { + // AdcSerializerBase::addSeqDelimiter(output, params); + AdcSerializerBase::addElemDelimiter(output, params); + } + } + + return AdcSerializerErrorCode::ERROR_OK; + } +}; + + +/* BASE DESERIALIZER CLASS (FOR IMPLEMENTATIONS BELOW) */ + +struct AdcDeserializerBase : adc_deserializer_interface_t { + using typename adc_deserializer_interface_t::error_t; + + virtual ~AdcDeserializerBase() = default; + + +protected: + AdcDeserializerBase() = default; + + // + // empty == true, if the 'input' is empty or if all elements consist of only spaces + // + static std::vector splitValueIntoElements(traits::adc_input_char_range auto const& input, + adc_serialization_params_c auto const& params, + bool& empty) + { + static_assert(std::ranges::contiguous_range, "NOT IMPLEMENTED FOR NON-CONTIGUIUS RANGES!!!"); + + std::vector res; + + if (std::ranges::size(input)) { + empty = true; + + std::ranges::for_each(std::views::split(input, params.elem_delim), [&res, &empty](auto const& el) { + std::back_inserter(res) = utils::AdcTrimSpacesView( + std::string_view{el.begin(), el.end()}); + if (empty && res.back().size()) { + empty = false; + } + }); + } else { + empty = true; + } + + return res; + } + + + template R, typename... DeserParamsT> + static error_t deserializingRange(adc_deserializer_c auto& dsr, + traits::adc_input_char_range auto const& input, + R& r, + adc_serialization_params_c auto const& params) + { + if (std::ranges::size(input) == 0) { // ignore an empty input, just return empty range?!! + r = R{}; + return AdcDeserializerErrorCode::ERROR_OK; + } + + // auto r_str = std::views::split(input, params.seq_delim); + auto r_str = std::views::split(input, params.elem_delim); + VT val; + + auto it = r.begin(); + for (auto const& el : r_str) { + // auto err = dsr(el, val, std::forward(params)...); + auto err = dsr(el, val, params); + if (err) { + return adc_deduced_err(err, AdcDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER); + } + + if (it == r.end()) { + if constexpr (!traits::adc_fixed_size_range) { + std::back_inserter(r) = val; + it = r.end(); + } + } else { + *it = val; + ++it; + } + } + + return AdcDeserializerErrorCode::ERROR_OK; + } +}; + + +/* MAIN (FALLBACK) TEMPLATED IMPLEMENTATION OF SERIALIZER/DESERIALIZER */ + +template +struct AdcSerializer : AdcSerializerBase { + constexpr static std::string_view serializerName{"ADC-FALLBACK-SERIALIZER"}; + + template + error_t operator()(traits::adc_output_char_range auto& output, + VT const& value, + ParamsT const& params = adc_serialization_params_t{}) + { + if constexpr (std::convertible_to) { + std::string s = value; + std::ranges::copy(s, std::back_inserter(output)); + } else if constexpr (std::ranges::range) { + using value_t = std::remove_cv_t>; + + // special range (character sequence) + if constexpr (std::same_as) { + std::ranges::copy(value, std::back_inserter(output)); + } else { + AdcSerializer sr; + + return AdcSerializerBase::serializeRange(sr, value, output, params); + } + } else if constexpr (traits::adc_tuple_like) { + return [&output, ¶ms](this auto& self, VT& tp) -> error_t { + if constexpr (I < (std::tuple_size_v - 1)) { + auto err = AdcSerializer>{}(output, std::get(tp), params); + if (err) { + return AdcSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER; + } + + AdcSerializerBase::addElemDelimiter(output, params); + + return self.template operator()(tp); + } else if constexpr (I < (std::tuple_size_v - 1)) { // the last element + auto err = AdcSerializer>{}(output, std::get(tp), params); + if (err) { + return AdcSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER; + } + + return AdcSerializerErrorCode::ERROR_OK; + } + }(value); + } else if constexpr (traits::adc_time_point_c) { + std::string_view fmt{params.timepoint_format}; + std::vformat_to(std::back_inserter(output), fmt, std::make_format_args(value)); + } else if constexpr (traits::adc_time_duration_c) { + std::format_to(std::back_inserter(output), "{}", value.count()); + } else if constexpr (std::formattable) { + std::format_to(std::back_inserter(output), "{}", value); + } else { + static_assert(false, "UNSUPPORTED TYPE!!!"); + } + + return AdcSerializerErrorCode::ERROR_OK; + } +}; + + +template +struct AdcDeserializer : AdcDeserializerBase { + static constexpr std::string_view deserializerName{"ADC-FALLBACK-DESERIALIZER"}; + + virtual ~AdcDeserializer() = default; + + + template + error_t operator()(traits::adc_input_char_range auto const& input, + VT& value, + ParamsT const& params = adc_serialization_params_t{}) + { + if constexpr (std::is_arithmetic_v) { + auto v = adc::utils::AdcFromChars(utils::AdcTrimSpaces(input)); + if (!v.has_value()) { + return AdcDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE; + } + + value = v.value(); + } else if constexpr (adc::traits::adc_output_char_range) { + VT r; + if constexpr (traits::adc_fixed_size_range) { + size_t N = + std::ranges::size(r) <= std::ranges::size(input) ? std::ranges::size(r) : std::ranges::size(input); + + for (size_t i = 0; i < N; ++i) { + r[i] = input[i]; + } + if (std::ranges::size(r) > N) { + for (size_t i = N; i < std::ranges::size(r); ++i) { + r[i] = '\0'; + } + } + } else { + std::ranges::copy(input, std::back_inserter(r)); + } + + value = r; + } else if constexpr (std::ranges::range) { + using el_t = std::ranges::range_value_t; + + static_assert(std::ranges::output_range, "INVALID RANGE TYPE!!!"); + + // no reference or constants allowed + static_assert(!(std::is_reference_v || std::is_const_v), "INVALID RANGE ELEMENT TYPE!!!"); + + AdcDeserializer dsr; + return deserializingRange(dsr, input, value, params); + } else if constexpr (traits::adc_tuple_like) { + bool empty; + auto elems = splitValueIntoElements(input, params, &empty); + if (empty) { + value = VT{}; + } else { + return [&input, ¶ms, &elems](this auto& self, VT& tp) { + if constexpr (I < std::tuple_size_v) { + if (I < elems.size()) { + auto err = + AdcDeserializer>{}(elems[I], std::get(tp), params); + if (err) { + return AdcDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER; + } + + return self.template operator()(tp); + } else { + return AdcDeserializerErrorCode::ERROR_OK; + } + } + + return AdcDeserializerErrorCode::ERROR_OK; + }(value); + } + + } else if constexpr (traits::adc_time_point_c) { + std::istringstream ist{utils::AdcTrimSpaces(input)}; + std::chrono::from_stream(ist, params.timepoint_format.c_str(), value); + if (ist.fail()) { + return AdcDeserializerErrorCode::ERROR_INVALID_SERIALIZED_VALUE; + } + } else if constexpr (traits::adc_time_duration_c) { + typename VT::rep vd; + + AdcDeserializer dsr; + + auto err = dsr(utils::AdcTrimSpaces(input), vd, params); + if (err) { + return adc_deduced_err(err, AdcDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER); + } + + value = VT{vd}; + } else { + static_assert(false, "UNSUPPORTED VALUE TYPE!!!"); + } + + + return AdcDeserializerErrorCode::ERROR_OK; + } +}; + + + +} // namespace adc diff --git a/common/adc_traits.h b/common/adc_traits.h index 51814a8..90517ed 100644 --- a/common/adc_traits.h +++ b/common/adc_traits.h @@ -86,6 +86,17 @@ struct adc_char_identity { } }; + +// std::array, std::span(v_t, N), std::tuple +template +concept adc_fixed_size_range = std::ranges::range && requires { std::tuple_size>::value; }; + +// non-resizable ranges +template +concept adc_non_resizable_range = std::ranges::range && !requires(R r, std::size_t n) { r.resize(n); }; + + + // deduce returned type of callable // template // using adc_retval_t = std::invoke_result_t>; @@ -270,6 +281,13 @@ using adc_common_duration_t = adc_duration_common_type_t; + +template +concept adc_time_point_c = requires { + [](std::type_identity>) { + }(std::type_identity>()); +}; + // concept for hashable types template concept adc_hashable_c = requires(T t) { diff --git a/common/adc_utils.h b/common/adc_utils.h index 046a214..dbb2447 100644 --- a/common/adc_utils.h +++ b/common/adc_utils.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/tart b/tart new file mode 100644 index 0000000..11b6206 --- /dev/null +++ b/tart @@ -0,0 +1,464 @@ +#pragma once + +/* + +ABSTRACT DEVICE COMPONENTS LIBRARY + +*/ + +#include  +#include  +#include  +#include  +#include  + +#if __has_include() // POSIX +#define FORK_EXISTS 1 +#include  +#include  +#include  +#endif + + +#include "../common/adc_utils.h" +#include "adc_net_concepts.h" + + +namespace adc +{ + + +/* SOME USEFULL PRIVITIVES */ + + +// A generic implementation of POSIX OS daemon +class AdcPosixGenericDaemon +{ +public: + virtual ~AdcPosixGenericDaemon() = default; + + // run server as daemon (still only on POSIX OSes) + void daemonize() + { + daemonizePrepare(); + +// reference implementation of forking for POSIX OSes +#ifdef FORK_EXISTS + // get TEMP directory in OS + + auto tmp_path = std::filesystem::temp_directory_path(); + if (tmp_path.empty()) { + tmp_path = std::filesystem::current_path().root_path(); + } + + if (pid_t pid = fork()) { + if (pid > 0) { + exit(0); + } else { + throw std::system_error(errno, std::generic_category(), "CANNOT FORK 1-STAGE"); + } + } + + if (setsid() == -1) { + throw std::system_error(errno, std::generic_category(), "CANNOT FORK SETSID"); + } + + std::filesystem::current_path(tmp_path); + + umask(0); + + if (pid_t pid = fork()) { + if (pid > 0) { + exit(0); + } else { + throw std::system_error(errno, std::generic_category(), "CANNOT FORK 2-STAGE"); + } + } + + close(0); + close(1); + close(2); + +#endif + daemonizeFinalize(); + } + +protected: + virtual void daemonizePrepare() = 0; + virtual void daemonizeFinalize() = 0; +}; + + +// a basic network session manager (basic start and stop functionality) + +class AdcNetSessionManager +{ +public: + virtual ~AdcNetSessionManager() = default; + +protected: + AdcNetSessionManager() = default; + + AdcNetSessionManager(const AdcNetSessionManager&) = delete; + AdcNetSessionManager(AdcNetSessionManager&& other) + { + moveInstFunc(&other, this); + } + + AdcNetSessionManager& operator=(const AdcNetSessionManager&) = delete; + AdcNetSessionManager& operator=(AdcNetSessionManager&& other) + { + moveInstFunc(&other, this); + + return *this; + }; + + + template <interfaces::adc_netsession_c SessionT> + constexpr static bool anySessionPredicate(const typename SessionT::netsession_ident_t&) + { + return true; + } + + // started sessions weak pointers + template <interfaces::adc_netsession_c SessionT> + static inline std::unordered_map<const AdcNetSessionManager*, std::list<std::weak_ptr<SessionT>>> _serverSessions{}; + + std::vector<std::function<bool(const AdcNetSessionManager*)>> _stopSessionFunc; + std::vector<std::function<void(const AdcNetSessionManager*, const AdcNetSessionManager*)>> _moveCtorFunc; + + template <interfaces::adc_netsession_c SessionT> + void startSession(std::shared_ptr<SessionT>& sess_ptr) + { + auto it = _serverSessions<SessionT>[this].end(); + it = _serverSessions<SessionT>[this].emplace(it, sess_ptr); + sess_ptr->start(); + + _stopSessionFunc.emplace_back([it](const AdcNetSessionManager* inst) { + if (!it->expired()) { // session is still existing + auto sess = it->lock(); + sess->stop(); + _serverSessions<SessionT>[inst].erase(it); + return true; + } else { + return false; + } + }); + + + // define move-function only once per SessionT! + if (_serverSessions<SessionT>[this].size() == 1) { + _moveCtorFunc.emplace_back( + [](const AdcNetSessionManager* new_instance, const AdcNetSessionManager* from_inst) { + _serverSessions<SessionT>[new_instance] = std::move(_serverSessions<SessionT>[from_inst]); + }); + } + } + + + template <interfaces::adc_netsession_c SessionT, + std::predicate<typename SessionT::netsession_ident_t> PredT = decltype(anySessionPredicate<SessionT>)> + size_t stopSessions(PredT&& comp_func = anySessionPredicate<SessionT>()) + { + size_t N = 0; + + std::set<std::weak_ptr<SessionT>> remove_wptr; + + for (auto& wptr : _serverSessions<SessionT>[this]) { + if (std::shared_ptr<SessionT> sptr = wptr.lock()) { + if constexpr (std::same_as<PredT, decltype(anySessionPredicate<SessionT>)>) { + sptr->stop(); + remove_wptr.emplace(wptr); + ++N; + } else { + if (std::forward<PredT>(comp_func)(sptr->ident())) { + sptr->stop(); + remove_wptr.emplace(wptr); + ++N; + } + } + } else { // remove already stopped sessions?!! + remove_wptr.emplace(wptr); + } + } + + for (auto& wptr : remove_wptr) { + _serverSessions<SessionT>[this].erase(wptr); + } + + + return N; + } + + + size_t stopAllSessions() + { + size_t N = 0; + + for (auto& func : _stopSessionFunc) { + func(this) ? ++N : 0; + } + + _stopSessionFunc.clear(); + _moveCtorFunc.clear(); // there are nothing to move after stopping of all sessions + + return N; + } + + + void moveInstFunc(const AdcNetSessionManager* to, const AdcNetSessionManager* from) + { + if (from != to) { + for (auto& func : _moveCtorFunc) { + func(to, from); + } + + _stopSessionFunc = std::move(from->_stopSessionFunc); + _moveCtorFunc = std::move(from->_moveCtorFunc); + } + } +}; + + +static_assert(interfaces::adc_logger_c<utils::AdcOstreamLogger<>>, "!!!!!"); + + + +/* very generic network server */ + +template <typename IdentT = std::string, interfaces::adc_logger_c LoggerT = utils::AdcOstreamLogger<char>> +class AdcGenericNetServer : public AdcPosixGenericDaemon, public AdcNetSessionManager, public LoggerT +{ +public: + typedef IdentT server_ident_t; + typedef LoggerT logger_t; + + template <typename... LoggerCtorArgTs> + AdcGenericNetServer(const server_ident_t& id, LoggerCtorArgTs&&... ctor_args) + : _serverIdent(id), LoggerT(std::forward<LoggerCtorArgTs>(ctor_args)...) + { + if constexpr (traits::formattable<IdentT>) { + logInfo("Create ADC generic network server with ID: {} (addr = {}, thread = {})", id, (void*)this, + utils::AdcThisThreadId()); + } else { + logInfo("Create ADC generic network server (addr = {}, thread = {})", (void*)this, + utils::AdcThisThreadId()); + } + } + + + AdcGenericNetServer(const AdcGenericNetServer&) = delete; + AdcGenericNetServer(AdcGenericNetServer&& other) + : AdcPosixGenericDaemon(std::move(other)), AdcNetSessionManager(std::move(other)), LoggerT(std::move(other)) + { + logDebug("Move ADC server class: this = {}, target = {}", (void*)this, (void*)&other); + + if (this == &other) { + return; + } + + _serverIdent = std::move(other._serverIdent); + _stopListenFunc = std::move(other._stopListenFunc); + + for (auto& func : other._moveCtorFunc) { + func(&other, this); + } + + _moveCtorFunc = std::move(other._moveCtorFunc); + } + + virtual ~AdcGenericNetServer() + { + if constexpr (traits::formattable<IdentT>) { + logInfo("Delete ADC generic network server with ID: {} (addr = {}, thread = {})", _serverIdent, (void*)this, + utils::AdcThisThreadId()); + } else { + logInfo("Delete ADC generic network server (addr = {}, thread = {})", (void*)this, + utils::AdcThisThreadId()); + } + }; + + AdcGenericNetServer& operator=(const AdcGenericNetServer&) = delete; + AdcGenericNetServer& operator=(AdcGenericNetServer&& other) + { + logDebug("Assign-move ADC server class: this = {}, target = {}", (void*)this, (void*)&other); + + if (this != &other) { + AdcPosixGenericDaemon::operator=(std::move(other)); + AdcNetSessionManager::operator=(std::move(other)); + + _serverIdent = std::move(other._serverIdent); + _stopListenFunc = std::move(other._stopListenFunc); + + for (auto& func : other._moveCtorFunc) { + func(&other, this); + } + + _moveCtorFunc = std::move(other._moveCtorFunc); + } + + return *this; + } + + virtual server_ident_t ident() const + { + return _serverIdent; + } + + // start accepting remote connections, create and start given network session + // It must be assumed that this is asynchronous operation!!! + template <interfaces::adc_netsession_c SessionT, typename... AcceptorCtorArgTs> + void start(SessionT::netsession_ident_t id, SessionT::netsession_ctx_t sess_ctx, AcceptorCtorArgTs&&... ctor_args) + requires traits::adc_hashable_c<typename SessionT::netsession_ident_t> + { + logDebug("Call {}", __PRETTY_FUNCTION__); + + if (!_isListening<SessionT>[this][id]) { + auto acceptor = std::make_shared<typename SessionT::netservice_t::acceptor_t>( + std::forward<AcceptorCtorArgTs>(ctor_args)...); + + _stopListenFunc.emplace_back([acceptor, id](const AdcGenericNetServer* inst) { + std::error_code ec; + acceptor->close(ec); + + _isListening<SessionT>[inst][id] = false; + }); + + if constexpr (traits::formattable<typename SessionT::netsession_ident_t>) { + logInfo( + "Start listening for client connections at <{}> endpoint (session ID: {}, server addr = {}, thread " + "= {})", + acceptor->localEndpoint(), id, (void*)this, utils::AdcThisThreadId()); + } else { + logInfo("Start listening for client connections at <{}> endpoint (server addr = {}, thread = {})", + acceptor->localEndpoint(), (void*)this, utils::AdcThisThreadId()); + } + + doAccept<SessionT>(acceptor, std::move(id), std::move(sess_ctx)); + } + + // only once per SessionT + if (_isListening<SessionT>[this].size() == 1) { + _moveCtorFunc.emplace_back( + [](const AdcGenericNetServer* new_instance, const AdcGenericNetServer* from_inst) { + _isListening<SessionT>[new_instance] = std::move(_isListening<SessionT>[from_inst]); + }); + } + }; + + + template <interfaces::adc_netsession_c SessionT> + bool isListening(const typename SessionT::netsession_ident_t& id) const + { + return _isListening<SessionT>[this][id]; + } + + virtual void start() = 0; + + virtual void stop() + { + for (auto& func : _stopListenFunc) { + func(this); + } + + _stopListenFunc.clear(); + + stopAllSessions(); + }; + + + void setAcceptTimeout(const traits::adc_time_duration_c auto& timeout) + { + _acceptTimeout = std::chrono::duration_cast<decltype(_acceptTimeout)>(timeout); + } + + + auto getAcceptTimeout() const + { + return _acceptTimeout; + } + + + // helper methods for logging + + template <traits::formattable... Ts> + void logMessage(LoggerT::loglevel_t level, std::format_string<Ts...> fmt, Ts&&... args) + { + LoggerT::logMessage(level, std::format(fmt, std::forward<Ts>(args)...)); + } + + template <traits::formattable... Ts> + void logInfo(std::format_string<Ts...> fmt, Ts&&... args) + { + LoggerT::logInfo(std::format(fmt, std::forward<Ts>(args)...)); + } + + template <traits::formattable... Ts> + void logDebug(std::format_string<Ts...> fmt, Ts&&... args) + { + LoggerT::logDebug(std::format(fmt, std::forward<Ts>(args)...)); + } + + template <traits::formattable... Ts> + void logError(std::format_string<Ts...> fmt, Ts&&... args) + { + LoggerT::logError(std::format(fmt, std::forward<Ts>(args)...)); + } + +protected: + std::chrono::seconds _acceptTimeout = std::chrono::seconds::max(); + + // template  + // inline static std::unordered_map _isListening{}; + template <interfaces::adc_netsession_c SessionT> + inline static std::unordered_map<const AdcGenericNetServer*, + std::unordered_map<typename SessionT::netsession_ident_t, bool>> + _isListening{}; + + std::vector<std::function<void(const AdcGenericNetServer*)>> _stopListenFunc; + std::vector<std::function<void(const AdcGenericNetServer*, const AdcGenericNetServer*)>> _moveCtorFunc; + + server_ident_t _serverIdent; + + template <typename SessionT, typename AT, typename IDT, typename CTXT> + void doAccept(std::shared_ptr<AT> acceptor, IDT id, CTXT sess_ctx) + { + acceptor->asyncAccept( + [acceptor, id = std::move(id), sess_ctx = std::move(sess_ctx), this]( + auto ec, typename SessionT::netservice_t srv) mutable { + if (!ec) { + logInfo( + "Client connection is succesfully accepted! Client endpoint: {} (server addr = {}, thread = " + "{})", + srv.remoteEndpoint(), (void*)this, utils::AdcThisThreadId()); + + auto sess = std::make_shared<SessionT>(id, std::move(srv), sess_ctx); + startSession(sess); + + _isListening<SessionT>[this][id] = true; + doAccept<SessionT>(acceptor, std::move(id), std::move(sess_ctx)); + } else { + this->logError("Cannot start accepting connection: {}", + SessionT::netservice_t::formattableError(ec)); + + _isListening<SessionT>[this][id] = false; + } + }, + _acceptTimeout); + } +}; + + +namespace interfaces +{ + +template <typename T> +concept adc_generic_netserver_c = requires { + typename T::server_ident_t; + requires std::derived_from<T, adc::AdcGenericNetServer<typename T::server_ident_t>>; +}; + +} // namespace interfaces + + +} // namespace adc