321 lines
11 KiB
C++
321 lines
11 KiB
C++
#pragma once
|
|
|
|
#include <filesystem>
|
|
|
|
#include <asio/awaitable.hpp>
|
|
#include <asio/co_spawn.hpp>
|
|
#include <asio/deferred.hpp>
|
|
#include <asio/detached.hpp>
|
|
#include <asio/ip/tcp.hpp>
|
|
#include <asio/local/seq_packet_protocol.hpp>
|
|
#include <asio/local/stream_protocol.hpp>
|
|
#include <asio/serial_port.hpp>
|
|
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include "comm_server_endpoint.h"
|
|
#include "mount.h"
|
|
|
|
namespace mcc
|
|
{
|
|
|
|
|
|
namespace traits
|
|
{
|
|
|
|
template <typename T>
|
|
concept mcc_endpoint_c = std::derived_from<T, asio::serial_port> || std::derived_from<T, asio::ip::tcp::endpoint> ||
|
|
std::derived_from<T, asio::local::stream_protocol::endpoint> ||
|
|
std::derived_from<T, asio::local::seq_packet_protocol::endpoint>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_serial_proto = std::derived_from<T, asio::serial_port>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_tcp_proto = std::derived_from<T, asio::ip::tcp::endpoint>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_local_stream_proto = std::derived_from<T, asio::local::stream_protocol::endpoint>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_local_seqpack_proto = std::derived_from<T, asio::local::seq_packet_protocol::endpoint>;
|
|
|
|
} // namespace traits
|
|
|
|
|
|
class MccMountServer
|
|
{
|
|
public:
|
|
MccMountServer(asio::io_context& ctx, std::shared_ptr<spdlog::logger> logger)
|
|
: _asioContext(ctx), _serverLogger(std::move(logger))
|
|
{
|
|
}
|
|
|
|
~MccMountServer() {}
|
|
|
|
|
|
template <typename... CtorArgTs>
|
|
asio::awaitable<bool> listen(std::derived_from<MccServerEndpoint> 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<CtorArgTs>(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<CtorArgTs>(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<bool> listen(traits::mcc_endpoint_c auto endpoint)
|
|
{
|
|
using epn_t = std::decay_t<decltype(endpoint)>;
|
|
|
|
std::error_code ec;
|
|
|
|
if constexpr (traits::is_serial_proto<epn_t>) {
|
|
// 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<epn_t> || traits::is_local_stream_proto<epn_t> ||
|
|
traits::is_local_seqpack_proto<epn_t>) {
|
|
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<epn_t>) {
|
|
_tcpAcceptors.emplace_back(std::move(acc));
|
|
} else if constexpr (traits::is_local_stream_proto<epn_t>) {
|
|
_localStreamAcceptors.emplace_back(std::move(acc));
|
|
} else if constexpr (traits::is_local_seqpack_proto<epn_t>) {
|
|
_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<spdlog::logger> _serverLogger;
|
|
|
|
std::vector<asio::serial_port> _serialPorts;
|
|
std::vector<asio::ip::tcp::acceptor> _tcpAcceptors;
|
|
std::vector<asio::local::stream_protocol::acceptor> _localStreamAcceptors;
|
|
std::vector<asio::local::seq_packet_protocol::acceptor> _localSeqpackAcceptors;
|
|
|
|
|
|
asio::awaitable<void> startSession() {}
|
|
|
|
// helpers
|
|
template <typename OptT, typename... OptTs>
|
|
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<OptT, asio::serial_port::baud_rate>) {
|
|
opt_name = "baud rate";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::parity>) {
|
|
opt_name = "parity";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::flow_control>) {
|
|
opt_name = "flow control";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::stop_bits>) {
|
|
opt_name = "stop bits";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::character_size>) {
|
|
opt_name = "char size";
|
|
}
|
|
|
|
_serverLogger->error("Cannot set serial port '{}' option! Just skip!", opt_name);
|
|
}
|
|
|
|
if constexpr (sizeof...(OptTs)) {
|
|
setSerialOpts(s_port, std::forward<OptTs>(opts)...);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace mcc
|