From 9a4ea40de0717a4140e9422ace0e629bb268280b Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Fri, 19 Jun 2026 09:34:27 +0300 Subject: [PATCH] ... --- CMakeLists.txt | 1 + include/snipplib/concepts/snplib_concepts.h | 2 + include/snipplib/network/snplib_endpoint.h | 485 ++++++++++++++++++++ include/snipplib/utils/snplib_string.h | 25 + 4 files changed, 513 insertions(+) create mode 100644 include/snipplib/network/snplib_endpoint.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a23df22..fd00611 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ set(LIB_HEADERS include/snipplib/utils/snplib_utils.h include/snipplib/containers/snplib_hmap.h include/snipplib/serialization/snplib_serialization.h + include/snipplib/network/snplib_endpoint.h ) add_library(${PROJECT_NAME} INTERFACE ${LIB_HEADERS}) diff --git a/include/snipplib/concepts/snplib_concepts.h b/include/snipplib/concepts/snplib_concepts.h index 5083ce9..504672a 100644 --- a/include/snipplib/concepts/snplib_concepts.h +++ b/include/snipplib/concepts/snplib_concepts.h @@ -28,6 +28,8 @@ concept snplib_output_char_range_c = std::ranges::output_range; template concept snplib_char_view_c = std::ranges::view && std::same_as, CharT>; +template +concept snplib_view_or_output_char_range_c = snplib_char_view_c || snplib_output_char_range_c; /* std::chrono related concepts */ diff --git a/include/snipplib/network/snplib_endpoint.h b/include/snipplib/network/snplib_endpoint.h new file mode 100644 index 0000000..28da349 --- /dev/null +++ b/include/snipplib/network/snplib_endpoint.h @@ -0,0 +1,485 @@ +#pragma once + + +/* + * Very simple various protocols endpoint parser and holder class + * + * endpoint: proto_mark://host_name:port_num/path + * where "path" is optional for all non-local protocol kinds; + * + * for local kind of protocols the endpoint must be given as: + * local://stream/PATH + * local://seqpacket/PATH + * local://serial/PATH + * where 'stream' and 'seqpacket' "host_name"-field marks the + * stream-type and seqpacket-type UNIX domain sockets protocols; + * 'serial' marks a serial (RS232/485) protocol. + * here, possible "port_num" field is allowed but ignored. + * + * NOTE: "proto_mark" and "host_name" (for local kind) fields are parsed in case-insensitive manner! + * + * EXAMPLES: tcp://192.168.70.130:3131 + * local://serial/dev/ttyS1 + * local://seqpacket/tmp/BM70_SERVER_SOCK + * + * + */ + + +#include +#include +#include +#include + +#include +#include + +namespace snplib +{ + +class snplib_endpoint_t +{ +public: + static constexpr std::string_view protoHostDelim = "://"; + static constexpr std::string_view hostPortDelim = ":"; + static constexpr std::string_view portPathDelim = "/"; + + enum proto_id_t : uint8_t { + PROTO_ID_LOCAL, + PROTO_ID_SEQLOCAL, + PROTO_ID_SERLOCAL, + PROTO_ID_TCP, + PROTO_ID_TLS, + PROTO_ID_UNKNOWN + }; + + static constexpr std::string_view protoMarkLocal{"local"}; // UNIX domain + static constexpr std::string_view protoMarkTCP{"tcp"}; // TCP + static constexpr std::string_view protoMarkTLS{"tls"}; // TLS + + static constexpr std::array validProtoMarks{protoMarkLocal, protoMarkTCP, protoMarkTLS}; + + + static constexpr std::string_view localProtoTypeStream{"stream"}; // UNIX domain stream + static constexpr std::string_view localProtoTypeSeqpacket{"seqpacket"}; // UNIX domain seqpacket + static constexpr std::string_view localProtoTypeSerial{"serial"}; // serial (RS232/485) + + static constexpr std::array validLocalProtoTypes{localProtoTypeStream, localProtoTypeSeqpacket, + localProtoTypeSerial}; + + + template + snplib_endpoint_t(const R& ept) + { + fromRange(ept); + } + + snplib_endpoint_t(const snplib_endpoint_t& other) + { + copyInst(other); + } + + snplib_endpoint_t(snplib_endpoint_t&& other) + { + moveInst(std::move(other)); + } + + virtual ~snplib_endpoint_t() = default; + + + snplib_endpoint_t& operator=(const snplib_endpoint_t& other) + { + copyInst(other); + + return *this; + } + + + snplib_endpoint_t& operator=(snplib_endpoint_t&& other) + { + moveInst(std::move(other)); + + return *this; + } + + + template + requires std::ranges::contiguous_range + bool fromRange(const R& ept) + { + _isValid = false; + + // at least 'ws://a' (proto, proto-host delimiter and at least a single character of hostname) + if (std::ranges::size(ept) < 6) { + return _isValid; + } + + if constexpr (std::is_array_v>) { + _endpoint = ept; + } else { + _endpoint.clear(); + std::ranges::copy(ept, std::back_inserter(_endpoint)); + } + + auto found = std::ranges::search(_endpoint, protoHostDelim); + if (found.empty()) { + return _isValid; + } + + + ssize_t idx; + if ((idx = checkProtoMark(std::string_view{_endpoint.begin(), found.begin()})) < 0) { + return _isValid; + } + + _proto = validProtoMarks[idx]; + + _host = std::string_view{found.end(), _endpoint.end()}; + + auto f1 = std::ranges::search(_host, portPathDelim); + // std::string_view port_sv; + if (f1.empty() && isLocal()) { // no path, but it is mandatory for 'local'! + return _isValid; + } else { + _host = std::string_view(_host.begin(), f1.begin()); + + _path = std::string_view(f1.end(), &*_endpoint.end()); + + f1 = std::ranges::search(_host, hostPortDelim); + if (f1.empty() && !isLocal()) { // no port, but it is mandatory for non-local! + return _isValid; + } + + _portView = std::string_view(f1.end(), _host.end()); + if (_portView.size()) { + _host = std::string_view(_host.begin(), f1.begin()); + + if (!isLocal()) { + // convert port string to int + auto end_ptr = _portView.data() + _portView.size(); + + auto [ptr, ec] = std::from_chars(_portView.data(), end_ptr, _port); + if (ec != std::errc() || ptr != end_ptr) { + return _isValid; + } + } else { // ignore for local + _port = -1; + } + } else { + _port = -1; + } + + if (isLocal()) { // check for special values + idx = 0; + if (std::ranges::any_of(validLocalProtoTypes, [&idx, this](const auto& el) { + bool ok = snplib_char_ranges_compare(_host, el, true); + if (!ok) { + ++idx; + } + return ok; + })) { + _host = validLocalProtoTypes[idx]; + } else { + return _isValid; + } + } + } + + _isValid = true; + + return _isValid; + } + + + bool isValid() const + { + return _isValid; + } + + + auto endpoint() const + { + return _endpoint; + } + + template + R proto() const + { + return part(PROTO_PART); + } + + std::string_view proto() const + { + return proto(); + } + + template + R host() const + { + return part(HOST_PART); + } + + std::string_view host() const + { + return host(); + } + + int port() const + { + return _port; + } + + template + R portView() const + { + return part(PORT_PART); + } + + std::string_view portView() const + { + return portView(); + } + + template + R path(RR&& root_path) const + { + if (_path.empty()) { + if constexpr (snplib_output_char_range_c) { + R res; + std::ranges::copy(std::forward(root_path), std::back_inserter(res)); + + return res; + } else { // can't add root path!!! + return part(PATH_PART); + } + } + + auto N = std::ranges::distance(root_path.begin(), root_path.end()); + + if (N) { + R res; + std::filesystem::path pt(root_path.begin(), root_path.end()); + + if (isLocal() && _path[0] == '\0') { + std::ranges::copy(std::string_view(" "), std::back_inserter(res)); + pt /= _path.substr(1); + std::ranges::copy(pt.string(), std::back_inserter(res)); + *res.begin() = '\0'; + } else { + pt /= _path; + std::ranges::copy(pt.string(), std::back_inserter(res)); + } + + return res; + } else { + return part(PATH_PART); + } + } + + template + std::string path(RR&& root_path) const + { + return path(std::forward(root_path)); + } + + template + R path() const + { + return part(PATH_PART); + } + + std::string_view path() const + { + return path(); + } + + + bool isLocal() const + { + return proto() == protoMarkLocal; + } + + bool isLocalStream() const + { + return host() == localProtoTypeStream; + } + + bool isLocalSerial() const + { + return host() == localProtoTypeSerial; + } + + bool isLocalSeqpacket() const + { + return host() == localProtoTypeSeqpacket; + } + + + bool isTCP() const + { + return proto() == protoMarkTCP; + } + + bool isTLS() const + { + return proto() == protoMarkTLS; + } + + + // add '\0' char (or replace special-meaning char/char-sequence) to construct UNIX abstract namespace + // endpoint path + template + snplib_endpoint_t& makeAbstract(const T& mark = nullptr) + requires(snplib_input_char_range_c || std::same_as, char> || + std::is_null_pointer_v>) + { + if (!(isLocalStream() || isLocalSeqpacket())) { // only local proto is valid! + return *this; + } + + if constexpr (std::is_null_pointer_v) { // just insert '\0' + auto it = _endpoint.insert(std::string::const_iterator(_path.begin()), '\0'); + _path = std::string_view(it, _endpoint.end()); + } else if constexpr (std::same_as, char>) { // replace a character (mark) + auto pos = std::distance(_endpoint.cbegin(), std::string::const_iterator(_path.begin())); + if (_endpoint[pos] == mark) { + _endpoint[pos] = '\0'; + } + } else { // replace a character range (mark) + if (std::ranges::equal(_path | std::views::take(std::ranges::size(mark), mark))) { + auto pos = std::distance(_endpoint.cbegin(), std::string::const_iterator(_path.begin())); + _endpoint.replace(pos, std::ranges::size(mark), 1, '\0'); + _path = std::string_view(_endpoint.begin() + pos, _endpoint.end()); + } + } + + return *this; + } + + +protected: + std::string _endpoint; + std::string_view _proto, _host, _path, _portView; + int _port; + bool _isValid; + + + virtual ssize_t checkProtoMark(std::string_view proto_mark) + { + ssize_t idx = 0; + + // case-insensitive look-up + bool found = std::ranges::any_of(snplib_endpoint_t::validProtoMarks, [&idx, &proto_mark](const auto& el) { + bool ok = snplib_char_ranges_compare(proto_mark, el, true); + + if (!ok) { + ++idx; + } + + return ok; + }); + + return found ? idx : -1; + } + + enum EndpointPart { PROTO_PART, HOST_PART, PATH_PART, PORT_PART }; + + template + R part(EndpointPart what) const + { + R res; + + // if (!_isValid) { + // return res; + // } + + auto part = _proto; + + switch (what) { + case PROTO_PART: + part = _proto; + break; + case HOST_PART: + part = _host; + break; + case PATH_PART: + part = _path; + break; + case PORT_PART: + part = _portView; + break; + default: + break; + } + + if constexpr (std::ranges::view) { + return {part.begin(), part.end()}; + } else { + std::ranges::copy(part, std::back_inserter(res)); + } + + return res; + } + + void copyInst(const snplib_endpoint_t& other) + { + if (&other != this) { + if (other._isValid) { + _isValid = other._isValid; + _endpoint = other._endpoint; + _proto = other._proto; + + std::iterator_traits::difference_type idx; + if (other.isLocal()) { // for 'local' host is one of static class constants + _host = other._host; + } else { + idx = std::distance(other._endpoint.c_str(), other._host.data()); + _host = std::string_view(_endpoint.c_str() + idx, other._host.size()); + } + + idx = std::distance(other._endpoint.c_str(), other._path.data()); + _path = std::string_view(_endpoint.c_str() + idx, other._path.size()); + + idx = std::distance(other._endpoint.c_str(), other._portView.data()); + _portView = std::string_view(_endpoint.c_str() + idx, other._portView.size()); + + _port = other._port; + } else { + _isValid = false; + _endpoint = std::string(); + _proto = std::string_view(); + _host = std::string_view(); + _path = std::string_view(); + _portView = std::string_view(); + _port = -1; + } + } + } + + + void moveInst(snplib_endpoint_t&& other) + { + if (&other != this) { + if (other._isValid) { + _isValid = std::move(other._isValid); + _endpoint = std::move(other._endpoint); + _proto = other._proto; + _host = std::move(other._host); + _path = std::move(other._path); + _port = std::move(other._port); + _portView = std::move(other._portView); + } else { + _isValid = false; + _endpoint = std::string(); + _proto = std::string_view(); + _host = std::string_view(); + _path = std::string_view(); + _portView = std::string_view(); + _port = -1; + } + } + } +}; + + +} // end of namespace snplib \ No newline at end of file diff --git a/include/snipplib/utils/snplib_string.h b/include/snipplib/utils/snplib_string.h index 3c0b1fe..78ae587 100644 --- a/include/snipplib/utils/snplib_string.h +++ b/include/snipplib/utils/snplib_string.h @@ -80,6 +80,31 @@ constexpr static std::string_view snplib_trim_spaces_as_view(R&& r, } +template +bool snplib_char_ranges_compare(R1&& what, R2&& where, bool case_insensitive = false) +{ + auto sz1 = std::ranges::distance(what.begin(), what.end()), sz2 = std::ranges::distance(where.begin(), where.end()); + + if (sz1 == sz2) { + for (std::tuple el : std::views::zip(what, where)) { + if (case_insensitive) { + if (std::tolower(std::get<0>(el)) != std::tolower(std::get<1>(el))) { + return false; + } + } else { + if (std::get<0>(el) != std::get<1>(el)) { + return false; + } + } + } + + return true; + } + + return false; +} + + template static std::optional snplib_num_from_range(R&& r)