ADC/net/adc_device_netserver.h
Timur A. Fatkhullin 6acc1f94ba CMakeLists.txt: requires C++23 standard
CMakeLists.txt: add compiler version checks
adc_netserver.h: add AdcTrivialLogger class (use of 'deduced this'
feature of C++23 standard); AdcGenericNetServer class now has basic
based on std::basic_ostream logging capability
2024-11-11 18:36:54 +03:00

414 lines
16 KiB
C++

#pragma once
#include <memory>
#include <system_error>
#include "adc_device_netmsg.h"
#include "adc_netserver.h"
#include "../device/adc_device_concepts.h"
namespace adc
{
enum class AdcDeviceNetServerSessionError : int {
ERROR_OK,
ERROR_NULL_DEVICE,
ERROR_UNKNOWN_PROTO_KWD,
ERROR_UNKNOWN_DEVICE_ID,
ERROR_NO_DEVICE_ID,
ERROR_NO_PROTO_KWDNAME,
ERROR_NO_PROTO_ATTRNAME,
ERROR_NO_PROTO_ATTRVALUE,
ERROR_NO_PROTO_CMDNAME,
ERROR_UNKNOWN_ERROR
};
}
// place here to allow clang compilation
namespace std
{
template <>
class is_error_code_enum<adc::AdcDeviceNetServerSessionError> : public true_type
{
};
} // namespace std
namespace adc
{
struct AdcDeviceNetServerSessionErrorCategory : std::error_category {
AdcDeviceNetServerSessionErrorCategory() : std::error_category() {}
const char* name() const noexcept
{
return "ADC_DEVICE_NESERVER_SESSION";
}
std::string message(int ec) const
{
AdcDeviceNetServerSessionError err = static_cast<AdcDeviceNetServerSessionError>(ec);
switch (err) {
case AdcDeviceNetServerSessionError::ERROR_OK:
return "OK";
case AdcDeviceNetServerSessionError::ERROR_NULL_DEVICE:
return "device was not bound";
case AdcDeviceNetServerSessionError::ERROR_UNKNOWN_PROTO_KWD:
return "invalid formatted message: unrecognized message keyword";
case AdcDeviceNetServerSessionError::ERROR_UNKNOWN_DEVICE_ID:
return "invalid device identificator";
case AdcDeviceNetServerSessionError::ERROR_NO_DEVICE_ID:
return "invalid formatted message: device identificator is omitted";
case AdcDeviceNetServerSessionError::ERROR_NO_PROTO_KWDNAME:
return "invalid formatted message: keyword name is omitted";
case AdcDeviceNetServerSessionError::ERROR_NO_PROTO_CMDNAME:
return "invalid formatted message: command name is omitted";
case AdcDeviceNetServerSessionError::ERROR_NO_PROTO_ATTRNAME:
return "invalid formatted message: attribute name is omitted";
case AdcDeviceNetServerSessionError::ERROR_NO_PROTO_ATTRVALUE:
return "invalid formatted message: attribute value is omitted";
case AdcDeviceNetServerSessionError::ERROR_UNKNOWN_ERROR:
return "catch unhandled exception";
default:
return "UNKNOWN";
}
}
static const AdcDeviceNetServerSessionErrorCategory& get()
{
static const AdcDeviceNetServerSessionErrorCategory constInst;
return constInst;
}
};
} // namespace adc
namespace std
{
inline std::error_code make_error_code(adc::AdcDeviceNetServerSessionError ec)
{
return std::error_code(static_cast<int>(ec), adc::AdcDeviceNetServerSessionErrorCategory::get());
}
} // namespace std
namespace adc
{
template <typename IdentT = std::string>
class AdcDeviceNetServer : public AdcGenericNetServer<IdentT>
{
public:
using typename AdcGenericNetServer<IdentT>::server_ident_t;
// type for serialized data (attr/command ID, attr values etc...)
typedef std::vector<char> serialized_t;
protected:
class DeviceWrapper
{
public:
// using char_range_t = std::span<const char>;
using char_range_t = std::span<char>;
private:
serialized_t _id;
// std::function<serialized_t()> _get_id = []() -> serialized_t { throw std::system_error(); };
std::function<serialized_t(const serialized_t&)> _get_attr = [](auto) -> serialized_t {
throw std::system_error(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NULL_DEVICE));
};
std::function<void(const serialized_t&, const serialized_t&)> _set_attr = [](auto, auto) {
throw std::system_error(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NULL_DEVICE));
};
std::function<void(const serialized_t&)> _exec_cmd = [](auto) {
throw std::system_error(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NULL_DEVICE));
};
public:
DeviceWrapper() : _id()
{
// null device
std::string_view id{"NULL-DEVICE"};
std::ranges::copy(id, std::back_inserter(_id));
};
template <interfaces::adc_device_c DeviceT,
// typename IdSerialT = traits::adc_char_identity<typename DeviceT::ident_t>,
typename AttrIdDeserialT = traits::adc_char_identity<typename DeviceT::attr_ident_t>,
typename CmdIdDeserialT = traits::adc_char_identity<typename DeviceT::cmd_ident_t>>
DeviceWrapper(DeviceT* dev_ptr,
const serialized_t& id,
AttrIdDeserialT&& attr_id_deser_func = {},
CmdIdDeserialT&& cmd_id_deser_func = {})
: _id(id)
{
_get_attr = [dev_ptr, wrapper = traits::adc_pf_wrapper(std::forward<AttrIdDeserialT>(attr_id_deser_func)),
this](const auto& attr_name) mutable {
auto attr_id = std::get<0>(wrapper)(attr_name);
auto& attr = (*dev_ptr)[attr_id];
// auto& attr = dev_ptr->operator[](attr_id);
auto val = attr.serialize();
return val;
};
_set_attr = [dev_ptr, wrapper = traits::adc_pf_wrapper(std::forward<AttrIdDeserialT>(attr_id_deser_func))](
const auto& attr_name, const auto& val) mutable {
auto attr_id = std::get<0>(wrapper)(attr_name);
(*dev_ptr)[attr_id].deserialize(val);
};
_exec_cmd = [dev_ptr, wrapper = traits::adc_pf_wrapper(std::forward<CmdIdDeserialT>(cmd_id_deser_func))](
const auto& cmd_name) mutable {
auto cmd_id = std::get<0>(wrapper)(cmd_name);
(*dev_ptr)(cmd_id);
};
}
serialized_t ident() const
{
// return _get_id();
return _id;
}
serialized_t getAttr(const serialized_t& attr_name)
{
return _get_attr(attr_name);
}
void setAttr(const serialized_t& attr_name, const serialized_t& val)
{
_set_attr(attr_name, val);
}
void exec(const serialized_t& cmd_name)
{
_exec_cmd(cmd_name);
}
};
static inline DeviceWrapper nullDevice{};
std::unordered_map<void*, DeviceWrapper> _devices;
public:
template <interfaces::adc_netservice_c NetServiceT, traits::adc_hashable_c SessionIdentT = std::string>
class Session : public std::enable_shared_from_this<Session<NetServiceT, SessionIdentT>>
{
public:
typedef SessionIdentT netsession_ident_t;
typedef NetServiceT netservice_t;
// typedef AdcDeviceNetServer* netsession_ctx_t;
struct netsession_ctx_t {
AdcDeviceNetServer* serverPtr;
std::chrono::milliseconds recvTimeout;
std::chrono::milliseconds sendTimeout;
};
typedef std::vector<char> message_t;
// Session(const netsession_ident_t& id, netservice_t srv, AdcDeviceNetServer* srv_ptr)
Session(const netsession_ident_t& id, netservice_t srv, netsession_ctx_t ctx)
: _ident(id),
_netService(std::move(srv)),
// _serverPtr(srv_ptr),
// _bindDevice(srv_ptr->_devices.size() ? &srv_ptr->_devices.begin()->second
// : &AdcDeviceNetServer::nullDevice)
_serverPtr(ctx.serverPtr),
_bindDevice(ctx.serverPtr->_devices.size() ? &ctx.serverPtr->_devices.begin()->second
: &AdcDeviceNetServer::nullDevice),
_recvTimeout(ctx.recvTimeout),
_sendTimeout(ctx.sendTimeout)
{
}
netsession_ident_t ident() const
{
return _ident;
}
void start()
{
auto self(this->shared_from_this());
_netService.asyncReceive(
[self, this](netservice_t::async_callback_err_t ec, message_t msg) {
if (ec) {
// std::string str("asyncReceive operation completed with error: ");
// netservice_t::formatError(ec, str);
// _serverPtr->errorMessage(str);
_serverPtr->logError("asyncReceive operation completed with error: {}",
netservice_t::formattableError(ec));
stop();
} else {
auto msg_sptr = std::make_shared<message_t>(std::move(msg));
AdcDeviceProtoMessage dev_msg(*msg_sptr);
processMessage(dev_msg);
_netService.asyncSend(
*msg_sptr,
[self, msg_sptr, this](netservice_t::async_callback_err_t ec) {
if (ec) {
// std::string str("asyncSend operation completed with error: ");
// netservice_t::formatError(ec, str);
// _serverPtr->errorMessage(str);
_serverPtr->logError("asyncSend operation completed with error: {}",
netservice_t::formattableError(ec));
stop();
} else {
start();
}
},
_sendTimeout);
}
},
_recvTimeout);
}
void stop()
{
_netService.close();
}
protected:
netsession_ident_t _ident;
netservice_t _netService;
AdcDeviceNetServer* _serverPtr;
AdcDeviceNetServer::DeviceWrapper* _bindDevice;
std::chrono::milliseconds _recvTimeout = std::chrono::hours(12);
std::chrono::milliseconds _sendTimeout = std::chrono::seconds(5);
void processMessage(auto& msg)
{
typedef std::decay_t<decltype(msg)> msg_t;
typedef std::vector<serialized_t> attr_vec_t;
attr_vec_t attrs;
auto get_elem = [&attrs](size_t idx) {
if (idx < attrs.size()) {
return attrs[idx];
} else {
return serialized_t();
}
};
try {
if (msg.isACK()) {
msg.ack();
} else if (msg.isDEVICE()) {
attrs = msg.template attrs<attr_vec_t>(0, 1);
if (attrs.size()) {
auto dev_name = get_elem(0);
bool found = false;
for (auto& [ptr, dev_wr] : _serverPtr->_devices) {
if (std::ranges::equal(dev_wr.ident(), dev_name)) {
_bindDevice = &dev_wr;
found = true;
break;
}
}
if (found) {
msg.ack(msg_t::DEVICE_KEY, attrs);
} else {
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_UNKNOWN_DEVICE_ID));
}
} else {
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NO_DEVICE_ID));
}
} else if (msg.isNAMES()) {
std::vector<serialized_t> names;
for (auto& [ptr, dev_wr] : _serverPtr->_devices) {
names.emplace_back(dev_wr.ident());
}
msg.ack(msg_t::NAMES_KEY, names);
} else if (msg.isHELLO()) {
msg.ack(msg_t::HELLO_KEY, _serverPtr->_serverIdent);
} else if (msg.isGET()) { // get attribute value
attrs = msg.template attrs<attr_vec_t>(0, 1);
if (attrs.size()) {
auto val = _bindDevice->getAttr(get_elem(0));
msg.ack(msg_t::GET_KEY, get_elem(0), val);
} else { // no attr name!
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NO_PROTO_ATTRNAME));
}
} else if (msg.isSET()) {
attrs = msg.template attrs<attr_vec_t>(0);
if (attrs.size() >= 2) {
auto val = msg.template joinAttrs<serialized_t>(1);
_bindDevice->setAttr(get_elem(0), val);
msg.ack(msg_t::SET_KEY, get_elem(0), val);
} else { // no attr name or its value!
if (attrs.size() == 1) {
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NO_PROTO_ATTRVALUE));
} else {
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NO_PROTO_ATTRNAME));
}
}
} else if (msg.isCMD()) {
attrs = msg.template attrs<attr_vec_t>(0, 1);
if (attrs.size()) {
auto cmd_name = get_elem(0);
_bindDevice->exec(cmd_name);
msg.ack(msg_t::CMD_KEY, cmd_name);
} else { // no cmd name!
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_NO_PROTO_CMDNAME));
}
} else {
msg.err(std::make_error_code(AdcDeviceNetServerSessionError::ERROR_UNKNOWN_PROTO_KWD));
}
} catch (std::system_error ex) {
msg.err(ex.code());
}
}
};
// using AdcGenericNetServer::AdcGenericNetServer;
AdcDeviceNetServer(const server_ident_t& id) : AdcGenericNetServer<IdentT>(id), _devices() {}
virtual ~AdcDeviceNetServer() = default;
template <interfaces::adc_device_c DeviceT,
typename IdSerialT = traits::adc_char_identity<serialized_t>,
typename AttrIdDeserialT = traits::adc_char_identity<typename DeviceT::attr_ident_t>,
typename CmdIdDeserialT = traits::adc_char_identity<typename DeviceT::cmd_ident_t>>
AdcDeviceNetServer& addDevice(DeviceT* dev_ptr,
IdSerialT&& id_ser_func = {}, // serializer of device ID
AttrIdDeserialT&& attr_id_deser_func = {}, // deserializer of attribute ID
CmdIdDeserialT&& cmd_id_deser_func = {}) // deserializer of command ID
{
auto id = std::forward<IdSerialT>(id_ser_func)(dev_ptr->ident());
_devices.try_emplace(dev_ptr, dev_ptr, id, std::forward<AttrIdDeserialT>(attr_id_deser_func),
std::forward<CmdIdDeserialT>(cmd_id_deser_func));
return *this;
}
template <interfaces::adc_device_c DeviceT>
AdcDeviceNetServer& delDevice(DeviceT* dev_ptr)
{
_devices.erase(dev_ptr);
return *this;
}
};
} // namespace adc