...
This commit is contained in:
parent
fedc324410
commit
b1a48d2b77
@ -52,6 +52,7 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
|
||||
logInfo(" IERS Bulletin A filename: {}", _mountConfig.bulletinAFilename);
|
||||
|
||||
logInfo("");
|
||||
logDebug("Delete previously defined prohobited zones");
|
||||
clearPZones();
|
||||
logInfo("Add prohibited zones ...");
|
||||
|
||||
@ -104,9 +105,16 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
|
||||
logInfo(" YPIDV: [P: {}, I: {}, D: {}]", _mountConfig.servoControllerConfig.devConfig.YPIDV.P,
|
||||
_mountConfig.servoControllerConfig.devConfig.YPIDV.I, _mountConfig.servoControllerConfig.devConfig.YPIDV.D);
|
||||
|
||||
auto hw_err = hardwareInit();
|
||||
// auto hw_err = hardwareInit();
|
||||
// if (hw_err) {
|
||||
// errorLogging("", hw_err);
|
||||
// }
|
||||
|
||||
// call base class initMount method
|
||||
auto hw_err = base_gm_class_t::initMount();
|
||||
if (hw_err) {
|
||||
errorLogging("", hw_err);
|
||||
return hw_err;
|
||||
}
|
||||
|
||||
return mcc::MccGenericMountErrorCode::ERROR_OK;
|
||||
|
||||
783
asibfm700/asibfm700_netserver.h
Normal file
783
asibfm700/asibfm700_netserver.h
Normal file
@ -0,0 +1,783 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include <asio/awaitable.hpp>
|
||||
#include <asio/co_spawn.hpp>
|
||||
#include <asio/deferred.hpp>
|
||||
#include <asio/detached.hpp>
|
||||
#include <asio/experimental/awaitable_operators.hpp>
|
||||
#include <asio/ip/tcp.hpp>
|
||||
#include <asio/local/seq_packet_protocol.hpp>
|
||||
#include <asio/local/stream_protocol.hpp>
|
||||
#include <asio/read.hpp>
|
||||
#include <asio/redirect_error.hpp>
|
||||
#include <asio/serial_port.hpp>
|
||||
#include <asio/signal_set.hpp>
|
||||
#include <asio/steady_timer.hpp>
|
||||
#include <asio/streambuf.hpp>
|
||||
#include <asio/write.hpp>
|
||||
|
||||
#include <spdlog/sinks/null_sink.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#if __has_include(<unistd.h>) // POSIX
|
||||
#define FORK_EXISTS 1
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#include "asibfm700_netserver_endpoint.h"
|
||||
#include "control_proto.h"
|
||||
#include "mcc_traits.h"
|
||||
// #include "mount.h"
|
||||
|
||||
namespace asibfm700
|
||||
{
|
||||
|
||||
|
||||
namespace traits
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
concept netserver_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<typename T::protocol_type, asio::ip::tcp>;
|
||||
|
||||
// template <typename T>
|
||||
// static constexpr bool is_local_stream_proto =
|
||||
// std::derived_from<typename T::protocol_type, asio::local::stream_protocol>;
|
||||
|
||||
// template <typename T>
|
||||
// static constexpr bool is_local_seqpack_proto =
|
||||
// std::derived_from<typename T::protocol_type, asio::local::seq_packet_protocol>;
|
||||
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool is_tcp_proto =
|
||||
std::derived_from<T, asio::ip::tcp::endpoint> || std::derived_from<T, asio::ip::tcp::socket>;
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool is_local_stream_proto = std::derived_from<T, asio::local::stream_protocol::endpoint> ||
|
||||
std::derived_from<T, asio::local::stream_protocol::socket>;
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool is_local_seqpack_proto = std::derived_from<T, asio::local::seq_packet_protocol::endpoint> ||
|
||||
std::derived_from<T, asio::local::seq_packet_protocol::socket>;
|
||||
|
||||
|
||||
} // namespace traits
|
||||
|
||||
|
||||
class MccMountServer
|
||||
{
|
||||
public:
|
||||
static constexpr std::chrono::duration DEFAULT_RCV_TIMEOUT = std::chrono::hours(12);
|
||||
static constexpr std::chrono::duration DEFAULT_SND_TIMEOUT = std::chrono::milliseconds(2000);
|
||||
|
||||
MccMountServer(asio::io_context& ctx, std::shared_ptr<spdlog::logger> logger = spdlog::null_logger_mt("NULL"))
|
||||
: _asioContext(ctx), _serverLogger(logger), _stopSignal(ctx), _restartSignal(ctx)
|
||||
{
|
||||
std::stringstream st;
|
||||
st << std::this_thread::get_id();
|
||||
|
||||
_serverLogger->set_pattern("[%Y-%m-%d %T.%e][%l]: %v");
|
||||
|
||||
_serverLogger->info("Create mount server instance (thread ID = {})", st.str());
|
||||
}
|
||||
|
||||
~MccMountServer()
|
||||
{
|
||||
std::stringstream st;
|
||||
st << std::this_thread::get_id();
|
||||
|
||||
_serverLogger->info("Delete mount server instance (thread ID = {}) ...", st.str());
|
||||
|
||||
stopListening();
|
||||
disconnectClients();
|
||||
}
|
||||
|
||||
|
||||
template <typename... CtorArgTs>
|
||||
asio::awaitable<void> listen(std::derived_from<Asibfm700NetserverEndpoint> auto endpoint, CtorArgTs&&... ctor_args)
|
||||
{
|
||||
if (!endpoint.isValid()) {
|
||||
_serverLogger->error("Cannot start listening! Invalid endpoint string representation ('{}')!",
|
||||
endpoint.endpoint());
|
||||
co_return;
|
||||
}
|
||||
|
||||
// add root path to endpoint one
|
||||
std::filesystem::path pt("/");
|
||||
|
||||
|
||||
if (endpoint.isLocalSerial()) {
|
||||
pt += endpoint.path();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// asio::co_spawn(_asioContext, listen(std::move(s_port)), asio::detached);
|
||||
co_await listen(std::move(s_port));
|
||||
} else if (endpoint.isLocal()) {
|
||||
// create abstract namespace socket endpoint if its path starts from '@' symbol
|
||||
endpoint.makeAbstract('@');
|
||||
|
||||
// if (endpoint.path()[0] == '\0') { // abstract namespace
|
||||
// std::string p;
|
||||
// std::ranges::copy(endpoint.path(), std::back_inserter(p));
|
||||
// p.insert(p.begin() + 1, '/'); // insert after '\0' symbol
|
||||
// pt = p;
|
||||
// } else {
|
||||
// pt += endpoint.path();
|
||||
// }
|
||||
|
||||
if (endpoint.isLocalStream()) {
|
||||
co_await listen(asio::local::stream_protocol::endpoint(endpoint.path(pt.string())));
|
||||
} else if (endpoint.isLocalSeqpacket()) {
|
||||
co_await listen(asio::local::seq_packet_protocol::endpoint(endpoint.path(pt.string())));
|
||||
} else {
|
||||
co_return; // it must not be!!!!
|
||||
}
|
||||
} 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::use_awaitable);
|
||||
|
||||
_serverLogger->info("Resolve hostname <{}> to {} IP-addresses", endpoint.host(), r_result.size());
|
||||
|
||||
|
||||
bool exit_flag = false;
|
||||
asio::ip::tcp::acceptor acc(_asioContext);
|
||||
|
||||
for (auto const& epn : r_result) {
|
||||
try {
|
||||
// std::stringstream st;
|
||||
|
||||
// _serverLogger->debug("Create connection acceptor for endpoint <{}> ...",
|
||||
// epn.address().to_string());
|
||||
acc = asio::ip::tcp::acceptor(_asioContext, epn);
|
||||
// st << acc.local_endpoint();
|
||||
exit_flag = true;
|
||||
break;
|
||||
} catch (const std::system_error& err) {
|
||||
_serverLogger->error("An error occuring while creating connection acceptor (ec = {})",
|
||||
err.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!exit_flag) {
|
||||
_serverLogger->error("Cannot start listening on any resolved endpoints!");
|
||||
co_return;
|
||||
}
|
||||
|
||||
_tcpAcceptors.emplace_back(&acc);
|
||||
|
||||
_serverLogger->info("Start listening at <{}> endpoint ...", acc.local_endpoint().address().to_string());
|
||||
|
||||
// start accepting connections
|
||||
for (;;) {
|
||||
auto sock = co_await acc.async_accept(asio::use_awaitable);
|
||||
// start new client session
|
||||
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
|
||||
}
|
||||
|
||||
} catch (const std::system_error& err) {
|
||||
_serverLogger->error("An error occured while trying to start accepting connections! ec = '{}'",
|
||||
err.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <traits::netserver_endpoint_c EpnT>
|
||||
asio::awaitable<void> listen(EpnT 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 open
|
||||
if (!endpoint.is_open()) {
|
||||
if (ec) {
|
||||
// ??????????
|
||||
_serverLogger->error("Serial port was not open! Do not start waiting for commands!");
|
||||
}
|
||||
} else {
|
||||
asio::co_spawn(_asioContext, startSession(std::move(endpoint)), asio::detached);
|
||||
}
|
||||
|
||||
} 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;
|
||||
|
||||
st << endpoint;
|
||||
_serverLogger->debug("Create connection acceptor for endpoint <{}> ...", st.str());
|
||||
auto acc = typename epn_t::protocol_type::acceptor(_asioContext, endpoint);
|
||||
|
||||
st.str("");
|
||||
st << acc.local_endpoint();
|
||||
_serverLogger->info("Start listening at <{}> endpoint ...", st.str());
|
||||
|
||||
|
||||
if constexpr (traits::is_tcp_proto<epn_t>) {
|
||||
_tcpAcceptors.emplace_back(&acc);
|
||||
} else if constexpr (traits::is_local_stream_proto<epn_t>) {
|
||||
_localStreamAcceptors.emplace_back(&acc);
|
||||
} else if constexpr (traits::is_local_seqpack_proto<epn_t>) {
|
||||
_localSeqpackAcceptors.emplace_back(&acc);
|
||||
} else {
|
||||
static_assert(false, "INVALID ENDPOINT!!!");
|
||||
}
|
||||
|
||||
// start accepting connections
|
||||
for (;;) {
|
||||
auto sock = co_await acc.async_accept(asio::use_awaitable);
|
||||
// start new client session
|
||||
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
|
||||
}
|
||||
|
||||
|
||||
} catch (const std::system_error& err) {
|
||||
_serverLogger->error("An error occured while trying to start accepting connections! ec = '{}'",
|
||||
err.what());
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "INVALID ENDPOINT!!!");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
|
||||
// close listening on all endpoints
|
||||
void stopListening()
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
auto close_func = [this](auto& acc_ptrs, std::string_view desc) {
|
||||
size_t N = 0, M = 0;
|
||||
std::error_code ec;
|
||||
|
||||
if (acc_ptrs.size()) {
|
||||
_serverLogger->info("Close {} acceptors ...", desc);
|
||||
|
||||
for (auto& acc : acc_ptrs) {
|
||||
acc->close(ec);
|
||||
if (ec) {
|
||||
_serverLogger->error("Cannot close {} acceptor! ec = '{}'", desc, ec.message());
|
||||
} else {
|
||||
++M;
|
||||
}
|
||||
++N;
|
||||
}
|
||||
|
||||
_serverLogger->debug("{} from {} {} acceptors were closed!", M, N, desc);
|
||||
|
||||
// pointers are invalidated here, so clear its container
|
||||
acc_ptrs.clear();
|
||||
}
|
||||
};
|
||||
|
||||
close_func(_tcpAcceptors, "TCP socket");
|
||||
close_func(_localStreamAcceptors, "local stream socket");
|
||||
close_func(_localSeqpackAcceptors, "local seqpack socket");
|
||||
|
||||
|
||||
_serverLogger->info("The all server listening endpoints were closed!");
|
||||
}
|
||||
|
||||
void disconnectClients()
|
||||
{
|
||||
auto disconn_func = [this](std::ranges::input_range auto& ptrs) {
|
||||
std::error_code ec;
|
||||
for (auto& ptr : ptrs) {
|
||||
// ptr->cancel(ec);
|
||||
// if (ec) {
|
||||
// _serverLogger->warn("socket_base::cancel: an error occured (ec = {})", ec.message());
|
||||
// }
|
||||
|
||||
ptr->shutdown(asio::socket_base::shutdown_both, ec);
|
||||
if (ec) {
|
||||
_serverLogger->warn("socket_base::shutdown: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
|
||||
ptr->close(ec);
|
||||
if (ec) {
|
||||
_serverLogger->warn("socket_base::close: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_serverLogger->info("Close all client connections ...");
|
||||
|
||||
if (_serialPorts.empty() && _localStreamSockets.empty() && _localSeqpackSockets.empty() &&
|
||||
_tcpSockets.empty()) {
|
||||
_serverLogger->info("There were no active client connections! Skip!");
|
||||
}
|
||||
|
||||
if (_serialPorts.size()) {
|
||||
std::lock_guard lock_g(_serialPortsMutex);
|
||||
|
||||
std::error_code ec;
|
||||
_serverLogger->info("Close serial port clients ({} in total) ...", _serialPorts.size());
|
||||
for (auto& ptr : _serialPorts) {
|
||||
ptr->cancel(ec);
|
||||
if (ec) {
|
||||
_serverLogger->warn("serial_port::cancel: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
ptr->close(ec);
|
||||
if (ec) {
|
||||
_serverLogger->warn("serial_port::close: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_localStreamSockets.size()) {
|
||||
std::lock_guard lock_g(_localStreamSocketsMutex);
|
||||
|
||||
_serverLogger->info("Close local stream socket-type clients ({} in total) ...", _localStreamSockets.size());
|
||||
disconn_func(_localStreamSockets);
|
||||
}
|
||||
|
||||
if (_localSeqpackSockets.size()) {
|
||||
std::lock_guard lock_g(_localSeqpackSocketsMutex);
|
||||
|
||||
_serverLogger->info("Close local seqpack socket-type clients ({} in total) ...",
|
||||
_localSeqpackSockets.size());
|
||||
disconn_func(_localSeqpackSockets);
|
||||
}
|
||||
|
||||
if (_tcpSockets.size()) {
|
||||
std::lock_guard lock_g(_tcpSocketsMutex);
|
||||
|
||||
_serverLogger->info("Close TCP socket-type clients ({} in total) ...", _tcpSockets.size());
|
||||
disconn_func(_tcpSockets);
|
||||
}
|
||||
|
||||
_serverLogger->info("Client connection were closed!");
|
||||
}
|
||||
|
||||
void daemonize()
|
||||
{
|
||||
#ifdef FORK_EXISTS
|
||||
_serverLogger->info("Daemonize the server ...");
|
||||
|
||||
_asioContext.notify_fork(asio::execution_context::fork_prepare);
|
||||
|
||||
auto tmp_path = std::filesystem::temp_directory_path();
|
||||
if (tmp_path.empty()) {
|
||||
tmp_path = std::filesystem::current_path().root_path();
|
||||
}
|
||||
|
||||
|
||||
if (pid_t pid = fork()) {
|
||||
if (pid > 0) {
|
||||
exit(0);
|
||||
} else {
|
||||
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK 1-STAGE");
|
||||
_serverLogger->error("CANNOT FORK 1-STAGE! The server was not daemonized!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (setsid() == -1) {
|
||||
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK SETSID");
|
||||
_serverLogger->error("CANNOT FORK SETSID! The server was not daemonized!");
|
||||
return;
|
||||
}
|
||||
|
||||
_serverLogger->info("Try to set the daemon current path to '{}' ...", tmp_path.string());
|
||||
|
||||
std::error_code ec{};
|
||||
|
||||
std::filesystem::current_path(tmp_path, ec);
|
||||
if (!ec) {
|
||||
_serverLogger->warn("Cannot change current path to '{}'! Ignore!", tmp_path.string());
|
||||
}
|
||||
|
||||
umask(0);
|
||||
|
||||
if (pid_t pid = fork()) {
|
||||
if (pid > 0) {
|
||||
exit(0);
|
||||
} else {
|
||||
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK 2-STAGE");
|
||||
_serverLogger->error("CANNOT FORK 2-STAGE! The server was not daemonized!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// stdin, stdout, stderr
|
||||
close(0);
|
||||
close(1);
|
||||
close(2);
|
||||
|
||||
_asioContext.notify_fork(asio::io_context::fork_child);
|
||||
|
||||
_serverLogger->info("The server was daemonized successfully!");
|
||||
|
||||
#else
|
||||
_serverLogger->warn("Host platform is not POSIX one, so cannot daemonize the server!");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <std::ranges::range RST = std::vector<int>, std::ranges::range RRT = std::vector<int>>
|
||||
void setupSignals(const RST& stop_sig_num = {SIGINT, SIGTERM}, const RRT& restart_sig_num = {SIGUSR1})
|
||||
requires(std::convertible_to<std::ranges::range_value_t<RST>, int> &&
|
||||
std::convertible_to<std::ranges::range_value_t<RRT>, int>)
|
||||
{
|
||||
for (const int sig : stop_sig_num) {
|
||||
_stopSignal.add(sig);
|
||||
}
|
||||
|
||||
_stopSignal.async_wait([this](std::error_code, int signo) {
|
||||
_serverLogger->info("Stop signal was received (signo = {})", signo);
|
||||
|
||||
stopListening();
|
||||
disconnectClients();
|
||||
|
||||
_asioContext.stop();
|
||||
});
|
||||
|
||||
for (const int sig : restart_sig_num) {
|
||||
_restartSignal.add(sig);
|
||||
}
|
||||
|
||||
_restartSignal.async_wait([this](std::error_code, int signo) {
|
||||
_serverLogger->info("Restart signal was received (signo = {})", signo);
|
||||
restart();
|
||||
});
|
||||
}
|
||||
|
||||
void restart()
|
||||
{
|
||||
disconnectClients();
|
||||
|
||||
_restartSignal.async_wait([this](std::error_code, int signo) {
|
||||
_serverLogger->info("Restart signal was received (signo = {})", signo);
|
||||
restart();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context& _asioContext;
|
||||
std::shared_ptr<spdlog::logger> _serverLogger;
|
||||
|
||||
asio::signal_set _stopSignal, _restartSignal;
|
||||
|
||||
std::set<asio::serial_port*> _serialPorts;
|
||||
// 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;
|
||||
|
||||
std::set<asio::ip::tcp::socket*> _tcpSockets;
|
||||
std::set<asio::local::stream_protocol::socket*> _localStreamSockets;
|
||||
std::set<asio::local::seq_packet_protocol::socket*> _localSeqpackSockets;
|
||||
// std::vector<asio::ip::tcp::socket*> _tcpSockets;
|
||||
// std::vector<asio::local::stream_protocol::socket*> _localStreamSockets;
|
||||
// std::vector<asio::local::seq_packet_protocol::socket*> _localSeqpackSockets;
|
||||
|
||||
std::mutex _serialPortsMutex, _tcpSocketsMutex, _localStreamSocketsMutex, _localSeqpackSocketsMutex;
|
||||
|
||||
// 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)...);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<char> handleClientCommand(std::string_view command)
|
||||
{
|
||||
std::vector<char> resp{BM700::CONTROL_PROTO_STR_RESP_ACK.begin(), BM700::CONTROL_PROTO_STR_RESP_ACK.end()};
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
template <mcc::traits::mcc_time_duration_c RCVT = decltype(DEFAULT_RCV_TIMEOUT),
|
||||
mcc::traits::mcc_time_duration_c SNDT = decltype(DEFAULT_SND_TIMEOUT)>
|
||||
asio::awaitable<void> startSession(auto socket,
|
||||
const RCVT& rcv_timeout = DEFAULT_RCV_TIMEOUT,
|
||||
const SNDT& snd_timeout = DEFAULT_SND_TIMEOUT)
|
||||
{
|
||||
using namespace asio::experimental::awaitable_operators;
|
||||
|
||||
using sock_t = std::decay_t<decltype(socket)>;
|
||||
|
||||
|
||||
auto look_for_whole_msg = [](auto const& bytes) {
|
||||
auto found = std::ranges::search(bytes, BM700::CONTROL_PROTO_STOP_SEQ);
|
||||
return found.empty() ? std::span(bytes.begin(), bytes.begin()) : std::span(bytes.begin(), found.end());
|
||||
};
|
||||
|
||||
|
||||
asio::streambuf sbuff;
|
||||
size_t nbytes;
|
||||
std::stringstream st;
|
||||
std::string r_epn;
|
||||
|
||||
st << std::this_thread::get_id();
|
||||
std::string thr_id = st.str();
|
||||
st.str("");
|
||||
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
st << "serial port: " << socket.native_handle();
|
||||
} else { // network sockets
|
||||
st << socket.remote_endpoint();
|
||||
}
|
||||
|
||||
r_epn = st.str();
|
||||
if (r_epn.empty()) { // UNIX domain sockets
|
||||
r_epn = "local";
|
||||
}
|
||||
|
||||
_serverLogger->info("Start client session: remote endpoint <{}> (session thread ID = {})", r_epn, thr_id);
|
||||
|
||||
try {
|
||||
if constexpr (!traits::is_serial_proto<sock_t>) {
|
||||
_serverLogger->trace("Set socket option KEEP_ALIVE to TRUE");
|
||||
socket.set_option(asio::socket_base::keep_alive(true));
|
||||
}
|
||||
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_serialPortsMutex);
|
||||
_serialPorts.insert(&socket);
|
||||
} else if constexpr (traits::is_tcp_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_tcpSocketsMutex);
|
||||
// _tcpSockets.emplace_back(&socket);
|
||||
_tcpSockets.insert(&socket);
|
||||
} else if constexpr (traits::is_local_stream_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_localStreamSocketsMutex);
|
||||
// _localStreamSockets.emplace_back(&socket);
|
||||
_localStreamSockets.insert(&socket);
|
||||
} else if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_localSeqpackSocketsMutex);
|
||||
// _localSeqpackSockets.emplace_back(&socket);
|
||||
_localSeqpackSockets.insert(&socket);
|
||||
} else {
|
||||
static_assert(false, "INVALID SOCKET TTYPE!!!");
|
||||
}
|
||||
|
||||
|
||||
// send buffer sequence
|
||||
// initiate the second element by "stop-sequence" symbols
|
||||
std::vector<asio::const_buffer> snd_buff_seq{
|
||||
{}, {BM700::CONTROL_PROTO_STOP_SEQ.data(), BM700::CONTROL_PROTO_STOP_SEQ.size()}};
|
||||
|
||||
asio::steady_timer timeout_timer(_asioContext);
|
||||
std::variant<size_t, std::monostate> op_res;
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
bool do_read = true;
|
||||
|
||||
// main client request -- server respond cycle
|
||||
for (;;) {
|
||||
// receive message
|
||||
|
||||
if (do_read) {
|
||||
_serverLogger->trace("Start socket/port reading operation with timeout {} ...", rcv_timeout);
|
||||
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
nbytes = 1024;
|
||||
} else {
|
||||
nbytes = socket.available();
|
||||
}
|
||||
|
||||
auto buff = sbuff.prepare(nbytes ? nbytes : 1);
|
||||
|
||||
// timeout_timer.expires_after(std::chrono::seconds(5));
|
||||
timeout_timer.expires_after(rcv_timeout);
|
||||
|
||||
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
asio::socket_base::message_flags oflags;
|
||||
|
||||
op_res = co_await (
|
||||
socket.async_receive(buff, oflags, asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
|
||||
} else {
|
||||
op_res = co_await (asio::async_read(socket, buff, asio::transfer_at_least(1),
|
||||
asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
throw std::system_error(ec);
|
||||
}
|
||||
|
||||
if (op_res.index()) {
|
||||
throw std::system_error(std::make_error_code(std::errc::timed_out));
|
||||
} else {
|
||||
nbytes = std::get<0>(op_res);
|
||||
|
||||
_serverLogger->trace("{} bytes were received", nbytes);
|
||||
|
||||
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
if (!nbytes) { // EOF!
|
||||
throw std::system_error(std::error_code(asio::error::misc_errors::eof));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sbuff.commit(nbytes);
|
||||
} // here, the input stream buffer still contains remaining bytes. try to handle its
|
||||
|
||||
auto start_ptr = static_cast<const char*>(sbuff.data().data());
|
||||
|
||||
auto msg = look_for_whole_msg(std::span(start_ptr, sbuff.size()));
|
||||
if (msg.empty()) { // still not whole message
|
||||
_serverLogger->trace(
|
||||
"It seems a partial command message was received, so waiting for remaining part ...");
|
||||
do_read = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// extract command without stop sequence symbols
|
||||
// std::string comm;
|
||||
// std::ranges::copy(msg | std::views::take(msg.size() - BM700::CONTROL_PROTO_STOP_SEQ.size()),
|
||||
// std::back_inserter(comm));
|
||||
|
||||
std::string_view comm{msg.begin(), msg.end() - BM700::CONTROL_PROTO_STOP_SEQ.size()};
|
||||
|
||||
_serverLogger->debug("A command [{}] was received from client (remote endpoint <{}>, thread ID = {})",
|
||||
comm, r_epn, thr_id);
|
||||
|
||||
auto resp = handleClientCommand(comm);
|
||||
|
||||
// remove received message from the input stream buffer. NOTE: 'msg' is now invalidated!!!
|
||||
sbuff.consume(msg.size());
|
||||
do_read = sbuff.size() == 0;
|
||||
|
||||
|
||||
_serverLogger->debug("Send respond [{}] to client (remote endpoint <{}>, thread ID = {})",
|
||||
std::string_view(resp.begin(), resp.end()), r_epn, thr_id);
|
||||
|
||||
// send server respond to client
|
||||
snd_buff_seq[0] = {resp.data(), resp.size()};
|
||||
|
||||
timeout_timer.expires_after(snd_timeout);
|
||||
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
op_res =
|
||||
co_await (socket.async_send(snd_buff_seq, 0, asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
} else {
|
||||
// nbytes = co_await asio::async_write(socket, snd_buff_seq, asio::use_awaitable);
|
||||
op_res = co_await (
|
||||
asio::async_write(socket, snd_buff_seq, asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
throw std::system_error(ec);
|
||||
}
|
||||
|
||||
if (op_res.index()) {
|
||||
throw std::system_error(std::make_error_code(std::errc::timed_out));
|
||||
} else {
|
||||
nbytes = std::get<0>(op_res);
|
||||
_serverLogger->trace("{} bytes were sent", nbytes);
|
||||
}
|
||||
|
||||
if (nbytes != (resp.size() + BM700::CONTROL_PROTO_STOP_SEQ.size())) { // !!!!!!!!!!
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::system_error& ex) {
|
||||
if (ex.code() == std::error_code(asio::error::misc_errors::eof)) {
|
||||
_serverLogger->info(
|
||||
"It seems client or server closed the connection (remote endpoint <{}>, thread ID = {})", r_epn,
|
||||
thr_id);
|
||||
} else {
|
||||
_serverLogger->error("An error '{}' occured in client session (remote endpoint <{}>, thread ID = {})",
|
||||
ex.what(), r_epn, thr_id);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
_serverLogger->error(
|
||||
"An unhandled error '{}' occured in client sesssion (remote endpoint <{}>, thread ID = {})", ex.what(),
|
||||
r_epn, thr_id);
|
||||
} catch (...) {
|
||||
_serverLogger->error("An unhandled error occured in client sesssion (remote endpoint <{}>, thread ID = {})",
|
||||
r_epn, thr_id);
|
||||
}
|
||||
|
||||
// remove pointer as it is invalidated here (at the exit of the method)
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
_serialPorts.erase(&socket);
|
||||
} else if constexpr (traits::is_tcp_proto<sock_t>) {
|
||||
_tcpSockets.erase(&socket);
|
||||
} else if constexpr (traits::is_local_stream_proto<sock_t>) {
|
||||
_localStreamSockets.erase(&socket);
|
||||
} else if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
_localSeqpackSockets.erase(&socket);
|
||||
} else {
|
||||
static_assert(false, "INVALID SOCKET TTYPE!!!");
|
||||
}
|
||||
|
||||
_serverLogger->info("Close client session: remote endpoint <{}> (thread ID = {})", r_epn, thr_id);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace asibfm700
|
||||
507
asibfm700/asibfm700_netserver_endpoint.h
Normal file
507
asibfm700/asibfm700_netserver_endpoint.h
Normal file
@ -0,0 +1,507 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
|
||||
#include "mcc_traits.h"
|
||||
|
||||
namespace asibfm700
|
||||
{
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
static constexpr bool charSubrangeCompare(const mcc::traits::mcc_char_view auto& what,
|
||||
const mcc::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 Asibfm700NetserverEndpoint
|
||||
{
|
||||
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 <mcc::traits::mcc_input_char_range R>
|
||||
Asibfm700NetserverEndpoint(const R& ept)
|
||||
{
|
||||
fromRange(ept);
|
||||
}
|
||||
|
||||
Asibfm700NetserverEndpoint(const Asibfm700NetserverEndpoint& other)
|
||||
{
|
||||
copyInst(other);
|
||||
}
|
||||
|
||||
Asibfm700NetserverEndpoint(Asibfm700NetserverEndpoint&& other)
|
||||
{
|
||||
moveInst(std::move(other));
|
||||
}
|
||||
|
||||
virtual ~Asibfm700NetserverEndpoint() = default;
|
||||
|
||||
|
||||
Asibfm700NetserverEndpoint& operator=(const Asibfm700NetserverEndpoint& other)
|
||||
{
|
||||
copyInst(other);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
Asibfm700NetserverEndpoint& operator=(Asibfm700NetserverEndpoint&& other)
|
||||
{
|
||||
moveInst(std::move(other));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <mcc::traits::mcc_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()};
|
||||
|
||||
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::charSubrangeCompare(_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 <mcc::traits::mcc_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 <mcc::traits::mcc_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 <mcc::traits::mcc_view_or_output_char_range R>
|
||||
R portView() const
|
||||
{
|
||||
return part<R>(PORT_PART);
|
||||
}
|
||||
|
||||
std::string_view portView() const
|
||||
{
|
||||
return portView<std::string_view>();
|
||||
}
|
||||
|
||||
template <mcc::traits::mcc_output_char_range R, mcc::traits::mcc_input_char_range RR = std::string_view>
|
||||
R path(RR&& root_path) const
|
||||
{
|
||||
if (_path.empty()) {
|
||||
if constexpr (mcc::traits::mcc_output_char_range<R>) {
|
||||
R res;
|
||||
std::ranges::copy(std::forward<RR>(root_path), std::back_inserter(res));
|
||||
|
||||
return res;
|
||||
} else { // can't add root path!!!
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
}
|
||||
|
||||
auto N = std::ranges::distance(root_path.begin(), root_path.end());
|
||||
|
||||
if (N) {
|
||||
R res;
|
||||
std::filesystem::path pt(root_path.begin(), root_path.end());
|
||||
|
||||
if (isLocal() && _path[0] == '\0') {
|
||||
std::ranges::copy(std::string_view(" "), std::back_inserter(res));
|
||||
pt /= _path.substr(1);
|
||||
std::ranges::copy(pt.string(), std::back_inserter(res));
|
||||
*res.begin() = '\0';
|
||||
} else {
|
||||
pt /= _path;
|
||||
std::ranges::copy(pt.string(), std::back_inserter(res));
|
||||
}
|
||||
|
||||
return res;
|
||||
} else {
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
}
|
||||
|
||||
template <mcc::traits::mcc_input_char_range RR = std::string_view>
|
||||
std::string path(RR&& root_path) const
|
||||
{
|
||||
return path<std::string, RR>(std::forward<RR>(root_path));
|
||||
}
|
||||
|
||||
template <mcc::traits::mcc_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;
|
||||
}
|
||||
|
||||
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 <typename T = std::nullptr_t>
|
||||
Asibfm700NetserverEndpoint& makeAbstract(const T& mark = nullptr)
|
||||
requires(mcc::traits::mcc_input_char_range<T> || std::same_as<std::remove_cv_t<T>, char> ||
|
||||
std::is_null_pointer_v<std::remove_cv_t<T>>)
|
||||
{
|
||||
if (!(isLocalStream() || isLocalSeqpacket())) { // only local proto is valid!
|
||||
return *this;
|
||||
}
|
||||
|
||||
if constexpr (std::is_null_pointer_v<T>) { // 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<std::remove_cv_t<T>, 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(Asibfm700NetserverEndpoint::validProtoMarks, [&idx, &proto_mark](const auto& el) {
|
||||
bool ok = utils::charSubrangeCompare(proto_mark, el, true);
|
||||
|
||||
if (!ok) {
|
||||
++idx;
|
||||
}
|
||||
|
||||
return ok;
|
||||
});
|
||||
|
||||
return found ? idx : -1;
|
||||
}
|
||||
|
||||
enum EndpointPart { PROTO_PART, HOST_PART, PATH_PART, PORT_PART };
|
||||
|
||||
template <mcc::traits::mcc_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;
|
||||
case PORT_PART:
|
||||
part = _portView;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if constexpr (std::ranges::view<R>) {
|
||||
return {part.begin(), part.end()};
|
||||
} else {
|
||||
std::ranges::copy(part, std::back_inserter(res));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void copyInst(const Asibfm700NetserverEndpoint& other)
|
||||
{
|
||||
if (&other != this) {
|
||||
if (other._isValid) {
|
||||
_isValid = other._isValid;
|
||||
_endpoint = other._endpoint;
|
||||
_proto = other._proto;
|
||||
|
||||
std::iterator_traits<const char*>::difference_type idx;
|
||||
if (other.isLocal()) { // for 'local' host is one of static class constants
|
||||
_host = other._host;
|
||||
} else {
|
||||
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());
|
||||
|
||||
idx = std::distance(other._endpoint.c_str(), other._portView.data());
|
||||
_portView = std::string_view(_endpoint.c_str() + idx, other._portView.size());
|
||||
|
||||
_port = other._port;
|
||||
} else {
|
||||
_isValid = false;
|
||||
_endpoint = std::string();
|
||||
_proto = std::string_view();
|
||||
_host = std::string_view();
|
||||
_path = std::string_view();
|
||||
_portView = std::string_view();
|
||||
_port = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void moveInst(Asibfm700NetserverEndpoint&& 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);
|
||||
_portView = std::move(other._portView);
|
||||
} else {
|
||||
_isValid = false;
|
||||
_endpoint = std::string();
|
||||
_proto = std::string_view();
|
||||
_host = std::string_view();
|
||||
_path = std::string_view();
|
||||
_portView = std::string_view();
|
||||
_port = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace asibfm700
|
||||
@ -110,13 +110,11 @@ struct MccGenericFsmMountCategory : public std::error_category {
|
||||
inline std::error_code make_error_code(MccGenericMountErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccGenericMountCategory::get());
|
||||
// return std::error_code(static_cast<int>(ec), std::generic_category());
|
||||
}
|
||||
|
||||
inline std::error_code make_error_code(MccGenericFsmMountErrorCode ec)
|
||||
{
|
||||
return std::error_code(static_cast<int>(ec), MccGenericFsmMountCategory::get());
|
||||
// return std::error_code(static_cast<int>(ec), std::generic_category());
|
||||
}
|
||||
|
||||
|
||||
@ -126,14 +124,12 @@ template <mcc_hardware_c HardwareT,
|
||||
mcc_pzone_container_c PZoneContT,
|
||||
mcc_slewing_model_c SlewModelT,
|
||||
mcc_tracking_model_c TrackModelT,
|
||||
// mcc_guiding_model_c GuidingModelT,
|
||||
mcc_logger_c LoggerT = MccNullLogger>
|
||||
class MccGenericMount : public HardwareT,
|
||||
public TelemetryT,
|
||||
public PZoneContT,
|
||||
public SlewModelT,
|
||||
public TrackModelT,
|
||||
// public GuidingModelT,
|
||||
public LoggerT
|
||||
{
|
||||
public:
|
||||
@ -145,7 +141,6 @@ public:
|
||||
using LoggerT::logWarn;
|
||||
|
||||
|
||||
// using typename GuidingModelT::guiding_params_t;
|
||||
using typename SlewModelT::slewing_params_t;
|
||||
using typename TrackModelT::tracking_params_t;
|
||||
|
||||
@ -191,14 +186,14 @@ public:
|
||||
|
||||
error_t initMount()
|
||||
{
|
||||
logInfo("Start mount initialization ...");
|
||||
logInfo("Start generic mount initialization ...");
|
||||
|
||||
auto hw_err = this->hardwareInit();
|
||||
if (hw_err) {
|
||||
return mcc_deduce_error_code(hw_err, MccGenericMountErrorCode::ERROR_HW_STOP);
|
||||
}
|
||||
|
||||
logInfo("Mount initialization was performed");
|
||||
logInfo("Generic mount initialization was performed");
|
||||
|
||||
return MccGenericMountErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
765
mcc/mcc_netserver.h
Normal file
765
mcc/mcc_netserver.h
Normal file
@ -0,0 +1,765 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include <asio/awaitable.hpp>
|
||||
#include <asio/co_spawn.hpp>
|
||||
#include <asio/deferred.hpp>
|
||||
#include <asio/detached.hpp>
|
||||
#include <asio/experimental/awaitable_operators.hpp>
|
||||
#include <asio/ip/tcp.hpp>
|
||||
#include <asio/local/seq_packet_protocol.hpp>
|
||||
#include <asio/local/stream_protocol.hpp>
|
||||
#include <asio/read.hpp>
|
||||
#include <asio/redirect_error.hpp>
|
||||
#include <asio/serial_port.hpp>
|
||||
#include <asio/signal_set.hpp>
|
||||
#include <asio/steady_timer.hpp>
|
||||
#include <asio/streambuf.hpp>
|
||||
#include <asio/write.hpp>
|
||||
|
||||
#include <spdlog/sinks/null_sink.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#if __has_include(<unistd.h>) // POSIX
|
||||
#define FORK_EXISTS 1
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#include "control_proto.h"
|
||||
#include "mcc_generics.h"
|
||||
#include "mcc_netserver_endpoint.h"
|
||||
#include "mcc_traits.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> || std::derived_from<T, asio::ip::tcp::socket>;
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool is_local_stream_proto = std::derived_from<T, asio::local::stream_protocol::endpoint> ||
|
||||
std::derived_from<T, asio::local::stream_protocol::socket>;
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool is_local_seqpack_proto = std::derived_from<T, asio::local::seq_packet_protocol::endpoint> ||
|
||||
std::derived_from<T, asio::local::seq_packet_protocol::socket>;
|
||||
|
||||
|
||||
} // namespace traits
|
||||
|
||||
|
||||
template <mcc_logger_c LoggerT = MccNullLogger>
|
||||
class MccNetworkServer : public LoggerT
|
||||
{
|
||||
public:
|
||||
using LoggerT::logDebug;
|
||||
using LoggerT::logError;
|
||||
using LoggerT::logInfo;
|
||||
using LoggerT::logWarn;
|
||||
|
||||
static constexpr std::chrono::duration DEFAULT_RCV_TIMEOUT = std::chrono::hours(12);
|
||||
static constexpr std::chrono::duration DEFAULT_SND_TIMEOUT = std::chrono::milliseconds(2000);
|
||||
|
||||
MccNetworkServer(asio::io_context& ctx, LoggerT logger = MccNullLogger{})
|
||||
: _asioContext(ctx), _stopSignal(ctx), _restartSignal(ctx)
|
||||
{
|
||||
std::stringstream st;
|
||||
st << std::this_thread::get_id();
|
||||
|
||||
logInfo("Create mount server instance (thread ID = {})", st.str());
|
||||
}
|
||||
|
||||
~MccNetworkServer()
|
||||
{
|
||||
std::stringstream st;
|
||||
st << std::this_thread::get_id();
|
||||
|
||||
logInfo("Delete mount server instance (thread ID = {}) ...", st.str());
|
||||
|
||||
stopListening();
|
||||
disconnectClients();
|
||||
}
|
||||
|
||||
|
||||
template <typename... CtorArgTs>
|
||||
asio::awaitable<void> listen(std::derived_from<MccServerEndpoint> auto endpoint, CtorArgTs&&... ctor_args)
|
||||
{
|
||||
if (!endpoint.isValid()) {
|
||||
logError("Cannot start listening! Invalid endpoint string representation ('{}')!", endpoint.endpoint());
|
||||
co_return;
|
||||
}
|
||||
|
||||
// add root path to endpoint one
|
||||
std::filesystem::path pt("/");
|
||||
|
||||
|
||||
if (endpoint.isLocalSerial()) {
|
||||
pt += endpoint.path();
|
||||
|
||||
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) {
|
||||
logError("Cannot open serial device '{}' (Error = '{}')!", pt.string(), ec.message());
|
||||
co_return;
|
||||
}
|
||||
|
||||
// asio::co_spawn(_asioContext, listen(std::move(s_port)), asio::detached);
|
||||
co_await listen(std::move(s_port));
|
||||
} else if (endpoint.isLocal()) {
|
||||
// create abstract namespace socket endpoint if its path starts from '@' symbol
|
||||
endpoint.makeAbstract('@');
|
||||
|
||||
// if (endpoint.path()[0] == '\0') { // abstract namespace
|
||||
// std::string p;
|
||||
// std::ranges::copy(endpoint.path(), std::back_inserter(p));
|
||||
// p.insert(p.begin() + 1, '/'); // insert after '\0' symbol
|
||||
// pt = p;
|
||||
// } else {
|
||||
// pt += endpoint.path();
|
||||
// }
|
||||
|
||||
if (endpoint.isLocalStream()) {
|
||||
co_await listen(asio::local::stream_protocol::endpoint(endpoint.path(pt.string())));
|
||||
} else if (endpoint.isLocalSeqpacket()) {
|
||||
co_await listen(asio::local::seq_packet_protocol::endpoint(endpoint.path(pt.string())));
|
||||
} else {
|
||||
co_return; // it must not be!!!!
|
||||
}
|
||||
} 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::use_awaitable);
|
||||
|
||||
logInfo("Resolve hostname <{}> to {} IP-addresses", endpoint.host(), r_result.size());
|
||||
|
||||
|
||||
bool exit_flag = false;
|
||||
asio::ip::tcp::acceptor acc(_asioContext);
|
||||
|
||||
for (auto const& epn : r_result) {
|
||||
try {
|
||||
// std::stringstream st;
|
||||
|
||||
// logDebug("Create connection acceptor for endpoint <{}> ...",
|
||||
// epn.address().to_string());
|
||||
acc = asio::ip::tcp::acceptor(_asioContext, epn);
|
||||
// st << acc.local_endpoint();
|
||||
exit_flag = true;
|
||||
break;
|
||||
} catch (const std::system_error& err) {
|
||||
logError("An error occuring while creating connection acceptor (ec = {})", err.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!exit_flag) {
|
||||
logError("Cannot start listening on any resolved endpoints!");
|
||||
co_return;
|
||||
}
|
||||
|
||||
_tcpAcceptors.emplace_back(&acc);
|
||||
|
||||
logInfo("Start listening at <{}> endpoint ...", acc.local_endpoint().address().to_string());
|
||||
|
||||
// start accepting connections
|
||||
for (;;) {
|
||||
auto sock = co_await acc.async_accept(asio::use_awaitable);
|
||||
// start new client session
|
||||
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
|
||||
}
|
||||
|
||||
} catch (const std::system_error& err) {
|
||||
logError("An error occured while trying to start accepting connections! ec = '{}'", err.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <traits::mcc_endpoint_c EpnT>
|
||||
asio::awaitable<void> listen(EpnT 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 open
|
||||
if (!endpoint.is_open()) {
|
||||
if (ec) {
|
||||
// ??????????
|
||||
logError("Serial port was not open! Do not start waiting for commands!");
|
||||
}
|
||||
} else {
|
||||
asio::co_spawn(_asioContext, startSession(std::move(endpoint)), asio::detached);
|
||||
}
|
||||
|
||||
} 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;
|
||||
|
||||
st << endpoint;
|
||||
logDebug("Create connection acceptor for endpoint <{}> ...", st.str());
|
||||
auto acc = typename epn_t::protocol_type::acceptor(_asioContext, endpoint);
|
||||
|
||||
st.str("");
|
||||
st << acc.local_endpoint();
|
||||
logInfo("Start listening at <{}> endpoint ...", st.str());
|
||||
|
||||
|
||||
if constexpr (traits::is_tcp_proto<epn_t>) {
|
||||
_tcpAcceptors.emplace_back(&acc);
|
||||
} else if constexpr (traits::is_local_stream_proto<epn_t>) {
|
||||
_localStreamAcceptors.emplace_back(&acc);
|
||||
} else if constexpr (traits::is_local_seqpack_proto<epn_t>) {
|
||||
_localSeqpackAcceptors.emplace_back(&acc);
|
||||
} else {
|
||||
static_assert(false, "INVALID ENDPOINT!!!");
|
||||
}
|
||||
|
||||
// start accepting connections
|
||||
for (;;) {
|
||||
auto sock = co_await acc.async_accept(asio::use_awaitable);
|
||||
// start new client session
|
||||
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
|
||||
}
|
||||
|
||||
|
||||
} catch (const std::system_error& err) {
|
||||
logError("An error occured while trying to start accepting connections! ec = '{}'", err.what());
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "INVALID ENDPOINT!!!");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
|
||||
// close listening on all endpoints
|
||||
void stopListening()
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
logInfo("Close all listening endpoints ...");
|
||||
|
||||
auto num =
|
||||
_serialPorts.size() + _tcpAcceptors.size() + _localStreamAcceptors.size() + _localSeqpackAcceptors.size();
|
||||
if (!num) {
|
||||
logInfo("There are no listening ports/sockets!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto close_func = [this](auto& acc_ptrs, std::string_view desc) {
|
||||
size_t N = 0, M = 0;
|
||||
std::error_code ec;
|
||||
|
||||
if (acc_ptrs.size()) {
|
||||
logInfo("Close {} acceptors ...", desc);
|
||||
|
||||
for (auto& acc : acc_ptrs) {
|
||||
acc->close(ec);
|
||||
if (ec) {
|
||||
logError("Cannot close {} acceptor! ec = '{}'", desc, ec.message());
|
||||
} else {
|
||||
++M;
|
||||
}
|
||||
++N;
|
||||
}
|
||||
|
||||
logDebug("{} from {} {} acceptors were closed!", M, N, desc);
|
||||
|
||||
// pointers are invalidated here, so clear its container
|
||||
acc_ptrs.clear();
|
||||
}
|
||||
};
|
||||
|
||||
close_func(_tcpAcceptors, "TCP socket");
|
||||
close_func(_localStreamAcceptors, "local stream socket");
|
||||
close_func(_localSeqpackAcceptors, "local seqpack socket");
|
||||
|
||||
|
||||
logInfo("The all server listening endpoints were closed!");
|
||||
}
|
||||
|
||||
void disconnectClients()
|
||||
{
|
||||
auto disconn_func = [this](std::ranges::input_range auto& ptrs) {
|
||||
std::error_code ec;
|
||||
for (auto& ptr : ptrs) {
|
||||
// ptr->cancel(ec);
|
||||
// if (ec) {
|
||||
// logWarn("socket_base::cancel: an error occured (ec = {})", ec.message());
|
||||
// }
|
||||
|
||||
ptr->shutdown(asio::socket_base::shutdown_both, ec);
|
||||
if (ec) {
|
||||
logWarn("socket_base::shutdown: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
|
||||
ptr->close(ec);
|
||||
if (ec) {
|
||||
logWarn("socket_base::close: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
logInfo("Close all client connections ...");
|
||||
|
||||
if (_serialPorts.empty() && _localStreamSockets.empty() && _localSeqpackSockets.empty() &&
|
||||
_tcpSockets.empty()) {
|
||||
logInfo("There were no active client connections! Skip!");
|
||||
}
|
||||
|
||||
if (_serialPorts.size()) {
|
||||
std::lock_guard lock_g(_serialPortsMutex);
|
||||
|
||||
std::error_code ec;
|
||||
logInfo("Close serial port clients ({} in total) ...", _serialPorts.size());
|
||||
for (auto& ptr : _serialPorts) {
|
||||
ptr->cancel(ec);
|
||||
if (ec) {
|
||||
logWarn("serial_port::cancel: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
ptr->close(ec);
|
||||
if (ec) {
|
||||
logWarn("serial_port::close: an error occured (ec = {})", ec.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_localStreamSockets.size()) {
|
||||
std::lock_guard lock_g(_localStreamSocketsMutex);
|
||||
|
||||
logInfo("Close local stream socket-type clients ({} in total) ...", _localStreamSockets.size());
|
||||
disconn_func(_localStreamSockets);
|
||||
}
|
||||
|
||||
if (_localSeqpackSockets.size()) {
|
||||
std::lock_guard lock_g(_localSeqpackSocketsMutex);
|
||||
|
||||
logInfo("Close local seqpack socket-type clients ({} in total) ...", _localSeqpackSockets.size());
|
||||
disconn_func(_localSeqpackSockets);
|
||||
}
|
||||
|
||||
if (_tcpSockets.size()) {
|
||||
std::lock_guard lock_g(_tcpSocketsMutex);
|
||||
|
||||
logInfo("Close TCP socket-type clients ({} in total) ...", _tcpSockets.size());
|
||||
disconn_func(_tcpSockets);
|
||||
}
|
||||
|
||||
logInfo("Client connection were closed!");
|
||||
}
|
||||
|
||||
void daemonize()
|
||||
{
|
||||
#ifdef FORK_EXISTS
|
||||
logInfo("Daemonize the server ...");
|
||||
|
||||
_asioContext.notify_fork(asio::execution_context::fork_prepare);
|
||||
|
||||
auto tmp_path = std::filesystem::temp_directory_path();
|
||||
if (tmp_path.empty()) {
|
||||
tmp_path = std::filesystem::current_path().root_path();
|
||||
}
|
||||
|
||||
|
||||
if (pid_t pid = fork()) {
|
||||
if (pid > 0) {
|
||||
exit(0);
|
||||
} else {
|
||||
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK 1-STAGE");
|
||||
logError("CANNOT FORK 1-STAGE! The server was not daemonized!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (setsid() == -1) {
|
||||
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK SETSID");
|
||||
logError("CANNOT FORK SETSID! The server was not daemonized!");
|
||||
return;
|
||||
}
|
||||
|
||||
logInfo("Try to set the daemon current path to '{}' ...", tmp_path.string());
|
||||
|
||||
std::error_code ec{};
|
||||
|
||||
std::filesystem::current_path(tmp_path, ec);
|
||||
if (!ec) {
|
||||
logWarn("Cannot change current path to '{}'! Ignore!", tmp_path.string());
|
||||
}
|
||||
|
||||
umask(0);
|
||||
|
||||
if (pid_t pid = fork()) {
|
||||
if (pid > 0) {
|
||||
exit(0);
|
||||
} else {
|
||||
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK 2-STAGE");
|
||||
logError("CANNOT FORK 2-STAGE! The server was not daemonized!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// stdin, stdout, stderr
|
||||
close(0);
|
||||
close(1);
|
||||
close(2);
|
||||
|
||||
_asioContext.notify_fork(asio::io_context::fork_child);
|
||||
|
||||
logInfo("The server was daemonized successfully!");
|
||||
|
||||
#else
|
||||
logWarn("Host platform is not POSIX one, so cannot daemonize the server!");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <std::ranges::range RST = std::vector<int>, std::ranges::range RRT = std::vector<int>>
|
||||
void setupSignals(const RST& stop_sig_num = {SIGINT, SIGTERM}, const RRT& restart_sig_num = {SIGUSR1})
|
||||
requires(std::convertible_to<std::ranges::range_value_t<RST>, int> &&
|
||||
std::convertible_to<std::ranges::range_value_t<RRT>, int>)
|
||||
{
|
||||
for (const int sig : stop_sig_num) {
|
||||
_stopSignal.add(sig);
|
||||
}
|
||||
|
||||
_stopSignal.async_wait([this](std::error_code, int signo) {
|
||||
logInfo("Stop signal was received (signo = {})", signo);
|
||||
|
||||
stopListening();
|
||||
disconnectClients();
|
||||
|
||||
_asioContext.stop();
|
||||
});
|
||||
|
||||
for (const int sig : restart_sig_num) {
|
||||
_restartSignal.add(sig);
|
||||
}
|
||||
|
||||
_restartSignal.async_wait([this](std::error_code, int signo) {
|
||||
logInfo("Restart signal was received (signo = {})", signo);
|
||||
restart();
|
||||
});
|
||||
}
|
||||
|
||||
void restart()
|
||||
{
|
||||
disconnectClients();
|
||||
|
||||
_restartSignal.async_wait([this](std::error_code, int signo) {
|
||||
logInfo("Restart signal was received (signo = {})", signo);
|
||||
restart();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context& _asioContext;
|
||||
|
||||
asio::signal_set _stopSignal, _restartSignal;
|
||||
|
||||
std::set<asio::serial_port*> _serialPorts;
|
||||
// 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;
|
||||
|
||||
std::set<asio::ip::tcp::socket*> _tcpSockets;
|
||||
std::set<asio::local::stream_protocol::socket*> _localStreamSockets;
|
||||
std::set<asio::local::seq_packet_protocol::socket*> _localSeqpackSockets;
|
||||
// std::vector<asio::ip::tcp::socket*> _tcpSockets;
|
||||
// std::vector<asio::local::stream_protocol::socket*> _localStreamSockets;
|
||||
// std::vector<asio::local::seq_packet_protocol::socket*> _localSeqpackSockets;
|
||||
|
||||
std::mutex _serialPortsMutex, _tcpSocketsMutex, _localStreamSocketsMutex, _localSeqpackSocketsMutex;
|
||||
|
||||
// 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";
|
||||
}
|
||||
|
||||
logError("Cannot set serial port '{}' option! Just skip!", opt_name);
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(OptTs)) {
|
||||
setSerialOpts(s_port, std::forward<OptTs>(opts)...);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<char> handleClientCommand(std::string_view command)
|
||||
{
|
||||
std::vector<char> resp{BM700::CONTROL_PROTO_STR_RESP_ACK.begin(), BM700::CONTROL_PROTO_STR_RESP_ACK.end()};
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
template <traits::mcc_time_duration_c RCVT = decltype(DEFAULT_RCV_TIMEOUT),
|
||||
traits::mcc_time_duration_c SNDT = decltype(DEFAULT_SND_TIMEOUT)>
|
||||
asio::awaitable<void> startSession(auto socket,
|
||||
const RCVT& rcv_timeout = DEFAULT_RCV_TIMEOUT,
|
||||
const SNDT& snd_timeout = DEFAULT_SND_TIMEOUT)
|
||||
{
|
||||
using namespace asio::experimental::awaitable_operators;
|
||||
|
||||
using sock_t = std::decay_t<decltype(socket)>;
|
||||
|
||||
|
||||
auto look_for_whole_msg = [](auto const& bytes) {
|
||||
auto found = std::ranges::search(bytes, BM700::CONTROL_PROTO_STOP_SEQ);
|
||||
return found.empty() ? std::span(bytes.begin(), bytes.begin()) : std::span(bytes.begin(), found.end());
|
||||
};
|
||||
|
||||
|
||||
asio::streambuf sbuff;
|
||||
size_t nbytes;
|
||||
std::stringstream st;
|
||||
std::string r_epn;
|
||||
|
||||
st << std::this_thread::get_id();
|
||||
std::string thr_id = st.str();
|
||||
st.str("");
|
||||
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
st << "serial port: " << socket.native_handle();
|
||||
} else { // network sockets
|
||||
st << socket.remote_endpoint();
|
||||
}
|
||||
|
||||
r_epn = st.str();
|
||||
if (r_epn.empty()) { // UNIX domain sockets
|
||||
r_epn = "local";
|
||||
}
|
||||
|
||||
logInfo("Start client session: remote endpoint <{}> (session thread ID = {})", r_epn, thr_id);
|
||||
|
||||
try {
|
||||
if constexpr (!traits::is_serial_proto<sock_t>) {
|
||||
_serverLogger->trace("Set socket option KEEP_ALIVE to TRUE");
|
||||
socket.set_option(asio::socket_base::keep_alive(true));
|
||||
}
|
||||
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_serialPortsMutex);
|
||||
_serialPorts.insert(&socket);
|
||||
} else if constexpr (traits::is_tcp_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_tcpSocketsMutex);
|
||||
// _tcpSockets.emplace_back(&socket);
|
||||
_tcpSockets.insert(&socket);
|
||||
} else if constexpr (traits::is_local_stream_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_localStreamSocketsMutex);
|
||||
// _localStreamSockets.emplace_back(&socket);
|
||||
_localStreamSockets.insert(&socket);
|
||||
} else if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
std::lock_guard lock_g(_localSeqpackSocketsMutex);
|
||||
// _localSeqpackSockets.emplace_back(&socket);
|
||||
_localSeqpackSockets.insert(&socket);
|
||||
} else {
|
||||
static_assert(false, "INVALID SOCKET TTYPE!!!");
|
||||
}
|
||||
|
||||
|
||||
// send buffer sequence
|
||||
// initiate the second element by "stop-sequence" symbols
|
||||
std::vector<asio::const_buffer> snd_buff_seq{
|
||||
{}, {BM700::CONTROL_PROTO_STOP_SEQ.data(), BM700::CONTROL_PROTO_STOP_SEQ.size()}};
|
||||
|
||||
asio::steady_timer timeout_timer(_asioContext);
|
||||
std::variant<size_t, std::monostate> op_res;
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
bool do_read = true;
|
||||
|
||||
// main client request -- server respond cycle
|
||||
for (;;) {
|
||||
// receive message
|
||||
|
||||
if (do_read) {
|
||||
_serverLogger->trace("Start socket/port reading operation with timeout {} ...", rcv_timeout);
|
||||
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
nbytes = 1024;
|
||||
} else {
|
||||
nbytes = socket.available();
|
||||
}
|
||||
|
||||
auto buff = sbuff.prepare(nbytes ? nbytes : 1);
|
||||
|
||||
// timeout_timer.expires_after(std::chrono::seconds(5));
|
||||
timeout_timer.expires_after(rcv_timeout);
|
||||
|
||||
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
asio::socket_base::message_flags oflags;
|
||||
|
||||
op_res = co_await (
|
||||
socket.async_receive(buff, oflags, asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
|
||||
} else {
|
||||
op_res = co_await (asio::async_read(socket, buff, asio::transfer_at_least(1),
|
||||
asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
throw std::system_error(ec);
|
||||
}
|
||||
|
||||
if (op_res.index()) {
|
||||
throw std::system_error(std::make_error_code(std::errc::timed_out));
|
||||
} else {
|
||||
nbytes = std::get<0>(op_res);
|
||||
|
||||
_serverLogger->trace("{} bytes were received", nbytes);
|
||||
|
||||
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
if (!nbytes) { // EOF!
|
||||
throw std::system_error(std::error_code(asio::error::misc_errors::eof));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sbuff.commit(nbytes);
|
||||
} // here, the input stream buffer still contains remaining bytes. try to handle its
|
||||
|
||||
auto start_ptr = static_cast<const char*>(sbuff.data().data());
|
||||
|
||||
auto msg = look_for_whole_msg(std::span(start_ptr, sbuff.size()));
|
||||
if (msg.empty()) { // still not whole message
|
||||
_serverLogger->trace(
|
||||
"It seems a partial command message was received, so waiting for remaining part ...");
|
||||
do_read = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// extract command without stop sequence symbols
|
||||
// std::string comm;
|
||||
// std::ranges::copy(msg | std::views::take(msg.size() - BM700::CONTROL_PROTO_STOP_SEQ.size()),
|
||||
// std::back_inserter(comm));
|
||||
|
||||
std::string_view comm{msg.begin(), msg.end() - BM700::CONTROL_PROTO_STOP_SEQ.size()};
|
||||
|
||||
logDebug("A command [{}] was received from client (remote endpoint <{}>, thread ID = {})", comm, r_epn,
|
||||
thr_id);
|
||||
|
||||
auto resp = handleClientCommand(comm);
|
||||
|
||||
// remove received message from the input stream buffer. NOTE: 'msg' is now invalidated!!!
|
||||
sbuff.consume(msg.size());
|
||||
do_read = sbuff.size() == 0;
|
||||
|
||||
|
||||
logDebug("Send respond [{}] to client (remote endpoint <{}>, thread ID = {})",
|
||||
std::string_view(resp.begin(), resp.end()), r_epn, thr_id);
|
||||
|
||||
// send server respond to client
|
||||
snd_buff_seq[0] = {resp.data(), resp.size()};
|
||||
|
||||
timeout_timer.expires_after(snd_timeout);
|
||||
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
op_res =
|
||||
co_await (socket.async_send(snd_buff_seq, 0, asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
} else {
|
||||
// nbytes = co_await asio::async_write(socket, snd_buff_seq, asio::use_awaitable);
|
||||
op_res = co_await (
|
||||
asio::async_write(socket, snd_buff_seq, asio::redirect_error(asio::use_awaitable, ec)) ||
|
||||
timeout_timer.async_wait(asio::use_awaitable));
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
throw std::system_error(ec);
|
||||
}
|
||||
|
||||
if (op_res.index()) {
|
||||
throw std::system_error(std::make_error_code(std::errc::timed_out));
|
||||
} else {
|
||||
nbytes = std::get<0>(op_res);
|
||||
_serverLogger->trace("{} bytes were sent", nbytes);
|
||||
}
|
||||
|
||||
if (nbytes != (resp.size() + BM700::CONTROL_PROTO_STOP_SEQ.size())) { // !!!!!!!!!!
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::system_error& ex) {
|
||||
if (ex.code() == std::error_code(asio::error::misc_errors::eof)) {
|
||||
logInfo("It seems client or server closed the connection (remote endpoint <{}>, thread ID = {})", r_epn,
|
||||
thr_id);
|
||||
} else {
|
||||
logError("An error '{}' occured in client session (remote endpoint <{}>, thread ID = {})", ex.what(),
|
||||
r_epn, thr_id);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
logError("An unhandled error '{}' occured in client sesssion (remote endpoint <{}>, thread ID = {})",
|
||||
ex.what(), r_epn, thr_id);
|
||||
} catch (...) {
|
||||
logError("An unhandled error occured in client sesssion (remote endpoint <{}>, thread ID = {})", r_epn,
|
||||
thr_id);
|
||||
}
|
||||
|
||||
// remove pointer as it is invalidated here (at the exit of the method)
|
||||
if constexpr (traits::is_serial_proto<sock_t>) {
|
||||
_serialPorts.erase(&socket);
|
||||
} else if constexpr (traits::is_tcp_proto<sock_t>) {
|
||||
_tcpSockets.erase(&socket);
|
||||
} else if constexpr (traits::is_local_stream_proto<sock_t>) {
|
||||
_localStreamSockets.erase(&socket);
|
||||
} else if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
||||
_localSeqpackSockets.erase(&socket);
|
||||
} else {
|
||||
static_assert(false, "INVALID SOCKET TTYPE!!!");
|
||||
}
|
||||
|
||||
logInfo("Close client session: remote endpoint <{}> (thread ID = {})", r_epn, thr_id);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcc
|
||||
512
mcc/mcc_netserver_endpoint.h
Normal file
512
mcc/mcc_netserver_endpoint.h
Normal file
@ -0,0 +1,512 @@
|
||||
#pragma once
|
||||
|
||||
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
||||
|
||||
|
||||
|
||||
/* NETWORK SERVER ENDPOINT CLASS IMPLEMENTATION */
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
|
||||
#include "mcc_traits.h"
|
||||
|
||||
namespace mcc
|
||||
{
|
||||
|
||||
namespace utils
|
||||
{
|
||||
|
||||
static constexpr bool mcc_char_subrange_compare(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 <traits::mcc_input_char_range R>
|
||||
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 <traits::mcc_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()};
|
||||
|
||||
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::mcc_char_subrange_compare(_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 <traits::mcc_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::mcc_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::mcc_view_or_output_char_range R>
|
||||
R portView() const
|
||||
{
|
||||
return part<R>(PORT_PART);
|
||||
}
|
||||
|
||||
std::string_view portView() const
|
||||
{
|
||||
return portView<std::string_view>();
|
||||
}
|
||||
|
||||
template <traits::mcc_output_char_range R, traits::mcc_input_char_range RR = std::string_view>
|
||||
R path(RR&& root_path) const
|
||||
{
|
||||
if (_path.empty()) {
|
||||
if constexpr (traits::mcc_output_char_range<R>) {
|
||||
R res;
|
||||
std::ranges::copy(std::forward<RR>(root_path), std::back_inserter(res));
|
||||
|
||||
return res;
|
||||
} else { // can't add root path!!!
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
}
|
||||
|
||||
auto N = std::ranges::distance(root_path.begin(), root_path.end());
|
||||
|
||||
if (N) {
|
||||
R res;
|
||||
std::filesystem::path pt(root_path.begin(), root_path.end());
|
||||
|
||||
if (isLocal() && _path[0] == '\0') {
|
||||
std::ranges::copy(std::string_view(" "), std::back_inserter(res));
|
||||
pt /= _path.substr(1);
|
||||
std::ranges::copy(pt.string(), std::back_inserter(res));
|
||||
*res.begin() = '\0';
|
||||
} else {
|
||||
pt /= _path;
|
||||
std::ranges::copy(pt.string(), std::back_inserter(res));
|
||||
}
|
||||
|
||||
return res;
|
||||
} else {
|
||||
return part<R>(PATH_PART);
|
||||
}
|
||||
}
|
||||
|
||||
template <traits::mcc_input_char_range RR = std::string_view>
|
||||
std::string path(RR&& root_path) const
|
||||
{
|
||||
return path<std::string, RR>(std::forward<RR>(root_path));
|
||||
}
|
||||
|
||||
template <traits::mcc_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;
|
||||
}
|
||||
|
||||
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 <typename T = std::nullptr_t>
|
||||
MccServerEndpoint& makeAbstract(const T& mark = nullptr)
|
||||
requires(traits::mcc_input_char_range<T> || std::same_as<std::remove_cv_t<T>, char> ||
|
||||
std::is_null_pointer_v<std::remove_cv_t<T>>)
|
||||
{
|
||||
if (!(isLocalStream() || isLocalSeqpacket())) { // only local proto is valid!
|
||||
return *this;
|
||||
}
|
||||
|
||||
if constexpr (std::is_null_pointer_v<T>) { // 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<std::remove_cv_t<T>, 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::mcc_char_subrange_compare(proto_mark, el, true);
|
||||
|
||||
if (!ok) {
|
||||
++idx;
|
||||
}
|
||||
|
||||
return ok;
|
||||
});
|
||||
|
||||
return found ? idx : -1;
|
||||
}
|
||||
|
||||
enum EndpointPart { PROTO_PART, HOST_PART, PATH_PART, PORT_PART };
|
||||
|
||||
template <traits::mcc_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;
|
||||
case PORT_PART:
|
||||
part = _portView;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if constexpr (std::ranges::view<R>) {
|
||||
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;
|
||||
|
||||
std::iterator_traits<const char*>::difference_type idx;
|
||||
if (other.isLocal()) { // for 'local' host is one of static class constants
|
||||
_host = other._host;
|
||||
} else {
|
||||
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());
|
||||
|
||||
idx = std::distance(other._endpoint.c_str(), other._portView.data());
|
||||
_portView = std::string_view(_endpoint.c_str() + idx, other._portView.size());
|
||||
|
||||
_port = other._port;
|
||||
} else {
|
||||
_isValid = false;
|
||||
_endpoint = std::string();
|
||||
_proto = std::string_view();
|
||||
_host = std::string_view();
|
||||
_path = std::string_view();
|
||||
_portView = 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);
|
||||
_portView = std::move(other._portView);
|
||||
} else {
|
||||
_isValid = false;
|
||||
_endpoint = std::string();
|
||||
_proto = std::string_view();
|
||||
_host = std::string_view();
|
||||
_path = std::string_view();
|
||||
_portView = std::string_view();
|
||||
_port = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mcc
|
||||
124
mcc/mcc_netserver_proto.h
Normal file
124
mcc/mcc_netserver_proto.h
Normal file
@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include "mcc_angle.h"
|
||||
|
||||
namespace mcc
|
||||
{
|
||||
|
||||
/*
|
||||
* network communication message format:
|
||||
* keyword[[key-param-delim]param1[param-param-delim][param2]...]<stop-seq>
|
||||
* e.g.
|
||||
* "target 12:23:45.56 00:32:21.978\n"
|
||||
*/
|
||||
|
||||
|
||||
/* message */
|
||||
static constexpr std::string_view MCC_COMMPROTO_STOP_SEQ = "\n";
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYPARAM_DELIM_DEQ = " ";
|
||||
static constexpr std::string_view MCC_COMMPROTO_PARAMPARAM_DELIM_DEQ = " ";
|
||||
|
||||
|
||||
/* server special response keywords */
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_SERVER_RESP_ACK = "ACK"; // ACK
|
||||
static constexpr std::string_view MCC_COMMPROTO_SERVER_RESP_ERROR = "ERROR"; // mount operational error
|
||||
// pre-defined errors
|
||||
static constexpr std::string_view MCC_COMMPROTO_SERVER_RESP_ERROR_INVKEY = "INVKEY"; // invalid keyword
|
||||
static constexpr std::string_view MCC_COMMPROTO_SERVER_RESP_ERROR_INVPAR = "INVPAR"; // invalid parameter
|
||||
|
||||
/* predefined parameters */
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_RADEC_ICRS = "RADEC_ICRS";
|
||||
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_RADEC = "RADEC"; // apparent RA and DEC
|
||||
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_HADEC = "HADEC";
|
||||
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_AZZD = "AZZD";
|
||||
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_AZALT = "AZALT";
|
||||
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_XY = "XY";
|
||||
|
||||
|
||||
static constexpr MccCoordPairKind mcc_str2pairkind(std::string_view spair)
|
||||
{
|
||||
return spair == MCC_COMMPROTO_COORD_KIND_RADEC_ICRS ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS
|
||||
: spair == MCC_COMMPROTO_COORD_KIND_RADEC ? MccCoordPairKind::COORDS_KIND_RADEC_APP
|
||||
: spair == MCC_COMMPROTO_COORD_KIND_HADEC ? MccCoordPairKind::COORDS_KIND_HADEC_APP
|
||||
: spair == MCC_COMMPROTO_COORD_KIND_AZZD ? MccCoordPairKind::COORDS_KIND_AZZD
|
||||
: spair == MCC_COMMPROTO_COORD_KIND_AZALT ? MccCoordPairKind::COORDS_KIND_AZALT
|
||||
: spair == MCC_COMMPROTO_COORD_KIND_XY ? MccCoordPairKind::COORDS_KIND_XY
|
||||
: MccCoordPairKind::COORDS_KIND_GENERIC;
|
||||
}
|
||||
|
||||
/* keywords */
|
||||
|
||||
// NOTE: THE COORDINATES AND TIME-RELATED QUANTITIES CAN BE EXPRESSED IN THE TWO FORMAT:
|
||||
// 1) fixed-point real number, e.g. 123.43987537359 or -0.09775
|
||||
// 2) sexagesimal number, e.g. 10:43:43.12 or -123:54:12.435
|
||||
//
|
||||
// IN THE FIRST CASE ALL NUMBERS MUST BE INTERPRETATED AS DEGREES,
|
||||
// IN THE SECOND CASE NUMBERS MUST BE INTERPRETATED ACCORDING TO ITS TYPE:
|
||||
// ALL TIME-RELATED QUANTITIES AND RA/HA COORDINATES MUST BE EXPRESSED
|
||||
// IN FORMAT 'HOURS:MINUTES:SECONDS' WHILE DEC/ALT/AZ/ZD COORDINATES MUST
|
||||
// BE EXPRESSED AS '+/-DEGREES:ARCMINUTES:ARCSECONDS'
|
||||
|
||||
|
||||
// format of output coordinates:
|
||||
// "COORDFMT FMT-type\n"
|
||||
// e.g.:
|
||||
// "COORDFMT SGM\n"
|
||||
// "COORDFMT\n"
|
||||
//
|
||||
// server must return "ACK" or "ERROR INVPAR" in the case of 'set'-operation and
|
||||
// "ACK COORDFMT FMT-type" in the case of 'get'-operation
|
||||
// e.g.:
|
||||
// "COORDFMT FIX\n" -> "ACK\n"
|
||||
// "COORDFMT SXT\n" -> "ERROR INVPAR\n" (invalid parameter of format type)
|
||||
// "COORDFMT\n" -> "ACK COORDFMT FIX\n"
|
||||
//
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_STR = "COORDFMT";
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_SEXGM_STR = "SGM"; // sexagesimal
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_FIXED_STR = "FIX"; // fixed point
|
||||
|
||||
// precision of returned coordinates
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDPREC_STR = "COORDPREC";
|
||||
|
||||
|
||||
// set/get target coordinates
|
||||
// "TRAGET X-coord Y-coord XY-kind\n", if 'XY-kind' is omitted then one should assume RADEC_ICRS
|
||||
// e.g.:
|
||||
// "TARGET 12.7683487 10:23:09.75 AZZD\n"
|
||||
// "TARGET HADEC\n"
|
||||
// "TARGET\n"
|
||||
//
|
||||
// server must return "ACK" or "ERROR INVPAR" in the case of 'set'-operation and
|
||||
// "ACK TARGET X-coord Y-coord XY-kind" in the case of 'get'-operation
|
||||
// e.g.:
|
||||
// "TARGET 12.7683487 10:23:09.75 AZZD\n" -> "ACK\n"
|
||||
// "TARGET 12.7683487 10:23:09.75 AZZE\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind)
|
||||
//
|
||||
// "TARGET HADEC\n" -> "ACK TARGET 20:21:56.32 -01:32:34.2 HADEC\n"
|
||||
// "TARGET\n" -> "ACK TARGET 20:21:56.32 -01:32:34.2 RADEC_ICRS\n"
|
||||
//
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TARGET_STR = "TARGET";
|
||||
|
||||
// get mount coordinates:
|
||||
// "MOUNT coord-kind", if 'coord-kind' is omitted then coordinates are according to mount type,
|
||||
// i.e., HADEC for equathorial-type mount and AZZD for alt-azimuthal one
|
||||
// e.g.:
|
||||
// "MOUNT RADEC\n" (get mount current apparent RA and DEC mount coordinates)
|
||||
//
|
||||
// server must return "ACK MOUNT X-coord Y-coord XY-kind" or "ERROR INVPAR"
|
||||
// e.g.
|
||||
// "MOUNT AZALT\n" -> "AC MOUNT 1.2332325 54.23321312 AZALT\n"
|
||||
// "MOUNT AZAL\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind)
|
||||
// "MOUNT\n" -> "AC MOUNT 1.2332325 54.23321312 AZZD\n" for alt-azimuthal mount
|
||||
// "MOUNT\n" -> "AC MOUNT 1.2332325 54.23321312 HADEC\n" for equathorial mount
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_MOUNT_STR = "MOUNT";
|
||||
|
||||
|
||||
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TELEMTRY_STR = "TELEMETRY";
|
||||
|
||||
|
||||
} // namespace mcc
|
||||
Loading…
x
Reference in New Issue
Block a user