818 lines
31 KiB
C++
818 lines
31 KiB
C++
#pragma once
|
|
|
|
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
|
|
|
/* BASIC NETWORK PROTOCOL DEFINITIONS */
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
#include <string_view>
|
|
#include "mcc_angle.h"
|
|
#include "mcc_defaults.h"
|
|
#include "mcc_generics.h"
|
|
#include "mcc_utils.h"
|
|
|
|
namespace mcc::network
|
|
{
|
|
|
|
/*
|
|
* The network protocol is the ASCII-based, case-sensitive textual protocol.
|
|
* The "client-server" communication is performed through messages.
|
|
* The message is a minimal unit of this communication.
|
|
* The model of network communication is a simple "client-server" one, i.e.,
|
|
* client asks - server responds.
|
|
*
|
|
* network communication message format:
|
|
* <keyword>[[<key-param-delim>]<param1>[<param-param-delim>][<param2>]...]<stop-seq>
|
|
*
|
|
* where
|
|
* <keyword> - mandatory message keyword (one or more ASCII symbols)
|
|
* <key-param-delim>
|
|
*
|
|
* e.g.
|
|
* "TARGET 12:23:45.56 00:32:21.978\n"
|
|
*/
|
|
|
|
|
|
/* low-level network message format definitions */
|
|
|
|
static constexpr std::string_view MCC_COMMPROTO_STOP_SEQ = "\n";
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYPARAM_DELIM_SEQ = " ";
|
|
static constexpr std::string_view MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ = ";";
|
|
static constexpr std::string_view MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ = ",";
|
|
|
|
|
|
/* server special keywords */
|
|
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR = "ACK"; // ACK
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR = "ERROR"; // mount operational error
|
|
// pre-defined errors
|
|
static constexpr std::string_view MCC_COMMPROTO_SERVER_ERROR_INVKEY_STR = "INVKEY"; // invalid keyword
|
|
static constexpr std::string_view MCC_COMMPROTO_SERVER_ERROR_INVPAR_STR = "INVPAR"; // invalid parameter
|
|
|
|
|
|
/* server control keywords */
|
|
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_RESTART_SERVER_STR = "RESTART"; // restart server
|
|
|
|
|
|
/* BELOW IS ONE OF THE PROTOCOL OPTIONS CORRESPONDING MCC_GENERIC_MOUNT_C CONCEPT */
|
|
|
|
|
|
/* predefined parameters */
|
|
|
|
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_RADEC_ICRS = "RADEC_ICRS"; // ICRS RA and DEC
|
|
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"; // apparent HA and DEC
|
|
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_AZZD = "AZZD"; // azimuth and zenithal distance
|
|
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_AZALT = "AZALT"; // azimuth and altitude
|
|
static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_XY = "XY"; // hardware (encoder) coordinates
|
|
|
|
|
|
// 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;
|
|
// }
|
|
|
|
template <mcc::traits::mcc_char_range R>
|
|
static constexpr MccCoordPairKind mcc_str2pairkind(R&& spair)
|
|
{
|
|
if constexpr (std::is_pointer_v<std::decay_t<R>>) {
|
|
return mcc_str2pairkind(std::string_view{spair});
|
|
}
|
|
|
|
const auto hash = mcc::utils::FNV1aHash(std::forward<R>(spair));
|
|
|
|
return hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_RADEC_ICRS) ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS
|
|
: hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_RADEC) ? MccCoordPairKind::COORDS_KIND_RADEC_APP
|
|
: hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_HADEC) ? MccCoordPairKind::COORDS_KIND_HADEC_APP
|
|
: hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_AZZD) ? MccCoordPairKind::COORDS_KIND_AZZD
|
|
: hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_AZALT) ? MccCoordPairKind::COORDS_KIND_AZALT
|
|
: hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_XY) ? MccCoordPairKind::COORDS_KIND_XY
|
|
: MccCoordPairKind::COORDS_KIND_GENERIC;
|
|
}
|
|
|
|
|
|
static constexpr std::string_view mcc_pairkind2str(MccCoordPairKind kind)
|
|
{
|
|
return kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COMMPROTO_COORD_KIND_RADEC_ICRS
|
|
: kind == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COMMPROTO_COORD_KIND_RADEC
|
|
: kind == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COMMPROTO_COORD_KIND_HADEC
|
|
: kind == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COMMPROTO_COORD_KIND_AZZD
|
|
: kind == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COMMPROTO_COORD_KIND_AZALT
|
|
: kind == MccCoordPairKind::COORDS_KIND_XY ? MCC_COMMPROTO_COORD_KIND_XY
|
|
: "UNKNOWN";
|
|
}
|
|
|
|
|
|
/* keywords */
|
|
|
|
// NOTE: THE COORDINATES AND TIME-RELATED QUANTITIES CAN BE EXPRESSED IN THE TWO FORMATS:
|
|
// 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'
|
|
//
|
|
// USER-ENTERED (FROM NETWORK CLIENTS) COORDINATE PAIR CAN BE PROVIDED IN A MIXED FORM, I.E.,
|
|
// 12.34436658678 10:32:11.432 or
|
|
// 10:32:11.432 12.34436658678
|
|
//
|
|
// SERVER-RESPONDED COORDINATES ARE ALWAYS IN THE SAME FORMAT, SEXAGESIMAL OR FIXED-POINT
|
|
//
|
|
|
|
// 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 (number of decimal places) of returned coordinates:
|
|
// "COORDPREC seconds-prec arcseconds-prec\n"
|
|
// seconds-prec - precision of hour-based coordinates (RA and HA) or time-related quantities
|
|
// arcseconds-prec - precision of degree-based coordinates (DEC, AZ, ZD, ALT)
|
|
// precision must be given as non-negative integer number
|
|
// e.g.
|
|
// "COORDPREC 2,1\n" (output sexagesimal RA=12:34:56.67, DEC=32:54:21.9)
|
|
//
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDPREC_STR = "COORDPREC";
|
|
|
|
|
|
// set/get target coordinates
|
|
// "TARGET 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 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" -> "ACK MOUNT 1.2332325 54.23321312 AZALT\n"
|
|
// "MOUNT AZAL\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind)
|
|
// "MOUNT\n" -> "ACK MOUNT 1.2332325 54.23321312 AZZD\n" for alt-azimuthal mount
|
|
// "MOUNT\n" -> "ACK 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_TELEMETRY_STR = "TELEMETRY";
|
|
|
|
// init mount
|
|
// "INIT\n"
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_INIT_STR = "INIT";
|
|
|
|
// stop any movements
|
|
// "STOP\n"
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_STOP_STR = "STOP";
|
|
|
|
// slew mount and track target:
|
|
// "SLEW\n"
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SLEW_STR = "SLEW";
|
|
|
|
// slew mount and stop:
|
|
// "MOVE\n"
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_MOVE_STR = "MOVE";
|
|
|
|
// track target
|
|
// "TRACK\n"
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TRACK_STR = "TRACK";
|
|
|
|
// get mount status
|
|
// "STATUS\n"
|
|
static constexpr std::string_view MCC_COMMPROTO_KEYWORD_STATUS_STR = "STATUS";
|
|
|
|
// valid keywords
|
|
static constexpr std::array MCC_COMMPROTO_VALID_KEYS = {
|
|
MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR, MCC_COMMPROTO_KEYWORD_COORDFMT_STR,
|
|
MCC_COMMPROTO_KEYWORD_COORDPREC_STR, MCC_COMMPROTO_KEYWORD_TARGET_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR,
|
|
MCC_COMMPROTO_KEYWORD_TELEMETRY_STR, MCC_COMMPROTO_KEYWORD_INIT_STR, MCC_COMMPROTO_KEYWORD_STOP_STR,
|
|
MCC_COMMPROTO_KEYWORD_SLEW_STR, MCC_COMMPROTO_KEYWORD_MOVE_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR,
|
|
MCC_COMMPROTO_KEYWORD_STATUS_STR};
|
|
|
|
|
|
// hashes of valid keywords
|
|
static constexpr std::array MCC_COMMPROTO_VALID_KEYS_HASH = []<size_t... Is>(std::index_sequence<Is...>) {
|
|
return std::array{mcc::utils::FNV1aHash(MCC_COMMPROTO_VALID_KEYS[Is])...};
|
|
}(std::make_index_sequence<MCC_COMMPROTO_VALID_KEYS.size()>());
|
|
|
|
|
|
|
|
template <typename T>
|
|
concept mcc_netmsg_valid_keys_c = requires(T t) {
|
|
// std::array of valid message keywords
|
|
[]<size_t N>(std::array<std::string_view, N>) {
|
|
// to ensure T::NETMSG_VALID_KEYWORDS can be used as compile-time constant
|
|
static constexpr auto v0 = T::NETMSG_VALID_KEYWORDS[0];
|
|
return v0;
|
|
}(T::NETMSG_VALID_KEYWORDS);
|
|
|
|
// std::array of valid message keywords hashes
|
|
[]<size_t N>(std::array<size_t, N>) {
|
|
// to ensure T::NETMSG_VALID_KEYWORD_HASHES can be used as compile-time constant
|
|
static constexpr auto v0 = T::NETMSG_VALID_KEYWORD_HASHES[0];
|
|
return v0;
|
|
}(T::NETMSG_VALID_KEYWORD_HASHES);
|
|
|
|
requires T::NETMSG_VALID_KEYWORDS.size() == T::NETMSG_VALID_KEYWORD_HASHES.size();
|
|
};
|
|
|
|
|
|
struct MccNetMessageValidKeywords {
|
|
static constexpr std::array NETMSG_VALID_KEYWORDS = {
|
|
MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR,
|
|
MCC_COMMPROTO_KEYWORD_COORDFMT_STR, MCC_COMMPROTO_KEYWORD_COORDPREC_STR,
|
|
MCC_COMMPROTO_KEYWORD_TARGET_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR,
|
|
MCC_COMMPROTO_KEYWORD_TELEMETRY_STR, MCC_COMMPROTO_KEYWORD_INIT_STR,
|
|
MCC_COMMPROTO_KEYWORD_STOP_STR, MCC_COMMPROTO_KEYWORD_SLEW_STR,
|
|
MCC_COMMPROTO_KEYWORD_MOVE_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR,
|
|
MCC_COMMPROTO_KEYWORD_STATUS_STR};
|
|
|
|
|
|
// hashes of valid keywords
|
|
static constexpr std::array NETMSG_VALID_KEYWORD_HASHES = []<size_t... Is>(std::index_sequence<Is...>) {
|
|
return std::array{mcc::utils::FNV1aHash(NETMSG_VALID_KEYWORDS[Is])...};
|
|
}(std::make_index_sequence<NETMSG_VALID_KEYWORDS.size()>());
|
|
|
|
constexpr static const size_t* isKeywordValid(std::string_view key)
|
|
{
|
|
const auto hash = mcc::utils::FNV1aHash(key);
|
|
|
|
for (auto const& h : NETMSG_VALID_KEYWORD_HASHES) {
|
|
if (h == hash) {
|
|
return &h;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
static_assert(mcc_netmsg_valid_keys_c<MccNetMessageValidKeywords>, "");
|
|
|
|
|
|
template <typename T>
|
|
concept mcc_netmessage_c = requires(T t) { T(); };
|
|
|
|
|
|
|
|
template <mcc::traits::mcc_char_range BYTEREPR_T = std::string_view,
|
|
mcc_netmsg_valid_keys_c BASE_T = MccNetMessageValidKeywords>
|
|
class MccNetMessage
|
|
{
|
|
protected:
|
|
class DefaultDeserializer : protected mcc::utils::MccSimpleDeserializer
|
|
{
|
|
protected:
|
|
using base_t = mcc::utils::MccSimpleDeserializer;
|
|
|
|
inline static mcc::MccCelestialPointDeserializer _cpDeserializer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ};
|
|
inline static mcc::MccEqtHrzCoordsDeserializer _eqhrDeserializer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ};
|
|
inline static mcc::MccTelemetryDataDeserializer _telemetryDeserializer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ};
|
|
|
|
public:
|
|
DefaultDeserializer() : base_t(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ) {}
|
|
|
|
template <traits::mcc_input_char_range IR, typename VT>
|
|
std::error_code operator()(IR&& bytes, VT& value)
|
|
{
|
|
if constexpr (mcc_telemetry_data_c<VT>) {
|
|
return _telemetryDeserializer(std::forward<IR>(bytes), value);
|
|
} else if constexpr (mcc_eqt_hrz_coord_c<VT>) {
|
|
return _eqhrDeserializer(std::forward<IR>(bytes), value);
|
|
} else if constexpr (mcc_celestial_point_c<VT>) {
|
|
return _cpDeserializer(std::forward<IR>(bytes), value);
|
|
} else if constexpr (std::same_as<VT, MccCoordPairKind>) {
|
|
value = MccCoordStrToPairKind(bytes);
|
|
if (value == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
|
|
return std::make_error_code(std::errc::invalid_argument);
|
|
}
|
|
} else if constexpr (std::same_as<VT, MccCoordinateSerializer::SerializedCoordFormat>) {
|
|
std::string v;
|
|
auto ec = (*this)(std::forward<IR>(bytes), v);
|
|
if (ec) {
|
|
return ec;
|
|
}
|
|
|
|
if (v.compare(MCC_COMMPROTO_KEYWORD_COORDFMT_SEXGM_STR) == 0) {
|
|
value = MccCoordinateSerializer::SerializedCoordFormat::CFMT_SGM;
|
|
} else if (v.compare(MCC_COMMPROTO_KEYWORD_COORDFMT_FIXED_STR) == 0) {
|
|
value = MccCoordinateSerializer::SerializedCoordFormat::CFMT_DEGREES;
|
|
} else {
|
|
return std::make_error_code(std::errc::invalid_argument);
|
|
}
|
|
} else if constexpr (std::same_as<VT, MccCoordinateSerializer::SexagesimalCoordPrec>) {
|
|
std::vector<int64_t> v;
|
|
auto ec = (*this)(std::forward<IR>(bytes), v);
|
|
if (ec) {
|
|
return ec;
|
|
}
|
|
|
|
auto hprec = v[0];
|
|
value.hour_prec = hprec > 0 ? (hprec < std::numeric_limits<decltype(value.hour_prec)>::max()
|
|
? hprec
|
|
: std::numeric_limits<decltype(value.hour_prec)>::max())
|
|
: 2;
|
|
if (v.size() == 1) {
|
|
value.deg_prec = 1;
|
|
} else {
|
|
auto dprec = v[1];
|
|
value.deg_prec = dprec > 0 ? dprec < std::numeric_limits<decltype(value.deg_prec)>::max()
|
|
? dprec
|
|
: std::numeric_limits<decltype(value.deg_prec)>::max()
|
|
: 1;
|
|
}
|
|
} else {
|
|
return base_t::operator()(std::forward<IR>(bytes), value);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
};
|
|
|
|
|
|
class DefaultSerializer
|
|
{
|
|
friend class MccNetMessage;
|
|
|
|
MccCoordinateSerializer::SerializedCoordFormat _coordFmt{};
|
|
MccCoordinateSerializer::SexagesimalCoordPrec _coordPrec{};
|
|
|
|
public:
|
|
template <typename T, traits::mcc_output_char_range OR>
|
|
void operator()(const T& value, OR& bytes)
|
|
{
|
|
if constexpr (std::is_arithmetic_v<T>) {
|
|
std::format_to(std::back_inserter(bytes), "{}", value);
|
|
} else if constexpr (std::convertible_to<T, std::string>) {
|
|
std::ranges::copy(static_cast<std::string>(value), std::back_inserter(bytes));
|
|
} else if constexpr (std::constructible_from<std::string, T>) {
|
|
std::ranges::copy(std::string(value), std::back_inserter(bytes));
|
|
} else if constexpr (traits::mcc_char_range<T>) {
|
|
std::ranges::copy(std::string(value.begin(), value.end()), std::back_inserter(bytes));
|
|
// } else if constexpr (std::same_as<T, MccCoordPairKind>) {
|
|
// std::ranges::copy(mcc_pairkind2str(value), std::back_inserter(bytes));
|
|
} else if constexpr (traits::mcc_time_duration_c<T>) {
|
|
(*this)(value.count(), bytes);
|
|
} else if constexpr (mcc_telemetry_data_c<T>) {
|
|
static MccTelemetryDataSerializer sr;
|
|
|
|
sr.setDelimiter(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ);
|
|
sr.setFormat(_coordFmt);
|
|
sr.setPrecision(_coordPrec);
|
|
|
|
sr(value, bytes);
|
|
} else if constexpr (mcc_eqt_hrz_coord_c<T>) {
|
|
static MccEqtHrzCoordsSerializer sr;
|
|
|
|
sr.setDelimiter(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ);
|
|
sr.setFormat(_coordFmt);
|
|
sr.setPrecision(_coordPrec);
|
|
|
|
sr(value, bytes);
|
|
} else if constexpr (mcc_celestial_point_c<T>) {
|
|
MccCelestialPointSerializer sr;
|
|
|
|
sr.setDelimiter(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ);
|
|
sr.setFormat(_coordFmt);
|
|
sr.setPrecision(_coordPrec);
|
|
|
|
sr(value, bytes);
|
|
} else if constexpr (std::ranges::range<T>) {
|
|
auto sz = std::ranges::size(value);
|
|
if (sz == 0) {
|
|
return;
|
|
}
|
|
|
|
(*this)(*value.begin(), bytes); // the first element
|
|
|
|
if (sz > 1) {
|
|
for (auto const& el : value | std::views::drop(1)) {
|
|
std::ranges::copy(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, std::back_inserter(bytes));
|
|
(*this)(el, bytes);
|
|
}
|
|
}
|
|
} else if constexpr (std::same_as<T, std::error_code>) {
|
|
std::format_to(std::back_inserter(bytes), "{}{}{}{}{}", value.value(),
|
|
MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, value.message(), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ,
|
|
value.category().name());
|
|
} else if constexpr (std::formattable<T, char>) {
|
|
std::format_to(std::back_inserter(bytes), "{}", value);
|
|
} else {
|
|
static_assert(false, "UNSUPPORTED TYPE!!!");
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
public:
|
|
typedef BASE_T valid_keys_t;
|
|
typedef BYTEREPR_T byte_repr_t;
|
|
|
|
enum MccNetMessageError { ERROR_OK, ERROR_EMPTY_MESSAGE, ERROR_INVALID_KEYWORD, ERROR_EMPTY_KEYWORD };
|
|
|
|
MccNetMessage() = default;
|
|
|
|
template <traits::mcc_input_char_range KT, typename... PTs>
|
|
MccNetMessage(KT&& key, PTs&&... params)
|
|
requires traits::mcc_output_char_range<BYTEREPR_T>
|
|
{
|
|
construct(_defaultSerializer, std::forward<KT>(key), std::forward<PTs>(params)...);
|
|
}
|
|
|
|
template <traits::mcc_input_char_range R>
|
|
constexpr MccNetMessage(const R& msg)
|
|
requires traits::mcc_input_char_range<BYTEREPR_T>
|
|
{
|
|
fromCharRange(msg);
|
|
}
|
|
|
|
// constexpr MccNetMessage(const BYTEREPR_T& msg)
|
|
// requires traits::mcc_input_char_range<BYTEREPR_T>
|
|
// {
|
|
// fromCharRange(msg);
|
|
// }
|
|
|
|
virtual ~MccNetMessage() = default;
|
|
|
|
template <traits::mcc_input_char_range KT>
|
|
constexpr bool withKey(const KT& key) const
|
|
{
|
|
if constexpr (std::is_pointer_v<std::decay_t<KT>>) {
|
|
return withKey(std::string_view{key});
|
|
}
|
|
|
|
return mcc::utils::FNV1aHash(key) == _keywordHash;
|
|
}
|
|
|
|
|
|
template <traits::mcc_view_or_output_char_range R>
|
|
R keyword() const
|
|
{
|
|
if constexpr (traits::mcc_char_view<R>) {
|
|
return R{_keyword.begin(), _keyword.end()};
|
|
} else {
|
|
R r;
|
|
std::ranges::copy(_keyword, std::back_inserter(r));
|
|
|
|
return r;
|
|
}
|
|
}
|
|
|
|
std::string_view keyword() const
|
|
{
|
|
return _keyword;
|
|
}
|
|
|
|
|
|
size_t paramSize() const
|
|
{
|
|
return _params.size();
|
|
}
|
|
|
|
template <std::ranges::range R>
|
|
R params(size_t start_idx = 0, size_t Nelemes = std::numeric_limits<size_t>::max()) const
|
|
requires(traits::mcc_view_or_output_char_range<R> || traits::mcc_range_of_char_range<R>)
|
|
{
|
|
if (start_idx >= _params.size()) {
|
|
return R{};
|
|
}
|
|
|
|
auto stop_idx = start_idx + Nelemes - 1;
|
|
if (stop_idx >= _params.size()) {
|
|
stop_idx = _params.size() - 1;
|
|
}
|
|
|
|
if constexpr (traits::mcc_range_of_char_range<R>) { // returm parameters as array
|
|
using el_t = std::ranges::range_value_t<R>;
|
|
|
|
R r;
|
|
if constexpr (traits::mcc_char_view<el_t> || traits::mcc_output_char_range<el_t>) {
|
|
for (size_t i = start_idx; i <= stop_idx; ++i) {
|
|
auto& el = _params[i];
|
|
std::back_inserter(r) = el_t{el.begin(), el.end()};
|
|
}
|
|
} else {
|
|
static_assert(false, "UNSUPPORTED RANGE TYPE!!!");
|
|
}
|
|
|
|
return r;
|
|
} else {
|
|
if constexpr (traits::mcc_char_view<R>) { // return joined parameters as a single char-range
|
|
return R{_params[start_idx].begin(), _params[stop_idx].end()};
|
|
} else {
|
|
R r;
|
|
std::ranges::copy(std::string_view{_params[start_idx].begin(), _params[stop_idx].end()},
|
|
std::back_inserter(r));
|
|
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string_view params(size_t start_idx = 0, size_t Nelemes = std::numeric_limits<size_t>::max()) const
|
|
{
|
|
return params<std::string_view>(start_idx, Nelemes);
|
|
}
|
|
|
|
template <traits::mcc_view_or_output_char_range R>
|
|
R param(size_t idx) const
|
|
{
|
|
if (idx >= _params.size()) {
|
|
return {};
|
|
}
|
|
|
|
if constexpr (traits::mcc_char_view<R>) {
|
|
return R{_params[idx].begin(), _params[idx].end()};
|
|
} else {
|
|
R r;
|
|
std::ranges::copy(_params[idx], std::back_inserter(r));
|
|
|
|
return r;
|
|
}
|
|
}
|
|
|
|
std::string_view param(size_t idx) const
|
|
{
|
|
if (idx >= _params.size()) {
|
|
return {};
|
|
}
|
|
|
|
return _params[idx];
|
|
}
|
|
|
|
|
|
template <typename T, typename DeserFuncT>
|
|
std::expected<T, std::error_code> paramValue(size_t idx, DeserFuncT&& deser_func) const
|
|
{
|
|
if (idx >= _params.size()) {
|
|
return std::unexpected{std::make_error_code(std::errc::argument_out_of_domain)};
|
|
}
|
|
|
|
T val;
|
|
|
|
auto ec = std::forward<DeserFuncT>(deser_func)(_params[idx], val);
|
|
if (ec) {
|
|
return std::unexpected(ec);
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
std::expected<T, std::error_code> paramValue(size_t idx) const
|
|
{
|
|
return paramValue<T>(idx, _defaultDeserializer);
|
|
}
|
|
|
|
|
|
template <traits::mcc_view_or_output_char_range R>
|
|
R byteRepr() const
|
|
{
|
|
if constexpr (traits::mcc_char_view<R>) {
|
|
return R{_msgBuffer.begin(), _msgBuffer.end()};
|
|
} else {
|
|
R r;
|
|
std::ranges::copy(_msgBuffer, std::back_inserter(r));
|
|
|
|
return r;
|
|
}
|
|
}
|
|
|
|
std::string_view byteRepr() const
|
|
{
|
|
return byteRepr<std::string_view>();
|
|
}
|
|
|
|
template <traits::mcc_input_char_range KT, typename... PTs>
|
|
std::error_code construct(KT&& key, PTs&&... params)
|
|
requires traits::mcc_output_char_range<BYTEREPR_T>
|
|
{
|
|
return construct(_defaultSerializer, std::forward<KT>(key), std::forward<PTs>(params)...);
|
|
}
|
|
|
|
//
|
|
// serializing function SerFuncT - a callable with the signature:
|
|
// template<typename T, mcc_output_char_range R>
|
|
// void ser_func(const T& val, R&& buffer)
|
|
//
|
|
template <typename SerFuncT, traits::mcc_input_char_range KT, typename... PTs>
|
|
std::error_code construct(SerFuncT&& ser_func, KT&& key, PTs&&... params)
|
|
requires(traits::mcc_output_char_range<BYTEREPR_T> &&
|
|
!traits::mcc_input_char_range<std::remove_cvref_t<SerFuncT>>)
|
|
{
|
|
if constexpr (std::is_pointer_v<std::decay_t<KT>>) {
|
|
return construct(std::forward<SerFuncT>(ser_func), std::string_view(key), std::forward<PTs>(params)...);
|
|
}
|
|
|
|
|
|
if (!std::ranges::size(key)) {
|
|
return std::make_error_code(std::errc::invalid_argument);
|
|
}
|
|
|
|
auto r = valid_keys_t::isKeywordValid(key);
|
|
if (!r) {
|
|
return std::make_error_code(std::errc::argument_out_of_domain);
|
|
}
|
|
|
|
_keywordHash = *r;
|
|
|
|
_msgBuffer = BYTEREPR_T{};
|
|
|
|
std::ranges::copy(std::forward<KT>(key), std::back_inserter(_msgBuffer));
|
|
// _keyword = {_msgBuffer.begin(), _msgBuffer.end()};
|
|
size_t key_idx = std::distance(_msgBuffer.begin(), _msgBuffer.end());
|
|
std::vector<size_t> par_idx;
|
|
|
|
_params.clear();
|
|
|
|
if constexpr (sizeof...(PTs)) {
|
|
std::ranges::copy(MCC_COMMPROTO_KEYPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer));
|
|
|
|
convertFunc(std::forward<SerFuncT>(ser_func), par_idx, std::forward<PTs>(params)...);
|
|
|
|
for (size_t i = 0; i < par_idx.size(); i += 2) {
|
|
_params.emplace_back(_msgBuffer.begin() + par_idx[i], _msgBuffer.begin() + par_idx[i + 1]);
|
|
}
|
|
}
|
|
|
|
_keyword = std::string_view{_msgBuffer.begin(), _msgBuffer.begin() + key_idx};
|
|
|
|
return {};
|
|
}
|
|
|
|
template <traits::mcc_input_char_range R>
|
|
constexpr MccNetMessageError fromCharRange(const R& r)
|
|
{
|
|
if constexpr (std::is_pointer_v<std::decay_t<R>>) {
|
|
return fromCharRange(std::string_view(r));
|
|
}
|
|
|
|
|
|
if (std::ranges::size(r) == 0) {
|
|
return ERROR_EMPTY_MESSAGE;
|
|
}
|
|
|
|
std::string_view key;
|
|
|
|
// auto prev_msg_buff = _msgBuffer;
|
|
|
|
if constexpr (traits::mcc_output_char_range<BYTEREPR_T>) {
|
|
_msgBuffer = BYTEREPR_T{};
|
|
std::ranges::copy(r, std::back_inserter(_msgBuffer));
|
|
} else {
|
|
_msgBuffer = {std::begin(r), std::end(r)};
|
|
}
|
|
|
|
auto found = std::ranges::search(_msgBuffer, MCC_COMMPROTO_KEYPARAM_DELIM_SEQ);
|
|
|
|
if (found.empty()) { // only keyword
|
|
key = mcc::utils::trimSpaces(std::string_view{_msgBuffer.begin(), _msgBuffer.end()});
|
|
} else {
|
|
key = mcc::utils::trimSpaces(std::string_view{_msgBuffer.begin(), found.begin()});
|
|
}
|
|
|
|
auto kv = valid_keys_t::isKeywordValid(key);
|
|
if (!kv) {
|
|
// _msgBuffer = prev_msg_buff; // restore previous netmessage state
|
|
return ERROR_INVALID_KEYWORD;
|
|
}
|
|
|
|
_keywordHash = *kv;
|
|
_keyword = key;
|
|
|
|
if (!found.empty()) { // params ...
|
|
_params.clear();
|
|
auto pr =
|
|
std::views::split(std::string_view{found.end(), _msgBuffer.end()}, MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ);
|
|
for (auto const& p : pr) {
|
|
_params.emplace_back(p.begin(), p.end());
|
|
}
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
protected:
|
|
size_t _keywordHash{};
|
|
std::string_view _keyword{};
|
|
std::vector<std::string_view> _params{};
|
|
|
|
BYTEREPR_T _msgBuffer{};
|
|
|
|
inline static DefaultDeserializer _defaultDeserializer{};
|
|
|
|
DefaultSerializer _defaultSerializer{};
|
|
|
|
template <typename T, typename... Ts>
|
|
void convertFunc(std::vector<size_t>& idx, const T& par, const Ts&... pars)
|
|
{
|
|
if constexpr (std::same_as<T, MccCoordinateSerializer::SerializedCoordFormat>) {
|
|
_defaultSerializer._coordFmt = par;
|
|
if constexpr (sizeof...(Ts)) {
|
|
convertFunc(idx, pars...);
|
|
}
|
|
} else if constexpr (std::same_as<T, MccCoordinateSerializer::SexagesimalCoordPrec>) {
|
|
_defaultSerializer._coordPrec = par;
|
|
if constexpr (sizeof...(Ts)) {
|
|
convertFunc(idx, pars...);
|
|
}
|
|
} else {
|
|
convertFunc(_defaultSerializer, idx, par, pars...);
|
|
// idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
|
|
|
// _defaultSerializer(par, _msgBuffer);
|
|
|
|
// idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
|
|
|
// if constexpr (sizeof...(Ts)) {
|
|
// std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer));
|
|
|
|
// convertFunc(idx, pars...);
|
|
// }
|
|
}
|
|
};
|
|
|
|
template <typename SerFuncT, typename T, typename... Ts>
|
|
void convertFunc(SerFuncT&& ser_func, std::vector<size_t>& idx, const T& par, const Ts&... pars)
|
|
requires(!std::same_as<std::remove_cvref_t<SerFuncT>, std::vector<size_t>>)
|
|
{
|
|
if constexpr (std::derived_from<std::remove_cvref_t<SerFuncT>, DefaultSerializer>) {
|
|
if constexpr (std::same_as<T, MccCoordinateSerializer::SerializedCoordFormat>) {
|
|
_defaultSerializer._coordFmt = par;
|
|
} else if constexpr (std::same_as<T, MccCoordinateSerializer::SexagesimalCoordPrec>) {
|
|
_defaultSerializer._coordPrec = par;
|
|
} else {
|
|
idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
|
|
|
std::forward<SerFuncT>(ser_func)(par, _msgBuffer);
|
|
|
|
idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
|
|
|
if constexpr (sizeof...(Ts)) {
|
|
std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer));
|
|
}
|
|
}
|
|
} else {
|
|
idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
|
|
|
std::forward<SerFuncT>(ser_func)(par, _msgBuffer);
|
|
|
|
idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end()));
|
|
|
|
if constexpr (sizeof...(Ts)) {
|
|
std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer));
|
|
}
|
|
}
|
|
|
|
if constexpr (sizeof...(Ts)) {
|
|
convertFunc(std::forward<SerFuncT>(ser_func), idx, pars...);
|
|
}
|
|
}
|
|
};
|
|
|
|
static_assert(MccNetMessage<std::string, MccNetMessageValidKeywords>{"ACK"}.withKey("ACK"));
|
|
static_assert(MccNetMessage{"ACK"}.withKey("ACK"));
|
|
|
|
} // namespace mcc::network
|