diff --git a/cxx/comm_server.h b/cxx/comm_server.h new file mode 100644 index 0000000..83adfa6 --- /dev/null +++ b/cxx/comm_server.h @@ -0,0 +1,211 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "comm_server_endpoint.h" +#include "mount.h" + +namespace mcc +{ + + +namespace traits +{ + +template +concept mcc_endpoint_c = std::derived_from || std::derived_from || + std::derived_from || + std::derived_from; + +template +static constexpr bool is_serial_proto = std::derived_from; + +template +static constexpr bool is_tcp_proto = std::derived_from; + +template +static constexpr bool is_local_stream_proto = std::derived_from; + +template +static constexpr bool is_local_seqpack_proto = std::derived_from; + +} // namespace traits + + +class MccMountServer +{ +public: + MccMountServer(asio::io_context& ctx, std::shared_ptr logger) + : _asioContext(ctx), _serverLogger(std::move(logger)) + { + } + + ~MccMountServer() {} + + void listen(traits::mcc_endpoint_c auto endpoint) + { + using epn_t = std::decay_t; + + std::error_code ec; + + if constexpr (traits::is_serial_proto) { + // first, check if port is openned + if (!endpoint.is_open()) { + if (ec) { + // ?????????? + _serverLogger->error("Serial port was not open!"); + return; + } + } + + _serialPorts.emplace_back(std::move(endpoint)); + + } else if constexpr (traits::is_tcp_proto) { + } else if constexpr (traits::is_local_stream_proto) { + } else if constexpr (traits::is_local_seqpack_proto) { + } else { + static_assert(false, "INVALID ENDPOINT!!!"); + } + } + + + // close listening on all endpoints + void stop() + { + std::error_code ec; + size_t N = 0, M = 0; + + _serverLogger->info("Close all listening endpoints ..."); + + auto num = + _serialPorts.size() + _tcpAcceptors.size() + _localStreamAcceptors.size() + _localSeqpackAcceptors.size(); + if (!num) { + _serverLogger->info("There are no listening ports/sockets!"); + return; + } + + if (_serialPorts.size()) { + _serverLogger->debug("Close serial ports ..."); + + for (auto& s_port : _serialPorts) { + s_port.close(ec); + if (ec) { + _serverLogger->error("Cannot close serial port! ec = '{}'", ec.message()); + ++M; + } + ++N; + } + + _serverLogger->debug("{} from {} serial ports were closed!", M, N); + + _serialPorts.clear(); + } + + if (_tcpAcceptors.size()) { + _serverLogger->debug("Close TCP listening sockets ..."); + N = 0; + M = 0; + + for (auto& acc : _tcpAcceptors) { + acc.close(ec); + if (ec) { + _serverLogger->error("Cannot close TCP socket! ec = '{}'", ec.message()); + ++M; + } + ++N; + } + + _serverLogger->debug("{} from {} TCP sockets were closed!", M, N); + + _tcpAcceptors.clear(); + } + + if (_localStreamAcceptors.size()) { + _serverLogger->debug("Close local stream listening sockets ..."); + N = 0; + M = 0; + + for (auto& acc : _localStreamAcceptors) { + acc.close(ec); + if (ec) { + _serverLogger->error("Cannot close local stream socket! ec = '{}'", ec.message()); + ++M; + } + ++N; + } + + _serverLogger->debug("{} from {} local stream sockets were closed!", M, N); + + _localStreamAcceptors.clear(); + } + + if (_localSeqpackAcceptors.size()) { + _serverLogger->debug("Close local seqpack listening sockets ..."); + N = 0; + M = 0; + + for (auto& acc : _localSeqpackAcceptors) { + acc.close(ec); + if (ec) { + _serverLogger->error("Cannot close local seqpack socket! ec = '{}'", ec.message()); + ++M; + } + ++N; + } + + _serverLogger->debug("{} from {} local seqpack sockets were closed!", M, N); + + _localSeqpackAcceptors.clear(); + } + + _serverLogger->info("The all server listening endpoints were closed!"); + } + +private: + asio::io_context& _asioContext; + std::shared_ptr _serverLogger; + + std::vector _serialPorts; + std::vector _tcpAcceptors; + std::vector _localStreamAcceptors; + std::vector _localSeqpackAcceptors; + + void startAccept(traits::mcc_endpoint_c auto endpoint) + { + using epn_t = std::decay_t; + + try { + std::stringstream st; + auto acc = epn_t::protocol_type::endpoint(_asioContext, endpoint); + st << acc.local_endpoint(); + _serverLogger->info("Try to start listening at <{}> endpoint ...", st.str()); + + if constexpr (traits::is_tcp_proto) { + _tcpAcceptors.emplace_back(std::move(acc)); + } else if constexpr (traits::is_local_stream_proto) { + _localStreamAcceptors.emplace_back(std::move(acc)); + } else if constexpr (traits::is_local_seqpack_proto) { + _localSeqpackAcceptors.emplace_back(std::move(acc)); + } else { + static_assert(false, "INVALID ENDPOINT!!!"); + } + + + } catch (const std::system_error& err) { + _serverLogger->error("An error occured while creating of connection acceptor! ec = '{}'", err.what()); + } + } + + template + void doAccept() + { + } +}; + +} // namespace mcc diff --git a/cxx/comm_server_endpoint.h b/cxx/comm_server_endpoint.h new file mode 100644 index 0000000..8fd50be --- /dev/null +++ b/cxx/comm_server_endpoint.h @@ -0,0 +1,476 @@ +#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()); + + _port = other._port; + } else { + _isValid = false; + _endpoint = std::string(); + _proto = std::string_view(); + _host = std::string_view(); + _path = 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); + } else { + _isValid = false; + _endpoint = std::string(); + _proto = std::string_view(); + _host = std::string_view(); + _path = std::string_view(); + _port = -1; + } + } + } +}; + +} // namespace mcc