diff --git a/CMakeLists.txt b/CMakeLists.txt index b65c014..1644d69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ set(ADC_NETWORK_HEADERS net/adc_net_concepts.h net/adc_device_netmsg.h net/adc_device_netserver.h + net/adc_device_netclient.h ) diff --git a/net/adc_device_netclient.h b/net/adc_device_netclient.h index 017835a..cf94990 100644 --- a/net/adc_device_netclient.h +++ b/net/adc_device_netclient.h @@ -4,6 +4,82 @@ #include "adc_device_netmsg.h" #include "adc_netclient.h" + +namespace adc +{ + +enum class AdcDeviceNetClientSessionError : int { + ERROR_OK, + ERROR_INVALID_SERVER_RESPOND, + ERROR_UNEXPECTED_SERVER_RESPOND, + ERROR_UNKNOWN_ERROR +}; + +} + + +// place here to allow clang compilation +namespace std +{ +template <> +class is_error_code_enum : public true_type +{ +}; + + +} // namespace std + +namespace adc +{ + + +struct AdcDeviceNetClientSessionErrorCategory : std::error_category { + AdcDeviceNetClientSessionErrorCategory() : std::error_category() {} + + const char* name() const noexcept + { + return "ADC_DEVICE_NESERVER_SESSION"; + } + + std::string message(int ec) const + { + AdcDeviceNetClientSessionError err = static_cast(ec); + + switch (err) { + case AdcDeviceNetClientSessionError::ERROR_OK: + return "OK"; + case AdcDeviceNetClientSessionError::ERROR_INVALID_SERVER_RESPOND: + return "invalid server respond message"; + case AdcDeviceNetClientSessionError::ERROR_UNEXPECTED_SERVER_RESPOND: + return "unexpected server respond message"; + case AdcDeviceNetClientSessionError::ERROR_UNKNOWN_ERROR: + return "catch unhandled exception"; + default: + return "UNKNOWN"; + } + } + + static const AdcDeviceNetClientSessionErrorCategory& get() + { + static const AdcDeviceNetClientSessionErrorCategory constInst; + return constInst; + } +}; + +} // namespace adc + +namespace std +{ + +inline std::error_code make_error_code(adc::AdcDeviceNetClientSessionError ec) +{ + return std::error_code(static_cast(ec), adc::AdcDeviceNetClientSessionErrorCategory::get()); +} + +} // namespace std + + + namespace adc { @@ -17,6 +93,8 @@ public: class Session : public std::enable_shared_from_this> { public: + enum ServerResponseType { RESP_INVALID, RESP_ACK, RESP_ERROR, RESP_UNEXPECTED }; + typedef SessionIdentT netsession_ident_t; typedef NetServiceT netservice_t; @@ -67,6 +145,125 @@ public: } + // ADC device helper methods (blocking) + + typedef std::vector default_server_resp_t; + + // get names of devices + template + R deviceNames(ServerResponseType& rtype) + { + typename netservice_t::send_msg_t bytes; + + AdcDeviceProtoMessage msg(bytes); + msg.names(); + using m_t = std::remove_cvref_t; + + // expected respond: ACK NAMES DEV1 DEV2 ... + // return DEV1 DEV2 ... (or error description 'code category what') + return _getFromServer(m_t::NAMES_KEY, bytes, rtype); + } + + default_server_resp_t deviceNames(ServerResponseType& rtype) + { + return deviceNames(rtype); + } + + // bind device to the client + template + R bindDevice(DevNameT&& dev_name, ServerResponseType& rtype) + { + typename netservice_t::send_msg_t bytes; + + AdcDeviceProtoMessage msg(bytes); + msg.device(std::forward(dev_name)); + using m_t = std::remove_cvref_t; + + // expected respond: ACK DEVICE DEV_NAME + // return DEV_NAME ... (or error description 'code category what') + return _getFromServer(m_t::DEVICE_KEY, bytes, rtype); + } + + template + default_server_resp_t bindDevice(DevNameT&& dev_name, ServerResponseType& rtype) + { + return bindDevice(std::forward(dev_name), rtype); + } + + + // execute a command + template + R exec(CmdNameT&& cmd_name, ServerResponseType& rtype) + { + typename netservice_t::send_msg_t bytes; + + AdcDeviceProtoMessage msg(bytes); + msg.cmd(std::forward(cmd_name)); + using m_t = std::remove_cvref_t; + + + // expected respond: ACK CMD CMD_NAME + // return CMD_NAME ... (or error description 'code category what') + return _getFromServer(m_t::CMD_KEY, bytes, rtype); + } + + template + default_server_resp_t exec(CmdNameT&& cmd_name, ServerResponseType& rtype) + { + return exec(std::forward(cmd_name), rtype); + } + + + // get an attribute value + template + R getAttr(AttrNameT&& attr_name, ServerResponseType& rtype) + { + typename netservice_t::send_msg_t bytes; + + AdcDeviceProtoMessage msg(bytes); + msg.get(std::forward(attr_name)); + using m_t = std::remove_cvref_t; + + // expected respond: ACK GET ATTR_NAME ATTR_VALUE + // return ATTR_NAME ATTR_VALUE (or error description 'code category what') + return _getFromServer(m_t::GET_KEY, bytes, rtype); + } + + template + default_server_resp_t getAttr(AttrNameT&& attr_name, ServerResponseType& rtype) + { + return getAttr(std::forward(attr_name), rtype); + } + + + // set an attribute value + template + R setAttr(AttrNameT&& attr_name, ServerResponseType& rtype, ValueT&& value, ValueTs&&... values) + { + typename netservice_t::send_msg_t bytes; + + AdcDeviceProtoMessage msg(bytes); + msg.set(std::forward(attr_name), std::forward(value), std::forward(values)...); + using m_t = std::remove_cvref_t; + + // expected respond: ACK SET ATTR_NAME ATTR_VALUE + // return ATTR_NAME ATTR_VALUE (or error description 'code category what') + return _getFromServer(m_t::SET_KEY, bytes, rtype); + } + + template + default_server_resp_t setAttr(AttrNameT&& attr_name, + ServerResponseType& rtype, + ValueT&& value, + ValueTs&&... values) + { + return setAttr(std::forward(attr_name), rtype, + std::forward(value), std::forward(values)...); + } + protected: netsession_ident_t _ident; @@ -76,29 +273,60 @@ public: std::chrono::milliseconds _recvTimeout = std::chrono::seconds(5); std::chrono::milliseconds _sendTimeout = std::chrono::seconds(5); + // main 'run' method + + virtual void run() = 0; // helper methods - std::function - _defaultRecvCallback = [this](auto err, auto msg) { - if (err) { - _clientPtr->logError("An error occured while receiving server respond: {}", - netservice_t::formattableError(err)); - } else { - AdcDeviceProtoMessage dev_msg(msg); - if (!dev_msg.isValid()) { - _clientPtr->logError("Invalid server respond"); - return; - } - if (dev_msg.isACK()) { - } else if (dev_msg.isERROR()) { - } else { - _clientPtr->logError("Unexpectable server respond"); - } + template + R _getFromServer(std::string_view key, const typename netservice_t::send_msg_t& bytes, ServerResponseType& type) + { + auto rbytes = sendRecv(bytes); + AdcDeviceProtoMessage dev_msg(rbytes); + + if (!dev_msg.isValid()) { + throw std::system_error(AdcDeviceNetClientSessionError::ERROR_INVALID_SERVER_RESPOND); + } + + if (dev_msg.isACK(key)) { + type = RESP_ACK; + return dev_msg.template attrs(1); + } else if (dev_msg.isERROR()) { + type = RESP_ERROR; + return dev_msg.template attrs(); + } else { + throw std::system_error(AdcDeviceNetClientSessionError::ERROR_UNEXPECTED_SERVER_RESPOND); + } + } + + + void _sendRecvCallbackWrapper(auto err, + typename netservice_t::recv_msg_t msg, + auto&& ack_func, + auto&& error_func) + { + if (err) { + _clientPtr->logError("An error occured while receiving server respond: {}", + netservice_t::formattableError(err)); + } else { + AdcDeviceProtoMessage dev_msg(msg); + + if (!dev_msg.isValid()) { + _clientPtr->logError("Invalid server respond"); + return; } - }; + + if (dev_msg.isACK()) { + std::forward(ack_func)(dev_msg.attrs()); + } else if (dev_msg.isERROR()) { + std::forward(error_func)(dev_msg.attrs()); + } else { + _clientPtr->logError("Unexpectable server respond (msg key: {})", dev_msg.key()); + } + } + }; template auto asyncSendRecv(const SendMsgT& send_msg, TokenT&& token) @@ -115,6 +343,14 @@ public: }, _sendTimeout); } + + template + auto sendRecv(const SendMsgT& send_msg) + { + _netService.send(send_msg, _sendTimeout); + return _netService.receive(_recvTimeout); + } + }; // end of 'Session' class declaration using base_t::base_t; diff --git a/net/adc_device_netmsg.h b/net/adc_device_netmsg.h index 982c7a5..36aa2ee 100644 --- a/net/adc_device_netmsg.h +++ b/net/adc_device_netmsg.h @@ -369,6 +369,17 @@ public: return isKey(ACK_KEY_IDX); } + // ACK KEY_NAME ... + bool isACK(std::string_view key) const + { + auto ats = attrs(); + if (std::ranges::size(ats)) { + return isKey(ACK_KEY_IDX) && (key == *ats.begin()); + } else { + return false; + } + } + bool isSET() const { return isKey(SET_KEY_IDX); diff --git a/net/adc_net_concepts.h b/net/adc_net_concepts.h index 18c86b0..afebab7 100644 --- a/net/adc_net_concepts.h +++ b/net/adc_net_concepts.h @@ -52,15 +52,15 @@ using adc_common_duration_t = adc_duration_common_type_t -concept adc_async_callback_err_t = std::convertible_to, bool> || +concept adc_async_callback_err_c = std::convertible_to, bool> || requires(const std::remove_cvref_t err) { err.operator bool(); }; // concepts for asynchronous opereration callback callable // 1) the type must be a callable with at least 1 input argument -// 2) the first argument type must satisfy the concept adc_async_callback_err_t +// 2) the first argument type must satisfy the concept adc_async_callback_err_c template -concept adc_async_callback_t = traits::adc_is_callable && (traits::adc_func_traits::arity >= 1) && - adc_async_callback_err_t>; +concept adc_async_callback_c = traits::adc_is_callable && (traits::adc_func_traits::arity >= 1) && + adc_async_callback_err_c>; /* struct NetService { @@ -107,17 +107,17 @@ concept adc_netservice_c = requires(SRVT srv, const SRVT srv_const) { // underlying protocol // asynchronous operation error - requires adc_async_callback_err_t; + requires adc_async_callback_err_c; // callback callables for asynchronous operations - requires adc_async_callback_t; - requires adc_async_callback_t; - requires adc_async_callback_t; + requires adc_async_callback_c; + requires adc_async_callback_c; + requires adc_async_callback_c; // acceptor type requires std::is_class_v; - requires adc_async_callback_t; + requires adc_async_callback_c; requires requires(typename SRVT::acceptor_t acc, const typename SRVT::acceptor_t acc_const) { acc.asyncAccept(std::declval(), std::declval()); @@ -136,9 +136,6 @@ concept adc_netservice_c = requires(SRVT srv, const SRVT srv_const) { // asynchronous (non-blocking) operations - // srv.asyncAccept(std::declval(), std::declval(), - // std::declval()); srv.asyncConnect(std::declval(), std::declval(), @@ -152,7 +149,7 @@ concept adc_netservice_c = requires(SRVT srv, const SRVT srv_const) { std::declval()); // synchronous (blocking) operations - // srv.accept(std::declval(), std::declval()); + // it is assumed these methods throw an exception if error occures srv.connect(std::declval(), std::declval());