#pragma once /* 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 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