This commit is contained in:
Timur A. Fatkhullin 2025-09-24 12:36:02 +03:00
parent fedc324410
commit b1a48d2b77
7 changed files with 2702 additions and 8 deletions

View File

@ -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;

View 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

View 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

View File

@ -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
View 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

View 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
View 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