mirror of
https://github.com/boostorg/mqtt5.git
synced 2026-05-04 11:54:13 +02:00
[mqtt-client] boost-like project folder structure
Summary: resolves T12767 Reviewers: ivica Reviewed By: ivica Subscribers: miljen Tags: #spacetime Maniphest Tasks: T12767 Differential Revision: https://repo.mireo.local/D25970
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
import glob
|
||||
|
||||
Import('ctx')
|
||||
|
||||
ctx.Project('#/3rdParty/openssl')
|
||||
|
||||
sources = [
|
||||
'example/tcp.cpp',
|
||||
# commented out to speed up compiling
|
||||
# 'example/openssl-tls.cpp',
|
||||
# 'example/websocket-tcp.cpp',
|
||||
# 'example/websocket-tls.cpp',
|
||||
'example/src/run_examples.cpp',
|
||||
]
|
||||
|
||||
test_sources = [
|
||||
# 'test/experimental/cancellation.cpp',
|
||||
# 'test/experimental/message_assembling.cpp',
|
||||
# 'test/experimental/memory.cpp',
|
||||
# 'test/experimental/mutex.cpp',
|
||||
# 'test/experimental/uri_parse.cpp',
|
||||
'test/unit/test/serialization.cpp',
|
||||
'test/unit/test/publish_send_op.cpp',
|
||||
'test/unit/test/client_broker.cpp',
|
||||
'test/unit/test/coroutine.cpp',
|
||||
'test/unit/src/run_tests.cpp'
|
||||
]
|
||||
|
||||
includes = [
|
||||
'include',
|
||||
'#/3rdParty/openssl/include'
|
||||
]
|
||||
|
||||
test_includes = [
|
||||
'include',
|
||||
'test/unit/include',
|
||||
'#/3rdParty/openssl/include'
|
||||
]
|
||||
|
||||
libs = {
|
||||
'all': Split('openssl'),
|
||||
}
|
||||
|
||||
defines = {
|
||||
'all' : ['BOOST_ALL_NO_LIB', 'BOOST_NO_TYPEID', '_REENTRANT'],
|
||||
'toolchain:llvm' : ['BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF'],
|
||||
}
|
||||
|
||||
test_defines = {
|
||||
'all' : ['BOOST_ALL_NO_LIB', 'BOOST_NO_TYPEID', 'BOOST_TEST_NO_MAIN=1','_REENTRANT'],
|
||||
'toolchain:llvm' : ['BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF'],
|
||||
}
|
||||
|
||||
# add ' -ftemplate-backtrace-limit=1' to cxxflags when necessary
|
||||
cxxflags = {
|
||||
'all': Split('-fexceptions -frtti -Wall -Wno-unused-local-typedefs -ftemplate-backtrace-limit=1'),
|
||||
}
|
||||
|
||||
frameworks = {
|
||||
'os:macos': Split('Security'),
|
||||
}
|
||||
|
||||
ctx.Program(name='mqtt-examples',
|
||||
source=sources,
|
||||
includes=includes,
|
||||
defines=defines,
|
||||
CXXFLAGs=cxxflags,
|
||||
libraries=libs,
|
||||
frameworks=frameworks,
|
||||
)
|
||||
|
||||
ctx.Program(name='mqtt-tests',
|
||||
source=test_sources,
|
||||
includes=test_includes,
|
||||
defines=test_defines,
|
||||
CXXFLAGs=cxxflags,
|
||||
frameworks=frameworks,
|
||||
)
|
||||
@@ -0,0 +1,282 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
|
||||
#include <async_mqtt5.hpp>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
template <typename StreamBase>
|
||||
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
|
||||
static constexpr auto client = asio::ssl::stream_base::client;
|
||||
static constexpr auto server = asio::ssl::stream_base::server;
|
||||
};
|
||||
|
||||
template <typename StreamBase>
|
||||
void assign_tls_sni(
|
||||
const authority_path& ap,
|
||||
asio::ssl::context& ctx,
|
||||
asio::ssl::stream<StreamBase>& stream
|
||||
) {
|
||||
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
constexpr char spacetime_ca[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDYDCCAkigAwIBAgIUZZsEKT8m+uGZRNMaTuCiZBchSU4wDQYJKoZIhvcNAQEL\n"
|
||||
"BQAwHTEbMBkGA1UEAwwSTWlyZW8gU3BhY2VUaW1lIENBMB4XDTIzMDIwNzIwMzU1\n"
|
||||
"MFoXDTMzMDIwNDIwMzU1MFowHTEbMBkGA1UEAwwSTWlyZW8gU3BhY2VUaW1lIENB\n"
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZshi2nJNyYZ4aJN+q27\n"
|
||||
"wA69lUAwRSHiJGBCGzppLue/LFDDC1t8GDicjYLGH5eJOlFwr8TbAr+ZH+/PyBoS\n"
|
||||
"7g5tsSn5xZhgEaivnq1MJNqYWHqW5KF2KhGxzzyC6m3JFK21H0xiJu9ej2wQs1tD\n"
|
||||
"ZWG3Y7pKeMFhCezEip5ueIyvmjsenK00TJKr6w1Rkr4BA40euLb5r0srWllKKUyl\n"
|
||||
"t5AEFghdVU7GeXfC2LPrzzMVngFWTaoL3QRf7VMhvNC0Xq7h2yjwd4wROYiJFZBj\n"
|
||||
"UgDSi2W50fPlVDliET2hPBR6lQPgCBRoIdQF8NneSBJ5xH+mw9ZZV8btL8ahwWtL\n"
|
||||
"GwIDAQABo4GXMIGUMB0GA1UdDgQWBBSM9pLZlAekgqt7ZXzPOdTEifMLmzBYBgNV\n"
|
||||
"HSMEUTBPgBSM9pLZlAekgqt7ZXzPOdTEifMLm6EhpB8wHTEbMBkGA1UEAwwSTWly\n"
|
||||
"ZW8gU3BhY2VUaW1lIENBghRlmwQpPyb64ZlE0xpO4KJkFyFJTjAMBgNVHRMEBTAD\n"
|
||||
"AQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAuSe6ZOwc8KnNXs1M\n"
|
||||
"KoShOUxZGDFBUJFNAtTSsMi0ap6GIo/yJr+6SAkHkVU0HFkl5lzRo9aUHRw4O7Ez\n"
|
||||
"579JMzUDdEGBxtYqda0Rxnw8N2mq5Fxpv+1b6v4GsWA30k6TdqnrFdNpFVI84W6u\n"
|
||||
"Fw3HTKA0Ah0jXryc1kC1jU7mYKf66TDI5PSbuZRjHgQzzyUXZmCn1WcLbvunsc4r\n"
|
||||
"Tk2FrfXHfvag12yPLc9aIOrtfRW2wtlZcxMzX4oE6wfllAIIsSZGx0muydiMe8bw\n"
|
||||
"Od5S0p1sspsWOthj1t9yhHMwznwV81QLePWzgGmml21uA067ZGG8NHxNbERd/9e+\n"
|
||||
"Qz9m6w==\n"
|
||||
"-----END CERTIFICATE-----\n"
|
||||
;
|
||||
|
||||
void publish_qos0_openssl_tls() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
|
||||
error_code ec;
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-qos0-openssl-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_most_once>(
|
||||
"test/mqtt-test", "hello world with qos0!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec) {
|
||||
fmt::print("[Test-publish-qos0-openssl-tls] error_code: {}\n", ec.message());
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
void publish_qos1_openssl_tls() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
|
||||
error_code ec;
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-qos1-openssl-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"test/mqtt-test", "hello world with qos1!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, puback_props) {
|
||||
fmt::print(
|
||||
"[Test-publish-qos1-openssl-tls] "
|
||||
"error_code: {}, reason_code: {}\n", ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void publish_qos2_openssl_tls() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
|
||||
error_code ec;
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-qos2-openssl-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::exactly_once>(
|
||||
"test/mqtt-test", "hello world with qos2!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, pubcomp_props) {
|
||||
fmt::print(
|
||||
"[Test-publish-qos2-openssl-tls] "
|
||||
"error_code: {}, reason_code: {}\n", ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void subscribe_and_receive_openssl_tls(int num_receive) {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
|
||||
error_code ec;
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-subscriber-openssl-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
|
||||
std::vector<subscribe_topic> topics;
|
||||
topics.push_back(subscribe_topic {
|
||||
"test/mqtt-test", {
|
||||
qos_e::exactly_once,
|
||||
subscribe_options::no_local_e::no,
|
||||
subscribe_options::retain_as_published_e::retain,
|
||||
subscribe_options::retain_handling_e::send
|
||||
}
|
||||
});
|
||||
|
||||
c.async_subscribe(
|
||||
topics, subscribe_props {},
|
||||
[](error_code ec, std::vector<reason_code> codes, suback_props) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"[Test-subscribe-and-receive-openssl-tls] subscribe error_code: {},"
|
||||
" reason_code: {}\n", ec.message(), codes[0].message()
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
for (auto i = 0; i < num_receive; i++) {
|
||||
c.async_receive(
|
||||
[&c, i, num_receive] (
|
||||
error_code ec, std::string topic,
|
||||
std::string payload, publish_props
|
||||
) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"[Test-subscribe-and-receive-openssl-tls] message {}/{}:"
|
||||
"ec: {}, topic: {}, payload: {}\n",
|
||||
i + 1, num_receive, ec.message(), topic, payload
|
||||
);
|
||||
|
||||
if (i == num_receive - 1)
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
void test_coro() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
co_spawn(ioc, [&ioc]() -> asio::awaitable<void> {
|
||||
using stream_type = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
|
||||
error_code ec;
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("coro-client", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
std::vector<subscribe_topic> topics;
|
||||
topics.push_back(subscribe_topic {
|
||||
"test/mqtt-test", {
|
||||
qos_e::exactly_once,
|
||||
subscribe_options::no_local_e::no,
|
||||
subscribe_options::retain_as_published_e::retain,
|
||||
subscribe_options::retain_handling_e::send
|
||||
}
|
||||
});
|
||||
|
||||
auto [codes, props] = co_await c.async_subscribe(
|
||||
topics, subscribe_props {}, asio::use_awaitable
|
||||
);
|
||||
fmt::print("Subscribe result: ({}),", codes[0].message());
|
||||
|
||||
auto [topic, payload, rec_props] = co_await c.async_receive(asio::use_awaitable);
|
||||
fmt::print("Receive from topic {}: {}\n", topic, payload);
|
||||
|
||||
asio::steady_timer timer(ioc);
|
||||
timer.expires_from_now(std::chrono::seconds(1));
|
||||
co_await timer.async_wait(asio::use_awaitable);
|
||||
c.cancel();
|
||||
|
||||
co_return;
|
||||
}, asio::detached);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void run_openssl_tls_examples() {
|
||||
publish_qos0_openssl_tls();
|
||||
publish_qos1_openssl_tls();
|
||||
publish_qos2_openssl_tls();
|
||||
subscribe_and_receive_openssl_tls(1);
|
||||
test_coro();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
void run_openssl_tls_examples();
|
||||
void run_tcp_examples();
|
||||
void run_websocket_tcp_examples();
|
||||
void run_websocket_tls_examples();
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
run_tcp_examples();
|
||||
//run_openssl_tls_examples();
|
||||
//run_websocket_tcp_examples();
|
||||
//run_websocket_tls_examples();
|
||||
|
||||
return 0;
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
#include <fmt/format.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <async_mqtt5.hpp>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
void publish_qos0_tcp() {
|
||||
fmt::print("[Test-publish-qos0-tcp]\n");
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ip::tcp::socket;
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-qos0-tcp", "", "")
|
||||
.brokers("mqtt.mireo.local", 1883)
|
||||
.will({ "test/mqtt-test", "i died",qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_most_once>(
|
||||
"test/mqtt-test", "hello world with qos0!",
|
||||
retain_e::no, publish_props {},
|
||||
[&c](error_code ec) {
|
||||
fmt::print("\terror_code: {}\n", ec.message());
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
|
||||
void publish_qos1_tcp() {
|
||||
fmt::print("[Test-publish-qos1-tcp]\n");
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ip::tcp::socket;
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-qos1-tcp", "", "")
|
||||
.brokers("mqtt.mireo.local", 1883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"test/mqtt-test", "hello world with qos1!",
|
||||
retain_e::no, publish_props {},
|
||||
[&c](error_code ec, reason_code rc, puback_props) {
|
||||
fmt::print(
|
||||
"\terror_code: {}, reason_code: {}\n",
|
||||
ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void publish_qos2_tcp() {
|
||||
fmt::print("[Test-publish-qos2-tcp]\n");
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ip::tcp::socket;
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-qos2-tcp", "", "")
|
||||
.brokers("mqtt.mireo.local", 1883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::exactly_once>(
|
||||
"test/mqtt-test", "hello world with qos2!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, pubcomp_props) {
|
||||
fmt::print(
|
||||
"\terror_code: {}, reason_code: {}\n",
|
||||
ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
|
||||
void subscribe_and_receive_tcp(int num_receive) {
|
||||
fmt::print("[Test-subscribe-and-receive-tcp]\n");
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ip::tcp::socket;
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-subscriber-tcp", "", "")
|
||||
.brokers("mqtt.mireo.local", 1883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_subscribe(
|
||||
{ "test/mqtt-test", { qos_e::exactly_once } }, subscribe_props {},
|
||||
[](error_code ec, std::vector<reason_code> codes, suback_props) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"\tsubscribe error_code: {}, reason_code: {}\n",
|
||||
ec.message(), codes[0].message()
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
for (auto i = 0; i < num_receive; i++) {
|
||||
c.async_receive(
|
||||
[&c, i, num_receive] (
|
||||
error_code ec, std::string topic,
|
||||
std::string payload, publish_props
|
||||
) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"\tmessage {}/{}: ec: {}, topic: {}, payload: {}\n",
|
||||
i + 1, num_receive, ec.message(), topic, payload
|
||||
);
|
||||
|
||||
if (i == num_receive - 1)
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
|
||||
void run_tcp_examples() {
|
||||
publish_qos0_tcp();
|
||||
publish_qos1_tcp();
|
||||
publish_qos2_tcp();
|
||||
subscribe_and_receive_tcp(1);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
|
||||
#include <async_mqtt5.hpp>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
void publish_qos0_websocket_tcp() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ip::tcp::socket
|
||||
>;
|
||||
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-qos0-websocket-tcp", "", "")
|
||||
.brokers("fcluster-5/mqtt", 8083)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_most_once>(
|
||||
"test/mqtt-test", "hello world with qos0!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec) {
|
||||
fmt::print("[Test-publish-qos0-websocket-tcp] error_code: {}\n", ec.message());
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
void publish_qos1_websocket_tcp() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ip::tcp::socket
|
||||
>;
|
||||
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-qos1-websocket-tcp", "", "")
|
||||
.brokers("fcluster-5/mqtt", 8083)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"test/mqtt-test", "hello world with qos1!",
|
||||
async_mqtt5::retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, puback_props) {
|
||||
fmt::print(
|
||||
"[Test-publish-qos1-websocket-tcp] "
|
||||
"error_code: {}, reason_code: {}\n", ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
void publish_qos2_websocket_tcp() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ip::tcp::socket
|
||||
>;
|
||||
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-qos2-websocket-tcp", "", "")
|
||||
.brokers("fcluster-5/mqtt", 8083)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::exactly_once>(
|
||||
"test/mqtt-test", "hello world with qos2!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, pubcomp_props) {
|
||||
fmt::print(
|
||||
"[Test-publish-qos2-websocket-tcp] "
|
||||
"error_code: {}, reason_code: {}\n", ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void subscribe_and_receive_websocket_tcp(int num_receive) {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ip::tcp::socket
|
||||
>;
|
||||
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("test-subscriber-websocket-tcp", "", "")
|
||||
.brokers("fcluster-5/mqtt", 8083)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
std::vector<subscribe_topic> topics;
|
||||
topics.push_back(subscribe_topic{
|
||||
"test/mqtt-test", {
|
||||
qos_e::exactly_once,
|
||||
subscribe_options::no_local_e::no,
|
||||
subscribe_options::retain_as_published_e::retain,
|
||||
subscribe_options::retain_handling_e::send
|
||||
}
|
||||
});
|
||||
|
||||
c.async_subscribe(
|
||||
topics, subscribe_props{},
|
||||
[](error_code ec, std::vector<reason_code> codes, suback_props) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"[Test-subscribe-and-receive-websocket-tcp] "
|
||||
" error_code: {}, reason_code: {}\n", ec.message(), codes[0].message()
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
for (auto i = 0; i < num_receive; i++) {
|
||||
c.async_receive(
|
||||
[&c, i, num_receive] (
|
||||
error_code ec, std::string topic,
|
||||
std::string payload, publish_props
|
||||
) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"[Test-subscribe-and-receive-websocket-tcp] message {}/{}:"
|
||||
"ec: {}, topic: {}, payload: {}\n",
|
||||
i + 1, num_receive, ec.message(), topic, payload
|
||||
);
|
||||
|
||||
if (i == num_receive - 1)
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void run_websocket_tcp_examples() {
|
||||
publish_qos0_websocket_tcp();
|
||||
publish_qos1_websocket_tcp();
|
||||
publish_qos2_websocket_tcp();
|
||||
subscribe_and_receive_websocket_tcp(1);
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include <async_mqtt5.hpp>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
namespace boost::beast::websocket {
|
||||
|
||||
template <typename TeardownHandler>
|
||||
void async_teardown(
|
||||
boost::beast::role_type role,
|
||||
asio::ssl::stream<asio::ip::tcp::socket>& stream,
|
||||
TeardownHandler&& handler
|
||||
) {
|
||||
return stream.async_shutdown(std::forward<TeardownHandler>(handler));
|
||||
}
|
||||
|
||||
} // end namespace boost::beast::websocket
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
template <typename StreamBase>
|
||||
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
|
||||
static constexpr auto client = asio::ssl::stream_base::client;
|
||||
static constexpr auto server = asio::ssl::stream_base::server;
|
||||
};
|
||||
|
||||
template <typename StreamBase>
|
||||
void assign_tls_sni(
|
||||
const authority_path& ap,
|
||||
asio::ssl::context& ctx,
|
||||
asio::ssl::stream<StreamBase>& stream
|
||||
) {
|
||||
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
constexpr const char spacetime_ca[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDYDCCAkigAwIBAgIUZZsEKT8m+uGZRNMaTuCiZBchSU4wDQYJKoZIhvcNAQEL\n"
|
||||
"BQAwHTEbMBkGA1UEAwwSTWlyZW8gU3BhY2VUaW1lIENBMB4XDTIzMDIwNzIwMzU1\n"
|
||||
"MFoXDTMzMDIwNDIwMzU1MFowHTEbMBkGA1UEAwwSTWlyZW8gU3BhY2VUaW1lIENB\n"
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZshi2nJNyYZ4aJN+q27\n"
|
||||
"wA69lUAwRSHiJGBCGzppLue/LFDDC1t8GDicjYLGH5eJOlFwr8TbAr+ZH+/PyBoS\n"
|
||||
"7g5tsSn5xZhgEaivnq1MJNqYWHqW5KF2KhGxzzyC6m3JFK21H0xiJu9ej2wQs1tD\n"
|
||||
"ZWG3Y7pKeMFhCezEip5ueIyvmjsenK00TJKr6w1Rkr4BA40euLb5r0srWllKKUyl\n"
|
||||
"t5AEFghdVU7GeXfC2LPrzzMVngFWTaoL3QRf7VMhvNC0Xq7h2yjwd4wROYiJFZBj\n"
|
||||
"UgDSi2W50fPlVDliET2hPBR6lQPgCBRoIdQF8NneSBJ5xH+mw9ZZV8btL8ahwWtL\n"
|
||||
"GwIDAQABo4GXMIGUMB0GA1UdDgQWBBSM9pLZlAekgqt7ZXzPOdTEifMLmzBYBgNV\n"
|
||||
"HSMEUTBPgBSM9pLZlAekgqt7ZXzPOdTEifMLm6EhpB8wHTEbMBkGA1UEAwwSTWly\n"
|
||||
"ZW8gU3BhY2VUaW1lIENBghRlmwQpPyb64ZlE0xpO4KJkFyFJTjAMBgNVHRMEBTAD\n"
|
||||
"AQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAuSe6ZOwc8KnNXs1M\n"
|
||||
"KoShOUxZGDFBUJFNAtTSsMi0ap6GIo/yJr+6SAkHkVU0HFkl5lzRo9aUHRw4O7Ez\n"
|
||||
"579JMzUDdEGBxtYqda0Rxnw8N2mq5Fxpv+1b6v4GsWA30k6TdqnrFdNpFVI84W6u\n"
|
||||
"Fw3HTKA0Ah0jXryc1kC1jU7mYKf66TDI5PSbuZRjHgQzzyUXZmCn1WcLbvunsc4r\n"
|
||||
"Tk2FrfXHfvag12yPLc9aIOrtfRW2wtlZcxMzX4oE6wfllAIIsSZGx0muydiMe8bw\n"
|
||||
"Od5S0p1sspsWOthj1t9yhHMwznwV81QLePWzgGmml21uA067ZGG8NHxNbERd/9e+\n"
|
||||
"Qz9m6w==\n"
|
||||
"-----END CERTIFICATE-----\n"
|
||||
;
|
||||
|
||||
void publish_qos0_websocket_tls() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ssl::stream<asio::ip::tcp::socket>
|
||||
>;
|
||||
|
||||
error_code ec;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-qos0-websocket-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8884)
|
||||
.will({ "test/mqtt-test", "i died", async_mqtt5::qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_most_once>(
|
||||
"test/mqtt-test", "hello world with qos0!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec) {
|
||||
fmt::print("[Test-publish-qos0-websocket-tls] error_code: {}\n", ec.message());
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
void publish_qos1_websocket_tls() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ssl::stream<asio::ip::tcp::socket>
|
||||
>;
|
||||
|
||||
error_code ec;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-qos1-websocket-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8884)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"test/mqtt-test", "hello world with qos1!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, puback_props) {
|
||||
fmt::print(
|
||||
"[Test-publish-qos1-websocket-tls] "
|
||||
"error_code: {}, reason_code: {}\n", ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
void publish_qos2_websocket_tls() {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ssl::stream<asio::ip::tcp::socket>
|
||||
>;
|
||||
|
||||
error_code ec;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-qos2-websocket-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8884)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::exactly_once>(
|
||||
"test/mqtt-test", "hello world with qos2!",
|
||||
retain_e::no, publish_props{},
|
||||
[&c](error_code ec, reason_code rc, pubcomp_props) {
|
||||
fmt::print(
|
||||
"[Test-publish-qos2-websocket-tls] "
|
||||
"error_code: {}, reason_code: {}\n", ec.message(), rc.message()
|
||||
);
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void subscribe_and_receive_websocket_tls(int num_receive) {
|
||||
using namespace async_mqtt5;
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ssl::stream<asio::ip::tcp::socket>
|
||||
>;
|
||||
|
||||
error_code ec;
|
||||
asio::ssl::context tls_context(asio::ssl::context::tls_client);
|
||||
tls_context.add_certificate_authority(asio::buffer(spacetime_ca), ec);
|
||||
tls_context.set_verify_mode(asio::ssl::verify_peer);
|
||||
|
||||
using client_type = mqtt_client<stream_type, decltype(tls_context)>;
|
||||
client_type c(ioc, "", std::move(tls_context));
|
||||
|
||||
c.credentials("test-subscriber-websocket-tls", "", "")
|
||||
.brokers("iot.fcluster.mireo.hr/mqtt", 8884)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
std::vector<subscribe_topic> topics;
|
||||
topics.push_back(subscribe_topic{
|
||||
"test/mqtt-test", {
|
||||
qos_e::exactly_once,
|
||||
subscribe_options::no_local_e::no,
|
||||
subscribe_options::retain_as_published_e::retain,
|
||||
subscribe_options::retain_handling_e::send
|
||||
}
|
||||
});
|
||||
|
||||
c.async_subscribe(
|
||||
topics, subscribe_props{},
|
||||
[](error_code ec, std::vector<reason_code> codes, suback_props) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"[Test-subscribe-and-receive-websocket-tls] "
|
||||
" error_code: {}, reason_code: {}\n", ec.message(), codes[0].message()
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
for (auto i = 0; i < num_receive; i++) {
|
||||
c.async_receive(
|
||||
[&c, i, num_receive] (
|
||||
error_code ec, std::string topic,
|
||||
std::string payload, publish_props
|
||||
) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
fmt::print(
|
||||
"[Test-subscribe-and-receive-websocket-tls] message {}/{}:"
|
||||
"ec: {}, topic: {}, payload: {}\n",
|
||||
i + 1, num_receive, ec.message(), topic, payload
|
||||
);
|
||||
|
||||
if (i == num_receive - 1)
|
||||
c.cancel();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ioc.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void run_websocket_tls_examples() {
|
||||
publish_qos0_websocket_tls();
|
||||
publish_qos1_websocket_tls();
|
||||
publish_qos2_websocket_tls();
|
||||
subscribe_and_receive_websocket_tls(1);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef ASYNC_MQTT5_HPP
|
||||
#define ASYNC_MQTT5_HPP
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/mqtt_client.hpp>
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#endif // !ASYNC_MQTT5_HPP
|
||||
@@ -0,0 +1,210 @@
|
||||
#ifndef ASYNC_MQTT5_ASYNC_MUTEX_HPP
|
||||
#define ASYNC_MQTT5_ASYNC_MUTEX_HPP
|
||||
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/execution.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/ring_buffer.hpp>
|
||||
#include <async_mqtt5/detail/spinlock.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
class async_mutex {
|
||||
public:
|
||||
using executor_type = asio::any_io_executor;
|
||||
private:
|
||||
using queued_op_t = asio::any_completion_handler<
|
||||
void (boost::system::error_code)
|
||||
>;
|
||||
using queue_t = detail::ring_buffer<queued_op_t>;
|
||||
|
||||
// Handler with assigned tracking executor.
|
||||
// Objects of this type are type-erased by any_completion_handler
|
||||
// and stored in the waiting queue.
|
||||
template <typename Handler>
|
||||
class tracked_op {
|
||||
tracking_type<Handler> _executor;
|
||||
std::decay_t<Handler> _handler;
|
||||
public:
|
||||
tracked_op(Handler&& h) :
|
||||
_executor(tracking_executor(h)),
|
||||
_handler(std::move(h))
|
||||
{}
|
||||
|
||||
tracked_op(tracked_op&&) noexcept = default;
|
||||
tracked_op(const tracked_op&) = delete;
|
||||
|
||||
using executor_type = tracking_type<Handler>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
void operator()(boost::system::error_code ec) {
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
// Per-operation cancellation helper.
|
||||
// It is safe to emit the cancellation signal from any thread
|
||||
// provided there are no other concurrent calls to the async_mutex.
|
||||
// The helper stores queue iterator to operation since the iterator
|
||||
// would not be invalidated by other queue operations.
|
||||
class cancel_waiting_op {
|
||||
async_mutex& _owner;
|
||||
queue_t::iterator _ihandler;
|
||||
public:
|
||||
cancel_waiting_op(async_mutex& owner, queue_t::iterator ih) :
|
||||
_owner(owner), _ihandler(ih)
|
||||
{}
|
||||
|
||||
void operator()(asio::cancellation_type_t type) {
|
||||
if (type == asio::cancellation_type_t::none)
|
||||
return;
|
||||
std::unique_lock l { _owner._thread_mutex };
|
||||
if (*_ihandler) {
|
||||
auto h = std::move(*_ihandler);
|
||||
auto ex = asio::get_associated_executor(h);
|
||||
l.unlock();
|
||||
asio::require(ex, asio::execution::blocking.possibly)
|
||||
.execute([h = std::move(h)]() mutable {
|
||||
std::move(h)(asio::error::operation_aborted);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
spinlock _thread_mutex;
|
||||
std::atomic<bool> _locked { false };
|
||||
queue_t _waiting;
|
||||
executor_type _ex;
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
async_mutex(Executor&& ex) : _ex(std::forward<Executor>(ex)) {}
|
||||
|
||||
async_mutex(const async_mutex&) = delete;
|
||||
async_mutex& operator=(const async_mutex&) = delete;
|
||||
|
||||
~async_mutex() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept {
|
||||
return _ex;
|
||||
}
|
||||
|
||||
bool is_locked() const noexcept {
|
||||
return _locked.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Schedules mutex for lock operation and return immediately.
|
||||
// Calls given completion handler when mutex is locked.
|
||||
// It's the responsibility of the completion handler to unlock the mutex.
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) lock(CompletionToken&& token) noexcept {
|
||||
auto initiation = [this] (auto handler) {
|
||||
this->execute_or_queue(std::move(handler));
|
||||
};
|
||||
return asio::async_initiate<CompletionToken, void (error_code)>(
|
||||
std::move(initiation), token
|
||||
);
|
||||
}
|
||||
|
||||
// Unlocks the mutex. The mutex must be in locked state.
|
||||
// Next queued operation, if any, will be executed in a manner
|
||||
// equivalent to asio::post.
|
||||
void unlock() {
|
||||
std::unique_lock l { _thread_mutex };
|
||||
if (_waiting.empty()) {
|
||||
_locked.store(false, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
while (!_waiting.empty()) {
|
||||
auto op = std::move(_waiting.front());
|
||||
_waiting.pop_front();
|
||||
if (!op) continue;
|
||||
op.get_cancellation_slot().clear();
|
||||
l.unlock();
|
||||
execute_op(std::move(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancels all outstanding operations waiting on the mutex.
|
||||
void cancel() {
|
||||
std::unique_lock l { _thread_mutex };
|
||||
|
||||
while (!_waiting.empty()) {
|
||||
auto op = std::move(_waiting.front());
|
||||
_waiting.pop_front();
|
||||
if (!op) continue;
|
||||
op.get_cancellation_slot().clear();
|
||||
auto ex = asio::get_associated_executor(op, _ex);
|
||||
asio::require(ex, asio::execution::blocking.possibly)
|
||||
.execute([op = std::move(op)]() mutable {
|
||||
std::move(op)(asio::error::operation_aborted);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Schedule operation to `opex` executor using `_ex` executor.
|
||||
// The operation is equivalent to asio::post(_ex, op) but
|
||||
// for some reason this form of execution is much faster.
|
||||
void execute_op(queued_op_t op) {
|
||||
asio::require(_ex, asio::execution::blocking.never)
|
||||
.execute([ex = _ex, op = std::move(op)]() mutable {
|
||||
auto opex = asio::get_associated_executor(op, ex);
|
||||
opex.execute(
|
||||
[op = std::move(op)]() mutable { op(error_code{}); }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Executes operation immediatelly if mutex is not locked
|
||||
// or queues it for later execution otherwise. In both cases
|
||||
// the operation will be executed in a manner equivalent
|
||||
// to asio::post to avoid recursion.
|
||||
void execute_or_queue(auto handler) noexcept {
|
||||
std::unique_lock l { _thread_mutex };
|
||||
tracked_op h { std::move(handler) };
|
||||
if (_locked.load(std::memory_order_relaxed)) {
|
||||
_waiting.emplace_back(std::move(h));
|
||||
auto slot = _waiting.back().get_cancellation_slot();
|
||||
if (slot.is_connected())
|
||||
slot.template emplace<cancel_waiting_op>(
|
||||
*this, _waiting.end() - 1
|
||||
);
|
||||
}
|
||||
else {
|
||||
_locked.store(true, std::memory_order_release);
|
||||
l.unlock();
|
||||
execute_op(queued_op_t { std::move(h) });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASYNC_MUTEX_HPP
|
||||
@@ -0,0 +1,161 @@
|
||||
#ifndef ASYNC_MQTT5_ASYNC_TRAITS_HPP
|
||||
#define ASYNC_MQTT5_ASYNC_TRAITS_HPP
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/prefer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/beast/core/stream_traits.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
// TODO: move tls_handshake_type and assign_tls_sni to
|
||||
// separate header
|
||||
|
||||
template <typename StreamType>
|
||||
struct tls_handshake_type {};
|
||||
|
||||
template <typename TlsContext, typename TlsStream>
|
||||
void assign_tls_sni(const authority_path& ap, TlsContext& ctx, TlsStream& s);
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Handler>
|
||||
decltype(auto) tracking_executor(const Handler& handler) {
|
||||
return asio::prefer(
|
||||
asio::get_associated_executor(handler),
|
||||
asio::execution::outstanding_work.tracked
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
using tracking_type = std::decay_t<
|
||||
decltype(tracking_executor(std::declval<Handler>()))
|
||||
>;
|
||||
|
||||
template <typename T, typename B>
|
||||
concept has_async_write = requires(T a) {
|
||||
a.async_write(
|
||||
std::declval<B>(),
|
||||
[](boost::system::error_code, size_t) {}
|
||||
);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept has_tls_handshake = requires(T a) {
|
||||
a.async_handshake(
|
||||
typename T::handshake_type{},
|
||||
[](error_code) {}
|
||||
);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept has_ws_handshake = requires(T a) {
|
||||
a.async_handshake(
|
||||
std::declval<std::string_view>(),
|
||||
std::declval<std::string_view>(),
|
||||
[](error_code) {}
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept has_tls_context = requires(T a) {
|
||||
a.tls_context();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept has_next_layer = requires(T a) {
|
||||
a.next_layer();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct next_layer_type {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires has_next_layer<T>
|
||||
struct next_layer_type<T> {
|
||||
using type = typename std::remove_reference_t<T>::next_layer_type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires (!has_next_layer<T>)
|
||||
typename next_layer_type<T>::type& next_layer(T&& a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires has_next_layer<T>
|
||||
typename next_layer_type<T>::type& next_layer(T&& a) {
|
||||
return a.next_layer();
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
using lowest_layer_type = typename boost::beast::lowest_layer_type<S>;
|
||||
|
||||
template <typename S>
|
||||
lowest_layer_type<S>& lowest_layer(S&& a) {
|
||||
return boost::beast::get_lowest_layer(std::forward<S>(a));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct has_tls_layer_impl : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
requires has_tls_handshake<T>
|
||||
struct has_tls_layer_impl<T> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
requires (!has_tls_handshake<T> && has_next_layer<T>)
|
||||
struct has_tls_layer_impl<T> : has_tls_layer_impl<
|
||||
std::remove_cvref_t<decltype(std::declval<T&>().next_layer())>
|
||||
> {};
|
||||
|
||||
template <typename T>
|
||||
concept has_tls_layer = has_tls_layer_impl<std::remove_cvref_t<T>>::value;
|
||||
|
||||
//TODO: move to appropriate place
|
||||
template <
|
||||
typename Stream,
|
||||
typename ConstBufferSequence,
|
||||
typename CompletionToken
|
||||
>
|
||||
decltype(auto) async_write(
|
||||
Stream& stream, const ConstBufferSequence& buff, CompletionToken&& token
|
||||
) {
|
||||
// TODO: find layer that has async write method
|
||||
if constexpr (has_async_write<Stream, ConstBufferSequence>)
|
||||
return stream.async_write(
|
||||
buff, std::forward<CompletionToken>(token)
|
||||
);
|
||||
else
|
||||
return asio::async_write(
|
||||
stream, buff, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename TlsContext, typename Stream>
|
||||
void setup_tls_sni(const authority_path& ap, TlsContext& ctx, Stream& s) {
|
||||
if constexpr (has_tls_handshake<Stream>) {
|
||||
using tls_stream_type = Stream;
|
||||
assign_tls_sni(ap, ctx, s);
|
||||
}
|
||||
else if constexpr (has_next_layer<Stream>) {
|
||||
using next_layer_type = typename Stream::next_layer_type;
|
||||
setup_tls_sni(ap, ctx, next_layer(s));
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASYNC_TRAITS_HPP
|
||||
@@ -0,0 +1,151 @@
|
||||
#ifndef ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
#define ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
template <
|
||||
typename Handler, typename Executor,
|
||||
typename CancelArgs = std::tuple<>
|
||||
>
|
||||
class cancellable_handler {
|
||||
struct op_state {
|
||||
std::decay_t<Handler> _handler;
|
||||
tracking_type<Handler> _handler_ex;
|
||||
cancellable_handler* _owner;
|
||||
|
||||
op_state(Handler&& handler, cancellable_handler* owner) :
|
||||
_handler(std::move(handler)),
|
||||
_handler_ex(tracking_executor(_handler)),
|
||||
_owner(owner)
|
||||
{}
|
||||
|
||||
void cancel_op(asio::cancellation_type_t ct) {
|
||||
if (ct != asio::cancellation_type_t::none)
|
||||
_owner->cancel();
|
||||
}
|
||||
};
|
||||
|
||||
struct cancel_proxy {
|
||||
std::weak_ptr<op_state> _state_weak_ptr;
|
||||
Executor _executor;
|
||||
|
||||
cancel_proxy(std::shared_ptr<op_state> state, const Executor& ex) :
|
||||
_state_weak_ptr(std::move(state)), _executor(ex)
|
||||
{}
|
||||
|
||||
void operator()(asio::cancellation_type_t type) {
|
||||
auto op = [](
|
||||
std::weak_ptr<op_state> wptr,
|
||||
asio::cancellation_type_t type
|
||||
) {
|
||||
if (auto state = wptr.lock())
|
||||
state->cancel_op(type);
|
||||
};
|
||||
asio::dispatch(
|
||||
_executor,
|
||||
asio::prepend(std::move(op), _state_weak_ptr, type)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<op_state> _state;
|
||||
Executor _executor;
|
||||
|
||||
public:
|
||||
cancellable_handler(Handler&& handler, const Executor& ex) {
|
||||
auto alloc = asio::get_associated_allocator(handler);
|
||||
_state = std::allocate_shared<op_state>(
|
||||
alloc, std::move(handler),this
|
||||
);
|
||||
_executor = ex;
|
||||
auto slot = asio::get_associated_cancellation_slot(_state->_handler);
|
||||
if (slot.is_connected())
|
||||
slot.template emplace<cancel_proxy>(_state, ex);
|
||||
}
|
||||
|
||||
cancellable_handler(cancellable_handler&& other) noexcept :
|
||||
_state(std::exchange(other._state, nullptr)),
|
||||
_executor(std::move(other._executor))
|
||||
{
|
||||
if (!empty())
|
||||
_state->_owner = this;
|
||||
}
|
||||
|
||||
cancellable_handler(const cancellable_handler&) = delete;
|
||||
|
||||
~cancellable_handler() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return _state == nullptr;
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_state->_handler);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (empty()) return;
|
||||
|
||||
auto h = std::move(_state->_handler);
|
||||
_state.reset();
|
||||
asio::get_associated_cancellation_slot(h).clear();
|
||||
|
||||
auto op = std::apply([&h](auto... args) {
|
||||
return asio::prepend(
|
||||
std::move(h), asio::error::operation_aborted, args...
|
||||
);
|
||||
}, CancelArgs {});
|
||||
|
||||
asio::dispatch(std::move(_executor), std::move(op));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void complete(Args&&... args) {
|
||||
if (empty()) return;
|
||||
|
||||
auto h = std::move(_state->_handler);
|
||||
_state.reset();
|
||||
asio::get_associated_cancellation_slot(h).clear();
|
||||
|
||||
asio::dispatch(
|
||||
std::move(_executor),
|
||||
asio::prepend(std::move(h), std::forward<Args>(args)...)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void complete_post(Args&&... args) {
|
||||
if (empty()) return;
|
||||
|
||||
auto h = std::move(_state->_handler);
|
||||
_state.reset();
|
||||
asio::get_associated_cancellation_slot(h).clear();
|
||||
|
||||
asio::post(
|
||||
std::move(_executor),
|
||||
asio::prepend(std::move(h), std::forward<Args>(args)...)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
@@ -0,0 +1,153 @@
|
||||
#ifndef ASYNC_MQTT5_CONTROL_PACKET_HPP
|
||||
#define ASYNC_MQTT5_CONTROL_PACKET_HPP
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/smart_ptr/allocate_unique.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
enum class control_code_e : std::uint8_t {
|
||||
no_packet = 0b00000000, // 0
|
||||
|
||||
connect = 0b00010000, // 1
|
||||
connack = 0b00100000, // 2
|
||||
publish = 0b00110000, // 3
|
||||
puback = 0b01000000, // 4
|
||||
pubrec = 0b01010000, // 5
|
||||
pubrel = 0b01100000, // 6
|
||||
pubcomp = 0b01110000, // 7
|
||||
subscribe = 0b10000000, // 8
|
||||
suback = 0b10010000, // 9
|
||||
unsubscribe = 0b10100000, // 10
|
||||
unsuback = 0b10110000, // 11
|
||||
pingreq = 0b11000000, // 12
|
||||
pingresp = 0b11010000, // 13
|
||||
disconnect = 0b11100000, // 14
|
||||
auth = 0b11110000, // 15
|
||||
};
|
||||
|
||||
constexpr struct with_pid_ {} with_pid {};
|
||||
constexpr struct no_pid_ {} no_pid {};
|
||||
|
||||
template <typename Allocator>
|
||||
class control_packet {
|
||||
uint16_t _packet_id;
|
||||
|
||||
using alloc_type = Allocator;
|
||||
using deleter = boost::alloc_deleter<std::string, alloc_type>;
|
||||
std::unique_ptr<std::string, deleter> _packet;
|
||||
|
||||
control_packet(
|
||||
const Allocator& a,
|
||||
uint16_t packet_id, std::string packet
|
||||
) noexcept :
|
||||
_packet_id(packet_id),
|
||||
_packet(boost::allocate_unique<std::string>(a, std::move(packet)))
|
||||
{}
|
||||
|
||||
public:
|
||||
control_packet(control_packet&&) noexcept = default;
|
||||
control_packet(const control_packet&) noexcept = delete;
|
||||
|
||||
template <
|
||||
typename EncodeFun,
|
||||
typename ...Args
|
||||
>
|
||||
static control_packet of(
|
||||
with_pid_, const Allocator& alloc,
|
||||
EncodeFun&& encode, uint16_t packet_id, Args&&... args
|
||||
) {
|
||||
return control_packet {
|
||||
alloc, packet_id, encode(packet_id, std::forward<Args>(args)...)
|
||||
};
|
||||
}
|
||||
|
||||
template <
|
||||
typename EncodeFun,
|
||||
typename ...Args
|
||||
>
|
||||
static control_packet of(
|
||||
no_pid_, const Allocator& alloc,
|
||||
EncodeFun&& encode, Args&&... args
|
||||
) {
|
||||
return control_packet {
|
||||
alloc, 0, encode(std::forward<Args>(args)...)
|
||||
};
|
||||
}
|
||||
|
||||
control_code_e control_code() const {
|
||||
return control_code_e(uint8_t(*(_packet->data())) & 0b11110000);
|
||||
}
|
||||
|
||||
uint16_t packet_id() const {
|
||||
return _packet_id;
|
||||
}
|
||||
|
||||
qos_e qos() const {
|
||||
assert(control_code() == control_code_e::publish);
|
||||
auto byte = (uint8_t(*(_packet->data())) & 0b00000110) >> 1;
|
||||
return qos_e(byte);
|
||||
}
|
||||
|
||||
control_packet& set_dup() {
|
||||
assert(control_code() == control_code_e::publish);
|
||||
auto& byte = *(_packet->data());
|
||||
byte |= 0b00001000;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string& wire_data() const {
|
||||
return *_packet;
|
||||
}
|
||||
};
|
||||
|
||||
class packet_id_allocator {
|
||||
std::mutex _mtx;
|
||||
boost::container::flat_map<uint16_t,uint16_t> _free_ids;
|
||||
static constexpr uint16_t MAX_PACKET_ID = 65535;
|
||||
|
||||
public:
|
||||
packet_id_allocator() {
|
||||
_free_ids.emplace(1, MAX_PACKET_ID);
|
||||
}
|
||||
|
||||
uint16_t allocate() {
|
||||
std::lock_guard _(_mtx);
|
||||
if (_free_ids.empty()) return 0;
|
||||
auto it = std::prev(_free_ids.end());
|
||||
auto ret = it->second;
|
||||
if (it->first > --it->second)
|
||||
_free_ids.erase(it);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void free(uint16_t pid) {
|
||||
std::lock_guard _(_mtx);
|
||||
auto it = _free_ids.upper_bound(pid);
|
||||
uint16_t* end_p = nullptr;
|
||||
if (it != _free_ids.begin()) {
|
||||
auto pit = std::prev(it);
|
||||
if (pit->second + 1 == pid)
|
||||
end_p = &pit->second;
|
||||
}
|
||||
auto end_v = pid;
|
||||
if (it != _free_ids.end() && pid + 1 == it->first) {
|
||||
end_v = it->second;
|
||||
_free_ids.erase(it);
|
||||
}
|
||||
if (!end_p)
|
||||
_free_ids.emplace(pid, end_v);
|
||||
else
|
||||
*end_p = end_v;
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_CONTROL_PACKET_HPP
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef ASYNC_MQTT5_INTERNAL_TYPES_HPP
|
||||
#define ASYNC_MQTT5_INTERNAL_TYPES_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
using byte_citer = std::string::const_iterator;
|
||||
|
||||
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using duration = time_stamp::duration;
|
||||
|
||||
struct credentials {
|
||||
std::string client_id;
|
||||
std::optional<std::string> username;
|
||||
std::optional<std::string> password;
|
||||
|
||||
credentials() = default;
|
||||
credentials(
|
||||
std::string client_id,
|
||||
std::string username, std::string password
|
||||
) :
|
||||
client_id(std::move(client_id))
|
||||
{
|
||||
if (!username.empty())
|
||||
this->username = std::move(username);
|
||||
if (!password.empty())
|
||||
this->password = std::move(password);
|
||||
}
|
||||
};
|
||||
|
||||
struct mqtt_context {
|
||||
credentials credentials;
|
||||
std::optional<will> will;
|
||||
connect_props co_props;
|
||||
connack_props ca_props;
|
||||
};
|
||||
|
||||
struct disconnect_context {
|
||||
disconnect_rc_e reason_code = disconnect_rc_e::normal_disconnection;
|
||||
disconnect_props props = {};
|
||||
bool terminal = false;
|
||||
};
|
||||
|
||||
using serial_num_t = uint32_t;
|
||||
constexpr serial_num_t no_serial = 0;
|
||||
|
||||
namespace send_flag {
|
||||
|
||||
constexpr unsigned none = 0b000;
|
||||
constexpr unsigned throttled = 0b001;
|
||||
constexpr unsigned prioritized = 0b010;
|
||||
constexpr unsigned terminal = 0b100;
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_INTERNAL_TYPES_HPP
|
||||
@@ -0,0 +1,416 @@
|
||||
#ifndef ASYNC_MQTT5_RING_BUFFER_HPP
|
||||
#define ASYNC_MQTT5_RING_BUFFER_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
/*
|
||||
|
||||
Best used LIFO queues.
|
||||
It supports random access iterators, constant time insert
|
||||
and erase operations at the beginning or the end of the
|
||||
buffer and interoperability with std algorithms. Buffer
|
||||
capacity is ensured to be power of 2.
|
||||
|
||||
*/
|
||||
|
||||
template <typename T, typename Allocator = std::allocator<T>>
|
||||
class ring_buffer {
|
||||
public:
|
||||
/*
|
||||
EMPTY | 1 | 2 | FULL
|
||||
|-----------------|-----------------|-----------------|----------------
|
||||
| ............... | ****........... | ****.......**** | ***************
|
||||
| > | [ > | > [ | ^
|
||||
| B, F == npos | F B | B F | B == F
|
||||
|-----------------|-----------------|-----------------|----------------
|
||||
*/
|
||||
using value_type = T;
|
||||
using allocator_type = Allocator;
|
||||
using allocator_traits = std::allocator_traits<allocator_type>;
|
||||
using size_type = std::size_t;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
|
||||
template <typename P, typename R, typename ring_pointer> class _iter;
|
||||
|
||||
using iterator = _iter<pointer, reference, ring_buffer*>;
|
||||
using const_iterator = _iter<const_pointer, const_reference, const ring_buffer*>;
|
||||
|
||||
iterator begin() noexcept;
|
||||
iterator end() noexcept;
|
||||
const_iterator begin() const noexcept;
|
||||
const_iterator end() const noexcept;
|
||||
const_iterator cbegin() const noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
|
||||
ring_buffer() = default;
|
||||
|
||||
template <typename Alloc> requires std::is_constructible_v<allocator_type, const Alloc&>
|
||||
explicit ring_buffer(size_type capacity, Alloc&& allocator) : _alloc((Alloc&&)allocator) {
|
||||
reserve(capacity);
|
||||
}
|
||||
|
||||
template <typename Alloc> requires std::is_constructible_v<allocator_type, const Alloc&>
|
||||
explicit ring_buffer(Alloc&& allocator) : _alloc((Alloc&&)allocator) {
|
||||
}
|
||||
|
||||
explicit ring_buffer(size_type capacity) : ring_buffer(capacity, allocator_type { }) {
|
||||
}
|
||||
|
||||
ring_buffer(ring_buffer&& other) noexcept :
|
||||
_buff { std::exchange(other._buff, nullptr) },
|
||||
_front { std::exchange(other._front, -1) },
|
||||
_back { std::exchange(other._back, 0) },
|
||||
_capacity { std::exchange(other._capacity, 0) },
|
||||
_alloc { other._alloc }
|
||||
{
|
||||
}
|
||||
|
||||
~ring_buffer() {
|
||||
if (_buff) {
|
||||
clear();
|
||||
_alloc.deallocate(_buff, _capacity);
|
||||
}
|
||||
}
|
||||
|
||||
ring_buffer& operator=(ring_buffer&& other) noexcept {
|
||||
if constexpr (allocator_traits::propagate_on_container_move_assignment::value) {
|
||||
clear_and_exchange_with(other);
|
||||
_alloc = other._alloc;
|
||||
}
|
||||
else {
|
||||
if (_alloc == other._alloc) {
|
||||
clear_and_exchange_with(other);
|
||||
}
|
||||
else {
|
||||
clear();
|
||||
auto s = other.size();
|
||||
reserve(s);
|
||||
for (size_type i = 0; i < s; ++i) {
|
||||
push_back((value_type&&)other.front());
|
||||
other.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_type capacity() const noexcept {
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
if (empty()) {
|
||||
return 0;
|
||||
}
|
||||
return _back > _front ? _back - _front : (_capacity - _front) + _back;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return _front == npos;
|
||||
}
|
||||
|
||||
bool full() const noexcept {
|
||||
return _front == _back;
|
||||
}
|
||||
|
||||
reference front() noexcept {
|
||||
return _buff[_front];
|
||||
}
|
||||
|
||||
const_reference front() const noexcept {
|
||||
return _buff[_front];
|
||||
}
|
||||
|
||||
reference back() noexcept {
|
||||
return _buff[index(_back - 1)];
|
||||
}
|
||||
|
||||
const_reference back() const noexcept {
|
||||
return _buff[index(_back - 1)];
|
||||
}
|
||||
|
||||
reference operator[](size_type i) noexcept {
|
||||
return _buff[index(_front + i)];
|
||||
}
|
||||
|
||||
const_reference operator[](size_type i) const noexcept {
|
||||
return _buff[index(_front + i)];
|
||||
}
|
||||
|
||||
void pop_front() noexcept {
|
||||
allocator_traits::destroy(_alloc, &_buff[_front]);
|
||||
_front = index(_front + 1);
|
||||
if (_front == _back) {
|
||||
_front = npos;
|
||||
_back = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void push_front(const value_type& v) noexcept {
|
||||
grow_if_needed();
|
||||
_front = _front == npos ? index(_back - 1) : index(_front - 1);
|
||||
allocator_traits::construct(_alloc, &_buff[_front], v);
|
||||
}
|
||||
|
||||
void push_front(value_type&& v) noexcept {
|
||||
grow_if_needed();
|
||||
_front = _front == npos ? index(_back - 1) : index(_front - 1);
|
||||
allocator_traits::construct(_alloc, &_buff[_front], (value_type&&)v);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void emplace_front(Args&&... args) noexcept {
|
||||
grow_if_needed();
|
||||
_front = _front == npos ? index(_back - 1) : index(_front - 1);
|
||||
allocator_traits::construct(_alloc, &_buff[_front], (Args&&)args...);
|
||||
}
|
||||
|
||||
void push_back(const value_type& v) noexcept {
|
||||
grow_if_needed();
|
||||
allocator_traits::construct(_alloc, &_buff[_back], v);
|
||||
if (_front == npos) {
|
||||
_front = _back;
|
||||
}
|
||||
_back = index(_back + 1);
|
||||
}
|
||||
|
||||
void push_back(value_type&& v) noexcept {
|
||||
grow_if_needed();
|
||||
allocator_traits::construct(_alloc, &_buff[_back], (value_type&&)v);
|
||||
if (_front == npos) {
|
||||
_front = _back;
|
||||
}
|
||||
_back = index(_back + 1);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void emplace_back(Args&&... args) {
|
||||
grow_if_needed();
|
||||
allocator_traits::construct(_alloc, &_buff[_back], (Args&&)args...);
|
||||
if (_front == npos) {
|
||||
_front = _back;
|
||||
}
|
||||
_back = index(_back + 1);
|
||||
}
|
||||
|
||||
void pop_back() noexcept {
|
||||
_back = index(_back - 1);
|
||||
allocator_traits::destroy(_alloc, &_buff[_back]);
|
||||
if (_front == _back) {
|
||||
_front = npos;
|
||||
_back = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() noexcept {
|
||||
for (size_type i = 0; i < size(); ++i) {
|
||||
allocator_traits::destroy(_alloc, &_buff[index(i)]);
|
||||
}
|
||||
_front = npos;
|
||||
_back = 0;
|
||||
}
|
||||
|
||||
void swap(ring_buffer& b) noexcept {
|
||||
_buff = std::exchange(b._buff, _buff);
|
||||
_front = std::exchange(b._front, _front);
|
||||
_back = std::exchange(b._back, _back);
|
||||
_capacity = std::exchange(b._capacity, _capacity);
|
||||
_alloc = std::exchange(b._alloc, _alloc); //?? allocator_traits::propagate_on_container_swap::value
|
||||
}
|
||||
|
||||
void reserve(size_type new_capacity) noexcept {
|
||||
if (new_capacity <= _capacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((new_capacity & (new_capacity - 1)) != 0) {
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long msb = 0;
|
||||
_BitScanReverse(&msb, static_cast<unsigned long>(new_capacity));
|
||||
uint32_t lz = 32u - msb - 1;
|
||||
#else
|
||||
uint32_t lz = __builtin_clz(new_capacity);
|
||||
#endif
|
||||
new_capacity = 1ull << (32u - lz);
|
||||
}
|
||||
|
||||
auto new_buff = _alloc.allocate(new_capacity);
|
||||
auto s = size();
|
||||
if (_buff) {
|
||||
for (size_type i = 0; i < s; ++i) {
|
||||
auto& v = operator[](i);
|
||||
allocator_traits::construct(_alloc, &new_buff[i], (value_type&&)v);
|
||||
allocator_traits::destroy(_alloc, &v);
|
||||
}
|
||||
_alloc.deallocate(_buff, _capacity);
|
||||
}
|
||||
_buff = new_buff;
|
||||
_back = s;
|
||||
_front = _back == 0 ? npos : 0;
|
||||
_capacity = new_capacity;
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr size_type index(size_type i) const noexcept {
|
||||
return i & (_capacity - 1);
|
||||
}
|
||||
|
||||
void grow_if_needed() {
|
||||
if (!_buff || full()) [[unlikely]]
|
||||
reserve(_capacity == 0 ? min_capacity : _capacity * 2);
|
||||
}
|
||||
|
||||
void clear_and_exchange_with(ring_buffer& other) noexcept {
|
||||
clear();
|
||||
_alloc.deallocate(_buff, _capacity);
|
||||
_buff = std::exchange(other._buff, nullptr);
|
||||
_front = std::exchange(other._front, npos);
|
||||
_back = std::exchange(other._back, 0);
|
||||
_capacity = std::exchange(other._capacity, 0);
|
||||
}
|
||||
|
||||
pointer _buff = nullptr;
|
||||
size_type _front = npos;
|
||||
size_type _back = 0;
|
||||
size_type _capacity = 0;
|
||||
[[no_unique_address]] allocator_type _alloc;
|
||||
|
||||
static constexpr size_type npos = static_cast<size_type>(-1);
|
||||
static constexpr size_type min_capacity = 4;
|
||||
};
|
||||
|
||||
template <typename T, typename Alloc> template <typename P, typename R, typename ring_pointer>
|
||||
class ring_buffer<T, Alloc>::_iter {
|
||||
public:
|
||||
using value_type = T;
|
||||
using pointer = P;
|
||||
using reference = R;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
_iter() noexcept = default;
|
||||
_iter(const _iter&) noexcept = default;
|
||||
_iter(_iter&&) noexcept = default;
|
||||
_iter& operator=(const _iter&) noexcept = default;
|
||||
_iter& operator=(_iter&&) noexcept = default;
|
||||
|
||||
reference operator*() const noexcept {
|
||||
return *_p;
|
||||
}
|
||||
|
||||
pointer operator->() const noexcept {
|
||||
return _p;
|
||||
}
|
||||
|
||||
_iter& operator++() noexcept {
|
||||
_p = &_b->operator[](++_i);
|
||||
return *this;
|
||||
}
|
||||
|
||||
_iter operator++(int) noexcept {
|
||||
auto tmp = *this;
|
||||
_p = &_b->operator[](++_i);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
_iter& operator--() noexcept {
|
||||
_p = &_b->operator[](--_i);
|
||||
return *this;
|
||||
}
|
||||
|
||||
_iter operator--(int) noexcept {
|
||||
auto tmp = *this;
|
||||
_p = &_b->operator[](++_i);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
_iter& operator+=(difference_type d) noexcept {
|
||||
_p = &_b->operator[]((_i += d));
|
||||
return *this;
|
||||
}
|
||||
|
||||
_iter& operator-=(difference_type d) noexcept {
|
||||
_p = &_b->operator[]((_i -= d));
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ring_buffer;
|
||||
|
||||
_iter(ring_pointer b, size_type i) : _b(b), _i(i), _p(&b->operator[](i)) {
|
||||
}
|
||||
|
||||
ring_pointer _b = nullptr;
|
||||
size_type _i = npos;
|
||||
pointer _p = nullptr;
|
||||
|
||||
friend bool operator==(const _iter& a, const _iter& b) noexcept {
|
||||
return a._i == b._i;
|
||||
}
|
||||
|
||||
friend auto operator<=>(const _iter& a, const _iter& b) noexcept {
|
||||
return difference_type(a._i - b._i);
|
||||
}
|
||||
|
||||
friend difference_type operator-(const _iter& a, const _iter& b) noexcept {
|
||||
return a._i - b._i;
|
||||
}
|
||||
|
||||
friend _iter operator+(const _iter& a, difference_type d) noexcept {
|
||||
return { a._b, a._i + d };
|
||||
}
|
||||
|
||||
friend _iter operator-(const _iter& a, difference_type d) noexcept {
|
||||
return { a._b, a._i - d };
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
typename ring_buffer<T, Alloc>::iterator ring_buffer<T, Alloc>::begin() noexcept {
|
||||
return { this, 0 };
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
typename ring_buffer<T, Alloc>::iterator ring_buffer<T, Alloc>::end() noexcept {
|
||||
return { this, size() };
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::begin() const noexcept {
|
||||
return { this, 0 };
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::end() const noexcept {
|
||||
return { this, size() };
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::cbegin() const noexcept {
|
||||
return { this, 0 };
|
||||
}
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::cend() const noexcept {
|
||||
return { this, size() };
|
||||
}
|
||||
|
||||
} // namespace async_mqtt5::detail
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename T, typename Alloc>
|
||||
void swap(
|
||||
async_mqtt5::detail::ring_buffer<T, Alloc>& a,
|
||||
async_mqtt5::detail::ring_buffer<T, Alloc>& b)
|
||||
{
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // !ASYNC_MQTT5_RING_BUFFER_HPP
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef ASYNC_MQTT5_SPINLOCK_HPP
|
||||
#define ASYNC_MQTT5_SPINLOCK_HPP
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
/* prefer using intrinsics directly instead of winnt.h macro */
|
||||
/* http://software.intel.com/en-us/forums/topic/296168 */
|
||||
//#include <intrin.h>
|
||||
#if defined(_M_AMD64) || defined(_M_IX86)
|
||||
#pragma intrinsic(_mm_pause)
|
||||
#define __pause() _mm_pause()
|
||||
/* (if pause not supported by older x86 assembler, "rep nop" is equivalent)*/
|
||||
/*#define __pause() __asm rep nop */
|
||||
#elif defined(_M_IA64)
|
||||
#pragma intrinsic(__yield)
|
||||
#define __pause() __yield()
|
||||
#else
|
||||
#define __pause() YieldProcessor()
|
||||
#endif
|
||||
#elif defined(__x86_64__) || defined(__i386__)
|
||||
#define __pause() __asm__ __volatile__ ("pause")
|
||||
#elif defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
|
||||
#define __pause() __asm__ __volatile__ ("yield")
|
||||
#endif
|
||||
|
||||
// https://rigtorp.se/spinlock/
|
||||
|
||||
class spinlock {
|
||||
std::atomic<bool> lock_ { false };
|
||||
public:
|
||||
void lock() noexcept {
|
||||
for (;;) {
|
||||
// Optimistically assume the lock is free on the first try
|
||||
if (!lock_.exchange(true, std::memory_order_acquire))
|
||||
return;
|
||||
// Wait for lock to be released without generating cache misses
|
||||
while (lock_.load(std::memory_order_relaxed)) __pause();
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock() noexcept {
|
||||
// First do a relaxed load to check if lock is free in order to prevent
|
||||
// unnecessary cache misses if someone does while(!try_lock())
|
||||
return !lock_.load(std::memory_order_relaxed) &&
|
||||
!lock_.exchange(true, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void unlock() noexcept {
|
||||
lock_.store(false, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_SPINLOCK_HPP
|
||||
@@ -0,0 +1,423 @@
|
||||
#ifndef ASYNC_MQTT5_ERROR_HPP
|
||||
#define ASYNC_MQTT5_ERROR_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
|
||||
enum class disconnect_rc_e : std::uint8_t {
|
||||
normal_disconnection = 0x00,
|
||||
disconnect_with_will_message = 0x04,
|
||||
unspecified_error = 0x80,
|
||||
malformed_packet = 0x81,
|
||||
protocol_error = 0x82,
|
||||
implementation_specific_error = 0x83,
|
||||
topic_name_invalid = 0x90,
|
||||
receive_maximum_exceeded = 0x93,
|
||||
topic_alias_invalid = 0x94,
|
||||
packet_too_large = 0x95,
|
||||
message_rate_too_high = 0x96,
|
||||
quota_exceeded = 0x97,
|
||||
administrative_action = 0x98,
|
||||
payload_format_invalid = 0x99
|
||||
};
|
||||
|
||||
namespace client {
|
||||
|
||||
|
||||
enum class error : int {
|
||||
fatal_error = 100,
|
||||
malformed_packet,
|
||||
pid_overrun,
|
||||
reconnected,
|
||||
disconnected,
|
||||
|
||||
// publish
|
||||
qos_not_supported,
|
||||
retain_not_available,
|
||||
topic_alias_maximum_reached
|
||||
};
|
||||
|
||||
|
||||
inline std::string client_error_to_string(error err) {
|
||||
using enum error;
|
||||
|
||||
switch (err) {
|
||||
case fatal_error:
|
||||
return "A fatal error occurred";
|
||||
case malformed_packet:
|
||||
return "Malformed packet has been received";
|
||||
case pid_overrun:
|
||||
return "Ran out of the available packet ids to use";
|
||||
case reconnected:
|
||||
return "The Client has reconnected";
|
||||
case disconnected:
|
||||
return "The Client has been disconnected";
|
||||
case qos_not_supported:
|
||||
return "The Server does not support the specified QoS";
|
||||
case retain_not_available:
|
||||
return "The Server does not support retained messages.";
|
||||
case topic_alias_maximum_reached:
|
||||
return "The Client attempted to send a Topic Alias "
|
||||
"that is greater than Topic Alias Maximum.";
|
||||
default:
|
||||
return "Unknown client error";
|
||||
}
|
||||
}
|
||||
|
||||
struct client_ec_category : public boost::system::error_category {
|
||||
const char* name() const noexcept override { return "mqtt_client_error"; }
|
||||
std::string message(int ev) const noexcept override {
|
||||
return client_error_to_string(static_cast<error>(ev));
|
||||
}
|
||||
};
|
||||
|
||||
inline const client_ec_category& get_error_code_category() {
|
||||
static client_ec_category cat;
|
||||
return cat;
|
||||
}
|
||||
|
||||
inline boost::system::error_code make_error_code(error r) {
|
||||
return { static_cast<int>(r), get_error_code_category() };
|
||||
}
|
||||
|
||||
|
||||
} // end namespace client
|
||||
|
||||
namespace reason_codes {
|
||||
|
||||
enum class category : uint8_t {
|
||||
none,
|
||||
connack, puback, pubrec,
|
||||
pubrel, pubcomp, suback,
|
||||
unsuback, auth, disconnect
|
||||
};
|
||||
|
||||
|
||||
} // end namespace reason_codes
|
||||
|
||||
|
||||
class reason_code {
|
||||
uint8_t _code;
|
||||
reason_codes::category _category { reason_codes::category::none };
|
||||
public:
|
||||
constexpr reason_code() : _code(0xff) {}
|
||||
|
||||
constexpr reason_code(uint8_t code, reason_codes::category cat)
|
||||
: _code(code), _category(cat)
|
||||
{}
|
||||
|
||||
constexpr reason_code(uint8_t code) : _code(code) {}
|
||||
|
||||
reason_code(const reason_code&) = default;
|
||||
reason_code(reason_code&&) = default;
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return _code >= 0x80;
|
||||
}
|
||||
|
||||
constexpr uint8_t value() const noexcept {
|
||||
return _code;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const reason_code& rc) {
|
||||
os << rc.message();
|
||||
return os;
|
||||
}
|
||||
|
||||
friend bool operator<(const reason_code& lhs, const reason_code& rhs) {
|
||||
return lhs._code < rhs._code;
|
||||
}
|
||||
|
||||
friend bool operator==(const reason_code& lhs, const reason_code& rhs) {
|
||||
return lhs._code == rhs._code && lhs._category == rhs._category;
|
||||
}
|
||||
|
||||
std::string message() const {
|
||||
switch (_code) {
|
||||
case 0x00:
|
||||
using enum reason_codes::category;
|
||||
if (_category == suback)
|
||||
return "The subscription is accepted with maximum QoS sent at 0";
|
||||
if (_category == disconnect)
|
||||
return "Close the connection normally";
|
||||
return "The operation completed successfully";
|
||||
case 0x01:
|
||||
return "The subscription is accepted with maximum QoS sent at 1";
|
||||
case 0x02:
|
||||
return "The subscription is accepted with maximum QoS sent at 2";
|
||||
case 0x04:
|
||||
return "The Client wishes to disconnect with the Will Message";
|
||||
case 0x10:
|
||||
return "The message is accepted but there are no subscribers";
|
||||
case 0x11:
|
||||
return "No matching Topic Filter is being used by the Client.";
|
||||
case 0x18:
|
||||
return "Continue the authentication with another step";
|
||||
case 0x19:
|
||||
return "Initiate a re-authentication";
|
||||
case 0x80:
|
||||
return "Unspecified error occurred";
|
||||
case 0x81:
|
||||
return "Data within the packet could not be correctly parsed";
|
||||
case 0x82:
|
||||
return "Data in the packet does not conform to this specification";
|
||||
case 0x83:
|
||||
return "The packet is valid but not accepted by this Server";
|
||||
case 0x84:
|
||||
return "The Server does not support the requested "
|
||||
"version of the MQTT protocol";
|
||||
case 0x85:
|
||||
return "The Client ID is valid but not allowed by this Server";
|
||||
case 0x86:
|
||||
return "The Server does not accept the User Name or Password provided";
|
||||
case 0x87:
|
||||
return "The request is not authorized";
|
||||
case 0x88:
|
||||
return "The MQTT Server is not available";
|
||||
case 0x89:
|
||||
return "The MQTT Server is busy, try again later";
|
||||
case 0x8a:
|
||||
return "The Client has been banned by administrative action";
|
||||
case 0x8b:
|
||||
return "The Server is shutting down";
|
||||
case 0x8c:
|
||||
return "The authentication method is not supported or "
|
||||
"does not match the method currently in use";
|
||||
case 0x8d:
|
||||
return "No packet has been received for 1.5 times the Keepalive time";
|
||||
case 0x8e:
|
||||
return "Another Connection using the same ClientID has connected "
|
||||
"causing this Connection to be closed";
|
||||
case 0x8f:
|
||||
return "The Topic Name is not malformed, but it is not accepted";
|
||||
case 0x90:
|
||||
return "The Topic Name is not malformed, but it is not accepted";
|
||||
case 0x91:
|
||||
return "The Packet Identifier is already in use";
|
||||
case 0x92:
|
||||
return "The Packet Identifier is not known";
|
||||
case 0x93:
|
||||
return "The Client or Server has received more than Receive "
|
||||
"Maximum publication for which it has not sent PUBACK or PUBCOMP";
|
||||
case 0x94:
|
||||
return "The Client or Server received a PUBLISH packet containing "
|
||||
"a Topic Alias greater than the Maximum Topic Alias";
|
||||
case 0x95:
|
||||
return "The packet exceeded the maximum permissible size";
|
||||
case 0x96:
|
||||
return "The received data rate is too high";
|
||||
case 0x97:
|
||||
return "An implementation or administrative imposed limit has been exceeded";
|
||||
case 0x98:
|
||||
return "The Connection is closed due to an administrative action";
|
||||
case 0x99:
|
||||
return "The Payload does not match the specified Payload Format Indicator";
|
||||
case 0x9a:
|
||||
return "The Server does not support retained messages";
|
||||
case 0x9b:
|
||||
return "The Server does not support the QoS the Client specified or "
|
||||
"it is greater than the Maximum QoS specified";
|
||||
case 0x9c:
|
||||
return "The Client should temporarily use another server";
|
||||
case 0x9d:
|
||||
return "The Client should permanently use another server";
|
||||
case 0x9e:
|
||||
return "The Server does not support Shared Subscriptions for this Client";
|
||||
case 0x9f:
|
||||
return "The connection rate limit has been exceeded";
|
||||
case 0xa0:
|
||||
return "The maximum connection time authorized for this "
|
||||
"connection has been exceeded";
|
||||
case 0xa1:
|
||||
return "The Server does not support Subscription Identifiers";
|
||||
case 0xa2:
|
||||
return "The Server does not support Wildcard Subscriptions";
|
||||
case 0xff:
|
||||
return "No reason code";
|
||||
default:
|
||||
return "Invalid reason code.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace reason_codes {
|
||||
|
||||
using enum category;
|
||||
constexpr reason_code empty {};
|
||||
|
||||
constexpr reason_code success { 0x00 };
|
||||
constexpr reason_code normal_disconnection { 0x00, disconnect };
|
||||
constexpr reason_code granted_qos_0 { 0x00, suback };
|
||||
constexpr reason_code granted_qos_1 { 0x01 };
|
||||
constexpr reason_code granted_qos_2 { 0x02 };
|
||||
constexpr reason_code disconnect_with_will_message { 0x04 };
|
||||
constexpr reason_code no_matching_subscribers { 0x10 };
|
||||
constexpr reason_code no_subscription_existed { 0x11 };
|
||||
constexpr reason_code continue_authentication { 0x18 };
|
||||
constexpr reason_code reauthenticate { 0x19 };
|
||||
|
||||
constexpr reason_code unspecified_error { 0x80 };
|
||||
constexpr reason_code malformed_packet { 0x81 };
|
||||
constexpr reason_code protocol_error { 0x82 };
|
||||
constexpr reason_code implementation_specific_error { 0x83 };
|
||||
constexpr reason_code unsupported_protocol_version { 0x84 };
|
||||
constexpr reason_code client_id_not_valid { 0x85 };
|
||||
constexpr reason_code bad_username_or_password { 0x86 };
|
||||
constexpr reason_code not_authorized { 0x87 };
|
||||
constexpr reason_code server_unavailable { 0x88 };
|
||||
constexpr reason_code server_busy { 0x89 };
|
||||
constexpr reason_code banned { 0x8a };
|
||||
constexpr reason_code server_shutting_down { 0x8b };
|
||||
constexpr reason_code bad_authentication_method { 0x8c };
|
||||
constexpr reason_code keep_alive_timeout { 0x8d };
|
||||
constexpr reason_code session_taken_over { 0x8e };
|
||||
constexpr reason_code topic_filter_invalid { 0x8f };
|
||||
constexpr reason_code topic_name_invalid { 0x90 };
|
||||
constexpr reason_code packet_id_in_use { 0x91 };
|
||||
constexpr reason_code packet_id_not_found { 0x92 };
|
||||
constexpr reason_code receive_maximum_exceeded { 0x93 };
|
||||
constexpr reason_code topic_alias_invalid { 0x94 };
|
||||
constexpr reason_code packet_too_large { 0x95 };
|
||||
constexpr reason_code message_rate_too_high { 0x96 };
|
||||
constexpr reason_code quota_exceeded { 0x97 };
|
||||
constexpr reason_code administrative_action { 0x98 };
|
||||
constexpr reason_code payload_format_invalid { 0x99 };
|
||||
constexpr reason_code retain_not_supported { 0x9a };
|
||||
constexpr reason_code qos_not_supported { 0x9b };
|
||||
constexpr reason_code use_another_server { 0x9c };
|
||||
constexpr reason_code server_moved { 0x9d };
|
||||
constexpr reason_code shared_subscriptions_not_supported { 0x9e };
|
||||
constexpr reason_code connection_rate_exceeded { 0x9f };
|
||||
constexpr reason_code maximum_connect_time { 0xa0 };
|
||||
constexpr reason_code subscription_ids_not_supported { 0xa1 };
|
||||
constexpr reason_code wildcard_subscriptions_not_supported { 0xa2 };
|
||||
|
||||
namespace detail {
|
||||
|
||||
using enum category;
|
||||
|
||||
template <category cat>
|
||||
inline std::pair<reason_code*, size_t> valid_codes()
|
||||
requires (cat == connack) {
|
||||
static reason_code valid_codes[] = {
|
||||
success, unspecified_error, malformed_packet,
|
||||
protocol_error, implementation_specific_error,
|
||||
unsupported_protocol_version, client_id_not_valid,
|
||||
bad_username_or_password, not_authorized,
|
||||
server_unavailable, server_busy, banned,
|
||||
bad_authentication_method, topic_name_invalid,
|
||||
packet_too_large, quota_exceeded,
|
||||
payload_format_invalid, retain_not_supported,
|
||||
qos_not_supported, use_another_server,
|
||||
server_moved, connection_rate_exceeded
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <category cat>
|
||||
inline std::pair<reason_code*, size_t> valid_codes()
|
||||
requires (cat == puback || cat == pubrec) {
|
||||
static reason_code valid_codes[] = {
|
||||
success, no_matching_subscribers, unspecified_error,
|
||||
implementation_specific_error, not_authorized,
|
||||
topic_name_invalid, packet_id_in_use,
|
||||
quota_exceeded, payload_format_invalid
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <category cat>
|
||||
inline std::pair<reason_code*, size_t> valid_codes()
|
||||
requires (cat == pubrel || cat == pubcomp) {
|
||||
static reason_code valid_codes[] = {
|
||||
success, packet_id_not_found
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <category cat>
|
||||
inline std::pair<reason_code*, size_t> valid_codes()
|
||||
requires (cat == suback) {
|
||||
static reason_code valid_codes[] = {
|
||||
granted_qos_0, granted_qos_1, granted_qos_2,
|
||||
unspecified_error, implementation_specific_error,
|
||||
not_authorized, topic_filter_invalid,
|
||||
packet_id_in_use, quota_exceeded,
|
||||
shared_subscriptions_not_supported,
|
||||
subscription_ids_not_supported,
|
||||
wildcard_subscriptions_not_supported
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <category cat>
|
||||
inline std::pair<reason_code*, size_t> valid_codes()
|
||||
requires (cat == unsuback) {
|
||||
static reason_code valid_codes[] = {
|
||||
success, no_subscription_existed,
|
||||
unspecified_error, implementation_specific_error,
|
||||
not_authorized, topic_filter_invalid,
|
||||
packet_id_in_use
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <category cat>
|
||||
inline std::pair<reason_code*, size_t> valid_codes()
|
||||
requires (cat == disconnect) {
|
||||
static reason_code valid_codes[] = {
|
||||
normal_disconnection, unspecified_error,
|
||||
malformed_packet,protocol_error,
|
||||
implementation_specific_error, not_authorized,
|
||||
server_busy, server_shutting_down,
|
||||
keep_alive_timeout, session_taken_over,
|
||||
topic_filter_invalid, topic_name_invalid,
|
||||
receive_maximum_exceeded, topic_alias_invalid,
|
||||
packet_too_large,message_rate_too_high,
|
||||
quota_exceeded, administrative_action,
|
||||
payload_format_invalid, retain_not_supported,
|
||||
qos_not_supported, use_another_server,
|
||||
server_moved, shared_subscriptions_not_supported,
|
||||
connection_rate_exceeded, maximum_connect_time,
|
||||
subscription_ids_not_supported,
|
||||
wildcard_subscriptions_not_supported
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace detail
|
||||
} // end namespace reason_codes
|
||||
|
||||
|
||||
template <reason_codes::category cat>
|
||||
inline std::optional<reason_code> to_reason_code(uint8_t code) {
|
||||
auto [ptr, len] = reason_codes::detail::valid_codes<cat>();
|
||||
auto it = std::lower_bound(ptr, ptr + len, reason_code(code));
|
||||
|
||||
if (it->value() == code)
|
||||
return *it;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
namespace boost::system {
|
||||
|
||||
template <>
|
||||
struct is_error_code_enum <async_mqtt5::client::error> : std::true_type {};
|
||||
|
||||
} // end namespace boost::system
|
||||
|
||||
#endif // !ASYNC_MQTT5_ERROR_HPP
|
||||
@@ -0,0 +1,231 @@
|
||||
#ifndef ASYNC_MQTT5_ASSEMBLE_OP_HPP
|
||||
#define ASYNC_MQTT5_ASSEMBLE_OP_HPP
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/base_decoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class data_span : private std::pair<byte_citer, byte_citer> {
|
||||
using base = std::pair<byte_citer, byte_citer>;
|
||||
public:
|
||||
using base::base;
|
||||
|
||||
auto first() const {
|
||||
return base::first;
|
||||
}
|
||||
auto last() const {
|
||||
return base::second;
|
||||
}
|
||||
void expand_suffix(size_t num_chars) {
|
||||
base::second += num_chars;
|
||||
}
|
||||
void remove_prefix(size_t num_chars) {
|
||||
base::first += num_chars;
|
||||
}
|
||||
size_t size() const {
|
||||
return std::distance(base::first, base::second);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class assemble_op {
|
||||
using client_service = ClientService;
|
||||
struct on_read {};
|
||||
|
||||
static constexpr size_t max_packet_size = 65536;
|
||||
|
||||
client_service& _svc;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
std::string& _read_buff;
|
||||
data_span& _data_span;
|
||||
|
||||
public:
|
||||
assemble_op(
|
||||
client_service& svc, Handler&& handler,
|
||||
std::string& read_buff, data_span& active_span
|
||||
) :
|
||||
_svc(svc),
|
||||
_handler(std::move(handler)),
|
||||
_read_buff(read_buff), _data_span(active_span)
|
||||
{}
|
||||
|
||||
assemble_op(assemble_op&&) noexcept = default;
|
||||
assemble_op(const assemble_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
template <typename CompletionCondition>
|
||||
void perform(duration wait_for, CompletionCondition cc) {
|
||||
_read_buff.erase(
|
||||
_read_buff.cbegin(), _data_span.first()
|
||||
);
|
||||
// TODO: respect max packet size from CONNACK
|
||||
_read_buff.resize(max_packet_size);
|
||||
_data_span = {
|
||||
_read_buff.cbegin(),
|
||||
_read_buff.cbegin() + _data_span.size()
|
||||
};
|
||||
|
||||
if (cc(error_code {}, 0) == 0 && _data_span.size()) {
|
||||
return asio::post(
|
||||
asio::prepend(
|
||||
std::move(*this), on_read {}, error_code {},
|
||||
0, wait_for, std::move(cc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Must be evaluated before this is moved
|
||||
auto store_begin = _read_buff.data() + _data_span.size();
|
||||
auto store_size = std::distance(_data_span.last(), _read_buff.cend());
|
||||
|
||||
_svc._stream.async_read_some(
|
||||
asio::buffer(store_begin, store_size), wait_for,
|
||||
asio::prepend(
|
||||
asio::append(std::move(*this), wait_for, std::move(cc)),
|
||||
on_read {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionCondition>
|
||||
void operator()(
|
||||
on_read, error_code ec, size_t bytes_read,
|
||||
duration wait_for, CompletionCondition cc
|
||||
) {
|
||||
if (ec == asio::error::try_again) {
|
||||
_svc._async_sender.resend();
|
||||
_data_span = { _read_buff.cend(), _read_buff.cend() };
|
||||
return perform(wait_for, std::move(cc));
|
||||
}
|
||||
|
||||
if (ec)
|
||||
return complete(ec, 0, 0, {}, {});
|
||||
|
||||
_data_span.expand_suffix(bytes_read);
|
||||
assert(_data_span.size());
|
||||
|
||||
auto control_code = uint8_t(*_data_span.first());
|
||||
|
||||
if ((control_code & 0b11110000) == 0)
|
||||
// close the connection, cancel
|
||||
return complete(client::error::malformed_packet, 0, 0, {}, {});
|
||||
|
||||
auto first = _data_span.first() + 1;
|
||||
auto varlen = decoders::type_parse(
|
||||
first, _data_span.last(), decoders::basic::varint_
|
||||
);
|
||||
|
||||
if (!varlen) {
|
||||
if (_data_span.size() < 5)
|
||||
return perform(wait_for, asio::transfer_at_least(1));
|
||||
return complete(client::error::malformed_packet, 0, 0, {}, {});
|
||||
}
|
||||
|
||||
// TODO: respect max packet size which could be dinamically set by the broker
|
||||
if (*varlen > max_packet_size - std::distance(_data_span.first(), first))
|
||||
return complete(client::error::malformed_packet, 0, 0, {}, {});
|
||||
|
||||
if (std::distance(first, _data_span.last()) < *varlen)
|
||||
return perform(wait_for, asio::transfer_at_least(1));
|
||||
|
||||
_data_span.remove_prefix(
|
||||
std::distance(_data_span.first(), first) + *varlen
|
||||
);
|
||||
|
||||
dispatch(wait_for, control_code, first, first + *varlen);
|
||||
}
|
||||
|
||||
private:
|
||||
static bool valid_header(uint8_t control_byte) {
|
||||
using enum control_code_e;
|
||||
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
if (code == publish)
|
||||
return true;
|
||||
|
||||
auto res = control_byte & 0b00001111;
|
||||
if (code == pubrel)
|
||||
return res == 0b00000010;
|
||||
return res == 0b00000000;
|
||||
}
|
||||
|
||||
static bool contains_packet_id(control_code_e code) {
|
||||
using enum control_code_e;
|
||||
|
||||
return code == puback || code == pubrec
|
||||
|| code == pubrel || code == pubcomp
|
||||
|| code == subscribe || code == suback
|
||||
|| code == unsubscribe || code == unsuback;
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
duration wait_for,
|
||||
uint8_t control_code, byte_citer first, byte_citer last
|
||||
) {
|
||||
using namespace decoders;
|
||||
using enum control_code_e;
|
||||
|
||||
if (!valid_header(control_code))
|
||||
return complete(client::error::malformed_packet, 0, 0, {}, {});
|
||||
|
||||
auto code = control_code_e(control_code & 0b11110000);
|
||||
|
||||
if (code == pingresp)
|
||||
return perform(wait_for, asio::transfer_at_least(0));
|
||||
|
||||
uint16_t packet_id = 0;
|
||||
if (contains_packet_id(code))
|
||||
packet_id = decoders::decode_packet_id(first).value();
|
||||
|
||||
bool is_reply = code != publish && code != auth && code != disconnect;
|
||||
if (is_reply) {
|
||||
_svc._replies.dispatch(error_code {}, code, packet_id, first, last);
|
||||
return perform(wait_for, asio::transfer_at_least(0));
|
||||
}
|
||||
|
||||
complete(error_code {}, packet_id, control_code, first, last);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id, uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(
|
||||
std::move(_handler), ec, packet_id, control_code,
|
||||
first, last
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASSEMBLE_OP_HPP
|
||||
@@ -0,0 +1,235 @@
|
||||
#ifndef ASYNC_MQTT5_ASYNC_SENDER_HPP
|
||||
#define ASYNC_MQTT5_ASYNC_SENDER_HPP
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class write_req {
|
||||
static constexpr unsigned SERIAL_BITS = sizeof(serial_num_t) * 8;
|
||||
|
||||
asio::const_buffer _buffer;
|
||||
serial_num_t _serial_num;
|
||||
unsigned _flags;
|
||||
asio::any_completion_handler<void (error_code)> _handler;
|
||||
|
||||
public:
|
||||
write_req(
|
||||
asio::const_buffer buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
asio::any_completion_handler<void (error_code)> handler
|
||||
) : _buffer(buffer), _serial_num(serial_num), _flags(flags),
|
||||
_handler(std::move(handler)) {}
|
||||
|
||||
static serial_num_t next_serial_num(serial_num_t last) {
|
||||
return ++last;
|
||||
}
|
||||
|
||||
asio::const_buffer buffer() const { return _buffer; }
|
||||
void complete(error_code ec) { std::move(_handler)(ec); }
|
||||
bool throttled() const { return _flags & send_flag::throttled; }
|
||||
bool terminal() const { return _flags & send_flag::terminal; }
|
||||
|
||||
bool operator<(const write_req& other) const {
|
||||
if (prioritized() != other.prioritized()) {
|
||||
return prioritized();
|
||||
}
|
||||
|
||||
auto s1 = _serial_num;
|
||||
auto s2 = other._serial_num;
|
||||
|
||||
if (s1 < s2)
|
||||
return (s2 - s1) < (1 << (SERIAL_BITS - 1));
|
||||
return (s1 - s2) >= (1 << (SERIAL_BITS - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
bool prioritized() const { return _flags & send_flag::prioritized; }
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class async_sender {
|
||||
using client_service = ClientService;
|
||||
|
||||
using queue_allocator_type = asio::recycling_allocator<write_req>;
|
||||
using write_queue_t = std::vector<write_req, queue_allocator_type>;
|
||||
|
||||
ClientService& _svc;
|
||||
write_queue_t _write_queue;
|
||||
bool _write_in_progress { false };
|
||||
|
||||
static constexpr uint16_t MAX_LIMIT = 65535;
|
||||
uint16_t _limit { MAX_LIMIT };
|
||||
uint16_t _quota { MAX_LIMIT };
|
||||
|
||||
serial_num_t _last_serial_num { 0 };
|
||||
|
||||
public:
|
||||
async_sender(ClientService& svc) : _svc(svc) {}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = queue_allocator_type;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
serial_num_t next_serial_num() {
|
||||
return _last_serial_num = write_req::next_serial_num(_last_serial_num);
|
||||
}
|
||||
|
||||
template <typename CompletionToken, typename BufferType>
|
||||
decltype(auto) async_send(
|
||||
const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
auto initiation = [this](
|
||||
auto handler, const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags
|
||||
) {
|
||||
_write_queue.emplace_back(
|
||||
asio::buffer(buffer), serial_num, flags, std::move(handler)
|
||||
);
|
||||
do_write();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, void (error_code)>(
|
||||
std::move(initiation), token, buffer, serial_num, flags
|
||||
);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
auto ops = std::move(_write_queue);
|
||||
for (auto& op : ops)
|
||||
op.complete(asio::error::operation_aborted);
|
||||
}
|
||||
|
||||
void resend() {
|
||||
if (_write_in_progress)
|
||||
return;
|
||||
|
||||
// The _write_in_progress flag is set to true to prevent any write
|
||||
// operations executing before the _write_queue is filled with
|
||||
// all the packets that require resending.
|
||||
_write_in_progress = true;
|
||||
|
||||
auto new_limit = _svc._stream_context.connack_prop(prop::receive_maximum);
|
||||
_limit = new_limit.value_or(MAX_LIMIT);
|
||||
_quota = _limit;
|
||||
|
||||
auto write_queue = std::move(_write_queue);
|
||||
_svc._replies.resend_unanswered();
|
||||
|
||||
for (auto& op : write_queue)
|
||||
op.complete(asio::error::try_again);
|
||||
|
||||
std::stable_sort(_write_queue.begin(), _write_queue.end());
|
||||
|
||||
_write_in_progress = false;
|
||||
do_write();
|
||||
}
|
||||
|
||||
void operator()(write_queue_t write_queue, error_code ec, size_t) {
|
||||
_write_in_progress = false;
|
||||
|
||||
if (ec == asio::error::try_again) {
|
||||
_write_queue.insert(
|
||||
_write_queue.begin(),
|
||||
std::make_move_iterator(write_queue.begin()),
|
||||
std::make_move_iterator(write_queue.end())
|
||||
);
|
||||
return resend();
|
||||
}
|
||||
|
||||
// errors, if any, are propagated to ops
|
||||
for (auto& op : write_queue)
|
||||
op.complete(ec);
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return;
|
||||
|
||||
do_write();
|
||||
}
|
||||
|
||||
void throttled_op_done() {
|
||||
if (_limit == MAX_LIMIT)
|
||||
return;
|
||||
|
||||
++_quota;
|
||||
do_write();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_write() {
|
||||
if (_write_in_progress || _write_queue.empty())
|
||||
return;
|
||||
|
||||
_write_in_progress = true;
|
||||
|
||||
write_queue_t write_queue;
|
||||
|
||||
auto terminal_req = std::find_if(
|
||||
_write_queue.begin(), _write_queue.end(),
|
||||
[](const auto& op) { return op.terminal(); }
|
||||
);
|
||||
if (terminal_req != _write_queue.end()) {
|
||||
write_queue.push_back(std::move(*terminal_req));
|
||||
_write_queue.erase(terminal_req);
|
||||
}
|
||||
else if (_limit == MAX_LIMIT)
|
||||
write_queue = std::move(_write_queue);
|
||||
else {
|
||||
auto throttled_ptr = std::stable_partition(
|
||||
_write_queue.begin(), _write_queue.end(),
|
||||
[](const auto& op) { return !op.throttled(); }
|
||||
);
|
||||
uint16_t dist = std::distance(throttled_ptr, _write_queue.end());
|
||||
uint16_t throttled_num = std::min(dist, _quota);
|
||||
_quota -= throttled_num;
|
||||
throttled_ptr += throttled_num;
|
||||
|
||||
if (throttled_ptr == _write_queue.begin()) {
|
||||
_write_in_progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
write_queue.insert(
|
||||
write_queue.end(),
|
||||
std::make_move_iterator(_write_queue.begin()),
|
||||
std::make_move_iterator(throttled_ptr)
|
||||
);
|
||||
_write_queue.erase(_write_queue.begin(), throttled_ptr);
|
||||
}
|
||||
|
||||
std::vector<asio::const_buffer> buffers;
|
||||
buffers.reserve(write_queue.size());
|
||||
for (const auto& op : write_queue)
|
||||
buffers.push_back(op.buffer());
|
||||
|
||||
_svc._replies.clear_fast_replies();
|
||||
|
||||
_svc._stream.async_write(
|
||||
buffers,
|
||||
asio::prepend(std::ref(*this), std::move(write_queue))
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASYNC_SENDER_HPP
|
||||
@@ -0,0 +1,196 @@
|
||||
#ifndef ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
#define ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_mutex.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/endpoints.hpp>
|
||||
#include <async_mqtt5/impl/reconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/read_op.hpp>
|
||||
#include <async_mqtt5/impl/write_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename StreamContext = std::monostate
|
||||
>
|
||||
class autoconnect_stream {
|
||||
public:
|
||||
using stream_type = StreamType;
|
||||
using stream_context_type = StreamContext;
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
private:
|
||||
using stream_ptr = std::shared_ptr<stream_type>;
|
||||
|
||||
executor_type _stream_executor;
|
||||
async_mutex _conn_mtx;
|
||||
asio::steady_timer _read_timer, _connect_timer;
|
||||
endpoints _endpoints;
|
||||
|
||||
stream_ptr _stream_ptr;
|
||||
stream_context_type& _stream_context;
|
||||
|
||||
template <typename Stream, typename Handler>
|
||||
friend class reconnect_op;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class read_op;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class write_op;
|
||||
|
||||
template <typename Owner, typename DisconnectContext, typename Handler>
|
||||
friend class disconnect_op;
|
||||
|
||||
public:
|
||||
autoconnect_stream(
|
||||
const executor_type& ex, stream_context_type& context
|
||||
) :
|
||||
_stream_executor(ex),
|
||||
_conn_mtx(_stream_executor),
|
||||
_read_timer(_stream_executor), _connect_timer(_stream_executor),
|
||||
_endpoints(_stream_executor, _connect_timer),
|
||||
_stream_context(context)
|
||||
{
|
||||
replace_next_layer(construct_next_layer());
|
||||
}
|
||||
|
||||
using next_layer_type = stream_type;
|
||||
next_layer_type& next_layer() {
|
||||
return *_stream_ptr;
|
||||
}
|
||||
|
||||
const next_layer_type& next_layer() const {
|
||||
return *_stream_ptr;
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream_executor;
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
_endpoints.brokers(std::move(hosts), default_port);
|
||||
}
|
||||
|
||||
bool is_open() const noexcept {
|
||||
return lowest_layer(*_stream_ptr).is_open();
|
||||
}
|
||||
|
||||
void open() {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).open(asio::ip::tcp::v4(), ec);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).cancel(ec);
|
||||
}
|
||||
|
||||
void close() {
|
||||
error_code ec;
|
||||
shutdown(asio::ip::tcp::socket::shutdown_both);
|
||||
lowest_layer(*_stream_ptr).close(ec);
|
||||
_connect_timer.cancel();
|
||||
}
|
||||
|
||||
void shutdown(asio::ip::tcp::socket::shutdown_type what) {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).shutdown(what, ec);
|
||||
}
|
||||
|
||||
bool was_connected() const {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).remote_endpoint(ec);
|
||||
return ec == boost::system::errc::success;
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_read_some(
|
||||
const BufferType& buffer, duration wait_for, CompletionToken&& token
|
||||
) {
|
||||
auto initiation = [this](
|
||||
auto handler, const BufferType& buffer, duration wait_for
|
||||
) {
|
||||
read_op { *this, std::move(handler) }
|
||||
.perform(buffer, wait_for);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, void (error_code, size_t)>(
|
||||
std::move(initiation), token,
|
||||
buffer, wait_for
|
||||
);
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_write(
|
||||
const BufferType& buffer, CompletionToken&& token
|
||||
) {
|
||||
auto initiation = [this](
|
||||
auto handler, const BufferType& buffer
|
||||
) {
|
||||
write_op { *this, std::move(handler) }.perform(buffer);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, void (error_code, size_t)>(
|
||||
std::move(initiation), token, buffer
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
stream_ptr construct_next_layer() const {
|
||||
stream_ptr sptr;
|
||||
if constexpr (has_tls_context<StreamContext>)
|
||||
sptr = std::make_shared<stream_type>(
|
||||
_stream_executor, _stream_context.tls_context()
|
||||
);
|
||||
else
|
||||
sptr = std::make_shared<stream_type>(_stream_executor);
|
||||
|
||||
error_code ec;
|
||||
lowest_layer(*sptr).set_option(
|
||||
asio::socket_base::reuse_address(true), ec
|
||||
);
|
||||
|
||||
return sptr;
|
||||
}
|
||||
|
||||
void replace_next_layer(stream_ptr sptr) {
|
||||
// close() will cancel all outstanding async operations on
|
||||
// _stream_ptr; cancelling posts operation_aborted to handlers
|
||||
// but handlers will be executed after std::exchange below;
|
||||
// handlers should therefore treat (operation_aborted && is_open())
|
||||
// equivalent to try_again.
|
||||
|
||||
if (_stream_ptr)
|
||||
close();
|
||||
std::exchange(_stream_ptr, std::move(sptr));
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_reconnect(stream_ptr s, CompletionToken&& token) {
|
||||
auto initiation = [this](auto handler, stream_ptr s) {
|
||||
reconnect_op { *this, std::move(handler) }.perform(s);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, void (error_code)>(
|
||||
std::move(initiation), token, s
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
@@ -0,0 +1,278 @@
|
||||
#ifndef ASYNC_MQTT5_CLIENT_SERVICE_HPP
|
||||
#define ASYNC_MQTT5_CLIENT_SERVICE_HPP
|
||||
|
||||
#include <boost/asio/experimental/concurrent_channel.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/autoconnect_stream.hpp>
|
||||
#include <async_mqtt5/impl/replies.hpp>
|
||||
#include <async_mqtt5/impl/async_sender.hpp>
|
||||
#include <async_mqtt5/impl/assemble_op.hpp>
|
||||
#include <async_mqtt5/impl/ping_op.hpp>
|
||||
#include <async_mqtt5/impl/sentry_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
|
||||
template <typename StreamType, typename TlsContext>
|
||||
class stream_context;
|
||||
|
||||
template <typename StreamType, typename TlsContext>
|
||||
requires has_tls_layer<StreamType>
|
||||
class stream_context<StreamType, TlsContext> {
|
||||
using tls_context_type = TlsContext;
|
||||
|
||||
mqtt_context _mqtt_context;
|
||||
tls_context_type _tls_context;
|
||||
public:
|
||||
stream_context(TlsContext tls_context) :
|
||||
_tls_context(std::move(tls_context))
|
||||
{}
|
||||
|
||||
mqtt_context& mqtt_context() {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
TlsContext& tls_context() {
|
||||
return _tls_context;
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
_mqtt_context.will = std::move(will);
|
||||
}
|
||||
|
||||
template <typename Prop>
|
||||
auto connack_prop(Prop p) {
|
||||
return _mqtt_context.ca_props[p];
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_mqtt_context.credentials = {
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename StreamType>
|
||||
requires (!has_tls_layer<StreamType>)
|
||||
class stream_context<StreamType, std::monostate> {
|
||||
mqtt_context _mqtt_context;
|
||||
public:
|
||||
stream_context(std::monostate) {}
|
||||
|
||||
mqtt_context& mqtt_context() {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
_mqtt_context.will = std::move(will);
|
||||
}
|
||||
|
||||
template <typename Prop>
|
||||
auto connack_prop(Prop p) {
|
||||
return _mqtt_context.ca_props[p];
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_mqtt_context.credentials = {
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename TlsContext = std::monostate
|
||||
>
|
||||
class client_service {
|
||||
using stream_context_type = detail::stream_context<StreamType, TlsContext>;
|
||||
using stream_type = detail::autoconnect_stream<
|
||||
StreamType, stream_context_type
|
||||
>;
|
||||
public:
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
private:
|
||||
using tls_context_type = TlsContext;
|
||||
using receive_channel = asio::experimental::concurrent_channel<
|
||||
void (error_code, std::string, std::string, publish_props)
|
||||
>;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class detail::async_sender;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class detail::assemble_op;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class detail::ping_op;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class detail::sentry_op;
|
||||
|
||||
stream_context_type _stream_context;
|
||||
stream_type _stream;
|
||||
|
||||
packet_id_allocator _pid_allocator;
|
||||
detail::replies _replies;
|
||||
detail::async_sender<client_service> _async_sender;
|
||||
|
||||
std::string _read_buff;
|
||||
detail::data_span _active_span;
|
||||
|
||||
receive_channel _rec_channel;
|
||||
|
||||
asio::cancellation_signal _cancel_ping;
|
||||
asio::cancellation_signal _cancel_sentry;
|
||||
|
||||
public:
|
||||
|
||||
client_service(
|
||||
const executor_type& ex,
|
||||
const std::string& cnf,
|
||||
tls_context_type tls_context = {}
|
||||
) :
|
||||
_stream_context(std::move(tls_context)),
|
||||
_stream(ex, _stream_context),
|
||||
_async_sender(*this),
|
||||
_rec_channel(ex, 128)
|
||||
{}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream.get_executor();
|
||||
}
|
||||
|
||||
decltype(auto) tls_context()
|
||||
requires (!std::is_same_v<TlsContext, std::monostate>) {
|
||||
return _stream_context.tls_context();
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
if (!is_open())
|
||||
_stream_context.will(std::move(will));
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
if (!is_open())
|
||||
_stream_context.credentials(
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
);
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
if (!is_open())
|
||||
_stream.brokers(std::move(hosts), default_port);
|
||||
}
|
||||
|
||||
template <typename Prop>
|
||||
auto connack_prop(Prop p) {
|
||||
return _stream_context.connack_prop(p);
|
||||
}
|
||||
|
||||
void open_stream() {
|
||||
_stream.open();
|
||||
}
|
||||
|
||||
bool is_open() const {
|
||||
return _stream.is_open();
|
||||
}
|
||||
|
||||
void close_stream() {
|
||||
_stream.close();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
_cancel_ping.emit(asio::cancellation_type::terminal);
|
||||
_cancel_sentry.emit(asio::cancellation_type::terminal);
|
||||
|
||||
_replies.cancel_unanswered();
|
||||
_async_sender.cancel();
|
||||
_stream.close();
|
||||
}
|
||||
|
||||
uint16_t allocate_pid() {
|
||||
return _pid_allocator.allocate();
|
||||
}
|
||||
|
||||
void free_pid(uint16_t pid, bool was_throttled = false) {
|
||||
_pid_allocator.free(pid);
|
||||
if (was_throttled)
|
||||
_async_sender.throttled_op_done();
|
||||
}
|
||||
|
||||
serial_num_t next_serial_num() {
|
||||
return _async_sender.next_serial_num();
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_send(
|
||||
const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return _async_sender.async_send(
|
||||
buffer, serial_num, flags, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_assemble(duration wait_for, CompletionToken&& token) {
|
||||
auto initiation = [this] (auto handler, duration wait_for) mutable {
|
||||
detail::assemble_op {
|
||||
*this, std::move(handler),
|
||||
_read_buff, _active_span
|
||||
}.perform(wait_for, asio::transfer_at_least(0));
|
||||
};
|
||||
|
||||
using signature = void (
|
||||
error_code, uint16_t, uint8_t, byte_citer, byte_citer
|
||||
);
|
||||
return asio::async_initiate<CompletionToken, signature> (
|
||||
std::move(initiation), token, wait_for
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_wait_reply(
|
||||
control_code_e code, uint16_t packet_id, CompletionToken&& token
|
||||
) {
|
||||
return _replies.async_wait_reply(
|
||||
code, packet_id, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
bool channel_store(decoders::publish_message message) {
|
||||
auto& [topic, packet_id, flags, props, payload] = message;
|
||||
return _rec_channel.try_send(
|
||||
error_code {}, std::move(topic),
|
||||
std::move(payload), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_channel_receive(CompletionToken&& token) {
|
||||
// sig = void (error_code, std::string, std::string, publish_props)
|
||||
return _rec_channel.async_receive(
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CLIENT_SERVICE_HPP
|
||||
@@ -0,0 +1,291 @@
|
||||
#ifndef ASYNC_MQTT5_CONNECT_OP_HPP
|
||||
#define ASYNC_MQTT5_CONNECT_OP_HPP
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
#include <boost/beast/websocket.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/internal/codecs/base_decoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
template <
|
||||
typename Stream, typename Handler
|
||||
>
|
||||
class connect_op {
|
||||
struct on_connect {};
|
||||
struct on_tls_handshake {};
|
||||
struct on_ws_handshake {};
|
||||
struct on_send_connect {};
|
||||
struct on_fixed_header {};
|
||||
struct on_read_connack {};
|
||||
|
||||
Stream& _stream;
|
||||
mqtt_context& _ctx;
|
||||
std::decay_t<Handler> _handler;
|
||||
std::unique_ptr<std::string> _buffer_ptr;
|
||||
|
||||
using endpoint = asio::ip::tcp::endpoint;
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
public:
|
||||
connect_op(
|
||||
Stream& stream, Handler&& handler, mqtt_context& ctx
|
||||
) :
|
||||
_stream(stream), _ctx(ctx),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
connect_op(connect_op&&) noexcept = default;
|
||||
connect_op(const connect_op&) = delete;
|
||||
|
||||
using executor_type = typename Stream::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
void perform(
|
||||
const connect_op::epoints& eps, authority_path ap
|
||||
) {
|
||||
lowest_layer(_stream).async_connect(
|
||||
*std::begin(eps),
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_connect {}),
|
||||
*std::begin(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_connect, error_code ec, connect_op::endpoint ep, authority_path ap
|
||||
) {
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
do_tls_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void do_tls_handshake(connect_op::endpoint ep, authority_path ap) {
|
||||
if constexpr (has_tls_handshake<Stream>) {
|
||||
_stream.async_handshake(
|
||||
tls_handshake_type<Stream>::client,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_tls_handshake {}),
|
||||
std::move(ep), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
else if constexpr (has_tls_handshake<typename next_layer_type<Stream>::type>) {
|
||||
_stream.next_layer().async_handshake(
|
||||
tls_handshake_type<typename next_layer_type<Stream>::type>::client,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_tls_handshake {}),
|
||||
std::move(ep), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
do_ws_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_tls_handshake, error_code ec,
|
||||
connect_op::endpoint ep, authority_path ap
|
||||
) {
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
do_ws_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void do_ws_handshake(connect_op::endpoint ep, authority_path ap) {
|
||||
if constexpr (has_ws_handshake<Stream>) {
|
||||
using namespace boost::beast;
|
||||
|
||||
// We'll need to turn off read timeouts on the underlying stream
|
||||
// because the websocket stream has its own timeout system.
|
||||
|
||||
// Set suggested timeout settings for the websocket
|
||||
_stream.set_option(
|
||||
websocket::stream_base::timeout::suggested(role_type::client)
|
||||
);
|
||||
|
||||
_stream.binary(true);
|
||||
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
_stream.set_option(websocket::stream_base::decorator(
|
||||
[](websocket::request_type& req) {
|
||||
req.set(http::field::sec_websocket_protocol, "mqtt");
|
||||
req.set(http::field::user_agent, "boost.mqtt");
|
||||
})
|
||||
);
|
||||
|
||||
_stream.async_handshake(
|
||||
ap.host + ':' + ap.port, ap.path,
|
||||
asio::prepend(std::move(*this), on_ws_handshake {})
|
||||
);
|
||||
}
|
||||
else
|
||||
send_connect();
|
||||
}
|
||||
|
||||
void operator()(on_ws_handshake, error_code ec) {
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
send_connect();
|
||||
}
|
||||
|
||||
void send_connect() {
|
||||
auto packet = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_connect,
|
||||
_ctx.credentials.client_id,
|
||||
_ctx.credentials.username, _ctx.credentials.password,
|
||||
10u, false, _ctx.co_props, _ctx.will
|
||||
);
|
||||
|
||||
const auto& wire_data = packet.wire_data();
|
||||
|
||||
async_mqtt5::detail::async_write(
|
||||
_stream, asio::buffer(wire_data),
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_send_connect{}),
|
||||
std::move(packet)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_send_connect, error_code ec, size_t) {
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
constexpr size_t min_connack_sz = 5;
|
||||
_buffer_ptr = std::make_unique<std::string>(min_connack_sz, 0);
|
||||
|
||||
auto buff = asio::buffer(_buffer_ptr->data(), min_connack_sz);
|
||||
asio::async_read(
|
||||
_stream, buff,
|
||||
asio::prepend(std::move(*this), on_fixed_header {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_fixed_header, error_code ec, size_t num_read
|
||||
) {
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
auto control_byte = (*_buffer_ptr)[0];
|
||||
if (control_byte != 0b00100000)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
auto varlen_ptr = _buffer_ptr->cbegin() + 1;
|
||||
auto varlen = decoders::type_parse(
|
||||
varlen_ptr, _buffer_ptr->cend(), decoders::basic::varint_
|
||||
);
|
||||
if (!varlen)
|
||||
complete(asio::error::try_again);
|
||||
|
||||
auto varlen_sz = std::distance(_buffer_ptr->cbegin() + 1, varlen_ptr);
|
||||
auto remain_len = *varlen -
|
||||
std::distance(varlen_ptr, _buffer_ptr->cbegin() + num_read);
|
||||
|
||||
_buffer_ptr->resize(_buffer_ptr->size() + remain_len);
|
||||
|
||||
auto buff = asio::buffer(_buffer_ptr->data() + num_read, remain_len);
|
||||
auto first = _buffer_ptr->cbegin() + varlen_sz + 1;
|
||||
auto last = first + *varlen;
|
||||
|
||||
asio::async_read(
|
||||
_stream, buff,
|
||||
asio::prepend(
|
||||
asio::append(
|
||||
std::move(*this), uint8_t(control_byte), first, last
|
||||
), on_read_connack {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_read_connack, error_code ec, size_t, uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
auto packet_length = std::distance(first, last);
|
||||
auto rv = decoders::decode_connack(packet_length, first);
|
||||
const auto& [session_present, reason_code, ca_props] = *rv;
|
||||
|
||||
_ctx.ca_props = ca_props;
|
||||
|
||||
// TODO: session_present logic
|
||||
// Unexpected result handling:
|
||||
// - If we don't have a Session State, and we get session_present = true,
|
||||
// we must close the network connection (and restart with a clean start)
|
||||
// - If we have a Session State, and we get session_present = false,
|
||||
// we must discard our Session State
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::connack>(reason_code);
|
||||
if (!rc.has_value()) // reason code not allowed in CONNACK
|
||||
return complete(client::error::malformed_packet);
|
||||
|
||||
complete(to_asio_error(*rc));
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec)
|
||||
);
|
||||
}
|
||||
|
||||
static error_code to_asio_error(reason_code rc) {
|
||||
using namespace boost::asio::error;
|
||||
using namespace reason_codes;
|
||||
|
||||
if (rc == success)
|
||||
return {};
|
||||
|
||||
if (rc == unspecified_error || rc == server_unavailable ||
|
||||
rc == server_busy || rc == connection_rate_exceeded)
|
||||
return connection_refused;
|
||||
|
||||
return access_denied;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CONNECT_OP_HPP
|
||||
@@ -0,0 +1,140 @@
|
||||
#ifndef ASYNC_MQTT5_DISCONNECT_OP_HPP
|
||||
#define ASYNC_MQTT5_DISCONNECT_OP_HPP
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename ClientService,
|
||||
typename DisconnectContext,
|
||||
typename Handler
|
||||
>
|
||||
class disconnect_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_disconnect {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
DisconnectContext _context;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
public:
|
||||
disconnect_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr,
|
||||
DisconnectContext&& context, Handler&& handler
|
||||
) :
|
||||
_svc_ptr(svc_ptr),
|
||||
_context(std::move(context)),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
disconnect_op(disconnect_op&&) noexcept = default;
|
||||
disconnect_op(const disconnect_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
void perform() {
|
||||
auto disconnect = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_disconnect,
|
||||
static_cast<uint8_t>(_context.reason_code), _context.props
|
||||
);
|
||||
|
||||
send_disconnect(std::move(disconnect));
|
||||
}
|
||||
|
||||
void send_disconnect(control_packet<allocator_type> disconnect) {
|
||||
const auto& wire_data = disconnect.wire_data();
|
||||
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::terminal,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_disconnect {}),
|
||||
std::move(disconnect)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
// The connection must be closed even
|
||||
// if we failed to send the DISCONNECT packet
|
||||
// with Reason Code of 0x80 or greater.
|
||||
// TODO: what about rc < 0x80?
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return complete(ec);
|
||||
|
||||
if (_context.terminal) {
|
||||
_svc_ptr->cancel();
|
||||
return complete(error_code {});
|
||||
}
|
||||
|
||||
if (ec == asio::error::try_again)
|
||||
return complete(ec);
|
||||
|
||||
_svc_ptr->close_stream();
|
||||
_svc_ptr->open_stream();
|
||||
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec) {
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, typename CompletionToken>
|
||||
decltype(auto) async_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
bool terminal, const std::shared_ptr<ClientService>& svc_ptr,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiate = [](
|
||||
auto handler, detail::disconnect_context ctx, bool terminal,
|
||||
const std::shared_ptr<ClientService>& svc_ptr
|
||||
) {
|
||||
detail::disconnect_op {
|
||||
svc_ptr, std::move(ctx), std::move(handler)
|
||||
}.perform();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
std::move(initiate), token,
|
||||
detail::disconnect_context { reason_code, props, terminal },
|
||||
terminal, svc_ptr
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_DISCONNECT_HPP
|
||||
@@ -0,0 +1,215 @@
|
||||
#ifndef ASYNC_MQTT5_ENDPOINTS_HPP
|
||||
#define ASYNC_MQTT5_ENDPOINTS_HPP
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class resolve_op {
|
||||
struct on_resolve {};
|
||||
|
||||
Owner& _owner;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
public:
|
||||
resolve_op(
|
||||
Owner& owner, Handler&& handler) :
|
||||
_owner(owner),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
resolve_op(resolve_op&&) noexcept = default;
|
||||
resolve_op(const resolve_op&) = delete;
|
||||
|
||||
using executor_type = typename Owner::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _owner.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
void perform() {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
if (_owner._servers.empty())
|
||||
return complete_post(asio::error::host_not_found, {}, {});
|
||||
|
||||
_owner._current_host++;
|
||||
|
||||
if (_owner._current_host + 1 > _owner._servers.size()) {
|
||||
_owner._current_host = -1;
|
||||
return complete_post(asio::error::try_again, {}, {});
|
||||
}
|
||||
|
||||
authority_path ap = _owner._servers[_owner._current_host];
|
||||
|
||||
_owner._connect_timer.expires_from_now(std::chrono::seconds(5));
|
||||
|
||||
auto timed_resolve = asioex::make_parallel_group(
|
||||
_owner._resolver.async_resolve(ap.host, ap.port, asio::deferred),
|
||||
_owner._connect_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_resolve.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_resolve {}),
|
||||
std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_resolve, auto ord,
|
||||
error_code resolve_ec, epoints epts,
|
||||
error_code timer_ec, authority_path ap
|
||||
) {
|
||||
if (
|
||||
ord[0] == 0 && resolve_ec == asio::error::operation_aborted ||
|
||||
ord[0] == 1 && timer_ec == asio::error::operation_aborted
|
||||
)
|
||||
return complete(asio::error::operation_aborted, {}, {});
|
||||
|
||||
if (!resolve_ec)
|
||||
return complete(error_code {}, std::move(epts), std::move(ap));
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, epoints eps, authority_path ap) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(
|
||||
std::move(_handler), ec,
|
||||
std::move(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void complete_post(error_code ec, epoints eps, authority_path ap) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
asio::post(
|
||||
get_executor(),
|
||||
asio::prepend(
|
||||
std::move(_handler), ec,
|
||||
std::move(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class endpoints {
|
||||
asio::ip::tcp::resolver _resolver;
|
||||
asio::steady_timer& _connect_timer;
|
||||
|
||||
std::vector<authority_path> _servers;
|
||||
|
||||
int _current_host { -1 };
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class resolve_op;
|
||||
|
||||
template <typename T>
|
||||
static constexpr auto to_(T& arg) {
|
||||
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
|
||||
}
|
||||
|
||||
template <typename T, typename Parser>
|
||||
static constexpr auto as_(Parser&& p){
|
||||
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
endpoints(Executor ex, asio::steady_timer& timer)
|
||||
: _resolver(ex), _connect_timer(timer)
|
||||
{}
|
||||
|
||||
using executor_type = asio::ip::tcp::resolver::executor_type;
|
||||
// NOTE: asio::ip::basic_resolver returns executor by value
|
||||
executor_type get_executor() {
|
||||
return _resolver.get_executor();
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_next_endpoint(CompletionToken&& token) {
|
||||
auto initiation = [this](auto handler) {
|
||||
resolve_op { *this, std::move(handler) }.perform();
|
||||
};
|
||||
|
||||
return asio::async_initiate<
|
||||
CompletionToken,
|
||||
void (error_code, epoints, authority_path)
|
||||
>(
|
||||
std::move(initiation), token
|
||||
);
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
_servers.clear();
|
||||
|
||||
std::string host, port, path;
|
||||
|
||||
// loosely based on RFC 3986
|
||||
auto unreserved_ = x3::char_("-a-zA-Z_0-9._~");
|
||||
auto digit_ = x3::char_("0-9");
|
||||
auto separator_ = x3::char_(',');
|
||||
|
||||
auto host_ = as_<std::string>(+unreserved_)[to_(host)];
|
||||
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
|
||||
auto path_ = as_<std::string>(x3::char_('/') >> *unreserved_)[to_(path)];
|
||||
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
|
||||
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
|
||||
|
||||
for (auto b = hosts.begin(); b != hosts.end(); ) {
|
||||
host.clear(); port.clear(); path.clear();
|
||||
if (phrase_parse(b, hosts.end(), uri_, x3::eps(false))) {
|
||||
_servers.push_back({
|
||||
std::move(host),
|
||||
port.empty()
|
||||
? std::to_string(default_port)
|
||||
: std::move(port),
|
||||
std::move(path)
|
||||
});
|
||||
}
|
||||
else b = hosts.end();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ENDPOINTS_HPP
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef ASYNC_MQTT5_MEMORY_H
|
||||
#define ASYNC_MQTT5_MEMORY_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "memory_resource.h"
|
||||
|
||||
namespace pma {
|
||||
|
||||
template <typename T>
|
||||
class alloc : public polymorphic_allocator<T> {
|
||||
using base = polymorphic_allocator<T>;
|
||||
|
||||
public:
|
||||
alloc(pma::memory_resource* r) noexcept : base(r) {}
|
||||
|
||||
template <class T2>
|
||||
alloc(const alloc<T2>& other) noexcept : base(other.resource()) {}
|
||||
|
||||
using value_type = typename base::value_type;
|
||||
using pointer = typename base::value_type*;
|
||||
using const_pointer = const typename base::value_type*;
|
||||
using reference = typename base::value_type&;
|
||||
using const_reference = const typename base::value_type&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
// https://stackoverflow.com/questions/27471053/example-usage-of-propagate-on-container-move-assignment
|
||||
|
||||
using propagate_on_container_copy_assignment = std::false_type;
|
||||
using propagate_on_container_move_assignment = std::false_type;
|
||||
using propagate_on_container_swap = std::false_type;
|
||||
|
||||
alloc select_on_container_copy_construction() const noexcept {
|
||||
return alloc(base::resource());
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace pma
|
||||
|
||||
#endif // !ASYNC_MQTT5_MEMORY_H
|
||||
@@ -0,0 +1,515 @@
|
||||
#ifndef ASYNC_MQTT5_MEMORY_RESOURCE_H
|
||||
#define ASYNC_MQTT5_MEMORY_RESOURCE_H
|
||||
|
||||
// -*- C++ -*-
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <tuple>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace pma {
|
||||
|
||||
struct erased_type { };
|
||||
|
||||
template <size_t...> struct __tuple_indices {};
|
||||
|
||||
template <class _IdxType, _IdxType... _Values>
|
||||
struct __integer_sequence {
|
||||
template <
|
||||
template <class _OIdxType, _OIdxType...> class _ToIndexSeq,
|
||||
class _ToIndexType
|
||||
>
|
||||
using __convert = _ToIndexSeq<_ToIndexType, _Values...>;
|
||||
|
||||
template <size_t _Sp>
|
||||
using __to_tuple_indices = __tuple_indices<(_Values + _Sp)...>;
|
||||
};
|
||||
|
||||
template <size_t _Ep, size_t _Sp>
|
||||
using __make_indices_imp =
|
||||
typename __make_integer_seq<__integer_sequence, size_t, _Ep - _Sp>::template
|
||||
__to_tuple_indices<_Sp>;
|
||||
|
||||
template <size_t _Ep, size_t _Sp = 0>
|
||||
struct __make_tuple_indices {
|
||||
static_assert(_Sp <= _Ep, "__make_tuple_indices input error");
|
||||
using type = __make_indices_imp<_Ep, _Sp>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct allocator_arg_t {
|
||||
explicit allocator_arg_t() = default;
|
||||
};
|
||||
|
||||
template <class _Tp>
|
||||
struct __has_allocator_type
|
||||
{
|
||||
private:
|
||||
struct __two {char __lx; char __lxx;};
|
||||
template <class _Up> static __two __test(...);
|
||||
template <class _Up> static char __test(typename _Up::allocator_type* = 0);
|
||||
public:
|
||||
static const bool value = sizeof(__test<_Tp>(0)) == 1;
|
||||
};
|
||||
|
||||
template <class _Tp, class _Alloc, bool = __has_allocator_type<_Tp>::value>
|
||||
struct __uses_allocator :
|
||||
public std::integral_constant<
|
||||
bool,
|
||||
std::is_convertible<_Alloc, typename _Tp::allocator_type>::value
|
||||
>
|
||||
{};
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
struct __uses_allocator<_Tp, _Alloc, false> : public std::false_type {};
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
struct uses_allocator : public __uses_allocator<_Tp, _Alloc> {};
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
inline constexpr size_t uses_allocator_v = uses_allocator<_Tp, _Alloc>::value;
|
||||
|
||||
template <
|
||||
class _Tp, class _Alloc,
|
||||
bool = std::uses_allocator<_Tp, _Alloc>::value,
|
||||
bool = __has_allocator_type<_Tp>::value
|
||||
>
|
||||
struct __lfts_uses_allocator : public std::false_type {};
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
struct __lfts_uses_allocator<_Tp, _Alloc, false, false> :
|
||||
public std::false_type {};
|
||||
|
||||
template <class _Tp, class _Alloc, bool HasAlloc>
|
||||
struct __lfts_uses_allocator<_Tp, _Alloc, true, HasAlloc> :
|
||||
public std::true_type {};
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
struct __lfts_uses_allocator<_Tp, _Alloc, false, true> :
|
||||
public std::integral_constant<
|
||||
bool,
|
||||
std::is_convertible<_Alloc, typename _Tp::allocator_type>::value
|
||||
|| std::is_same<erased_type, typename _Tp::allocator_type>::value
|
||||
>
|
||||
{};
|
||||
|
||||
template <bool _UsesAlloc, class _Tp, class _Alloc, class ..._Args>
|
||||
struct __lfts_uses_alloc_ctor_imp {
|
||||
static const int value = 0;
|
||||
};
|
||||
|
||||
template <class _Tp, class _Alloc, class ..._Args>
|
||||
struct __lfts_uses_alloc_ctor_imp<true, _Tp, _Alloc, _Args...>
|
||||
{
|
||||
static const bool __ic_first =
|
||||
std::is_constructible<_Tp, std::allocator_arg_t, _Alloc, _Args...>::value;
|
||||
|
||||
static const bool __ic_second =
|
||||
std::conditional<
|
||||
__ic_first,
|
||||
std::false_type,
|
||||
std::is_constructible<_Tp, _Args..., _Alloc>
|
||||
>::type::value;
|
||||
|
||||
static_assert(
|
||||
__ic_first || __ic_second,
|
||||
"Request for uses allocator construction is ill-formed"
|
||||
);
|
||||
|
||||
static const int value = __ic_first ? 1 : 2;
|
||||
};
|
||||
|
||||
template <class _Tp, class _Alloc, class ..._Args>
|
||||
struct __lfts_uses_alloc_ctor :
|
||||
std::integral_constant<int,
|
||||
__lfts_uses_alloc_ctor_imp<
|
||||
__lfts_uses_allocator<_Tp, _Alloc>::value
|
||||
, _Tp, _Alloc, _Args...
|
||||
>::value
|
||||
>
|
||||
{};
|
||||
|
||||
template <class _Tp, class _Allocator, class... _Args>
|
||||
inline void __user_alloc_construct_impl(
|
||||
std::integral_constant<int, 2>, _Tp *__storage,
|
||||
const _Allocator &__a, _Args &&... __args
|
||||
) {
|
||||
new (__storage) _Tp (std::forward<_Args>(__args)..., __a);
|
||||
}
|
||||
|
||||
template <class _Tp, class _Alloc, class ..._Args>
|
||||
inline void __lfts_user_alloc_construct(
|
||||
_Tp * __store, const _Alloc & __a, _Args &&... __args
|
||||
) {
|
||||
__user_alloc_construct_impl(
|
||||
typename __lfts_uses_alloc_ctor<_Tp, _Alloc, _Args...>::type(),
|
||||
__store, __a, std::forward<_Args>(__args)...
|
||||
);
|
||||
}
|
||||
|
||||
inline size_t __aligned_allocation_size(size_t __s, size_t __a) noexcept {
|
||||
return (__s + __a - 1) & ~(__a - 1);
|
||||
}
|
||||
|
||||
class memory_resource {
|
||||
static const size_t __max_align = alignof(max_align_t);
|
||||
|
||||
public:
|
||||
virtual ~memory_resource() = default;
|
||||
|
||||
void* allocate(size_t __bytes, size_t __align = __max_align) {
|
||||
return do_allocate(__bytes, __align);
|
||||
}
|
||||
|
||||
void deallocate(void * __p, size_t __bytes, size_t __align = __max_align) {
|
||||
do_deallocate(__p, __bytes, __align);
|
||||
}
|
||||
|
||||
bool is_equal(memory_resource const & __other) const noexcept {
|
||||
return do_is_equal(__other);
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void* do_allocate(size_t, size_t) = 0;
|
||||
virtual void do_deallocate(void*, size_t, size_t) = 0;
|
||||
virtual bool do_is_equal(memory_resource const &) const noexcept = 0;
|
||||
};
|
||||
|
||||
inline bool operator==(
|
||||
memory_resource const & __lhs,
|
||||
memory_resource const & __rhs
|
||||
) noexcept {
|
||||
return &__lhs == &__rhs || __lhs.is_equal(__rhs);
|
||||
}
|
||||
|
||||
inline
|
||||
bool operator!=(
|
||||
memory_resource const & __lhs,
|
||||
memory_resource const & __rhs
|
||||
) noexcept {
|
||||
return !(__lhs == __rhs);
|
||||
}
|
||||
|
||||
memory_resource* new_delete_resource() noexcept;
|
||||
|
||||
memory_resource* null_memory_resource() noexcept;
|
||||
|
||||
memory_resource* get_default_resource() noexcept;
|
||||
|
||||
// memory_resource* set_default_resource(memory_resource * __new_res) noexcept;
|
||||
|
||||
template <class _ValueType>
|
||||
class polymorphic_allocator {
|
||||
public:
|
||||
using value_type = _ValueType;
|
||||
|
||||
polymorphic_allocator() noexcept :
|
||||
__res_(get_default_resource())
|
||||
{}
|
||||
|
||||
polymorphic_allocator(memory_resource* __r) noexcept :
|
||||
__res_(__r)
|
||||
{}
|
||||
|
||||
polymorphic_allocator(polymorphic_allocator const &) = default;
|
||||
|
||||
template <class _Tp>
|
||||
polymorphic_allocator(polymorphic_allocator<_Tp> const & __other) noexcept :
|
||||
__res_(__other.resource())
|
||||
{}
|
||||
|
||||
polymorphic_allocator &
|
||||
operator=(polymorphic_allocator const &) = delete;
|
||||
|
||||
_ValueType* allocate(size_t __n) {
|
||||
if (__n > __max_size())
|
||||
throw std::bad_array_new_length();
|
||||
return static_cast<_ValueType*>(
|
||||
__res_->allocate(__n * sizeof(_ValueType), alignof(_ValueType))
|
||||
);
|
||||
}
|
||||
|
||||
void deallocate(_ValueType * __p, size_t __n) noexcept {
|
||||
__res_->deallocate(__p, __n * sizeof(_ValueType), alignof(_ValueType));
|
||||
}
|
||||
|
||||
template <class _Tp, class ..._Ts>
|
||||
void construct(_Tp* __p, _Ts &&... __args) {
|
||||
__lfts_user_alloc_construct(
|
||||
__p, *this, std::forward<_Ts>(__args)...
|
||||
);
|
||||
}
|
||||
|
||||
template <class _T1, class _T2, class ..._Args1, class ..._Args2>
|
||||
void construct(
|
||||
std::pair<_T1, _T2>* __p, std::piecewise_construct_t,
|
||||
std::tuple<_Args1...> __x, std::tuple<_Args2...> __y
|
||||
) {
|
||||
::new ((void*)__p) std::pair<_T1, _T2>(
|
||||
std::piecewise_construct,
|
||||
__transform_tuple(
|
||||
typename __lfts_uses_alloc_ctor<
|
||||
_T1, polymorphic_allocator&, _Args1...
|
||||
>::type(),
|
||||
std::move(__x),
|
||||
typename __make_tuple_indices<sizeof...(_Args1)>::type{}
|
||||
),
|
||||
__transform_tuple(
|
||||
typename __lfts_uses_alloc_ctor<
|
||||
_T2, polymorphic_allocator&, _Args2...
|
||||
>::type(),
|
||||
std::move(__y),
|
||||
typename __make_tuple_indices<sizeof...(_Args2)>::type{}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
template <class _T1, class _T2>
|
||||
void construct(std::pair<_T1, _T2>* __p) {
|
||||
construct(__p, std::piecewise_construct, std::tuple<>(), std::tuple<>());
|
||||
}
|
||||
|
||||
template <class _T1, class _T2, class _Up, class _Vp>
|
||||
void construct(std::pair<_T1, _T2> * __p, _Up && __u, _Vp && __v) {
|
||||
construct(
|
||||
__p, std::piecewise_construct,
|
||||
std::forward_as_tuple(std::forward<_Up>(__u)),
|
||||
std::forward_as_tuple(std::forward<_Vp>(__v))
|
||||
);
|
||||
}
|
||||
|
||||
template <class _T1, class _T2, class _U1, class _U2>
|
||||
void construct(std::pair<_T1, _T2> * __p, std::pair<_U1, _U2> const & __pr) {
|
||||
construct(
|
||||
__p, std::piecewise_construct,
|
||||
std::forward_as_tuple(__pr.first),
|
||||
std::forward_as_tuple(__pr.second)
|
||||
);
|
||||
}
|
||||
|
||||
template <class _T1, class _T2, class _U1, class _U2>
|
||||
void construct(std::pair<_T1, _T2> * __p, std::pair<_U1, _U2> && __pr) {
|
||||
construct(
|
||||
__p, std::piecewise_construct,
|
||||
std::forward_as_tuple(std::forward<_U1>(__pr.first)),
|
||||
std::forward_as_tuple(std::forward<_U2>(__pr.second))
|
||||
);
|
||||
}
|
||||
|
||||
template <class _Tp>
|
||||
void destroy(_Tp * __p) noexcept {
|
||||
__p->~_Tp();
|
||||
}
|
||||
|
||||
polymorphic_allocator
|
||||
select_on_container_copy_construction() const noexcept {
|
||||
return polymorphic_allocator();
|
||||
}
|
||||
|
||||
memory_resource* resource() const noexcept {
|
||||
return __res_;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class ..._Args, size_t ..._Idx>
|
||||
std::tuple<_Args&&...>
|
||||
__transform_tuple(
|
||||
std::integral_constant<int, 0>, std::tuple<_Args...>&& __t,
|
||||
__tuple_indices<_Idx...>
|
||||
) const {
|
||||
return std::forward_as_tuple(std::get<_Idx>(std::move(__t))...);
|
||||
}
|
||||
|
||||
template <class ..._Args, size_t ..._Idx>
|
||||
std::tuple<allocator_arg_t const&, polymorphic_allocator&, _Args&&...>
|
||||
__transform_tuple(
|
||||
std::integral_constant<int, 1>, std::tuple<_Args...> && __t,
|
||||
__tuple_indices<_Idx...>
|
||||
) {
|
||||
using _Tup = std::tuple<
|
||||
allocator_arg_t const&,
|
||||
polymorphic_allocator&, _Args&&...
|
||||
>;
|
||||
return _Tup(allocator_arg_t{}, *this, std::get<_Idx>(std::move(__t))...);
|
||||
}
|
||||
|
||||
template <class ..._Args, size_t ..._Idx>
|
||||
std::tuple<_Args&&..., polymorphic_allocator&>
|
||||
__transform_tuple(
|
||||
std::integral_constant<int, 2>, std::tuple<_Args...> && __t,
|
||||
__tuple_indices<_Idx...>
|
||||
) {
|
||||
using _Tup = std::tuple<_Args&&..., polymorphic_allocator&>;
|
||||
return _Tup(std::get<_Idx>(std::move(__t))..., *this);
|
||||
}
|
||||
|
||||
size_t __max_size() const noexcept {
|
||||
return std::numeric_limits<size_t>::max() / sizeof(value_type);
|
||||
}
|
||||
|
||||
memory_resource * __res_;
|
||||
};
|
||||
|
||||
template <class _Tp, class _Up>
|
||||
inline bool operator==(
|
||||
polymorphic_allocator<_Tp> const & __lhs,
|
||||
polymorphic_allocator<_Up> const & __rhs
|
||||
) noexcept {
|
||||
return *__lhs.resource() == *__rhs.resource();
|
||||
}
|
||||
|
||||
template <class _Tp, class _Up>
|
||||
inline bool operator!=(
|
||||
polymorphic_allocator<_Tp> const & __lhs,
|
||||
polymorphic_allocator<_Up> const & __rhs
|
||||
) noexcept {
|
||||
return !(__lhs == __rhs);
|
||||
}
|
||||
|
||||
template <class _CharAlloc>
|
||||
class __resource_adaptor_imp : public memory_resource {
|
||||
using _CTraits = std::allocator_traits<_CharAlloc>;
|
||||
static_assert(
|
||||
std::is_same<typename _CTraits::value_type, char>::value &&
|
||||
std::is_same<typename _CTraits::pointer, char*>::value &&
|
||||
std::is_same<typename _CTraits::void_pointer, void*>::value
|
||||
);
|
||||
|
||||
static const size_t _MaxAlign = alignof(max_align_t);
|
||||
|
||||
using _Alloc = typename _CTraits::template rebind_alloc<
|
||||
typename std::aligned_storage<_MaxAlign, _MaxAlign>::type
|
||||
>;
|
||||
|
||||
using _ValueType = typename _Alloc::value_type;
|
||||
|
||||
_Alloc __alloc_;
|
||||
|
||||
public:
|
||||
using allocator_type = _CharAlloc;
|
||||
|
||||
__resource_adaptor_imp() = default;
|
||||
__resource_adaptor_imp(__resource_adaptor_imp const &) = default;
|
||||
__resource_adaptor_imp(__resource_adaptor_imp &&) noexcept = default;
|
||||
|
||||
explicit __resource_adaptor_imp(allocator_type const & __a) :
|
||||
__alloc_(__a)
|
||||
{}
|
||||
|
||||
explicit __resource_adaptor_imp(allocator_type && __a) :
|
||||
__alloc_(std::move(__a))
|
||||
{}
|
||||
|
||||
__resource_adaptor_imp &
|
||||
operator=(__resource_adaptor_imp const &) = default;
|
||||
|
||||
allocator_type get_allocator() const {
|
||||
return __alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
void * do_allocate(size_t __bytes, size_t) override {
|
||||
if (__bytes > __max_size())
|
||||
throw std::bad_array_new_length();
|
||||
size_t __s = __aligned_allocation_size(__bytes, _MaxAlign) / _MaxAlign;
|
||||
return __alloc_.allocate(__s);
|
||||
}
|
||||
|
||||
void do_deallocate(void * __p, size_t __bytes, size_t) override {
|
||||
size_t __s = __aligned_allocation_size(__bytes, _MaxAlign) / _MaxAlign;
|
||||
__alloc_.deallocate((_ValueType*)__p, __s);
|
||||
}
|
||||
|
||||
bool do_is_equal(memory_resource const & __other) const noexcept override {
|
||||
auto __p = dynamic_cast<__resource_adaptor_imp const *>(&__other);
|
||||
return __p ? __alloc_ == __p->__alloc_ : false;
|
||||
}
|
||||
|
||||
size_t __max_size() const noexcept {
|
||||
return std::numeric_limits<size_t>::max() - _MaxAlign;
|
||||
}
|
||||
};
|
||||
|
||||
template <class _Alloc>
|
||||
using resource_adaptor = __resource_adaptor_imp<
|
||||
typename std::allocator_traits<_Alloc>::template rebind_alloc<char>
|
||||
>;
|
||||
|
||||
class __new_delete_memory_resource_imp : public memory_resource {
|
||||
void* do_allocate(size_t size, size_t /*align*/) override {
|
||||
return new std::byte[size];
|
||||
}
|
||||
|
||||
void do_deallocate(void *p, size_t n, size_t align) override {
|
||||
delete [](std::byte*)(p);
|
||||
}
|
||||
|
||||
bool do_is_equal(memory_resource const & other) const noexcept override {
|
||||
return &other == this;
|
||||
}
|
||||
|
||||
public:
|
||||
~__new_delete_memory_resource_imp() override = default;
|
||||
};
|
||||
|
||||
class __null_memory_resource_imp : public memory_resource {
|
||||
public:
|
||||
~__null_memory_resource_imp() override = default;
|
||||
|
||||
protected:
|
||||
void* do_allocate(size_t, size_t) override {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
void do_deallocate(void*, size_t, size_t) override {}
|
||||
bool do_is_equal(memory_resource const & __other) const noexcept override {
|
||||
return &__other == this;
|
||||
}
|
||||
};
|
||||
|
||||
inline memory_resource* new_delete_resource() noexcept {
|
||||
static __new_delete_memory_resource_imp inst { };
|
||||
return &inst;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// Commented out to prevent creation of polymorphic_allocator without
|
||||
// explicitly provided memory_resource
|
||||
|
||||
inline std::atomic<memory_resource*>& __default_memory_resource(
|
||||
bool set = false, memory_resource* = nullptr
|
||||
) {
|
||||
static std::atomic<memory_resource*> def {
|
||||
new_delete_resource()
|
||||
};
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
inline memory_resource * get_default_resource() noexcept {
|
||||
return __default_memory_resource();
|
||||
}
|
||||
|
||||
inline memory_resource * set_default_resource(memory_resource * __new_res) noexcept {
|
||||
return __default_memory_resource(true, __new_res);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
} // end namespace pma
|
||||
|
||||
#endif // !ASYNC_MQTT5_MEMORY_RESOURCE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef ASYNC_MQTT5_STRING_H
|
||||
#define ASYNC_MQTT5_STRING_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
namespace pma {
|
||||
|
||||
template <class _CharT, class _Traits = std::char_traits<_CharT>>
|
||||
using basic_string =
|
||||
std::basic_string<_CharT, _Traits, alloc<_CharT>>;
|
||||
|
||||
|
||||
using string = basic_string<char>;
|
||||
using u16string = basic_string<char16_t>;
|
||||
using u32string = basic_string<char32_t>;
|
||||
using wstring = basic_string<wchar_t>;
|
||||
|
||||
} // namespace pma
|
||||
|
||||
#endif // !ASYNC_MQTT5_STRING_H
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef ASYNC_MQTT5_VECTOR_H
|
||||
#define ASYNC_MQTT5_VECTOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
namespace pma {
|
||||
|
||||
template <class _ValueT>
|
||||
using vector = std::vector<_ValueT, alloc<_ValueT>>;
|
||||
|
||||
} // namespace pma
|
||||
|
||||
#endif // !ASYNC_MQTT5_VECTOR_H
|
||||
@@ -0,0 +1,408 @@
|
||||
#ifndef ASYNC_MQTT5_BASE_DECODERS_HPP
|
||||
#define ASYNC_MQTT5_BASE_DECODERS_HPP
|
||||
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/spirit/home/x3/binary/binary.hpp>
|
||||
#include <boost/fusion/adapted/std_tuple.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/traits.hpp>
|
||||
|
||||
|
||||
namespace async_mqtt5::decoders {
|
||||
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
template <typename T>
|
||||
struct convert { using type = T; };
|
||||
|
||||
template <typename ...Args>
|
||||
struct convert<boost::fusion::deque<Args...>> {
|
||||
using type = std::tuple<typename convert<Args>::type...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct convert<boost::optional<T>> {
|
||||
using type = std::optional<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct convert<std::vector<T>> {
|
||||
using type = std::vector<typename convert<T>::type>;
|
||||
};
|
||||
|
||||
template <typename T, typename Parser>
|
||||
constexpr auto as(Parser&& p) {
|
||||
return x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
|
||||
template <typename It, typename Parser>
|
||||
auto type_parse(It& first, const It last, const Parser& p) {
|
||||
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Parser&>()));
|
||||
using attr_type = typename x3::traits::attribute_of<Parser, ctx_type>::type;
|
||||
|
||||
using rv_type = typename convert<attr_type>::type;
|
||||
|
||||
std::optional<rv_type> rv;
|
||||
rv_type value {};
|
||||
if (x3::phrase_parse(first, last, as<rv_type>(p), x3::eps(false), value))
|
||||
rv = std::move(value);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
template <typename AttributeType, typename It, typename Parser>
|
||||
auto type_parse(It& first, const It last, const Parser& p) {
|
||||
std::optional<AttributeType> rv;
|
||||
AttributeType value {};
|
||||
if (x3::phrase_parse(first, last, as<AttributeType>(p), x3::eps(false), value))
|
||||
rv = std::move(value);
|
||||
return rv;
|
||||
}
|
||||
|
||||
namespace basic {
|
||||
|
||||
template <typename T>
|
||||
constexpr auto to(T& arg) {
|
||||
return [&](auto& ctx) {
|
||||
using ctx_type = decltype(ctx);
|
||||
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
|
||||
if constexpr (is_boost_iterator<attr_type>)
|
||||
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
|
||||
else
|
||||
arg = x3::_attr(ctx);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename LenParser, typename Subject>
|
||||
class scope_limit {};
|
||||
|
||||
template <typename LenParser, typename Subject>
|
||||
requires (x3::traits::is_parser<LenParser>::value)
|
||||
class scope_limit<LenParser, Subject> :
|
||||
public x3::unary_parser<Subject, scope_limit<LenParser, Subject>> {
|
||||
|
||||
using base_type = x3::unary_parser<Subject, scope_limit<LenParser, Subject>>;
|
||||
LenParser _lp;
|
||||
public:
|
||||
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using attribute_type = typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
scope_limit(const LenParser& lp, const Subject& subject) : base_type(subject), _lp(lp) {}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx& ctx, RCtx& rctx, Attr& attr) const {
|
||||
|
||||
It iter = first;
|
||||
typename x3::traits::attribute_of<LenParser, Ctx>::type len;
|
||||
if (!_lp.parse(iter, last, ctx, rctx, len))
|
||||
return false;
|
||||
if (iter + len > last)
|
||||
return false;
|
||||
|
||||
if (!base_type::subject.parse(iter, iter + len, ctx, rctx, attr))
|
||||
return false;
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Size, typename Subject>
|
||||
requires (std::is_arithmetic_v<Size>)
|
||||
class scope_limit<Size, Subject> :
|
||||
public x3::unary_parser<Subject, scope_limit<Size, Subject>> {
|
||||
|
||||
using base_type = x3::unary_parser<Subject, scope_limit<Size, Subject>>;
|
||||
size_t _limit;
|
||||
public:
|
||||
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using attribute_type = typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
scope_limit(Size limit, const Subject& subject) : base_type(subject), _limit(limit) {}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx& ctx, RCtx& rctx, Attr& attr) const {
|
||||
|
||||
It iter = first;
|
||||
if (iter + _limit > last)
|
||||
return false;
|
||||
|
||||
if (!base_type::subject.parse(iter, iter + _limit, ctx, rctx, attr))
|
||||
return false;
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename LenParser>
|
||||
struct scope_limit_gen {
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return scope_limit<LenParser, Subject> { _lp, x3::as_parser(p) };
|
||||
}
|
||||
LenParser _lp;
|
||||
};
|
||||
|
||||
template <typename Size>
|
||||
requires (std::is_arithmetic_v<Size>)
|
||||
struct scope_limit_gen<Size> {
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return scope_limit<Size, Subject> { limit, x3::as_parser(p) };
|
||||
}
|
||||
Size limit;
|
||||
};
|
||||
|
||||
template <typename Parser>
|
||||
requires (x3::traits::is_parser<Parser>::value)
|
||||
scope_limit_gen<Parser> scope_limit_(const Parser& p) {
|
||||
return { p };
|
||||
}
|
||||
|
||||
template <typename Size>
|
||||
requires (std::is_arithmetic_v<Size>)
|
||||
scope_limit_gen<Size> scope_limit_(Size limit) {
|
||||
return { limit };
|
||||
}
|
||||
|
||||
|
||||
struct verbatim_parser : x3::parser<verbatim_parser> {
|
||||
using attribute_type = std::string;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx&, RCtx&, Attr& attr) const {
|
||||
attr = std::string { first, last };
|
||||
first = last;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto verbatim_ = verbatim_parser{};
|
||||
|
||||
struct varint_parser : x3::parser<varint_parser> {
|
||||
using attribute_type = uint32_t;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx& ctx, RCtx& rctx, Attr& attr) const {
|
||||
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
if (iter == last)
|
||||
return false;
|
||||
|
||||
uint32_t result = 0; unsigned bit_shift = 0;
|
||||
|
||||
for (; iter != last && bit_shift < sizeof(int32_t) * 7; ++iter) {
|
||||
auto val = *iter;
|
||||
if (val & 0b1000'0000u) {
|
||||
result |= (val & 0b0111'1111u) << bit_shift;
|
||||
bit_shift += 7;
|
||||
}
|
||||
else {
|
||||
result |= (static_cast<int32_t>(val) << bit_shift);
|
||||
bit_shift = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bit_shift)
|
||||
return false;
|
||||
|
||||
attr = result;
|
||||
first = ++iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr varint_parser varint_{};
|
||||
|
||||
struct len_prefix_parser : x3::parser<len_prefix_parser> {
|
||||
using attribute_type = std::string;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx& ctx, RCtx& rctx, Attr& attr) const {
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
typename x3::traits::attribute_of<decltype(x3::big_word), Ctx>::type len;
|
||||
if (x3::big_word.parse(iter, last, ctx, rctx, len)) {
|
||||
if (std::distance(iter, last) < len)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
attr = std::string(iter, iter + len);
|
||||
first = iter + len;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr len_prefix_parser utf8_{};
|
||||
constexpr len_prefix_parser binary_{};
|
||||
|
||||
/*
|
||||
Boost Spirit incorrectly deduces atribute type for a parser of the form
|
||||
(eps(a) | parser1) >> (eps(b) | parser)
|
||||
and we had to create if_ parser to remedy the issue
|
||||
*/
|
||||
|
||||
template <typename Subject>
|
||||
class conditional_parser : public x3::unary_parser<Subject, conditional_parser<Subject>> {
|
||||
using base_type = x3::unary_parser<Subject, conditional_parser<Subject>>;
|
||||
bool _condition;
|
||||
public:
|
||||
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using subject_attr_type = typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
using attribute_type = boost::optional<subject_attr_type>;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
conditional_parser(const Subject& s, bool condition) : base_type(s), _condition(condition) {}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx& ctx, RCtx& rctx, Attr& attr) const {
|
||||
if (!_condition)
|
||||
return true;
|
||||
|
||||
It iter = first;
|
||||
subject_attr_type sattr {};
|
||||
if (!base_type::subject.parse(iter, last, ctx, rctx, sattr))
|
||||
return false;
|
||||
|
||||
attr.emplace(std::move(sattr));
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct conditional_gen {
|
||||
bool _condition;
|
||||
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return conditional_parser<Subject> { p, _condition };
|
||||
}
|
||||
};
|
||||
|
||||
inline conditional_gen if_(bool condition) {
|
||||
return { condition };
|
||||
}
|
||||
|
||||
} // end namespace basic
|
||||
|
||||
|
||||
namespace prop {
|
||||
|
||||
namespace basic = async_mqtt5::decoders::basic;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Prop>
|
||||
bool parse_to_prop(It& iter, const It last, const Ctx& ctx, RCtx& rctx, Prop& prop) {
|
||||
using prop_type = decltype(prop);
|
||||
|
||||
bool rv = false;
|
||||
if constexpr (is_optional<prop_type>) {
|
||||
using value_type = typename std::remove_reference_t<prop_type>::value_type;
|
||||
if constexpr (std::is_same_v<value_type, uint8_t>) {
|
||||
uint8_t attr;
|
||||
rv = x3::byte_.parse(iter, last, ctx, rctx, attr);
|
||||
prop = attr;
|
||||
}
|
||||
if constexpr (std::is_same_v<value_type, int16_t>) {
|
||||
int16_t attr;
|
||||
rv = x3::big_word.parse(iter, last, ctx, rctx, attr);
|
||||
prop = attr;
|
||||
}
|
||||
if constexpr (std::is_same_v<value_type, uint16_t>) {
|
||||
uint16_t attr;
|
||||
rv = x3::big_word.parse(iter, last, ctx, rctx, attr);
|
||||
prop = attr;
|
||||
}
|
||||
if constexpr (std::is_same_v<value_type, int32_t>) {
|
||||
int32_t attr;
|
||||
rv = x3::big_dword.parse(iter, last, ctx, rctx, attr);
|
||||
prop = attr;
|
||||
}
|
||||
if constexpr (std::is_same_v<value_type, uint32_t>) {
|
||||
uint32_t attr;
|
||||
rv = basic::varint_.parse(iter, last, ctx, rctx, attr);
|
||||
prop = attr;
|
||||
}
|
||||
if constexpr (std::is_same_v<value_type, std::string>) {
|
||||
std::string attr;
|
||||
rv = basic::utf8_.parse(iter, last, ctx, rctx, attr);
|
||||
prop.emplace(std::move(attr));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (async_mqtt5::is_vector<prop_type>) {
|
||||
std::string value;
|
||||
rv = basic::utf8_.parse(iter, last, ctx, rctx, value);
|
||||
if (rv) prop.push_back(std::move(value));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <typename Props>
|
||||
class prop_parser : public x3::parser<prop_parser<Props>> {
|
||||
public:
|
||||
using attribute_type = Props;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx& ctx, RCtx& rctx, Attr& attr) const {
|
||||
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
if (iter == last)
|
||||
return true;
|
||||
|
||||
uint32_t props_length;
|
||||
if (!basic::varint_.parse(iter, last, ctx, rctx, props_length))
|
||||
return false;
|
||||
|
||||
const It scoped_last = iter + props_length;
|
||||
// attr = Props{};
|
||||
|
||||
while (iter < scoped_last) {
|
||||
uint8_t prop_id = *iter++;
|
||||
bool rv = true;
|
||||
It saved = iter;
|
||||
|
||||
attr.apply_on(prop_id, [&rv, &iter, scoped_last, &ctx, &rctx](auto& prop) {
|
||||
rv = detail::parse_to_prop(iter, scoped_last, ctx, rctx, prop);
|
||||
});
|
||||
|
||||
// either rv = false or property with prop_id was not found
|
||||
if (!rv || iter == saved)
|
||||
break;
|
||||
}
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Props>
|
||||
constexpr auto props_ = prop_parser<Props>{};
|
||||
|
||||
} // end namespace prop
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::decoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_BASE_DECODERS_HPP
|
||||
@@ -0,0 +1,492 @@
|
||||
#ifndef ASYNC_MQTT5_BASE_ENCODERS_HPP
|
||||
#define ASYNC_MQTT5_BASE_ENCODERS_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/traits.hpp>
|
||||
|
||||
namespace async_mqtt5::encoders {
|
||||
|
||||
namespace basic {
|
||||
|
||||
inline void to_variable_bytes(std::string& s, int32_t val) {
|
||||
if (val > 0xfffffff) return;
|
||||
while (val > 127) {
|
||||
s.push_back(char((val & 0b01111111) | 0b10000000));
|
||||
val >>= 7;
|
||||
}
|
||||
s.push_back(val & 0b01111111);
|
||||
}
|
||||
|
||||
inline size_t variable_length(int32_t val) {
|
||||
if (val > 0xfffffff) return 0;
|
||||
size_t rv = 1;
|
||||
for (; val > 127; ++rv) val >>= 7;
|
||||
return rv;
|
||||
}
|
||||
|
||||
struct encoder {};
|
||||
|
||||
template <size_t bits, typename repr = uint8_t>
|
||||
class flag_def : public encoder {
|
||||
template <size_t num_bits>
|
||||
using least_type = std::conditional_t<
|
||||
num_bits <= 8, uint8_t,
|
||||
std::conditional_t<
|
||||
num_bits <= 16, uint16_t,
|
||||
std::conditional_t<
|
||||
num_bits <= 32, uint32_t,
|
||||
std::conditional_t<num_bits <= 64, uint64_t, void>
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
template <size_t oth_bits, typename oth_repr>
|
||||
friend class flag_def;
|
||||
|
||||
repr _val { 0 };
|
||||
|
||||
public:
|
||||
flag_def(repr val) : _val(val) {}
|
||||
flag_def() = default;
|
||||
|
||||
template <class T, typename projection = std::identity>
|
||||
requires (is_optional<T>)
|
||||
auto operator()(T&& value, projection proj = {}) const {
|
||||
if constexpr (std::is_same_v<projection, std::identity>) {
|
||||
repr val = value.has_value();
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
else {
|
||||
repr val = value.has_value() ? static_cast<repr>(std::invoke(proj, *value)) : 0;
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, typename projection = std::identity>
|
||||
requires (!is_optional<T>)
|
||||
auto operator()(T&& value, projection proj = {}) const {
|
||||
auto val = static_cast<repr>(std::invoke(proj, value));
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
|
||||
uint16_t byte_size() const { return sizeof(repr); }
|
||||
|
||||
template <size_t rhs_bits, typename rhs_repr>
|
||||
auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
|
||||
using res_repr = least_type<bits + rhs_bits>;
|
||||
auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
|
||||
return flag_def<bits + rhs_bits, res_repr> { val };
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
using namespace boost::endian;
|
||||
size_t sz = s.size(); s.resize(sz + sizeof(repr));
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<repr, sizeof(repr), order::big>(p, _val);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t bits, typename repr = uint8_t>
|
||||
constexpr auto flag = flag_def<bits, repr>{};
|
||||
|
||||
|
||||
template <typename T, typename Repr>
|
||||
class int_val : public encoder {
|
||||
T _val;
|
||||
public:
|
||||
int_val(T val) : _val(val) {}
|
||||
|
||||
uint16_t byte_size() const {
|
||||
if constexpr (is_optional<T>) {
|
||||
if (_val) return uint16_t(val_length(*_val));
|
||||
return uint16_t(0);
|
||||
}
|
||||
else
|
||||
return uint16_t(val_length(_val));
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if constexpr (is_optional<T>) {
|
||||
if (_val) return encode_val(s, *_val);
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return encode_val(s, _val);
|
||||
}
|
||||
private:
|
||||
template <typename U>
|
||||
static size_t val_length(U&& val) {
|
||||
if constexpr (std::is_same_v<Repr, intptr_t>)
|
||||
return variable_length(int32_t(val));
|
||||
else
|
||||
return sizeof(Repr);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static std::string& encode_val(std::string& s, U&& val) {
|
||||
using namespace boost::endian;
|
||||
if constexpr (std::is_same_v<Repr, intptr_t>) {
|
||||
to_variable_bytes(s, int32_t(val));
|
||||
return s;
|
||||
}
|
||||
else {
|
||||
size_t sz = s.size(); s.resize(sz + sizeof(Repr));
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<Repr, sizeof(Repr), order::big>(p, val);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Repr>
|
||||
class int_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& val) const {
|
||||
return int_val<T, Repr> { std::forward<T>(val) };
|
||||
}
|
||||
|
||||
template <typename T, typename projection>
|
||||
auto operator()(T&& val, projection proj) const {
|
||||
if constexpr (is_optional<T>) {
|
||||
using rv_type = std::invoke_result_t<
|
||||
projection, typename std::remove_cvref_t<T>::value_type
|
||||
>;
|
||||
if (val.has_value())
|
||||
return (*this)(std::invoke(proj, *val));
|
||||
return int_val<rv_type, Repr> { rv_type {} };
|
||||
}
|
||||
else {
|
||||
using rv_type = std::invoke_result_t<projection, T>;
|
||||
return int_val<rv_type, Repr> { std::invoke(proj, val) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto byte_ = int_def<uint8_t>{};
|
||||
constexpr auto int16_ = int_def<uint16_t>{};
|
||||
constexpr auto int32_ = int_def<uint32_t>{};
|
||||
constexpr auto varlen_ = int_def<intptr_t>{};
|
||||
|
||||
|
||||
template <typename T>
|
||||
class array_val : public encoder {
|
||||
T _val;
|
||||
bool _with_length;
|
||||
public:
|
||||
array_val(T val, bool with_length) : _val(val), _with_length(with_length) {
|
||||
static_assert(std::is_reference_v<T> || std::is_same_v<T, std::string_view>);
|
||||
}
|
||||
|
||||
uint16_t byte_size() const {
|
||||
if constexpr (is_optional<T>)
|
||||
return uint16_t(_val ? _with_length * 2 + val_length(*_val) : 0);
|
||||
else
|
||||
return uint16_t(_with_length * 2 + val_length(_val));
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if constexpr (is_optional<T>) {
|
||||
if (_val) return encode_val(s, *_val);
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return encode_val(s, _val);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename U>
|
||||
static size_t val_length(U&& val) {
|
||||
if constexpr (std::same_as<std::remove_cvref_t<U>, const char*>)
|
||||
return std::strlen(val);
|
||||
if constexpr (requires { val.size(); })
|
||||
return val.size();
|
||||
else // fallback to type const char (&)[N] (substract 1 for trailing 0)
|
||||
return sizeof(val) - 1;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
std::string& encode_val(std::string& s, U&& u) const {
|
||||
using namespace boost::endian;
|
||||
int16_t byte_len = int16_t(val_length(std::forward<U>(u)));
|
||||
if (byte_len == 0 && !_with_length) return s;
|
||||
if (_with_length) {
|
||||
size_t sz = s.size(); s.resize(sz + 2);
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<int16_t, sizeof(int16_t), order::big>(p, byte_len);
|
||||
}
|
||||
s.append(std::begin(u), std::begin(u) + byte_len);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool with_length = true>
|
||||
class array_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& val) const {
|
||||
return array_val<T> { std::forward<T>(val), with_length };
|
||||
}
|
||||
|
||||
template <typename T, typename projection>
|
||||
auto operator()(T&& val, projection proj) const {
|
||||
if constexpr (is_optional<T>) {
|
||||
using rv_type = std::invoke_result_t<
|
||||
projection, typename std::remove_cvref_t<T>::value_type
|
||||
>;
|
||||
if (val.has_value())
|
||||
return (*this)(std::invoke(proj, *val));
|
||||
return array_val<rv_type> { rv_type {}, false };
|
||||
}
|
||||
else {
|
||||
const auto& av = std::invoke(proj, val);
|
||||
return array_val<T> { av, true };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using utf8_def = array_def<true>;
|
||||
|
||||
constexpr auto utf8_ = utf8_def{};
|
||||
constexpr auto binary_ = array_def<true>{}; // for now
|
||||
constexpr auto verbatim_ = array_def<false>{};
|
||||
|
||||
|
||||
template <class T, class U>
|
||||
class composed_val : public encoder {
|
||||
T _lhs; U _rhs;
|
||||
public:
|
||||
composed_val(T lhs, U rhs) :
|
||||
_lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs)) {}
|
||||
|
||||
uint16_t byte_size() const {
|
||||
return uint16_t(_lhs.byte_size() + _rhs.byte_size());
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
_lhs.encode(s);
|
||||
return _rhs.encode(s);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
requires (std::derived_from<std::decay_t<T>, encoder> && std::derived_from<std::decay_t<U>, encoder>)
|
||||
inline auto operator&(T&& t, U&& u) {
|
||||
return composed_val(std::forward<T>(t), std::forward<U>(u));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires (std::derived_from<std::decay_t<T>, encoder>)
|
||||
std::string& operator<<(std::string& s, T&& t) {
|
||||
return t.encode(s);
|
||||
}
|
||||
|
||||
} // end namespace basic
|
||||
|
||||
namespace detail {
|
||||
template <std::integral_constant p, std::size_t I, typename Tuple>
|
||||
constexpr bool match_v = std::is_same_v<decltype(p), typename std::tuple_element_t<I, Tuple>::key>;
|
||||
|
||||
template <std::integral_constant p, typename Tuple, typename Idxs = std::make_index_sequence<std::tuple_size_v<Tuple>>>
|
||||
struct type_index;
|
||||
|
||||
template <std::integral_constant p, template <typename...> typename Tuple, typename... Args, std::size_t... Is>
|
||||
struct type_index<p, Tuple<Args...>, std::index_sequence<Is...>>
|
||||
: std::integral_constant<std::size_t, ((Is * match_v<p, Is, Tuple<Args...>>)+... + 0)> {
|
||||
static_assert(1 == (match_v<p, Is, Tuple<Args...>> + ... + 0), "T doesn't appear once in tuple");
|
||||
};
|
||||
} // end namespace detail
|
||||
|
||||
namespace prop {
|
||||
|
||||
|
||||
namespace pp = async_mqtt5::prop;
|
||||
|
||||
template <std::integral_constant p, typename T>
|
||||
struct prop_encoder_type { using key = decltype(p); using value = T; };
|
||||
|
||||
using encoder_types = std::tuple<
|
||||
prop_encoder_type<pp::shared_subscription_available, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::payload_format_indicator, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::message_expiry_interval, basic::int_def<int32_t>>,
|
||||
prop_encoder_type<pp::content_type, basic::utf8_def>,
|
||||
prop_encoder_type<pp::response_topic, basic::utf8_def>,
|
||||
prop_encoder_type<pp::correlation_data, basic::utf8_def>,
|
||||
prop_encoder_type<pp::subscription_identifier, basic::int_def<intptr_t>>,
|
||||
prop_encoder_type<pp::session_expiry_interval, basic::int_def<int32_t>>,
|
||||
prop_encoder_type<pp::assigned_client_identifier, basic::utf8_def>,
|
||||
prop_encoder_type<pp::server_keep_alive, basic::int_def<int16_t>>,
|
||||
prop_encoder_type<pp::authentication_method, basic::utf8_def>,
|
||||
prop_encoder_type<pp::authentication_data, basic::utf8_def>,
|
||||
prop_encoder_type<pp::request_problem_information, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::will_delay_interval, basic::int_def<int32_t>>,
|
||||
prop_encoder_type<pp::request_response_information, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::response_information, basic::utf8_def>,
|
||||
prop_encoder_type<pp::server_reference, basic::utf8_def>,
|
||||
prop_encoder_type<pp::reason_string, basic::utf8_def>,
|
||||
prop_encoder_type<pp::receive_maximum, basic::int_def<int16_t>>,
|
||||
prop_encoder_type<pp::topic_alias_maximum, basic::int_def<int16_t>>,
|
||||
prop_encoder_type<pp::topic_alias, basic::int_def<int16_t>>,
|
||||
prop_encoder_type<pp::maximum_qos, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::retain_available, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::user_property, basic::utf8_def>,
|
||||
prop_encoder_type<pp::maximum_packet_size, basic::int_def<int32_t>>,
|
||||
prop_encoder_type<pp::wildcard_subscription_available, basic::int_def<uint8_t>>,
|
||||
prop_encoder_type<pp::subscription_identifier_available, basic::int_def<uint8_t>>
|
||||
>;
|
||||
|
||||
template <std::integral_constant p>
|
||||
constexpr auto encoder_for_prop = typename std::tuple_element_t<
|
||||
detail::type_index<p, encoder_types>::value, encoder_types
|
||||
>::value {};
|
||||
|
||||
|
||||
template <typename T, std::integral_constant p>
|
||||
class prop_val;
|
||||
|
||||
template <typename T, std::integral_constant p>
|
||||
requires (!is_vector<T> && is_optional<T>)
|
||||
class prop_val<T, p> : public basic::encoder {
|
||||
// T is always std::optional
|
||||
using opt_type = typename std::remove_cvref_t<T>::value_type;
|
||||
// allows T to be reference type to std::optional
|
||||
static inline std::optional<opt_type> nulltype;
|
||||
T _val;
|
||||
public:
|
||||
prop_val(T val) : _val(val) {
|
||||
static_assert(std::is_reference_v<T>);
|
||||
}
|
||||
prop_val() : _val(nulltype) {}
|
||||
|
||||
size_t byte_size() const {
|
||||
if (!_val) return 0;
|
||||
auto sval = encoder_for_prop<p>(_val);
|
||||
return 1 + sval.byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if (!_val)
|
||||
return s;
|
||||
s.push_back(p());
|
||||
auto sval = encoder_for_prop<p>(_val);
|
||||
return sval.encode(s);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, std::integral_constant p>
|
||||
requires (is_vector<T>)
|
||||
class prop_val<T, p> : public basic::encoder {
|
||||
// allows T to be reference type to std::vector
|
||||
static inline std::remove_cvref_t<T> nulltype;
|
||||
T _val;
|
||||
public:
|
||||
prop_val(T val) : _val(val) {
|
||||
static_assert(std::is_reference_v<T>);
|
||||
}
|
||||
|
||||
prop_val() : _val(nulltype) { }
|
||||
|
||||
size_t byte_size() const {
|
||||
if (_val.empty()) return 0;
|
||||
size_t total_size = 0;
|
||||
for (const auto& pr: _val) {
|
||||
auto sval = encoder_for_prop<p>(pr);
|
||||
size_t prop_size = sval.byte_size();
|
||||
if (prop_size) total_size += 1 + prop_size;
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if (_val.empty())
|
||||
return s;
|
||||
|
||||
for (const auto& pr: _val) {
|
||||
auto sval = encoder_for_prop<p>(pr);
|
||||
s.push_back(p());
|
||||
sval.encode(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Props>
|
||||
class props_val : public basic::encoder {
|
||||
static inline std::decay_t<Props> nulltype;
|
||||
|
||||
template <std::integral_constant P, typename T>
|
||||
static auto to_prop_val(const T& val) {
|
||||
return prop_val<const T&, P>(val);
|
||||
}
|
||||
|
||||
template <std::integral_constant... Ps>
|
||||
static auto to_prop_vals(const pp::properties<Ps...>& props) {
|
||||
return std::make_tuple(to_prop_val<Ps>(props[Ps])...);
|
||||
}
|
||||
|
||||
template <class Func>
|
||||
auto apply_each(Func&& func) const {
|
||||
return std::apply([&func](const auto&... props) {
|
||||
return (std::invoke(func, props), ...);
|
||||
}, _prop_vals);
|
||||
}
|
||||
|
||||
decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
|
||||
bool _may_omit;
|
||||
public:
|
||||
props_val(Props val, bool may_omit) : _prop_vals(to_prop_vals(val)), _may_omit(may_omit) {
|
||||
static_assert(std::is_reference_v<Props>);
|
||||
}
|
||||
props_val(bool may_omit) : _prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit) { }
|
||||
|
||||
size_t byte_size() const {
|
||||
size_t psize = props_size();
|
||||
if (_may_omit && psize == 0) return 0;
|
||||
return psize + basic::varlen_(psize).byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
size_t psize = props_size();
|
||||
if (_may_omit && psize == 0) return s;
|
||||
basic::varlen_(psize).encode(s);
|
||||
apply_each([&s](const auto& pv) { return pv.encode(s); });
|
||||
return s;
|
||||
}
|
||||
private:
|
||||
size_t props_size() const {
|
||||
size_t retval = 0;
|
||||
apply_each([&retval](const auto& pv) { return retval += pv.byte_size(); });
|
||||
return retval;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool may_omit>
|
||||
class props_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& prop_container) const {
|
||||
if constexpr (is_optional<T>) {
|
||||
if (prop_container.has_value())
|
||||
return (*this)(*prop_container);
|
||||
return props_val<const typename std::remove_cvref_t<T>::value_type&>(true);
|
||||
}
|
||||
else {
|
||||
return props_val<T> { prop_container, may_omit };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto props_ = props_def<false>{};
|
||||
constexpr auto props_may_omit_ = props_def<true>{};
|
||||
|
||||
} // end namespace prop
|
||||
|
||||
} // end namespace async_mqtt5::encoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_BASE_ENCODERS_HPP
|
||||
@@ -0,0 +1,284 @@
|
||||
#ifndef ASYNC_MQTT5_MESSAGE_DECODERS_HPP
|
||||
#define ASYNC_MQTT5_MESSAGE_DECODERS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/base_decoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
namespace async_mqtt5::decoders {
|
||||
|
||||
using byte_citer = detail::byte_citer;
|
||||
|
||||
using fixed_header = std::tuple<
|
||||
uint8_t, // control byte
|
||||
uint32_t // remaining_length
|
||||
>;
|
||||
|
||||
inline std::optional<fixed_header> decode_fixed_header(
|
||||
byte_citer& it, const byte_citer last
|
||||
) {
|
||||
auto fixed_header_ = x3::byte_ >> basic::varint_;
|
||||
return type_parse(it, last, fixed_header_);
|
||||
}
|
||||
|
||||
using packet_id = uint16_t;
|
||||
|
||||
inline std::optional<packet_id> decode_packet_id(
|
||||
byte_citer& it
|
||||
) {
|
||||
auto packet_id_ = x3::big_word;
|
||||
return type_parse(it, it + sizeof(uint16_t), packet_id_);
|
||||
}
|
||||
|
||||
using connect_message = std::tuple<
|
||||
std::string, // client_id,
|
||||
std::optional<std::string>, // user_name,
|
||||
std::optional<std::string>, // password,
|
||||
uint16_t, // keep_alive,
|
||||
bool, // clean_start,
|
||||
connect_props, // props,
|
||||
std::optional<will> // will
|
||||
>;
|
||||
|
||||
inline std::optional<connect_message> decode_connect(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto var_header_ =
|
||||
basic::utf8_ >> // MQTT
|
||||
x3::byte_ >> // (num 5)
|
||||
x3::byte_ >> // conn_flags_
|
||||
x3::big_word >> // keep_alive
|
||||
prop::props_<connect_props>;
|
||||
|
||||
auto vh = type_parse(it, it + remain_length, var_header_);
|
||||
if (!vh)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh;
|
||||
|
||||
if (mqtt_str != "MQTT" || version != 5)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
bool has_will = (flags & 0b00000100);
|
||||
bool has_uname = (flags & 0b10000000);
|
||||
bool has_pwd = (flags & 0b01000000);
|
||||
|
||||
auto payload_ =
|
||||
basic::utf8_ >> // client_id
|
||||
basic::if_(has_will)[prop::props_<will_props>] >>
|
||||
basic::if_(has_will)[basic::utf8_] >> // will topic
|
||||
basic::if_(has_will)[basic::binary_] >> // will message
|
||||
basic::if_(has_uname)[basic::utf8_] >> // username
|
||||
basic::if_(has_pwd)[basic::utf8_]; // password
|
||||
|
||||
auto pload = type_parse(it, it + remain_length, payload_);
|
||||
if (!pload)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
std::optional<will> w;
|
||||
|
||||
if (has_will)
|
||||
w.emplace(
|
||||
std::move(*std::get<2>(*pload)), // will_topic
|
||||
std::move(*std::get<3>(*pload)), // will_message
|
||||
qos_e((flags & 0b00011000) >> 3),
|
||||
retain_e((flags & 0b00100000) >> 5),
|
||||
std::move(*std::get<1>(*pload)) // will props
|
||||
);
|
||||
|
||||
connect_message retval = {
|
||||
std::move(std::get<0>(*pload)), // client_id
|
||||
std::move(std::get<4>(*pload)), // user_name
|
||||
std::move(std::get<5>(*pload)), // password
|
||||
keep_alive,
|
||||
flags & 0b00000010, // clean_start
|
||||
std::move(cprops), // connect_props
|
||||
std::move(w) // will
|
||||
};
|
||||
|
||||
return std::optional<connect_message> { std::move(retval) };
|
||||
}
|
||||
|
||||
using connack_message = std::tuple<
|
||||
uint8_t, // session_present
|
||||
uint8_t, // connect reason code
|
||||
connack_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<connack_message> decode_connack(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto connack_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> x3::byte_ >> prop::props_<connack_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, connack_);
|
||||
}
|
||||
|
||||
using publish_message = std::tuple<
|
||||
std::string, // topic
|
||||
std::optional<uint16_t>, // packet_id
|
||||
uint8_t, // dup_e, qos_e, retain_e
|
||||
publish_props, // publish props
|
||||
std::string // payload
|
||||
>;
|
||||
|
||||
inline std::optional<publish_message> decode_publish(
|
||||
uint8_t control_byte, uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
uint8_t flags = control_byte & 0b1111;
|
||||
auto qos = qos_e((flags >> 1) & 0b11);
|
||||
|
||||
auto publish_ = basic::scope_limit_(remain_length)[
|
||||
basic::utf8_ >> basic::if_(qos != qos_e::at_most_once)[x3::big_word] >> x3::attr(flags) >>
|
||||
prop::props_<publish_props> >> basic::verbatim_
|
||||
];
|
||||
return type_parse(it, it + remain_length, publish_);
|
||||
}
|
||||
|
||||
using puback_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
puback_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<puback_message> decode_puback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto puback_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<puback_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, puback_);
|
||||
}
|
||||
|
||||
using pubrec_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubrec_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubrec_message> decode_pubrec(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto pubrec_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubrec_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubrec_);
|
||||
}
|
||||
|
||||
using pubrel_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubrel_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubrel_message> decode_pubrel(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto pubrel_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubrel_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubrel_);
|
||||
}
|
||||
|
||||
using pubcomp_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubcomp_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubcomp_message> decode_pubcomp(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto pubcomp_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubcomp_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubcomp_);
|
||||
}
|
||||
|
||||
using subscribe_message = std::tuple<
|
||||
subscribe_props,
|
||||
std::vector<std::tuple<std::string, uint8_t>> // topic filter with opts
|
||||
>;
|
||||
|
||||
inline std::optional<subscribe_message> decode_subscribe(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto subscribe_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<subscribe_props> >> +(basic::utf8_ >> x3::byte_)
|
||||
];
|
||||
return type_parse(it, it + remain_length, subscribe_);
|
||||
}
|
||||
|
||||
using suback_message = std::tuple<
|
||||
suback_props,
|
||||
std::vector<uint8_t> // reason_codes
|
||||
>;
|
||||
|
||||
inline std::optional<suback_message> decode_suback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto suback_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<suback_props> >> +x3::byte_
|
||||
];
|
||||
return type_parse(it, it + remain_length, suback_);
|
||||
}
|
||||
|
||||
using unsubscribe_message = std::tuple<
|
||||
unsubscribe_props,
|
||||
std::vector<std::string> // topics
|
||||
>;
|
||||
|
||||
inline std::optional<unsubscribe_message> decode_unsubscribe(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto unsubscribe_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<unsubscribe_props> >> +basic::utf8_
|
||||
];
|
||||
return type_parse(it, it + remain_length, unsubscribe_);
|
||||
}
|
||||
|
||||
using unsuback_message = std::tuple<
|
||||
unsuback_props,
|
||||
std::vector<uint8_t> // reason_codes
|
||||
>;
|
||||
|
||||
inline std::optional<unsuback_message> decode_unsuback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto unsuback_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<unsuback_props> >> +x3::byte_
|
||||
];
|
||||
return type_parse(it, it + remain_length, unsuback_);
|
||||
}
|
||||
|
||||
using disconnect_message = std::tuple<
|
||||
uint8_t, // reason_code
|
||||
disconnect_props
|
||||
>;
|
||||
|
||||
inline std::optional<disconnect_message> decode_disconnect(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto disconnect_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<disconnect_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, disconnect_);
|
||||
}
|
||||
|
||||
using auth_message = std::tuple<
|
||||
uint8_t, // reason_code
|
||||
auth_props
|
||||
>;
|
||||
|
||||
inline std::optional<auth_message> decode_auth(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto auth_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<auth_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, auth_);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::decoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_MESSAGE_DECODERS_HPP
|
||||
@@ -0,0 +1,422 @@
|
||||
#ifndef ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
#define ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/base_encoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::encoders {
|
||||
|
||||
template <typename encoder>
|
||||
std::string encode(const encoder& e) {
|
||||
std::string s;
|
||||
s.reserve(e.byte_size());
|
||||
s << e;
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_connect(
|
||||
std::string_view client_id,
|
||||
std::optional<std::string_view> user_name,
|
||||
std::optional<std::string_view> password,
|
||||
uint16_t keep_alive, bool clean_start,
|
||||
const connect_props& props,
|
||||
const std::optional<will>& w
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0001) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto conn_flags_ =
|
||||
basic::flag<1>(user_name) |
|
||||
basic::flag<1>(password) |
|
||||
basic::flag<1>(w, &will::retain) |
|
||||
basic::flag<2>(w, &will::qos) |
|
||||
basic::flag<1>(w) |
|
||||
basic::flag<1>(clean_start) |
|
||||
basic::flag<1>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::utf8_("MQTT") &
|
||||
basic::byte_(uint8_t(5)) &
|
||||
conn_flags_ &
|
||||
basic::int16_(keep_alive) &
|
||||
prop::props_(props);
|
||||
|
||||
auto payload_ =
|
||||
basic::utf8_(client_id) &
|
||||
prop::props_(w) &
|
||||
basic::utf8_(w, &will::topic) &
|
||||
basic::binary_(w, &will::message) &
|
||||
basic::utf8_(user_name) &
|
||||
basic::utf8_(password);
|
||||
|
||||
auto message_body_ = var_header_ & payload_;
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(message_body_.byte_size());
|
||||
|
||||
auto connect_message_ = fixed_header_ & message_body_;
|
||||
|
||||
return encode(connect_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_connack(
|
||||
bool session_present,
|
||||
uint8_t reason_code,
|
||||
const connack_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0010) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::flag<1>(session_present) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto connack_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(connack_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_publish(
|
||||
uint16_t packet_id,
|
||||
std::string_view topic_name,
|
||||
std::string_view payload,
|
||||
qos_e qos, retain_e retain, dup_e dup,
|
||||
const publish_props& props
|
||||
) {
|
||||
|
||||
std::optional<uint16_t> used_packet_id;
|
||||
if (qos != qos_e::at_most_once) used_packet_id.emplace(packet_id);
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0011) |
|
||||
basic::flag<1>(dup) |
|
||||
basic::flag<2>(qos) |
|
||||
basic::flag<1>(retain);
|
||||
|
||||
auto var_header_ =
|
||||
basic::utf8_(topic_name) &
|
||||
basic::int16_(used_packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_body_ = var_header_ & basic::verbatim_(payload);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(message_body_.byte_size());
|
||||
|
||||
auto publish_message_ = fixed_header_ & message_body_;
|
||||
|
||||
return encode(publish_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_puback(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const puback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0100) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto puback_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(puback_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubrec(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubrec_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0101) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubrec_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubrec_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubrel(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubrel_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0110) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubrel_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubrel_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubcomp(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubcomp_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0111) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubcomp_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubcomp_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_subscribe(
|
||||
uint16_t packet_id,
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1000) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
size_t payload_size = 0;
|
||||
for (const auto& [topic_filter, _]: topics)
|
||||
payload_size += basic::utf8_(topic_filter).byte_size() + 1;
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + payload_size) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + payload_size);
|
||||
|
||||
for (const auto& [topic_filter, sub_opts]: topics) {
|
||||
auto opts_ =
|
||||
basic::flag<2>(sub_opts.retain_handling) |
|
||||
basic::flag<1>(sub_opts.retain_as_published) |
|
||||
basic::flag<1>(sub_opts.no_local) |
|
||||
basic::flag<2>(sub_opts.max_qos);
|
||||
auto filter_ = basic::utf8_(topic_filter) & opts_;
|
||||
s << filter_;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_suback(
|
||||
uint16_t packet_id,
|
||||
std::vector<uint8_t>& reason_codes,
|
||||
const suback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1001) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + reason_codes.size());
|
||||
|
||||
for (auto reason_code: reason_codes)
|
||||
s << basic::byte_(reason_code);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_unsubscribe(
|
||||
uint16_t packet_id,
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1010) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
size_t payload_size = 0;
|
||||
for (const auto& topic: topics)
|
||||
payload_size += basic::utf8_(topic).byte_size();
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + payload_size) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + payload_size);
|
||||
|
||||
for (const auto& topic: topics)
|
||||
s << basic::utf8_(topic);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_unsuback(
|
||||
uint16_t packet_id,
|
||||
std::vector<uint8_t>& reason_codes,
|
||||
const unsuback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1011) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + reason_codes.size());
|
||||
|
||||
for (auto reason_code: reason_codes)
|
||||
s << basic::byte_(reason_code);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_pingreq() {
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1100) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto remaining_len_ =
|
||||
basic::byte_(0);
|
||||
|
||||
auto ping_req_ = packet_type_ & remaining_len_;
|
||||
|
||||
return encode(ping_req_);
|
||||
}
|
||||
|
||||
inline std::string encode_pingresp() {
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1101) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto remaining_len_ =
|
||||
basic::byte_(0);
|
||||
|
||||
auto ping_resp_ = packet_type_ & remaining_len_;
|
||||
|
||||
return encode(ping_resp_);
|
||||
}
|
||||
|
||||
inline std::string encode_disconnect(
|
||||
uint8_t reason_code,
|
||||
const disconnect_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1110) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto disconnect_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(disconnect_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_auth(
|
||||
uint8_t reason_code,
|
||||
const auth_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1111) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto auth_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(auth_message_);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::encoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef ASYNC_MQTT5_TRAITS_HPP
|
||||
#define ASYNC_MQTT5_TRAITS_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/iterator_range_core.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
template <typename> constexpr bool is_optional_impl = false;
|
||||
template <typename T> constexpr bool is_optional_impl<std::optional<T>> = true;
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_optional = is_optional_impl<std::remove_cvref_t<T>>;
|
||||
|
||||
template <class, template<class...> class>
|
||||
constexpr bool is_specialization = false;
|
||||
|
||||
template <template<class...> class T, class... Args>
|
||||
constexpr bool is_specialization<T<Args...>, T> = true;
|
||||
|
||||
template <typename T>
|
||||
concept is_vector = is_specialization<std::remove_cvref_t<T>, std::vector>;
|
||||
|
||||
template <typename T>
|
||||
concept is_boost_iterator = is_specialization<
|
||||
std::remove_cvref_t<T>, boost::iterator_range
|
||||
>;
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_TRAITS_HPP
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifndef ASYNC_MQTT5_PING_OP_HPP
|
||||
#define ASYNC_MQTT5_PING_OP_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class ping_op {
|
||||
using client_service = ClientService;
|
||||
struct on_timer {};
|
||||
struct on_pingreq {};
|
||||
|
||||
static constexpr auto ping_interval = std::chrono::seconds(5);
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
std::unique_ptr<asio::steady_timer> _ping_timer;
|
||||
|
||||
public:
|
||||
ping_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr
|
||||
) :
|
||||
_svc_ptr(svc_ptr),
|
||||
_ping_timer(new asio::steady_timer(svc_ptr->get_executor()))
|
||||
{}
|
||||
|
||||
ping_op(ping_op&&) noexcept = default;
|
||||
ping_op(const ping_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
asio::cancellation_slot get_cancellation_slot() const noexcept {
|
||||
return _svc_ptr->_cancel_ping.slot();
|
||||
}
|
||||
|
||||
void perform(duration from_now) {
|
||||
_ping_timer->expires_from_now(from_now);
|
||||
_ping_timer->async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_timer, error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
if (ec == asio::error::operation_aborted || !_svc_ptr->is_open())
|
||||
return;
|
||||
|
||||
auto pingreq = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(), encoders::encode_pingreq
|
||||
);
|
||||
|
||||
const auto& wire_data = pingreq.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_pingreq {}),
|
||||
std::move(pingreq)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_pingreq, error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
if (!ec || ec == asio::error::try_again)
|
||||
perform(ping_interval - std::chrono::seconds(1));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_PING_OP_HPP
|
||||
@@ -0,0 +1,204 @@
|
||||
#ifndef ASYNC_MQTT5_PUBLISH_REC_OP_HPP
|
||||
#define ASYNC_MQTT5_PUBLISH_REC_OP_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class publish_rec_op {
|
||||
using client_service = ClientService;
|
||||
struct on_puback {};
|
||||
struct on_pubrec {};
|
||||
struct on_pubrel {};
|
||||
struct on_pubcomp {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
decoders::publish_message _message;
|
||||
|
||||
public:
|
||||
publish_rec_op(const std::shared_ptr<client_service>& svc_ptr) :
|
||||
_svc_ptr(svc_ptr)
|
||||
{}
|
||||
|
||||
publish_rec_op(publish_rec_op&&) noexcept = default;
|
||||
publish_rec_op(const publish_rec_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
void perform(decoders::publish_message message) {
|
||||
auto flags = std::get<2>(message);
|
||||
auto qos_bits = (flags >> 1) & 0b11;
|
||||
if (qos_bits == 0b11)
|
||||
return on_malformed_packet(
|
||||
"Malformed PUBLISH received: QoS bits set to 0b11"
|
||||
);
|
||||
|
||||
auto qos = qos_e(qos_bits);
|
||||
_message = std::move(message);
|
||||
|
||||
if (qos == qos_e::at_most_once)
|
||||
return complete();
|
||||
|
||||
auto packet_id = std::get<1>(_message);
|
||||
|
||||
if (qos == qos_e::at_least_once) {
|
||||
auto puback = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_puback, *packet_id,
|
||||
uint8_t(0), puback_props{}
|
||||
);
|
||||
return send_puback(std::move(puback));
|
||||
}
|
||||
|
||||
// qos == qos_e::exactly_once
|
||||
auto pubrec = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubrec, *packet_id,
|
||||
uint8_t(0), pubrec_props{}
|
||||
);
|
||||
|
||||
return send_pubrec(std::move(pubrec));
|
||||
}
|
||||
|
||||
void send_puback(control_packet<allocator_type> puback) {
|
||||
const auto& wire_data = puback.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_puback {}),
|
||||
std::move(puback)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_puback, error_code ec) {
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
void send_pubrec(control_packet<allocator_type> pubrec) {
|
||||
const auto& wire_data = pubrec.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(std::move(*this), on_pubrec {}, std::move(pubrec))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrec, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
wait_pubrel(packet.packet_id());
|
||||
}
|
||||
|
||||
void wait_pubrel(uint16_t packet_id) {
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubrel, packet_id,
|
||||
asio::prepend(std::move(*this), on_pubrel {}, packet_id)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrel, uint16_t packet_id,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return wait_pubrel(packet_id);
|
||||
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
auto pubrel = decoders::decode_pubrel(std::distance(first, last), first);
|
||||
if (!pubrel.has_value()) {
|
||||
on_malformed_packet("Malformed PUBREL received: cannot decode");
|
||||
return wait_pubrel(packet_id);
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubrel;
|
||||
auto rc = to_reason_code<reason_codes::category::pubrel>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBREL received: invalid Reason Code");
|
||||
return wait_pubrel(packet_id);
|
||||
}
|
||||
|
||||
auto pubcomp = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubcomp, packet_id,
|
||||
uint8_t(0), pubcomp_props{}
|
||||
);
|
||||
send_pubcomp(std::move(pubcomp));
|
||||
}
|
||||
|
||||
void send_pubcomp(control_packet<allocator_type> pubcomp) {
|
||||
const auto& wire_data = pubcomp.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubcomp))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubcomp, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return wait_pubrel(packet.packet_id());
|
||||
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
private:
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props,
|
||||
false, _svc_ptr, asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete() {
|
||||
// TODO: if rv == false then the channel buffer is full and
|
||||
// there is no listener; we may need to log this
|
||||
/* auto rv = */_svc_ptr->channel_store(std::move(_message));
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_PUBLISH_REC_OP_HPP
|
||||
@@ -0,0 +1,383 @@
|
||||
#ifndef ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
#define ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using on_publish_signature = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
void (error_code),
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
void (error_code, reason_code, puback_props),
|
||||
void (error_code, reason_code, pubcomp_props)
|
||||
>
|
||||
>;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using on_publish_props_type = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
void,
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
puback_props,
|
||||
pubcomp_props
|
||||
>
|
||||
>;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using cancel_args = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
std::tuple<>,
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
std::tuple<reason_code, puback_props>,
|
||||
std::tuple<reason_code, pubcomp_props>
|
||||
>
|
||||
>;
|
||||
|
||||
template <typename ClientService, typename Handler, qos_e qos_type>
|
||||
class publish_send_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_publish {};
|
||||
struct on_puback {};
|
||||
struct on_pubrec {};
|
||||
struct on_pubrel {};
|
||||
struct on_pubcomp {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type,
|
||||
cancel_args<qos_type>
|
||||
> _handler;
|
||||
|
||||
serial_num_t _serial_num;
|
||||
|
||||
public:
|
||||
publish_send_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr, Handler&& handler
|
||||
) :
|
||||
_svc_ptr(svc_ptr),
|
||||
_handler(std::move(handler), get_executor())
|
||||
{}
|
||||
|
||||
publish_send_op(publish_send_op&&) noexcept = default;
|
||||
publish_send_op(const publish_send_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
void perform(
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) {
|
||||
auto ec = validate_publish(retain, props);
|
||||
if (ec)
|
||||
return complete_post(ec);
|
||||
|
||||
uint16_t packet_id = 0;
|
||||
if constexpr (qos_type != qos_e::at_most_once) {
|
||||
packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_post(client::error::pid_overrun);
|
||||
}
|
||||
|
||||
_serial_num = _svc_ptr->next_serial_num();
|
||||
|
||||
auto publish = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_publish, packet_id,
|
||||
std::move(topic), std::move(payload),
|
||||
qos_type, retain, dup_e::no, props
|
||||
);
|
||||
|
||||
send_publish(std::move(publish));
|
||||
}
|
||||
|
||||
error_code validate_publish(
|
||||
retain_e retain, const publish_props& props
|
||||
) {
|
||||
auto max_qos = _svc_ptr->connack_prop(prop::maximum_qos);
|
||||
if (max_qos && uint8_t(qos_type) > *max_qos)
|
||||
return client::error::qos_not_supported;
|
||||
|
||||
auto retain_available = _svc_ptr->connack_prop(prop::retain_available);
|
||||
if (retain_available && *retain_available == 0 && retain == retain_e::yes)
|
||||
return client::error::retain_not_available;
|
||||
|
||||
// TODO: topic alias mapping
|
||||
auto topic_alias_max = _svc_ptr->connack_prop(prop::topic_alias_maximum);
|
||||
auto topic_alias = props[prop::topic_alias];
|
||||
if ((!topic_alias_max || topic_alias_max && *topic_alias_max == 0) && topic_alias)
|
||||
return client::error::topic_alias_maximum_reached;
|
||||
if (topic_alias_max && topic_alias && *topic_alias > *topic_alias_max)
|
||||
return client::error::topic_alias_maximum_reached;
|
||||
return {};
|
||||
}
|
||||
|
||||
void send_publish(control_packet<allocator_type> publish) {
|
||||
const auto& wire_data = publish.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
_serial_num,
|
||||
send_flag::throttled * (qos_type != qos_e::at_most_once),
|
||||
asio::prepend(std::move(*this), on_publish {}, std::move(publish))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_publish, control_packet<allocator_type> publish,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return send_publish(std::move(publish));
|
||||
|
||||
if constexpr (qos_type == qos_e::at_most_once)
|
||||
return complete(ec);
|
||||
|
||||
else {
|
||||
auto packet_id = publish.packet_id();
|
||||
|
||||
if constexpr (qos_type == qos_e::at_least_once) {
|
||||
if (ec)
|
||||
return complete(
|
||||
ec, reason_codes::empty, packet_id, puback_props {}
|
||||
);
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::puback, packet_id,
|
||||
asio::prepend(
|
||||
std::move(*this), on_puback {}, std::move(publish)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
else if constexpr (qos_type == qos_e::exactly_once) {
|
||||
if (ec)
|
||||
return complete(
|
||||
ec, reason_codes::empty, packet_id, pubcomp_props {}
|
||||
);
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubrec, packet_id,
|
||||
asio::prepend(
|
||||
std::move(*this), on_pubrec {}, std::move(publish)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_puback, control_packet<allocator_type> publish,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
)
|
||||
requires (qos_type == qos_e::at_least_once) {
|
||||
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_publish(std::move(publish.set_dup()));
|
||||
|
||||
uint16_t packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(
|
||||
ec, reason_codes::empty, packet_id, puback_props {}
|
||||
);
|
||||
|
||||
auto puback = decoders::decode_puback(std::distance(first, last), first);
|
||||
if (!puback.has_value()) {
|
||||
on_malformed_packet("Malformed PUBACK: cannot decode");
|
||||
return send_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *puback;
|
||||
auto rc = to_reason_code<reason_codes::category::puback>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBACK: invalid Reason Code");
|
||||
return send_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
complete(ec, *rc, packet_id, std::move(props));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrec, control_packet<allocator_type> publish,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
)
|
||||
requires (qos_type == qos_e::exactly_once) {
|
||||
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_publish(std::move(publish.set_dup()));
|
||||
|
||||
uint16_t packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(
|
||||
ec, reason_codes::empty, packet_id, pubcomp_props {}
|
||||
);
|
||||
|
||||
auto pubrec = decoders::decode_pubrec(std::distance(first, last), first);
|
||||
if (!pubrec.has_value()) {
|
||||
on_malformed_packet("Malformed PUBREC: cannot decode");
|
||||
return send_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubrec;
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::pubrec>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBREC: invalid Reason Code");
|
||||
return send_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
if (*rc)
|
||||
return complete(ec, *rc, packet_id, pubcomp_props {});
|
||||
|
||||
auto pubrel = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubrel, packet_id,
|
||||
0, pubrel_props {}
|
||||
);
|
||||
|
||||
send_pubrel(std::move(pubrel), false);
|
||||
}
|
||||
|
||||
void send_pubrel(control_packet<allocator_type> pubrel, bool throttled) {
|
||||
const auto& wire_data = pubrel.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
_serial_num,
|
||||
(send_flag::throttled * throttled) | send_flag::prioritized,
|
||||
asio::prepend(std::move(*this), on_pubrel {}, std::move(pubrel))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrel, control_packet<allocator_type> pubrel, error_code ec
|
||||
)
|
||||
requires (qos_type == qos_e::exactly_once) {
|
||||
|
||||
if (ec == asio::error::try_again)
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
|
||||
uint16_t packet_id = pubrel.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(
|
||||
ec, reason_codes::empty, packet_id, pubcomp_props {}
|
||||
);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubcomp, packet_id,
|
||||
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubrel))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubcomp, control_packet<allocator_type> pubrel,
|
||||
error_code ec,
|
||||
byte_citer first, byte_citer last
|
||||
)
|
||||
requires (qos_type == qos_e::exactly_once) {
|
||||
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
|
||||
uint16_t packet_id = pubrel.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(
|
||||
ec, reason_codes::empty, packet_id, pubcomp_props {}
|
||||
);
|
||||
|
||||
auto pubcomp = decoders::decode_pubcomp(std::distance(first, last), first);
|
||||
if (!pubcomp.has_value()) {
|
||||
on_malformed_packet("Malformed PUBCOMP: cannot decode");
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubcomp;
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::pubcomp>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBCOMP: invalid Reason Code");
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
}
|
||||
|
||||
return complete(ec, *rc, pubrel.packet_id(), pubcomp_props{});
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, false, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete(error_code ec)
|
||||
requires (qos_type == qos_e::at_most_once)
|
||||
{
|
||||
_handler.complete(ec);
|
||||
}
|
||||
|
||||
void complete_post(error_code ec)
|
||||
requires (qos_type == qos_e::at_most_once)
|
||||
{
|
||||
_handler.complete_post(ec);
|
||||
}
|
||||
|
||||
template <typename Props = on_publish_props_type<qos_type>>
|
||||
requires (
|
||||
std::is_same_v<Props, puback_props> ||
|
||||
std::is_same_v<Props, pubcomp_props>
|
||||
)
|
||||
void complete(
|
||||
error_code ec, reason_code rc,
|
||||
uint16_t packet_id, Props&& props
|
||||
) {
|
||||
_svc_ptr->free_pid(packet_id, true);
|
||||
_handler.complete(ec, rc, std::forward<Props>(props));
|
||||
}
|
||||
|
||||
template <typename Props = on_publish_props_type<qos_type>>
|
||||
requires (
|
||||
std::is_same_v<Props, puback_props> ||
|
||||
std::is_same_v<Props, pubcomp_props>
|
||||
)
|
||||
void complete_post(error_code ec) {
|
||||
_handler.complete_post(ec, reason_codes::empty, Props {});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
@@ -0,0 +1,127 @@
|
||||
#ifndef ASYNC_MQTT5_READ_MESSAGE_OP_HPP
|
||||
#define ASYNC_MQTT5_READ_MESSAGE_OP_HPP
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/publish_rec_op.hpp>
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class read_message_op {
|
||||
using client_service = ClientService;
|
||||
struct on_message {};
|
||||
struct on_disconnect {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
public:
|
||||
read_message_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr
|
||||
) :
|
||||
_svc_ptr(svc_ptr)
|
||||
{}
|
||||
|
||||
read_message_op(read_message_op&&) noexcept = default;
|
||||
read_message_op(const read_message_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->async_assemble(
|
||||
std::chrono::seconds(20),
|
||||
asio::prepend(std::move(*this), on_message {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_message, error_code ec,
|
||||
uint16_t packet_id, uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == client::error::malformed_packet)
|
||||
return on_malformed_packet(
|
||||
"Malformed Packet received from the Server"
|
||||
);
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return;
|
||||
|
||||
dispatch(ec, packet_id, control_code, first, last);
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
if (!ec || ec == asio::error::try_again)
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void dispatch(
|
||||
error_code ec, uint16_t packet_id, uint8_t control_byte,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
using enum control_code_e;
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
switch (code) {
|
||||
case publish: {
|
||||
auto msg = decoders::decode_publish(
|
||||
control_byte, std::distance(first, last), first
|
||||
);
|
||||
if (!msg.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed PUBLISH received: cannot decode"
|
||||
);
|
||||
|
||||
publish_rec_op { _svc_ptr }.perform(std::move(*msg));
|
||||
}
|
||||
break;
|
||||
case disconnect: {
|
||||
_svc_ptr->close_stream();
|
||||
_svc_ptr->open_stream();
|
||||
}
|
||||
break;
|
||||
case auth: {
|
||||
// TODO: dispatch auth
|
||||
}
|
||||
break;
|
||||
}
|
||||
perform();
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
auto svc_ptr = _svc_ptr; // copy before this is moved
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, false, svc_ptr,
|
||||
asio::prepend(std::move(*this), on_disconnect {})
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_READ_MESSAGE_OP_HPP
|
||||
@@ -0,0 +1,117 @@
|
||||
#ifndef ASYNC_MQTT5_READ_OP_HPP
|
||||
#define ASYNC_MQTT5_READ_OP_HPP
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class read_op {
|
||||
struct on_read {};
|
||||
struct on_reconnect {};
|
||||
|
||||
Owner& _owner;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
public:
|
||||
read_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
read_op(read_op&&) noexcept = default;
|
||||
read_op(const read_op&) = delete;
|
||||
|
||||
using executor_type = typename Owner::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _owner.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(
|
||||
const BufferType& buffer, duration wait_for
|
||||
) {
|
||||
auto stream_ptr = _owner._stream_ptr;
|
||||
|
||||
if (_owner.was_connected()) {
|
||||
_owner._read_timer.expires_from_now(wait_for);
|
||||
|
||||
auto timed_read = asioex::make_parallel_group(
|
||||
stream_ptr->async_read_some(buffer, asio::deferred),
|
||||
_owner._read_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_read.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::prepend(std::move(*this), on_read {}, stream_ptr)
|
||||
);
|
||||
|
||||
}
|
||||
else
|
||||
(*this)(
|
||||
on_read {}, stream_ptr,
|
||||
{ 0, 1 }, asio::error::not_connected, 0, {}
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_read, typename Owner::stream_ptr stream_ptr,
|
||||
std::array<std::size_t, 2> ord, error_code read_ec, size_t bytes_read,
|
||||
error_code timer_ec
|
||||
) {
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted, bytes_read);
|
||||
|
||||
error_code ec = ord[0] == 1 ? asio::error::timed_out : read_ec;
|
||||
bytes_read = ord[0] == 0 ? bytes_read : 0;
|
||||
|
||||
if (!ec)
|
||||
return complete(ec, bytes_read);
|
||||
|
||||
// websocket returns operation_aborted if disconnected
|
||||
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
|
||||
return _owner.async_reconnect(
|
||||
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
|
||||
);
|
||||
|
||||
return complete(asio::error::no_recovery, bytes_read);
|
||||
}
|
||||
|
||||
void operator()(on_reconnect, error_code ec) {
|
||||
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
|
||||
ec = asio::error::try_again;
|
||||
|
||||
return complete(ec, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, size_t bytes_read) {
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec, bytes_read)
|
||||
);
|
||||
}
|
||||
|
||||
static bool should_reconnect(error_code ec) {
|
||||
using namespace asio::error;
|
||||
return ec == connection_aborted || ec == not_connected ||
|
||||
ec == timed_out || ec == connection_reset || ec == broken_pipe;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_READ_OP_HPP
|
||||
@@ -0,0 +1,190 @@
|
||||
#ifndef ASYNC_MQTT5_RECONNECT_OP_HPP
|
||||
#define ASYNC_MQTT5_RECONNECT_OP_HPP
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/impl/connect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class reconnect_op {
|
||||
struct on_locked {};
|
||||
struct on_next_endpoint {};
|
||||
struct on_connect {};
|
||||
struct on_backoff {};
|
||||
|
||||
Owner& _owner;
|
||||
std::decay_t<Handler> _handler;
|
||||
std::unique_ptr<std::string> _buffer_ptr;
|
||||
|
||||
using endpoint = asio::ip::tcp::endpoint;
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
public:
|
||||
reconnect_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
reconnect_op(reconnect_op&&) noexcept = default;
|
||||
reconnect_op(const reconnect_op&) = delete;
|
||||
|
||||
using executor_type = typename Owner::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _owner.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
void perform(typename Owner::stream_ptr s) {
|
||||
_owner._conn_mtx.lock(
|
||||
asio::prepend(std::move(*this), on_locked {}, s)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_locked, typename Owner::stream_ptr s, error_code ec) {
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (s != _owner._stream_ptr)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
do_reconnect();
|
||||
}
|
||||
|
||||
void do_reconnect() {
|
||||
_owner._endpoints.async_next_endpoint(
|
||||
asio::prepend(std::move(*this), on_next_endpoint {})
|
||||
);
|
||||
}
|
||||
|
||||
void backoff_and_reconnect() {
|
||||
_owner._connect_timer.expires_from_now(std::chrono::seconds(5));
|
||||
_owner._connect_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_backoff {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_backoff, error_code ec) {
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
do_reconnect();
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_next_endpoint, error_code ec,
|
||||
epoints eps, authority_path ap
|
||||
) {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
// the three error codes below are the only possible codes
|
||||
// that may be returned from async_next_endpont
|
||||
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec == asio::error::try_again)
|
||||
return backoff_and_reconnect();
|
||||
|
||||
if (ec == asio::error::host_not_found)
|
||||
return complete(asio::error::no_recovery);
|
||||
|
||||
auto sptr = _owner.construct_next_layer();
|
||||
|
||||
if constexpr (has_tls_context<typename Owner::stream_context_type>)
|
||||
setup_tls_sni(
|
||||
ap, _owner._stream_context.tls_context(), *sptr
|
||||
);
|
||||
|
||||
// wait max 5 seconds for the connect (handshake) op to finish
|
||||
_owner._connect_timer.expires_from_now(std::chrono::seconds(5));
|
||||
|
||||
auto init_connect = [this, sptr](
|
||||
auto handler, const auto& eps, auto ap
|
||||
) {
|
||||
connect_op {
|
||||
*sptr, std::move(handler),
|
||||
_owner._stream_context.mqtt_context()
|
||||
}.perform(eps, std::move(ap));
|
||||
};
|
||||
|
||||
auto timed_connect = asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void (error_code)>(
|
||||
std::move(init_connect), asio::deferred,
|
||||
std::move(eps), std::move(ap)
|
||||
),
|
||||
_owner._connect_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_connect.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::prepend(std::move(*this), on_connect {}, std::move(sptr))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_connect, typename Owner::stream_ptr sptr,
|
||||
auto ord, error_code connect_ec, error_code timer_ec
|
||||
) {
|
||||
// connect_ec may be any of stream.async_connect() error codes
|
||||
// plus access_denied, connection_refused and
|
||||
// client::error::malformed_packet
|
||||
if (
|
||||
ord[0] == 0 && connect_ec == asio::error::operation_aborted ||
|
||||
ord[0] == 1 && timer_ec == asio::error::operation_aborted ||
|
||||
!_owner.is_open()
|
||||
)
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
// operation timed out so retry
|
||||
if (ord[0] == 1)
|
||||
return do_reconnect();
|
||||
|
||||
if (connect_ec == asio::error::access_denied)
|
||||
return complete(asio::error::no_recovery);
|
||||
|
||||
// retry for any other stream.async_connect() error or
|
||||
// connection_refused, client::error::malformed_packet
|
||||
if (connect_ec)
|
||||
return do_reconnect();
|
||||
|
||||
_owner.replace_next_layer(std::move(sptr));
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
_owner._conn_mtx.unlock();
|
||||
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_RECONNECT_OP_HPP
|
||||
@@ -0,0 +1,186 @@
|
||||
#ifndef ASYNC_MQTT5_REPLIES_HPP
|
||||
#define ASYNC_MQTT5_REPLIES_HPP
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class replies {
|
||||
using signature = void (error_code, byte_citer, byte_citer);
|
||||
|
||||
static constexpr auto max_reply_time = std::chrono::seconds(20);
|
||||
|
||||
class handler_type : public asio::any_completion_handler<signature> {
|
||||
using base = asio::any_completion_handler<signature>;
|
||||
control_code_e _code;
|
||||
uint16_t _packet_id;
|
||||
std::chrono::time_point<std::chrono::system_clock> _ts;
|
||||
public:
|
||||
template <typename H>
|
||||
handler_type(control_code_e code, uint16_t pid, H&& handler) :
|
||||
base(std::forward<H>(handler)), _code(code), _packet_id(pid),
|
||||
_ts(std::chrono::system_clock::now())
|
||||
{}
|
||||
|
||||
handler_type(handler_type&& other) noexcept :
|
||||
base(static_cast<base&&>(other)),
|
||||
_code(other._code), _packet_id(other._packet_id), _ts(other._ts)
|
||||
{}
|
||||
|
||||
handler_type& operator=(handler_type&& other) noexcept {
|
||||
base::operator=(static_cast<base&&>(other));
|
||||
_code = other._code;
|
||||
_packet_id = other._packet_id;
|
||||
_ts = other._ts;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint16_t packet_id() const noexcept {
|
||||
return _packet_id;
|
||||
}
|
||||
|
||||
control_code_e code() const noexcept {
|
||||
return _code;
|
||||
}
|
||||
|
||||
auto time() const noexcept {
|
||||
return _ts;
|
||||
}
|
||||
};
|
||||
|
||||
using handlers = std::vector<handler_type>;
|
||||
handlers _handlers;
|
||||
|
||||
struct fast_reply {
|
||||
control_code_e code;
|
||||
uint16_t packet_id;
|
||||
std::unique_ptr<std::string> packet;
|
||||
};
|
||||
using fast_replies = std::vector<fast_reply>;
|
||||
fast_replies _fast_replies;
|
||||
|
||||
public:
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_wait_reply(
|
||||
control_code_e code, uint16_t packet_id, CompletionToken&& token
|
||||
) {
|
||||
auto dup_handler_ptr = find_handler(code, packet_id);
|
||||
if (dup_handler_ptr != _handlers.end()) {
|
||||
std::move(*dup_handler_ptr)(
|
||||
asio::error::operation_aborted, byte_citer {}, byte_citer {}
|
||||
);
|
||||
_handlers.erase(dup_handler_ptr);
|
||||
}
|
||||
|
||||
auto freply = find_fast_reply(code, packet_id);
|
||||
if (freply == _fast_replies.end()) {
|
||||
auto initiate = [this](
|
||||
auto handler, control_code_e code, uint16_t packet_id
|
||||
) {
|
||||
_handlers.emplace_back(code, packet_id, std::move(handler));
|
||||
};
|
||||
return asio::async_initiate<CompletionToken, signature>(
|
||||
std::move(initiate), token, code, packet_id
|
||||
);
|
||||
}
|
||||
|
||||
auto fdata = std::move(*freply);
|
||||
_fast_replies.erase(freply);
|
||||
|
||||
byte_citer first = fdata.packet->cbegin(), last = fdata.packet->cend();
|
||||
auto with_packet = asio::consign(
|
||||
std::forward<CompletionToken>(token), std::move(fdata.packet)
|
||||
);
|
||||
auto initiate = [](auto handler, byte_citer first, byte_citer last) {
|
||||
auto ex = asio::get_associated_executor(handler);
|
||||
asio::post(ex, [h = std::move(handler), first, last]() mutable {
|
||||
std::move(h)(error_code {}, first, last);
|
||||
});
|
||||
};
|
||||
return asio::async_initiate<decltype(with_packet), signature>(
|
||||
std::move(initiate), with_packet, first, last
|
||||
);
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
error_code ec, control_code_e code, uint16_t packet_id,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
auto handler_ptr = find_handler(code, packet_id);
|
||||
if (handler_ptr == _handlers.end()) {
|
||||
_fast_replies.push_back({
|
||||
code, packet_id, std::make_unique<std::string>(first, last)
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto handler = std::move(*handler_ptr);
|
||||
_handlers.erase(handler_ptr);
|
||||
std::move(handler)(ec, first, last);
|
||||
}
|
||||
|
||||
void resend_unanswered() {
|
||||
auto ua = std::move(_handlers);
|
||||
for (auto& h : ua)
|
||||
std::move(h)(asio::error::try_again, byte_citer {}, byte_citer {});
|
||||
}
|
||||
|
||||
void cancel_unanswered() {
|
||||
auto ua = std::move(_handlers);
|
||||
for (auto& h : ua)
|
||||
std::move(h)(
|
||||
asio::error::operation_aborted,
|
||||
byte_citer {}, byte_citer {}
|
||||
);
|
||||
}
|
||||
|
||||
bool any_expired() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
return std::any_of(
|
||||
_handlers.begin(), _handlers.end(),
|
||||
[now](const auto& h) {
|
||||
return now - h.time() > max_reply_time;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void clear_fast_replies() {
|
||||
_fast_replies.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
handlers::iterator find_handler(control_code_e code, uint16_t packet_id) {
|
||||
return std::find_if(
|
||||
_handlers.begin(), _handlers.end(),
|
||||
[code, packet_id](const auto& h) {
|
||||
return h.code() == code && h.packet_id() == packet_id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fast_replies::iterator find_fast_reply(
|
||||
control_code_e code, uint16_t packet_id
|
||||
) {
|
||||
return std::find_if(
|
||||
_fast_replies.begin(), _fast_replies.end(),
|
||||
[code, packet_id](const auto& f) {
|
||||
return f.code == code && f.packet_id == packet_id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_REPLIES_HPP
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef ASYNC_MQTT5_SENTRY_OP_HPP
|
||||
#define ASYNC_MQTT5_SENTRY_OP_HPP
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class sentry_op {
|
||||
using client_service = ClientService;
|
||||
struct on_timer {};
|
||||
struct on_disconnect {};
|
||||
|
||||
static constexpr auto check_interval = std::chrono::seconds(3);
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
std::unique_ptr<asio::steady_timer> _sentry_timer;
|
||||
|
||||
public:
|
||||
sentry_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr
|
||||
) :
|
||||
_svc_ptr(svc_ptr),
|
||||
_sentry_timer(new asio::steady_timer(svc_ptr->get_executor()))
|
||||
{}
|
||||
|
||||
sentry_op(sentry_op&&) noexcept = default;
|
||||
sentry_op(const sentry_op&) = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
asio::cancellation_slot get_cancellation_slot() const noexcept {
|
||||
return _svc_ptr->_cancel_sentry.slot();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_sentry_timer->expires_after(check_interval);
|
||||
_sentry_timer->async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_timer, error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
if (ec == asio::error::operation_aborted || !_svc_ptr->is_open())
|
||||
return;
|
||||
|
||||
if (_svc_ptr->_replies.any_expired()) {
|
||||
auto props = disconnect_props{};
|
||||
// TODO add what packet was expected?
|
||||
props[prop::reason_string] = "No reply received within 20 seconds";
|
||||
auto svc_ptr = _svc_ptr;
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::unspecified_error, props, false, svc_ptr,
|
||||
asio::prepend(std::move(*this), on_disconnect {})
|
||||
);
|
||||
}
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
if (!ec || ec == asio::error::try_again)
|
||||
perform();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_SENTRY_OP_HPP
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef ASYNC_MQTT5_SUBSCRIBE_OP_HPP
|
||||
#define ASYNC_MQTT5_SUBSCRIBE_OP_HPP
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class subscribe_op {
|
||||
using client_service = ClientService;
|
||||
struct on_subscribe {};
|
||||
struct on_suback {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type,
|
||||
std::tuple<std::vector<reason_code>, suback_props>
|
||||
> _handler;
|
||||
|
||||
public:
|
||||
subscribe_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr, Handler&& handler
|
||||
) :
|
||||
_svc_ptr(svc_ptr),
|
||||
_handler(std::move(handler), get_executor())
|
||||
{}
|
||||
|
||||
subscribe_op(subscribe_op&&) noexcept = default;
|
||||
subscribe_op(const subscribe_op&) noexcept = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
void perform(
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props
|
||||
) {
|
||||
uint16_t packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_post(client::error::pid_overrun);
|
||||
|
||||
auto subscribe = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_subscribe, packet_id,
|
||||
topics, props
|
||||
);
|
||||
|
||||
send_subscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void send_subscribe(control_packet<allocator_type> subscribe) {
|
||||
const auto& wire_data = subscribe.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(
|
||||
std::move(*this), on_subscribe {}, std::move(subscribe)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_subscribe, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return send_subscribe(std::move(packet));
|
||||
|
||||
auto packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id, {}, {});
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::suback, packet_id,
|
||||
asio::prepend(std::move(*this), on_suback{}, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_suback, control_packet<allocator_type> packet,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_subscribe(std::move(packet));
|
||||
|
||||
uint16_t packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id, {}, {});
|
||||
|
||||
auto suback = decoders::decode_suback(std::distance(first, last), first);
|
||||
if (!suback.has_value()) {
|
||||
on_malformed_packet("Malformed SUBACK: cannot decode");
|
||||
return send_subscribe(std::move(packet));
|
||||
}
|
||||
|
||||
auto& [props, reason_codes] = *suback;
|
||||
// TODO: perhaps do something with the topics we subscribed to (one day)
|
||||
|
||||
complete(
|
||||
ec, packet_id,
|
||||
to_reason_codes(std::move(reason_codes)), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static std::vector<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
|
||||
std::vector<reason_code> ret;
|
||||
for (uint8_t code : codes) {
|
||||
auto rc = to_reason_code<reason_codes::category::suback>(code);
|
||||
if (rc)
|
||||
ret.push_back(*rc);
|
||||
// TODO: on off chance that one of the rcs is invalid, should we push something to mark that?
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props{};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, false, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void complete_post(error_code ec) {
|
||||
_handler.complete_post(
|
||||
ec, std::vector<reason_code> {}, suback_props {}
|
||||
);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
std::vector<reason_code> reason_codes, suback_props props
|
||||
) {
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete(ec, std::move(reason_codes), std::move(props));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_SUBSCRIBE_OP_HPP
|
||||
@@ -0,0 +1,173 @@
|
||||
#ifndef ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
#define ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class unsubscribe_op {
|
||||
using client_service = ClientService;
|
||||
struct on_unsubscribe {};
|
||||
struct on_unsuback {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type,
|
||||
std::tuple<std::vector<reason_code>, unsuback_props>
|
||||
> _handler;
|
||||
|
||||
public:
|
||||
unsubscribe_op(
|
||||
const std::shared_ptr<client_service>& svc_ptr, Handler&& handler
|
||||
) :
|
||||
_svc_ptr(svc_ptr),
|
||||
_handler(std::move(handler), get_executor())
|
||||
{}
|
||||
|
||||
unsubscribe_op(unsubscribe_op&&) noexcept = default;
|
||||
unsubscribe_op(const unsubscribe_op&) noexcept = delete;
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
void perform(
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
uint16_t packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_post(client::error::pid_overrun);
|
||||
|
||||
auto unsubscribe = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_unsubscribe, packet_id,
|
||||
topics, props
|
||||
);
|
||||
|
||||
send_unsubscribe(std::move(unsubscribe));
|
||||
}
|
||||
|
||||
void send_unsubscribe(control_packet<allocator_type> unsubscribe) {
|
||||
const auto& wire_data = unsubscribe.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(
|
||||
std::move(*this), on_unsubscribe{}, std::move(unsubscribe)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_unsubscribe, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return send_unsubscribe(std::move(packet));
|
||||
|
||||
auto packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id, {}, {});
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::unsuback, packet_id,
|
||||
asio::prepend(std::move(*this), on_unsuback{}, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_unsuback, control_packet<allocator_type> packet,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_unsubscribe(std::move(packet));
|
||||
|
||||
uint16_t packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id, {}, {});
|
||||
|
||||
auto unsuback = decoders::decode_unsuback(
|
||||
std::distance(first, last), first
|
||||
);
|
||||
if (!unsuback.has_value()) {
|
||||
on_malformed_packet("Malformed UNSUBACK: cannot decode");
|
||||
return send_unsubscribe(std::move(packet));
|
||||
}
|
||||
|
||||
auto& [props, reason_codes] = *unsuback;
|
||||
// TODO: perhaps do something with the topics we unsubscribed from (one day)
|
||||
|
||||
complete(
|
||||
ec, packet_id,
|
||||
to_reason_codes(std::move(reason_codes)), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static std::vector<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
|
||||
std::vector<reason_code> ret;
|
||||
for (uint8_t code : codes) {
|
||||
auto rc = to_reason_code<reason_codes::category::unsuback>(code);
|
||||
if (rc)
|
||||
ret.push_back(*rc);
|
||||
// TODO: on off chance that one of the rcs is invalid, should we push something to mark that?
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_malformed_packet(
|
||||
const std::string& reason
|
||||
) {
|
||||
auto props = disconnect_props{};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, false, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete_post(error_code ec) {
|
||||
_handler.complete_post(
|
||||
ec, std::vector<reason_code> {}, unsuback_props {}
|
||||
);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
std::vector<reason_code> reason_codes, unsuback_props props
|
||||
) {
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete(ec, std::move(reason_codes), std::move(props));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifndef ASYNC_MQTT5_WRITE_OP_HPP
|
||||
#define ASYNC_MQTT5_WRITE_OP_HPP
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class write_op {
|
||||
struct on_write_locked {};
|
||||
struct on_write {};
|
||||
struct on_reconnect {};
|
||||
|
||||
Owner& _owner;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
public:
|
||||
write_op(
|
||||
Owner& owner, Handler&& handler) :
|
||||
_owner(owner),
|
||||
_handler(std::forward<Handler>(handler))
|
||||
{}
|
||||
|
||||
write_op(write_op&&) noexcept = default;
|
||||
write_op(const write_op&) = delete;
|
||||
|
||||
using executor_type = typename Owner::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _owner.get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(BufferType& buffer) {
|
||||
auto stream_ptr = _owner._stream_ptr;
|
||||
if (_owner.was_connected())
|
||||
// note: write operation should not be time-limited
|
||||
detail::async_write(
|
||||
*stream_ptr, buffer,
|
||||
asio::prepend(std::move(*this), on_write {}, stream_ptr)
|
||||
);
|
||||
else
|
||||
(*this)(on_write {}, stream_ptr, asio::error::not_connected, 0);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_write, typename Owner::stream_ptr stream_ptr,
|
||||
error_code ec, size_t bytes_written
|
||||
) {
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted, 0);
|
||||
|
||||
if (!ec)
|
||||
return complete(ec, bytes_written);
|
||||
|
||||
// websocket returns operation_aborted if disconnected
|
||||
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
|
||||
return _owner.async_reconnect(
|
||||
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
|
||||
);
|
||||
|
||||
return complete(asio::error::no_recovery, 0);
|
||||
}
|
||||
|
||||
void operator()(on_reconnect, error_code ec) {
|
||||
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
|
||||
ec = asio::error::try_again;
|
||||
|
||||
return complete(ec, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, size_t bytes_written) {
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec, bytes_written)
|
||||
);
|
||||
}
|
||||
|
||||
static bool should_reconnect(error_code ec) {
|
||||
using namespace asio::error;
|
||||
return ec == connection_aborted || ec == not_connected
|
||||
|| ec == timed_out || ec == connection_reset
|
||||
|| ec == broken_pipe || ec == asio::error::eof;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_WRITE_OP_HPP
|
||||
@@ -0,0 +1,230 @@
|
||||
#ifndef ASYNC_MQTT5_MQTT_CLIENT_HPP
|
||||
#define ASYNC_MQTT5_MQTT_CLIENT_HPP
|
||||
|
||||
#include <async_mqtt5/impl/client_service.hpp>
|
||||
#include <async_mqtt5/impl/read_message_op.hpp>
|
||||
#include <async_mqtt5/impl/publish_send_op.hpp>
|
||||
#include <async_mqtt5/impl/subscribe_op.hpp>
|
||||
#include <async_mqtt5/impl/unsubscribe_op.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename TlsContext = std::monostate
|
||||
>
|
||||
class mqtt_client {
|
||||
public:
|
||||
using executor_type = typename StreamType::executor_type;
|
||||
private:
|
||||
using stream_type = StreamType;
|
||||
using tls_context_type = TlsContext;
|
||||
|
||||
static constexpr auto read_timeout = std::chrono::seconds(5);
|
||||
|
||||
using client_service_type = detail::client_service<
|
||||
stream_type, tls_context_type
|
||||
>;
|
||||
using clisvc_ptr = std::shared_ptr<client_service_type>;
|
||||
clisvc_ptr _svc_ptr;
|
||||
|
||||
public:
|
||||
|
||||
explicit mqtt_client(
|
||||
const executor_type& ex,
|
||||
const std::string& cnf,
|
||||
tls_context_type tls_context = {}
|
||||
) :
|
||||
_svc_ptr(std::make_shared<client_service_type>(
|
||||
ex, cnf, std::move(tls_context)
|
||||
))
|
||||
{}
|
||||
|
||||
template <typename ExecutionContext>
|
||||
requires (std::is_convertible_v<ExecutionContext&, asio::execution_context&>)
|
||||
explicit mqtt_client(
|
||||
ExecutionContext& context,
|
||||
const std::string& cnf,
|
||||
TlsContext tls_context = {}
|
||||
) :
|
||||
mqtt_client(context.get_executor(), cnf, std::move(tls_context))
|
||||
{}
|
||||
|
||||
~mqtt_client() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
decltype(auto) tls_context()
|
||||
requires (!std::is_same_v<TlsContext, std::monostate>) {
|
||||
return _svc_ptr->tls_context();
|
||||
}
|
||||
|
||||
void run() {
|
||||
_svc_ptr->open_stream();
|
||||
detail::ping_op { _svc_ptr }
|
||||
.perform(read_timeout - std::chrono::seconds(1));
|
||||
detail::read_message_op { _svc_ptr }.perform();
|
||||
detail::sentry_op { _svc_ptr }.perform();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
get_executor().execute([svc_ptr = _svc_ptr]() {
|
||||
svc_ptr->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
mqtt_client& will(will will) {
|
||||
_svc_ptr->will(std::move(will));
|
||||
return *this;
|
||||
}
|
||||
|
||||
mqtt_client& credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_svc_ptr->credentials(
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
);
|
||||
return *this;
|
||||
}
|
||||
|
||||
mqtt_client& brokers(std::string hosts, uint16_t default_port = 1883) {
|
||||
_svc_ptr->brokers(std::move(hosts), default_port);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <qos_e qos_type, typename CompletionToken>
|
||||
decltype(auto) async_publish(
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = detail::on_publish_signature<qos_type>;
|
||||
|
||||
auto initiate = [] (
|
||||
auto handler, std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props,
|
||||
const clisvc_ptr& svc_ptr
|
||||
) {
|
||||
detail::publish_send_op<
|
||||
client_service_type, decltype(handler), qos_type
|
||||
> { svc_ptr, std::move(handler) }
|
||||
.perform(
|
||||
std::move(topic), std::move(payload),
|
||||
retain, props
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
std::move(initiate), token,
|
||||
std::move(topic), std::move(payload), retain, props, _svc_ptr
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_subscribe(
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (
|
||||
error_code, std::vector<reason_code>, suback_props
|
||||
);
|
||||
|
||||
auto initiate = [] (
|
||||
auto handler, const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props, const clisvc_ptr& impl
|
||||
) {
|
||||
detail::subscribe_op { impl, std::move(handler) }
|
||||
.perform(topics, props);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
std::move(initiate), token, topics, props, _svc_ptr
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_subscribe(
|
||||
const subscribe_topic& topic, const subscribe_props& props,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return async_subscribe(
|
||||
std::vector<subscribe_topic> { topic }, props,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_unsubscribe(
|
||||
const std::vector<std::string>& topics, const unsubscribe_props& props,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (
|
||||
error_code, std::vector<reason_code>, unsuback_props
|
||||
);
|
||||
|
||||
auto initiate = [](
|
||||
auto handler,
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props, const clisvc_ptr& impl
|
||||
) {
|
||||
detail::unsubscribe_op { impl, std::move(handler) }
|
||||
.perform(topics, props);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
std::move(initiate), token, topics, props, _svc_ptr
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_unsubscribe(
|
||||
const std::string& topic, const unsubscribe_props& props,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return async_unsubscribe(
|
||||
std::vector<std::string> { topic }, props,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_receive(CompletionToken&& token) {
|
||||
// Sig = void (error_code, std::string, std::string, publish_props)
|
||||
return _svc_ptr->async_channel_receive(
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return detail::async_disconnect(
|
||||
reason_code, props, true, _svc_ptr,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_disconnect(CompletionToken&& token) {
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::normal_disconnection,
|
||||
disconnect_props {}, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_MQTT_CLIENT_HPP
|
||||
@@ -0,0 +1,167 @@
|
||||
#ifndef ASYNC_MQTT5_PROPERTY_TYPES_HPP
|
||||
#define ASYNC_MQTT5_PROPERTY_TYPES_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace async_mqtt5::prop {
|
||||
|
||||
constexpr std::integral_constant<uint8_t, 0x01> payload_format_indicator{};
|
||||
constexpr std::integral_constant<uint8_t, 0x02> message_expiry_interval{};
|
||||
constexpr std::integral_constant<uint8_t, 0x03> content_type{};
|
||||
constexpr std::integral_constant<uint8_t, 0x08> response_topic{};
|
||||
constexpr std::integral_constant<uint8_t, 0x09> correlation_data{};
|
||||
constexpr std::integral_constant<uint8_t, 0x0b> subscription_identifier{};
|
||||
constexpr std::integral_constant<uint8_t, 0x11> session_expiry_interval{};
|
||||
constexpr std::integral_constant<uint8_t, 0x12> assigned_client_identifier{};
|
||||
constexpr std::integral_constant<uint8_t, 0x13> server_keep_alive{};
|
||||
constexpr std::integral_constant<uint8_t, 0x15> authentication_method{};
|
||||
constexpr std::integral_constant<uint8_t, 0x16> authentication_data{};
|
||||
constexpr std::integral_constant<uint8_t, 0x17> request_problem_information{};
|
||||
constexpr std::integral_constant<uint8_t, 0x18> will_delay_interval{};
|
||||
constexpr std::integral_constant<uint8_t, 0x19> request_response_information{};
|
||||
constexpr std::integral_constant<uint8_t, 0x1a> response_information{};
|
||||
constexpr std::integral_constant<uint8_t, 0x1c> server_reference{};
|
||||
constexpr std::integral_constant<uint8_t, 0x1f> reason_string{};
|
||||
constexpr std::integral_constant<uint8_t, 0x21> receive_maximum{};
|
||||
constexpr std::integral_constant<uint8_t, 0x22> topic_alias_maximum{};
|
||||
constexpr std::integral_constant<uint8_t, 0x23> topic_alias{};
|
||||
constexpr std::integral_constant<uint8_t, 0x24> maximum_qos{};
|
||||
constexpr std::integral_constant<uint8_t, 0x25> retain_available{};
|
||||
constexpr std::integral_constant<uint8_t, 0x26> user_property{};
|
||||
constexpr std::integral_constant<uint8_t, 0x27> maximum_packet_size{};
|
||||
constexpr std::integral_constant<uint8_t, 0x28> wildcard_subscription_available{};
|
||||
constexpr std::integral_constant<uint8_t, 0x29> subscription_identifier_available{};
|
||||
constexpr std::integral_constant<uint8_t, 0x2a> shared_subscription_available{};
|
||||
|
||||
template <std::integral_constant p>
|
||||
struct property_traits;
|
||||
|
||||
#define DEF_PROPERTY_TRAIT(Pname, Ptype) \
|
||||
template <> struct property_traits<Pname> {\
|
||||
static constexpr std::string_view name = #Pname; \
|
||||
using type = Ptype;\
|
||||
}\
|
||||
|
||||
|
||||
DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional<int32_t>);
|
||||
DEF_PROPERTY_TRAIT(content_type, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(response_topic, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(correlation_data, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(subscription_identifier, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional<int32_t>);
|
||||
DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(server_keep_alive, std::optional<int16_t>);
|
||||
DEF_PROPERTY_TRAIT(authentication_method, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(authentication_data, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(request_problem_information, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(will_delay_interval, std::optional<int32_t>);
|
||||
DEF_PROPERTY_TRAIT(request_response_information, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(response_information, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(server_reference, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(reason_string, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(receive_maximum, std::optional<int16_t>);
|
||||
DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(topic_alias, std::optional<int16_t>);
|
||||
DEF_PROPERTY_TRAIT(maximum_qos, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(retain_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(user_property, std::vector<std::string>);
|
||||
DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional<int32_t>);
|
||||
DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional<uint8_t>);
|
||||
|
||||
#undef DEF_PROPERTY_TRAIT
|
||||
|
||||
template <std::integral_constant p>
|
||||
using value_type_t = typename property_traits<p>::type;
|
||||
|
||||
template <std::integral_constant p>
|
||||
constexpr std::string_view name_v = property_traits<p>::name;
|
||||
|
||||
template <std::integral_constant... Ps>
|
||||
class properties {
|
||||
template <std::integral_constant p>
|
||||
struct prop_type {
|
||||
using key = decltype(p);
|
||||
constexpr static std::string_view name = name_v<p>;
|
||||
value_type_t<p> value;
|
||||
};
|
||||
std::tuple<prop_type<Ps>...> _props;
|
||||
|
||||
public:
|
||||
|
||||
template <typename T, T v>
|
||||
constexpr auto& operator[](std::integral_constant<T, v> p) noexcept {
|
||||
using Ptype = decltype(p);
|
||||
return std::get<prop_type<Ptype{}>>(_props).value;
|
||||
}
|
||||
|
||||
template <typename T, T v>
|
||||
constexpr const auto& operator[](std::integral_constant<T, v> p) const noexcept {
|
||||
using Ptype = decltype(p);
|
||||
return std::get<prop_type<Ptype{}>>(_props).value;
|
||||
}
|
||||
|
||||
template <class Func>
|
||||
constexpr static bool is_apply_on_v =
|
||||
std::conjunction_v<std::is_invocable<Func, value_type_t<Ps>&>...>;
|
||||
|
||||
template <class Func>
|
||||
constexpr static bool is_nothrow_apply_on_v =
|
||||
std::conjunction_v<std::is_nothrow_invocable<Func, value_type_t<Ps>&>...>;
|
||||
|
||||
template <class Func> requires is_apply_on_v<Func>
|
||||
constexpr bool apply_on(uint8_t property_id, Func&& func) noexcept(is_nothrow_apply_on_v<Func>) {
|
||||
return std::apply([&func, property_id](auto&... props) {
|
||||
auto pc = [&func, property_id]<std::integral_constant prop>(prop_type<prop>& px) {
|
||||
if (prop.value == property_id)
|
||||
std::invoke(func, px.value);
|
||||
return prop.value != property_id;
|
||||
};
|
||||
return (pc(props) && ...);
|
||||
}, _props);
|
||||
}
|
||||
|
||||
template <class Func>
|
||||
constexpr static bool is_visitor_v =
|
||||
std::conjunction_v<std::is_invocable_r<bool, Func, decltype(Ps), value_type_t<Ps>&>...>;
|
||||
|
||||
template <class Func>
|
||||
constexpr static bool is_nothrow_visitor_v =
|
||||
std::conjunction_v<std::is_nothrow_invocable<Func, decltype(Ps), value_type_t<Ps>&>...>;
|
||||
|
||||
template <typename Func> requires is_visitor_v<Func>
|
||||
constexpr bool visit(Func && func) const noexcept(is_nothrow_visitor_v<Func>) {
|
||||
return std::apply(
|
||||
[&func](const auto&... props) {
|
||||
auto pc = [&func]<std::integral_constant prop>(const prop_type<prop>& px) {
|
||||
return std::invoke(func, prop, px.value);
|
||||
};
|
||||
return (pc(props) &&...);
|
||||
},
|
||||
_props);
|
||||
}
|
||||
|
||||
template <typename Func> requires is_visitor_v<Func>
|
||||
constexpr bool visit(Func&& func) noexcept(is_nothrow_visitor_v<Func>) {
|
||||
return std::apply(
|
||||
[&func](auto&... props) {
|
||||
auto pc = [&func]<std::integral_constant prop>(prop_type<prop>& px) {
|
||||
return std::invoke(func, prop, px.value);
|
||||
};
|
||||
return (pc(props) && ...);
|
||||
},
|
||||
_props);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::prop
|
||||
|
||||
#endif // !ASYNC_MQTT5_PROPERTY_TYPES_HPP
|
||||
@@ -0,0 +1,272 @@
|
||||
#ifndef ASYNC_MQTT5_TYPES_HPP
|
||||
#define ASYNC_MQTT5_TYPES_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/error.hpp>
|
||||
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
struct authority_path {
|
||||
std::string host, port, path;
|
||||
};
|
||||
|
||||
enum class qos_e : std::uint8_t {
|
||||
at_most_once = 0b00,
|
||||
at_least_once = 0b01,
|
||||
exactly_once = 0b10
|
||||
};
|
||||
|
||||
enum class retain_e : std::uint8_t {
|
||||
yes = 0b1, no = 0b0,
|
||||
};
|
||||
|
||||
enum class dup_e : std::uint8_t {
|
||||
yes = 0b1, no = 0b0,
|
||||
};
|
||||
|
||||
|
||||
struct subscribe_options {
|
||||
enum class no_local_e : std::uint8_t {
|
||||
no = 0b0,
|
||||
yes = 0b1,
|
||||
};
|
||||
|
||||
enum class retain_as_published_e : std::uint8_t {
|
||||
dont = 0b0,
|
||||
retain = 0b1,
|
||||
};
|
||||
|
||||
enum class retain_handling_e : std::uint8_t {
|
||||
send = 0b00,
|
||||
new_subscription_only = 0b01,
|
||||
not_send = 0b10,
|
||||
};
|
||||
|
||||
qos_e max_qos = qos_e::exactly_once;
|
||||
no_local_e no_local = no_local_e::yes;
|
||||
retain_as_published_e retain_as_published = retain_as_published_e::retain;
|
||||
retain_handling_e retain_handling = retain_handling_e::new_subscription_only;
|
||||
};
|
||||
|
||||
struct subscribe_topic {
|
||||
std::string topic_filter;
|
||||
subscribe_options sub_opts;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
reason codes:
|
||||
|
||||
0x00 success
|
||||
0x01 success_qos_1
|
||||
0x02 success_qos_2
|
||||
0x04 disconnect_with_will_message
|
||||
0x10 no_matching_subscribers
|
||||
0x11 no_subscription_existed
|
||||
0x18 continue_authentication
|
||||
0x19 re_authenticate
|
||||
0x80 failure
|
||||
0x81 malformed_packet
|
||||
0x82 protocol_error
|
||||
0x83 implementation_specific_error
|
||||
0x84 unsupported_protocol_version
|
||||
0x85 client_identifier_not_valid
|
||||
0x86 bad_user_name_or_password
|
||||
0x87 not_authorized
|
||||
0x88 server_unavailable
|
||||
0x89 server_busy
|
||||
0x8a banned
|
||||
0x8b server_shutting_down
|
||||
0x8c bad_authentication_method
|
||||
0x8d keep_alive_timeout
|
||||
0x8e session_taken_over
|
||||
0x8f topic_filter_invalid
|
||||
0x90 topic_name_invalid
|
||||
0x91 packet_identifier_in_use
|
||||
0x92 packet_identifier_not_found
|
||||
0x93 receive_maximum_exceeded
|
||||
0x94 topic_alias_invalid
|
||||
0x95 packet_too_large
|
||||
0x95 packet_too_large
|
||||
0x96 message_rate_too_high
|
||||
0x97 quota_exceeded
|
||||
0x98 administrative_action
|
||||
0x99 payload_format_invalid
|
||||
0x9a retain_not_supported
|
||||
0x9b qos_not_supported
|
||||
0x9c use_another_server
|
||||
0x9d server_moved
|
||||
0x9e shared_subscriptions_not_supported
|
||||
0x9f connection_rate_exceeded
|
||||
0xa0 maximum_connect_time
|
||||
0xa1 subscription_identifiers_not_supported
|
||||
0xa2 wildcard_subscriptions_not_supported
|
||||
|
||||
*/
|
||||
|
||||
class connect_props : public prop::properties<
|
||||
prop::session_expiry_interval,
|
||||
prop::receive_maximum,
|
||||
prop::maximum_packet_size,
|
||||
prop::topic_alias_maximum,
|
||||
prop::request_response_information,
|
||||
prop::request_problem_information,
|
||||
prop::user_property,
|
||||
prop::authentication_method,
|
||||
prop::authentication_data
|
||||
> {};
|
||||
|
||||
class connack_props : public prop::properties<
|
||||
prop::session_expiry_interval,
|
||||
prop::receive_maximum,
|
||||
prop::maximum_qos,
|
||||
prop::retain_available,
|
||||
prop::maximum_packet_size,
|
||||
prop::assigned_client_identifier,
|
||||
prop::topic_alias_maximum,
|
||||
prop::reason_string,
|
||||
prop::user_property,
|
||||
prop::wildcard_subscription_available,
|
||||
prop::subscription_identifier_available,
|
||||
prop::shared_subscription_available,
|
||||
prop::server_keep_alive,
|
||||
prop::response_information,
|
||||
prop::server_reference,
|
||||
prop::authentication_method,
|
||||
prop::authentication_data
|
||||
> {};
|
||||
|
||||
class publish_props : public prop::properties<
|
||||
prop::payload_format_indicator,
|
||||
prop::message_expiry_interval,
|
||||
prop::content_type,
|
||||
prop::response_topic,
|
||||
prop::correlation_data,
|
||||
prop::subscription_identifier,
|
||||
prop::topic_alias,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
// puback, pubcomp
|
||||
class puback_props : public prop::properties<
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
class pubcomp_props : public prop::properties<
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
class pubrec_props : public prop::properties<
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
class pubrel_props : public prop::properties<
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
|
||||
class subscribe_props : public prop::properties<
|
||||
prop::subscription_identifier,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
class suback_props : public prop::properties<
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
class unsubscribe_props : public prop::properties<
|
||||
prop::subscription_identifier
|
||||
> {};
|
||||
|
||||
class unsuback_props : public prop::properties<
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
class disconnect_props : public prop::properties<
|
||||
prop::session_expiry_interval,
|
||||
prop::reason_string,
|
||||
prop::user_property,
|
||||
prop::server_reference
|
||||
> {};
|
||||
|
||||
class auth_props : public prop::properties<
|
||||
prop::authentication_method,
|
||||
prop::authentication_data,
|
||||
prop::reason_string,
|
||||
prop::user_property
|
||||
> {};
|
||||
|
||||
|
||||
class will_props : public prop::properties<
|
||||
prop::will_delay_interval,
|
||||
prop::payload_format_indicator,
|
||||
prop::message_expiry_interval,
|
||||
prop::content_type,
|
||||
prop::response_topic,
|
||||
prop::correlation_data,
|
||||
prop::user_property
|
||||
>{};
|
||||
|
||||
class will : public will_props {
|
||||
std::string _topic;
|
||||
std::string _message;
|
||||
qos_e _qos; retain_e _retain;
|
||||
|
||||
public:
|
||||
will() = default;
|
||||
|
||||
will(
|
||||
std::string topic, std::string message,
|
||||
qos_e qos = qos_e::at_most_once, retain_e retain = retain_e::no
|
||||
) :
|
||||
_topic(std::move(topic)), _message(std::move(message)),
|
||||
_qos(qos), _retain(retain)
|
||||
{}
|
||||
|
||||
will(
|
||||
std::string topic, std::string message,
|
||||
qos_e qos, retain_e retain, will_props props
|
||||
) :
|
||||
will_props(std::move(props)),
|
||||
_topic(std::move(topic)), _message(std::move(message)),
|
||||
_qos(qos), _retain(retain)
|
||||
{}
|
||||
|
||||
// just to make sure that we don't accidentally make a copy
|
||||
will(const will&) = delete;
|
||||
will(will&&) noexcept = default;
|
||||
|
||||
will& operator=(const will&) = delete;
|
||||
will& operator=(will&&) noexcept = default;
|
||||
|
||||
constexpr std::string_view topic() const {
|
||||
return _topic;
|
||||
}
|
||||
constexpr std::string_view message() const {
|
||||
return _message;
|
||||
}
|
||||
constexpr qos_e qos() const {
|
||||
return _qos;
|
||||
}
|
||||
constexpr retain_e retain() const {
|
||||
return _retain;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_TYPES_HPP
|
||||
@@ -0,0 +1,155 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename Handler>
|
||||
decltype(auto) tracking_executor(Handler&& handler) {
|
||||
return asio::prefer(
|
||||
asio::get_associated_executor(std::forward<Handler>(handler)),
|
||||
asio::execution::outstanding_work.tracked
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
using tracking_type = std::decay_t<
|
||||
decltype(tracking_executor(std::declval<Handler>()))
|
||||
>;
|
||||
|
||||
template <typename Handler>
|
||||
class async_op {
|
||||
struct on_timer {};
|
||||
|
||||
std::decay_t<Handler> _handler;
|
||||
tracking_type<Handler> _handler_ex;
|
||||
asio::cancellation_state _cancel_state;
|
||||
|
||||
// must be unique_ptr because move(timer) cancels previous op
|
||||
std::unique_ptr<asio::steady_timer> _timer;
|
||||
public:
|
||||
template <typename Executor>
|
||||
async_op(const Executor& ex, Handler&& handler, const asio::cancellation_slot& cs) :
|
||||
_handler(std::forward<Handler>(handler)),
|
||||
_handler_ex(tracking_executor(_handler)),
|
||||
_cancel_state(cs),
|
||||
_timer(std::make_unique<asio::steady_timer>(ex))
|
||||
{}
|
||||
|
||||
async_op(async_op&&) noexcept = default;
|
||||
async_op& operator=(async_op&&) noexcept = default;
|
||||
|
||||
using executor_type = asio::steady_timer::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _timer->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
asio::cancellation_slot get_cancellation_slot() const noexcept {
|
||||
return _cancel_state.slot();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_timer->expires_from_now(std::chrono::seconds(5));
|
||||
_timer->async_wait(asio::prepend(std::move(*this), on_timer {}));
|
||||
}
|
||||
|
||||
void operator()(on_timer, boost::system::error_code ec) {
|
||||
if (ec == asio::error::operation_aborted) {
|
||||
fmt::print(stderr, "Aborted {}\n", ec.message());
|
||||
return;
|
||||
}
|
||||
_cancel_state.slot().clear();
|
||||
|
||||
fmt::print(stderr, "Dispatching with error {}\n", ec.message());
|
||||
asio::dispatch(_handler_ex, [h = std::move(_handler), ec]() mutable {
|
||||
std::move(h)(ec);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class owner {
|
||||
asio::cancellation_signal _cancel_signal;
|
||||
public:
|
||||
void cancel() {
|
||||
_cancel_signal.emit(asio::cancellation_type::terminal);
|
||||
_cancel_signal.slot().clear();
|
||||
}
|
||||
~owner() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_perform(asio::io_context& ioc, CompletionToken&& token) {
|
||||
auto initiation = [this, &ioc](auto handler) {
|
||||
auto slot = asio::get_associated_cancellation_slot(handler);
|
||||
async_op<decltype(handler)>(
|
||||
ioc.get_executor(), std::move(handler),
|
||||
slot.is_connected() ? slot : _cancel_signal.slot()
|
||||
).perform();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, void (boost::system::error_code)>(
|
||||
std::move(initiation), token
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
void cancel_test(asio::io_context& ioc) {
|
||||
asio::cancellation_signal cancel_signal;
|
||||
|
||||
asio::thread_pool thp;
|
||||
|
||||
{
|
||||
owner b;
|
||||
b.async_perform(ioc,
|
||||
asio::bind_cancellation_slot(
|
||||
cancel_signal.slot(),
|
||||
asio::bind_executor(thp.get_executor(),
|
||||
[](boost::system::error_code ec) {
|
||||
fmt::print(stderr, "Finished with error {}\n", ec.message());
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
asio::steady_timer timer3(ioc);
|
||||
timer3.expires_from_now(std::chrono::seconds(3));
|
||||
timer3.async_wait(
|
||||
asio::bind_cancellation_slot(
|
||||
cancel_signal.slot(),
|
||||
[&] (boost::system::error_code) {
|
||||
// cancel_signal.emit(asio::cancellation_type::terminal);
|
||||
// b.cancel();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
*/
|
||||
asio::steady_timer timer2(ioc);
|
||||
timer2.expires_from_now(std::chrono::seconds(1));
|
||||
timer2.async_wait([&] (boost::system::error_code) {
|
||||
// cancel_signal.emit(asio::cancellation_type::terminal);
|
||||
// b.cancel();
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
thp.join();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <fmt/format.h>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include "../../alloc/memory.h"
|
||||
#include "../../alloc/string.h"
|
||||
#include "../../alloc/vector.h"
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
struct bx {
|
||||
pma::string s1, s2;
|
||||
|
||||
bx(std::string v1, std::string v2, const pma::alloc<size_t>& alloc) :
|
||||
s1(v1.begin(), v1.end(), alloc),
|
||||
s2(v2.begin(), v2.end(), alloc)
|
||||
{}
|
||||
};
|
||||
|
||||
void test_memory() {
|
||||
asio::recycling_allocator<char> base_alloc;
|
||||
pma::resource_adaptor<asio::recycling_allocator<char>> mem_res { base_alloc };
|
||||
auto alloc = pma::alloc<char>(&mem_res);
|
||||
|
||||
pma::string s1 { "abcdefgrthoasofjasfasf", alloc };
|
||||
pma::string s2 { alloc };
|
||||
s2 = s1;
|
||||
|
||||
//TODO: the commented lines do not compile on Windows
|
||||
//pma::vector<char> v1 { { 'a', 'b'}, alloc };
|
||||
pma::vector<char> v2 { alloc };
|
||||
//v2 = std::move(v1);
|
||||
//pma::vector<char> v3 = v2;
|
||||
//pma::vector<char> v4 = std::move(v3);
|
||||
//v1.swap(v2);
|
||||
|
||||
bx vbx { "ABCD", "EFGH", alloc };
|
||||
|
||||
fmt::print(stderr, "String = {}, is equal: {}\n", s2,
|
||||
s1.get_allocator() == vbx.s2.get_allocator()
|
||||
);
|
||||
|
||||
//fmt::print(stderr, "Vector allocators are equal: {}\n",
|
||||
// v1.get_allocator() == v2.get_allocator()
|
||||
//);
|
||||
|
||||
std::allocator_traits<decltype(alloc)>::rebind_alloc<std::string> char_alloc =
|
||||
alloc;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <async/async.h>
|
||||
|
||||
#include <mqtt-client/detail/internal_types.hpp>
|
||||
#include <mqtt-client/codecs/message_encoders.hpp>
|
||||
#include <mqtt-client/codecs/message_decoders.hpp>
|
||||
|
||||
#include <mqtt-client/impl/assemble_op.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
namespace async_mqtt {
|
||||
|
||||
using byte_iter = detail::byte_iter;
|
||||
|
||||
class fake_stream {
|
||||
asio::any_io_executor _ex;
|
||||
std::string _data;
|
||||
int _chunk_no = -1;
|
||||
|
||||
std::string _read_buff;
|
||||
detail::data_span _data_span;
|
||||
|
||||
public:
|
||||
fake_stream(asio::any_io_executor ex) : _ex(std::move(ex)) {
|
||||
prepare_data();
|
||||
}
|
||||
|
||||
using executor_type = asio::any_io_executor;
|
||||
const executor_type& get_executor() const noexcept { return _ex; }
|
||||
|
||||
template <
|
||||
typename BufferType,
|
||||
typename CompletionToken
|
||||
>
|
||||
auto async_read_some(
|
||||
const BufferType& buffer, detail::duration wait_for,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
template <typename Stream, typename CompletionToken>
|
||||
decltype(auto) async_assemble(
|
||||
Stream& stream, detail::duration wait_for, CompletionToken&& token
|
||||
);
|
||||
|
||||
private:
|
||||
void prepare_data();
|
||||
std::string_view next_frame();
|
||||
};
|
||||
|
||||
template <
|
||||
typename BufferType,
|
||||
typename CompletionToken
|
||||
>
|
||||
auto fake_stream::async_read_some(
|
||||
const BufferType& buffer, detail::duration wait_for,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
auto read_op = [this] (auto handler, const BufferType& buffer) {
|
||||
auto data = next_frame();
|
||||
size_t bytes_read = data.size();
|
||||
std::copy(data.begin(), data.end(), static_cast<uint8_t*>(buffer.data()));
|
||||
|
||||
asio::post(get_executor(), [h = std::move(handler), bytes_read] () mutable {
|
||||
h(error_code{}, bytes_read);
|
||||
});
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, void (error_code, size_t)>(
|
||||
std::move(read_op), token, buffer
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Stream, typename CompletionToken>
|
||||
decltype(auto) fake_stream::async_assemble(
|
||||
Stream& stream, detail::duration wait_for, CompletionToken&& token
|
||||
) {
|
||||
auto initiation = [this] (auto handler, Stream& stream, detail::duration wait_for) mutable {
|
||||
detail::assemble_op (
|
||||
stream, std::move(handler),
|
||||
_read_buff, _data_span
|
||||
).perform(wait_for, asio::transfer_at_least(0));
|
||||
};
|
||||
|
||||
return asio::async_initiate<
|
||||
CompletionToken, void (error_code, uint8_t, byte_iter, byte_iter)
|
||||
> (
|
||||
std::move(initiation), token, std::ref(stream), wait_for
|
||||
);
|
||||
}
|
||||
|
||||
void fake_stream::prepare_data() {
|
||||
connack_props cap;
|
||||
cap[prop::session_expiry_interval] = 60;
|
||||
cap[prop::maximum_packet_size] = 16384;
|
||||
cap[prop::wildcard_subscription_available] = true;
|
||||
_data = encoders::encode_connack(true, 0x8A, cap);
|
||||
|
||||
puback_props pap;
|
||||
pap[prop::user_property].emplace_back("PUBACK user property");
|
||||
_data += encoders::encode_puback(42, 28, pap);
|
||||
}
|
||||
|
||||
std::string_view fake_stream::next_frame() {
|
||||
++_chunk_no;
|
||||
if (_chunk_no == 0)
|
||||
return { _data.begin(), _data.begin() + 2 };
|
||||
if (_chunk_no == 1)
|
||||
return { _data.begin() + 2, _data.begin() + 13 };
|
||||
if (_chunk_no == 2)
|
||||
return { _data.begin() + 13, _data.begin() + 23 };
|
||||
if (_chunk_no == 3)
|
||||
return { _data.begin() + 23, _data.begin() + 35 };
|
||||
if (_chunk_no == 4)
|
||||
return { _data.begin() + 35, _data.end() };
|
||||
return { _data.end(), _data.end() };
|
||||
}
|
||||
|
||||
template <typename Stream, typename CompletionToken>
|
||||
decltype(auto) async_assemble(
|
||||
Stream& stream, detail::duration wait_for,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return stream.async_assemble(
|
||||
stream, wait_for, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void test_single(asio::io_context& ioc) {
|
||||
using namespace std::chrono;
|
||||
|
||||
fake_stream s(asio::make_strand(ioc));
|
||||
|
||||
auto on_message = [] (
|
||||
error_code ec, uint8_t control_code, byte_iter first, byte_iter last
|
||||
) {
|
||||
fmt::print(stderr, "Error code: {}\n", ec.message());
|
||||
if (ec) return;
|
||||
size_t remain_length = std::distance(first, last);
|
||||
auto rv = decoders::decode_connack(control_code, remain_length, first);
|
||||
const auto& [session_present, reason_code, cap] = *rv;
|
||||
fmt::print(stderr, "Got CONNACK message, reason_code {}, session {}\n", reason_code, session_present);
|
||||
fmt::print(stderr, "session_expiry_interval: {}\n", *cap[prop::session_expiry_interval]);
|
||||
fmt::print(stderr, "maximum_packet_size: {}\n", *cap[prop::maximum_packet_size]);
|
||||
fmt::print(stderr, "wildcard_subscription_available: {}\n", *cap[prop::wildcard_subscription_available]);
|
||||
};
|
||||
|
||||
async_assemble(s, seconds(1), std::move(on_message));
|
||||
|
||||
ioc.run();
|
||||
ioc.restart();
|
||||
}
|
||||
|
||||
asio::awaitable<void> test_multiple_coro(asio::io_context& ioc) {
|
||||
using namespace std::chrono;
|
||||
|
||||
fake_stream s(asio::make_strand(ioc));
|
||||
|
||||
auto [ec1, cc1, first1, last1] = co_await async_assemble(
|
||||
s, seconds(1), asio::use_nothrow_awaitable
|
||||
);
|
||||
size_t remain_length1 = std::distance(first1, last1);
|
||||
auto rv1 = decoders::decode_connack(cc1, remain_length1, first1);
|
||||
if (rv1)
|
||||
fmt::print(stderr, "CONNACK correctly decoded\n");
|
||||
|
||||
auto [ec2, cc2, first2, last2] = co_await async_assemble(
|
||||
s, seconds(1), asio::use_nothrow_awaitable
|
||||
);
|
||||
size_t remain_length2 = std::distance(first2, last2);
|
||||
auto rv2 = decoders::decode_puback(cc2, remain_length2, first2);
|
||||
|
||||
if (rv2)
|
||||
fmt::print(stderr, "PUBACK correctly decoded\n");
|
||||
}
|
||||
|
||||
void test_multiple(asio::io_context& ioc) {
|
||||
co_spawn(ioc, test_multiple_coro(ioc), asio::detached);
|
||||
ioc.run();
|
||||
ioc.restart();
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt
|
||||
|
||||
void test_assembling(asio::io_context& ioc) {
|
||||
using namespace std::chrono;
|
||||
|
||||
async_mqtt::test_single(ioc);
|
||||
async_mqtt::test_multiple(ioc);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// #include <async/run_loop.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include <mqtt-client/detail/async_mutex.hpp>
|
||||
#include <async/mutex.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
||||
#include <nanobench/nanobench.h>
|
||||
|
||||
constexpr size_t nthreads = 4;
|
||||
constexpr size_t per_job_count = 10'000 / nthreads;
|
||||
|
||||
using namespace async_mqtt5::detail;
|
||||
|
||||
void with_async_mutex(async_mutex& mutex, std::size_t* count) {
|
||||
for (int c = 0; c < per_job_count; ++c) {
|
||||
mutex.lock([&mutex, count](error_code) {
|
||||
++(*count);
|
||||
mutex.unlock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void test_with_async_mutex(asio::any_io_executor e, async_mutex& mutex, size_t* count) {
|
||||
asio::post(e, [&mutex, count]() {
|
||||
with_async_mutex(mutex, count);
|
||||
});
|
||||
}
|
||||
|
||||
void with_old_async_mutex(async::mutex& mutex, std::size_t* count) {
|
||||
for (int c = 0; c < per_job_count; ++c) {
|
||||
mutex.lock([&mutex, count]() {
|
||||
++(*count);
|
||||
mutex.unlock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void test_with_old_async_mutex(asio::any_io_executor e, async::mutex& mutex, size_t* count) {
|
||||
asio::post(e, [&mutex, count]() {
|
||||
with_old_async_mutex(mutex, count);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
struct std_mutex : public std::mutex {
|
||||
std_mutex(asio::any_io_executor) : std::mutex() { }
|
||||
};
|
||||
|
||||
void with_mutex(std_mutex& mutex, size_t* count) {
|
||||
for (int c = 0; c < per_job_count; ++c) {
|
||||
mutex.lock();
|
||||
++(*count);
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void test_with_mutex(asio::any_io_executor e, std_mutex& mutex, size_t* count) {
|
||||
asio::post(e, [&mutex, count]() {
|
||||
with_mutex(mutex, count);
|
||||
});
|
||||
}
|
||||
|
||||
template <class Mutex, class Test>
|
||||
void test_with(Test func) {
|
||||
asio::thread_pool pool(nthreads);
|
||||
auto ex = pool.get_executor();
|
||||
auto count = std::make_unique<size_t>(0);
|
||||
Mutex mutex { ex };
|
||||
{
|
||||
for (auto i = 0; i < nthreads; ++i) {
|
||||
func(ex, mutex, count.get());
|
||||
}
|
||||
}
|
||||
pool.wait();
|
||||
if (*count != nthreads * per_job_count)
|
||||
throw "greska!";
|
||||
}
|
||||
|
||||
void test_cancellation() {
|
||||
|
||||
asio::thread_pool tp;
|
||||
asio::cancellation_signal cancel_signal;
|
||||
|
||||
async_mutex mutex(tp.get_executor());
|
||||
asio::steady_timer timer(tp);
|
||||
|
||||
auto op = [&](error_code ec) {
|
||||
if (ec == asio::error::operation_aborted) {
|
||||
mutex.unlock();
|
||||
return;
|
||||
}
|
||||
timer.expires_from_now(std::chrono::seconds(2));
|
||||
timer.async_wait([&] (boost::system::error_code) {
|
||||
fmt::print(stderr, "Async-locked operation done\n");
|
||||
mutex.unlock();
|
||||
});
|
||||
};
|
||||
|
||||
auto cancellable_op = [&](error_code ec) {
|
||||
fmt::print(
|
||||
stderr,
|
||||
"Cancellable async-locked operation finished with ec: {}\n", ec.message()
|
||||
);
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
mutex.unlock();
|
||||
};
|
||||
|
||||
mutex.lock(std::move(op));
|
||||
|
||||
mutex.lock(
|
||||
asio::bind_cancellation_slot(
|
||||
cancel_signal.slot(), std::move(cancellable_op)
|
||||
)
|
||||
);
|
||||
|
||||
asio::steady_timer timer2(tp);
|
||||
timer2.expires_from_now(std::chrono::seconds(1));
|
||||
timer2.async_wait([&] (boost::system::error_code) {
|
||||
cancel_signal.emit(asio::cancellation_type::terminal);
|
||||
});
|
||||
|
||||
tp.wait();
|
||||
}
|
||||
|
||||
void test_destructor() {
|
||||
asio::thread_pool tp;
|
||||
asio::cancellation_signal cancel_signal;
|
||||
|
||||
{
|
||||
async_mutex mutex(tp.get_executor());
|
||||
asio::steady_timer timer(tp);
|
||||
|
||||
auto op = [&](error_code ec_mtx) {
|
||||
if (ec_mtx == asio::error::operation_aborted) {
|
||||
fmt::print(
|
||||
stderr,
|
||||
"Mutex operation cancelled error_code {}\n",
|
||||
ec_mtx.message()
|
||||
);
|
||||
mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
timer.expires_from_now(std::chrono::seconds(2));
|
||||
timer.async_wait([&] (boost::system::error_code ec) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
return;
|
||||
|
||||
fmt::print(
|
||||
stderr,
|
||||
"Async-locked operation done with error_code {}\n",
|
||||
ec.message()
|
||||
);
|
||||
mutex.unlock();
|
||||
});
|
||||
};
|
||||
|
||||
mutex.lock(std::move(op));
|
||||
}
|
||||
|
||||
tp.wait();
|
||||
}
|
||||
|
||||
void test_basics() {
|
||||
asio::thread_pool tp;
|
||||
|
||||
// {
|
||||
asio::cancellation_signal cs;
|
||||
async_mutex mutex(tp.get_executor());
|
||||
auto s1 = asio::make_strand(tp.get_executor());
|
||||
auto s2 = asio::make_strand(tp.get_executor());
|
||||
|
||||
mutex.lock(asio::bind_executor(s1, [&mutex, s1](boost::system::error_code ec) mutable {
|
||||
fmt::print(
|
||||
stderr,
|
||||
"Scoped-locked operation (1) done with error_code {} ({})\n",
|
||||
ec.message(),
|
||||
s1.running_in_this_thread()
|
||||
);
|
||||
if (ec != asio::error::operation_aborted)
|
||||
mutex.unlock();
|
||||
}));
|
||||
|
||||
mutex.lock(
|
||||
asio::bind_cancellation_slot(
|
||||
cs.slot(),
|
||||
asio::bind_executor(s2, [s2](boost::system::error_code ec){
|
||||
fmt::print(
|
||||
stderr,
|
||||
"Scoped-locked operation (2) done with error_code {} ({})\n",
|
||||
ec.message(),
|
||||
s2.running_in_this_thread()
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
cs.emit(asio::cancellation_type_t::terminal);
|
||||
cs.slot().clear();
|
||||
// }
|
||||
|
||||
tp.wait();
|
||||
}
|
||||
|
||||
void test_mutex() {
|
||||
// test_basics();
|
||||
// return;
|
||||
// test_destructor();
|
||||
// test_cancellation();
|
||||
// return;
|
||||
|
||||
auto bench = ankerl::nanobench::Bench();
|
||||
bench.relative(true);
|
||||
|
||||
bench.run("std::mutex", [] {
|
||||
test_with<std_mutex>(test_with_mutex);
|
||||
});
|
||||
|
||||
bench.run("async_mutex", [] {
|
||||
test_with<async_mutex>(test_with_async_mutex);
|
||||
});
|
||||
|
||||
bench.run("async::mutex", [] {
|
||||
test_with<async::mutex>(test_with_old_async_mutex);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
template <typename T>
|
||||
static constexpr auto to_(T& arg) {
|
||||
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
|
||||
}
|
||||
|
||||
template <typename T, typename Parser>
|
||||
static constexpr auto as_(Parser&& p){
|
||||
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
void test_uri_parser(){
|
||||
struct authority_path { std::string host, port, path; };
|
||||
std::vector<authority_path> _servers;
|
||||
|
||||
std::string brokers = "iot.fcluster.mireo.hr:1234, fc/nesto";
|
||||
std::string default_port = "8883";
|
||||
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
std::string host, port, path;
|
||||
|
||||
// loosely based on RFC 3986
|
||||
auto unreserved_ = x3::char_("-a-zA-Z_0-9._~");
|
||||
auto digit_ = x3::char_("0-9");
|
||||
auto separator_ = x3::char_(',');
|
||||
|
||||
auto host_ = as_<std::string>(+unreserved_)[to_(host)];
|
||||
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
|
||||
auto path_ = as_<std::string>('/' >> *unreserved_)[to_(path)];
|
||||
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
|
||||
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
|
||||
|
||||
for (auto b = brokers.begin(); b != brokers.end(); ) {
|
||||
host.clear(); port.clear(); path.clear();
|
||||
if (phrase_parse(b, brokers.end(), uri_, x3::eps(false))) {
|
||||
_servers.push_back({
|
||||
std::move(host), port.empty() ? default_port : std::move(port),
|
||||
std::move(path)
|
||||
});
|
||||
}
|
||||
else b = brokers.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_DELAYED_OP_HPP
|
||||
#define ASYNC_MQTT5_TEST_DELAYED_OP_HPP
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using error_code = boost::system::error_code;
|
||||
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using duration = time_stamp::duration;
|
||||
|
||||
template <typename ...BoundArgs>
|
||||
class delayed_op {
|
||||
struct on_timer {};
|
||||
|
||||
std::unique_ptr<asio::steady_timer> _timer;
|
||||
time_stamp::duration _delay;
|
||||
asio::cancellation_slot _cancel_slot;
|
||||
|
||||
std::tuple<BoundArgs...> _args;
|
||||
|
||||
public:
|
||||
template <typename Executor, typename ...Args>
|
||||
delayed_op(
|
||||
const Executor& ex, time_stamp::duration delay, Args&& ...args
|
||||
) :
|
||||
_timer(new asio::steady_timer(ex)), _delay(delay),
|
||||
_args(std::move(args)...)
|
||||
{}
|
||||
|
||||
delayed_op(delayed_op&&) noexcept = default;
|
||||
delayed_op(const delayed_op&) = delete;
|
||||
|
||||
using executor_type = asio::steady_timer::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _timer->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::cancellation_slot;
|
||||
asio::cancellation_slot get_cancellation_slot() const noexcept {
|
||||
return _cancel_slot;
|
||||
}
|
||||
|
||||
template <typename CompletionHandler>
|
||||
void perform(CompletionHandler&& handler) {
|
||||
_cancel_slot = asio::get_associated_cancellation_slot(handler);
|
||||
|
||||
_timer->expires_from_now(_delay);
|
||||
_timer->async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {}, std::move(handler))
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionHandler>
|
||||
void operator()(on_timer, CompletionHandler&& h, error_code ec) {
|
||||
get_cancellation_slot().clear();
|
||||
|
||||
auto bh = std::apply(
|
||||
[h = std::move(h)](auto&&... args) mutable {
|
||||
return asio::append(std::move(h), std::move(args)...);
|
||||
},
|
||||
_args
|
||||
);
|
||||
|
||||
asio::dispatch(asio::prepend(std::move(bh), ec));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename CompletionToken, typename ...BoundArgs>
|
||||
decltype(auto) async_delay(
|
||||
asio::cancellation_slot cancel_slot,
|
||||
delayed_op<BoundArgs...>&& op,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, std::remove_cvref_t<BoundArgs>...);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, asio::cancellation_slot cancel_slot,
|
||||
delayed_op<BoundArgs...> op
|
||||
) {
|
||||
op.perform(
|
||||
asio::bind_cancellation_slot(cancel_slot, std::move(handler))
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
std::move(initiation), token, cancel_slot, std::move(op)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_DELAYED_OP_HPP
|
||||
@@ -0,0 +1,254 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_MESSAGE_EXCHANGE_HPP
|
||||
#define ASYNC_MQTT5_TEST_MESSAGE_EXCHANGE_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include "test_common/delayed_op.hpp"
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using error_code = boost::system::error_code;
|
||||
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using duration = time_stamp::duration;
|
||||
|
||||
class msg_exchange;
|
||||
class broker_message;
|
||||
|
||||
inline duration after(duration d) { return d; }
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class stream_message {
|
||||
error_code _ec;
|
||||
duration _after { 0 };
|
||||
std::vector<uint8_t> _content;
|
||||
|
||||
public:
|
||||
|
||||
template <typename ...Args>
|
||||
stream_message(error_code ec, duration after, Args&& ...args) :
|
||||
_ec(ec), _after(after)
|
||||
{
|
||||
(_content.insert(_content.end(), args.begin(), args.end()), ...);
|
||||
}
|
||||
|
||||
stream_message(const stream_message&) = delete;
|
||||
stream_message(stream_message&&) = default;
|
||||
|
||||
template <typename Executor>
|
||||
auto to_operation(const Executor& ex) {
|
||||
return delayed_op<error_code, std::vector<uint8_t>> {
|
||||
ex, _after, _ec, std::move(_content)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
|
||||
class client_message {
|
||||
msg_exchange* _owner;
|
||||
|
||||
error_code _write_ec;
|
||||
duration _complete_after { 0 };
|
||||
std::vector<std::string> _expected_packets;
|
||||
|
||||
std::vector<detail::stream_message> _replies;
|
||||
|
||||
public:
|
||||
template <typename ...Args>
|
||||
client_message(msg_exchange* owner, Args&&... args) :
|
||||
_owner(owner),
|
||||
_expected_packets({ std::forward<Args>(args)... })
|
||||
{}
|
||||
|
||||
client_message(const client_message&) = delete;
|
||||
client_message(client_message&&) = default;
|
||||
|
||||
client_message& complete_with(error_code ec, duration af) {
|
||||
_write_ec = ec;
|
||||
_complete_after = af;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
client_message& reply_with(Args&& ...args) {
|
||||
// just to allow duration to be the last parameter
|
||||
auto t = std::make_tuple(std::forward<Args>(args)...);
|
||||
using Tuple = decltype(t);
|
||||
|
||||
return[&]<auto... I>(std::index_sequence<I...>) -> client_message& {
|
||||
return reply_with_impl(
|
||||
std::get<std::tuple_size_v<Tuple> -1>(t),
|
||||
std::get<I>(t)...
|
||||
);
|
||||
}(std::make_index_sequence<std::tuple_size_v<Tuple> -1>{});
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
client_message& expect(Args&& ...args);
|
||||
|
||||
template <typename ...Args>
|
||||
broker_message& send(Args&& ...args);
|
||||
|
||||
template <typename Executor>
|
||||
decltype(auto) write_completion(const Executor& ex) const {
|
||||
return delayed_op<error_code>(ex, _complete_after, _write_ec);
|
||||
}
|
||||
|
||||
template <typename Executor>
|
||||
decltype(auto) pop_reply_ops(const Executor& ex) {
|
||||
std::vector<delayed_op<error_code, std::vector<uint8_t>>> ret;
|
||||
std::transform(
|
||||
_replies.begin(), _replies.end(), std::back_inserter(ret),
|
||||
[&ex](auto& r) { return r.to_operation(ex); }
|
||||
);
|
||||
_replies.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <typename ...Args>
|
||||
requires (std::is_same_v<std::remove_cvref_t<Args>, std::string> && ...)
|
||||
client_message& reply_with_impl(duration af, Args&& ...args) {
|
||||
_replies.emplace_back(
|
||||
error_code {}, af, std::forward<Args>(args)...
|
||||
);
|
||||
return *this;
|
||||
}
|
||||
|
||||
client_message& reply_with_impl(duration af, error_code ec) {
|
||||
_replies.emplace_back(ec, af);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class broker_message {
|
||||
msg_exchange* _owner;
|
||||
detail::stream_message _message;
|
||||
|
||||
public:
|
||||
template <typename ...Args>
|
||||
broker_message(
|
||||
msg_exchange* owner, error_code ec, duration af, Args&&... args
|
||||
) :
|
||||
_owner(owner), _message(ec, af, std::forward<Args>(args) ...)
|
||||
{}
|
||||
|
||||
broker_message(const broker_message&) = delete;
|
||||
broker_message(broker_message&&) = default;
|
||||
|
||||
template <typename ...Args>
|
||||
client_message& expect(Args&& ...args);
|
||||
|
||||
template <typename ...Args>
|
||||
broker_message& send(Args&& ...args);
|
||||
|
||||
template <typename Executor>
|
||||
decltype(auto) pop_send_op(const Executor& ex) {
|
||||
return _message.to_operation(ex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class msg_exchange {
|
||||
std::deque<client_message> _to_broker;
|
||||
std::vector<broker_message> _from_broker;
|
||||
|
||||
public:
|
||||
template <typename ...Args>
|
||||
requires (std::is_same_v<std::remove_cvref_t<Args>, std::string> && ...)
|
||||
client_message& expect(Args&& ...args) {
|
||||
_to_broker.emplace_back(this, std::forward<Args>(args)...);
|
||||
return _to_broker.back();
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
broker_message& send(Args&& ...args) {
|
||||
// just to allow duration to be the last parameter
|
||||
auto t = std::make_tuple(std::forward<Args>(args)...);
|
||||
using Tuple = decltype(t);
|
||||
|
||||
return[&]<auto... I>(std::index_sequence<I...>) -> broker_message& {
|
||||
return send_impl(
|
||||
std::get<std::tuple_size_v<Tuple> -1>(t),
|
||||
std::get<I>(t)...
|
||||
);
|
||||
}(std::make_index_sequence<std::tuple_size_v<Tuple> -1>{});
|
||||
}
|
||||
|
||||
std::optional<client_message> pop_reply_action() {
|
||||
if (_to_broker.empty())
|
||||
return std::nullopt;
|
||||
|
||||
auto rv = std::move(_to_broker.front());
|
||||
_to_broker.pop_front();
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename Executor>
|
||||
auto pop_broker_ops(const Executor& ex) {
|
||||
std::vector<delayed_op<error_code, std::vector<uint8_t>>> ret;
|
||||
std::transform(
|
||||
_from_broker.begin(), _from_broker.end(), std::back_inserter(ret),
|
||||
[&ex](auto& s) { return s.pop_send_op(ex); }
|
||||
);
|
||||
_from_broker.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <typename ...Args>
|
||||
requires (std::is_same_v<std::remove_cvref_t<Args>, std::string> && ...)
|
||||
broker_message& send_impl(duration after, Args&& ...args) {
|
||||
_from_broker.emplace_back(
|
||||
this, error_code {}, after, std::forward<Args>(args)...
|
||||
);
|
||||
return _from_broker.back();
|
||||
}
|
||||
|
||||
broker_message& send_impl(duration after, error_code ec) {
|
||||
_from_broker.emplace_back(this, ec, after);
|
||||
return _from_broker.back();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
client_message& client_message::expect(Args&& ...args) {
|
||||
return _owner->expect(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
broker_message& client_message::send(Args&& ...args) {
|
||||
return _owner->send(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
client_message& broker_message::expect(Args&& ...args) {
|
||||
return _owner->expect(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
broker_message& broker_message::send(Args&& ...args) {
|
||||
return _owner->send(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_MESSAGE_EXCHANGE_HPP
|
||||
@@ -0,0 +1,126 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_PACKET_UTIL_HPP
|
||||
#define ASYNC_MQTT5_TEST_PACKET_UTIL_HPP
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
|
||||
inline qos_e extract_qos(uint8_t flags) {
|
||||
auto byte = (flags & 0b0110) >> 1;
|
||||
return qos_e(byte);
|
||||
}
|
||||
|
||||
inline control_code_e extract_code(uint8_t control_byte) {
|
||||
using enum control_code_e;
|
||||
|
||||
constexpr uint8_t mask = 0b11110000;
|
||||
constexpr uint8_t publish_bits = 0b0011;
|
||||
constexpr uint8_t special_mask = 0b00000010;
|
||||
constexpr control_code_e codes_with_non_zero_end[] = {
|
||||
pubrel, subscribe, unsubscribe
|
||||
};
|
||||
|
||||
if ((control_byte >> 4) == publish_bits)
|
||||
return publish;
|
||||
if ((control_byte & mask) == control_byte)
|
||||
return control_code_e(control_byte & mask);
|
||||
|
||||
for (const auto& special_code : codes_with_non_zero_end)
|
||||
if (control_byte == (uint8_t(special_code) | special_mask))
|
||||
return special_code;
|
||||
|
||||
return no_packet;
|
||||
}
|
||||
|
||||
|
||||
inline std::string_view code_to_str(control_code_e code) {
|
||||
using enum control_code_e;
|
||||
|
||||
switch (code) {
|
||||
case connect: return "CONNECT";
|
||||
case connack: return "CONNACK";
|
||||
case publish: return "PUBLISH";
|
||||
case puback: return "PUBACK";
|
||||
case pubrec: return "PUBREC";
|
||||
case pubrel: return "PUBREL";
|
||||
case pubcomp: return "PUBCOMP";
|
||||
case subscribe: return "SUBSCRIBE";
|
||||
case suback: return "SUBACK";
|
||||
case unsubscribe: return "UNSUBSCRIBE";
|
||||
case unsuback: return "UNSUBACK";
|
||||
case auth: return "AUTH";
|
||||
case disconnect: return "DISCONNECT";
|
||||
case pingreq: return "PINGREQ";
|
||||
case pingresp: return "PINGRESP";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
inline std::string to_readable_packet(
|
||||
std::string packet, error_code ec = {}, bool incoming = false
|
||||
) {
|
||||
using enum control_code_e;
|
||||
|
||||
auto control_byte = uint8_t(*packet.data());
|
||||
auto code = extract_code(control_byte);
|
||||
|
||||
if (code == no_packet)
|
||||
return {};
|
||||
|
||||
std::ostringstream stream;
|
||||
|
||||
if (incoming)
|
||||
stream << "-> ";
|
||||
|
||||
if (code == connect || code == connack || code == disconnect) {
|
||||
stream << code_to_str(code) << (ec ? " ec: " + ec.message() : "");
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
auto begin = ++packet.cbegin();
|
||||
auto varlen = decoders::type_parse(
|
||||
begin, packet.cend(), decoders::basic::varint_
|
||||
);
|
||||
|
||||
if (code == publish) {
|
||||
auto publish = decoders::decode_publish(
|
||||
control_byte, *varlen, begin
|
||||
);
|
||||
auto& [topic, packet_id, flags, props, payload] = *publish;
|
||||
stream << code_to_str(code);
|
||||
stream << (packet_id ? " " + std::to_string(*packet_id) : "");
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
const auto packet_id = decoders::decode_packet_id(begin).value();
|
||||
stream << code_to_str(code) << " " << packet_id;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
template <typename ConstBufferSequence>
|
||||
std::vector<std::string> to_packets(const ConstBufferSequence& buffers) {
|
||||
std::vector<std::string> content;
|
||||
|
||||
for (const auto& buff : buffers) {
|
||||
auto control_byte = *(const uint8_t*) buff.data();
|
||||
auto code = extract_code(control_byte);
|
||||
|
||||
if (code == control_code_e::pingreq)
|
||||
continue;
|
||||
|
||||
content.push_back({ (const char*)buff.data(), buff.size() });
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_PACKET_UTIL_HPP
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_PROTOCOL_LOGGING_HPP
|
||||
#define ASYNC_MQTT5_TEST_PROTOCOL_LOGGING_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
inline bool& logging_enabled() {
|
||||
static bool enabled = false;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
inline void log(const std::string& message) {
|
||||
if (logging_enabled())
|
||||
fprintf(stderr, "%s\n", message.c_str());
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_PROTOCOL_LOGGING_HPP
|
||||
@@ -0,0 +1,324 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_TEST_BROKER_HPP
|
||||
#define ASYNC_MQTT5_TEST_TEST_BROKER_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
|
||||
#include "test_common/protocol_logging.hpp"
|
||||
#include "test_common/message_exchange.hpp"
|
||||
#include "test_common/packet_util.hpp"
|
||||
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
class pending_read {
|
||||
void* _buffer_data { nullptr };
|
||||
size_t _buffer_size { 0 };
|
||||
asio::any_completion_handler<void (error_code, size_t)> _handler {};
|
||||
|
||||
public:
|
||||
template <typename MutableBuffer, typename Handler>
|
||||
pending_read(const MutableBuffer& buffer, Handler&& handler) :
|
||||
_buffer_data(asio::buffer_cast<void*>(buffer)),
|
||||
_buffer_size(buffer.size()),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
pending_read() = default;
|
||||
pending_read(pending_read&&) = default;
|
||||
pending_read& operator=(pending_read&&) = default;
|
||||
|
||||
size_t consume(const std::vector<uint8_t>& data) {
|
||||
size_t num_bytes = std::min(_buffer_size, data.size());
|
||||
std::memcpy(_buffer_data, data.data(), num_bytes);
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
template <typename Executor>
|
||||
void complete(const Executor& ex, error_code ec, size_t bytes_read) {
|
||||
if (empty())
|
||||
return;
|
||||
if (ec || bytes_read || _buffer_size == 0)
|
||||
asio::post(ex, asio::prepend(std::move(_handler), ec, bytes_read));
|
||||
}
|
||||
|
||||
constexpr bool empty() const {
|
||||
return !_handler;
|
||||
}
|
||||
};
|
||||
|
||||
class test_broker : public asio::execution_context::service {
|
||||
public:
|
||||
using executor_type = asio::any_io_executor;
|
||||
using protocol_type = asio::ip::tcp;
|
||||
using endpoint_type = asio::ip::tcp::endpoint;
|
||||
|
||||
static inline asio::execution_context::id id {};
|
||||
|
||||
private:
|
||||
using base = asio::execution_context::service;
|
||||
|
||||
struct on_receive {};
|
||||
struct on_delayed_complete {};
|
||||
|
||||
struct broker_data {
|
||||
error_code ec;
|
||||
std::vector<uint8_t> bytes;
|
||||
};
|
||||
|
||||
executor_type _ex;
|
||||
std::deque<broker_data> _broker_data;
|
||||
pending_read _pending_read;
|
||||
|
||||
msg_exchange _broker_side;
|
||||
std::vector<std::unique_ptr<asio::cancellation_signal>> _cancel_signals;
|
||||
|
||||
public:
|
||||
test_broker(
|
||||
asio::execution_context& context,
|
||||
asio::any_io_executor ex = {}, msg_exchange broker_side = {}
|
||||
) :
|
||||
base(context), _ex(std::move(ex)), _broker_side(std::move(broker_side))
|
||||
{
|
||||
launch_broker_ops();
|
||||
}
|
||||
|
||||
test_broker(const test_broker&) = delete;
|
||||
void operator=(const test_broker&) = delete;
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _ex;
|
||||
}
|
||||
|
||||
void close_connection() {
|
||||
_pending_read.complete(
|
||||
get_executor(), asio::error::operation_aborted, 0
|
||||
);
|
||||
|
||||
for (auto& cs : _cancel_signals)
|
||||
cs->emit(asio::cancellation_type::terminal);
|
||||
|
||||
_broker_data.clear();
|
||||
}
|
||||
|
||||
|
||||
template <typename ConstBufferSequence, typename WriteToken>
|
||||
decltype(auto) write_to_network(
|
||||
const ConstBufferSequence& buffers,
|
||||
WriteToken&& token
|
||||
) {
|
||||
auto initiation = [this](
|
||||
auto handler, const ConstBufferSequence& buffers
|
||||
) {
|
||||
auto reply_action = _broker_side.pop_reply_action();
|
||||
|
||||
size_t bytes_written = std::accumulate(
|
||||
std::begin(buffers), std::end(buffers), size_t(0),
|
||||
[](size_t a, const auto& b) { return a + b.size(); }
|
||||
);
|
||||
|
||||
executor_type ex = get_executor();
|
||||
// TODO: validate
|
||||
|
||||
auto complete_op = reply_action ?
|
||||
reply_action->write_completion(ex) :
|
||||
delayed_op<error_code>(ex, 0ms, error_code {});
|
||||
|
||||
async_delay(
|
||||
make_cancel_slot(), std::move(complete_op),
|
||||
asio::prepend(
|
||||
std::ref(*this), on_delayed_complete {},
|
||||
std::move(handler), bytes_written
|
||||
)
|
||||
);
|
||||
|
||||
if (!reply_action.has_value())
|
||||
return;
|
||||
|
||||
for (auto& op : reply_action->pop_reply_ops(ex))
|
||||
async_delay(
|
||||
make_cancel_slot(), std::move(op),
|
||||
asio::prepend(std::ref(*this), on_receive {})
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<WriteToken, void (error_code, size_t)>(
|
||||
std::move(initiation), token, buffers
|
||||
);
|
||||
}
|
||||
|
||||
template <typename MutableBuffer, typename ReadToken>
|
||||
decltype(auto) read_from_network(
|
||||
const MutableBuffer& buffer, ReadToken&& token
|
||||
) {
|
||||
auto initiation = [this](
|
||||
auto handler, const MutableBuffer& buffer
|
||||
) {
|
||||
_pending_read = pending_read(buffer, std::move(handler));
|
||||
complete_read();
|
||||
};
|
||||
|
||||
return asio::async_initiate<ReadToken, void (error_code, size_t)>(
|
||||
std::move(initiation), token, buffer
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void operator()(
|
||||
on_receive, error_code delay_ec,
|
||||
error_code ec, std::vector<uint8_t> bytes
|
||||
) {
|
||||
remove_cancel_signal();
|
||||
|
||||
if (delay_ec) // asio::operation_aborted
|
||||
return;
|
||||
|
||||
_broker_data.push_back({ ec, std::move(bytes) });
|
||||
complete_read();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
on_delayed_complete, Handler handler, size_t bytes,
|
||||
error_code delay_ec, error_code ec
|
||||
) {
|
||||
remove_cancel_signal();
|
||||
|
||||
if (delay_ec) { // asio::operation_aborted
|
||||
ec = delay_ec;
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
asio::dispatch(asio::prepend(std::move(handler), ec, bytes));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void shutdown() override { }
|
||||
|
||||
void launch_broker_ops() {
|
||||
for (auto& op: _broker_side.pop_broker_ops(get_executor())) {
|
||||
async_delay(
|
||||
asio::cancellation_slot {},
|
||||
std::move(op),
|
||||
asio::prepend(std::ref(*this), on_receive {})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void complete_read() {
|
||||
if (_pending_read.empty())
|
||||
return;
|
||||
|
||||
error_code ec = {};
|
||||
size_t bytes_read = 0;
|
||||
|
||||
if (!_broker_data.empty()) {
|
||||
auto& [read_ec, bytes] = _broker_data.front();
|
||||
ec = read_ec;
|
||||
bytes_read = _pending_read.consume(bytes);
|
||||
|
||||
if (bytes_read == bytes.size())
|
||||
_broker_data.pop_front();
|
||||
else
|
||||
bytes.erase(bytes.begin(), bytes.begin() + bytes_read);
|
||||
}
|
||||
|
||||
_pending_read.complete(get_executor(), ec, bytes_read);
|
||||
}
|
||||
|
||||
asio::cancellation_slot make_cancel_slot() {
|
||||
_cancel_signals.push_back(
|
||||
std::make_unique<asio::cancellation_signal>()
|
||||
);
|
||||
return _cancel_signals.back()->slot();
|
||||
}
|
||||
|
||||
void remove_cancel_signal() {
|
||||
_cancel_signals.erase(
|
||||
std::remove_if(
|
||||
_cancel_signals.begin(), _cancel_signals.end(),
|
||||
[](auto& sig_ptr) { return !sig_ptr->slot().has_handler(); }
|
||||
),
|
||||
_cancel_signals.end()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
|
||||
|
||||
// Funs temporarily moved out of network service
|
||||
//
|
||||
//void process_packet(const std::string& packet) {
|
||||
// using enum control_code_e;
|
||||
//
|
||||
// auto code = extract_code(uint8_t(*packet.data()));
|
||||
// if (code == connack)
|
||||
// determine_network_properties(packet);
|
||||
// else if (code == puback || code == pubcomp)
|
||||
// _num_outgoing_publishes--;
|
||||
//}
|
||||
//
|
||||
//void determine_network_properties(const std::string& connack) {
|
||||
// auto begin = connack.cbegin() + 1 /* fixed header */;
|
||||
// auto _ = decoders::type_parse(begin, connack.cend(), decoders::basic::varint_);
|
||||
// auto rv = decoders::decode_connack(connack.size(), begin);
|
||||
// const auto& [session_present, reason_code, ca_props] = *rv;
|
||||
//
|
||||
// if (ca_props[prop::receive_maximum])
|
||||
// _max_receive = *ca_props[prop::receive_maximum];
|
||||
// else
|
||||
// _max_receive = MAX_LIMIT;
|
||||
//}
|
||||
//
|
||||
//void count_outgoing_publishes(
|
||||
// const std::vector<std::string>& packets
|
||||
//) {
|
||||
// for (const auto& packet : packets) {
|
||||
// auto code = extract_code(uint8_t(*packet.data()));
|
||||
// if (code == control_code_e::publish) {
|
||||
// auto flags = *packet.data() & 0b00001111;
|
||||
// auto qos = extract_qos(flags);
|
||||
//
|
||||
// if (qos != qos_e::at_most_once)
|
||||
// _num_outgoing_publishes++;
|
||||
//
|
||||
// BOOST_ASIO_CHECK_MESSAGE(
|
||||
// _num_outgoing_publishes <= _max_receive,
|
||||
// "There are more outgoing PUBLISH packets than\
|
||||
// it is allowed by the Maxmimum Receive Limit!"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
// write_to_network stuff
|
||||
|
||||
//auto packets = to_packets(buffers);
|
||||
//count_outgoing_publishes(packets);
|
||||
//// TODO: this is just for debug right now
|
||||
//if (!write_ec)
|
||||
// for (const auto& packet : packets)
|
||||
// test::log(to_readable_packet(packet, write_ec, false));
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_TEST_BROKER_HPP
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_TEST_SERVICE_HPP
|
||||
#define ASYNC_MQTT5_TEST_TEST_SERVICE_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/client_service.hpp>
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename TlsContext = std::monostate
|
||||
>
|
||||
class test_service : public detail::client_service<StreamType, TlsContext> {
|
||||
using error_code = boost::system::error_code;
|
||||
using base = detail::client_service<StreamType, TlsContext>;
|
||||
|
||||
asio::any_io_executor _ex;
|
||||
public:
|
||||
test_service(const asio::any_io_executor ex)
|
||||
: base(ex, {}), _ex(ex)
|
||||
{}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_send(
|
||||
const BufferType&, uint32_t, unsigned,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
auto initiation = [this](auto handler) {
|
||||
auto ex = boost::asio::get_associated_executor(handler, _ex);
|
||||
boost::asio::post(ex,
|
||||
boost::asio::prepend(std::move(handler), error_code {})
|
||||
);
|
||||
};
|
||||
|
||||
return boost::asio::async_initiate<
|
||||
CompletionToken, void (error_code)
|
||||
> (std::move(initiation), token);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_TEST_SERVICE_HPP
|
||||
@@ -0,0 +1,337 @@
|
||||
#ifndef ASYNC_MQTT5_TEST_TEST_STREAM_HPP
|
||||
#define ASYNC_MQTT5_TEST_TEST_STREAM_HPP
|
||||
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/execution_context.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/experimental/channel.hpp>
|
||||
|
||||
#include "test_common/test_broker.hpp"
|
||||
|
||||
namespace async_mqtt5::test {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace asioex = asio::experimental;
|
||||
|
||||
using error_code = boost::system::error_code;
|
||||
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using duration = time_stamp::duration;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class test_stream_impl {
|
||||
public:
|
||||
using executor_type = test_broker::executor_type;
|
||||
using protocol_type = test_broker::protocol_type;
|
||||
using endpoint_type = test_broker::endpoint_type;
|
||||
|
||||
private:
|
||||
executor_type _ex;
|
||||
test_broker* _test_broker { nullptr };
|
||||
endpoint_type _remote_ep;
|
||||
|
||||
template <typename Handler>
|
||||
friend class read_op;
|
||||
|
||||
template <typename Handler>
|
||||
friend class write_op;
|
||||
|
||||
public:
|
||||
test_stream_impl(executor_type ex) : _ex(std::move(ex)) {}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _ex;
|
||||
}
|
||||
|
||||
void open(const protocol_type&, error_code& ec) {
|
||||
ec = {};
|
||||
_test_broker = &asio::use_service<test_broker>(_ex.context());
|
||||
}
|
||||
|
||||
void close(error_code& ec) {
|
||||
disconnect();
|
||||
ec = {};
|
||||
_test_broker = nullptr;
|
||||
}
|
||||
|
||||
void shutdown(asio::ip::tcp::socket::shutdown_type, error_code& ec) {
|
||||
ec = {};
|
||||
}
|
||||
|
||||
void connect(const endpoint_type& ep, error_code& ec) {
|
||||
ec = {};
|
||||
_remote_ep = ep;
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
_remote_ep = {};
|
||||
if (_test_broker)
|
||||
_test_broker->close_connection();
|
||||
}
|
||||
|
||||
endpoint_type remote_endpoint(error_code& ec) {
|
||||
if (_remote_ep == endpoint_type {})
|
||||
ec = asio::error::not_connected;
|
||||
else
|
||||
ec = {};
|
||||
return _remote_ep;
|
||||
}
|
||||
|
||||
bool is_open() const {
|
||||
return _test_broker != nullptr;
|
||||
}
|
||||
|
||||
bool is_connected() const {
|
||||
return _remote_ep != endpoint_type {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename Handler>
|
||||
class read_op {
|
||||
struct on_read {};
|
||||
std::shared_ptr<test_stream_impl> _stream_impl;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
public:
|
||||
read_op(
|
||||
std::shared_ptr<test_stream_impl> stream_impl, Handler handler
|
||||
) :
|
||||
_stream_impl(std::move(stream_impl)),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
read_op(read_op&&) noexcept = default;
|
||||
read_op(const read_op&) = delete;
|
||||
|
||||
using executor_type = test_stream_impl::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream_impl->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(const BufferType& buffer) {
|
||||
if (!_stream_impl->is_open() || !_stream_impl->is_connected())
|
||||
return complete_post(asio::error::not_connected, 0);
|
||||
|
||||
_stream_impl->_test_broker->read_from_network(
|
||||
buffer,
|
||||
asio::prepend(std::move(*this), on_read {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_read, error_code ec, size_t bytes_read) {
|
||||
if (ec)
|
||||
_stream_impl->disconnect();
|
||||
complete(ec, bytes_read);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete_post(error_code ec, size_t bytes_read) {
|
||||
asio::post(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec, bytes_read)
|
||||
);
|
||||
}
|
||||
|
||||
void complete(error_code ec, size_t bytes_read) {
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec, bytes_read)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Handler>
|
||||
class write_op {
|
||||
struct on_write {};
|
||||
|
||||
std::shared_ptr<test_stream_impl> _stream_impl;
|
||||
std::decay_t<Handler> _handler;
|
||||
|
||||
public:
|
||||
write_op(
|
||||
std::shared_ptr<test_stream_impl> stream_impl, Handler handler
|
||||
) :
|
||||
_stream_impl(std::move(stream_impl)),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
write_op(write_op&&) noexcept = default;
|
||||
write_op(const write_op&) = delete;
|
||||
|
||||
using executor_type = test_stream_impl::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream_impl->get_executor();
|
||||
}
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(const BufferType& buffers) {
|
||||
if (!_stream_impl->is_open() || !_stream_impl->is_connected())
|
||||
return complete_post(asio::error::not_connected, 0);
|
||||
|
||||
_stream_impl->_test_broker->write_to_network(
|
||||
buffers,
|
||||
asio::prepend(std::move(*this), on_write {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_write, error_code ec, size_t bytes_written) {
|
||||
if (ec)
|
||||
_stream_impl->disconnect();
|
||||
complete(ec, bytes_written);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete_post(error_code ec, size_t bytes_written) {
|
||||
asio::post(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec, bytes_written)
|
||||
);
|
||||
}
|
||||
|
||||
void complete(error_code ec, size_t bytes_written) {
|
||||
asio::dispatch(
|
||||
get_executor(),
|
||||
asio::prepend(std::move(_handler), ec, bytes_written)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
class test_stream {
|
||||
public:
|
||||
using executor_type = test_broker::executor_type;
|
||||
using protocol_type = test_broker::protocol_type;
|
||||
using endpoint_type = test_broker::endpoint_type;
|
||||
|
||||
private:
|
||||
std::shared_ptr<detail::test_stream_impl> _impl;
|
||||
|
||||
public:
|
||||
test_stream(executor_type ex) :
|
||||
_impl(std::make_shared<detail::test_stream_impl>(std::move(ex)))
|
||||
{}
|
||||
|
||||
test_stream(const test_stream&) = delete;
|
||||
|
||||
~test_stream() {
|
||||
error_code ec;
|
||||
close(ec); // cancel() would be more appropriate
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _impl->get_executor();
|
||||
}
|
||||
|
||||
void open(const protocol_type& p, error_code& ec) {
|
||||
_impl->open(p, ec);
|
||||
}
|
||||
|
||||
void close(error_code& ec) {
|
||||
_impl->close(ec);
|
||||
}
|
||||
|
||||
void connect(const endpoint_type& ep, error_code& ec) {
|
||||
_impl->connect(ep, ec);
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
_impl->disconnect();
|
||||
}
|
||||
|
||||
bool is_open() const {
|
||||
return _impl->is_open();
|
||||
}
|
||||
|
||||
bool is_connected() const {
|
||||
return _impl->is_connected();
|
||||
}
|
||||
|
||||
void shutdown(asio::ip::tcp::socket::shutdown_type st, error_code& ec) {
|
||||
return _impl->shutdown(st, ec);
|
||||
}
|
||||
|
||||
endpoint_type remote_endpoint(error_code& ec) {
|
||||
return _impl->remote_endpoint(ec);
|
||||
}
|
||||
|
||||
template<typename SettableSocketOption>
|
||||
void set_option(const SettableSocketOption&, error_code&) {}
|
||||
|
||||
template <typename ConnectToken>
|
||||
decltype(auto) async_connect(
|
||||
const endpoint_type& ep, ConnectToken&& token
|
||||
) {
|
||||
|
||||
auto initiation = [this](auto handler, const endpoint_type& ep) {
|
||||
error_code ec;
|
||||
open(asio::ip::tcp::v4(), ec);
|
||||
|
||||
if (!ec)
|
||||
connect(ep, ec);
|
||||
|
||||
asio::post(get_executor(), asio::prepend(std::move(handler), ec));
|
||||
};
|
||||
|
||||
return async_initiate<ConnectToken, void (error_code)>(
|
||||
std::move(initiation), token, ep
|
||||
);
|
||||
}
|
||||
|
||||
template<typename ConstBufferSequence, typename WriteToken>
|
||||
decltype(auto) async_write_some(
|
||||
const ConstBufferSequence& buffers, WriteToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, size_t);
|
||||
|
||||
auto initiation = [this](
|
||||
auto handler, const ConstBufferSequence& buffers
|
||||
) {
|
||||
detail::write_op { _impl, std::move(handler) }.perform(buffers);
|
||||
};
|
||||
|
||||
return asio::async_initiate<WriteToken, Signature>(
|
||||
std::move(initiation), token, buffers
|
||||
);
|
||||
}
|
||||
|
||||
template<typename MutableBufferSequence, typename ReadToken>
|
||||
decltype(auto) async_read_some(
|
||||
const MutableBufferSequence& buffers,
|
||||
ReadToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, size_t);
|
||||
|
||||
auto initiation = [this](
|
||||
auto handler, const MutableBufferSequence& buffers
|
||||
) {
|
||||
detail::read_op { _impl, std::move(handler) }.perform(buffers);
|
||||
};
|
||||
|
||||
return asio::async_initiate<ReadToken, Signature>(
|
||||
std::move(initiation), token, buffers
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::test
|
||||
|
||||
#endif // ASYNC_MQTT5_TEST_TEST_STREAM_HPP
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <test_common/protocol_logging.hpp>
|
||||
|
||||
boost::unit_test::test_suite* init_tests(
|
||||
int /*argc*/, char* /*argv*/[]
|
||||
) {
|
||||
async_mqtt5::test::logging_enabled() = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return boost::unit_test::unit_test_main(&init_tests, argc, argv);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage: ./mqtt-test [boost test --arg=val]*
|
||||
* example: ./mqtt-test --log_level=test_suite
|
||||
*
|
||||
* all boost test parameters can be found here:
|
||||
* https://www.boost.org/doc/libs/1_82_0/libs/test/doc/html/boost_test/runtime_config/summary.html
|
||||
*/
|
||||
@@ -0,0 +1,576 @@
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <async_mqtt5.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
|
||||
#include "test_common/test_stream.hpp"
|
||||
#include "test_common/message_exchange.hpp"
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace async_mqtt5;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(framework, *boost::unit_test::disabled())
|
||||
|
||||
BOOST_AUTO_TEST_CASE(publish_qos_0) {
|
||||
using test::after;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
|
||||
// packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish_1 = encoders::encode_publish(
|
||||
65535, "t", "p_1", qos_e::at_most_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success {};
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(20ms))
|
||||
.expect(publish_1);
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_most_once>(
|
||||
"t", "p_1", retain_e::no, publish_props{},
|
||||
[&](error_code ec) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(1));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(two_publishes_qos_1_with_fail_on_write) {
|
||||
using test::after;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
constexpr int expected_handlers_called = 2;
|
||||
int handlers_called = 0;
|
||||
|
||||
// packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish_1 = encoders::encode_publish(
|
||||
65535, "t", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto puback_1 = encoders::encode_puback(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish_2 = encoders::encode_publish(
|
||||
65534, "t", "p_2", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto puback_2 = encoders::encode_puback(
|
||||
65534, reason_codes::success.value(), {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success {};
|
||||
error_code fail = asio::error::not_connected;
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(10ms))
|
||||
.expect(publish_1)
|
||||
.complete_with(fail, after(10ms))
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(10ms))
|
||||
.expect(publish_1, publish_2)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(puback_1, puback_2, after(20ms));
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t", "p_1", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t", "p_2", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(6));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(receive_publish_qos_2) {
|
||||
using test::after;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
std::string topic = "topic";
|
||||
std::string payload = "payload";
|
||||
|
||||
// packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish = encoders::encode_publish(
|
||||
65535, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto pubrec = encoders::encode_pubrec(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto pubrel = encoders::encode_pubrel(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto pubcomp = encoders::encode_pubcomp(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success {};
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(15ms))
|
||||
.send(publish, after(300ms))
|
||||
.expect(pubrec)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(pubrel, after(15ms))
|
||||
.expect(pubcomp)
|
||||
.complete_with(success, after(5ms));
|
||||
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_receive(
|
||||
[&](error_code ec, std::string rec_topic, std::string rec_payload, publish_props)
|
||||
{
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_EQUAL(topic, rec_topic);
|
||||
BOOST_CHECK_EQUAL(payload, rec_payload);
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(1));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(send_publish_qos_2_with_fail_on_read) {
|
||||
using test::after;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
|
||||
// packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish = encoders::encode_publish(
|
||||
65535, "t_1", "p_1", qos_e::exactly_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto pubrec = encoders::encode_pubrec(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto pubrel = encoders::encode_pubrel(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto pubcomp = encoders::encode_pubcomp(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success {};
|
||||
error_code fail = asio::error::not_connected;
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(20ms))
|
||||
.expect(publish)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(pubrec, after(25ms))
|
||||
.expect(pubrel)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(fail, after(10ms))
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(20ms))
|
||||
.expect(pubrel)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(pubcomp, after(20ms));
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::exactly_once>(
|
||||
"t_1", "p_1", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(7));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ordering_after_reconnect) {
|
||||
using test::after;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
constexpr int expected_handlers_called = 2;
|
||||
int handlers_called = 0;
|
||||
|
||||
// packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish_1 = encoders::encode_publish(
|
||||
65535, "t_1", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto publish_1_dup = encoders::encode_publish(
|
||||
65535, "t_1", "p_1", qos_e::at_least_once, retain_e::no, dup_e::yes, {}
|
||||
);
|
||||
auto puback = encoders::encode_puback(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish_2 = encoders::encode_publish(
|
||||
65534, "t_2", "p_2", qos_e::exactly_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto pubrec = encoders::encode_pubrec(
|
||||
65534, reason_codes::success.value(), {}
|
||||
);
|
||||
auto pubrel = encoders::encode_pubrel(
|
||||
65534, reason_codes::success.value(), {}
|
||||
);
|
||||
auto pubcomp = encoders::encode_pubcomp(
|
||||
65534, reason_codes::success.value(), {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success {};
|
||||
error_code fail = asio::error::not_connected;
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(20ms))
|
||||
.expect(publish_1, publish_2)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(pubrec, after(20ms))
|
||||
.expect(pubrel)
|
||||
.complete_with(fail, after(10ms))
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(15ms))
|
||||
.expect(pubrel, publish_1_dup)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(pubcomp, puback, after(20ms));
|
||||
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t_1", "p_1", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
c.async_publish<qos_e::exactly_once>(
|
||||
"t_2", "p_2", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(7));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(throttling) {
|
||||
using test::after;
|
||||
using std::chrono_literals::operator ""ms;
|
||||
|
||||
constexpr int expected_handlers_called = 3;
|
||||
int handlers_called = 0;
|
||||
|
||||
connack_props props;
|
||||
props[prop::receive_maximum] = 1;
|
||||
|
||||
//packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), props
|
||||
);
|
||||
auto publish_1 = encoders::encode_publish(
|
||||
65535, "t_1", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto publish_2 = encoders::encode_publish(
|
||||
65534, "t_1", "p_2", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto publish_3 = encoders::encode_publish(
|
||||
65533, "t_1", "p_3", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto puback_1 = encoders::encode_puback(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
auto puback_2 = encoders::encode_puback(
|
||||
65534, reason_codes::success.value(), {}
|
||||
);
|
||||
auto puback_3 = encoders::encode_puback(
|
||||
65533, reason_codes::success.value(), {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success {};
|
||||
error_code fail = asio::error::not_connected;
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(15ms))
|
||||
.expect(publish_1)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(puback_1, after(15ms))
|
||||
.expect(publish_2)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(puback_2, after(15ms))
|
||||
.expect(publish_3)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(puback_3, after(15ms));
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t_1", "p_1", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
BOOST_CHECK_EQUAL(handlers_called, 0);
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t_1", "p_2", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
BOOST_CHECK_EQUAL(handlers_called, 1);
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t_1", "p_3", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(!ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(!rc, rc.message());
|
||||
BOOST_CHECK_EQUAL(handlers_called, 2);
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(2));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_multiple_ops) {
|
||||
using test::after;
|
||||
using namespace std::chrono;
|
||||
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
|
||||
auto begin = high_resolution_clock::now();
|
||||
|
||||
// packets
|
||||
auto connect = encoders::encode_connect(
|
||||
"", std::nullopt, std::nullopt, 10, false, {}, std::nullopt
|
||||
);
|
||||
auto connack = encoders::encode_connack(
|
||||
false, reason_codes::success.value(), {}
|
||||
);
|
||||
auto publish_1 = encoders::encode_publish(
|
||||
65535, "t_1", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {}
|
||||
);
|
||||
auto puback = encoders::encode_puback(
|
||||
65535, reason_codes::success.value(), {}
|
||||
);
|
||||
|
||||
test::msg_exchange broker_side;
|
||||
error_code success{};
|
||||
error_code fail = asio::error::not_connected;
|
||||
|
||||
broker_side
|
||||
.expect(connect)
|
||||
.complete_with(success, after(10ms))
|
||||
.reply_with(connack, after(20ms))
|
||||
.expect(publish_1)
|
||||
.complete_with(success, after(10s))
|
||||
.reply_with(puback, after(10s));
|
||||
//.send(publish_1, after(10s));
|
||||
|
||||
asio::io_context ioc;
|
||||
auto executor = ioc.get_executor();
|
||||
asio::make_service<test::test_broker>(ioc, executor, std::move(broker_side));
|
||||
|
||||
using client_type = mqtt_client<test::test_stream>;
|
||||
client_type c(executor, "");
|
||||
c.brokers("127.0.0.1")
|
||||
.run();
|
||||
|
||||
c.async_publish<qos_e::at_least_once>(
|
||||
"t_1", "p_1", retain_e::no, publish_props{},
|
||||
[&](error_code ec, reason_code rc, auto) {
|
||||
BOOST_CHECK_MESSAGE(ec, ec.message());
|
||||
BOOST_CHECK_MESSAGE(rc, rc.message());
|
||||
++handlers_called;
|
||||
}
|
||||
);
|
||||
|
||||
asio::steady_timer timer(c.get_executor());
|
||||
timer.expires_after(std::chrono::seconds(2));
|
||||
timer.async_wait([&](auto) { c.cancel(); });
|
||||
|
||||
ioc.run();
|
||||
auto end = high_resolution_clock::now();
|
||||
auto duration = duration_cast<milliseconds>(end - begin);
|
||||
|
||||
BOOST_CHECK_MESSAGE(
|
||||
duration <= std::chrono::seconds(3),
|
||||
"The client did not cancel properly!"
|
||||
);
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <async_mqtt5.hpp>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
|
||||
#include <boost/beast/websocket.hpp>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(coroutine/*, *boost::unit_test::disabled()*/)
|
||||
|
||||
using namespace async_mqtt5;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template<typename StreamType, typename TlsContext>
|
||||
asio::awaitable<void> sanity_check(mqtt_client<StreamType, TlsContext>& c) {
|
||||
co_await c.template async_publish<qos_e::at_most_once>(
|
||||
"test/mqtt-test", "hello world with qos0!", retain_e::no, publish_props{},
|
||||
asio::use_awaitable
|
||||
);
|
||||
|
||||
auto [puback_rc, puback_props] = co_await c.template async_publish<qos_e::at_least_once>(
|
||||
"test/mqtt-test", "hello world with qos1!",
|
||||
retain_e::no, publish_props{},
|
||||
asio::use_awaitable
|
||||
);
|
||||
BOOST_CHECK(!puback_rc);
|
||||
|
||||
auto [pubcomp_rc, pubcomp_props] = co_await c.template async_publish<qos_e::exactly_once>(
|
||||
"test/mqtt-test", "hello world with qos2!",
|
||||
retain_e::no, publish_props{},
|
||||
asio::use_awaitable
|
||||
);
|
||||
BOOST_CHECK(!pubcomp_rc);
|
||||
|
||||
std::vector<subscribe_topic> topics;
|
||||
topics.push_back(subscribe_topic{
|
||||
"test/mqtt-test", {
|
||||
qos_e::exactly_once,
|
||||
subscribe_options::no_local_e::no,
|
||||
subscribe_options::retain_as_published_e::retain,
|
||||
subscribe_options::retain_handling_e::send
|
||||
}
|
||||
});
|
||||
|
||||
auto [sub_codes, sub_props] = co_await c.async_subscribe(
|
||||
topics, subscribe_props{}, asio::use_awaitable
|
||||
);
|
||||
BOOST_CHECK(!sub_codes[0]);
|
||||
auto [topic, payload, publish_props] = co_await c.async_receive(asio::use_awaitable);
|
||||
|
||||
auto [unsub_codes, unsub_props] = co_await c.async_unsubscribe(
|
||||
std::vector<std::string>{"test/mqtt-test"}, unsubscribe_props{},
|
||||
asio::use_awaitable
|
||||
);
|
||||
BOOST_CHECK(!unsub_codes[0]);
|
||||
|
||||
co_await c.async_disconnect(asio::use_awaitable);
|
||||
co_return;
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tcp_client_check) {
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = asio::ip::tcp::socket;
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("tcp-tester", "", "")
|
||||
.brokers("mqtt.mireo.local", 1883)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
asio::steady_timer timer(ioc);
|
||||
timer.expires_after(std::chrono::seconds(10));
|
||||
|
||||
timer.async_wait(
|
||||
[&](boost::system::error_code ec) {
|
||||
BOOST_CHECK_MESSAGE(ec, "Failed to receive all the expected replies!");
|
||||
c.cancel();
|
||||
ioc.stop();
|
||||
}
|
||||
);
|
||||
|
||||
co_spawn(ioc,
|
||||
[&]() -> asio::awaitable<void> {
|
||||
co_await sanity_check(c);
|
||||
timer.cancel();
|
||||
},
|
||||
asio::detached
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
// TODO: SSL
|
||||
|
||||
BOOST_AUTO_TEST_CASE(websocket_tcp_client_check) {
|
||||
asio::io_context ioc;
|
||||
|
||||
using stream_type = boost::beast::websocket::stream<
|
||||
asio::ip::tcp::socket
|
||||
>;
|
||||
|
||||
using client_type = mqtt_client<stream_type>;
|
||||
client_type c(ioc, "");
|
||||
|
||||
c.credentials("websocket-tcp-tester", "", "")
|
||||
.brokers("fcluster-5/mqtt", 8083)
|
||||
.will({ "test/mqtt-test", "i died", qos_e::at_least_once })
|
||||
.run();
|
||||
|
||||
asio::steady_timer timer(ioc);
|
||||
timer.expires_after(std::chrono::seconds(10));
|
||||
|
||||
timer.async_wait(
|
||||
[&](boost::system::error_code ec) {
|
||||
BOOST_CHECK_MESSAGE(ec, "Failed to receive all the expected replies!");
|
||||
c.cancel();
|
||||
ioc.stop();
|
||||
}
|
||||
);
|
||||
|
||||
co_spawn(ioc,
|
||||
[&]() -> asio::awaitable<void> {
|
||||
co_await sanity_check(c);
|
||||
timer.cancel();
|
||||
},
|
||||
asio::detached
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/impl/client_service.hpp>
|
||||
#include <async_mqtt5/impl/publish_send_op.hpp>
|
||||
|
||||
#include "test_common/test_service.hpp"
|
||||
|
||||
using namespace async_mqtt5;
|
||||
|
||||
namespace async_mqtt5::client {
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const error& err) {
|
||||
os << client_error_to_string(err);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5::client
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(publish_send_op/*, *boost::unit_test::disabled()*/)
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename TlsContext = std::monostate
|
||||
>
|
||||
class overrun_client : public detail::client_service<StreamType, TlsContext> {
|
||||
public:
|
||||
overrun_client(const asio::any_io_executor& ex, const std::string& cnf) :
|
||||
detail::client_service<StreamType, TlsContext>(ex, cnf)
|
||||
{}
|
||||
|
||||
uint16_t allocate_pid() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_pid_overrun) {
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
|
||||
asio::io_context ioc;
|
||||
using client_service_type = overrun_client<asio::ip::tcp::socket>;
|
||||
auto svc_ptr = std::make_shared<client_service_type>(ioc.get_executor(), "");
|
||||
|
||||
auto handler = [&](error_code ec, reason_code rc, puback_props) {
|
||||
++handlers_called;
|
||||
BOOST_CHECK_EQUAL(ec, client::error::pid_overrun);
|
||||
BOOST_CHECK_EQUAL(rc, reason_codes::empty);
|
||||
};
|
||||
|
||||
detail::publish_send_op<
|
||||
client_service_type, decltype(handler), qos_e::at_least_once
|
||||
> { svc_ptr, std::move(handler) }
|
||||
.perform(
|
||||
"test", "payload", retain_e::no, {}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_publish_immediate_cancellation) {
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
|
||||
asio::io_context ioc;
|
||||
using client_service_type = test::test_service<asio::ip::tcp::socket>;
|
||||
auto svc_ptr = std::make_shared<client_service_type>(ioc.get_executor());
|
||||
asio::cancellation_signal cancel_signal;
|
||||
|
||||
auto h = [&](error_code ec, reason_code rc, puback_props) {
|
||||
++handlers_called;
|
||||
BOOST_CHECK_EQUAL(ec, asio::error::operation_aborted);
|
||||
BOOST_CHECK_EQUAL(rc, reason_codes::empty);
|
||||
};
|
||||
|
||||
auto handler = asio::bind_cancellation_slot(cancel_signal.slot(), std::move(h));
|
||||
|
||||
detail::publish_send_op<
|
||||
client_service_type, decltype(handler), qos_e::at_least_once
|
||||
> { svc_ptr, std::move(handler) }
|
||||
.perform(
|
||||
"test", "payload", retain_e::no, {}
|
||||
);
|
||||
|
||||
cancel_signal.emit(asio::cancellation_type::terminal);
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_publish_cancellation) {
|
||||
constexpr int expected_handlers_called = 1;
|
||||
int handlers_called = 0;
|
||||
|
||||
asio::io_context ioc;
|
||||
using client_service_type = test::test_service<asio::ip::tcp::socket>;
|
||||
auto svc_ptr = std::make_shared<client_service_type>(ioc.get_executor());
|
||||
asio::cancellation_signal cancel_signal;
|
||||
|
||||
auto h = [&](error_code ec, reason_code rc, puback_props) {
|
||||
++handlers_called;
|
||||
BOOST_CHECK_EQUAL(ec, asio::error::operation_aborted);
|
||||
BOOST_CHECK_EQUAL(rc, reason_codes::empty);
|
||||
};
|
||||
|
||||
auto handler = asio::bind_cancellation_slot(cancel_signal.slot(), std::move(h));
|
||||
|
||||
asio::steady_timer timer(ioc.get_executor());
|
||||
timer.expires_after(std::chrono::milliseconds(60));
|
||||
timer.async_wait(
|
||||
[&cancel_signal](error_code) {
|
||||
cancel_signal.emit(asio::cancellation_type::terminal);
|
||||
}
|
||||
);
|
||||
|
||||
detail::publish_send_op<
|
||||
client_service_type, decltype(handler), qos_e::at_least_once
|
||||
> { svc_ptr, std::move(handler) }
|
||||
.perform(
|
||||
"test", "payload", retain_e::no, {}
|
||||
);
|
||||
|
||||
ioc.run();
|
||||
BOOST_CHECK_EQUAL(
|
||||
handlers_called, expected_handlers_called
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@@ -0,0 +1,298 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/internal/codecs/message_encoders.hpp>
|
||||
|
||||
using namespace async_mqtt5;
|
||||
using byte_citer = detail::byte_citer;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(serialization/*, *boost::unit_test::disabled()*/)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_connect) {
|
||||
// testing variables
|
||||
std::string_view client_id = "async_mqtt_client_id";
|
||||
std::string_view uname = "username";
|
||||
std::optional<std::string_view> password = std::nullopt;
|
||||
uint16_t keep_alive = 60;
|
||||
bool clean_start = true;
|
||||
std::string will_topic = "will_topic";
|
||||
std::string will_message = "will_message";
|
||||
|
||||
connect_props cprops;
|
||||
cprops[prop::session_expiry_interval] = 29;
|
||||
cprops[prop::user_property].emplace_back("connect user prop");
|
||||
|
||||
will w{ will_topic, will_message };
|
||||
w[prop::content_type] = "will content type";
|
||||
w[prop::response_topic] = "will response topic";
|
||||
w[prop::user_property].emplace_back("first user prop");
|
||||
w[prop::user_property].emplace_back("second user prop");
|
||||
std::optional<will> will_opt { std::move(w) };
|
||||
|
||||
auto msg = encoders::encode_connect(
|
||||
client_id, uname, password, keep_alive, clean_start, cprops, will_opt
|
||||
);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing CONNECT fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto rv = decoders::decode_connect(remain_length, it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing CONNECT failed.");
|
||||
|
||||
const auto& [client_id_, uname_, password_, keep_alive_, clean_start_, _, w_] = *rv;
|
||||
BOOST_CHECK_EQUAL(client_id_, client_id);
|
||||
BOOST_CHECK(uname_);
|
||||
BOOST_CHECK_EQUAL(*uname_, uname);
|
||||
BOOST_CHECK(password_ == password);
|
||||
BOOST_CHECK_EQUAL(keep_alive_, keep_alive);
|
||||
BOOST_CHECK_EQUAL(clean_start_, clean_start);
|
||||
BOOST_CHECK(w_);
|
||||
BOOST_CHECK_EQUAL((*w_).topic(), will_topic);
|
||||
BOOST_CHECK_EQUAL((*w_).message(), will_message);
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_connack) {
|
||||
// testing variables
|
||||
bool session_present = true;
|
||||
uint8_t reason_code = 0x89;
|
||||
bool wildcard_sub = true;
|
||||
|
||||
connack_props cap;
|
||||
cap[prop::wildcard_subscription_available] = wildcard_sub;
|
||||
auto msg = encoders::encode_connack(session_present, reason_code, cap);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing CONNACK fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto rv = decoders::decode_connack(remain_length, it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing CONNACK failed.");
|
||||
|
||||
const auto& [session_present_, reason_code_, caprops] = *rv;
|
||||
BOOST_CHECK_EQUAL(session_present_, session_present);
|
||||
BOOST_CHECK_EQUAL(reason_code_, reason_code);
|
||||
BOOST_CHECK_EQUAL(*caprops[prop::wildcard_subscription_available], wildcard_sub);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_publish) {
|
||||
// testing variables
|
||||
uint16_t packet_id = 31283;
|
||||
std::string_view topic = "publish_topic";
|
||||
std::string_view payload = "This is some payload I am publishing!";
|
||||
uint8_t message_expiry = 70;
|
||||
std::string content_type = "application/octet-stream";
|
||||
std::string publish_prop_1 = "first publish prop";
|
||||
std::string publish_prop_2 = "second publish prop";
|
||||
|
||||
publish_props pp;
|
||||
pp[prop::message_expiry_interval] = message_expiry;
|
||||
pp[prop::content_type] = content_type;
|
||||
pp[prop::user_property].emplace_back(publish_prop_1);
|
||||
pp[prop::user_property].emplace_back(publish_prop_2);
|
||||
|
||||
auto msg = encoders::encode_publish(
|
||||
packet_id, topic, payload,
|
||||
qos_e::at_least_once, retain_e::yes, dup_e::no,
|
||||
pp
|
||||
);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing PUBLISH fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto rv = decoders::decode_publish(control_byte, remain_length, it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing PUBLISH failed.");
|
||||
|
||||
const auto& [topic_, packet_id_, flags, pprops, payload_] = *rv;
|
||||
BOOST_CHECK(packet_id);
|
||||
BOOST_CHECK_EQUAL(*packet_id_, packet_id);
|
||||
BOOST_CHECK_EQUAL(topic_, topic);
|
||||
BOOST_CHECK_EQUAL(payload_, payload_);
|
||||
BOOST_CHECK_EQUAL(*pprops[prop::message_expiry_interval], message_expiry);
|
||||
BOOST_CHECK_EQUAL(*pprops[prop::content_type], content_type);
|
||||
BOOST_CHECK_EQUAL(pprops[prop::user_property][0], publish_prop_1);
|
||||
BOOST_CHECK_EQUAL(pprops[prop::user_property][1], publish_prop_2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_puback) {
|
||||
// testing variables
|
||||
uint16_t packet_id = 9199;
|
||||
uint8_t reason_code = 0x93;
|
||||
std::string reason_string = "PUBACK reason string";
|
||||
std::string user_prop = "PUBACK user prop";
|
||||
|
||||
puback_props pp;
|
||||
pp[prop::reason_string] = reason_string;
|
||||
pp[prop::user_property].emplace_back(user_prop);
|
||||
|
||||
auto msg = encoders::encode_puback(packet_id, reason_code, pp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing PUBACK fixed header failed.");
|
||||
|
||||
auto packet_id_ = decoders::decode_packet_id(it);
|
||||
BOOST_CHECK(packet_id);
|
||||
BOOST_CHECK_EQUAL(*packet_id_, packet_id);
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto rv = decoders::decode_puback(remain_length, it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing PUBACK failed.");
|
||||
|
||||
const auto& [reason_code_, pprops] = *rv;
|
||||
BOOST_CHECK_EQUAL(reason_code_, reason_code);
|
||||
BOOST_CHECK_EQUAL(*pprops[prop::reason_string], reason_string);
|
||||
BOOST_CHECK_EQUAL(pprops[prop::user_property][0], user_prop);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_subscribe) {
|
||||
subscribe_props sp;
|
||||
std::vector<subscribe_topic> filters {
|
||||
{ "subscribe topic", { qos_e::at_least_once } }
|
||||
};
|
||||
uint16_t packet_id = 65535;
|
||||
|
||||
auto msg = encoders::encode_subscribe(packet_id, filters, sp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing SUBSCRIBE fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto packet_id_ = decoders::decode_packet_id(it);
|
||||
BOOST_CHECK(packet_id);
|
||||
BOOST_CHECK_EQUAL(*packet_id_, packet_id);
|
||||
auto rv = decoders::decode_subscribe(remain_length - sizeof(uint16_t), it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing SUBSCRIBE failed.");
|
||||
|
||||
const auto& [props_, filters_] = *rv;
|
||||
BOOST_CHECK_EQUAL(filters[0].topic_filter, std::get<0>(filters_[0]));
|
||||
//TODO: sub options
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_suback) {
|
||||
//testing variables
|
||||
suback_props sp;
|
||||
std::vector<uint8_t> reason_codes { 48, 28 };
|
||||
uint16_t packet_id = 142;
|
||||
|
||||
auto msg = encoders::encode_suback(packet_id, reason_codes, sp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing SUBACK fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto packet_id_ = decoders::decode_packet_id(it);
|
||||
BOOST_CHECK(packet_id);
|
||||
BOOST_CHECK_EQUAL(*packet_id_, packet_id);
|
||||
auto rv = decoders::decode_suback(remain_length - sizeof(uint16_t), it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing SUBACK failed.");
|
||||
|
||||
const auto& [sp_, reason_codes_] = *rv;
|
||||
BOOST_CHECK(reason_codes_ == reason_codes);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_unsubscribe) {
|
||||
// testing variables
|
||||
unsubscribe_props sp;
|
||||
std::vector<std::string> topics { "first topic", "second/topic" };
|
||||
uint16_t packet_id = 14423;
|
||||
|
||||
auto msg = encoders::encode_unsubscribe(packet_id, topics, sp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing UNSUBSCRIBE fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto packet_id_ = decoders::decode_packet_id(it);
|
||||
BOOST_CHECK(packet_id);
|
||||
BOOST_CHECK_EQUAL(*packet_id_, packet_id);
|
||||
auto rv = decoders::decode_unsubscribe(remain_length - sizeof(uint16_t), it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing UNSUBSCRIBE failed.");
|
||||
|
||||
const auto& [props_, topics_] = *rv;
|
||||
BOOST_CHECK(topics_ == topics);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_unsuback) {
|
||||
// testing variables
|
||||
std::string reason_string = "some unsuback reason string";
|
||||
unsuback_props sp;
|
||||
sp[prop::reason_string] = reason_string;
|
||||
std::vector<uint8_t> reason_codes { 48, 28 };
|
||||
uint16_t packet_id = 42;
|
||||
|
||||
auto msg = encoders::encode_unsuback(packet_id, reason_codes, sp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing UNSUBACK fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto packet_id_ = decoders::decode_packet_id(it);
|
||||
BOOST_CHECK(packet_id);
|
||||
BOOST_CHECK_EQUAL(*packet_id_, packet_id);
|
||||
auto rv = decoders::decode_unsuback(remain_length - sizeof(uint16_t), it);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing UNSUBACK failed.");
|
||||
|
||||
const auto& [props_, reason_codes_] = *rv;
|
||||
BOOST_CHECK(reason_codes_ == reason_codes);
|
||||
BOOST_CHECK_EQUAL(*props_[prop::reason_string], reason_string);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_disconnect) {
|
||||
// testing variables
|
||||
uint8_t reason_code = 0;
|
||||
std::string user_property = "DISCONNECT user property";
|
||||
disconnect_props sp;
|
||||
sp[prop::user_property].emplace_back(user_property);
|
||||
|
||||
auto msg = encoders::encode_disconnect(reason_code, sp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing DISCONNECT fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto rv = decoders::decode_disconnect(remain_length, it);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing DISCONNECT failed.");
|
||||
|
||||
const auto& [reason_code_, props_] = *rv;
|
||||
BOOST_CHECK_EQUAL(reason_code_, reason_code);
|
||||
BOOST_CHECK_EQUAL(props_[prop::user_property][0], user_property);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_auth) {
|
||||
// testing variables
|
||||
uint8_t reason_code = 0x18;
|
||||
std::string reason_string = "AUTH reason";
|
||||
std::string user_property = "AUTH user propety";
|
||||
auth_props sp;
|
||||
sp[prop::reason_string] = reason_string;
|
||||
sp[prop::user_property].emplace_back(user_property);
|
||||
|
||||
auto msg = encoders::encode_auth(reason_code, sp);
|
||||
|
||||
byte_citer it = msg.cbegin(), last = msg.cend();
|
||||
auto header = decoders::decode_fixed_header(it, last);
|
||||
BOOST_CHECK_MESSAGE(header, "Parsing AUTH fixed header failed.");
|
||||
|
||||
const auto& [control_byte, remain_length] = *header;
|
||||
auto rv = decoders::decode_auth(remain_length, it);
|
||||
BOOST_CHECK_MESSAGE(rv, "Parsing AUTH failed.");
|
||||
|
||||
const auto& [reason_code_, props_] = *rv;
|
||||
BOOST_CHECK_EQUAL(reason_code_, reason_code);
|
||||
BOOST_CHECK_EQUAL(*props_[prop::reason_string], reason_string);
|
||||
BOOST_CHECK_EQUAL(props_[prop::user_property][0], user_property);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33110.190
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mqtt-client", "mqtt-client.vcxproj", "{303BA426-05B5-47FB-9F00-E932257FB28F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mqtt-test", "test/mqtt-test.vcxproj", "{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Debug|x64.Build.0 = Debug|x64
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Debug|x86.Build.0 = Debug|Win32
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Release|x64.ActiveCfg = Release|x64
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Release|x64.Build.0 = Release|x64
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Release|x86.ActiveCfg = Release|Win32
|
||||
{303BA426-05B5-47FB-9F00-E932257FB28F}.Release|x86.Build.0 = Release|Win32
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Debug|x64.Build.0 = Debug|x64
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Debug|x86.Build.0 = Debug|Win32
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Release|x64.ActiveCfg = Release|x64
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Release|x64.Build.0 = Release|x64
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Release|x86.ActiveCfg = Release|Win32
|
||||
{48DEAF83-BA87-4FDE-8A4C-A539BA44A479}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E5126E4A-FF26-4E63-BC54-CE092A8E7966}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\async_mqtt5.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\async_mutex.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\async_traits.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\cancellable_handler.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\control_packet.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\internal_types.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\ring_buffer.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\spinlock.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\error.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\assemble_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\async_sender.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\autoconnect_stream.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\client_service.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\connect_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\disconnect_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\endpoints.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\memory.h" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\memory_resource.h" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\string.h" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\vector.h" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\base_decoders.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\base_encoders.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\message_decoders.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\message_encoders.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\traits.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\ping_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\publish_rec_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\publish_send_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\read_message_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\read_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\reconnect_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\replies.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\sentry_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\subscribe_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\unsubscribe_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\write_op.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\property_types.hpp" />
|
||||
<ClInclude Include="..\include\async_mqtt5\types.hpp" />
|
||||
<ClInclude Include="..\include\mqtt_client.hpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\example\openssl-tls.cpp" />
|
||||
<ClCompile Include="..\example\src\run_examples.cpp" />
|
||||
<ClCompile Include="..\example\tcp.cpp" />
|
||||
<ClCompile Include="..\example\websocket-tcp.cpp" />
|
||||
<ClCompile Include="..\example\websocket-tls.cpp" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{303ba426-05b5-47fb-9f00-e932257fb28f}</ProjectGuid>
|
||||
<RootNamespace>mqttclient</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NOMINMAX;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;WIN32_LEAN_AND_MEAN;WIN32;_WIN32_WINDOWS=0x0601;_WIN32_WINNT=0x0601;_DEBUG;_CONSOLE;_FILE_OFFSET_BITS=64;BOOST_ALL_NO_LIB;BOOST_UUID_RANDOM_PROVIDER_FORCE_WINCRYPT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>..\include;$(DevRoot)3rdParty\openssl\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>crypt32.lib;openssl.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(DevRoot)Components\Bin\libd\x64\MT</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NOMINMAX;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;WIN32_LEAN_AND_MEAN;WIN32;_WIN32_WINDOWS=0x0601;_WIN32_WINNT=0x0601;_CONSOLE;NDEBUG;_FILE_OFFSET_BITS=64;BOOST_ALL_NO_LIB;BOOST_UUID_RANDOM_PROVIDER_FORCE_WINCRYPT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>..\include;$(DevRoot)3rdParty\openssl\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>crypt32.lib;openssl.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(DevRoot)Components\Bin\libd\x64\MT</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\example">
|
||||
<UniqueIdentifier>{fc537c67-a947-4840-80e0-6fe3f3d5d460}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\include">
|
||||
<UniqueIdentifier>{b786d33e-710a-4a1c-bc48-a14f6d79399c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\include\impl">
|
||||
<UniqueIdentifier>{0ce770cf-e2fa-429c-94f5-0e70835bed03}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\include\impl\internal">
|
||||
<UniqueIdentifier>{ec0b6550-a767-411d-9cea-1e33bcd63aab}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\include\impl\internal\codecs">
|
||||
<UniqueIdentifier>{e3af9391-bfab-4e35-aad7-0e5b8541ac1c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\include\impl\internal\alloc">
|
||||
<UniqueIdentifier>{e83887a6-c73a-45ca-b224-45de8f660c74}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\include\detail">
|
||||
<UniqueIdentifier>{ab9e608c-0bf9-4e0a-8dfe-1e0a2e040718}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\async_mutex.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\async_traits.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\cancellable_handler.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\control_packet.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\internal_types.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\ring_buffer.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\detail\spinlock.hpp">
|
||||
<Filter>Header Files\include\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\memory.h">
|
||||
<Filter>Header Files\include\impl\internal\alloc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\memory_resource.h">
|
||||
<Filter>Header Files\include\impl\internal\alloc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\string.h">
|
||||
<Filter>Header Files\include\impl\internal\alloc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\alloc\vector.h">
|
||||
<Filter>Header Files\include\impl\internal\alloc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\base_decoders.hpp">
|
||||
<Filter>Header Files\include\impl\internal\codecs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\base_encoders.hpp">
|
||||
<Filter>Header Files\include\impl\internal\codecs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\message_decoders.hpp">
|
||||
<Filter>Header Files\include\impl\internal\codecs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\message_encoders.hpp">
|
||||
<Filter>Header Files\include\impl\internal\codecs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\internal\codecs\traits.hpp">
|
||||
<Filter>Header Files\include\impl\internal\codecs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\assemble_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\async_sender.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\autoconnect_stream.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\client_service.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\connect_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\disconnect_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\endpoints.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\ping_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\publish_rec_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\publish_send_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\read_message_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\read_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\reconnect_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\replies.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\sentry_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\subscribe_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\unsubscribe_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\impl\write_op.hpp">
|
||||
<Filter>Header Files\include\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\property_types.hpp">
|
||||
<Filter>Header Files\include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\types.hpp">
|
||||
<Filter>Header Files\include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5\error.hpp">
|
||||
<Filter>Header Files\include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\mqtt_client.hpp">
|
||||
<Filter>Header Files\include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\async_mqtt5.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\example\openssl-tls.cpp">
|
||||
<Filter>Source Files\example</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\example\tcp.cpp">
|
||||
<Filter>Source Files\example</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\example\websocket-tcp.cpp">
|
||||
<Filter>Source Files\example</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\example\websocket-tls.cpp">
|
||||
<Filter>Source Files\example</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\example\src\run_examples.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\test\unit\src\run_tests.cpp" />
|
||||
<ClCompile Include="..\..\test\unit\test\client_broker.cpp" />
|
||||
<ClCompile Include="..\..\test\unit\test\coroutine.cpp" />
|
||||
<ClCompile Include="..\..\test\unit\test\publish_send_op.cpp" />
|
||||
<ClCompile Include="..\..\test\unit\test\serialization.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\delayed_op.hpp" />
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\message_exchange.hpp" />
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\packet_util.hpp" />
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\protocol_logging.hpp" />
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\test_broker.hpp" />
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\test_service.hpp" />
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\test_stream.hpp" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{48deaf83-ba87-4fde-8a4c-a539ba44a479}</ProjectGuid>
|
||||
<RootNamespace>mqtttest</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NOMINMAX;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;WIN32_LEAN_AND_MEAN;WIN32;_WIN32_WINDOWS=0x0601;_WIN32_WINNT=0x0601;BOOST_TEST_NO_MAIN=1;_DEBUG;_CONSOLE;_FILE_OFFSET_BITS=64;BOOST_ALL_NO_LIB;BOOST_UUID_RANDOM_PROVIDER_FORCE_WINCRYPT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<ExceptionHandling>Async</ExceptionHandling>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>../../include;../../test/unit/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>crypt32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NOMINMAX;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;WIN32_LEAN_AND_MEAN;WIN32;_WIN32_WINDOWS=0x0601;_WIN32_WINNT=0x0601;BOOST_TEST_NO_MAIN=1;NDEBUG;_CONSOLE;_FILE_OFFSET_BITS=64;BOOST_ALL_NO_LIB;BOOST_UUID_RANDOM_PROVIDER_FORCE_WINCRYPT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<ExceptionHandling>Async</ExceptionHandling>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>../../include;../../test/unit/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>crypt32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\test">
|
||||
<UniqueIdentifier>{237ad32b-00b2-42f0-8b54-e4f62a437742}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\test_common">
|
||||
<UniqueIdentifier>{6361a7f6-7954-4ea3-a2a7-f3b15537a3ac}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\test\unit\test\client_broker.cpp">
|
||||
<Filter>Source Files\test</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\test\unit\test\coroutine.cpp">
|
||||
<Filter>Source Files\test</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\test\unit\test\publish_send_op.cpp">
|
||||
<Filter>Source Files\test</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\test\unit\test\serialization.cpp">
|
||||
<Filter>Source Files\test</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\test\unit\src\run_tests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\delayed_op.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\message_exchange.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\packet_util.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\protocol_logging.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\test_broker.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\test_service.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\test\unit\include\test_common\test_stream.hpp">
|
||||
<Filter>Header Files\test_common</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user