749 lines
19 KiB
C++
749 lines
19 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
|
|
#include "../common/adc_utils.h"
|
|
|
|
|
|
/*
|
|
|
|
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 non-local protocol kinds;
|
|
*
|
|
* for local kind of protocols the endpoint must be given as:
|
|
* local://stream/PATH
|
|
* local://datagram/PATH
|
|
* local://seqpacket/PATH
|
|
* where 'stream', 'datagram' and 'seqpacket' "host_name"-field marks the
|
|
* stream-type, datagram-type and seqpacket-type UNIX domain sockets protocols.
|
|
* 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!
|
|
*
|
|
*
|
|
*/
|
|
|
|
|
|
class AdcEndpointParser
|
|
{
|
|
typedef std::span<char> host_part_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_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};
|
|
|
|
|
|
static constexpr std::string_view localProtoTypeStream{"stream"}; // UNIX domain stream
|
|
static constexpr std::string_view localProtoTypeDatagram{"datagram"}; // UNIX domain datagram
|
|
static constexpr std::string_view localProtoTypeSeqpacket{"seqpacket"}; // UNIX domain seqpacket
|
|
|
|
static constexpr std::array validLocalProtoTypes{localProtoTypeStream, localProtoTypeDatagram,
|
|
localProtoTypeSeqpacket};
|
|
|
|
|
|
template <traits::adc_input_char_range R>
|
|
AdcEndpointParser(const R& ept)
|
|
{
|
|
fromRange(ept);
|
|
}
|
|
|
|
|
|
virtual ~AdcEndpointParser() = default;
|
|
|
|
template <traits::adc_input_char_range R>
|
|
requires std::ranges::contiguous_range<R>
|
|
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<std::remove_cvref_t<R>>) {
|
|
_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()};
|
|
_host = host_part_t{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());
|
|
_host = host_part_t{found.end(), _endpoint.end()};
|
|
|
|
// _path = std::string_view(f1.end(), &*_endpoint.end());
|
|
_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;
|
|
}
|
|
|
|
port_sv = std::string_view(f1.end(), _host.end());
|
|
if (port_sv.size()) {
|
|
// _host = std::string_view(_host.begin(), f1.begin());
|
|
_host = host_part_t{found.end(), _endpoint.end()};
|
|
|
|
if (!isLocal()) {
|
|
// 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;
|
|
}
|
|
} 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::AdcCharRangeCompare(_host, el, true);
|
|
if (!ok) {
|
|
++idx;
|
|
}
|
|
return ok;
|
|
})) {
|
|
// _host = validLocalProtoTypes[idx];
|
|
} else {
|
|
return _isValid;
|
|
}
|
|
}
|
|
}
|
|
|
|
_isValid = true;
|
|
|
|
return _isValid;
|
|
}
|
|
|
|
|
|
bool isValid() const
|
|
{
|
|
return _isValid;
|
|
}
|
|
|
|
template <traits::adc_view_or_output_char_range R>
|
|
R proto() const
|
|
{
|
|
return part<R>(PROTO_PART);
|
|
}
|
|
|
|
std::string_view proto() const
|
|
{
|
|
return proto<std::string_view>();
|
|
}
|
|
|
|
template <traits::adc_view_or_output_char_range R>
|
|
R host() const
|
|
{
|
|
return part<R>(HOST_PART);
|
|
}
|
|
|
|
std::string_view host() const
|
|
{
|
|
return host<std::string_view>();
|
|
}
|
|
|
|
int port() const
|
|
{
|
|
return _port;
|
|
}
|
|
|
|
template <traits::adc_view_or_output_char_range R>
|
|
R path() const
|
|
{
|
|
return part<R>(PATH_PART);
|
|
}
|
|
|
|
std::string_view path() const
|
|
{
|
|
return path<std::string_view>();
|
|
}
|
|
|
|
|
|
bool isLocal() const
|
|
{
|
|
return proto() == protoMarkLocal;
|
|
}
|
|
|
|
bool isLocalStream() const
|
|
{
|
|
// return host() == localProtoTypeStream;
|
|
return utils::AdcCharRangeCompare(host(), localProtoTypeStream, true);
|
|
}
|
|
|
|
bool isLocalDatagram() const
|
|
{
|
|
// return host() == localProtoTypeDatagram;
|
|
return utils::AdcCharRangeCompare(host(), localProtoTypeDatagram, true);
|
|
}
|
|
|
|
bool isLocalSeqpacket() const
|
|
{
|
|
// return host() == localProtoTypeSeqpacket;
|
|
return utils::AdcCharRangeCompare(host(), localProtoTypeSeqpacket, true);
|
|
}
|
|
|
|
|
|
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;
|
|
std::string_view _proto, _path;
|
|
host_part_t _host;
|
|
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(AdcEndpointParser::validProtoMarks, [&idx, &proto_mark, this](const auto& el) {
|
|
bool ok = utils::AdcCharRangeCompare(proto_mark, el, true);
|
|
|
|
if (!ok) {
|
|
++idx;
|
|
}
|
|
|
|
return ok;
|
|
});
|
|
|
|
return found ? idx : -1;
|
|
}
|
|
|
|
enum EndpointPart { PROTO_PART, HOST_PART, PATH_PART };
|
|
|
|
template <traits::adc_view_or_output_char_range R>
|
|
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<R>) {
|
|
// return R(part.begin(), part.end());
|
|
// } else {
|
|
// std::ranges::copy(part, std::back_inserter(res));
|
|
// }
|
|
|
|
switch (what) {
|
|
case PROTO_PART:
|
|
if constexpr (std::ranges::view<R>) {
|
|
res = R(_proto.begin(), _proto.size());
|
|
} else {
|
|
std::ranges::copy(_proto, std::back_inserter(res));
|
|
}
|
|
break;
|
|
case HOST_PART:
|
|
if constexpr (std::ranges::view<R>) {
|
|
res = R(_host.begin(), _host.end());
|
|
} else {
|
|
std::ranges::copy(_host, std::back_inserter(res));
|
|
}
|
|
break;
|
|
case PATH_PART:
|
|
if constexpr (std::ranges::view<R>) {
|
|
res = R(_path.begin(), _path.end());
|
|
} else {
|
|
std::ranges::copy(_path, std::back_inserter(res));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
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 <traits::adc_input_char_range R>
|
|
AdcEndpoint createLocal(R&& path)
|
|
{
|
|
return AdcEndpoint(PROTO_ID_LOCAL, std::string_view(""), -1, std::forward<R>(path));
|
|
}
|
|
|
|
template <traits::adc_input_char_range HT>
|
|
AdcEndpoint createTCP(HT&& host, int port)
|
|
{
|
|
return AdcEndpoint(PROTO_ID_TCP, std::forward<HT>(host), port);
|
|
}
|
|
|
|
#ifdef USE_OPENSSL_WITH_ASIO
|
|
template <traits::adc_input_char_range HT>
|
|
AdcEndpoint createTLS(HT&& host, int port)
|
|
{
|
|
return AdcEndpoint(PROTO_ID_TLS, std::forward<HT>(host), port);
|
|
}
|
|
#endif
|
|
|
|
template <traits::adc_input_char_range HT>
|
|
AdcEndpoint createUDP(HT&& host, int port)
|
|
{
|
|
return AdcEndpoint(PROTO_ID_UDP, std::forward<HT>(host), port);
|
|
}
|
|
|
|
|
|
template <traits::adc_input_char_range HT, traits::adc_input_char_range PTT = std::string_view>
|
|
AdcEndpoint createWS(HT&& host, int port, PTT&& path = PTT())
|
|
{
|
|
return AdcEndpoint(PROTO_ID_WS, std::forward<HT>(host), port, std::forward<PTT>(path));
|
|
}
|
|
|
|
|
|
template <traits::adc_input_char_range HT, traits::adc_input_char_range PTT = std::string_view>
|
|
AdcEndpoint createWSS(HT&& host, int port, PTT&& path = PTT())
|
|
{
|
|
return AdcEndpoint(PROTO_ID_WSS, std::forward<HT>(host), port, std::forward<PTT>(path));
|
|
}
|
|
|
|
|
|
/* Constructors and destructor */
|
|
|
|
AdcEndpoint() = default;
|
|
|
|
// full-feature constructor
|
|
template <traits::adc_input_char_range HT, traits::adc_input_char_range PTT = std::string_view>
|
|
AdcEndpoint(proto_id_t proto_id, HT&& host, int port = -1, PTT&& path = PTT()) : AdcEndpoint()
|
|
{
|
|
setEndpoint(proto_id, std::forward<HT>(host), port, std::forward<PTT>(path));
|
|
}
|
|
|
|
|
|
bool valid() const
|
|
{
|
|
//
|
|
return _isValid;
|
|
}
|
|
|
|
|
|
template <traits::adc_output_char_range R>
|
|
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<std::string>();
|
|
}
|
|
|
|
|
|
template <traits::adc_char_view R>
|
|
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 <traits::adc_output_char_range R>
|
|
R host() const
|
|
{
|
|
R r;
|
|
if (!_isValid) {
|
|
return r;
|
|
}
|
|
|
|
std::ranges::copy(_host, std::back_inserter(r));
|
|
|
|
return r;
|
|
}
|
|
|
|
template <traits::adc_char_view R>
|
|
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 <traits::adc_output_char_range R>
|
|
R path() const
|
|
{
|
|
R r;
|
|
if (!_isValid) {
|
|
return r;
|
|
}
|
|
|
|
std::ranges::copy(_path, std::back_inserter(r));
|
|
|
|
return r;
|
|
}
|
|
|
|
template <traits::adc_char_view R>
|
|
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 <traits::adc_input_char_range HT, traits::adc_input_char_range PTT>
|
|
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 <size_t N>
|
|
bool setEndpoint(char (&r)[N])
|
|
{
|
|
return setEndpoint(std::string_view(r));
|
|
}
|
|
|
|
template <traits::adc_input_char_range R>
|
|
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<proto_id_t> i = 0;
|
|
proto_id_t id = PROTO_ID_UNKNOWN;
|
|
|
|
for (const auto& el : validProtoMarks) {
|
|
if (std::ranges::equal(el, proto)) {
|
|
id = static_cast<proto_id_t>(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<std::string_view, 7> 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
|