#pragma once #include #include #include #include #include #include namespace mcc { namespace traits { template concept mcc_char_view = std::ranges::view && std::same_as, char>; // input range of char/const char template concept mcc_input_char_range = std::ranges::input_range && std::is_same_v>, CharT>; // output range of char/const char template concept mcc_output_char_range = std::ranges::output_range && std::same_as>, CharT>; template concept mcc_view_or_output_char_range = mcc_char_view || mcc_output_char_range; } // namespace traits namespace utils { static constexpr bool MccCharRangeCompare(const traits::mcc_char_view auto& what, const traits::mcc_char_view auto& where, bool case_insensitive = false) { if (std::ranges::size(what) == std::ranges::size(where)) { if (case_insensitive) { auto f = std::ranges::search(where, std::views::transform(what, [](const char& ch) { return std::tolower(ch); })); return !f.empty(); } else { auto f = std::ranges::search(where, what); return !f.empty(); } } return false; } } // namespace utils /* * Very simple various protocols endpoint parser and holder class * * endpoint: proto_mark://host_name:port_num/path * where "part" 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 * * */ class MccServerEndpoint { 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 MccServerEndpoint(const R& ept) { fromRange(ept); } MccServerEndpoint(const MccServerEndpoint& other) { copyInst(other); } MccServerEndpoint(MccServerEndpoint&& other) { moveInst(std::move(other)); } virtual ~MccServerEndpoint() = default; MccServerEndpoint& operator=(const MccServerEndpoint& other) { copyInst(other); return *this; } MccServerEndpoint& operator=(MccServerEndpoint&& 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 = utils::MccCharRangeCompare(_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() 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 MccServerEndpoint& makeAbstract(const T& mark = nullptr) requires(traits::mcc_input_char_range || 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(MccServerEndpoint::validProtoMarks, [&idx, &proto_mark](const auto& el) { bool ok = utils::MccCharRangeCompare(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 MccServerEndpoint& other) { if (&other != this) { if (other._isValid) { _isValid = other._isValid; _endpoint = other._endpoint; _proto = other._proto; auto 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(MccServerEndpoint&& 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; } } } }; } // namespace mcc