This commit is contained in:
2026-05-30 16:21:45 +03:00
parent 434521a435
commit 21d79a4cb5
6 changed files with 1073 additions and 46 deletions

1
.gitignore vendored
View File

@@ -72,3 +72,4 @@ CMakeLists.txt.user*
*.dll *.dll
*.exe *.exe
build/

View File

@@ -4,7 +4,7 @@ project(ADC LANGUAGES CXX)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}") 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_CXX_STANDARD_REQUIRED ON)
# set(CMAKE_BUILD_TYPE Release) # 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.") # message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang and GCC.")
# endif() # endif()
set(ADC_COMMON_HEADERS set(ADC_COMMON_HEADERS
common/adc_traits.h common/adc_traits.h
common/adc_utils.h common/adc_utils.h
common/adc_serialization.h
# common/adc_value_holder.h # common/adc_value_holder.h
# common/adc_value.h # common/adc_value.h
# common/adc_valholder.h # common/adc_valholder.h
@@ -45,7 +44,6 @@ set(ADC_DEVICE_HEADERS
device/adc_device_concepts.h device/adc_device_concepts.h
) )
set(ADC_NETWORK_HEADERS set(ADC_NETWORK_HEADERS
# net/adc_netmsg.h # net/adc_netmsg.h
# net/adc_netmessage.h # net/adc_netmessage.h
@@ -60,56 +58,59 @@ set(ADC_NETWORK_HEADERS
net/adc_device_netclient.h net/adc_device_netclient.h
) )
option(ASIO_LIBRARY "Use of ASIO library for networking implementation" ON) option(ASIO_LIBRARY "Use of ASIO library for networking implementation" ON)
if (ASIO_LIBRARY) if(ASIO_LIBRARY)
find_package(ASIO REQUIRED) 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_netservice_asio.h
net/asio/adc_device_netserver_asio.h net/asio/adc_device_netserver_asio.h
) )
add_compile_definitions(PUBLIC USE_ASIO_LIBRARY) add_compile_definitions(PUBLIC USE_ASIO_LIBRARY)
option(OPENSSL_LIBRARY "Use openssl library for related ASIO-based implementation" ON) option(
if (OPENSSL_LIBRARY) OPENSSL_LIBRARY
"Use openssl library for related ASIO-based implementation"
ON
)
if(OPENSSL_LIBRARY)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
add_compile_definitions(PUBLIC USE_OPENSSL_WITH_ASIO) add_compile_definitions(PUBLIC USE_OPENSSL_WITH_ASIO)
endif() endif()
endif() endif()
option(SPDLOG_LIBRARY "Use of SPDLOG library for logging" ON) option(SPDLOG_LIBRARY "Use of SPDLOG library for logging" ON)
if (SPDLOG_LIBRARY) if(SPDLOG_LIBRARY)
find_package(spdlog REQUIRED) find_package(spdlog REQUIRED)
find_package(fmt REQUIRED) find_package(fmt REQUIRED)
set(ADC_COMMON_HEADERS ${ADC_COMMON_HEADERS} set(ADC_COMMON_HEADERS ${ADC_COMMON_HEADERS} common/adc_spdlog.h)
common/adc_spdlog.h
)
set(ADC_NETWORK_HEADERS ${ADC_NETWORK_HEADERS} set(ADC_NETWORK_HEADERS ${ADC_NETWORK_HEADERS} net/adc_netserver_spdlog.h)
net/adc_netserver_spdlog.h
)
add_compile_definitions(PUBLIC USE_SPDLOG_LIBRARY) add_compile_definitions(PUBLIC USE_SPDLOG_LIBRARY)
endif() endif()
option(
USE_UWEBSOCKET
"Use of uWebsocket library for websocket-related staff"
ON
)
if(USE_UWEBSOCKET)
option(USE_UWEBSOCKET "Use of uWebsocket library for websocket-related staff" ON)
if (USE_UWEBSOCKET)
include(FetchContent) include(FetchContent)
include(ExternalProject) include(ExternalProject)
FetchContent_Declare(uwebsockets FetchContent_Declare(
SOURCE_DIR ${CMAKE_BINARY_DIR}/uWebsockets uwebsockets
BINARY_DIR ${CMAKE_BINARY_DIR} SOURCE_DIR
${CMAKE_BINARY_DIR}/uWebsockets
BINARY_DIR
${CMAKE_BINARY_DIR}
GIT_REPOSITORY "https://github.com/uNetworking/uWebSockets.git" GIT_REPOSITORY "https://github.com/uNetworking/uWebSockets.git"
GIT_TAG "v20.67.0" GIT_TAG "v20.67.0"
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
@@ -121,9 +122,12 @@ if (USE_UWEBSOCKET)
set(UWEBSOCKETS_INCLUDE_DIR ${uwebsockets_SOURCE_DIR}/src) set(UWEBSOCKETS_INCLUDE_DIR ${uwebsockets_SOURCE_DIR}/src)
FetchContent_Declare(usockets FetchContent_Declare(
SOURCE_DIR ${uwebsockets_SOURCE_DIR}/uSockets usockets
BINARY_DIR ${CMAKE_BINARY_DIR} SOURCE_DIR
${uwebsockets_SOURCE_DIR}/uSockets
BINARY_DIR
${CMAKE_BINARY_DIR}
GIT_REPOSITORY "https://github.com/uNetworking/uSockets.git" GIT_REPOSITORY "https://github.com/uNetworking/uSockets.git"
GIT_TAG "v0.8.8" GIT_TAG "v0.8.8"
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
@@ -137,7 +141,8 @@ if (USE_UWEBSOCKET)
include(ExternalProject) include(ExternalProject)
ExternalProject_Add(LIBUS ExternalProject_Add(
LIBUS
SOURCE_DIR ${usockets_SOURCE_DIR} SOURCE_DIR ${usockets_SOURCE_DIR}
PREFIX ${CMAKE_BINARY_DIR}/_deps PREFIX ${CMAKE_BINARY_DIR}/_deps
BUILD_IN_SOURCE TRUE BUILD_IN_SOURCE TRUE
@@ -148,17 +153,19 @@ if (USE_UWEBSOCKET)
) )
add_library(LIBUS_LIB STATIC IMPORTED) 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) get_target_property(LIBUS_LIB_PATH LIBUS_LIB IMPORTED_LOCATION)
include_directories(${USOCKETS_INCLUDE_DIR}) include_directories(${USOCKETS_INCLUDE_DIR})
include_directories(${UWEBSOCKETS_INCLUDE_DIR}) include_directories(${UWEBSOCKETS_INCLUDE_DIR})
endif(USE_UWEBSOCKET) endif(USE_UWEBSOCKET)
option(BUILD_TESTS "Build tests" ON) option(BUILD_TESTS "Build tests" ON)
if (BUILD_TESTS) if(BUILD_TESTS)
find_package(doctest) find_package(doctest)
# set(VALUEHOLDER_TEST_APP adc_valueholder_test) # set(VALUEHOLDER_TEST_APP adc_valueholder_test)
@@ -175,21 +182,34 @@ if (BUILD_TESTS)
set(NETSERVICE_TEST_APP adc_netservice_test) set(NETSERVICE_TEST_APP adc_netservice_test)
add_executable(${NETSERVICE_TEST_APP} tests/adc_netservice_test.cpp) add_executable(${NETSERVICE_TEST_APP} tests/adc_netservice_test.cpp)
if (OPENSSL_LIBRARY) if(OPENSSL_LIBRARY)
target_link_libraries(${NETSERVICE_TEST_APP} OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(
${NETSERVICE_TEST_APP}
OpenSSL::SSL
OpenSSL::Crypto
)
endif() endif()
if (ASIO_LIBRARY) if(ASIO_LIBRARY)
find_package(cxxopts CONFIG) find_package(cxxopts CONFIG)
set(ASIO_NETSERVER_TEST_APP adc_asio_netserver_test) set(ASIO_NETSERVER_TEST_APP adc_asio_netserver_test)
add_executable(${ASIO_NETSERVER_TEST_APP} tests/adc_asio_netserver_test.cpp) add_executable(
target_link_libraries(${ASIO_NETSERVER_TEST_APP} PUBLIC Threads::Threads cxxopts::cxxopts) ${ASIO_NETSERVER_TEST_APP}
if (OPENSSL_LIBRARY) tests/adc_asio_netserver_test.cpp
target_link_libraries(${ASIO_NETSERVER_TEST_APP} PUBLIC OpenSSL::SSL OpenSSL::Crypto) )
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()
endif() endif()
if (NOT doctest_FOUND) if(NOT doctest_FOUND)
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
doctest doctest
@@ -207,7 +227,7 @@ if (BUILD_TESTS)
add_test(NETMSG_TEST ${NETMSG_TEST_APP}) add_test(NETMSG_TEST ${NETMSG_TEST_APP})
add_test(ASIO_NETSRV_TEST ${ASIO_NETSERVER_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) target_link_libraries(${ASIO_NETSERVER_TEST_APP} PRIVATE fmt::fmt)
endif() endif()
@@ -216,7 +236,13 @@ endif(BUILD_TESTS)
include(GNUInstallDirs) 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_compile_features(${PROJECT_NAME} INTERFACE cxx_std_20)
# target_link_libraries(${PROJECT_NAME} INTERFACE ASIO::ASIO) # target_link_libraries(${PROJECT_NAME} INTERFACE ASIO::ASIO)
target_include_directories( target_include_directories(
@@ -225,4 +251,3 @@ target_include_directories(
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )

518
common/adc_serialization.h Normal file
View File

@@ -0,0 +1,518 @@
#pragma once
/*
ABSTRACT DEVICE COMPONENTS LIBRARY
*/
#include <algorithm>
#include <concepts>
#include <ranges>
#include "adc_traits.h"
#include "adc_utils.h"
namespace adc
{
/* SERIALIZATION/DESERIALIZATION PROCESS ERROR */
template <typename T>
concept adc_serialization_error_c = std::default_initializable<T> && (std::convertible_to<T, bool> || 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 <typename T>
concept adc_serialization_params_c = std::copyable<T> && requires(T t) {
// delimiter between items of serializing values sequence
requires traits::adc_output_char_range<decltype(t.elem_delim)>;
// delimiter between items of aggregative (multi-element) serializing value
requires traits::adc_output_char_range<decltype(t.seq_delim)>;
// a format string for std::chrono::time_point types
requires traits::adc_output_char_range<decltype(t.timepoint_format)>;
};
// 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 <adc_serialization_error_c RetT>
struct adc_serializer_interface_t {
virtual ~adc_serializer_interface_t() = default;
typedef RetT error_t;
template <std::derived_from<adc_serializer_interface_t> SelfT, traits::adc_output_char_range R, typename ValueT>
RetT operator()(this SelfT&& self, R& output, ValueT const& value)
{
return std::forward<SelfT>(self)(output, value);
}
template <std::derived_from<adc_serializer_interface_t> 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<SelfT>(self)(output, value, params);
}
protected:
adc_serializer_interface_t() = default;
};
template <typename T>
concept adc_serializer_c =
std::derived_from<T, adc_serializer_interface_t<typename T::error_t>> && requires(T t, const T t_const) {
// static const variable with name of the serializer
requires std::formattable<decltype(T::serializerName), char> && std::is_const_v<decltype(T::serializerName)>;
};
template <adc_serialization_error_c RetT>
struct adc_deserializer_interface_t {
virtual ~adc_deserializer_interface_t() = default;
typedef RetT error_t;
template <std::derived_from<adc_deserializer_interface_t> SelfT, traits::adc_input_char_range R, typename ValueT>
RetT operator()(this SelfT&& self, R const& input, ValueT& value)
{
return std::forward<SelfT>(self)(input, value);
}
template <std::derived_from<adc_deserializer_interface_t> 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<SelfT>(self)(input, value, params);
}
protected:
adc_deserializer_interface_t() = default;
};
template <typename T>
concept adc_deserializer_c =
std::derived_from<T, adc_deserializer_interface_t<typename T::error_t>> && requires(T t, const T t_const) {
// static const variable with name of the deserializer
requires std::formattable<decltype(T::deserializerName), char> &&
std::is_const_v<decltype(T::deserializerName)>;
};
} // 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<adc::AdcSerializerErrorCode> : public true_type
{
};
template <>
class is_error_code_enum<adc::AdcDeserializerErrorCode> : 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<AdcSerializerErrorCode>(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<AdcDeserializerErrorCode>(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<adc_serialization_error_t> {
virtual ~AdcSerializerBase() = default;
using typename adc_serializer_interface_t<adc_serialization_error_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 <typename VT, typename R>
requires(std::ranges::input_range<R> && std::same_as<VT, std::ranges::range_value_t<R>>)
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<adc_serialization_error_t> {
using typename adc_deserializer_interface_t<adc_serialization_error_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<std::string_view> splitValueIntoElements(traits::adc_input_char_range auto const& input,
adc_serialization_params_c auto const& params,
bool& empty)
{
static_assert(std::ranges::contiguous_range<decltype(input)>, "NOT IMPLEMENTED FOR NON-CONTIGUIUS RANGES!!!");
std::vector<std::string_view> 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, std::string_view>(
std::string_view{el.begin(), el.end()});
if (empty && res.back().size()) {
empty = false;
}
});
} else {
empty = true;
}
return res;
}
template <typename VT, std::ranges::output_range<VT> 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<DeserParamsT>(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<R>) {
std::back_inserter(r) = val;
it = r.end();
}
} else {
*it = val;
++it;
}
}
return AdcDeserializerErrorCode::ERROR_OK;
}
};
/* MAIN (FALLBACK) TEMPLATED IMPLEMENTATION OF SERIALIZER/DESERIALIZER */
template <typename VT>
struct AdcSerializer : AdcSerializerBase {
constexpr static std::string_view serializerName{"ADC-FALLBACK-SERIALIZER"};
template <adc_serialization_params_c ParamsT = adc_serialization_params_t>
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<VT, std::string>) {
std::string s = value;
std::ranges::copy(s, std::back_inserter(output));
} else if constexpr (std::ranges::range<VT>) {
using value_t = std::remove_cv_t<std::ranges::range_value_t<VT>>;
// special range (character sequence)
if constexpr (std::same_as<value_t, char>) {
std::ranges::copy(value, std::back_inserter(output));
} else {
AdcSerializer<value_t> sr;
return AdcSerializerBase::serializeRange<value_t>(sr, value, output, params);
}
} else if constexpr (traits::adc_tuple_like<VT>) {
return [&output, &params]<size_t I = 0>(this auto& self, VT& tp) -> error_t {
if constexpr (I < (std::tuple_size_v<VT> - 1)) {
auto err = AdcSerializer<std::tuple_element_t<I, VT>>{}(output, std::get<I>(tp), params);
if (err) {
return AdcSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER;
}
AdcSerializerBase::addElemDelimiter(output, params);
return self.template operator()<I + 1>(tp);
} else if constexpr (I < (std::tuple_size_v<VT> - 1)) { // the last element
auto err = AdcSerializer<std::tuple_element_t<I, VT>>{}(output, std::get<I>(tp), params);
if (err) {
return AdcSerializerErrorCode::ERROR_UNDERLYING_SERIALIZER;
}
return AdcSerializerErrorCode::ERROR_OK;
}
}(value);
} else if constexpr (traits::adc_time_point_c<VT>) {
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<VT>) {
std::format_to(std::back_inserter(output), "{}", value.count());
} else if constexpr (std::formattable<VT, char>) {
std::format_to(std::back_inserter(output), "{}", value);
} else {
static_assert(false, "UNSUPPORTED TYPE!!!");
}
return AdcSerializerErrorCode::ERROR_OK;
}
};
template <typename VT>
struct AdcDeserializer : AdcDeserializerBase {
static constexpr std::string_view deserializerName{"ADC-FALLBACK-DESERIALIZER"};
virtual ~AdcDeserializer() = default;
template <adc_serialization_params_c ParamsT = adc_serialization_params_t>
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<VT>) {
auto v = adc::utils::AdcFromChars<VT>(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>) {
VT r;
if constexpr (traits::adc_fixed_size_range<VT>) {
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<VT>) {
using el_t = std::ranges::range_value_t<VT>;
static_assert(std::ranges::output_range<VT, el_t>, "INVALID RANGE TYPE!!!");
// no reference or constants allowed
static_assert(!(std::is_reference_v<el_t> || std::is_const_v<el_t>), "INVALID RANGE ELEMENT TYPE!!!");
AdcDeserializer<el_t> dsr;
return deserializingRange<el_t>(dsr, input, value, params);
} else if constexpr (traits::adc_tuple_like<VT>) {
bool empty;
auto elems = splitValueIntoElements(input, params, &empty);
if (empty) {
value = VT{};
} else {
return [&input, &params, &elems]<size_t I = 0>(this auto& self, VT& tp) {
if constexpr (I < std::tuple_size_v<VT>) {
if (I < elems.size()) {
auto err =
AdcDeserializer<std::tuple_element_t<I, VT>>{}(elems[I], std::get<I>(tp), params);
if (err) {
return AdcDeserializerErrorCode::ERROR_UNDERLYING_DESERIALIZER;
}
return self.template operator()<I + 1>(tp);
} else {
return AdcDeserializerErrorCode::ERROR_OK;
}
}
return AdcDeserializerErrorCode::ERROR_OK;
}(value);
}
} else if constexpr (traits::adc_time_point_c<VT>) {
std::istringstream ist{utils::AdcTrimSpaces<std::string>(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<VT>) {
typename VT::rep vd;
AdcDeserializer<typename VT::rep> 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

View File

@@ -86,6 +86,17 @@ struct adc_char_identity {
} }
}; };
// std::array, std::span(v_t, N), std::tuple
template <typename R>
concept adc_fixed_size_range = std::ranges::range<R> && requires { std::tuple_size<std::remove_cvref_t<R>>::value; };
// non-resizable ranges
template <typename R>
concept adc_non_resizable_range = std::ranges::range<R> && !requires(R r, std::size_t n) { r.resize(n); };
// deduce returned type of callable // deduce returned type of callable
// template <typename T> // template <typename T>
// using adc_retval_t = std::invoke_result_t<std::remove_cvref_t<T>>; // using adc_retval_t = std::invoke_result_t<std::remove_cvref_t<T>>;
@@ -270,6 +281,13 @@ using adc_common_duration_t = adc_duration_common_type_t<std::chrono::nanosecond
std::chrono::years>; std::chrono::years>;
template <typename T>
concept adc_time_point_c = requires {
[]<typename ClockT, typename DurT>(std::type_identity<std::chrono::time_point<ClockT, DurT>>) {
}(std::type_identity<std::remove_cvref_t<T>>());
};
// concept for hashable types // concept for hashable types
template <typename T> template <typename T>
concept adc_hashable_c = requires(T t) { concept adc_hashable_c = requires(T t) {

View File

@@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <charconv> #include <charconv>
#include <iomanip>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <mutex> #include <mutex>

464
tart Normal file
View File

@@ -0,0 +1,464 @@
#pragma once
/*
ABSTRACT DEVICE COMPONENTS LIBRARY
*/
#include <filesystem>
#include <functional>
#include <list>
#include <set>
#include <unordered_map>
#if __has_include(<unistd.h>) // POSIX
#define FORK_EXISTS 1
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#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 <interfaces::adc_netsession_c SessionT>
 // inline static std::unordered_map<const AdcGenericNetServer*, bool> _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