From e0e10395fb085cb8b90d6e2dac6759fe46cf9e42 Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Wed, 8 Oct 2025 18:13:46 +0300 Subject: [PATCH] ... --- asibfm700/CMakeLists.txt | 1 + mcc/CMakeLists.txt | 4 + mcc/mcc_netserver_proto.h | 356 +++++++++++++++++++++++++++++++++----- mcc/mcc_utils.h | 18 +- mcc/tests/netmsg_test.cpp | 54 ++++++ 5 files changed, 384 insertions(+), 49 deletions(-) create mode 100644 mcc/tests/netmsg_test.cpp diff --git a/asibfm700/CMakeLists.txt b/asibfm700/CMakeLists.txt index 18e7837..dd5ac07 100644 --- a/asibfm700/CMakeLists.txt +++ b/asibfm700/CMakeLists.txt @@ -40,6 +40,7 @@ set(ASIBFM700_LIB asibfm700mount) add_library(${ASIBFM700_LIB} STATIC ${ASIBFM700_LIB_SRC} asibfm700_mount.h asibfm700_mount.cpp asibfm700_configfile.h) +target_include_directories(${ASIBFM700_LIB} PRIVATE mcc spdlog) target_link_libraries(${ASIBFM700_LIB} PRIVATE mcc spdlog) option(WITH_TESTS "Build tests" ON) diff --git a/mcc/CMakeLists.txt b/mcc/CMakeLists.txt index 2013049..de0b998 100644 --- a/mcc/CMakeLists.txt +++ b/mcc/CMakeLists.txt @@ -92,5 +92,9 @@ if (WITH_TESTS) target_include_directories(${CTTE_TEST_APP} PRIVATE ${ERFA_INCLUDE_DIR}) target_link_libraries(${CTTE_TEST_APP} ERFA_LIB bsplines) + set(NETMSG_TESTS_APP netmsg_test) + add_executable(${NETMSG_TESTS_APP} tests/netmsg_test.cpp) + target_link_libraries(${NETMSG_TESTS_APP} mcc) + enable_testing() endif() diff --git a/mcc/mcc_netserver_proto.h b/mcc/mcc_netserver_proto.h index 57e1e43..aeb282b 100644 --- a/mcc/mcc_netserver_proto.h +++ b/mcc/mcc_netserver_proto.h @@ -471,124 +471,392 @@ struct MccNetMessageValidKeywords { return std::array{mcc::utils::FNV1aHash(NETMSG_VALID_KEYWORDS[Is])...}; }(std::make_index_sequence()); - static std::expected isKeywordValid(std::string_view key) + constexpr static const size_t* isKeywordValid(std::string_view key) { const auto hash = utils::FNV1aHash(key); for (auto const& h : NETMSG_VALID_KEYWORD_HASHES) { if (h == hash) { - return h; + return &h; } } - return std::unexpected(std::make_error_code(std::errc::invalid_argument)); + return nullptr; } }; static_assert(mcc_netmsg_valid_keys_c, ""); -template +template class MccNetMessage { + static inline utils::MccSimpleDeserializer defaultDeserilizer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ}; + public: typedef BASE_T valid_keys_t; typedef BYTEREPR_T byte_repr_t; + enum MccNetMessageError { ERROR_OK, ERROR_EMPTY_MESSAGE, ERROR_INVALID_KEYWORD, ERROR_EMPTY_KEYWORD }; + + MccNetMessage() = default; + template MccNetMessage(KT&& key, PTs&&... params) + requires traits::mcc_output_char_range { construct(std::forward(key), std::forward(params)...); } - template - bool withKey(const KT& key) + template + constexpr MccNetMessage(const R& msg) + requires traits::mcc_input_char_range { + fromCharRange(msg); + } + + template + constexpr bool withKey(const KT& key) + { + if constexpr (std::is_pointer_v>) { + return withKey(std::string_view{key}); + } + return utils::FNV1aHash(key) == _keywordHash; } - template - std::expected params(DeserFuncT&& deser_func) + + template + R keyword() const { + if constexpr (traits::mcc_char_view) { + return R{_keyword.begin(), _keyword.end()}; + } else { + R r; + std::ranges::copy(_keyword, std::back_inserter(r)); + + return r; + } + } + + std::string_view keyword() const + { + return _keyword; + } + + // template + // R params(size_t start_idx, size_t Nelemes = std::numeric_limits::max()) + // { + // if (start_idx >= _params.size()) { + // return R{}; + // } + + // auto stop_idx = start_idx + Nelemes - 1; + // if (stop_idx >= _params.size()) { + // stop_idx = _params.size() - 1; + // } + + // if constexpr (traits::mcc_char_view) { + // return R{_params[start_idx].begin(), _params[stop_idx].end()}; + // } else { + // R r; + // std::ranges::copy(std::string_view{_params[start_idx].begin(), _params[stop_idx].end()}, + // std::back_inserter(r)); + + // return r; + // } + // } + + template + R params(size_t start_idx, size_t Nelemes = std::numeric_limits::max()) + requires(traits::mcc_view_or_output_char_range || traits::mcc_range_of_char_range) + { + if (start_idx >= _params.size()) { + return R{}; + } + + auto stop_idx = start_idx + Nelemes - 1; + if (stop_idx >= _params.size()) { + stop_idx = _params.size() - 1; + } + + if constexpr (traits::mcc_range_of_char_range) { // returm parameters as array + using el_t = std::ranges::range_value_t; + + R r; + if constexpr (traits::mcc_char_view || traits::mcc_output_char_range) { + for (size_t i = start_idx; i <= stop_idx; ++i) { + auto& el = _params[i]; + std::back_inserter(r) = el_t{el.begin(), el.end()}; + } + } else { + static_assert(false, "UNSUPPORTED RANGE ELEMENT TYPE!!!"); + } + + return r; + } else { + if constexpr (traits::mcc_char_view) { // return joined parameters as a single char-range + return R{_params[start_idx].begin(), _params[stop_idx].end()}; + } else { + R r; + std::ranges::copy(std::string_view{_params[start_idx].begin(), _params[stop_idx].end()}, + std::back_inserter(r)); + + return r; + } + } + } + + std::string_view params(size_t start_idx, size_t Nelemes = std::numeric_limits::max()) + { + return params(start_idx, Nelemes); + } + + template + R param(size_t idx) + { + if (idx >= _params.size()) { + return {}; + } + + if constexpr (traits::mcc_char_view) { + return R{_params[idx].begin(), _params[idx].end()}; + } else { + R r; + std::ranges::copy(_params[idx], std::back_inserter(r)); + + return r; + } + } + + std::string_view param(size_t idx) + { + if (idx >= _params.size()) { + return {}; + } + + return _params[idx]; + } + + + template + std::expected paramValue(size_t idx, DeserFuncT&& deser_func) + { + if (idx >= _params.size()) { + return std::unexpected{std::make_error_code(std::errc::argument_out_of_domain)}; + } + T val; - auto ec = deser_func(_params, val); + auto ec = std::forward(deser_func)(_params[idx], val); if (ec) { - return std::unexpect(ec); + return std::unexpected(ec); } else { return val; } } template - std::expected params() + std::expected paramValue(size_t idx) { - return params(utils::MccSimpleDeserializer{}.setRangeDelim(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ)); + return paramValue(idx, defaultDeserilizer); + } + + + template + R byteRepr() const + { + if constexpr (traits::mcc_char_view) { + return R{_msgBuffer.begin(), _msgBuffer.end()}; + } else { + R r; + std::ranges::copy(_msgBuffer, std::back_inserter(r)); + + return r; + } + } + + std::string_view byteRepr() const + { + return byteRepr(); } template std::error_code construct(KT&& key, PTs&&... params) + requires traits::mcc_output_char_range { + if constexpr (std::is_pointer_v>) { + return construct(std::string_view(key), std::forward(params)...); + } + + if (!std::ranges::size(key)) { return std::make_error_code(std::errc::invalid_argument); } auto r = valid_keys_t::isKeywordValid(key); if (!r) { - return r.error(); + return std::make_error_code(std::errc::argument_out_of_domain); } - _keywordHash = r.value(); + _keywordHash = *r; _msgBuffer = BYTEREPR_T{}; - std::ranges::copy(key, std::back_inserter(_msgBuffer)); - _keyword = {_msgBuffer.begin(), _msgBuffer.end()}; + std::ranges::copy(std::forward(key), std::back_inserter(_msgBuffer)); + // _keyword = {_msgBuffer.begin(), _msgBuffer.end()}; + size_t key_idx = std::distance(_msgBuffer.begin(), _msgBuffer.end()); + std::vector par_idx; - _params = std::string_view{}; + _params.clear(); if constexpr (sizeof...(PTs)) { std::ranges::copy(MCC_COMMPROTO_KEYPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer)); - [this](this auto&& self, const T& par, const Ts&... pars) { - if constexpr (std::is_arithmetic_v) { - std::ranges::copy(std::to_string(par), std::back_inserter(_msgBuffer)); - } else if constexpr (std::convertible_to) { - std::ranges::copy(static_cast(par), std::back_inserter(_msgBuffer)); - } else if constexpr (std::constructible_from) { - std::ranges::copy(std::string(par), std::back_inserter(_msgBuffer)); - } else if constexpr (traits::mcc_char_range) { - std::ranges::copy(std::string(par.begin(), par.end()), std::back_inserter(_msgBuffer)); - } else if constexpr (std::same_as) { - std::ranges::copy(mcc_pairkind2str(par), std::back_inserter(_msgBuffer)); - } else if constexpr (traits::mcc_time_duration_c) { - std::forward(self)(par.count()); - } else { - static_assert(false, "UNSUPPORTED TYPE!!!"); - } + convertFunc(par_idx, std::forward(params)...); - if constexpr (sizeof...(Ts)) { - std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer)); - - std::forward(self)(pars...); - } - }(std::forward(params)...); - - _params = {_msgBuffer.begin() + _keyword.size() + MCC_COMMPROTO_KEYPARAM_DELIM_SEQ.size(), - _msgBuffer.end()}; + for (size_t i = 0; i < par_idx.size(); i += 2) { + _params.emplace_back(_msgBuffer.begin() + par_idx[i], _msgBuffer.begin() + par_idx[i + 1]); + } } + _keyword = std::string_view{_msgBuffer.begin(), _msgBuffer.begin() + key_idx}; + return {}; } + + template + constexpr MccNetMessageError fromCharRange(const R& r) + { + if constexpr (std::is_pointer_v>) { + return fromCharRange(std::string_view(r)); + } + + + if (std::ranges::size(r) == 0) { + return ERROR_EMPTY_MESSAGE; + } + + std::string_view key, val; + + auto prev_msg_buff = _msgBuffer; + + if constexpr (traits::mcc_output_char_range) { + _msgBuffer = BYTEREPR_T{}; + std::ranges::copy(r, std::back_inserter(_msgBuffer)); + } else { + _msgBuffer = {std::begin(r), std::end(r)}; + } + + auto found = std::ranges::search(_msgBuffer, MCC_COMMPROTO_KEYPARAM_DELIM_SEQ); + + if (found.empty()) { // only keyword + key = utils::trimSpaces(std::string_view{_msgBuffer.begin(), _msgBuffer.end()}); + } else { + key = utils::trimSpaces(std::string_view{_msgBuffer.begin(), found.begin()}); + } + + auto kv = valid_keys_t::isKeywordValid(key); + if (!kv) { + // _msgBuffer = prev_msg_buff; // restore previous netmessage state + return ERROR_INVALID_KEYWORD; + } + + _keywordHash = *kv; + _keyword = key; + + if (!found.empty()) { // params ... + _params.clear(); + auto pr = + std::views::split(std::string_view{found.end(), _msgBuffer.end()}, MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ); + for (auto const& p : pr) { + _params.emplace_back(p.begin(), p.end()); + } + } + + return ERROR_OK; + } + protected: size_t _keywordHash{}; std::string_view _keyword{}; - std::string_view _params{}; + std::vector _params{}; BYTEREPR_T _msgBuffer{}; + + template + void convertFunc(std::vector& idx, const T& par, const Ts&... pars) + { + idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end())); + + // if constexpr (std::is_arithmetic_v) { + // std::format_to(std::back_inserter(_msgBuffer), "{}", par); + // } else if constexpr (std::convertible_to) { + // std::ranges::copy(static_cast(par), std::back_inserter(_msgBuffer)); + // } else if constexpr (std::constructible_from) { + // std::ranges::copy(std::string(par), std::back_inserter(_msgBuffer)); + // } else if constexpr (traits::mcc_char_range) { + // std::ranges::copy(std::string(par.begin(), par.end()), std::back_inserter(_msgBuffer)); + // } else if constexpr (std::same_as) { + // std::ranges::copy(mcc_pairkind2str(par), std::back_inserter(_msgBuffer)); + // } else if constexpr (traits::mcc_time_duration_c) { + // idx.resize(idx.size() - 1); + // convertFunc(idx, par.count()); + // idx.resize(idx.size() - 1); + // } else if constexpr (std::formattable) { + // std::format_to(std::back_inserter(_msgBuffer), "{}", par); + // } else { + // static_assert(false, "UNSUPPORTED TYPE!!!"); + // } + + // use the lambda here to enable recursive calling with no special handling of 'idx' + [](this auto const& self, auto& buff, auto const& p) { + using p_t = std::decay_t; + + if constexpr (std::is_arithmetic_v) { + std::format_to(std::back_inserter(buff), "{}", p); + } else if constexpr (std::convertible_to) { + std::ranges::copy(static_cast(p), std::back_inserter(buff)); + } else if constexpr (std::constructible_from) { + std::ranges::copy(std::string(p), std::back_inserter(buff)); + } else if constexpr (traits::mcc_char_range) { + std::ranges::copy(std::string(p.begin(), p.end()), std::back_inserter(buff)); + } else if constexpr (std::same_as) { + std::ranges::copy(mcc_pairkind2str(p), std::back_inserter(buff)); + } else if constexpr (traits::mcc_time_duration_c) { + self(buff, p.count()); + } else if constexpr (std::ranges::range) { + auto sz = std::ranges::size(p); + if (sz == 0) { + return; + } + + self(buff, *p.begin()); // the first element + + if (sz > 1) { + for (auto const& el : p | std::views::drop(1)) { + std::ranges::copy(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, std::back_inserter(buff)); + self(buff, el); + } + } + } else if constexpr (std::formattable) { + std::format_to(std::back_inserter(buff), "{}", p); + } else { + static_assert(false, "UNSUPPORTED TYPE!!!"); + } + }(_msgBuffer, par); + + idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end())); + + if constexpr (sizeof...(Ts)) { + std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer)); + + convertFunc(idx, pars...); + } + }; }; - +static_assert(MccNetMessage{"ACK"}.withKey("ACK")); } // namespace mcc::network diff --git a/mcc/mcc_utils.h b/mcc/mcc_utils.h index d2bdaf2..a86844a 100644 --- a/mcc/mcc_utils.h +++ b/mcc/mcc_utils.h @@ -16,7 +16,7 @@ namespace mcc::utils static const std::regex decimalNumberRx{" *[-+]?([0-9]*[.])?[0-9]+([eE][-+]?[0-9]+)? *"}; static const std::regex sexagesimalReprRx{" *[-+]?[0-9]{1,2}:[0-9]{1,2}:([0-9]{0,2}[.])?[0-9]+ *"}; -static bool isEqual(std::floating_point auto const& v1, std::floating_point auto const& v2) +constexpr static bool isEqual(std::floating_point auto const& v1, std::floating_point auto const& v2) { constexpr auto eps = std::numeric_limits>::epsilon(); @@ -26,7 +26,7 @@ static bool isEqual(std::floating_point auto const& v1, std::floating_point auto enum class TrimType { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH }; template -static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH) +constexpr static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH) requires std::same_as>> { auto is_space = [](const auto& ch) { return ch == ' '; }; @@ -59,7 +59,7 @@ static std::string_view trimSpaces(R&& r, TrimType type = TrimType::TRIM_BOTH) return std::string_view(f1, f2); } -static std::string_view trimSpaces(const char* r, TrimType type = TrimType::TRIM_BOTH) +constexpr static std::string_view trimSpaces(const char* r, TrimType type = TrimType::TRIM_BOTH) { return trimSpaces(std::string_view(r), type); } @@ -404,7 +404,15 @@ static constexpr size_t FNV1aHash(std::forward_iterator auto begin, std::sentine class MccSimpleDeserializer { public: - static constexpr std::string RANGE_DELIM_SEQ = ","; + static constexpr std::string_view RANGE_DELIM_SEQ = ","; + + MccSimpleDeserializer() : _rangeDelim(RANGE_DELIM_SEQ) {} + + template + MccSimpleDeserializer(R&& r) : MccSimpleDeserializer() + { + setRangeDelim(std::forward(r)); + } template MccSimpleDeserializer& setRangeDelim(R&& r) @@ -485,7 +493,7 @@ public: } protected: - std::string _rangeDelim{RANGE_DELIM_SEQ}; + std::string _rangeDelim; }; diff --git a/mcc/tests/netmsg_test.cpp b/mcc/tests/netmsg_test.cpp new file mode 100644 index 0000000..e0725d0 --- /dev/null +++ b/mcc/tests/netmsg_test.cpp @@ -0,0 +1,54 @@ +#include +#include + + +#include "../mcc_netserver_proto.h" + + +int main() +{ + using msg1_t = mcc::network::MccNetMessage; + using msg2_t = mcc::network::MccNetMessage; + + std::string_view sv{"TARGET 11:22:33.212; -0.23876984; RADEC "}; + std::vector arr{sv.begin(), sv.end()}; + + msg1_t msg1; + + msg1.fromCharRange(arr); + + std::cout << "msg.key = <" << msg1.keyword() << ">\n"; + std::cout << "msg.par[1] = <" << msg1.param(1) << ">\n"; + + std::vector vd{1.1, 2.2, 3.3}; + msg2_t msg2("ACK", 12.43298042, "EEE", std::chrono::seconds(10), vd); + + std::cout << "msg.bytes = <" << msg2.byteRepr() << ">\n"; + std::cout << "msg.key = <" << msg2.keyword() << ">\n"; + std::cout << "msg.par[1] = <" << msg2.param(1) << ">\n"; + std::cout << "msg.par[2] = <" << msg2.param(2) << ">\n"; + + auto p2 = msg2.paramValue(2); + std::cout << "msg.par[2] = <" << p2.value_or({}) << ">\n"; + + std::cout << "msg.par[3] = <"; + auto p3 = msg2.paramValue(3); + if (p3.has_value()) { + for (auto const& el : p3.value()) { + std::cout << el << " "; + } + } + std::cout << ">\n"; + + std::cout << "msg.par[1-2] joined = <" << msg2.params(1, 2) << ">\n"; + + auto vv = msg2.params>(1, 3); + std::cout << "msg.par[1-3] array = <"; + for (auto const& el : vv) { + std::cout << el << " "; + } + std::cout << ">\n"; + + + return 0; +}