#pragma once #include /* ABSTRACT DEVICE COMPONENTS LIBRARY */ #include "../common/adc_traits.h" namespace adc { /* * Very simple various protocols endpoint parser and holder class * * endpoint: proto_mark://host_name:port_num/path * where "part" is optional for all protocol kinds; * * for the "local" kind protocol the endpoint string must consists of * only "proto_mark" and "host_name" fields * (e.g.: local://APP_UNIX_SOCKET) * * NOTE: "proto_mark" field is parsed as case-insensitive string! * */ class AdcEndpointParser { 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_TCP, PROTO_ID_TLS, PROTO_ID_UDP, PROTO_ID_WS, PROTO_ID_WSS, 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::string_view protoMarkUDP{"udp"}; // UDP static constexpr std::string_view protoMarkWS{"ws"}; // Websocket static constexpr std::string_view protoMarkWSS{"wss"}; // Secure Websocket static constexpr std::array validProtoMarks{protoMarkLocal, protoMarkTCP, protoMarkTLS, protoMarkUDP, protoMarkWS, protoMarkWSS}; template AdcEndpointParser(const R& ept) { fromRange(ept); } virtual ~AdcEndpointParser() = default; 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 { std::ranges::copy(ept, _endpoint); } auto found = std::ranges::search(_endpoint, protoHostDelim); if (found.empty()) { return _isValid; } _proto = std::string_view{_endpoint.begin(), found.begin()}; // for (auto& valid_proto : validProtoMarks) { // _isValid = _proto == valid_proto; // if (_isValid) { // break; // } // } // if (!_isValid) { // return _isValid; // } // _isValid = false; if (!checkProtoMark(_proto)) { return _isValid; } _host = std::string_view{found.end(), _endpoint.end()}; if (_proto == protoMarkLocal) { // local proto allows only hostname _path = std::string_view(); _port = -1; } else { auto f1 = std::ranges::search(_host, hostPortDelim); std::string_view port_sv; if (f1.empty()) { // no port, but is must be! return _isValid; } else { _host = std::string_view(_host.begin(), f1.begin()); // port_sv = std::string_view(f1.end(), _endpoint.end()); port_sv = std::string_view(f1.end(), &*_endpoint.end()); f1 = std::ranges::search(port_sv, portPathDelim); if (f1.empty()) { _path = std::string_view(); } else { port_sv = std::string_view(port_sv.begin(), f1.begin()); _path = std::string_view(f1.end(), &*_endpoint.end()); } // convert port string to int auto end_ptr = port_sv.data() + port_sv.size(); auto [ptr, ec] = std::from_chars(port_sv.data(), end_ptr, _port); if (ec != std::errc() || ptr != end_ptr) { return _isValid; } } } _isValid = true; return _isValid; } bool isValid() const { return _isValid; } 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 path() const { return part(PATH_PART); } std::string_view path() const { return path(); } bool isLocal() const { return proto() == protoMarkLocal; } bool isTCP() const { return proto() == protoMarkTCP; } bool isTLS() const { return proto() == protoMarkTLS; } bool isUDP() const { return proto() == protoMarkUDP; } bool isWS() const { return proto() == protoMarkWS; } bool isWSS() const { return proto() == protoMarkWSS; } protected: std::string _endpoint; std::string_view _proto, _host, _path; int _port; bool _isValid; virtual bool checkProtoMark(std::string_view proto_mark) { return std::ranges::any_of(AdcEndpointParser::validProtoMarks, [proto_mark](const auto& el) { return el == proto_mark; }); } enum EndpointPart { PROTO_PART, HOST_PART, PATH_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; default: break; } if constexpr (std::ranges::view) { return {part.begin(), part.end()}; } else { std::ranges::copy(part, res); } return res; } }; class AdcEndpoint { 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_TCP, PROTO_ID_TLS, PROTO_ID_UDP, PROTO_ID_WS, PROTO_ID_WSS, 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::string_view protoMarkUDP{"udp"}; // UDP static constexpr std::string_view protoMarkWS{"ws"}; // Websocket static constexpr std::string_view protoMarkWSS{"wss"}; // Secure Websocket static constexpr std::array validProtoMarks = {protoMarkLocal, protoMarkTCP, protoMarkTLS, protoMarkUDP, protoMarkWS, protoMarkWSS}; // factory methods template AdcEndpoint createLocal(R&& path) { return AdcEndpoint(PROTO_ID_LOCAL, std::string_view(""), -1, std::forward(path)); } template AdcEndpoint createTCP(HT&& host, int port) { return AdcEndpoint(PROTO_ID_TCP, std::forward(host), port); } #ifdef USE_OPENSSL_WITH_ASIO template AdcEndpoint createTLS(HT&& host, int port) { return AdcEndpoint(PROTO_ID_TLS, std::forward(host), port); } #endif template AdcEndpoint createUDP(HT&& host, int port) { return AdcEndpoint(PROTO_ID_UDP, std::forward(host), port); } template AdcEndpoint createWS(HT&& host, int port, PTT&& path = PTT()) { return AdcEndpoint(PROTO_ID_WS, std::forward(host), port, std::forward(path)); } template AdcEndpoint createWSS(HT&& host, int port, PTT&& path = PTT()) { return AdcEndpoint(PROTO_ID_WSS, std::forward(host), port, std::forward(path)); } /* Constructors and destructor */ AdcEndpoint() = default; // full-feature constructor template AdcEndpoint(proto_id_t proto_id, HT&& host, int port = -1, PTT&& path = PTT()) : AdcEndpoint() { setEndpoint(proto_id, std::forward(host), port, std::forward(path)); } bool valid() const { // return _isValid; } template R endpoint() const { R r; if (!_isValid) { return r; } if (_protoID == PROTO_ID_LOCAL) { // only proto mark, host and its delimiter std::ranges::copy(endpointView | std::views::take(3) | std::views::join, std::back_inserter(r)); } else { std::ranges::copy(endpointView | std::views::join, std::back_inserter(r)); } return r; } std::string endpoint() const { // return endpoint(); } template R proto() const { if (!_isValid) { return R(); } const auto& proto = validProtoMarks[_protoID]; return R{proto.begin(), proto.end()}; } std::string_view proto() const { if (!_isValid) { return std::string_view(); } return validProtoMarks[_protoID]; } template R host() const { R r; if (!_isValid) { return r; } std::ranges::copy(_host, std::back_inserter(r)); return r; } template R hostView() const { if (!_isValid) { return R(); } return R{_host.begin(), _host.end()}; } std::string host() const { // return _host; } std::string_view hostView() const { // return std::string_view{_host.begin(), _host.end()}; } int port() const { // return _isValid ? _port : -1; } template R path() const { R r; if (!_isValid) { return r; } std::ranges::copy(_path, std::back_inserter(r)); return r; } template R pathView() const { if (!_isValid) { return R(); } return R{_path.begin(), _path.end()}; } std::string path() const { // return _isValid ? _path : ""; } std::string_view pathView() const { // return _isValid ? std::string_view{_path.begin(), _path.end()} : std::string_view(); } template bool setEndpoint(proto_id_t proto_id, const HT& host, int port, const PTT& path) { if (!std::distance(std::begin(host), std::end(host))) { return false; } switch (proto_id) { case PROTO_ID_LOCAL: // ignore path (always "") and port (just skip) _port = -1; _path.clear(); break; case PROTO_ID_TCP: case PROTO_ID_TLS: case PROTO_ID_UDP: case PROTO_ID_WS: case PROTO_ID_WSS: if (port <= 0) { return false; } _port = port; _path.clear(); std::ranges::copy(path, std::back_inserter(_path)); break; default: break; } _isValid = true; _protoID = proto_id; _host.clear(); std::ranges::copy(host, std::back_inserter(_host)); updateEndpointView(); return true; } template bool setEndpoint(char (&r)[N]) { return setEndpoint(std::string_view(r)); } template bool setEndpoint(const R& r) { auto found = std::ranges::search(r, protoHostDelim); if (found.empty()) { return false; } if (found.end() == r.end()) { // no host, only delimiter!!! return false; } auto sz = std::distance(r.begin(), found.begin()); std::string proto; // case-insensitive! std::ranges::copy( r | std::views::take(sz) | std::views::transform([](auto ch) -> char { return std::tolower(ch); }), std::back_inserter(proto)); std::underlying_type_t i = 0; proto_id_t id = PROTO_ID_UNKNOWN; for (const auto& el : validProtoMarks) { if (std::ranges::equal(el, proto)) { id = static_cast(i); break; } ++i; } if (id == PROTO_ID_UNKNOWN) { return false; } if (id == PROTO_ID_LOCAL) { // remaining part is host _protoID = id; _host.clear(); std::ranges::copy(r | std::views::drop(sz), std::back_inserter(_host)); _path.clear(); _port = -1; _isValid = true; return true; } sz += proto.size(); found = std::ranges::search(r | std::views::drop(sz), hostPortDelim); if (found.empty()) { // no host-to-port delimiter return false; } if (found.end() == r.end()) { // the delimiter is at the end!!! return false; } auto pos = std::distance(r.begin(), found.begin()); if (pos == sz) { // empty host field return false; } std::string host; std::ranges::copy(r | std::views::drop(sz) | std::views::take(pos - sz), std::back_inserter(host)); sz = pos + hostPortDelim.size(); found = std::ranges::search(r | std::views::drop(sz), portPathDelim); pos = std::distance(r.begin(), found.begin()); std::string port_str; std::ranges::copy(r | std::views::drop(sz) | std::views::take(pos - sz), std::back_inserter(port_str)); int port; auto end_ptr = port_str.data() + port_str.size(); auto [ptr, ec] = std::from_chars(port_str.data(), end_ptr, port); if (ec != std::errc() || ptr != end_ptr) { return false; } sz = pos + portPathDelim.size(); _path.clear(); std::ranges::copy(r | std::views::drop(sz), std::back_inserter(_path)); _protoID = id; _host = host; _port = port; _isValid = true; updateEndpointView(); return true; } protected: bool _isValid{false}; proto_id_t _protoID; std::string _host, _path, _portStr; int _port; std::array endpointView{protoMarkLocal, protoHostDelim, "localhost", hostPortDelim, "7777", portPathDelim, ""}; void updateEndpointView() { endpointView[0] = validProtoMarks[_protoID]; endpointView[2] = _host.c_str(); _portStr = std::to_string(_port); endpointView[4] = _port == -1 ? "" : _portStr.c_str(); endpointView[6] = _path.c_str(); } }; } // namespace adc