ADC/net/adc_endpoint.h
2024-10-08 17:49:48 +03:00

487 lines
12 KiB
C++

#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 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 <traits::adc_input_char_range R>
AdcEndpointParser(const R& ept)
{
}
protected:
std::string _proto, _host, _path;
int port;
template <traits::adc_input_char_range R>
bool parse(const R& ept)
{
// at least 'ws://a' (proto, proto-host delimiter and at least a single character of hostname)
if (std::ranges::size(ept) < 6) {
return false;
}
auto found = std::ranges::search(ept, protoHostDelim);
if (found.empty()) {
return false;
}
bool ok = false;
std::string_view proto{ept.begin(), found.begin()};
for (auto& valid_proto : validProtoMarks) {
ok = proto == valid_proto;
if (ok) {
break;
}
}
if (!ok) {
return ok;
}
_proto.clear();
std::ranges::copy(proto, std::back_inserter(_proto));
}
};
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