This commit is contained in:
Timur A. Fatkhullin 2024-06-15 21:23:57 +03:00
parent daf4e1eab9
commit 9a2baa702d
5 changed files with 182 additions and 116 deletions

View File

@ -45,6 +45,12 @@ if (ASIO_LIBRARY)
add_compile_options(ASIO::ASIO) add_compile_options(ASIO::ASIO)
add_compile_definitions(PUBLIC USE_ASIO_LIBRARY) add_compile_definitions(PUBLIC USE_ASIO_LIBRARY)
option(OPENSSL_LIBRARY "Use openssl library for related ASIO-based implementation" ON)
if (OPENSSL_LIBRARY)
find_package(OpenSSL REQUIRED)
add_compile_definitions(PUBLIC USE_OPENSSL_WITH_ASIO)
endif()
endif() endif()

View File

@ -1,6 +1,13 @@
#pragma once #pragma once
/*
ABSTRACT DEVICE COMPONENTS LIBRARY
*/
#include "../common/adc_traits.h" #include "../common/adc_traits.h"
namespace adc namespace adc
@ -13,20 +20,21 @@ namespace adc
* endpoint: proto_mark://host_name:port_num/path * endpoint: proto_mark://host_name:port_num/path
* where "part" is optional for all protocol kinds; * where "part" is optional for all protocol kinds;
* *
* for "local" kind protocol the endpoint string must consists of * for the "local" kind protocol the endpoint string must consists of
* only "proto_mark" and "host_name" fields * only "proto_mark" and "host_name" fields
* (e.g.: local://APP_UNIX_SOCKET) * (e.g.: local://APP_UNIX_SOCKET)
* *
* NOTE: "proto_mark" field is parsed as case-insensitive string!
*
*/ */
class AdcEndpoint class AdcEndpoint
{ {
protected: public:
static constexpr std::string_view protoHostDelim = "://"; static constexpr std::string_view protoHostDelim = "://";
static constexpr std::string_view hostPortDelim = ":"; static constexpr std::string_view hostPortDelim = ":";
static constexpr std::string_view portPathDelim = "/"; static constexpr std::string_view portPathDelim = "/";
public:
enum proto_id_t : uint8_t { enum proto_id_t : uint8_t {
PROTO_ID_LOCAL, PROTO_ID_LOCAL,
PROTO_ID_TCP, PROTO_ID_TCP,
@ -47,6 +55,50 @@ public:
static constexpr std::array validProtoMarks = {protoMarkLocal, protoMarkTCP, protoMarkTLS, static constexpr std::array validProtoMarks = {protoMarkLocal, protoMarkTCP, protoMarkTLS,
protoMarkUDP, protoMarkWS, protoMarkWSS}; protoMarkUDP, protoMarkWS, protoMarkWSS};
// factory methods
template <traits::adc_input_char_range R>
AdcEndpoint createLocal(R&& path)
{
return AdcEndpoint(PROTO_ID_LOCAL, std::string_view(""), -1, std::forward<R>(path));
}
template <traits::adc_input_char_range HT>
AdcEndpoint createTCP(HT&& host, int port)
{
return AdcEndpoint(PROTO_ID_TCP, std::forward<HT>(host), port);
}
#ifdef USE_OPENSSL_WITH_ASIO
template <traits::adc_input_char_range HT>
AdcEndpoint createTLS(HT&& host, int port)
{
return AdcEndpoint(PROTO_ID_TLS, std::forward<HT>(host), port);
}
#endif
template <traits::adc_input_char_range HT>
AdcEndpoint createUDP(HT&& host, int port)
{
return AdcEndpoint(PROTO_ID_UDP, std::forward<HT>(host), port);
}
template <traits::adc_input_char_range HT, traits::adc_input_char_range PTT = std::string_view>
AdcEndpoint createWS(HT&& host, int port, PTT&& path = PTT())
{
return AdcEndpoint(PROTO_ID_WS, std::forward<HT>(host), port, std::forward<PTT>(path));
}
template <traits::adc_input_char_range HT, traits::adc_input_char_range PTT = std::string_view>
AdcEndpoint createWSS(HT&& host, int port, PTT&& path = PTT())
{
return AdcEndpoint(PROTO_ID_WSS, std::forward<HT>(host), port, std::forward<PTT>(path));
}
/* Constructors and destructor */ /* Constructors and destructor */
AdcEndpoint() = default; AdcEndpoint() = default;
@ -255,8 +307,10 @@ public:
auto sz = std::distance(r.begin(), found.begin()); auto sz = std::distance(r.begin(), found.begin());
std::string proto; std::string proto; // case-insensitive!
std::ranges::copy(r | std::views::take(sz), std::back_inserter(proto)); std::ranges::copy(
r | std::views::take(sz) | std::views::transform([](auto ch) -> char { return std::tolower(ch); }),
std::back_inserter(proto));
std::underlying_type_t<proto_id_t> i = 0; std::underlying_type_t<proto_id_t> i = 0;
proto_id_t id = PROTO_ID_UNKNOWN; proto_id_t id = PROTO_ID_UNKNOWN;

View File

@ -43,91 +43,17 @@ void convertToBytes(ByteStorageT& res, const T& v, const Ts&... vs)
} // namespace utils } // namespace utils
namespace traits
{
template <typename T, typename IT>
concept adc_netmessage_c = requires(const T t) { // const methods
requires std::same_as<std::iter_value_t<IT>, char>;
{ t.empty() } -> std::convertible_to<bool>;
{ t.byteSize() } -> std::convertible_to<size_t>;
{ t.bytes() } -> adc_output_char_range;
{ t.byteView() } -> adc_range_of_view_char_range;
{ t.setFromBytes(std::input_iterator<IT>) } -> std::same_as<void>;
};
} // namespace traits
/* /*
Trivial message interface: */
byte storage: just a char-range
*/
template <traits::adc_output_char_range ByteStorageT = std::string, traits::adc_char_view ByteViewT = std::string_view> template <traits::adc_output_char_range ByteStorageT = std::string, traits::adc_char_view ByteViewT = std::string_view>
class AdcNetMessageTrivialInterface class AdcNetMessageInterface
{ {
public: public:
virtual ~AdcNetMessageTrivialInterface() = default; typedef ByteStorageT byte_storage_t;
typedef ByteViewT byte_view_t;
bool empty() const { return std::ranges::distance(_bytes.begin(), _bytes.end()) == 0; } virtual ~AdcNetMessageInterface() = default;
size_t byteSize() const
{
//
return std::ranges::distance(_bytes.begin(), _bytes.end());
}
// get a copy of message bytes
template <traits::adc_output_char_range R>
R bytes() const
{
R r;
std::ranges::copy(_bytes, std::back_inserter(r));
return r;
}
virtual ByteStorageT bytes() const
{
//
return bytes<ByteStorageT>();
}
// get a view of message bytes
template <traits::adc_range_of_view_char_range R>
R bytesView() const
{
R r;
r.emplace_back(_bytes.begin(), _bytes.end());
return r;
}
std::vector<ByteViewT> bytesView() const
{
//
return bytesView<std::vector<ByteViewT>>();
}
protected:
ByteStorageT _bytes;
AdcNetMessageTrivialInterface() = default;
};
/*
interface for more complex messages:
byte storage: std::vector of char-range (sequence of buffers)
*/
template <traits::adc_output_char_range ByteStorageT = std::string, traits::adc_char_view ByteViewT = std::string_view>
class AdcNetMessageSeqInterface
{
public:
virtual ~AdcNetMessageSeqInterface() = default;
bool empty() const bool empty() const
{ {
@ -201,11 +127,11 @@ public:
} }
protected: protected:
std::vector<ByteStorageT> _bytes; std::vector<ByteStorageT> _bytes; // sequence of byte buffers
size_t _reservedNum; size_t _reservedNum;
AdcNetMessageSeqInterface(size_t reserved = 0) : _reservedNum(reserved), _bytes() AdcNetMessageInterface(size_t reserved = 0) : _reservedNum(reserved), _bytes()
{ {
// reserve the "_reservedNum" first elements // reserve the "_reservedNum" first elements
_bytes.resize(_reservedNum); _bytes.resize(_reservedNum);
@ -220,16 +146,24 @@ protected:
// Generic message class // Generic message class
template <traits::adc_output_char_range ByteStorageT = std::string, traits::adc_char_view ByteViewT = std::string_view> template <traits::adc_output_char_range ByteStorageT = std::string, traits::adc_char_view ByteViewT = std::string_view>
class AdcGenericNetMessage : public AdcNetMessageTrivialInterface<ByteStorageT, ByteViewT> class AdcGenericNetMessage : public AdcNetMessageInterface<ByteStorageT, ByteViewT>
{ {
using base_t = AdcNetMessageTrivialInterface<ByteStorageT, ByteViewT>; using base_t = AdcNetMessageInterface<ByteStorageT, ByteViewT>;
public: public:
using typename base_t::byte_storage_t;
using typename base_t::byte_view_t;
using base_t::base_t; using base_t::base_t;
using base_t::bytes; using base_t::bytes;
using base_t::bytesView; using base_t::bytesView;
using base_t::empty; using base_t::empty;
AdcGenericNetMessage() : base_t()
{
// just the single buffer
this->_bytes.resize(1);
}
template <typename T, typename... Ts> template <typename T, typename... Ts>
AdcGenericNetMessage(const T& v, const Ts&... vs) : AdcGenericNetMessage() AdcGenericNetMessage(const T& v, const Ts&... vs) : AdcGenericNetMessage()
@ -241,13 +175,13 @@ public:
template <typename T, typename... Ts> template <typename T, typename... Ts>
void appendBytes(const T& v, const Ts&... vs) void appendBytes(const T& v, const Ts&... vs)
{ {
utils::convertToBytes(this->_bytes, v, vs...); utils::convertToBytes(this->_bytes[0], v, vs...);
} }
template <typename T, typename... Ts> template <typename T, typename... Ts>
void setBytes(const T& v, const Ts&... vs) void setBytes(const T& v, const Ts&... vs)
{ {
this->_bytes = ByteStorageT(); this->_bytes[0] = ByteStorageT();
appendBytes(v, vs...); appendBytes(v, vs...);
} }
@ -257,7 +191,7 @@ public:
void appendFromBytes(IT begin, IT end) void appendFromBytes(IT begin, IT end)
requires std::same_as<std::iter_value_t<IT>, char> requires std::same_as<std::iter_value_t<IT>, char>
{ {
std::copy(begin, end, std::back_inserter(this->_bytes)); std::copy(begin, end, std::back_inserter(this->_bytes[0]));
} }
@ -265,7 +199,7 @@ public:
void setFromBytes(IT begin, IT end) void setFromBytes(IT begin, IT end)
requires std::same_as<std::iter_value_t<IT>, char> requires std::same_as<std::iter_value_t<IT>, char>
{ {
this->_bytes = ByteStorageT(); this->_bytes[0] = ByteStorageT();
appendFromBytes(begin, end); appendFromBytes(begin, end);
} }
@ -283,10 +217,10 @@ static constexpr char ADC_DEFAULT_KEY_TOKEN_DELIMITER[] = " ";
template <const char* TOKEN_DELIM = constants::ADC_DEFAULT_TOKEN_DELIMITER, template <const char* TOKEN_DELIM = constants::ADC_DEFAULT_TOKEN_DELIMITER,
traits::adc_output_char_range ByteStorageT = std::string, traits::adc_output_char_range ByteStorageT = std::string,
traits::adc_char_view ByteViewT = std::string_view> traits::adc_char_view ByteViewT = std::string_view>
class AdcTokenNetMessage : public AdcNetMessageSeqInterface<ByteStorageT, ByteViewT> class AdcTokenNetMessage : public AdcNetMessageInterface<ByteStorageT, ByteViewT>
{ {
// AdcNetMessageSeqInterface<ByteStorageT, ByteViewT>::_bytes - tokens // AdcNetMessageSeqInterface<ByteStorageT, ByteViewT>::_bytes - tokens
using base_t = AdcNetMessageSeqInterface<ByteStorageT, ByteViewT>; using base_t = AdcNetMessageInterface<ByteStorageT, ByteViewT>;
public: public:
static constexpr std::string_view tokenDelimiter{TOKEN_DELIM}; static constexpr std::string_view tokenDelimiter{TOKEN_DELIM};
@ -457,7 +391,7 @@ public:
using base_t::tokens; using base_t::tokens;
AdcKeyTokenNetMessage() AdcKeyTokenNetMessage() : base_t()
{ {
this->_reservedNum = 1; // reserve the first element for keyword this->_reservedNum = 1; // reserve the first element for keyword
this->_bytes.resize(this->_reservedNum); this->_bytes.resize(this->_reservedNum);
@ -599,4 +533,29 @@ public:
} }
}; };
namespace traits
{
template <typename T>
concept adc_netmessage_c = requires {
typename T::byte_storage_t;
typename T::byte_view_t;
std::derived_from<T, AdcNetMessageInterface<typename T::byte_storage_t, typename T::byte_view_t>>;
};
// template <typename T, typename IT>
// concept adc_netmessage_c = requires(const T t) { // const methods
// requires std::same_as<std::iter_value_t<IT>, char>;
// { t.empty() } -> std::convertible_to<bool>;
// { t.byteSize() } -> std::convertible_to<size_t>;
// { t.bytes() } -> adc_output_char_range;
// { t.byteView() } -> adc_range_of_view_char_range;
// { t.setFromBytes(std::input_iterator<IT>) } -> std::same_as<void>;
// };
} // namespace traits
} // namespace adc } // namespace adc

View File

@ -11,12 +11,13 @@ ABSTRACT DEVICE COMPONENTS LIBRARY
#include <utility> #include <utility>
#include "adc_netmsg.h"
namespace adc namespace adc
{ {
template <typename NetMessageT, typename ImplT> template <typename ImplT>
class AdcNetService class AdcNetService
{ {
protected: protected:
@ -56,13 +57,13 @@ public:
} }
template <typename... ArgTs> template <traits::adc_netmessage_c NetMessageT, typename... ArgTs>
auto asyncSend(const NetMessageT& msg, const timeout_drtn_t& timeout = defaultSendTimeout, ArgTs&&... args) auto asyncSend(const NetMessageT& msg, const timeout_drtn_t& timeout = defaultSendTimeout, ArgTs&&... args)
{ {
return _impl.asyncSend(msg, timeout, std::forward<ArgTs>(args)...); return _impl.asyncSend(msg, timeout, std::forward<ArgTs>(args)...);
} }
template <typename... ArgTs> template <traits::adc_netmessage_c NetMessageT, typename... ArgTs>
auto asyncReceive(const timeout_drtn_t& timeout = defaultRecvTimeout, ArgTs&&... args) auto asyncReceive(const timeout_drtn_t& timeout = defaultRecvTimeout, ArgTs&&... args)
{ {
return _impl.asyncReceive(timeout, std::forward<ArgTs>(args)...); return _impl.asyncReceive(timeout, std::forward<ArgTs>(args)...);
@ -78,14 +79,14 @@ public:
return _impl.connect(endpoint, timeout, std::forward<ArgTs>(args)...); return _impl.connect(endpoint, timeout, std::forward<ArgTs>(args)...);
} }
template <typename... ArgTs> template <traits::adc_netmessage_c NetMessageT, typename... ArgTs>
auto send(const NetMessageT& msg, const timeout_drtn_t& timeout = defaultSendTimeout, ArgTs&&... args) auto send(const NetMessageT& msg, const timeout_drtn_t& timeout = defaultSendTimeout, ArgTs&&... args)
{ {
return _impl.send(msg, timeout, std::forward<ArgTs>(args)...); return _impl.send(msg, timeout, std::forward<ArgTs>(args)...);
} }
template <typename... ArgTs> template <traits::adc_netmessage_c NetMessageT, typename... ArgTs>
NetMessageT receive(const timeout_drtn_t& timeout = defaultRecvTimeout, ArgTs&&... args) NetMessageT receive(const timeout_drtn_t& timeout = defaultRecvTimeout, ArgTs&&... args)
{ {
return _impl.receive(timeout, std::forward<ArgTs>(args)...); return _impl.receive(timeout, std::forward<ArgTs>(args)...);
@ -102,11 +103,11 @@ public:
namespace traits namespace traits
{ {
// template <typename T> template <typename T>
// concept adc_netservice_c = requires { concept adc_netservice_c = requires {
// typename T::impl_t; typename T::impl_t;
// std::derived_from<AdcNetService<typename T::impl_t>>; std::derived_from<T, AdcNetService<typename T::impl_t>>;
// }; };
} // namespace traits } // namespace traits

View File

@ -10,15 +10,19 @@
#include <future> #include <future>
#include "adc_netservice.h"
#ifdef USE_ASIO_LIBRARY #ifdef USE_ASIO_LIBRARY
#include <asio/awaitable.hpp> // #include <asio/awaitable.hpp>
#include <asio/basic_datagram_socket.hpp> #include <asio/basic_datagram_socket.hpp>
#include <asio/basic_seq_packet_socket.hpp> #include <asio/basic_seq_packet_socket.hpp>
#include <asio/basic_stream_socket.hpp> #include <asio/basic_stream_socket.hpp>
#include <asio/compose.hpp> #include <asio/compose.hpp>
#include <asio/experimental/awaitable_operators.hpp> // #include <asio/experimental/awaitable_operators.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/read_until.hpp> #include <asio/read_until.hpp>
#include <asio/ssl.hpp>
#include <asio/ssl/stream.hpp>
#include <asio/steady_timer.hpp> #include <asio/steady_timer.hpp>
#include <asio/streambuf.hpp> #include <asio/streambuf.hpp>
#include <asio/use_future.hpp> #include <asio/use_future.hpp>
@ -26,11 +30,14 @@
#include <concepts> #include <concepts>
#include "adc_netmsg.h"
namespace adc::impl namespace adc::impl
{ {
template <typename NetMessageT, typename InetProtoT> template <typename InetProtoT>
class AdcNetServiceASIO : public InetProtoT class AdcNetServiceASIO : public InetProtoT
{ {
public: public:
@ -63,7 +70,12 @@ public:
switch (state) { switch (state) {
case starting: case starting:
state = cancel_timer; state = cancel_timer;
return _socket.async_connect(endpoint, std::move(self)); if constexpr (std::derived_from<socket_t,
asio::ssl::stream<typename socket_t::lowest_layer_type>>) {
return _socket.lowest_layer().async_connect(endpoint, std::move(self));
} else {
return _socket.async_connect(endpoint, std::move(self));
}
break; break;
case cancel_timer: case cancel_timer:
timer->cancel(); timer->cancel();
@ -78,7 +90,9 @@ public:
token, _socket); token, _socket);
} }
template <typename TimeoutT, asio::completion_token_for<void(std::error_code)> CompletionTokenT> template <traits::adc_netmessage_c NetMessageT,
typename TimeoutT,
asio::completion_token_for<void(std::error_code)> CompletionTokenT>
auto asynSend(const NetMessageT& msg, const TimeoutT& timeout, CompletionTokenT&& token) auto asynSend(const NetMessageT& msg, const TimeoutT& timeout, CompletionTokenT&& token)
{ {
enum { starting, cancel_timer }; enum { starting, cancel_timer };
@ -93,10 +107,11 @@ public:
// wrapper // wrapper
return asio::async_compose<CompletionTokenT, void(std::error_code)>( return asio::async_compose<CompletionTokenT, void(std::error_code)>(
[buff = std::move(buff), timer = std::move(timer), state = starting, this]( [buff = std::move(buff), timer = std::move(timer), state = starting, this](
auto& self, const std::error_code& ec = {}, size_t sz = 0) mutable { auto& self, const std::error_code& ec = {}, size_t = 0) mutable {
if (!ec) { if (!ec) {
switch (state) { switch (state) {
case starting: case starting:
state = cancel_timer;
if constexpr (std::derived_from< if constexpr (std::derived_from<
socket_t, asio::basic_stream_socket<typename socket_t::protocol_type>>) { socket_t, asio::basic_stream_socket<typename socket_t::protocol_type>>) {
return asio::async_write(_socket, buff, std::move(self)); return asio::async_write(_socket, buff, std::move(self));
@ -124,7 +139,7 @@ public:
} }
template <typename TimeoutT, typename CompletionTokenT> template <traits::adc_netmessage_c NetMessageT, typename TimeoutT, typename CompletionTokenT>
auto asyncReceive(const TimeoutT& timeout, CompletionTokenT&& token) auto asyncReceive(const TimeoutT& timeout, CompletionTokenT&& token)
{ {
enum { starting, cancel_timer }; enum { starting, cancel_timer };
@ -135,15 +150,16 @@ public:
return asio::async_compose<CompletionTokenT, void(const std::error_code&, const NetMessageT&)>( return asio::async_compose<CompletionTokenT, void(const std::error_code&, const NetMessageT&)>(
[timer = std::move(timer), out_flags = std::move(out_flags), state = starting, this]( [timer = std::move(timer), out_flags = std::move(out_flags), state = starting, this](
auto& self, const std::error_code& ec = {}, size_t sz = 0) mutable { auto& self, const std::error_code& ec = {}, size_t = 0) mutable {
if (!ec) { if (!ec) {
switch (state) { switch (state) {
case starting: case starting:
state = cancel_timer;
if constexpr (std::derived_from< if constexpr (std::derived_from<
socket_t, asio::basic_stream_socket<typename socket_t::protocol_type>>) { socket_t, asio::basic_stream_socket<typename socket_t::protocol_type>>) {
return asio::async_read_until( return asio::async_read_until(
_socket, _streamBuffer, _socket, _streamBuffer,
[this](auto begin, auto end) { this->matchCondition(begin, end); }, [this](auto begin, auto end) { return this->matchCondition(begin, end); },
std::move(self)); std::move(self));
} else if constexpr (std::derived_from<socket_t, asio::basic_datagram_socket< } else if constexpr (std::derived_from<socket_t, asio::basic_datagram_socket<
typename socket_t::protocol_type>>) { typename socket_t::protocol_type>>) {
@ -200,20 +216,43 @@ public:
ftr.get(); ftr.get();
} }
template <typename TimeoutT> template <traits::adc_netmessage_c NetMessageT, typename TimeoutT>
auto send(const NetMessageT& msg, const TimeoutT& timeout) auto send(const NetMessageT& msg, const TimeoutT& timeout)
{ {
std::future<void> ftr = asyncSend(msg, timeout, asio::use_future); std::future<void> ftr = asyncSend(msg, timeout, asio::use_future);
ftr.get(); ftr.get();
} }
template <typename TimeoutT> template <traits::adc_netmessage_c NetMessageT, typename TimeoutT>
auto receive(const TimeoutT& timeout) auto receive(const TimeoutT& timeout)
{ {
std::future<NetMessageT> ftr = asyncReceive(timeout, asio::use_future); std::future<NetMessageT> ftr = asyncReceive(timeout, asio::use_future);
return ftr.get(); return ftr.get();
} }
std::error_code close(asio::socket_base::shutdown_type stype = asio::socket_base::shutdown_both)
{
std::error_code ec;
if constexpr (std::derived_from<socket_t, asio::ssl::stream<typename socket_t::lowest_layer_type>>) {
_socket.shutdown(ec); // shutdown OpenSSL stream
if (!ec) {
_socket.lowest_layer().shutdown(stype, ec);
if (!ec) {
_socket.lowest_layer().close(ec);
}
}
} else {
_socket.shutdown(stype, ec);
if (!ec) {
_socket.close(ec);
}
}
return ec;
}
protected: protected:
socket_t& _socket; socket_t& _socket;
@ -242,4 +281,11 @@ protected:
} // namespace adc::impl } // namespace adc::impl
namespace adc
{
typedef AdcNetService<impl::AdcNetServiceASIO<asio::ip::tcp>> AdcNetServiceAsioTcp;
} // namespace adc
#endif #endif