#pragma once #include #include #include #include #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() {} template asio::awaitable listen(std::derived_from auto endpoint, CtorArgTs&&... ctor_args) { bool exit_flag = false; if (!endpoint.isValid()) { _serverLogger->error("Cannot start listening! Invalid endpoint string representation ('{}')!", endpoint.endpoint()); co_return exit_flag; } // add root path to endpoint one std::filesystem::path pt("/"); pt += endpoint.path(); auto args = std::make_tuple(std::forward(ctor_args)...); if (endpoint.isLocalSerial()) { asio::serial_port s_port(_asioContext); std::error_code ec; if constexpr (sizeof...(CtorArgTs)) { // options setSerialOpts(s_port, std::forward(ctor_args)...); } s_port.open(pt.string(), ec); if (ec) { _serverLogger->error("Cannot open serial device '{}' (Error = '{}')!", pt.string(), ec.message()); co_return exit_flag; } // asio::co_spawn(_asioContext, listen(std::move(s_port)), asio::detached); co_return listen(std::move(s_port)); } else if (endpoint.isLocal()) { // create abstract namespace socket endpoint if its path starts from '@' symbol endpoint.makeAbstract('@'); if (endpoint.isLocalStream()) { co_return listen(asio::local::stream_protocol::endpoint(pt.string())); // asio::co_spawn(_asioContext, listen(asio::local::stream_protocol::endpoint(pt.string())), // asio::detached); } else if (endpoint.isLocalSeqpacket()) { co_return listen(asio::local::seq_packet_protocol::endpoint(pt.string())); // asio::co_spawn(_asioContext, listen(asio::local::seq_packet_protocol::endpoint(pt.string())), // asio::detached); } else { co_return exit_flag; // ???!!!! } } else if (endpoint.isTCP()) { // resolve hostname try { asio::ip::tcp::resolver res(_asioContext); auto r_result = co_await res.async_resolve(endpoint.host(), endpoint.portView(), asio::deferred); for (auto const& epn : r_result) { exit_flag = co_await listen(epn); if (exit_flag) { break; } } if (!exit_flag) { _serverLogger->error("Cannot start listening on any resolved endpoints!"); co_return exit_flag; } // listen(std::move(*r_result.begin())); } catch (const std::system_error& err) { _serverLogger->error("An error occured while resolving '{}' hostname (Error = '{}')", endpoint.host(), err.code().message()); co_return exit_flag; } } co_return true; } asio::awaitable 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! Do not start waiting for commands!"); co_return false; } } _serialPorts.emplace_back(std::move(endpoint)); } else if constexpr (traits::is_tcp_proto || traits::is_local_stream_proto || traits::is_local_seqpack_proto) { try { std::stringstream st; auto acc = epn_t::protocol_type::acceptor(_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()); } } else { static_assert(false, "INVALID ENDPOINT!!!"); } co_return true; } // 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; asio::awaitable startSession() {} // helpers template void setSerialOpts(asio::serial_port& s_port, OptT&& opt, OptTs&&... opts) { std::error_code ec; s_port.set_option(opt, ec); if (ec) { std::string_view opt_name; if constexpr (std::same_as) { opt_name = "baud rate"; } else if constexpr (std::same_as) { opt_name = "parity"; } else if constexpr (std::same_as) { opt_name = "flow control"; } else if constexpr (std::same_as) { opt_name = "stop bits"; } else if constexpr (std::same_as) { opt_name = "char size"; } _serverLogger->error("Cannot set serial port '{}' option! Just skip!", opt_name); } if constexpr (sizeof...(OptTs)) { setSerialOpts(s_port, std::forward(opts)...); } } }; } // namespace mcc