[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:
Korina Šimičević
2023-10-05 13:59:32 +02:00
commit 2d957cd46f
67 changed files with 12627 additions and 0 deletions

78
SConscript Normal file
View File

@@ -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,
)

282
example/openssl-tls.cpp Normal file
View File

@@ -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();
}

View File

@@ -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
example/tcp.cpp Normal file
View File

@@ -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);
}

173
example/websocket-tcp.cpp Normal file
View File

@@ -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);
}

250
example/websocket-tls.cpp Normal file
View File

@@ -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);
}

9
include/async_mqtt5.hpp Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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);
}

237
test/experimental/mutex.cpp Normal file
View File

@@ -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);
});
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

41
win/mqtt-client.sln Normal file
View File

@@ -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

189
win/mqtt-client.vcxproj Normal file
View File

@@ -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>

View File

@@ -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>

158
win/test/mqtt-test.vcxproj Normal file
View File

@@ -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>

View File

@@ -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>