diff --git a/README.md b/README.md index 4bafbcc..0b68d57 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -Async.MQTT5: A C++17 MQTT client based on Boost.Asio +Boost.MQTT5: A C++17 MQTT client based on Boost.Asio =============================== Branch | Windows/Linux Build | Coverage | Documentation | -------|---------------------|----------|---------------| [`master`](https://github.com/mireo/async-mqtt5/tree/master) | [![build status](https://github.com/mireo/async-mqtt5/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/mireo/async-mqtt5/actions/workflows/ci.yml) | [![codecov](https://codecov.io/gh/mireo/async-mqtt5/branch/master/graph/badge.svg)](https://codecov.io/gh/mireo/async-mqtt5/branch/master) | [Documentation](https://spacetime.mireo.com/async-mqtt5/) -Async.MQTT5 is a professional, industrial-grade C++17 client built on [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio.html). This Client is designed for publishing or receiving messages from an MQTT 5.0 compatible Broker. Async.MQTT5 represents a comprehensive implementation of the MQTT 5.0 protocol standard, offering full support for publishing or receiving messages with QoS 0, 1, and 2. +Boost.MQTT5 is a professional, industrial-grade C++17 client built on [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio.html). This Client is designed for publishing or receiving messages from an MQTT 5.0 compatible Broker. Boost.MQTT5 represents a comprehensive implementation of the MQTT 5.0 protocol standard, offering full support for publishing or receiving messages with QoS 0, 1, and 2. -~~Our clear intention is to include the Async.MQTT5 library into [Boost](https://www.boost.org/). We are actively working on it.~~\ -As of October 2024, the Async.MQTT5 library has been **conditionally ACCEPTED** into [Boost](https://www.boost.org/). +~~Our clear intention is to include the Boost.MQTT5 library into [Boost](https://www.boost.org/). We are actively working on it.~~\ +As of October 2024, the Boost.MQTT5 library has been **conditionally ACCEPTED** into [Boost](https://www.boost.org/). Motivation --------- - The [MQTT](https://mqtt.org/) protocol is widely used for communication in various real-world scenarios, primarily as a reliable communication protocol for data transfer to and from IoT devices. While the MQTT protocol is relatively straightforward, integrating it into an application can be complex, primarily due to the challenging implementation of message retransmission after a disconnect/reconnect sequence. To address these challenges and simplify MQTT integration once and for all, Async.MQTT5 was designed with the following core objectives in mind: + The [MQTT](https://mqtt.org/) protocol is widely used for communication in various real-world scenarios, primarily as a reliable communication protocol for data transfer to and from IoT devices. While the MQTT protocol is relatively straightforward, integrating it into an application can be complex, primarily due to the challenging implementation of message retransmission after a disconnect/reconnect sequence. To address these challenges and simplify MQTT integration once and for all, Boost.MQTT5 was designed with the following core objectives in mind: Objective | Description | ----------|---------| @@ -22,7 +22,7 @@ Complete adherence to the Boost.Asio's universal asynchronous model | The interf When to Use --------- - Async.MQTT5 might be suitable for you if any of the following statements is true: + Boost.MQTT5 might be suitable for you if any of the following statements is true: - Your application uses Boost.Asio and requires integrating an MQTT Client. - You require asynchronous access to an MQTT Broker. @@ -35,17 +35,17 @@ It may not be suitable for you if: Features --------- -Async.MQTT5 is a library designed with the core belief that users should focus solely on their application logic, not the network complexities. +Boost.MQTT5 is a library designed with the core belief that users should focus solely on their application logic, not the network complexities. The library attempts to embody this belief with a range of key features designed to elevate the development experience: Feature | Description | --------|-------------| -**Complete TCP, TLS/SSL, and WebSocket support** | The MQTT protocol requires an underlying network protocol that provides ordered, lossless, bi-directional connection (stream). Users can customize this stream through a template parameter. The Async.MQTT5 library has been tested with the most used transport protocols: TCP/IP using `boost::asio::ip::tcp::socket`, TLS/SSL using `boost::asio::ssl::stream`, WebSocket/TCP and WebSocket/TLS using `boost::beast::websocket::stream`). | +**Complete TCP, TLS/SSL, and WebSocket support** | The MQTT protocol requires an underlying network protocol that provides ordered, lossless, bi-directional connection (stream). Users can customize this stream through a template parameter. The Boost.MQTT5 library has been tested with the most used transport protocols: TCP/IP using `boost::asio::ip::tcp::socket`, TLS/SSL using `boost::asio::ssl::stream`, WebSocket/TCP and WebSocket/TLS using `boost::beast::websocket::stream`). | **Automatic reconnect and retry mechanism** | Automatically handles connection loss, backoffs, reconnections, and message transmissions. Automating these processes enables users to focus entirely on the application's functionality. See [Built-in Auto-Reconnect and Retry Mechanism](https://spacetime.mireo.com/async-mqtt5/async_mqtt5/auto_reconnect.html). | **Prioritised efficiency** | Utilises network and memory resources as efficiently as possible. | **Minimal memory footprint** | Ensures optimal performance in resource-constrained environments typical of IoT devices. | **Completion token** | All asynchronous functions are implemented using Boost.Asio's universal asynchronous model and support CompletionToken. This allows versatile usage with callbacks, coroutines, and futures. | -**Custom allocators** | Support for custom allocators allows extra flexibility and control over the memory resources. Async.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation. | +**Custom allocators** | Support for custom allocators allows extra flexibility and control over the memory resources. Boost.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation. | **Per-operation cancellation** | All asynchronous operations support individual, targeted cancellation as per Asio's [Per-Operation Cancellation](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview/core/cancellation.html). This enables all asynchronous functions to be used in [Parallel Operations](https://www.boost.org/doc/libs/1_86_0/doc/html/boost_asio/overview/composition/parallel_group.html), which is especially beneficial for executing operations that require a timeout mechanism. **Full implementation of MQTT 5.0 specification** | Ensures full compatibility with [MQTT 5.0 standard](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html). This latest version introduces essential features that enhance system robustness, including advanced error-handling mechanisms, session and message expiry, and other improvements designed to support modern IoT use cases. | **Support for QoS 0, QoS 1, and QoS 2**| Fully implements all quality-of-service levels defined by the MQTT protocol to match different reliability needs in message delivery. | @@ -55,12 +55,12 @@ Feature | Description | Getting Started --------- -Async.MQTT5 is a header-only library. To use Async.MQTT5 it requires the following: +Boost.MQTT5 is a header-only library. To use Boost.MQTT5 it requires the following: - **C++17 capable compiler** - **Boost 1.82 or later**. In addition to Asio, we use other header-only libraries. - **OpenSSL**. If you require an SSL connection by using [boost::asio::ssl::stream](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/reference/ssl__stream.html). -Async.MQTT5 has been tested with the following compilers: +Boost.MQTT5 has been tested with the following compilers: - clang 12.0 - 18.0 (Linux) - GCC 9 - 14 (Linux) - MSVC 14.37 - Visual Studio 2022 (Windows) @@ -70,7 +70,7 @@ Using the Library 1. Download [Boost](https://www.boost.org/users/download/), and add it to your include path. 2. If you use SSL, download [OpenSSL](https://www.openssl.org/), link the library and add it to your include path. -3. Add Async.MQTT5's `include` folder to your include path. +3. Add Boost.MQTT5's `include` folder to your include path. You can compile the example below with the following command line on Linux: @@ -83,28 +83,28 @@ The following example illustrates a scenario of configuring a Client and publish "Hello World!" Application Message with `QoS` 0. ```cpp -#include +#include +#include #include #include #include -#include -#include +#include int main() { boost::asio::io_context ioc; - async_mqtt5::mqtt_client c(ioc); + boost::mqtt5::mqtt_client c(ioc); c.brokers("", 1883) .credentials("", "", "") .async_run(boost::asio::detached); - c.async_publish( + c.async_publish( "", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, - [&c](async_mqtt5::error_code ec) { + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, + [&c](boost::mqtt5::error_code ec) { std::cout << ec.message() << std::endl; c.async_disconnect(boost::asio::detached); // disconnect and close the Client } diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk index 4267dc5..e536b62 100644 --- a/doc/qbk/00_main.qbk +++ b/doc/qbk/00_main.qbk @@ -4,7 +4,7 @@ (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[library Async.MQTT5: a C++17 MQTT client +[library Boost.MQTT5: a C++17 MQTT client [quickbook 1.7] [copyright 2023-2024 Mireo] [id async_mqtt5] @@ -74,7 +74,7 @@ [/ MQTT ] [def __MQTT__ [@https://mqtt.org/ MQTT]] -[def __Self__ [@https://github.com/mireo/async-mqtt5/ Async.MQTT5]] +[def __Self__ [@https://github.com/mireo/async-mqtt5/ Boost.MQTT5]] [def __Client__ [reflink2 mqtt_client `mqtt_client`]] [def __UTF8_STRING_PAIR__ [mqttlink 3901013 `UTF-8 String Pair`]] diff --git a/doc/qbk/01_intro.qbk b/doc/qbk/01_intro.qbk index a6d5c75..0dda4d4 100644 --- a/doc/qbk/01_intro.qbk +++ b/doc/qbk/01_intro.qbk @@ -70,7 +70,7 @@ The library attempts to embody this belief with a range of key features designed [ The MQTT protocol requires an underlying network protocol that provides ordered, lossless, bi-directional connection (stream). Users can customize this stream through a template parameter. - The Async.MQTT5 library has been tested with the most used transport protocols: + The Boost.MQTT5 library has been tested with the most used transport protocols: TCP/IP using `boost::asio::ip::tcp::socket`, TLS/SSL using `boost::asio::ssl::stream`, WebSocket/TCP and WebSocket/TLS using `boost::beast::websocket::stream`). ] ] @@ -101,7 +101,7 @@ The library attempts to embody this belief with a range of key features designed [[*Custom allocators]] [ Support for custom allocators allows extra flexibility and control over the memory resources. - Async.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation. + Boost.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation. ] ] [ @@ -166,7 +166,7 @@ __Self__ has been tested with the following compilers: [heading Using the Library] # Download [@https://www.boost.org/users/download/ Boost], and add it to your include path. # If you use SSL, download [@https://www.openssl.org/ OpenSSL], link the library and add it to your include path. -# Add Async.MQTT5's `include` folder to your include path. +# Add Boost.MQTT5's `include` folder to your include path. You can compile the example below with the following command line on Linux: @@ -177,32 +177,33 @@ The following example illustrates a scenario of configuring a Client and publish "Hello World!" Application Message with `QoS` 0. [!c++] - #include + #include + #include - #include #include + #include #include - #include + #include int main() { boost::asio::io_context ioc; - async_mqtt5::mqtt_client c(ioc); + boost::mqtt5::mqtt_client c(ioc); - c.credentials("", "", "") - .brokers("", 1883) + c.brokers("", 1883) + .credentials("", "", "") .async_run(boost::asio::detached); - c.async_publish( + c.async_publish( "", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, - [&c](async_mqtt5::error_code ec) { + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, + [&c](boost::mqtt5::error_code ec) { std::cout << ec.message() << std::endl; c.async_disconnect(boost::asio::detached); // disconnect and close the Client } ); - + ioc.run(); } diff --git a/example/hello_world_in_coro_multithreaded_env.cpp b/example/hello_world_in_coro_multithreaded_env.cpp index d656ac3..d0f3609 100644 --- a/example/hello_world_in_coro_multithreaded_env.cpp +++ b/example/hello_world_in_coro_multithreaded_env.cpp @@ -9,97 +9,97 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT //[hello_world_in_coro_multithreaded_env -#include -#include -#include +#include +#include +#include #include #include -#include #include +#include +#include #include #include -#include - -#include -#include -#include +#include +#include +#include +#include struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; // client_type with logging enabled -using client_type = async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +using client_type = boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger >; // client_type without logging -//using client_type = async_mqtt5::mqtt_client; +//using client_type = boost::mqtt5::mqtt_client; // Modified completion token that will prevent co_await from throwing exceptions. constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); boost::asio::awaitable publish_hello_world( - const config& cfg, client_type& client, - const boost::asio::strand& strand + const config& cfg, client_type& client, + const boost::asio::strand& strand ) { - // Confirmation that the coroutine running in the strand. - assert(strand.running_in_this_thread()); + // Confirmation that the coroutine running in the strand. + assert(strand.running_in_this_thread()); - // All these function calls will be executed by the strand that is executing the coroutine. - // All the completion handler's associated executors will be that same strand - // because the Client was constructed with it as the default associated executor. - client.brokers(cfg.brokers, cfg.port) // Set the Broker to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + // All these function calls will be executed by the strand that is executing the coroutine. + // All the completion handler's associated executors will be that same strand + // because the Client was constructed with it as the default associated executor. + client.brokers(cfg.brokers, cfg.port) // Set the Broker to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - auto&& [ec, rc, puback_props] = co_await client.async_publish( - "async-mqtt5/test" /* topic */, "Hello world!" /* payload*/, async_mqtt5::retain_e::no, - async_mqtt5::publish_props {}, use_nothrow_awaitable); + auto&& [ec, rc, puback_props] = co_await client.async_publish( + "async-mqtt5/test" /* topic */, "Hello world!" /* payload*/, boost::mqtt5::retain_e::no, + boost::mqtt5::publish_props {}, use_nothrow_awaitable); - co_await client.async_disconnect(use_nothrow_awaitable); - co_return; + co_await client.async_disconnect(use_nothrow_awaitable); + co_return; } int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - // Create a thread pool with 4 threads. - boost::asio::thread_pool tp(4); + // Create a thread pool with 4 threads. + boost::asio::thread_pool tp(4); - // Create an explicit strand from io_context's executor. - // The strand guarantees a serialised handler execution regardless of the - // number of threads running in the io_context. - boost::asio::strand strand = boost::asio::make_strand(tp.get_executor()); + // Create an explicit strand from io_context's executor. + // The strand guarantees a serialised handler execution regardless of the + // number of threads running in the io_context. + boost::asio::strand strand = boost::asio::make_strand(tp.get_executor()); - // Create the Client with the explicit strand as the default associated executor. - client_type client(strand, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Create the Client with the explicit strand as the default associated executor. + client_type client(strand, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // Spawn the coroutine. - // The executor that executes the coroutine must be the same executor - // that is the Client's default associated executor. - co_spawn( - strand, - publish_hello_world(cfg, client, strand), - [](std::exception_ptr e) { - if (e) - std::rethrow_exception(e); - } - ); + // Spawn the coroutine. + // The executor that executes the coroutine must be the same executor + // that is the Client's default associated executor. + co_spawn( + strand, + publish_hello_world(cfg, client, strand), + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); - tp.join(); + tp.join(); - return 0; + return 0; } //] @@ -109,7 +109,7 @@ int main(int argc, char** argv) { #include int main() { - std::cout << "This example requires C++20 standard to compile and run" << std::endl; + std::cout << "This example requires C++20 standard to compile and run" << std::endl; } #endif diff --git a/example/hello_world_in_multithreaded_env.cpp b/example/hello_world_in_multithreaded_env.cpp index e648145..2aa36d8 100644 --- a/example/hello_world_in_multithreaded_env.cpp +++ b/example/hello_world_in_multithreaded_env.cpp @@ -6,101 +6,101 @@ // //[hello_world_in_multithreaded_env +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + #include #include #include #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - // Create a thread pool with 4 threads. - boost::asio::thread_pool tp(4); - - // Create an explicit strand from thread_pool's executor. - // The strand guarantees a serialised handler execution regardless of the - // number of threads running in the thread_pool. - boost::asio::strand strand = boost::asio::make_strand(tp.get_executor()); + // Create a thread pool with 4 threads. + boost::asio::thread_pool tp(4); + + // Create an explicit strand from thread_pool's executor. + // The strand guarantees a serialised handler execution regardless of the + // number of threads running in the thread_pool. + boost::asio::strand strand = boost::asio::make_strand(tp.get_executor()); - // Create the Client with the explicit strand as the default associated executor. - async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger - > client(strand, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Create the Client with the explicit strand as the default associated executor. + boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger + > client(strand, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // Configure the client. - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id); // Set the Client Identifier. (optional) + // Configure the client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id); // Set the Client Identifier. (optional) - // Start the Client. - // The async_run function call must be posted/dispatched to the strand. - boost::asio::dispatch( - boost::asio::bind_executor( - strand, - [&client, &strand, &cfg] { - // Considering that the default associated executor of all completion handlers is the strand, - // it is not necessary to explicitly bind it to async_run or other async_xxx's handlers. - client.async_run(boost::asio::detached); // Start the Client. - } - ) - ); - - // The async_publish function call must be posted/dispatched to the strand. - // The associated executor of async_publish's completion handler must be the same strand. - boost::asio::dispatch( - boost::asio::bind_executor( - strand, - [&client, &strand, &cfg] { - assert(strand.running_in_this_thread()); + // Start the Client. + // The async_run function call must be posted/dispatched to the strand. + boost::asio::dispatch( + boost::asio::bind_executor( + strand, + [&client, &strand, &cfg] { + // Considering that the default associated executor of all completion handlers is the strand, + // it is not necessary to explicitly bind it to async_run or other async_xxx's handlers. + client.async_run(boost::asio::detached); // Start the Client. + } + ) + ); + + // The async_publish function call must be posted/dispatched to the strand. + // The associated executor of async_publish's completion handler must be the same strand. + boost::asio::dispatch( + boost::asio::bind_executor( + strand, + [&client, &strand, &cfg] { + assert(strand.running_in_this_thread()); - client.async_publish( - "async-mqtt5/test", "Hello World!", async_mqtt5::retain_e::no, - async_mqtt5::publish_props {}, - // You may bind the strand to this handler, but it is not necessary - // as the strand is already the default associated handler. - [&client, &strand]( - async_mqtt5::error_code ec, async_mqtt5::reason_code rc, - async_mqtt5::puback_props props - ) { - assert(strand.running_in_this_thread()); + client.async_publish( + "async-mqtt5/test", "Hello World!", boost::mqtt5::retain_e::no, + boost::mqtt5::publish_props {}, + // You may bind the strand to this handler, but it is not necessary + // as the strand is already the default associated handler. + [&client, &strand]( + boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc, + boost::mqtt5::puback_props props + ) { + assert(strand.running_in_this_thread()); - std::cout << ec.message() << std::endl; - std::cout << rc.message() << std::endl; + std::cout << ec.message() << std::endl; + std::cout << rc.message() << std::endl; - // Stop the Client. - client.cancel(); - } - ); - } - ) - ); + // Stop the Client. + client.cancel(); + } + ); + } + ) + ); - tp.join(); + tp.join(); - return 0; + return 0; } //] diff --git a/example/hello_world_over_tcp.cpp b/example/hello_world_over_tcp.cpp index 8107391..67b9c82 100644 --- a/example/hello_world_over_tcp.cpp +++ b/example/hello_world_over_tcp.cpp @@ -6,64 +6,64 @@ // //[hello_world_over_tcp +#include +#include +#include + +#include +#include +#include + #include #include -#include -#include -#include - -#include -#include -#include - struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; // 1883 is the default TCP MQTT port. - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; // 1883 is the default TCP MQTT port. + std::string client_id = "async_mqtt5_tester"; }; int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - //[init_tcp_client_with_logger - // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging. - // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. - async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger - > client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); - //] + //[init_tcp_client_with_logger + // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging. + // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. + boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger + > client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); + //] - // If you want to use the Client without logging, initialise it with the following line instead. - //async_mqtt5::mqtt_client client(ioc); + // If you want to use the Client without logging, initialise it with the following line instead. + //boost::mqtt5::mqtt_client client(ioc); - //[configure_tcp_client - client.brokers(cfg.brokers, cfg.port) // Set the Broker to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. - //] + //[configure_tcp_client + client.brokers(cfg.brokers, cfg.port) // Set the Broker to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. + //] - //[publish_hello_world - client.async_publish( - "async-mqtt5/test", "Hello world!", - async_mqtt5::retain_e::yes, async_mqtt5::publish_props {}, - [&client](async_mqtt5::error_code ec) { - std::cout << ec.message() << std::endl; + //[publish_hello_world + client.async_publish( + "async-mqtt5/test", "Hello world!", + boost::mqtt5::retain_e::yes, boost::mqtt5::publish_props {}, + [&client](boost::mqtt5::error_code ec) { + std::cout << ec.message() << std::endl; - // Disconnnect the Client. - client.async_disconnect(boost::asio::detached); - } - ); - //] + // Disconnnect the Client. + client.async_disconnect(boost::asio::detached); + } + ); + //] - ioc.run(); + ioc.run(); } //] diff --git a/example/hello_world_over_tls.cpp b/example/hello_world_over_tls.cpp index 3884bb4..5bc4cac 100644 --- a/example/hello_world_over_tls.cpp +++ b/example/hello_world_over_tls.cpp @@ -6,96 +6,96 @@ // //[hello_world_over_tls +#include +#include +#include + +#include +#include +#include +#include + #include #include -#include -#include -#include -#include - -#include -#include -#include - struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 8883; // 8883 is the default TLS MQTT port. - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 8883; // 8883 is the default TLS MQTT port. + std::string client_id = "async_mqtt5_tester"; }; // External customization point. -namespace async_mqtt5 { +namespace boost::mqtt5 { // Specify that the TLS handshake will be performed as a client. template struct tls_handshake_type> { - static constexpr auto client = boost::asio::ssl::stream_base::client; + static constexpr auto client = boost::asio::ssl::stream_base::client; }; // This client uses this function to indicate which hostname it is // attempting to connect to at the start of the handshaking process. template void assign_tls_sni( - const authority_path& ap, - boost::asio::ssl::context& /* ctx */, - boost::asio::ssl::stream& stream + const authority_path& ap, + boost::asio::ssl::context& /* ctx */, + boost::asio::ssl::stream& stream ) { - SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); + SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); } -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - // TLS context that the underlying SSL stream will use. - // The purpose of the context is to allow us to set up TLS/SSL-related options. - // See ``__SSL__`` for more information and options. - boost::asio::ssl::context context(boost::asio::ssl::context::tls_client); + // TLS context that the underlying SSL stream will use. + // The purpose of the context is to allow us to set up TLS/SSL-related options. + // See ``__SSL__`` for more information and options. + boost::asio::ssl::context context(boost::asio::ssl::context::tls_client); - // Set up the TLS context. - // This step is highly dependent on the specific requirements of the Broker you are connecting to. - // Each broker may have its own standards and expectations for establishing a secure TLS/SSL connection. - // This can include verifying certificates, setting up private keys, PSK authentication, and others. + // Set up the TLS context. + // This step is highly dependent on the specific requirements of the Broker you are connecting to. + // Each broker may have its own standards and expectations for establishing a secure TLS/SSL connection. + // This can include verifying certificates, setting up private keys, PSK authentication, and others. - // Construct the Client with ``__SSL_STREAM__`` as the underlying stream - // with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type - // and logging enabled. - async_mqtt5::mqtt_client< - boost::asio::ssl::stream, - boost::asio::ssl::context, - async_mqtt5::logger - > client(ioc, std::move(context), async_mqtt5::logger(async_mqtt5::log_level::info)); + // Construct the Client with ``__SSL_STREAM__`` as the underlying stream + // with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type + // and logging enabled. + boost::mqtt5::mqtt_client< + boost::asio::ssl::stream, + boost::asio::ssl::context, + boost::mqtt5::logger + > client(ioc, std::move(context), boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // If you want to use the Client without logging, initialise it with the following line instead. - //async_mqtt5::mqtt_client< - // boost::asio::ssl::stream, - // boost::asio::ssl::context - //> client(ioc, std::move(context)); + // If you want to use the Client without logging, initialise it with the following line instead. + //boost::mqtt5::mqtt_client< + // boost::asio::ssl::stream, + // boost::asio::ssl::context + //> client(ioc, std::move(context)); - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - client.async_publish( - "async-mqtt5/test", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props{}, - [&client](async_mqtt5::error_code ec) { - std::cout << ec.message() << std::endl; - client.async_disconnect(boost::asio::detached); - } - ); + client.async_publish( + "async-mqtt5/test", "Hello world!", + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props{}, + [&client](boost::mqtt5::error_code ec) { + std::cout << ec.message() << std::endl; + client.async_disconnect(boost::asio::detached); + } + ); - ioc.run(); + ioc.run(); } //] diff --git a/example/hello_world_over_websocket_tcp.cpp b/example/hello_world_over_websocket_tcp.cpp index 007e566..9d1578f 100644 --- a/example/hello_world_over_websocket_tcp.cpp +++ b/example/hello_world_over_websocket_tcp.cpp @@ -6,62 +6,60 @@ // //[hello_world_over_websocket_tcp +#include +#include +#include +#include // WebSocket traits + +#include +#include +#include +#include + #include #include -#include -#include -#include - -#include - -#include -#include -#include - -#include // WebSocket traits - struct config { - std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt - uint16_t port = 8000; // 8083 is the default Webscoket/TCP MQTT port. However, HiveMQ's public broker uses 8000 instead. - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt + uint16_t port = 8000; // 8083 is the default Webscoket/TCP MQTT port. However, HiveMQ's public broker uses 8000 instead. + std::string client_id = "async_mqtt5_tester"; }; int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - // Construct the Client with WebSocket/TCP as the underlying stream and enabled logging. - // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. - async_mqtt5::mqtt_client< - boost::beast::websocket::stream, - std::monostate, - async_mqtt5::logger - > client(ioc, {}, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Construct the Client with WebSocket/TCP as the underlying stream and enabled logging. + // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. + boost::mqtt5::mqtt_client< + boost::beast::websocket::stream, + std::monostate, + boost::mqtt5::logger + > client(ioc, {}, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // If you want to use the Client without logging, initialise it with the following line instead. - //async_mqtt5::mqtt_client> client(ioc); + // If you want to use the Client without logging, initialise it with the following line instead. + //boost::mqtt5::mqtt_client> client(ioc); - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - client.async_publish( - "async-mqtt5/test", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, - [&client](async_mqtt5::error_code ec) { - std::cout << ec.message() << std::endl; - client.async_disconnect(boost::asio::detached); - } - ); + client.async_publish( + "async-mqtt5/test", "Hello world!", + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, + [&client](boost::mqtt5::error_code ec) { + std::cout << ec.message() << std::endl; + client.async_disconnect(boost::asio::detached); + } + ); - ioc.run(); + ioc.run(); } //] diff --git a/example/hello_world_over_websocket_tls.cpp b/example/hello_world_over_websocket_tls.cpp index b562b45..7086ac6 100644 --- a/example/hello_world_over_websocket_tls.cpp +++ b/example/hello_world_over_websocket_tls.cpp @@ -6,99 +6,97 @@ // //[hello_world_over_websocket_tls +#include +#include +#include +#include // WebSocket traits + +#include +#include +#include +#include +#include // async_teardown specialization for WebSocket SSL stream +#include + #include #include -#include -#include -#include -#include - -#include -#include // async_teardown specialization for WebSocket SSL stream - -#include -#include -#include - -#include // WebSocket traits - struct config { - std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt - uint16_t port = 8884; // 8884 is the default Websocket/TLS MQTT port. - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt + uint16_t port = 8884; // 8884 is the default Websocket/TLS MQTT port. + std::string client_id = "async_mqtt5_tester"; }; // External customization point. -namespace async_mqtt5 { +namespace boost::mqtt5 { // Specify that the TLS handshake will be performed as a client. template struct tls_handshake_type> { - static constexpr auto client = boost::asio::ssl::stream_base::client; + static constexpr auto client = boost::asio::ssl::stream_base::client; }; // This client uses this function to indicate which hostname it is // attempting to connect to at the start of the handshaking process. template void assign_tls_sni( - const authority_path& ap, - boost::asio::ssl::context& /*ctx*/, - boost::asio::ssl::stream& stream + const authority_path& ap, + boost::asio::ssl::context& /*ctx*/, + boost::asio::ssl::stream& stream ) { - SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); + SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); } -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - // TLS context that the underlying SSL stream will use. - // The purpose of the context is to allow us to set up TLS/SSL-related options. - // See ``__SSL__`` for more information and options. - boost::asio::ssl::context context(boost::asio::ssl::context::tls_client); + // TLS context that the underlying SSL stream will use. + // The purpose of the context is to allow us to set up TLS/SSL-related options. + // See ``__SSL__`` for more information and options. + boost::asio::ssl::context context(boost::asio::ssl::context::tls_client); - // Set up the TLS context. - // This step is highly dependent on the specific requirements of the Broker you are connecting to. - // Each broker may have its own standards and expectations for establishing a secure TLS/SSL connection. - // This can include verifying certificates, setting up private keys, PSK authentication, and others. + // Set up the TLS context. + // This step is highly dependent on the specific requirements of the Broker you are connecting to. + // Each broker may have its own standards and expectations for establishing a secure TLS/SSL connection. + // This can include verifying certificates, setting up private keys, PSK authentication, and others. - // Construct the Client with WebSocket/SSL as the underlying stream - // with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type and enabled logging. - async_mqtt5::mqtt_client< - boost::beast::websocket::stream>, - boost::asio::ssl::context, - async_mqtt5::logger - > client(ioc, std::move(context), async_mqtt5::logger(async_mqtt5::log_level::info)); + // Construct the Client with WebSocket/SSL as the underlying stream + // with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type and enabled logging. + boost::mqtt5::mqtt_client< + boost::beast::websocket::stream>, + boost::asio::ssl::context, + boost::mqtt5::logger + > client(ioc, std::move(context), boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // If you want to use the Client without logging, initialise it with the following line instead. - //async_mqtt5::mqtt_client< - // boost::beast::websocket::stream>, - // boost::asio::ssl::context - //> client(ioc, std::move(context)); + // If you want to use the Client without logging, initialise it with the following line instead. + //boost::mqtt5::mqtt_client< + // boost::beast::websocket::stream>, + // boost::asio::ssl::context + //> client(ioc, std::move(context)); - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - client.async_publish( - "async-mqtt5/test", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props{}, - [&client](async_mqtt5::error_code ec) { - std::cout << ec.message() << std::endl; - client.async_disconnect(boost::asio::detached); - } - ); + client.async_publish( + "async-mqtt5/test", "Hello world!", + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props{}, + [&client](boost::mqtt5::error_code ec) { + std::cout << ec.message() << std::endl; + client.async_disconnect(boost::asio::detached); + } + ); - ioc.run(); + ioc.run(); } //] diff --git a/example/multiflight_client.cpp b/example/multiflight_client.cpp index 8c710e4..70a9b8e 100644 --- a/example/multiflight_client.cpp +++ b/example/multiflight_client.cpp @@ -6,65 +6,65 @@ // //[multiflight_client -#include -#include +#include +#include +#include +#include -#include #include +#include #include #include -#include -#include -#include -#include +#include +#include struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging. - // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. - async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger - > client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging. + // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. + boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger + > client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the client. - // Publish with QoS 2 five times in a row without waiting for the handler - // of the previous async_publish call to be invoked. - for (auto i = 1; i <= 5; ++i) - client.async_publish( - "async-mqtt5/test", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, - [i](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::pubcomp_props) { - std::cout << "Publish number " << i << " completed with: " << std::endl; - std::cout << "\t ec: " << ec.message() << std::endl; - std::cout << "\t rc: " << rc.message() << std::endl; - } - ); + // Publish with QoS 2 five times in a row without waiting for the handler + // of the previous async_publish call to be invoked. + for (auto i = 1; i <= 5; ++i) + client.async_publish( + "async-mqtt5/test", "Hello world!", + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, + [i](boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc, boost::mqtt5::pubcomp_props) { + std::cout << "Publish number " << i << " completed with: " << std::endl; + std::cout << "\t ec: " << ec.message() << std::endl; + std::cout << "\t rc: " << rc.message() << std::endl; + } + ); - // We can stop the Client by using signals. - boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); - signals.async_wait([&client](async_mqtt5::error_code, int) { - client.async_disconnect(boost::asio::detached); - }); + // We can stop the Client by using signals. + boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); + signals.async_wait([&client](boost::mqtt5::error_code, int) { + client.async_disconnect(boost::asio::detached); + }); - ioc.run(); + ioc.run(); } //] diff --git a/example/publisher.cpp b/example/publisher.cpp index 2091b12..a3e34c3 100644 --- a/example/publisher.cpp +++ b/example/publisher.cpp @@ -9,131 +9,130 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT //[publisher -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include -#include - -#include -#include -#include -#include +#include +#include +#include +#include struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; // Modified completion token that will prevent co_await from throwing exceptions. constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); // client_type with logging enabled -using client_type = async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +using client_type = boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger >; // client_type without logging -//using client_type = async_mqtt5::mqtt_client; +//using client_type = boost::mqtt5::mqtt_client; int next_sensor_reading() { - srand(static_cast(std::time(0))); - return rand() % 100; + srand(static_cast(std::time(0))); + return rand() % 100; } boost::asio::awaitable publish_sensor_readings( - const config& cfg, client_type& client, boost::asio::steady_timer& timer + const config& cfg, client_type& client, boost::asio::steady_timer& timer ) { - // Configure the Client. - // It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client. - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + // Configure the Client. + // It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - for (;;) { - // Get the next sensor reading. - auto reading = std::to_string(next_sensor_reading()); + for (;;) { + // Get the next sensor reading. + auto reading = std::to_string(next_sensor_reading()); - // Publish the sensor reading with QoS 1. - auto&& [ec, rc, props] = co_await client.async_publish( - "async-mqtt5/test" /* topic */, reading /* payload */, - async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, use_nothrow_awaitable - ); - // An error can occur as a result of: - // a) wrong publish parameters - // b) mqtt_client::cancel is called while the Client is publishing the message - // resulting in cancellation. - if (ec) { - std::cout << "Publish error occurred: " << ec.message() << std::endl; - break; - } + // Publish the sensor reading with QoS 1. + auto&& [ec, rc, props] = co_await client.async_publish( + "async-mqtt5/test" /* topic */, reading /* payload */, + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, use_nothrow_awaitable + ); + // An error can occur as a result of: + // a) wrong publish parameters + // b) mqtt_client::cancel is called while the Client is publishing the message + // resulting in cancellation. + if (ec) { + std::cout << "Publish error occurred: " << ec.message() << std::endl; + break; + } - // Reason code is the reply from the server presenting the result of the publish operation. - std::cout << "Result of publish request: " << rc.message() << std::endl; - if (!rc) - std::cout << "Published sensor reading: " << reading << std::endl; + // Reason code is the reply from the server presenting the result of the publish operation. + std::cout << "Result of publish request: " << rc.message() << std::endl; + if (!rc) + std::cout << "Published sensor reading: " << reading << std::endl; - // Wait 5 seconds before publishing the next reading. - timer.expires_after(std::chrono::seconds(5)); - auto&& [tec] = co_await timer.async_wait(use_nothrow_awaitable); + // Wait 5 seconds before publishing the next reading. + timer.expires_after(std::chrono::seconds(5)); + auto&& [tec] = co_await timer.async_wait(use_nothrow_awaitable); - // An error occurred if we cancelled the timer. - if (tec) - break; - } + // An error occurred if we cancelled the timer. + if (tec) + break; + } - co_return; + co_return; } int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - // Initialise execution context. - boost::asio::io_context ioc; + // Initialise execution context. + boost::asio::io_context ioc; - // Initialise the Client to connect to the Broker over TCP. - client_type client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Initialise the Client to connect to the Broker over TCP. + client_type client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // Initialise the timer. - boost::asio::steady_timer timer(ioc); + // Initialise the timer. + boost::asio::steady_timer timer(ioc); - // Set up signals to stop the program on demand. - boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); - signals.async_wait([&client, &timer](async_mqtt5::error_code /* ec */, int /* signal */) { - // After we are done with publishing all the messages, cancel the timer and the Client. - // Alternatively, use mqtt_client::async_disconnect. - timer.cancel(); - client.cancel(); - }); + // Set up signals to stop the program on demand. + boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); + signals.async_wait([&client, &timer](boost::mqtt5::error_code /* ec */, int /* signal */) { + // After we are done with publishing all the messages, cancel the timer and the Client. + // Alternatively, use mqtt_client::async_disconnect. + timer.cancel(); + client.cancel(); + }); - // Spawn the coroutine. - co_spawn( - ioc.get_executor(), - publish_sensor_readings(cfg, client, timer), - [](std::exception_ptr e) { - if (e) - std::rethrow_exception(e); - } - ); + // Spawn the coroutine. + co_spawn( + ioc.get_executor(), + publish_sensor_readings(cfg, client, timer), + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); - // Start the execution. - ioc.run(); + // Start the execution. + ioc.run(); } //] @@ -143,7 +142,7 @@ int main(int argc, char** argv) { #include int main() { - std::cout << "This example requires C++20 standard to compile and run" << std::endl; + std::cout << "This example requires C++20 standard to compile and run" << std::endl; } #endif diff --git a/example/receiver.cpp b/example/receiver.cpp index fa7757a..6fe4b96 100644 --- a/example/receiver.cpp +++ b/example/receiver.cpp @@ -9,142 +9,142 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT //[receiver -#include -#include + +#include +#include +#include +#include #include #include #include #include #include +#include #include -#include - -#include -#include -#include -#include +#include +#include struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; // Modified completion token that will prevent co_await from throwing exceptions. constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); // client_type with logging enabled -using client_type = async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +using client_type = boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger >; // client_type without logging -//using client_type = async_mqtt5::mqtt_client; +//using client_type = boost::mqtt5::mqtt_client; boost::asio::awaitable subscribe(client_type& client) { - // Configure the request to subscribe to a Topic. - async_mqtt5::subscribe_topic sub_topic = async_mqtt5::subscribe_topic{ - "test" /* topic */, - async_mqtt5::subscribe_options { - async_mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2. - async_mqtt5::no_local_e::no, // Forward message from Clients with same ID. - async_mqtt5::retain_as_published_e::retain, // Keep the original RETAIN flag. - async_mqtt5::retain_handling_e::send // Send retained messages when the subscription is established. - } - }; + // Configure the request to subscribe to a Topic. + boost::mqtt5::subscribe_topic sub_topic = boost::mqtt5::subscribe_topic{ + "test" /* topic */, + boost::mqtt5::subscribe_options { + boost::mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2. + boost::mqtt5::no_local_e::no, // Forward message from Clients with same ID. + boost::mqtt5::retain_as_published_e::retain, // Keep the original RETAIN flag. + boost::mqtt5::retain_handling_e::send // Send retained messages when the subscription is established. + } + }; - // Subscribe to a single Topic. - auto&& [ec, sub_codes, sub_props] = co_await client.async_subscribe( - sub_topic, async_mqtt5::subscribe_props {}, use_nothrow_awaitable - ); - // Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call. + // Subscribe to a single Topic. + auto&& [ec, sub_codes, sub_props] = co_await client.async_subscribe( + sub_topic, boost::mqtt5::subscribe_props {}, use_nothrow_awaitable + ); + // Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call. - // An error can occur as a result of: - // a) wrong subscribe parameters - // b) mqtt_client::cancel is called while the Client is in the process of subscribing - if (ec) - std::cout << "Subscribe error occurred: " << ec.message() << std::endl; - else - std::cout << "Result of subscribe request: " << sub_codes[0].message() << std::endl; + // An error can occur as a result of: + // a) wrong subscribe parameters + // b) mqtt_client::cancel is called while the Client is in the process of subscribing + if (ec) + std::cout << "Subscribe error occurred: " << ec.message() << std::endl; + else + std::cout << "Result of subscribe request: " << sub_codes[0].message() << std::endl; - co_return !ec && !sub_codes[0]; // True if the subscription was successfully established. + co_return !ec && !sub_codes[0]; // True if the subscription was successfully established. } boost::asio::awaitable subscribe_and_receive( - const config& cfg, client_type& client + const config& cfg, client_type& client ) { - // Configure the Client. - // It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client. - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the client. + // Configure the Client. + // It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the client. - // Before attempting to receive an Application Message from the Topic we just subscribed to, - // it is advisable to verify that the subscription succeeded. - // It is not recommended to call mqtt_client::async_receive if you do not have any - // subscription established as the corresponding handler will never be invoked. - if (!(co_await subscribe(client))) - co_return; + // Before attempting to receive an Application Message from the Topic we just subscribed to, + // it is advisable to verify that the subscription succeeded. + // It is not recommended to call mqtt_client::async_receive if you do not have any + // subscription established as the corresponding handler will never be invoked. + if (!(co_await subscribe(client))) + co_return; - for (;;) { - // Receive an Appplication Message from the subscribed Topic(s). - auto&& [ec, topic, payload, publish_props] = co_await client.async_receive(use_nothrow_awaitable); + for (;;) { + // Receive an Appplication Message from the subscribed Topic(s). + auto&& [ec, topic, payload, publish_props] = co_await client.async_receive(use_nothrow_awaitable); - if (ec == async_mqtt5::client::error::session_expired) { - // The Client has reconnected, and the prior session has expired. - // As a result, any previous subscriptions have been lost and must be reinstated. - if (co_await subscribe(client)) - continue; - else - break; - } else if (ec) - break; + if (ec == boost::mqtt5::client::error::session_expired) { + // The Client has reconnected, and the prior session has expired. + // As a result, any previous subscriptions have been lost and must be reinstated. + if (co_await subscribe(client)) + continue; + else + break; + } else if (ec) + break; - std::cout << "Received message from the Broker" << std::endl; - std::cout << "\t topic: " << topic << std::endl; - std::cout << "\t payload: " << payload << std::endl; - } + std::cout << "Received message from the Broker" << std::endl; + std::cout << "\t topic: " << topic << std::endl; + std::cout << "\t payload: " << payload << std::endl; + } - co_return; + co_return; } int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - // Initialise execution context. - boost::asio::io_context ioc; + // Initialise execution context. + boost::asio::io_context ioc; - // Initialise the Client to connect to the Broker over TCP. - client_type client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Initialise the Client to connect to the Broker over TCP. + client_type client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // Set up signals to stop the program on demand. - boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); - signals.async_wait([&client](async_mqtt5::error_code /* ec */, int /* signal */) { - // After we are done with publishing all the messages, cancel the timer and the Client. - // Alternatively, use mqtt_client::async_disconnect. - client.cancel(); - }); + // Set up signals to stop the program on demand. + boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); + signals.async_wait([&client](boost::mqtt5::error_code /* ec */, int /* signal */) { + // After we are done with publishing all the messages, cancel the timer and the Client. + // Alternatively, use mqtt_client::async_disconnect. + client.cancel(); + }); - // Spawn the coroutine. - co_spawn( - ioc, - subscribe_and_receive(cfg, client), - [](std::exception_ptr e) { - if (e) - std::rethrow_exception(e); - } - ); + // Spawn the coroutine. + co_spawn( + ioc, + subscribe_and_receive(cfg, client), + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); - // Start the execution. - ioc.run(); + // Start the execution. + ioc.run(); } //] @@ -154,7 +154,7 @@ int main(int argc, char** argv) { #include int main() { - std::cout << "This example requires C++20 standard to compile and run" << std::endl; + std::cout << "This example requires C++20 standard to compile and run" << std::endl; } #endif diff --git a/example/timeout_with_awaitable_operators.cpp b/example/timeout_with_awaitable_operators.cpp index 1ef6044..db9f010 100644 --- a/example/timeout_with_awaitable_operators.cpp +++ b/example/timeout_with_awaitable_operators.cpp @@ -9,104 +9,106 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT //[timeout_with_awaitable_operators + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; // Modified completion token that will prevent co_await from throwing exceptions. constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); // client_type with logging enabled -using client_type = async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +using client_type = boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger >; // client_type without logging -//using client_type = async_mqtt5::mqtt_client; +//using client_type = boost::mqtt5::mqtt_client; boost::asio::awaitable send_over_mqtt( - const config& cfg, client_type& client + const config& cfg, client_type& client ) { - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - auto&& [pec, prc, puback_props] = co_await client.async_publish( - "async-mqtt5/test", "Hello World!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, - use_nothrow_awaitable - ); + auto&& [pec, prc, puback_props] = co_await client.async_publish( + "async-mqtt5/test", "Hello World!", + boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, + use_nothrow_awaitable + ); - co_await client.async_disconnect(use_nothrow_awaitable); - co_return; + co_await client.async_disconnect(use_nothrow_awaitable); + co_return; } int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - co_spawn( - ioc, - [&ioc, &cfg]() -> boost::asio::awaitable { - // Initialise the Client to connect to the Broker over TCP. - client_type client(ioc); + co_spawn( + ioc, + [&ioc, &cfg]() -> boost::asio::awaitable { + // Initialise the Client to connect to the Broker over TCP. + client_type client(ioc); - // You can also initialise the Client and its logger with a specific log_level (default log_level::info). - //client_type client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::debug)); + // You can also initialise the Client and its logger with a specific log_level (default log_level::info). + //client_type client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::debug)); - // Construct the timer. - boost::asio::steady_timer timer(ioc, std::chrono::seconds(5)); + // Construct the timer. + boost::asio::steady_timer timer(ioc, std::chrono::seconds(5)); - using namespace boost::asio::experimental::awaitable_operators; - auto res = co_await ( - send_over_mqtt(cfg, client) || - timer.async_wait(boost::asio::as_tuple(boost::asio::use_awaitable)) - ); + using namespace boost::asio::experimental::awaitable_operators; + auto res = co_await ( + send_over_mqtt(cfg, client) || + timer.async_wait(boost::asio::as_tuple(boost::asio::use_awaitable)) + ); - // The timer expired first. The client is cancelled. - if (res.index() == 1) - std::cout << "Send over MQTT timed out!" << std::endl; - // send_over_mqtt completed first. The timer is cancelled. - else - std::cout << "Send over MQTT completed!" << std::endl; - - }, - [](std::exception_ptr e) { - if (e) - std::rethrow_exception(e); - } - ); + // The timer expired first. The client is cancelled. + if (res.index() == 1) + std::cout << "Send over MQTT timed out!" << std::endl; + // send_over_mqtt completed first. The timer is cancelled. + else + std::cout << "Send over MQTT completed!" << std::endl; + + }, + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); - ioc.run(); + ioc.run(); } //] @@ -116,7 +118,7 @@ int main(int argc, char** argv) { #include int main() { - std::cout << "This example requires C++20 standard to compile and run" << std::endl; + std::cout << "This example requires C++20 standard to compile and run" << std::endl; } #endif diff --git a/example/timeout_with_parallel_group.cpp b/example/timeout_with_parallel_group.cpp index 32a076b..1c18aff 100644 --- a/example/timeout_with_parallel_group.cpp +++ b/example/timeout_with_parallel_group.cpp @@ -6,94 +6,94 @@ // //[timeout_with_parallel_group +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + #include #include #include #include #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - struct config { - std::string brokers = "broker.hivemq.com"; - uint16_t port = 1883; - std::string client_id = "async_mqtt5_tester"; + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; }; int main(int argc, char** argv) { - config cfg; + config cfg; - if (argc == 4) { - cfg.brokers = argv[1]; - cfg.port = uint16_t(std::stoi(argv[2])); - cfg.client_id = argv[3]; - } + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - boost::asio::io_context ioc; + boost::asio::io_context ioc; - // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging. - // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. - async_mqtt5::mqtt_client< - boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger - > client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); + // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging. + // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. + boost::mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger + > client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info)); - // If you want to use the Client without logging, initialise it with the following line instead. - //async_mqtt5::mqtt_client client(ioc); + // If you want to use the Client without logging, initialise it with the following line instead. + //boost::mqtt5::mqtt_client client(ioc); - // Construct the timer. - boost::asio::steady_timer timer(ioc, std::chrono::seconds(5)); + // Construct the timer. + boost::asio::steady_timer timer(ioc, std::chrono::seconds(5)); - client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. - .credentials(cfg.client_id) // Set the Client Identifier. (optional) - .async_run(boost::asio::detached); // Start the Client. + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. - // Subscribe to a Topic. - client.async_subscribe( - { "test" /* Topic */}, async_mqtt5::subscribe_props {}, - [](async_mqtt5::error_code ec, std::vector rcs, async_mqtt5::suback_props) { - std::cout << "[subscribe ec]: " << ec.message() << std::endl; - std::cout << "[subscribe rc]: " << rcs[0].message() << std::endl; - } - ); + // Subscribe to a Topic. + client.async_subscribe( + { "test" /* Topic */}, boost::mqtt5::subscribe_props {}, + [](boost::mqtt5::error_code ec, std::vector rcs, boost::mqtt5::suback_props) { + std::cout << "[subscribe ec]: " << ec.message() << std::endl; + std::cout << "[subscribe rc]: " << rcs[0].message() << std::endl; + } + ); - // Create a parallel group to wait up to 5 seconds to receive a message - // using client.async_receive(...). - boost::asio::experimental::make_parallel_group( - timer.async_wait(boost::asio::deferred), - client.async_receive(boost::asio::deferred) - ).async_wait( - boost::asio::experimental::wait_for_one(), - [&client]( - std::array ord, // Completion order - async_mqtt5::error_code /* timer_ec */, // timer.async_wait(...) handler signature - // client.async_receive(...) handler signature - async_mqtt5::error_code receive_ec, - std::string topic, std::string payload, async_mqtt5::publish_props /* props */ - ) { - if (ord[0] == 1) { - std::cout << "Received a message!" << std::endl; - std::cout << "[receive ec]: " << receive_ec.message() << std::endl; - std::cout << "[receive topic]: " << topic << std::endl; - std::cout << "[receive payload]: " << payload << std::endl; - } - else - std::cout << "Timed out! Did not receive a message within 5 seconds." << std::endl; + // Create a parallel group to wait up to 5 seconds to receive a message + // using client.async_receive(...). + boost::asio::experimental::make_parallel_group( + timer.async_wait(boost::asio::deferred), + client.async_receive(boost::asio::deferred) + ).async_wait( + boost::asio::experimental::wait_for_one(), + [&client]( + std::array ord, // Completion order + boost::mqtt5::error_code /* timer_ec */, // timer.async_wait(...) handler signature + // client.async_receive(...) handler signature + boost::mqtt5::error_code receive_ec, + std::string topic, std::string payload, boost::mqtt5::publish_props /* props */ + ) { + if (ord[0] == 1) { + std::cout << "Received a message!" << std::endl; + std::cout << "[receive ec]: " << receive_ec.message() << std::endl; + std::cout << "[receive topic]: " << topic << std::endl; + std::cout << "[receive payload]: " << payload << std::endl; + } + else + std::cout << "Timed out! Did not receive a message within 5 seconds." << std::endl; - client.cancel(); - } - ); + client.cancel(); + } + ); - ioc.run(); + ioc.run(); } //] diff --git a/include/async_mqtt5.hpp b/include/async_mqtt5.hpp deleted file mode 100644 index ca2032f..0000000 --- a/include/async_mqtt5.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_HPP -#define ASYNC_MQTT5_HPP - -#include -#include -#include -#include -#include -#include -#include - -#endif // !ASYNC_MQTT5_HPP diff --git a/include/async_mqtt5/detail/any_authenticator.hpp b/include/async_mqtt5/detail/any_authenticator.hpp deleted file mode 100644 index 3e7b4f9..0000000 --- a/include/async_mqtt5/detail/any_authenticator.hpp +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_ANY_AUTHENTICATOR -#define ASYNC_MQTT5_ANY_AUTHENTICATOR - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; -using error_code = boost::system::error_code; - -using auth_handler_type = asio::any_completion_handler< - void (error_code, std::string) ->; - -template -using async_auth_sig = decltype( - std::declval().async_auth(std::declval()...) -); - -template -using method_sig = decltype( - std::declval().method() -); - -template -constexpr bool is_authenticator = - boost::is_detected< - async_auth_sig, T, - auth_step_e, std::string, auth_handler_type - >::value && - boost::is_detected_convertible_v; - -class auth_fun_base { - using auth_func = void(*)( - auth_step_e, std::string, auth_handler_type, auth_fun_base* - ); - auth_func _auth_func; - -public: - auth_fun_base(auth_func f) : _auth_func(f) {} - ~auth_fun_base() = default; - - void async_auth( - auth_step_e step, std::string data, - auth_handler_type auth_handler - ) { - _auth_func(step, std::move(data), std::move(auth_handler), this); - } -}; - -template < - typename Authenticator, - typename = std::enable_if_t> -> -class auth_fun : public auth_fun_base { - Authenticator _authenticator; - -public: - auth_fun(Authenticator authenticator) : - auth_fun_base(&async_auth), - _authenticator(std::forward(authenticator)) - {} - - static void async_auth( - auth_step_e step, std::string data, auth_handler_type auth_handler, - auth_fun_base* base_ptr - ) { - auto auth_fun_ptr = static_cast(base_ptr); - auth_fun_ptr->_authenticator.async_auth( - step, std::move(data), std::move(auth_handler) - ); - } -}; - -class any_authenticator { - std::string _method; - std::shared_ptr _auth_fun; - -public: - any_authenticator() = default; - - template < - typename Authenticator, - std::enable_if_t, bool> = true - > - any_authenticator(Authenticator&& a) : - _method(a.method()), - _auth_fun( - new detail::auth_fun( - std::forward(a) - ) - ) - {} - - std::string_view method() const { - return _method; - } - - template - decltype(auto) async_auth( - auth_step_e step, std::string data, - CompletionToken&& token - ) { - using Signature = void (error_code, std::string); - - auto initiation = []( - auto handler, any_authenticator& self, - auth_step_e step, std::string data - ) { - self._auth_fun->async_auth( - step, std::move(data), std::move(handler) - ); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this), step, std::move(data) - ); - } -}; - -} // end namespace async_mqtt5::detail - - -#endif // !ASYNC_MQTT5_ANY_AUTHENTICATOR diff --git a/include/async_mqtt5/detail/async_mutex.hpp b/include/async_mqtt5/detail/async_mutex.hpp deleted file mode 100644 index 6e496c2..0000000 --- a/include/async_mqtt5/detail/async_mutex.hpp +++ /dev/null @@ -1,218 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_ASYNC_MUTEX_HPP -#define ASYNC_MQTT5_ASYNC_MUTEX_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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 (error_code) - >; - using queue_t = std::deque; - - // Handler with assigned tracking executor. - // Objects of this type are type-erased by any_completion_handler - // and stored in the waiting queue. - template - class tracked_op { - tracking_type _executor; - Handler _handler; - public: - tracked_op(Handler&& h, const Executor& ex) : - _executor(tracking_executor(h, ex)), _handler(std::move(h)) - {} - - tracked_op(tracked_op&&) = default; - tracked_op(const tracked_op&) = delete; - - tracked_op& operator=(tracked_op&&) = default; - tracked_op& operator=(const tracked_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using cancellation_slot_type = - asio::associated_cancellation_slot_t; - cancellation_slot_type get_cancellation_slot() const noexcept { - return asio::get_associated_cancellation_slot(_handler); - } - - using executor_type = tracking_type; - executor_type get_executor() const noexcept { - return _executor; - } - - void operator()(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 { - queue_t::iterator _ihandler; - public: - explicit cancel_waiting_op(queue_t::iterator ih) : _ihandler(ih) {} - - void operator()(asio::cancellation_type_t type) { - if (type == asio::cancellation_type_t::none) - return; - if (*_ihandler) { - auto h = std::move(*_ihandler); - auto ex = asio::get_associated_executor(h); - asio::require(ex, asio::execution::blocking.possibly) - .execute([h = std::move(h)]() mutable { - std::move(h)(asio::error::operation_aborted); - }); - } - } - }; - - bool _locked { false }; - queue_t _waiting; - executor_type _ex; - -public: - template - explicit async_mutex(Executor&& ex) : _ex(std::forward(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; - } - - // 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 - decltype(auto) lock(CompletionToken&& token) noexcept { - using Signature = void (error_code); - - auto initiation = [] (auto handler, async_mutex& self) { - self.execute_or_queue(std::move(handler)); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this) - ); - } - - // 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() { - while (!_waiting.empty()) { - auto op = std::move(_waiting.front()); - _waiting.pop_front(); - if (!op) continue; - op.get_cancellation_slot().clear(); - execute_op(std::move(op)); - return; - } - _locked = false; - } - - // Cancels all outstanding operations waiting on the mutex. - void cancel() { - while (!_waiting.empty()) { - auto op = std::move(_waiting.front()); - _waiting.pop_front(); - if (!op) continue; - op.get_cancellation_slot().clear(); - 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(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 immediately 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. - template - void execute_or_queue(Handler&& handler) noexcept { - tracked_op h { std::move(handler), _ex }; - if (_locked) { - _waiting.emplace_back(std::move(h)); - auto slot = _waiting.back().get_cancellation_slot(); - if (slot.is_connected()) - slot.template emplace( - _waiting.end() - 1 - ); - } - else { - _locked = true; - execute_op(queued_op_t { std::move(h) }); - } - } -}; - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_ASYNC_MUTEX_HPP diff --git a/include/async_mqtt5/detail/cancellable_handler.hpp b/include/async_mqtt5/detail/cancellable_handler.hpp deleted file mode 100644 index 16379d4..0000000 --- a/include/async_mqtt5/detail/cancellable_handler.hpp +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP -#define ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace async_mqtt5::detail { - -template -class cancellable_handler { - Executor _executor; - Handler _handler; - tracking_type _handler_ex; - asio::cancellation_state _cancellation_state; - -public: - cancellable_handler(Handler&& handler, const Executor& ex) : - _executor(ex), - _handler(std::move(handler)), - _handler_ex(tracking_executor(_handler, ex)), - _cancellation_state( - asio::get_associated_cancellation_slot(_handler), - asio::enable_total_cancellation {}, - asio::enable_terminal_cancellation {} - ) - {} - - cancellable_handler(cancellable_handler&&) = default; - cancellable_handler(const cancellable_handler&) = delete; - - cancellable_handler& operator=(cancellable_handler&&) = default; - cancellable_handler& operator=(const cancellable_handler&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using cancellation_slot_type = asio::associated_cancellation_slot_t; - cancellation_slot_type get_cancellation_slot() const noexcept { - return _cancellation_state.slot(); - } - - using executor_type = tracking_type; - executor_type get_executor() const noexcept { - return _handler_ex; - } - - using immediate_executor_type = - asio::associated_immediate_executor_t; - immediate_executor_type get_immediate_executor() const noexcept { - // get_associated_immediate_executor will require asio::execution::blocking.never - // on the default executor. - return asio::get_associated_immediate_executor(_handler, _executor); - } - - asio::cancellation_type_t cancelled() const { - return _cancellation_state.cancelled(); - } - - template - void complete(Args&&... args) { - asio::get_associated_cancellation_slot(_handler).clear(); - asio::dispatch( - _handler_ex, - asio::prepend(std::move(_handler), std::forward(args)...) - ); - } - - template - void complete_immediate(Args&&... args) { - asio::get_associated_cancellation_slot(_handler).clear(); - auto ex = get_immediate_executor(); - asio::dispatch( - ex, - asio::prepend(std::move(_handler), std::forward(args)...) - ); - } - -}; - -} // end async_mqtt5::detail - -#endif // !ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP diff --git a/include/async_mqtt5/detail/channel_traits.hpp b/include/async_mqtt5/detail/channel_traits.hpp deleted file mode 100644 index ae0517b..0000000 --- a/include/async_mqtt5/detail/channel_traits.hpp +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_CHANNEL_TRAITS_HPP -#define ASYNC_MQTT5_CHANNEL_TRAITS_HPP - -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; -using error_code = boost::system::error_code; - -template -class bounded_deque { - std::deque _buffer; - static constexpr size_t MAX_SIZE = 65535; - -public: - bounded_deque() = default; - bounded_deque(size_t n) : _buffer(n) {} - - size_t size() const { - return _buffer.size(); - } - - template - void push_back(E&& e) { - if (_buffer.size() == MAX_SIZE) - _buffer.pop_front(); - _buffer.push_back(std::forward(e)); - } - - void pop_front() { - _buffer.pop_front(); - } - - void clear() { - _buffer.clear(); - } - - const auto& front() const noexcept { - return _buffer.front(); - } - - auto& front() noexcept { - return _buffer.front(); - } -}; - -template -struct channel_traits { - template - struct rebind { - using other = channel_traits; - }; -}; - -template -struct channel_traits { - static_assert(sizeof...(Args) > 0); - - template - struct rebind { - using other = channel_traits; - }; - - template - struct container { - using type = bounded_deque; - }; - - using receive_cancelled_signature = R(error_code, Args...); - - template - static void invoke_receive_cancelled(F f) { - std::forward(f)( - asio::error::operation_aborted, - typename std::decay_t()... - ); - } - - using receive_closed_signature = R(error_code, Args...); - - template - static void invoke_receive_closed(F f) { - std::forward(f)( - asio::error::operation_aborted, - typename std::decay_t()... - ); - } -}; - -} // namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_CHANNEL_TRAITS_HPP diff --git a/include/async_mqtt5/detail/control_packet.hpp b/include/async_mqtt5/detail/control_packet.hpp deleted file mode 100644 index 3d248cc..0000000 --- a/include/async_mqtt5/detail/control_packet.hpp +++ /dev/null @@ -1,193 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_CONTROL_PACKET_HPP -#define ASYNC_MQTT5_CONTROL_PACKET_HPP - -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace async_mqtt5::detail { - -/* max varint number (268'435'455) + fixed header size (1 + 4) */ -static constexpr int32_t default_max_send_size = 268'435'460; - -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 -class control_packet { - uint16_t _packet_id; - - using alloc_type = Allocator; - using deleter = boost::alloc_deleter; - std::unique_ptr _packet; - - control_packet( - const Allocator& a, - uint16_t packet_id, std::string packet - ) : - _packet_id(packet_id), - _packet(boost::allocate_unique(a, std::move(packet))) - {} - -public: - control_packet(control_packet&&) noexcept = default; - control_packet(const control_packet&) = delete; - - control_packet& operator=(control_packet&&) noexcept = default; - control_packet& operator=(const control_packet&) = 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)...) - }; - } - - template < - typename EncodeFun, - typename ...Args - > - static control_packet of( - no_pid_, const Allocator& alloc, - EncodeFun&& encode, Args&&... args - ) { - return control_packet { - alloc, uint16_t(0), encode(std::forward(args)...) - }; - } - - size_t size() const { - return _packet->size(); - } - - 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; - } - - std::string_view wire_data() const { - return *_packet; - } -}; - -class packet_id_allocator { - struct interval { - uint16_t start, end; - - interval(uint16_t start, uint16_t end) : - start(start), end(end) - {} - }; - - std::vector _free_ids; - static constexpr uint16_t MAX_PACKET_ID = 65535; - -public: - packet_id_allocator() { - _free_ids.emplace_back(MAX_PACKET_ID, uint16_t(0)); - } - - packet_id_allocator(packet_id_allocator&&) noexcept = default; - packet_id_allocator(const packet_id_allocator&) = delete; - - packet_id_allocator& operator=(packet_id_allocator&&) noexcept = default; - packet_id_allocator& operator=(const packet_id_allocator&) = delete; - - uint16_t allocate() { - if (_free_ids.empty()) return 0; - auto& last = _free_ids.back(); - if (last.start == ++last.end) { - auto ret = last.end; - _free_ids.pop_back(); - return ret; - } - return last.end; - } - - void free(uint16_t pid) { - auto it = std::upper_bound( - _free_ids.begin(), _free_ids.end(), pid, - [](const uint16_t x, const interval& i) { return x > i.start; } - ); - uint16_t* end_p = nullptr; - if (it != _free_ids.begin()) { - auto pit = std::prev(it); - if (pit->end == pid) - end_p = &pit->end; - } - if (it != _free_ids.end() && pid - 1 == it->start) { - if (!end_p) - it->start = pid; - else { - *end_p = it->end; - _free_ids.erase(it); - } - } - else { - if (!end_p) - _free_ids.insert(it, interval(pid, pid - 1)); - else - *end_p = pid - 1; - } - } -}; - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_CONTROL_PACKET_HPP diff --git a/include/async_mqtt5/detail/internal_types.hpp b/include/async_mqtt5/detail/internal_types.hpp deleted file mode 100644 index 5d62d01..0000000 --- a/include/async_mqtt5/detail/internal_types.hpp +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_INTERNAL_TYPES_HPP -#define ASYNC_MQTT5_INTERNAL_TYPES_HPP - -#include -#include -#include -#include - -#include - -#include -#include - -namespace async_mqtt5::detail { - -using byte_citer = std::string::const_iterator; - -using time_stamp = std::chrono::time_point; -using duration = time_stamp::duration; - -struct credentials { - std::string client_id; - std::optional username; - std::optional 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); - } -}; - -class session_state { - uint8_t _flags = 0b00; - - static constexpr uint8_t session_present_flag = 0b01; - static constexpr uint8_t subscriptions_present_flag = 0b10; -public: - void session_present(bool present) { - return update_flag(present, session_present_flag); - } - - bool session_present() const { - return _flags & session_present_flag; - } - - void subscriptions_present(bool present) { - return update_flag(present, subscriptions_present_flag); - } - - bool subscriptions_present() const { - return _flags & subscriptions_present_flag; - } - -private: - void update_flag(bool set, uint8_t flag) { - if (set) - _flags |= flag; - else - _flags &= ~flag; - } -}; - -struct mqtt_ctx { - credentials creds; - std::optional will_msg; - uint16_t keep_alive = 60; - connect_props co_props; - connack_props ca_props; - session_state state; - any_authenticator authenticator; - - mqtt_ctx() = default; - - mqtt_ctx(const mqtt_ctx& other) : - creds(other.creds), will_msg(other.will_msg), - keep_alive(other.keep_alive), co_props(other.co_props), - ca_props {}, state {}, - authenticator(other.authenticator) - {} -}; - -struct disconnect_ctx { - 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 diff --git a/include/async_mqtt5/detail/log_invoke.hpp b/include/async_mqtt5/detail/log_invoke.hpp deleted file mode 100644 index 583f8ed..0000000 --- a/include/async_mqtt5/detail/log_invoke.hpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_LOG_INVOKE_HPP -#define ASYNC_MQTT5_LOG_INVOKE_HPP - -#include -#include - -#include -#include - -#include -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; -using boost::system::error_code; - -template -class log_invoke { - LoggerType _logger; -public: - explicit log_invoke(LoggerType logger = {}) : - _logger(std::move(logger)) - {} - - void at_resolve( - error_code ec, std::string_view host, std::string_view port, - const asio::ip::tcp::resolver::results_type& eps - ) { - if constexpr (has_at_resolve) - _logger.at_resolve(ec, host, port, eps); - } - - void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) { - if constexpr (has_at_tcp_connect) - _logger.at_tcp_connect(ec, ep); - } - - void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) { - if constexpr (has_at_tls_handshake) - _logger.at_tls_handshake(ec, ep); - } - - void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) { - if constexpr (has_at_ws_handshake) - _logger.at_ws_handshake(ec, ep); - } - - void at_connack( - reason_code rc, - bool session_present, const connack_props& ca_props - ) { - if constexpr (has_at_connack) - _logger.at_connack(rc, session_present, ca_props); - } - - void at_disconnect(reason_code rc, const disconnect_props& dc_props) { - if constexpr (has_at_disconnect) - _logger.at_disconnect(rc, dc_props); - } - -}; - -} // end namespace async_mqtt5::detail - - -#endif // !ASYNC_MQTT5_LOG_INVOKE_HPP diff --git a/include/async_mqtt5/detail/topic_validation.hpp b/include/async_mqtt5/detail/topic_validation.hpp deleted file mode 100644 index 6dcbb9e..0000000 --- a/include/async_mqtt5/detail/topic_validation.hpp +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_TOPIC_VALIDATION_HPP -#define ASYNC_MQTT5_TOPIC_VALIDATION_HPP - -#include -#include - -#include - -namespace async_mqtt5::detail { - -static constexpr int32_t min_subscription_identifier = 1; -static constexpr int32_t max_subscription_identifier = 268'435'455; - -static constexpr std::string_view shared_sub_prefix = "$share/"; - -inline bool is_utf8_no_wildcard(validation_result result) { - return result == validation_result::valid; -} - -inline bool is_not_empty(size_t sz) { - return sz != 0; -} - -inline bool is_valid_topic_size(size_t sz) { - return is_not_empty(sz) && is_valid_string_size(sz); -} - -inline validation_result validate_topic_name(std::string_view str) { - return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard); -} - -inline validation_result validate_topic_alias_name(std::string_view str) { - return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard); -} - -inline validation_result validate_shared_topic_name(std::string_view str) { - return validate_impl(str, is_not_empty, is_utf8_no_wildcard); -} - -inline validation_result validate_topic_filter(std::string_view str) { - if (!is_valid_topic_size(str.size())) - return validation_result::invalid; - - constexpr int multi_lvl_wildcard = '#'; - constexpr int single_lvl_wildcard = '+'; - - // must be the last character preceded by '/' or stand alone - // #, .../# - if (str.back() == multi_lvl_wildcard) { - str.remove_suffix(1); - - if (!str.empty() && str.back() != '/') - return validation_result::invalid; - } - - int last_c = -1; - validation_result result; - while (!str.empty()) { - int c = pop_front_unichar(str); - - // can be used at any level, but must occupy an entire level - // +, +/..., .../+/..., .../+ - bool is_valid_single_lvl = (c == single_lvl_wildcard) && - (str.empty() || str.front() == '/') && - (last_c == -1 || last_c == '/'); - - result = validate_mqtt_utf8_char(c); - if ( - result == validation_result::valid || - is_valid_single_lvl - ) { - last_c = c; - continue; - } - - return validation_result::invalid; - } - - return validation_result::valid; -} - -inline validation_result validate_shared_topic_filter( - std::string_view str, bool wildcard_allowed = true -) { - if (!is_valid_topic_size(str.size())) - return validation_result::invalid; - - if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0) - return validation_result::invalid; - - str.remove_prefix(shared_sub_prefix.size()); - - size_t share_name_end = str.find_first_of('/'); - if (share_name_end == std::string::npos) - return validation_result::invalid; - - validation_result result; - result = validate_shared_topic_name(str.substr(0, share_name_end)); - - if (result != validation_result::valid) - return validation_result::invalid; - - auto topic_filter = str.substr(share_name_end + 1); - return wildcard_allowed ? - validate_topic_filter(topic_filter) : - validate_topic_name(topic_filter) - ; -} - -} // end namespace async_mqtt5::detail - -#endif //ASYNC_MQTT5_TOPIC_VALIDATION_HPP diff --git a/include/async_mqtt5/detail/utf8_mqtt.hpp b/include/async_mqtt5/detail/utf8_mqtt.hpp deleted file mode 100644 index 5939898..0000000 --- a/include/async_mqtt5/detail/utf8_mqtt.hpp +++ /dev/null @@ -1,116 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_UTF8_MQTT_HPP -#define ASYNC_MQTT5_UTF8_MQTT_HPP - -#include -#include -#include -#include - -namespace async_mqtt5::detail { - -enum class validation_result : uint8_t { - valid = 0, - has_wildcard_character, - invalid -}; - -inline int pop_front_unichar(std::string_view& s) { - // assuming that s.length() is > 0 - - int n = s[0] & 0xF0; - int ch = -1; - - if ((n & 0x80) == 0) { - ch = s[0]; - s.remove_prefix(1); - } - else if ((n == 0xC0 || n == 0xD0) && s.size() > 1) { - ch = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F); - s.remove_prefix(2); - } - else if ((n == 0xE0) && s.size() > 2) { - ch = ((s[0] & 0x1F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); - s.remove_prefix(3); - } - else if ((n == 0xF0) && s.size() > 3) { - ch = ((s[0] & 0x1F) << 18) | ((s[1] & 0x3F) << 12) | - ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); - s.remove_prefix(4); - } - - return ch; -} - -inline validation_result validate_mqtt_utf8_char(int c) { - constexpr int fe_flag = 0xFE; - constexpr int ff_flag = 0xFF; - - constexpr int multi_lvl_wildcard = '#'; - constexpr int single_lvl_wildcard = '+'; - - if (c == multi_lvl_wildcard || c == single_lvl_wildcard) - return validation_result::has_wildcard_character; - - if (c > 0x001F && // U+0000...U+001F control characters - (c < 0x007F || c > 0x009F) && // U+007F...0+009F control characters - (c < 0xD800 || c > 0xDFFF) && // U+D800...U+DFFF surrogates - (c < 0xFDD0 || c > 0xFDEF) && // U+FDD0...U+FDEF non-characters - (c & fe_flag) != fe_flag && // non-characters - (c & ff_flag) != ff_flag - ) - return validation_result::valid; - - return validation_result::invalid; -} - -inline bool is_valid_string_size(size_t sz) { - constexpr size_t max_sz = 65535; - return sz <= max_sz; -} - -inline bool is_utf8(validation_result result) { - return result == validation_result::valid || - result == validation_result::has_wildcard_character; -} - -template -validation_result validate_impl( - std::string_view str, - ValidSizeCondition&& size_condition, ValidCondition&& condition -) { - if (!size_condition(str.size())) - return validation_result::invalid; - - validation_result result; - while (!str.empty()) { - int c = pop_front_unichar(str); - - result = validate_mqtt_utf8_char(c); - if (!condition(result)) - return result; - } - - return validation_result::valid; -} - -inline validation_result validate_mqtt_utf8(std::string_view str) { - return validate_impl(str, is_valid_string_size, is_utf8); -} - -inline bool is_valid_string_pair( - const std::pair& str_pair -) { - return validate_mqtt_utf8(str_pair.first) == validation_result::valid && - validate_mqtt_utf8(str_pair.second) == validation_result::valid; -} - -} // namespace async_mqtt5::detail - -#endif //ASYNC_MQTT5_UTF8_MQTT_HPP diff --git a/include/async_mqtt5/error.hpp b/include/async_mqtt5/error.hpp deleted file mode 100644 index 87ef7d1..0000000 --- a/include/async_mqtt5/error.hpp +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_ERROR_HPP -#define ASYNC_MQTT5_ERROR_HPP - -#include -#include -#include - -#include - -namespace async_mqtt5 { - -/** - * \brief A representation of Disconnect Reason Code. - * - * \details Represents all Reason Codes that the Client can send to the Server - * in the \__DISCONNECT\__ packet as the reason for the disconnection. - */ -enum class disconnect_rc_e : uint8_t { - /** Close the connection normally. Do not send the Will Message. */ - normal_disconnection = 0x00, - - /** The Client wishes to disconnect but requires that - the Server also publishes its Will Message. */ - disconnect_with_will_message = 0x04 -}; - -namespace detail { - -enum class disconnect_rc_e : 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 { -/** - * \brief Defines error codes related to MQTT client. - * - * \details Encapsulates errors that occur on the client side. - */ -enum class error : int { - /** The packet is malformed */ - malformed_packet = 100, - - /** The packet has exceeded the Maximum Packet Size the Server is willing to accept */ - packet_too_large, - - /** The Client's session does not exist or it has expired */ - session_expired, - - /** There are no more available Packet Identifiers to use */ - pid_overrun, - - /** The Topic is invalid and does not conform to the specification */ - invalid_topic, - - // publish - /** The Server does not support the specified \ref qos_e */ - qos_not_supported, - - /** The Server does not support retained messages */ - retain_not_available, - - /** The Client attempted to send a Topic Alias that is greater than Topic Alias Maximum */ - topic_alias_maximum_reached, - - // subscribe - /** The Server does not support Wildcard Subscriptions */ - wildcard_subscription_not_available, - - /** The Server does not support this Subscription Identifier */ - subscription_identifier_not_available, - - /** The Server does not support Shared Subscriptions */ - shared_subscription_not_available -}; - - -inline std::string client_error_to_string(error err) { - switch (err) { - case error::malformed_packet: - return "The packet is malformed"; - case error::packet_too_large: - return "The packet has exceeded the Maximum Packet Size " - "the Server is willing to accept"; - case error::session_expired: - return "The Client's session does not exist or it has expired"; - case error::pid_overrun: - return "There are no more available Packet Identifiers to use"; - case error::invalid_topic: - return "The Topic is invalid and " - "does not conform to the specification"; - case error::qos_not_supported: - return "The Server does not support the specified QoS"; - case error::retain_not_available: - return "The Server does not support retained messages"; - case error::topic_alias_maximum_reached: - return "The Client attempted to send a Topic Alias " - "that is greater than Topic Alias Maximum"; - case error::wildcard_subscription_not_available: - return "The Server does not support Wildcard Subscriptions"; - case error::subscription_identifier_not_available: - return "The Server does not support this Subscription Identifier"; - case error::shared_subscription_not_available: - return "The Server does not support Shared Subscriptions"; - 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(ev)); - } -}; - -/// Returns the error category associated with \ref client::error. -inline const client_ec_category& get_error_code_category() { - static client_ec_category cat; - return cat; -} - -/// Creates an \ref error_code from a \ref client::error. -inline boost::system::error_code make_error_code(error r) { - return { static_cast(r), get_error_code_category() }; -} - -inline std::ostream& operator<<(std::ostream& os, const error& err) { - os << get_error_code_category().name() << ":" << static_cast(err); - return os; -} - -} // end namespace client - -} // end namespace async_mqtt5 - -namespace boost::system { - -template <> -struct is_error_code_enum : std::true_type {}; - -} // end namespace boost::system - - -#endif // !ASYNC_MQTT5_ERROR_HPP diff --git a/include/async_mqtt5/impl/assemble_op.hpp b/include/async_mqtt5/impl/assemble_op.hpp deleted file mode 100644 index 33c9b0c..0000000 --- a/include/async_mqtt5/impl/assemble_op.hpp +++ /dev/null @@ -1,243 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_ASSEMBLE_OP_HPP -#define ASYNC_MQTT5_ASSEMBLE_OP_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -class data_span : private std::pair { - using base = std::pair; -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 -class assemble_op { - using client_service = ClientService; - using handler_type = Handler; - - struct on_read {}; - - static constexpr uint32_t max_recv_size = 65'536; - - client_service& _svc; - handler_type _handler; - - std::string& _read_buff; - data_span& _data_span; - -public: - assemble_op( - client_service& svc, handler_type&& 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; - - assemble_op& operator=(assemble_op&&) noexcept = default; - assemble_op& operator=(const assemble_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_handler); - } - - template - void perform(CompletionCondition cc) { - _read_buff.erase( - _read_buff.cbegin(), _data_span.first() - ); - _read_buff.resize( - _svc.connect_property(prop::maximum_packet_size).value_or(max_recv_size) - ); - _data_span = { - _read_buff.cbegin(), - _read_buff.cbegin() + _data_span.size() - }; - - if (cc(error_code {}, 0) == 0 && _data_span.size()) { - return asio::post( - _svc.get_executor(), - asio::prepend( - std::move(*this), on_read {}, error_code {}, - 0, 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), compute_read_timeout(), - asio::prepend( - asio::append(std::move(*this), std::move(cc)), - on_read {} - ) - ); - } - - template - void operator()( - on_read, error_code ec, size_t bytes_read, - CompletionCondition cc - ) { - if (ec == asio::error::try_again) { - _svc.update_session_state(); - _svc._async_sender.resend(); - _data_span = { _read_buff.cend(), _read_buff.cend() }; - return perform(std::move(cc)); - } - - if (ec) - return complete(ec, 0, {}, {}); - - _data_span.expand_suffix(bytes_read); - assert(_data_span.size()); - - auto control_byte = uint8_t(*_data_span.first()); - - if ((control_byte & 0b11110000) == 0) - // close the connection, cancel - return complete(client::error::malformed_packet, 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(asio::transfer_at_least(1)); - return complete(client::error::malformed_packet, 0, {}, {}); - } - - auto recv_size = _svc.connect_property(prop::maximum_packet_size) - .value_or(max_recv_size); - if (static_cast(*varlen) > recv_size - std::distance(_data_span.first(), first)) - return complete(client::error::malformed_packet, 0, {}, {}); - - if (std::distance(first, _data_span.last()) < *varlen) - return perform(asio::transfer_at_least(1)); - - _data_span.remove_prefix( - std::distance(_data_span.first(), first) + *varlen - ); - - dispatch(control_byte, first, first + *varlen); - } - -private: - duration compute_read_timeout() const { - auto negotiated_ka = _svc.negotiated_keep_alive(); - return negotiated_ka ? - std::chrono::milliseconds(3 * negotiated_ka * 1000 / 2) : - duration(std::numeric_limits::max()); - } - - static bool valid_header(uint8_t control_byte) { - auto code = control_code_e(control_byte & 0b11110000); - - if (code == control_code_e::publish) - return true; - - auto res = control_byte & 0b00001111; - if (code == control_code_e::pubrel) - return res == 0b00000010; - return res == 0b00000000; - } - - void dispatch( - uint8_t control_byte, byte_citer first, byte_citer last - ) { - using namespace decoders; - - if (!valid_header(control_byte)) - return complete(client::error::malformed_packet, 0, {}, {}); - - auto code = control_code_e(control_byte & 0b11110000); - - if (code == control_code_e::pingresp) - return perform(asio::transfer_at_least(0)); - - bool is_reply = code != control_code_e::publish && - code != control_code_e::auth && - code != control_code_e::disconnect; - - if (is_reply) { - auto packet_id = decoders::decode_packet_id(first).value(); - _svc._replies.dispatch(error_code {}, code, packet_id, first, last); - return perform(asio::transfer_at_least(0)); - } - - complete(error_code {}, control_byte, first, last); - } - - void complete( - error_code ec, uint8_t control_code, - byte_citer first, byte_citer last - ) { - if (ec) - _data_span = { _read_buff.cend(), _read_buff.cend() }; - std::move(_handler)(ec, control_code, first, last); - } -}; - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_ASSEMBLE_OP_HPP diff --git a/include/async_mqtt5/impl/async_sender.hpp b/include/async_mqtt5/impl/async_sender.hpp deleted file mode 100644 index 32b36a3..0000000 --- a/include/async_mqtt5/impl/async_sender.hpp +++ /dev/null @@ -1,303 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_ASYNC_SENDER_HPP -#define ASYNC_MQTT5_ASYNC_SENDER_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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; - - using handler_type = asio::any_completion_handler; - handler_type _handler; - -public: - write_req( - asio::const_buffer buffer, - serial_num_t serial_num, unsigned flags, - handler_type handler - ) : - _buffer(buffer), _serial_num(serial_num), _flags(flags), - _handler(std::move(handler)) - {} - - write_req(write_req&&) = default; - write_req(const write_req&) = delete; - - write_req& operator=(write_req&&) = default; - write_req& operator=(const write_req&) = delete; - - static serial_num_t next_serial_num(serial_num_t last) { - return last + 1; - } - - asio::const_buffer buffer() const { - return _buffer; - } - - void complete(error_code ec) { - std::move(_handler)(ec); - } - - void complete_post(const asio::any_io_executor& ex, error_code ec) { - asio::post( - ex, - asio::prepend(std::move(_handler), ec) - ); - } - - bool empty() const { - return !_handler; - } - - 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) < (1u << (SERIAL_BITS - 1)); - - return (s1 - s2) >= (1u << (SERIAL_BITS - 1)); - } - -private: - bool prioritized() const { - return _flags & send_flag::prioritized; - } -}; - - -template -class async_sender { - using self_type = async_sender; - - using client_service = ClientService; - - using queue_allocator_type = asio::recycling_allocator; - using write_queue_t = std::vector; - - 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: - explicit async_sender(ClientService& svc) : _svc(svc) {} - - async_sender(async_sender&&) = default; - async_sender(const async_sender&) = delete; - - async_sender& operator=(async_sender&&) = default; - async_sender& operator=(const async_sender&) = delete; - - using allocator_type = queue_allocator_type; - allocator_type get_allocator() const noexcept { - return allocator_type {}; - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc.get_executor(); - } - - serial_num_t next_serial_num() { - return _last_serial_num = write_req::next_serial_num(_last_serial_num); - } - - template - decltype(auto) async_send( - const BufferType& buffer, - serial_num_t serial_num, unsigned flags, - CompletionToken&& token - ) { - using Signature = void (error_code); - - auto initiation = []( - auto handler, self_type& self, const BufferType& buffer, - serial_num_t serial_num, unsigned flags - ) { - self._write_queue.emplace_back( - asio::buffer(buffer), serial_num, flags, std::move(handler) - ); - self.do_write(); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this), - buffer, serial_num, flags - ); - } - - void cancel() { - auto ops = std::move(_write_queue); - for (auto& op : ops) - op.complete_post(_svc.get_executor(), 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_property(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) { - _svc.update_session_state(); - _write_queue.insert( - _write_queue.begin(), - std::make_move_iterator(write_queue.begin()), - std::make_move_iterator(write_queue.end()) - ); - return resend(); - } - - if (ec == asio::error::no_recovery) - _svc.cancel(); - - // 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 { - for (write_req& req : _write_queue) - if (!req.throttled()) - write_queue.push_back(std::move(req)); - else if (_quota > 0) { - --_quota; - write_queue.push_back(std::move(req)); - } - - if (write_queue.empty()) { - _write_in_progress = false; - return; - } - - auto it = std::remove_if( - _write_queue.begin(), _write_queue.end(), - [](const write_req& req) { return req.empty(); } - ); - _write_queue.erase(it, _write_queue.end()); - } - - std::vector 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 diff --git a/include/async_mqtt5/impl/autoconnect_stream.hpp b/include/async_mqtt5/impl/autoconnect_stream.hpp deleted file mode 100644 index 7101395..0000000 --- a/include/async_mqtt5/impl/autoconnect_stream.hpp +++ /dev/null @@ -1,236 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP -#define ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; -using error_code = boost::system::error_code; - -template < - typename StreamType, - typename StreamContext = std::monostate, - typename LoggerType = noop_logger -> -class autoconnect_stream { -public: - using self_type = autoconnect_stream; - using stream_type = StreamType; - using stream_context_type = StreamContext; - using logger_type = LoggerType; - using executor_type = typename stream_type::executor_type; -private: - using stream_ptr = std::shared_ptr; - - 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; - - log_invoke& _log; - - template - friend class read_op; - - template - friend class write_op; - - template - friend class reconnect_op; - -public: - autoconnect_stream( - const executor_type& ex, stream_context_type& context, - log_invoke& log - ) : - _stream_executor(ex), - _conn_mtx(_stream_executor), - _read_timer(_stream_executor), _connect_timer(_stream_executor), - _endpoints(_stream_executor, _connect_timer, log), - _stream_context(context), - _log(log) - { - replace_next_layer(construct_next_layer()); - } - - autoconnect_stream(const autoconnect_stream&) = delete; - autoconnect_stream& operator=(const autoconnect_stream&) = delete; - - 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); - } - - void clone_endpoints(const autoconnect_stream& other) { - _endpoints.clone_servers(other._endpoints); - } - - bool is_open() const noexcept { - return lowest_layer(*_stream_ptr).is_open(); - } - - void open() { - open_lowest_layer(_stream_ptr, asio::ip::tcp::v4()); - } - - void cancel() { - error_code ec; - lowest_layer(*_stream_ptr).cancel(ec); - _conn_mtx.cancel(); - _connect_timer.cancel(); - } - - void close() { - error_code ec; - shutdown(asio::ip::tcp::socket::shutdown_both); - lowest_layer(*_stream_ptr).close(ec); - } - - 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 - decltype(auto) async_read_some( - const BufferType& buffer, duration wait_for, CompletionToken&& token - ) { - using Signature = void (error_code, size_t); - - auto initiation = []( - auto handler, self_type& self, - const BufferType& buffer, duration wait_for - ) { - read_op { self, std::move(handler) }.perform(buffer, wait_for); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this), buffer, wait_for - ); - } - - template - decltype(auto) async_write( - const BufferType& buffer, CompletionToken&& token - ) { - using Signature = void (error_code, size_t); - - auto initiation = []( - auto handler, self_type& self, const BufferType& buffer - ) { - write_op { self, std::move(handler) }.perform(buffer); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this), buffer - ); - } - -private: - - log_invoke& log() { - return _log; - } - - static void open_lowest_layer(const stream_ptr& sptr, asio::ip::tcp protocol) { - error_code ec; - auto& layer = lowest_layer(*sptr); - layer.open(protocol, ec); - layer.set_option(asio::socket_base::reuse_address(true), ec); - layer.set_option(asio::ip::tcp::no_delay(true), ec); - } - - stream_ptr construct_next_layer() const { - stream_ptr sptr; - if constexpr (has_tls_context) - sptr = std::make_shared( - _stream_executor, _stream_context.tls_context() - ); - else - sptr = std::make_shared(_stream_executor); - - return sptr; - } - - stream_ptr construct_and_open_next_layer(asio::ip::tcp protocol) const { - auto sptr = construct_next_layer(); - open_lowest_layer(sptr, protocol); - 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 - decltype(auto) async_reconnect(stream_ptr s, CompletionToken&& token) { - using Signature = void (error_code); - - auto initiation = [](auto handler, self_type& self, stream_ptr s) { - reconnect_op { self, std::move(handler) }.perform(std::move(s)); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this), std::move(s) - ); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP diff --git a/include/async_mqtt5/impl/client_service.hpp b/include/async_mqtt5/impl/client_service.hpp deleted file mode 100644 index 8f12726..0000000 --- a/include/async_mqtt5/impl/client_service.hpp +++ /dev/null @@ -1,524 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_CLIENT_SERVICE_HPP -#define ASYNC_MQTT5_CLIENT_SERVICE_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template < - typename StreamType, typename TlsContext, - typename Enable = void -> -class stream_context; - -template < - typename StreamType, typename TlsContext -> -class stream_context< - StreamType, TlsContext, - std::enable_if_t> -> { - using tls_context_type = TlsContext; - - mqtt_ctx _mqtt_context; - std::shared_ptr _tls_context_ptr; - -public: - explicit stream_context(TlsContext tls_context) : - _tls_context_ptr(std::make_shared(std::move(tls_context))) - {} - - stream_context(const stream_context& other) : - _mqtt_context(other._mqtt_context), _tls_context_ptr(other._tls_context_ptr) - {} - - auto& mqtt_context() { - return _mqtt_context; - } - - const auto& mqtt_context() const { - return _mqtt_context; - } - - auto& tls_context() { - return *_tls_context_ptr; - } - - auto& session_state() { - return _mqtt_context.state; - } - - const auto& session_state() const { - return _mqtt_context.state; - } - - void will(will will) { - _mqtt_context.will_msg = std::move(will); - } - - template - const auto& connack_property( - std::integral_constant prop - ) const { - return _mqtt_context.ca_props[prop]; - } - - const auto& connack_properties() const { - return _mqtt_context.ca_props; - } - - template - const auto& connect_property( - std::integral_constant prop - ) const { - return _mqtt_context.co_props[prop]; - } - - template - auto& connect_property( - std::integral_constant prop - ) { - return _mqtt_context.co_props[prop]; - } - - void connect_properties(connect_props props) { - _mqtt_context.co_props = std::move(props); - } - - void credentials( - std::string client_id, - std::string username = "", std::string password = "" - ) { - _mqtt_context.creds = { - std::move(client_id), - std::move(username), std::move(password) - }; - } - - template - void authenticator(Authenticator&& authenticator) { - _mqtt_context.authenticator = any_authenticator( - std::forward(authenticator) - ); - } -}; - -template -class stream_context< - StreamType, std::monostate, - std::enable_if_t> -> { - mqtt_ctx _mqtt_context; -public: - explicit stream_context(std::monostate) {} - - stream_context(const stream_context& other) : - _mqtt_context(other._mqtt_context) - {} - - auto& mqtt_context() { - return _mqtt_context; - } - - const auto& mqtt_context() const { - return _mqtt_context; - } - - auto& session_state() { - return _mqtt_context.state; - } - - const auto& session_state() const { - return _mqtt_context.state; - } - - void will(will will) { - _mqtt_context.will_msg = std::move(will); - } - - template - const auto& connack_property( - std::integral_constant prop - ) const { - return _mqtt_context.ca_props[prop]; - } - - const auto& connack_properties() const { - return _mqtt_context.ca_props; - } - - template - const auto& connect_property( - std::integral_constant prop - ) const { - return _mqtt_context.co_props[prop]; - } - - template - auto& connect_property( - std::integral_constant prop - ) { - return _mqtt_context.co_props[prop]; - } - - void connect_properties(connect_props props) { - _mqtt_context.co_props = std::move(props); - } - - void credentials( - std::string client_id, - std::string username = "", std::string password = "" - ) { - _mqtt_context.creds = { - std::move(client_id), - std::move(username), std::move(password) - }; - } - - template - void authenticator(Authenticator&& authenticator) { - _mqtt_context.authenticator = any_authenticator( - std::forward(authenticator) - ); - } -}; - -template < - typename StreamType, - typename TlsContext = std::monostate, - typename LoggerType = noop_logger -> -class client_service { - using self_type = client_service; - using stream_context_type = stream_context; - using stream_type = autoconnect_stream< - StreamType, stream_context_type, LoggerType - >; -public: - using executor_type = typename stream_type::executor_type; -private: - using tls_context_type = TlsContext; - using logger_type = LoggerType; - using receive_channel = asio::experimental::basic_channel< - executor_type, - channel_traits<>, - void (error_code, std::string, std::string, publish_props) - >; - - template - friend class run_op; - - template - friend class async_sender; - - template - friend class assemble_op; - - template - friend class ping_op; - - template - friend class sentry_op; - - template - friend class re_auth_op; - - executor_type _executor; - - log_invoke _log; - - stream_context_type _stream_context; - stream_type _stream; - - packet_id_allocator _pid_allocator; - replies _replies; - async_sender _async_sender; - - std::string _read_buff; - data_span _active_span; - - receive_channel _rec_channel; - - asio::steady_timer _ping_timer; - asio::steady_timer _sentry_timer; - - client_service(const client_service& other) : - _executor(other._executor), - _log(other._log), - _stream_context(other._stream_context), - _stream(_executor, _stream_context, _log), - _replies(_executor), - _async_sender(*this), - _active_span(_read_buff.cend(), _read_buff.cend()), - _rec_channel(_executor, std::numeric_limits::max()), - _ping_timer(_executor), - _sentry_timer(_executor) - { - _stream.clone_endpoints(other._stream); - } - -public: - - explicit client_service( - const executor_type& ex, - tls_context_type tls_context = {}, logger_type logger = {} - ) : - _executor(ex), - _log(std::move(logger)), - _stream_context(std::move(tls_context)), - _stream(ex, _stream_context, _log), - _replies(ex), - _async_sender(*this), - _active_span(_read_buff.cend(), _read_buff.cend()), - _rec_channel(ex, std::numeric_limits::max()), - _ping_timer(ex), - _sentry_timer(ex) - {} - - executor_type get_executor() const noexcept { - return _executor; - } - - auto dup() const { - return std::shared_ptr(new client_service(*this)); - } - - template < - typename Ctx = TlsContext, - std::enable_if_t, bool> = true - > - decltype(auto) tls_context() { - 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 Authenticator, - std::enable_if_t, bool> = true - > - void authenticator(Authenticator&& authenticator) { - if (!is_open()) - _stream_context.authenticator( - std::forward(authenticator) - ); - } - - uint16_t negotiated_keep_alive() const { - return connack_property(prop::server_keep_alive) - .value_or(_stream_context.mqtt_context().keep_alive); - } - - void keep_alive(uint16_t seconds) { - if (!is_open()) - _stream_context.mqtt_context().keep_alive = seconds; - } - - template - const auto& connect_property( - std::integral_constant prop - ) const { - return _stream_context.connect_property(prop); - } - - template - void connect_property( - std::integral_constant prop, - prop::value_type_t

value - ){ - if (!is_open()) - _stream_context.connect_property(prop) = value; - } - - void connect_properties(connect_props props) { - if (!is_open()) - _stream_context.connect_properties(std::move(props)); - } - - template - const auto& connack_property( - std::integral_constant prop - ) const { - return _stream_context.connack_property(prop); - } - - const auto& connack_properties() const { - return _stream_context.connack_properties(); - } - - void open_stream() { - _stream.open(); - } - - bool is_open() const { - return _stream.is_open(); - } - - void close_stream() { - _stream.close(); - } - - void cancel() { - if (!_stream.is_open()) return; - - _ping_timer.cancel(); - _sentry_timer.cancel(); - - _rec_channel.close(); - _replies.cancel_unanswered(); - _async_sender.cancel(); - _stream.cancel(); - _stream.close(); - } - - log_invoke& log() { - return _log; - } - - 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(); - } - - bool subscriptions_present() const { - return _stream_context.session_state().subscriptions_present(); - } - - void subscriptions_present(bool present) { - _stream_context.session_state().subscriptions_present(present); - } - - void update_session_state() { - auto& session_state = _stream_context.session_state(); - - if (!session_state.session_present()) { - _replies.clear_pending_pubrels(); - session_state.session_present(true); - - if (session_state.subscriptions_present()) { - channel_store_error(client::error::session_expired); - session_state.subscriptions_present(false); - } - } - - _ping_timer.cancel(); - } - - 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) - ); - } - - bool channel_store_error(error_code ec) { - return _rec_channel.try_send( - ec, std::string {}, std::string {}, publish_props {} - ); - } - - template - 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(token) - ); - } - - template - decltype(auto) async_assemble(CompletionToken&& token) { - using Signature = void (error_code, uint8_t, byte_citer, byte_citer); - - auto initiation = [] ( - auto handler, self_type& self, - std::string& read_buff, data_span& active_span - ) { - assemble_op { - self, std::move(handler), read_buff, active_span - }.perform(asio::transfer_at_least(0)); - }; - - return asio::async_initiate ( - initiation, token, std::ref(*this), - std::ref(_read_buff), std::ref(_active_span) - ); - } - - template - 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(token) - ); - } - - template - decltype(auto) async_channel_receive(CompletionToken&& token) { - return _rec_channel.async_receive(std::forward(token)); - } - -}; - -} // namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_CLIENT_SERVICE_HPP diff --git a/include/async_mqtt5/impl/codecs/base_decoders.hpp b/include/async_mqtt5/impl/codecs/base_decoders.hpp deleted file mode 100644 index 9c95391..0000000 --- a/include/async_mqtt5/impl/codecs/base_decoders.hpp +++ /dev/null @@ -1,461 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_BASE_DECODERS_HPP -#define ASYNC_MQTT5_BASE_DECODERS_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -namespace async_mqtt5::decoders { - -namespace x3 = boost::spirit::x3; - -template -struct convert { using type = T; }; - -template -struct convert> { - using type = std::tuple::type...>; -}; - -template -struct convert> { - using type = std::optional; -}; - -template -struct convert> { - using type = std::vector::type>; -}; - -template -constexpr auto as(Parser&& p) { - return x3::rule{} = std::forward(p); -} - - -template -auto type_parse(It& first, const It last, const Parser& p) { - using ctx_type = decltype(x3::make_context(std::declval())); - using attr_type = typename x3::traits::attribute_of::type; - - using rv_type = typename convert::type; - - std::optional rv; - rv_type value {}; - if (x3::phrase_parse(first, last, as(p), x3::eps(false), value)) - rv = std::move(value); - return rv; -} - - -template -auto type_parse(It& first, const It last, const Parser& p) { - std::optional rv; - AttributeType value {}; - if (x3::phrase_parse(first, last, as(p), x3::eps(false), value)) - rv = std::move(value); - return rv; -} - -namespace basic { - -template -constexpr auto to(T& arg) { - return [&](auto& ctx) { - using ctx_type = decltype(ctx); - using attr_type = decltype(x3::_attr(std::declval())); - if constexpr (detail::is_boost_iterator) - arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() }; - else - arg = x3::_attr(ctx); - }; -} - -template -class scope_limit {}; - -template -class scope_limit< - LenParser, Subject, - std::enable_if_t::value> -> : - public x3::unary_parser> -{ - using base_type = x3::unary_parser>; - LenParser _lp; -public: - using ctx_type = - decltype(x3::make_context(std::declval())); - using attribute_type = - typename x3::traits::attribute_of::type; - - static bool const has_attribute = true; - - scope_limit(const LenParser& lp, const Subject& subject) : - base_type(subject), _lp(lp) - {} - - template - bool parse( - It& first, const It last, - const Ctx& ctx, RCtx& rctx, Attr& attr - ) const { - - It iter = first; - typename x3::traits::attribute_of::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 -class scope_limit< - Size, Subject, - std::enable_if_t> -> : - public x3::unary_parser> -{ - using base_type = x3::unary_parser>; - size_t _limit; -public: - using ctx_type = - decltype(x3::make_context(std::declval())); - using attribute_type = - typename x3::traits::attribute_of::type; - - static bool const has_attribute = true; - - scope_limit(Size limit, const Subject& subject) : - base_type(subject), _limit(limit) - {} - - template - 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 -struct scope_limit_gen { - template - auto operator[](const Subject& p) const { - return scope_limit { _lp, x3::as_parser(p) }; - } - LenParser _lp; -}; - -template -struct scope_limit_gen< - Size, - std::enable_if_t> -> { - template - auto operator[](const Subject& p) const { - return scope_limit { limit, x3::as_parser(p) }; - } - Size limit; -}; - -template < - typename Parser, - std::enable_if_t::value, bool> = true -> -scope_limit_gen scope_limit_(const Parser& p) { - return { p }; -} - -template < - typename Size, - std::enable_if_t, bool> = true -> -scope_limit_gen scope_limit_(Size limit) { - return { limit }; -} - -struct verbatim_parser : x3::parser { - using attribute_type = std::string; - static bool const has_attribute = true; - - template - 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 { - using attribute_type = int32_t; - static bool const has_attribute = true; - - template - bool parse( - It& first, const It last, - const Ctx& ctx, RCtx&, Attr& attr - ) const { - - It iter = first; - x3::skip_over(iter, last, ctx); - - if (iter == last) - return false; - - int32_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(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 { - using attribute_type = std::string; - static bool const has_attribute = true; - - template - 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::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 -class conditional_parser : - public x3::unary_parser> { - - using base_type = x3::unary_parser>; - bool _condition; -public: - using ctx_type = - decltype(x3::make_context(std::declval())); - using subject_attr_type = - typename x3::traits::attribute_of::type; - - using attribute_type = boost::optional; - static bool const has_attribute = true; - - conditional_parser(const Subject& s, bool condition) : - base_type(s), _condition(condition) {} - - template - 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 - auto operator[](const Subject& p) const { - return conditional_parser { p, _condition }; - } -}; - -inline conditional_gen if_(bool condition) { - return { condition }; -} - -} // end namespace basic - - -namespace prop { - -namespace basic = async_mqtt5::decoders::basic; - -namespace detail { - -template -bool parse_to_prop( - It& iter, const It last, - const Ctx& ctx, RCtx& rctx, Prop& prop -) { - using namespace async_mqtt5::detail; - using prop_type = std::remove_reference_t; - - bool rv = false; - - if constexpr (std::is_same_v) - rv = x3::byte_.parse(iter, last, ctx, rctx, prop); - else if constexpr (std::is_same_v) - rv = x3::big_word.parse(iter, last, ctx, rctx, prop); - else if constexpr (std::is_same_v) - rv = basic::varint_.parse(iter, last, ctx, rctx, prop); - else if constexpr (std::is_same_v) - rv = x3::big_dword.parse(iter, last, ctx, rctx, prop); - else if constexpr (std::is_same_v) - rv = basic::utf8_.parse(iter, last, ctx, rctx, prop); - - else if constexpr (is_optional) { - typename prop_type::value_type val; - rv = parse_to_prop(iter, last, ctx, rctx, val); - if (rv) prop.emplace(std::move(val)); - } - - else if constexpr (is_pair) { - rv = parse_to_prop(iter, last, ctx, rctx, prop.first); - rv = parse_to_prop(iter, last, ctx, rctx, prop.second); - } - - else if constexpr (is_vector || is_small_vector) { - typename std::remove_reference_t::value_type value; - rv = parse_to_prop(iter, last, ctx, rctx, value); - if (rv) prop.push_back(std::move(value)); - } - - return rv; -} - -} // end namespace detail - -template -class prop_parser : public x3::parser> { -public: - using attribute_type = Props; - static bool const has_attribute = true; - - template - 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; - - int32_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) - return false; - } - - first = iter; - return true; - } -}; - - -template -constexpr auto props_ = prop_parser {}; - -} // end namespace prop - - -} // end namespace async_mqtt5::decoders - -#endif // !ASYNC_MQTT5_BASE_DECODERS_HPP diff --git a/include/async_mqtt5/impl/codecs/base_encoders.hpp b/include/async_mqtt5/impl/codecs/base_encoders.hpp deleted file mode 100644 index db73163..0000000 --- a/include/async_mqtt5/impl/codecs/base_encoders.hpp +++ /dev/null @@ -1,513 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_BASE_ENCODERS_HPP -#define ASYNC_MQTT5_BASE_ENCODERS_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -namespace async_mqtt5::encoders { - -namespace basic { - -using varint_t = int*; - -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 -class flag_def : public encoder { - template - 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 - > - > - >; - - template - friend class flag_def; - - repr _val { 0 }; - -public: - flag_def(repr val) : _val(val) {} - flag_def() = default; - - template < - typename T, - typename projection = boost::identity, - std::enable_if_t, bool> = true - > - auto operator()(T&& value, projection proj = {}) const { - if constexpr (std::is_same_v) { - repr val = value.has_value(); - return flag_def { val }; - } - else { - repr val = value.has_value() ? - static_cast(std::invoke(proj, *value)) : 0; - return flag_def { val }; - } - } - - template < - typename T, - typename projection = boost::identity, - std::enable_if_t, bool> = true - > - auto operator()(T&& value, projection proj = {}) const { - auto val = static_cast(std::invoke(proj, value)); - return flag_def { val }; - } - - size_t byte_size() const { return sizeof(repr); } - - template - auto operator|(const flag_def& rhs) const { - using res_repr = least_type; - auto val = static_cast((_val << rhs_bits) | rhs._val); - return flag_def { 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(s.data() + sz); - endian_store(p, _val); - return s; - } -}; - -template -constexpr auto flag = flag_def{}; - - -template -class int_val : public encoder { - T _val; -public: - int_val(T val) : _val(val) {} - - size_t byte_size() const { - if constexpr (detail::is_optional) { - if (_val) return val_length(*_val); - return 0; - } - else - return val_length(_val); - } - - std::string& encode(std::string& s) const { - if constexpr (detail::is_optional) { - if (_val) return encode_val(s, *_val); - return s; - } - else - return encode_val(s, _val); - } -private: - template - static size_t val_length(U&& val) { - if constexpr (std::is_same_v) - return variable_length(int32_t(val)); - else - return sizeof(Repr); - } - - template - static std::string& encode_val(std::string& s, U&& val) { - using namespace boost::endian; - if constexpr (std::is_same_v) { - to_variable_bytes(s, int32_t(val)); - return s; - } - else { - size_t sz = s.size(); s.resize(sz + sizeof(Repr)); - auto p = reinterpret_cast(s.data() + sz); - endian_store(p, val); - return s; - } - } -}; - -template -class int_def { -public: - template - auto operator()(T&& val) const { - return int_val { std::forward(val) }; - } - - template - auto operator()(T&& val, projection proj) const { - if constexpr (detail::is_optional) { - using rv_type = std::invoke_result_t< - projection, typename boost::remove_cv_ref_t::value_type - >; - if (val.has_value()) - return (*this)(std::invoke(proj, *val)); - return int_val { rv_type {} }; - } - else { - using rv_type = std::invoke_result_t; - return int_val { std::invoke(proj, val) }; - } - } -}; - -constexpr auto byte_ = int_def {}; -constexpr auto int16_ = int_def {}; -constexpr auto int32_ = int_def {}; -constexpr auto varlen_ = int_def {}; - - -template -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 || std::is_same_v - ); - } - - size_t byte_size() const { - if constexpr (detail::is_optional) - return _val ? _with_length * 2 + val_length(*_val) : 0; - else - return _with_length * 2 + val_length(_val); - } - - std::string& encode(std::string& s) const { - if constexpr (detail::is_optional) { - if (_val) return encode_val(s, *_val); - return s; - } - else - return encode_val(s, _val); - } - -private: - template - using has_size = decltype(std::declval().size()); - - template - static size_t val_length(U&& val) { - if constexpr (std::is_same_v, const char*>) - return std::strlen(val); - - if constexpr (boost::is_detected_exact_v) - return val.size(); - else // fallback to type const char (&)[N] (substract 1 for trailing 0) - return sizeof(val) - 1; - } - - template - std::string& encode_val(std::string& s, U&& u) const { - using namespace boost::endian; - auto byte_len = val_length(std::forward(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(s.data() + sz); - endian_store( - p, int16_t(byte_len) - ); - } - s.append(std::begin(u), std::begin(u) + byte_len); - return s; - } -}; - -template -class array_def { -public: - template - auto operator()(T&& val) const { - return array_val { std::forward(val), with_length }; - } - - template - auto operator()(T&& val, projection proj) const { - if constexpr (detail::is_optional) { - using rv_type = std::invoke_result_t< - projection, typename boost::remove_cv_ref_t::value_type - >; - if (val.has_value()) - return (*this)(std::invoke(proj, *val)); - return array_val { rv_type {}, false }; - } - else { - const auto& av = std::invoke(proj, val); - return array_val { av, true }; - } - } -}; - -using utf8_def = array_def; - -constexpr auto utf8_ = utf8_def {}; -constexpr auto binary_ = array_def {}; // for now -constexpr auto verbatim_ = array_def {}; - - -template -class composed_val : public encoder { - T _lhs; U _rhs; -public: - composed_val(T lhs, U rhs) : - _lhs(std::forward(lhs)), _rhs(std::forward(rhs)) - {} - - size_t byte_size() const { - return _lhs.byte_size() + _rhs.byte_size(); - } - - std::string& encode(std::string& s) const { - _lhs.encode(s); - return _rhs.encode(s); - } -}; - -template < - typename T, typename U, - std::enable_if_t< - std::is_base_of_v> && - std::is_base_of_v>, - bool - > = true -> -inline auto operator&(T&& t, U&& u) { - return composed_val(std::forward(t), std::forward(u)); -} - -template < - typename T, - std::enable_if_t>, bool> = true -> -std::string& operator<<(std::string& s, T&& t) { - return t.encode(s); -} - -} // end namespace basic - -namespace prop { - -namespace pp = async_mqtt5::prop; - -template -auto encoder_for_prop_value(const T& val) { - if constexpr (std::is_same_v) - return basic::int_def{}(val); - else if constexpr (std::is_same_v) - return basic::int_def{}(val); - else if constexpr (std::is_same_v) - return basic::int_def{}(val); - else if constexpr (std::is_same_v) - return basic::int_def{}(val); - else if constexpr (std::is_same_v) - return basic::utf8_def{}(val); - else if constexpr (detail::is_pair) - return encoder_for_prop_value(val.first) & - encoder_for_prop_value(val.second); -} - -template -class prop_val; - -template < - typename T, pp::property_type p -> -class prop_val< - T, p, - std::enable_if_t && detail::is_optional> -> : public basic::encoder { - // allows T to be reference type to std::optional - static inline boost::remove_cv_ref_t nulltype; - T _val; -public: - prop_val(T val) : _val(val) { - static_assert(std::is_reference_v); - } - prop_val() : _val(nulltype) {} - - size_t byte_size() const { - if (!_val) return 0; - return 1 + encoder_for_prop_value(*_val).byte_size(); - } - - std::string& encode(std::string& s) const { - if (!_val) - return s; - s.push_back(p); - return encoder_for_prop_value(*_val).encode(s); - } -}; - -template < - typename T, pp::property_type p -> -class prop_val< - T, p, - std::enable_if_t || detail::is_small_vector> -> : public basic::encoder { - // allows T to be reference type to std::vector - static inline boost::remove_cv_ref_t nulltype; - T _val; -public: - prop_val(T val) : _val(val) { - static_assert(std::is_reference_v); - } - - prop_val() : _val(nulltype) { } - - size_t byte_size() const { - if (_val.empty()) return 0; - - size_t total_size = 0; - for (const auto& elem : _val) - total_size += 1 + encoder_for_prop_value(elem).byte_size(); - - return total_size; - } - - std::string& encode(std::string& s) const { - if (_val.empty()) - return s; - - for (const auto& elem: _val) { - s.push_back(p); - encoder_for_prop_value(elem).encode(s); - } - - return s; - } -}; - - -template -class props_val : public basic::encoder { - static inline std::decay_t nulltype; - - template - static auto to_prop_val(const T& val) { - return prop_val(val); - } - - template - static auto to_prop_vals(const pp::properties& props) { - return std::make_tuple( - to_prop_val( - props[std::integral_constant {}] - )... - ); - } - - template - 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())) _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_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 -class props_def { -public: - template - auto operator()(T&& prop_container) const { - if constexpr (detail::is_optional) { - if (prop_container.has_value()) - return (*this)(*prop_container); - return props_val< - const typename boost::remove_cv_ref_t::value_type& - >(true); - } - else { - return props_val { prop_container, may_omit }; - } - } -}; - -constexpr auto props_ = props_def {}; -constexpr auto props_may_omit_ = props_def {}; - -} // end namespace prop - -} // end namespace async_mqtt5::encoders - -#endif // !ASYNC_MQTT5_BASE_ENCODERS_HPP diff --git a/include/async_mqtt5/impl/codecs/message_decoders.hpp b/include/async_mqtt5/impl/codecs/message_decoders.hpp deleted file mode 100644 index d133dba..0000000 --- a/include/async_mqtt5/impl/codecs/message_decoders.hpp +++ /dev/null @@ -1,307 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_MESSAGE_DECODERS_HPP -#define ASYNC_MQTT5_MESSAGE_DECODERS_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include - -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 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 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, // user_name, - std::optional, // password, - uint16_t, // keep_alive, - bool, // clean_start, - connect_props, // props, - std::optional // will ->; - -inline std::optional 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_; - - const byte_citer end = it + remain_length; - auto vh = type_parse(it, end, var_header_); - if (!vh) - return std::optional{}; - - auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh; - - if (mqtt_str != "MQTT" || version != 5) - return std::optional{}; - - 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_] >> - 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, end, payload_); - if (!pload) - return std::optional{}; - - std::optional 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 { std::move(retval) }; -} - -using connack_message = std::tuple< - uint8_t, // session_present - uint8_t, // connect reason code - connack_props // props ->; - -inline std::optional decode_connack( - uint32_t remain_length, byte_citer& it -) { - auto connack_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> x3::byte_ >> prop::props_ - ]; - return type_parse(it, it + remain_length, connack_); -} - -using publish_message = std::tuple< - std::string, // topic - std::optional, // packet_id - uint8_t, // dup_e, qos_e, retain_e - publish_props, // publish props - std::string // payload ->; - -inline std::optional 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_ >> 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 decode_puback( - uint32_t remain_length, byte_citer& it -) { - if (remain_length == 0) - return puback_message {}; - auto puback_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> prop::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 decode_pubrec( - uint32_t remain_length, byte_citer& it -) { - if (remain_length == 0) - return pubrec_message {}; - auto pubrec_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> prop::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 decode_pubrel( - uint32_t remain_length, byte_citer& it -) { - if (remain_length == 0) - return pubrel_message {}; - auto pubrel_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> prop::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 decode_pubcomp( - uint32_t remain_length, byte_citer& it -) { - if (remain_length == 0) - return pubcomp_message {}; - auto pubcomp_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> prop::props_ - ]; - return type_parse(it, it + remain_length, pubcomp_); -} - -using subscribe_message = std::tuple< - subscribe_props, - std::vector> // topic filter with opts ->; - -inline std::optional decode_subscribe( - uint32_t remain_length, byte_citer& it -) { - auto subscribe_ = basic::scope_limit_(remain_length)[ - prop::props_ >> +(basic::utf8_ >> x3::byte_) - ]; - return type_parse(it, it + remain_length, subscribe_); -} - -using suback_message = std::tuple< - suback_props, - std::vector // reason_codes ->; - -inline std::optional decode_suback( - uint32_t remain_length, byte_citer& it -) { - auto suback_ = basic::scope_limit_(remain_length)[ - prop::props_ >> +x3::byte_ - ]; - return type_parse(it, it + remain_length, suback_); -} - -using unsubscribe_message = std::tuple< - unsubscribe_props, - std::vector // topics ->; - -inline std::optional decode_unsubscribe( - uint32_t remain_length, byte_citer& it -) { - auto unsubscribe_ = basic::scope_limit_(remain_length)[ - prop::props_ >> +basic::utf8_ - ]; - return type_parse(it, it + remain_length, unsubscribe_); -} - -using unsuback_message = std::tuple< - unsuback_props, - std::vector // reason_codes ->; - -inline std::optional decode_unsuback( - uint32_t remain_length, byte_citer& it -) { - auto unsuback_ = basic::scope_limit_(remain_length)[ - prop::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 decode_disconnect( - uint32_t remain_length, byte_citer& it -) { - if (remain_length == 0) - return disconnect_message {}; - auto disconnect_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> prop::props_ - ]; - return type_parse(it, it + remain_length, disconnect_); -} - -using auth_message = std::tuple< - uint8_t, // reason_code - auth_props ->; - -inline std::optional decode_auth( - uint32_t remain_length, byte_citer& it -) { - if (remain_length == 0) - return auth_message {}; - auto auth_ = basic::scope_limit_(remain_length)[ - x3::byte_ >> prop::props_ - ]; - return type_parse(it, it + remain_length, auth_); -} - - -} // end namespace async_mqtt5::decoders - -#endif // !ASYNC_MQTT5_MESSAGE_DECODERS_HPP diff --git a/include/async_mqtt5/impl/codecs/message_encoders.hpp b/include/async_mqtt5/impl/codecs/message_encoders.hpp deleted file mode 100644 index 0559b04..0000000 --- a/include/async_mqtt5/impl/codecs/message_encoders.hpp +++ /dev/null @@ -1,431 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_MESSAGE_ENCODERS_HPP -#define ASYNC_MQTT5_MESSAGE_ENCODERS_HPP - -#include -#include -#include -#include -#include - -#include -#include - -namespace async_mqtt5::encoders { - -template -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 user_name, - std::optional password, - uint16_t keep_alive, bool clean_start, - const connect_props& props, - const std::optional& 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 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& 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, - const std::vector& 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& 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, - const std::vector& 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_(uint8_t(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_(uint8_t(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 diff --git a/include/async_mqtt5/impl/connect_op.hpp b/include/async_mqtt5/impl/connect_op.hpp deleted file mode 100644 index 1710568..0000000 --- a/include/async_mqtt5/impl/connect_op.hpp +++ /dev/null @@ -1,436 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_CONNECT_OP_HPP -#define ASYNC_MQTT5_CONNECT_OP_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include - -namespace async_mqtt5::detail { - -template -class connect_op { - static constexpr size_t min_packet_sz = 5; - - struct on_connect {}; - struct on_tls_handshake {}; - struct on_ws_handshake {}; - struct on_send_connect {}; - struct on_fixed_header {}; - struct on_read_packet {}; - struct on_init_auth_data {}; - struct on_auth_data {}; - struct on_send_auth {}; - struct on_complete_auth {}; - - Stream& _stream; - mqtt_ctx& _ctx; - log_invoke& _log; - - using handler_type = asio::any_completion_handler; - handler_type _handler; - - std::unique_ptr _buffer_ptr; - asio::cancellation_state _cancellation_state; - - using endpoint = asio::ip::tcp::endpoint; - -public: - template - connect_op( - Stream& stream, mqtt_ctx& ctx, - log_invoke& log, - Handler&& handler - ) : - _stream(stream), _ctx(ctx), _log(log), - _handler(std::forward(handler)), - _cancellation_state( - asio::get_associated_cancellation_slot(_handler), - asio::enable_total_cancellation {}, - asio::enable_total_cancellation {} - ) - {} - - connect_op(connect_op&&) = default; - connect_op(const connect_op&) = delete; - - connect_op& operator=(connect_op&&) = default; - connect_op& operator=(const connect_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using cancellation_slot_type = - asio::associated_cancellation_slot_t; - cancellation_slot_type get_cancellation_slot() const noexcept { - return _cancellation_state.slot(); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_handler); - } - - void perform(const endpoint& ep, authority_path ap) { - lowest_layer(_stream).async_connect( - ep, - asio::append( - asio::prepend(std::move(*this), on_connect {}), - ep, std::move(ap) - ) - ); - } - - void operator()( - on_connect, error_code ec, endpoint ep, authority_path ap - ) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - _log.at_tcp_connect(ec, ep); - if (ec) - return complete(ec); - - do_tls_handshake(std::move(ep), std::move(ap)); - } - - void do_tls_handshake(endpoint ep, authority_path ap) { - if constexpr (has_tls_handshake) { - _stream.async_handshake( - tls_handshake_type::client, - asio::append( - asio::prepend(std::move(*this), on_tls_handshake {}), - std::move(ep), std::move(ap) - ) - ); - } - else if constexpr ( - has_tls_handshake> - ) { - _stream.next_layer().async_handshake( - tls_handshake_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, endpoint ep, authority_path ap - ) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - _log.at_tls_handshake(ec, ep); - if (ec) - return complete(ec); - - do_ws_handshake(std::move(ep), std::move(ap)); - } - - void do_ws_handshake(endpoint ep, authority_path ap) { - if constexpr (has_ws_handshake) - // If you get a compilation error here, - // it might be because of a missing include - ws_handshake_traits::async_handshake( - _stream, std::move(ap), - asio::append( - asio::prepend(std::move(*this), on_ws_handshake {}), ep - ) - ); - else - (*this)(on_ws_handshake {}, error_code {}, ep); - } - - void operator()(on_ws_handshake, error_code ec, endpoint ep) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if constexpr (has_ws_handshake) - _log.at_ws_handshake(ec, ep); - - if (ec) - return complete(ec); - - auto auth_method = _ctx.authenticator.method(); - if (!auth_method.empty()) { - _ctx.co_props[prop::authentication_method] = auth_method; - return _ctx.authenticator.async_auth( - auth_step_e::client_initial, "", - asio::prepend(std::move(*this), on_init_auth_data {}) - ); - } - - send_connect(); - } - - void operator()(on_init_auth_data, error_code ec, std::string data) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(asio::error::try_again); - - _ctx.co_props[prop::authentication_data] = std::move(data); - send_connect(); - } - - void send_connect() { - auto packet = control_packet::of( - no_pid, get_allocator(), - encoders::encode_connect, - _ctx.creds.client_id, - _ctx.creds.username, _ctx.creds.password, - _ctx.keep_alive, false, _ctx.co_props, _ctx.will_msg - ); - - auto wire_data = packet.wire_data(); - - 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 (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(ec); - - _buffer_ptr = std::make_unique(min_packet_sz, char(0)); - - auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz); - asio::async_read( - _stream, buff, asio::transfer_all(), - asio::prepend(std::move(*this), on_fixed_header {}) - ); - } - - void operator()( - on_fixed_header, error_code ec, size_t num_read - ) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(ec); - - auto code = control_code_e((*_buffer_ptr)[0] & 0b11110000); - - if (code != control_code_e::auth && code != control_code_e::connack) - 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) - return 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); - - if (num_read + remain_len > _buffer_ptr->size()) - _buffer_ptr->resize(num_read + 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::transfer_all(), - asio::prepend( - asio::append(std::move(*this), code, first, last), - on_read_packet {} - ) - ); - } - - void operator()( - on_read_packet, error_code ec, size_t, control_code_e code, - byte_citer first, byte_citer last - ) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(ec); - - if (code == control_code_e::connack) - return on_connack(first, last); - - if (!_ctx.co_props[prop::authentication_method].has_value()) - return complete(client::error::malformed_packet); - - on_auth(first, last); - } - - void on_connack(byte_citer first, byte_citer last) { - auto packet_length = static_cast(std::distance(first, last)); - auto rv = decoders::decode_connack(packet_length, first); - if (!rv.has_value()) - return complete(client::error::malformed_packet); - const auto& [session_present, reason_code, ca_props] = *rv; - - _ctx.ca_props = ca_props; - _ctx.state.session_present(session_present); - - // 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_code); - if (!rc.has_value()) // reason code not allowed in CONNACK - return complete(client::error::malformed_packet); - - _log.at_connack(*rc, session_present, ca_props); - if (*rc) - return complete(asio::error::try_again); - - if (_ctx.co_props[prop::authentication_method].has_value()) - return _ctx.authenticator.async_auth( - auth_step_e::server_final, - ca_props[prop::authentication_data].value_or(""), - asio::prepend(std::move(*this), on_complete_auth {}) - ); - - complete(error_code {}); - } - - void on_auth(byte_citer first, byte_citer last) { - auto packet_length = static_cast(std::distance(first, last)); - auto rv = decoders::decode_auth(packet_length, first); - if (!rv.has_value()) - return complete(client::error::malformed_packet); - const auto& [reason_code, auth_props] = *rv; - - auto rc = to_reason_code(reason_code); - if ( - !rc.has_value() || - auth_props[prop::authentication_method] - != _ctx.co_props[prop::authentication_method] - ) - return complete(client::error::malformed_packet); - - _ctx.authenticator.async_auth( - auth_step_e::server_challenge, - auth_props[prop::authentication_data].value_or(""), - asio::prepend(std::move(*this), on_auth_data {}) - ); - } - - void operator()(on_auth_data, error_code ec, std::string data) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(asio::error::try_again); - - auth_props props; - props[prop::authentication_method] = - _ctx.co_props[prop::authentication_method]; - props[prop::authentication_data] = std::move(data); - - auto packet = control_packet::of( - no_pid, get_allocator(), - encoders::encode_auth, - reason_codes::continue_authentication.value(), props - ); - - auto wire_data = packet.wire_data(); - - detail::async_write( - _stream, asio::buffer(wire_data), - asio::consign( - asio::prepend(std::move(*this), on_send_auth {}), - std::move(packet) - ) - ); - } - - void operator()(on_send_auth, error_code ec, size_t) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(ec); - - auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz); - asio::async_read( - _stream, buff, asio::transfer_all(), - asio::prepend(std::move(*this), on_fixed_header {}) - ); - } - - void operator()(on_complete_auth, error_code ec, std::string) { - if (is_cancelled()) - return complete(asio::error::operation_aborted); - - if (ec) - return complete(asio::error::try_again); - - complete(error_code {}); - } - -private: - bool is_cancelled() const { - return _cancellation_state.cancelled() != asio::cancellation_type::none; - } - - void complete(error_code ec) { - _cancellation_state.slot().clear(); - std::move(_handler)(ec); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_CONNECT_OP_HPP diff --git a/include/async_mqtt5/impl/disconnect_op.hpp b/include/async_mqtt5/impl/disconnect_op.hpp deleted file mode 100644 index 778f4a6..0000000 --- a/include/async_mqtt5/impl/disconnect_op.hpp +++ /dev/null @@ -1,317 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_DISCONNECT_OP_HPP -#define ASYNC_MQTT5_DISCONNECT_OP_HPP - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template < - typename ClientService, - typename DisconnectContext -> -class disconnect_op { - using client_service = ClientService; - - struct on_disconnect {}; - - std::shared_ptr _svc_ptr; - DisconnectContext _context; - - using handler_type = cancellable_handler< - asio::any_completion_handler, - typename ClientService::executor_type - >; - handler_type _handler; - -public: - template - disconnect_op( - std::shared_ptr svc_ptr, - DisconnectContext&& context, Handler&& handler - ) : - _svc_ptr(std::move(svc_ptr)), _context(std::move(context)), - _handler(std::move(handler), _svc_ptr->get_executor()) - { - auto slot = asio::get_associated_cancellation_slot(_handler); - if (slot.is_connected()) - slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { - svc.cancel(); - }); - } - - disconnect_op(disconnect_op&&) = default; - disconnect_op(const disconnect_op&) = delete; - - disconnect_op& operator=(disconnect_op&&) = default; - disconnect_op& operator=(const disconnect_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform() { - error_code ec = validate_disconnect(_context.props); - if (ec) - return complete_immediate(ec); - - auto disconnect = control_packet::of( - no_pid, get_allocator(), - encoders::encode_disconnect, - static_cast(_context.reason_code), _context.props - ); - - auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) - .value_or(default_max_send_size); - if (disconnect.size() > max_packet_size) - // drop properties - return send_disconnect(control_packet::of( - no_pid, get_allocator(), - encoders::encode_disconnect, - static_cast(_context.reason_code), disconnect_props {} - )); - - send_disconnect(std::move(disconnect)); - } - - void send_disconnect(control_packet disconnect) { - auto wire_data = disconnect.wire_data(); - _svc_ptr->async_send( - wire_data, - no_serial, send_flag::terminal, - asio::prepend( - std::move(*this), - on_disconnect {}, std::move(disconnect) - ) - ); - } - - void operator()( - on_disconnect, - control_packet 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. - - if ( - ec == asio::error::operation_aborted || - ec == asio::error::no_recovery - ) - return complete(asio::error::operation_aborted); - - if (ec == asio::error::try_again) { - if (_context.terminal) - return send_disconnect(std::move(disconnect)); - return complete(error_code {}); - } - - if (_context.terminal) { - _svc_ptr->cancel(); - return complete(error_code {}); - } - - _svc_ptr->close_stream(); - _svc_ptr->open_stream(); - - complete(error_code {}); - } - -private: - static error_code validate_disconnect(const disconnect_props& props) { - const auto& reason_string = props[prop::reason_string]; - if ( - reason_string && - validate_mqtt_utf8(*reason_string) != validation_result::valid - ) - return client::error::malformed_packet; - - const auto& user_properties = props[prop::user_property]; - for (const auto& user_property: user_properties) - if (!is_valid_string_pair(user_property)) - return client::error::malformed_packet; - return error_code {}; - } - - void complete(error_code ec) { - _handler.complete(ec); - } - - void complete_immediate(error_code ec) { - _handler.complete_immediate(ec); - } -}; - -template -class terminal_disconnect_op { - using client_service = ClientService; - - static constexpr uint8_t seconds = 5; - - std::shared_ptr _svc_ptr; - std::unique_ptr _timer; - - using handler_type = Handler; - handler_type _handler; - -public: - terminal_disconnect_op( - std::shared_ptr svc_ptr, - Handler&& handler - ) : - _svc_ptr(std::move(svc_ptr)), - _timer(new asio::steady_timer(_svc_ptr->get_executor())), - _handler(std::move(handler)) - {} - - terminal_disconnect_op(terminal_disconnect_op&&) = default; - terminal_disconnect_op(const terminal_disconnect_op&) = delete; - - terminal_disconnect_op& operator=(terminal_disconnect_op&&) = default; - terminal_disconnect_op& operator=(const terminal_disconnect_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using cancellation_slot_type = asio::associated_cancellation_slot_t; - cancellation_slot_type get_cancellation_slot() const noexcept { - return asio::get_associated_cancellation_slot(_handler); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_handler); - } - - template - void perform(DisconnectContext&& context) { - namespace asioex = boost::asio::experimental; - - auto init_disconnect = []( - auto handler, disconnect_ctx ctx, - std::shared_ptr svc_ptr - ) { - disconnect_op { - std::move(svc_ptr), std::move(ctx), std::move(handler) - }.perform(); - }; - - _timer->expires_after(std::chrono::seconds(seconds)); - - auto timed_disconnect = asioex::make_parallel_group( - asio::async_initiate( - init_disconnect, asio::deferred, - std::forward(context), _svc_ptr - ), - _timer->async_wait(asio::deferred) - ); - - timed_disconnect.async_wait( - asioex::wait_for_one(), std::move(*this) - ); - } - - void operator()( - std::array /* ord */, - error_code disconnect_ec, error_code /* timer_ec */ - ) { - std::move(_handler)(disconnect_ec); - } -}; - -template -class initiate_async_disconnect { - std::shared_ptr _svc_ptr; -public: - explicit initiate_async_disconnect(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)) - {} - - using executor_type = typename ClientService::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - template - void operator()( - Handler&& handler, - disconnect_rc_e rc, const disconnect_props& props - ) { - auto ctx = disconnect_ctx { rc, props, terminal }; - if constexpr (terminal) - terminal_disconnect_op { _svc_ptr, std::move(handler) } - .perform(std::move(ctx)); - else - disconnect_op { _svc_ptr, std::move(ctx), std::move(handler) } - .perform(); - } -}; - -template -decltype(auto) async_disconnect( - disconnect_rc_e reason_code, const disconnect_props& props, - std::shared_ptr svc_ptr, - CompletionToken&& token -) { - using Signature = void (error_code); - return asio::async_initiate( - initiate_async_disconnect(std::move(svc_ptr)), token, - reason_code, props - ); -} - -template -decltype(auto) async_terminal_disconnect( - disconnect_rc_e reason_code, const disconnect_props& props, - std::shared_ptr svc_ptr, - CompletionToken&& token -) { - using Signature = void (error_code); - return asio::async_initiate( - initiate_async_disconnect(std::move(svc_ptr)), token, - reason_code, props - ); -} - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_DISCONNECT_HPP diff --git a/include/async_mqtt5/impl/endpoints.hpp b/include/async_mqtt5/impl/endpoints.hpp deleted file mode 100644 index a96875e..0000000 --- a/include/async_mqtt5/impl/endpoints.hpp +++ /dev/null @@ -1,242 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_ENDPOINTS_HPP -#define ASYNC_MQTT5_ENDPOINTS_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -using epoints = asio::ip::tcp::resolver::results_type; - -template -class resolve_op { - struct on_resolve {}; - - Owner& _owner; - - using handler_type = Handler; - handler_type _handler; - -public: - resolve_op(Owner& owner, Handler&& handler) : - _owner(owner), _handler(std::move(handler)) - {} - - resolve_op(resolve_op&&) = default; - resolve_op(const resolve_op&) = delete; - - resolve_op& operator=(resolve_op&&) = default; - resolve_op& operator=(const resolve_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using cancellation_slot_type = - asio::associated_cancellation_slot_t; - cancellation_slot_type get_cancellation_slot() const noexcept { - return asio::get_associated_cancellation_slot(_handler); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_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 > static_cast(_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_after(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, std::array 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, {}, {}); - - resolve_ec = timer_ec ? resolve_ec : asio::error::timed_out; - _owner._log.at_resolve(resolve_ec, ap.host, ap.port, epts); - 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) { - std::move(_handler)(ec, std::move(eps), std::move(ap)); - } - - void complete_post(error_code ec, epoints eps, authority_path ap) { - asio::post( - _owner.get_executor(), - asio::prepend( - std::move(_handler), ec, - std::move(eps), std::move(ap) - ) - ); - - } -}; - - -template -class endpoints { - using logger_type = LoggerType; - - asio::ip::tcp::resolver _resolver; - asio::steady_timer& _connect_timer; - - std::vector _servers; - - int _current_host { -1 }; - - log_invoke& _log; - - template - friend class resolve_op; - - template - static constexpr auto to_(T& arg) { - return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); }; - } - - template - static constexpr auto as_(Parser&& p){ - return boost::spirit::x3::rule{} = std::forward(p); - } - -public: - template - endpoints( - Executor ex, asio::steady_timer& timer, - log_invoke& log - ) : - _resolver(std::move(ex)), _connect_timer(timer), - _log(log) - {} - - endpoints(const endpoints&) = delete; - endpoints& operator=(const endpoints&) = delete; - - void clone_servers(const endpoints& other) { - _servers = other._servers; - } - - using executor_type = asio::ip::tcp::resolver::executor_type; - // NOTE: asio::ip::basic_resolver returns executor by value - executor_type get_executor() noexcept { - return _resolver.get_executor(); - } - - template - decltype(auto) async_next_endpoint(CompletionToken&& token) { - using Signature = void (error_code, epoints, authority_path); - - auto initiation = [](auto handler, endpoints& self) { - resolve_op { self, std::move(handler) }.perform(); - }; - - return asio::async_initiate( - initiation, token, std::ref(*this) - ); - } - - 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_(+unreserved_)[to_(host)]; - auto port_ = as_(':' >> +digit_)[to_(port)]; - auto path_ = as_(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 diff --git a/include/async_mqtt5/impl/ping_op.hpp b/include/async_mqtt5/impl/ping_op.hpp deleted file mode 100644 index c2f0f45..0000000 --- a/include/async_mqtt5/impl/ping_op.hpp +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_PING_OP_HPP -#define ASYNC_MQTT5_PING_OP_HPP - -#include -#include - -#include -#include -#include - -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class ping_op { - using client_service = ClientService; - using handler_type = Handler; - - struct on_timer {}; - struct on_pingreq {}; - - std::shared_ptr _svc_ptr; - handler_type _handler; - -public: - ping_op(std::shared_ptr svc_ptr, Handler&& handler) : - _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler)) - {} - - ping_op(ping_op&&) noexcept = default; - ping_op(const ping_op&) = delete; - - ping_op& operator=(ping_op&&) noexcept = default; - ping_op& operator=(const ping_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform() { - _svc_ptr->_ping_timer.expires_after(compute_wait_time()); - _svc_ptr->_ping_timer.async_wait( - asio::prepend(std::move(*this), on_timer {}) - ); - } - - void operator()(on_timer, error_code ec) { - if (!_svc_ptr->is_open()) - return complete(); - else if (ec == asio::error::operation_aborted) - return perform(); - - auto pingreq = control_packet::of( - no_pid, get_allocator(), encoders::encode_pingreq - ); - - 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) { - if (!ec || ec == asio::error::try_again) - return perform(); - - complete(); - } - -private: - duration compute_wait_time() const { - auto negotiated_ka = _svc_ptr->negotiated_keep_alive(); - return negotiated_ka ? - std::chrono::seconds(negotiated_ka) : - duration(std::numeric_limits::max()); - } - - void complete() { - return std::move(_handler)(); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_PING_OP_HPP diff --git a/include/async_mqtt5/impl/publish_rec_op.hpp b/include/async_mqtt5/impl/publish_rec_op.hpp deleted file mode 100644 index f827af6..0000000 --- a/include/async_mqtt5/impl/publish_rec_op.hpp +++ /dev/null @@ -1,219 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_PUBLISH_REC_OP_HPP -#define ASYNC_MQTT5_PUBLISH_REC_OP_HPP - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class publish_rec_op { - using client_service = ClientService; - - struct on_puback {}; - struct on_pubrec {}; - struct on_pubrel {}; - struct on_pubcomp {}; - - std::shared_ptr _svc_ptr; - decoders::publish_message _message; - -public: - explicit publish_rec_op(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)) - {} - - publish_rec_op(publish_rec_op&&) noexcept = default; - publish_rec_op(const publish_rec_op&) = delete; - - publish_rec_op& operator=(publish_rec_op&&) noexcept = default; - publish_rec_op& operator=(const publish_rec_op&) = delete; - - using allocator_type = asio::recycling_allocator; - allocator_type get_allocator() const noexcept { - return allocator_type {}; - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - 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::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::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 puback) { - 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 pubrec) { - 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 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(static_cast(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_code); - if (!rc) { - on_malformed_packet("Malformed PUBREL received: invalid Reason Code"); - return wait_pubrel(packet_id); - } - - auto pubcomp = control_packet::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 pubcomp) { - 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 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, - _svc_ptr, asio::detached - ); - } - - void complete() { - /* auto rv = */_svc_ptr->channel_store(std::move(_message)); - } -}; - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_PUBLISH_REC_OP_HPP diff --git a/include/async_mqtt5/impl/publish_send_op.hpp b/include/async_mqtt5/impl/publish_send_op.hpp deleted file mode 100644 index de9eaaa..0000000 --- a/include/async_mqtt5/impl/publish_send_op.hpp +++ /dev/null @@ -1,508 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_PUBLISH_SEND_OP_HPP -#define ASYNC_MQTT5_PUBLISH_SEND_OP_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -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 -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 -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 _svc_ptr; - - using handler_type = cancellable_handler< - Handler, - typename client_service::executor_type - >; - handler_type _handler; - - serial_num_t _serial_num; - -public: - publish_send_op( - std::shared_ptr svc_ptr, - Handler&& handler - ) : - _svc_ptr(std::move(svc_ptr)), - _handler(std::move(handler), _svc_ptr->get_executor()) - { - auto slot = asio::get_associated_cancellation_slot(_handler); - if (slot.is_connected()) - slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { - svc.cancel(); - }); - } - - publish_send_op(publish_send_op&&) = default; - publish_send_op(const publish_send_op&) = delete; - - publish_send_op& operator=(publish_send_op&&) = default; - publish_send_op& operator=(const publish_send_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform( - std::string topic, std::string payload, - retain_e retain, const publish_props& props - ) { - 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_immediate(client::error::pid_overrun, packet_id); - } - - auto ec = validate_publish(topic, payload, retain, props); - if (ec) - return complete_immediate(ec, packet_id); - - _serial_num = _svc_ptr->next_serial_num(); - - auto publish = control_packet::of( - with_pid, get_allocator(), - encoders::encode_publish, packet_id, - std::move(topic), std::move(payload), - qos_type, retain, dup_e::no, props - ); - - auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) - .value_or(default_max_send_size); - if (publish.size() > max_packet_size) - return complete_immediate(client::error::packet_too_large, packet_id); - - send_publish(std::move(publish)); - } - - void send_publish(control_packet publish) { - 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 resend_publish(control_packet publish) { - if (_handler.cancelled() != asio::cancellation_type_t::none) - return complete( - asio::error::operation_aborted, publish.packet_id() - ); - send_publish(std::move(publish)); - } - - void operator()( - on_publish, control_packet publish, - error_code ec - ) { - if (ec == asio::error::try_again) - return resend_publish(std::move(publish)); - - if constexpr (qos_type == qos_e::at_most_once) - return complete(ec); - - else { - auto packet_id = publish.packet_id(); - - if (ec) - return complete(ec, packet_id); - - if constexpr (qos_type == qos_e::at_least_once) - _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) - _svc_ptr->async_wait_reply( - control_code_e::pubrec, packet_id, - asio::prepend( - std::move(*this), on_pubrec {}, std::move(publish) - ) - ); - } - } - - - template < - qos_e q = qos_type, - std::enable_if_t = true - > - void operator()( - on_puback, control_packet publish, - error_code ec, byte_citer first, byte_citer last - ) { - if (ec == asio::error::try_again) // "resend unanswered" - return resend_publish(std::move(publish.set_dup())); - - uint16_t packet_id = publish.packet_id(); - - if (ec) - return complete(ec, packet_id); - - auto puback = decoders::decode_puback( - static_cast(std::distance(first, last)), first - ); - if (!puback.has_value()) { - on_malformed_packet("Malformed PUBACK: cannot decode"); - return resend_publish(std::move(publish.set_dup())); - } - - auto& [reason_code, props] = *puback; - auto rc = to_reason_code(reason_code); - if (!rc) { - on_malformed_packet("Malformed PUBACK: invalid Reason Code"); - return resend_publish(std::move(publish.set_dup())); - } - - complete(ec, packet_id, *rc, std::move(props)); - } - - template < - qos_e q = qos_type, - std::enable_if_t = true - > - void operator()( - on_pubrec, control_packet publish, - error_code ec, byte_citer first, byte_citer last - ) { - if (ec == asio::error::try_again) // "resend unanswered" - return resend_publish(std::move(publish.set_dup())); - - uint16_t packet_id = publish.packet_id(); - - if (ec) - return complete(ec, packet_id); - - auto pubrec = decoders::decode_pubrec( - static_cast(std::distance(first, last)), first - ); - if (!pubrec.has_value()) { - on_malformed_packet("Malformed PUBREC: cannot decode"); - return resend_publish(std::move(publish.set_dup())); - } - - auto& [reason_code, props] = *pubrec; - - auto rc = to_reason_code(reason_code); - if (!rc) { - on_malformed_packet("Malformed PUBREC: invalid Reason Code"); - return resend_publish(std::move(publish.set_dup())); - } - - if (*rc) - return complete(ec, packet_id, *rc); - - auto pubrel = control_packet::of( - with_pid, get_allocator(), - encoders::encode_pubrel, packet_id, - 0, pubrel_props {} - ); - - send_pubrel(std::move(pubrel), false); - } - - void send_pubrel(control_packet pubrel, bool throttled) { - 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)) - ); - } - - template < - qos_e q = qos_type, - std::enable_if_t = true - > - void operator()( - on_pubrel, control_packet pubrel, error_code ec - ) { - 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, packet_id); - - _svc_ptr->async_wait_reply( - control_code_e::pubcomp, packet_id, - asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubrel)) - ); - } - - template < - qos_e q = qos_type, - std::enable_if_t = true - > - void operator()( - on_pubcomp, control_packet pubrel, - error_code ec, - byte_citer first, byte_citer last - ) { - 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, packet_id); - - auto pubcomp = decoders::decode_pubcomp( - static_cast(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_code); - if (!rc) { - on_malformed_packet("Malformed PUBCOMP: invalid Reason Code"); - return send_pubrel(std::move(pubrel), true); - } - - return complete(ec, pubrel.packet_id(), *rc); - } - -private: - - error_code validate_publish( - const std::string& topic, const std::string& payload, - retain_e retain, const publish_props& props - ) const { - constexpr uint8_t default_retain_available = 1; - constexpr uint8_t default_maximum_qos = 2; - constexpr uint8_t default_payload_format_ind = 0; - - auto topic_name_valid = props[prop::topic_alias].has_value() ? - validate_topic_alias_name(topic) == validation_result::valid : - validate_topic_name(topic) == validation_result::valid - ; - - if (!topic_name_valid) - return client::error::invalid_topic; - - auto max_qos = _svc_ptr->connack_property(prop::maximum_qos) - .value_or(default_maximum_qos); - auto retain_available = _svc_ptr->connack_property(prop::retain_available) - .value_or(default_retain_available); - - if (uint8_t(qos_type) > max_qos) - return client::error::qos_not_supported; - - if (retain_available == 0 && retain == retain_e::yes) - return client::error::retain_not_available; - - auto payload_format_ind = props[prop::payload_format_indicator] - .value_or(default_payload_format_ind); - if ( - payload_format_ind == 1 && - validate_mqtt_utf8(payload) != validation_result::valid - ) - return client::error::malformed_packet; - - return validate_props(props); - } - - error_code validate_props(const publish_props& props) const { - constexpr uint16_t default_topic_alias_max = 0; - - const auto& topic_alias = props[prop::topic_alias]; - if (topic_alias) { - auto topic_alias_max = _svc_ptr->connack_property(prop::topic_alias_maximum) - .value_or(default_topic_alias_max); - - if (topic_alias_max == 0 || *topic_alias > topic_alias_max) - return client::error::topic_alias_maximum_reached; - if (*topic_alias == 0 ) - return client::error::malformed_packet; - } - - const auto& response_topic = props[prop::response_topic]; - if ( - response_topic && - validate_topic_name(*response_topic) != validation_result::valid - ) - return client::error::malformed_packet; - - const auto& user_properties = props[prop::user_property]; - for (const auto& user_property: user_properties) - if (!is_valid_string_pair(user_property)) - return client::error::malformed_packet; - - if (!props[prop::subscription_identifier].empty()) - return client::error::malformed_packet; - - const auto& content_type = props[prop::content_type]; - if ( - content_type && - validate_mqtt_utf8(*content_type) != validation_result::valid - ) - return client::error::malformed_packet; - - return error_code {}; - } - - 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, _svc_ptr, - asio::detached - ); - } - - template < - qos_e q = qos_type, - std::enable_if_t = true - > - void complete(error_code ec, uint16_t = 0) { - _handler.complete(ec); - } - - template < - qos_e q = qos_type, - std::enable_if_t = true - > - void complete_immediate(error_code ec, uint16_t) { - _handler.complete_immediate(ec); - } - - template < - typename Props = on_publish_props_type, - std::enable_if_t< - std::is_same_v || - std::is_same_v, - bool - > = true - > - void complete( - error_code ec, uint16_t packet_id, - reason_code rc = reason_codes::empty, Props&& props = Props {} - ) { - _svc_ptr->free_pid(packet_id, true); - _handler.complete(ec, rc, std::forward(props)); - } - - template < - typename Props = on_publish_props_type, - std::enable_if_t< - std::is_same_v || - std::is_same_v, - bool - > = true - > - void complete_immediate(error_code ec, uint16_t packet_id) { - if (packet_id != 0) - _svc_ptr->free_pid(packet_id, false); - _handler.complete_immediate(ec, reason_codes::empty, Props {}); - } -}; - -template -class initiate_async_publish { - std::shared_ptr _svc_ptr; -public: - explicit initiate_async_publish(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)) - {} - - using executor_type = typename ClientService::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - template - void operator()( - Handler&& handler, - std::string topic, std::string payload, - retain_e retain, const publish_props& props - ) { - detail::publish_send_op { - _svc_ptr, std::move(handler) - }.perform( - std::move(topic), std::move(payload), retain, props - ); - } -}; - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_PUBLISH_SEND_OP_HPP diff --git a/include/async_mqtt5/impl/re_auth_op.hpp b/include/async_mqtt5/impl/re_auth_op.hpp deleted file mode 100644 index c6fbb31..0000000 --- a/include/async_mqtt5/impl/re_auth_op.hpp +++ /dev/null @@ -1,152 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_RE_AUTH_OP_hpp -#define ASYNC_MQTT5_RE_AUTH_OP_hpp - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class re_auth_op { - using client_service = ClientService; - struct on_auth_data {}; - - std::shared_ptr _svc_ptr; - any_authenticator& _auth; - -public: - explicit re_auth_op(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)), - _auth(_svc_ptr->_stream_context.mqtt_context().authenticator) - {} - - re_auth_op(re_auth_op&&) noexcept = default; - re_auth_op(const re_auth_op&) = delete; - - re_auth_op& operator=(re_auth_op&&) noexcept = default; - re_auth_op& operator=(const re_auth_op&) = delete; - - using allocator_type = asio::recycling_allocator; - allocator_type get_allocator() const noexcept { - return allocator_type {}; - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform() { - if (_auth.method().empty()) - return; - - auto auth_step = auth_step_e::client_initial; - return _auth.async_auth( - auth_step, "", - asio::prepend(std::move(*this), on_auth_data {}, auth_step) - ); - } - - void perform(decoders::auth_message auth_message) { - if (_auth.method().empty()) - return on_auth_fail( - "Unexpected AUTH received", - disconnect_rc_e::protocol_error - ); - - const auto& [rc, props] = auth_message; - auto auth_rc = to_reason_code(rc); - if (!auth_rc.has_value()) - return on_auth_fail( - "Malformed AUTH received: bad reason code", - disconnect_rc_e::malformed_packet - ); - - const auto& server_auth_method = props[prop::authentication_method]; - if (!server_auth_method || *server_auth_method != _auth.method()) - return on_auth_fail( - "Malformed AUTH received: wrong authentication method", - disconnect_rc_e::protocol_error - ); - - auto auth_step = auth_rc == reason_codes::success ? - auth_step_e::server_final : auth_step_e::server_challenge; - auto data = props[prop::authentication_data].value_or(""); - - return _auth.async_auth( - auth_step, std::move(data), - asio::prepend(std::move(*this), on_auth_data {}, auth_step) - ); - } - - void operator()( - on_auth_data, auth_step_e auth_step, error_code ec, std::string data - ) { - if (ec) - return on_auth_fail( - "Re-authentication: authentication fail", - disconnect_rc_e::unspecified_error - ); - - if (auth_step == auth_step_e::server_final) - return; - - auth_props props; - props[prop::authentication_method] = _auth.method(); - props[prop::authentication_data] = std::move(data); - auto rc = auth_step == auth_step_e::client_initial ? - reason_codes::reauthenticate : reason_codes::continue_authentication; - - auto packet = control_packet::of( - no_pid, get_allocator(), - encoders::encode_auth, - rc.value(), props - ); - - auto wire_data = packet.wire_data(); - - _svc_ptr->async_send( - wire_data, - no_serial, send_flag::none, - asio::consign(asio::detached, std::move(packet)) - ); - } - -private: - void on_auth_fail(std::string message, disconnect_rc_e reason) { - auto props = disconnect_props {}; - props[prop::reason_string] = std::move(message); - - async_disconnect(reason, props, _svc_ptr, asio::detached); - } - -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_RE_AUTH_OP_HPP diff --git a/include/async_mqtt5/impl/read_message_op.hpp b/include/async_mqtt5/impl/read_message_op.hpp deleted file mode 100644 index 53a24fe..0000000 --- a/include/async_mqtt5/impl/read_message_op.hpp +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_READ_MESSAGE_OP_HPP -#define ASYNC_MQTT5_READ_MESSAGE_OP_HPP - -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class read_message_op { - using client_service = ClientService; - using handler_type = Handler; - - struct on_message {}; - struct on_disconnect {}; - - std::shared_ptr _svc_ptr; - handler_type _handler; - -public: - read_message_op(std::shared_ptr svc_ptr, Handler&& handler) - : _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler)) - {} - - read_message_op(read_message_op&&) noexcept = default; - read_message_op(const read_message_op&) = delete; - - read_message_op& operator=(read_message_op&&) noexcept = default; - read_message_op& operator=(const read_message_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform() { - _svc_ptr->async_assemble( - asio::prepend(std::move(*this), on_message {}) - ); - } - - void operator()( - on_message, error_code ec, - 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::no_recovery) - _svc_ptr->cancel(); - - if (ec) - return complete(); - - dispatch(control_code, first, last); - } - - void operator()(on_disconnect, error_code ec) { - if (ec) - return complete(); - - perform(); - } - -private: - void dispatch( - uint8_t control_byte, - byte_citer first, byte_citer last - ) { - auto code = control_code_e(control_byte & 0b11110000); - - switch (code) { - case control_code_e::publish: { - auto msg = decoders::decode_publish( - control_byte, static_cast(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 control_code_e::disconnect: { - auto rv = decoders::decode_disconnect( - static_cast(std::distance(first, last)), first - ); - if (!rv.has_value()) - return on_malformed_packet( - "Malformed DISCONNECT received: cannot decode" - ); - - const auto& [rc, props] = *rv; - _svc_ptr->log().at_disconnect( - to_reason_code(rc) - .value_or(reason_codes::unspecified_error), - props - ); - _svc_ptr->close_stream(); - _svc_ptr->open_stream(); - } - break; - case control_code_e::auth: { - auto rv = decoders::decode_auth( - static_cast(std::distance(first, last)), first - ); - if (!rv.has_value()) - return on_malformed_packet( - "Malformed AUTH received: cannot decode" - ); - - re_auth_op { _svc_ptr }.perform(std::move(*rv)); - } - break; - default: - assert(false); - } - - 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, svc_ptr, - asio::prepend(std::move(*this), on_disconnect {}) - ); - } - - void complete() { - return std::move(_handler)(); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_READ_MESSAGE_OP_HPP diff --git a/include/async_mqtt5/impl/read_op.hpp b/include/async_mqtt5/impl/read_op.hpp deleted file mode 100644 index 3745f10..0000000 --- a/include/async_mqtt5/impl/read_op.hpp +++ /dev/null @@ -1,140 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_READ_OP_HPP -#define ASYNC_MQTT5_READ_OP_HPP - -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; -namespace asioex = boost::asio::experimental; - -template -class read_op { - struct on_read {}; - struct on_reconnect {}; - - Owner& _owner; - - using handler_type = Handler; - handler_type _handler; - -public: - read_op(Owner& owner, Handler&& handler) : - _owner(owner), _handler(std::move(handler)) - {} - - read_op(read_op&&) = default; - read_op(const read_op&) = delete; - - read_op& operator=(read_op&&) = default; - read_op& operator=(const read_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_handler); - } - - template - void perform( - const BufferType& buffer, duration wait_for - ) { - auto stream_ptr = _owner._stream_ptr; - - if (_owner.was_connected()) { - _owner._read_timer.expires_after(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 - asio::post( - _owner.get_executor(), - asio::prepend( - std::move(*this), on_read {}, stream_ptr, - std::array { 0, 1 }, - asio::error::not_connected, 0, error_code {} - ) - ); - } - - void operator()( - on_read, typename Owner::stream_ptr stream_ptr, - std::array ord, error_code read_ec, size_t bytes_read, - error_code - ) { - 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) { - std::move(_handler)(ec, bytes_read); - } - - static bool should_reconnect(error_code ec) { - using namespace asio::error; - // note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved) - return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */ - ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */ - 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_READ_OP_HPP diff --git a/include/async_mqtt5/impl/reconnect_op.hpp b/include/async_mqtt5/impl/reconnect_op.hpp deleted file mode 100644 index 91323b9..0000000 --- a/include/async_mqtt5/impl/reconnect_op.hpp +++ /dev/null @@ -1,251 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_RECONNECT_OP_HPP -#define ASYNC_MQTT5_RECONNECT_OP_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -class exponential_backoff { - int _curr_exp { 0 }; - - static constexpr int _base_mulptilier = 1000; - static constexpr int _max_exp = 4; - - // sizeof(_generator) = 8 - boost::random::rand48 _generator { uint32_t(std::time(0)) }; - boost::random::uniform_smallint<> _distribution { -500, 500 }; -public: - exponential_backoff() = default; - - duration generate() { - int exponent = _curr_exp < _max_exp ? _curr_exp++ : _max_exp; - int base = 1 << exponent; - return std::chrono::milliseconds( - base * _base_mulptilier + _distribution(_generator) /* noise */ - ); - } -}; - -namespace asio = boost::asio; - -template -class reconnect_op { - struct on_locked {}; - struct on_next_endpoint {}; - struct on_connect {}; - struct on_backoff {}; - - Owner& _owner; - - using handler_type = asio::any_completion_handler; - handler_type _handler; - - std::unique_ptr _buffer_ptr; - - exponential_backoff _generator; - - using endpoint = asio::ip::tcp::endpoint; - using epoints = asio::ip::tcp::resolver::results_type; - -public: - template - reconnect_op(Owner& owner, Handler&& handler) : - _owner(owner), _handler(std::move(handler)) - {} - - reconnect_op(reconnect_op&&) = default; - reconnect_op(const reconnect_op&) = delete; - - reconnect_op& operator=(reconnect_op&&) = default; - reconnect_op& operator=(const reconnect_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using cancellation_slot_type = - asio::associated_cancellation_slot_t; - cancellation_slot_type get_cancellation_slot() const noexcept { - return asio::get_associated_cancellation_slot(_handler); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_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) - // cancelled without acquiring the lock (by calling client.cancel()) - return std::move(_handler)(ec); - - if (!_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_after(_generator.generate()); - _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 - ) { - // 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); - - connect(eps.cbegin(), std::move(ap)); - } - - void connect(epoints::const_iterator eps, authority_path ap) { - namespace asioex = boost::asio::experimental; - - const auto& ep = eps->endpoint(); - auto sptr = _owner.construct_and_open_next_layer(ep.protocol()); - - if constexpr (has_tls_context) - 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_after(std::chrono::seconds(5)); - - auto init_connect = []( - auto handler, typename Owner::stream_type& stream, - mqtt_ctx& context, log_invoke& log, - endpoint ep, authority_path ap - ) { - connect_op { stream, context, log, std::move(handler) } - .perform(ep, std::move(ap)); - }; - - auto timed_connect = asioex::make_parallel_group( - asio::async_initiate( - init_connect, asio::deferred, std::ref(*sptr), - std::ref(_owner._stream_context.mqtt_context()), - std::ref(_owner.log()), - ep, 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), std::move(eps), std::move(ap) - ) - ); - } - - void operator()( - on_connect, - typename Owner::stream_ptr sptr, epoints::const_iterator eps, authority_path ap, - std::array ord, - error_code connect_ec, error_code timer_ec - ) { - // connect_ec may be any of: - // 1) async_connect error codes - // 2) async_handshake (TLS) error codes - // 3) async_handshake (WebSocket) error codes - // 4) async_write error codes - // 5) async_read error codes - // 5) 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); - - // retry for operation timed out and any other error_code or client::error::malformed_packet - if (ord[0] == 1 || connect_ec) { - // if the hostname resolved into more endpoints, try the next one - if (++eps != epoints::const_iterator()) - return connect(std::move(eps), std::move(ap)); - // try next server - return do_reconnect(); - } - - _owner.replace_next_layer(std::move(sptr)); - complete(error_code {}); - } - -private: - void complete(error_code ec) { - _owner._conn_mtx.unlock(); - std::move(_handler)(ec); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_RECONNECT_OP_HPP diff --git a/include/async_mqtt5/impl/replies.hpp b/include/async_mqtt5/impl/replies.hpp deleted file mode 100644 index 4dd5280..0000000 --- a/include/async_mqtt5/impl/replies.hpp +++ /dev/null @@ -1,246 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_REPLIES_HPP -#define ASYNC_MQTT5_REPLIES_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -class replies { -public: - using executor_type = asio::any_io_executor; -private: - using Signature = void (error_code, byte_citer, byte_citer); - - static constexpr auto max_reply_time = std::chrono::seconds(20); - - class reply_handler { - asio::any_completion_handler _handler; - control_code_e _code; - uint16_t _packet_id; - std::chrono::time_point _ts; - public: - template - reply_handler(control_code_e code, uint16_t pid, H&& handler) : - _handler(std::forward(handler)), _code(code), _packet_id(pid), - _ts(std::chrono::system_clock::now()) - {} - - reply_handler(reply_handler&&) = default; - reply_handler(const reply_handler&) = delete; - - reply_handler& operator=(reply_handler&&) = default; - reply_handler& operator=(const reply_handler&) = delete; - - void complete( - error_code ec, - byte_citer first = byte_citer {}, byte_citer last = byte_citer {} - ) { - std::move(_handler)(ec, first, last); - } - - void complete_post(const executor_type& ex, error_code ec) { - asio::post( - ex, - asio::prepend( - std::move(_handler), ec, byte_citer {}, byte_citer {} - ) - ); - } - - uint16_t packet_id() const noexcept { - return _packet_id; - } - - control_code_e code() const noexcept { - return _code; - } - - auto time() const noexcept { - return _ts; - } - }; - - executor_type _ex; - - using handlers = std::vector; - handlers _handlers; - - struct fast_reply { - control_code_e code; - uint16_t packet_id; - std::unique_ptr packet; - }; - using fast_replies = std::vector; - fast_replies _fast_replies; - -public: - template - explicit replies(Executor ex) : _ex(std::move(ex)) {} - - replies(replies&&) = default; - replies(const replies&) = delete; - - replies& operator=(replies&&) = default; - replies& operator=(const replies&) = delete; - - template - 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()) { - dup_handler_ptr->complete_post(_ex, asio::error::operation_aborted); - _handlers.erase(dup_handler_ptr); - } - - auto freply = find_fast_reply(code, packet_id); - - if (freply == _fast_replies.end()) { - auto initiation = []( - auto handler, replies& self, - control_code_e code, uint16_t packet_id - ) { - self._handlers.emplace_back( - code, packet_id, std::move(handler) - ); - }; - return asio::async_initiate( - initiation, token, std::ref(*this), code, packet_id - ); - } - - auto fdata = std::move(*freply); - _fast_replies.erase(freply); - - auto initiation = []( - auto handler, std::unique_ptr packet, - const executor_type& ex - ) { - byte_citer first = packet->cbegin(); - byte_citer last = packet->cend(); - - asio::post( - ex, - asio::consign( - asio::prepend( - std::move(handler), error_code {}, first, last - ), - std::move(packet) - ) - ); - }; - - return asio::async_initiate( - initiation, token, std::move(fdata.packet), _ex - ); - } - - 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(first, last) - }); - return; - } - - auto handler = std::move(*handler_ptr); - _handlers.erase(handler_ptr); - handler.complete(ec, first, last); - } - - void resend_unanswered() { - auto ua = std::move(_handlers); - for (auto& h : ua) - h.complete(asio::error::try_again); - } - - void cancel_unanswered() { - auto ua = std::move(_handlers); - for (auto& h : ua) - h.complete_post(_ex, asio::error::operation_aborted); - } - - 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(); - } - - void clear_pending_pubrels() { - for (auto it = _handlers.begin(); it != _handlers.end();) { - if (it->code() == control_code_e::pubrel) { - it->complete(asio::error::operation_aborted); - it = _handlers.erase(it); - } - else - ++it; - } - } - -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 diff --git a/include/async_mqtt5/impl/run_op.hpp b/include/async_mqtt5/impl/run_op.hpp deleted file mode 100644 index b8d7eab..0000000 --- a/include/async_mqtt5/impl/run_op.hpp +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_RUN_OP_HPP -#define ASYNC_MQTT5_RUN_OP_HPP - -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class run_op { - using client_service = ClientService; - - std::shared_ptr _svc_ptr; - - using handler_type = cancellable_handler< - Handler, - typename client_service::executor_type - >; - handler_type _handler; - -public: - run_op( - std::shared_ptr svc_ptr, - Handler&& handler - ) : - _svc_ptr(std::move(svc_ptr)), - _handler(std::move(handler), _svc_ptr->get_executor()) - { - auto slot = asio::get_associated_cancellation_slot(_handler); - if (slot.is_connected()) - slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { - svc.cancel(); - }); - } - - run_op(run_op&&) = default; - run_op(const run_op&) = delete; - - run_op& operator=(run_op&&) = default; - run_op& operator=(const run_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform() { - namespace asioex = boost::asio::experimental; - - _svc_ptr->_stream.open(); - _svc_ptr->_rec_channel.reset(); - - auto init_read_message_op = []( - auto handler, std::shared_ptr svc_ptr - ) { - return read_message_op { std::move(svc_ptr), std::move(handler) } - .perform(); - }; - - auto init_ping_op = []( - auto handler, std::shared_ptr svc_ptr - ) { - return ping_op { std::move(svc_ptr), std::move(handler) } - .perform(); - }; - - auto init_senty_op = []( - auto handler, std::shared_ptr svc_ptr - ) { - return sentry_op { std::move(svc_ptr), std::move(handler) } - .perform(); - }; - - asioex::make_parallel_group( - asio::async_initiate( - init_read_message_op, asio::deferred, _svc_ptr - ), - asio::async_initiate( - init_ping_op, asio::deferred, _svc_ptr - ), - asio::async_initiate( - init_senty_op, asio::deferred, _svc_ptr - ) - ).async_wait(asioex::wait_for_all(), std::move(*this)); - } - - void operator()(std::array /* ord */) { - _handler.complete(asio::error::operation_aborted); - } -}; - -template -class initiate_async_run { - std::shared_ptr _svc_ptr; -public: - explicit initiate_async_run(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)) - {} - - using executor_type = typename ClientService::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - template - void operator()(Handler&& handler) { - run_op { - _svc_ptr, std::move(handler) - }.perform(); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_RUN_OP_HPP diff --git a/include/async_mqtt5/impl/sentry_op.hpp b/include/async_mqtt5/impl/sentry_op.hpp deleted file mode 100644 index bbd50b9..0000000 --- a/include/async_mqtt5/impl/sentry_op.hpp +++ /dev/null @@ -1,100 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_SENTRY_OP_HPP -#define ASYNC_MQTT5_SENTRY_OP_HPP - -#include -#include - -#include - -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class sentry_op { - using client_service = ClientService; - using handler_type = Handler; - - struct on_timer {}; - struct on_disconnect {}; - - static constexpr auto check_interval = std::chrono::seconds(3); - - std::shared_ptr _svc_ptr; - handler_type _handler; - -public: - sentry_op(std::shared_ptr svc_ptr, Handler&& handler) : - _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler)) - {} - - sentry_op(sentry_op&&) noexcept = default; - sentry_op(const sentry_op&) = delete; - - sentry_op& operator=(sentry_op&&) noexcept = default; - sentry_op& operator=(const sentry_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform() { - _svc_ptr->_sentry_timer.expires_after(check_interval); - _svc_ptr->_sentry_timer.async_wait( - asio::prepend(std::move(*this), on_timer {}) - ); - } - - void operator()(on_timer, error_code) { - if (!_svc_ptr->is_open()) - return complete(); - - 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, svc_ptr, - asio::prepend(std::move(*this), on_disconnect {}) - ); - } - - perform(); - } - - void operator()(on_disconnect, error_code ec) { - if (ec) - return complete(); - - perform(); - } - -private: - void complete() { - return std::move(_handler)(); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_SENTRY_OP_HPP diff --git a/include/async_mqtt5/impl/subscribe_op.hpp b/include/async_mqtt5/impl/subscribe_op.hpp deleted file mode 100644 index f912bda..0000000 --- a/include/async_mqtt5/impl/subscribe_op.hpp +++ /dev/null @@ -1,336 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_SUBSCRIBE_OP_HPP -#define ASYNC_MQTT5_SUBSCRIBE_OP_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class subscribe_op { - using client_service = ClientService; - - struct on_subscribe {}; - struct on_suback {}; - - std::shared_ptr _svc_ptr; - - using handler_type = cancellable_handler< - Handler, - typename client_service::executor_type - >; - handler_type _handler; - - size_t _num_topics { 0 }; - -public: - subscribe_op( - std::shared_ptr svc_ptr, - Handler&& handler - ) : - _svc_ptr(std::move(svc_ptr)), - _handler(std::move(handler), _svc_ptr->get_executor()) - { - auto slot = asio::get_associated_cancellation_slot(_handler); - if (slot.is_connected()) - slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { - svc.cancel(); - }); - } - - subscribe_op(subscribe_op&&) = default; - subscribe_op(const subscribe_op&) = delete; - - subscribe_op& operator=(subscribe_op&&) = default; - subscribe_op& operator=(const subscribe_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform( - const std::vector& topics, - const subscribe_props& props - ) { - _num_topics = topics.size(); - - uint16_t packet_id = _svc_ptr->allocate_pid(); - if (packet_id == 0) - return complete_immediate(client::error::pid_overrun, packet_id); - - if (_num_topics == 0) - return complete_immediate(client::error::invalid_topic, packet_id); - - auto ec = validate_subscribe(topics, props); - if (ec) - return complete_immediate(ec, packet_id); - - auto subscribe = control_packet::of( - with_pid, get_allocator(), - encoders::encode_subscribe, packet_id, - topics, props - ); - - auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) - .value_or(default_max_send_size); - if (subscribe.size() > max_packet_size) - return complete_immediate(client::error::packet_too_large, packet_id); - - send_subscribe(std::move(subscribe)); - } - - void send_subscribe(control_packet subscribe) { - 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 resend_subscribe(control_packet subscribe) { - if (_handler.cancelled() != asio::cancellation_type_t::none) - return complete( - asio::error::operation_aborted, subscribe.packet_id() - ); - send_subscribe(std::move(subscribe)); - } - - void operator()( - on_subscribe, control_packet packet, - error_code ec - ) { - if (ec == asio::error::try_again) - return resend_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 packet, - error_code ec, byte_citer first, byte_citer last - ) { - if (ec == asio::error::try_again) // "resend unanswered" - return resend_subscribe(std::move(packet)); - - uint16_t packet_id = packet.packet_id(); - - if (ec) - return complete(ec, packet_id); - - auto suback = decoders::decode_suback( - static_cast(std::distance(first, last)), first - ); - if (!suback.has_value()) { - on_malformed_packet("Malformed SUBACK: cannot decode"); - return resend_subscribe(std::move(packet)); - } - - auto& [props, rcs] = *suback; - auto reason_codes = to_reason_codes(std::move(rcs)); - if (reason_codes.size() != _num_topics) { - on_malformed_packet( - "Malformed SUBACK: does not contain a " - "valid Reason Code for every Topic Filter" - ); - return resend_subscribe(std::move(packet)); - } - - complete( - ec, packet_id, std::move(reason_codes), std::move(props) - ); - } - -private: - - error_code validate_subscribe( - const std::vector& topics, const subscribe_props& props - ) const { - error_code ec; - for (const auto& topic: topics) { - ec = validate_topic(topic); - if (ec) - return ec; - } - - ec = validate_props(props); - return ec; - } - - error_code validate_topic(const subscribe_topic& topic) const { - auto wildcard_available = _svc_ptr->connack_property( - prop::wildcard_subscription_available - ).value_or(1); - auto shared_available = _svc_ptr->connack_property( - prop::shared_subscription_available - ).value_or(1); - - std::string_view topic_filter = topic.topic_filter; - - validation_result result = validation_result::valid; - if ( - topic_filter.compare(0, shared_sub_prefix.size(), shared_sub_prefix) == 0 - ) { - if (!shared_available) - return client::error::shared_subscription_not_available; - - result = validate_shared_topic_filter(topic_filter, wildcard_available); - } else - result = wildcard_available ? - validate_topic_filter(topic_filter) : - validate_topic_name(topic_filter); - - if (result == validation_result::invalid) - return client::error::invalid_topic; - if (!wildcard_available && result != validation_result::valid) - return client::error::wildcard_subscription_not_available; - return error_code {}; - } - - error_code validate_props(const subscribe_props& props) const { - const auto& user_properties = props[prop::user_property]; - for (const auto& user_property: user_properties) - if (!is_valid_string_pair(user_property)) - return client::error::malformed_packet; - - const auto& sub_id = props[prop::subscription_identifier]; - if (!sub_id.has_value()) - return error_code {}; - - auto sub_id_available = _svc_ptr->connack_property( - prop::subscription_identifier_available - ).value_or(1); - - if (!sub_id_available) - return client::error::subscription_identifier_not_available; - - return (min_subscription_identifier <= *sub_id && - *sub_id <= max_subscription_identifier) ? - error_code {} : - client::error::malformed_packet; - } - - static std::vector to_reason_codes(std::vector codes) { - std::vector ret; - for (uint8_t code : codes) { - auto rc = to_reason_code(code); - if (rc) - ret.push_back(*rc); - } - 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, _svc_ptr, - asio::detached - ); - } - - void complete_immediate(error_code ec, uint16_t packet_id) { - if (packet_id != 0) - _svc_ptr->free_pid(packet_id); - _handler.complete_immediate( - ec, std::vector(_num_topics, reason_codes::empty), - suback_props {} - ); - } - - void complete( - error_code ec, uint16_t packet_id, - std::vector reason_codes = {}, suback_props props = {} - ) { - if (reason_codes.empty() && _num_topics) - reason_codes = std::vector(_num_topics, reason_codes::empty); - - if (!_svc_ptr->subscriptions_present()) { - bool has_success_rc = std::any_of( - reason_codes.cbegin(), reason_codes.cend(), - [](const reason_code& rc) { return !rc; } - ); - if (has_success_rc) - _svc_ptr->subscriptions_present(true); - } - - _svc_ptr->free_pid(packet_id); - _handler.complete(ec, std::move(reason_codes), std::move(props)); - } -}; - -template -class initiate_async_subscribe { - std::shared_ptr _svc_ptr; -public: - explicit initiate_async_subscribe(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)) - {} - - using executor_type = typename ClientService::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - template - void operator()( - Handler&& handler, - const std::vector& topics, const subscribe_props& props - ) { - detail::subscribe_op { _svc_ptr, std::move(handler) } - .perform(topics, props); - } -}; - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_SUBSCRIBE_OP_HPP diff --git a/include/async_mqtt5/impl/unsubscribe_op.hpp b/include/async_mqtt5/impl/unsubscribe_op.hpp deleted file mode 100644 index 9ad86dd..0000000 --- a/include/async_mqtt5/impl/unsubscribe_op.hpp +++ /dev/null @@ -1,277 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP -#define ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -namespace async_mqtt5::detail { - -namespace asio = boost::asio; - -template -class unsubscribe_op { - using client_service = ClientService; - - struct on_unsubscribe {}; - struct on_unsuback {}; - - std::shared_ptr _svc_ptr; - - using handler_type = cancellable_handler< - Handler, - typename client_service::executor_type - >; - handler_type _handler; - - size_t _num_topics { 0 }; - -public: - unsubscribe_op( - std::shared_ptr svc_ptr, - Handler&& handler - ) : - _svc_ptr(std::move(svc_ptr)), - _handler(std::move(handler), _svc_ptr->get_executor()) - { - auto slot = asio::get_associated_cancellation_slot(_handler); - if (slot.is_connected()) - slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { - svc.cancel(); - }); - } - - unsubscribe_op(unsubscribe_op&&) = default; - unsubscribe_op(const unsubscribe_op&) = delete; - - unsubscribe_op& operator=(unsubscribe_op&&) = default; - unsubscribe_op& operator=(const unsubscribe_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = typename client_service::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - void perform( - const std::vector& topics, - const unsubscribe_props& props - ) { - _num_topics = topics.size(); - - uint16_t packet_id = _svc_ptr->allocate_pid(); - if (packet_id == 0) - return complete_immediate(client::error::pid_overrun, packet_id); - - if (_num_topics == 0) - return complete_immediate(client::error::invalid_topic, packet_id); - - auto ec = validate_unsubscribe(topics, props); - if (ec) - return complete_immediate(ec, packet_id); - - auto unsubscribe = control_packet::of( - with_pid, get_allocator(), - encoders::encode_unsubscribe, packet_id, - topics, props - ); - - auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) - .value_or(default_max_send_size); - if (unsubscribe.size() > max_packet_size) - return complete_immediate(client::error::packet_too_large, packet_id); - - send_unsubscribe(std::move(unsubscribe)); - } - - void send_unsubscribe(control_packet unsubscribe) { - 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 resend_unsubscribe(control_packet subscribe) { - if (_handler.cancelled() != asio::cancellation_type_t::none) - return complete( - asio::error::operation_aborted, subscribe.packet_id() - ); - send_unsubscribe(std::move(subscribe)); - } - - void operator()( - on_unsubscribe, control_packet packet, - error_code ec - ) { - if (ec == asio::error::try_again) - return resend_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 packet, - error_code ec, byte_citer first, byte_citer last - ) { - if (ec == asio::error::try_again) // "resend unanswered" - return resend_unsubscribe(std::move(packet)); - - uint16_t packet_id = packet.packet_id(); - - if (ec) - return complete(ec, packet_id); - - auto unsuback = decoders::decode_unsuback( - static_cast(std::distance(first, last)), first - ); - if (!unsuback.has_value()) { - on_malformed_packet("Malformed UNSUBACK: cannot decode"); - return resend_unsubscribe(std::move(packet)); - } - - auto& [props, rcs] = *unsuback; - auto reason_codes = to_reason_codes(std::move(rcs)); - if (reason_codes.size() != _num_topics) { - on_malformed_packet( - "Malformed UNSUBACK: does not contain a " - "valid Reason Code for every Topic Filter" - ); - return resend_unsubscribe(std::move(packet)); - } - - complete( - ec, packet_id, std::move(reason_codes), std::move(props) - ); - } - -private: - - static error_code validate_unsubscribe( - const std::vector& topics, - const unsubscribe_props& props - ) { - for (const auto& topic : topics) - if (validate_topic_filter(topic) != validation_result::valid) - return client::error::invalid_topic; - - const auto& user_properties = props[prop::user_property]; - for (const auto& user_property: user_properties) - if (!is_valid_string_pair(user_property)) - return client::error::malformed_packet; - return error_code {}; - } - - static std::vector to_reason_codes(std::vector codes) { - std::vector ret; - for (uint8_t code : codes) { - auto rc = to_reason_code(code); - if (rc) - ret.push_back(*rc); - } - 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, _svc_ptr, - asio::detached - ); - } - - void complete_immediate(error_code ec, uint16_t packet_id) { - if (packet_id != 0) - _svc_ptr->free_pid(packet_id); - _handler.complete_immediate( - ec, std::vector(_num_topics, reason_codes::empty), - unsuback_props {} - ); - } - - void complete( - error_code ec, uint16_t packet_id, - std::vector reason_codes = {}, unsuback_props props = {} - ) { - if (reason_codes.empty() && _num_topics) - reason_codes = std::vector(_num_topics, reason_codes::empty); - - _svc_ptr->free_pid(packet_id); - _handler.complete(ec, std::move(reason_codes), std::move(props)); - } -}; - -template -class initiate_async_unsubscribe { - std::shared_ptr _svc_ptr; -public: - explicit initiate_async_unsubscribe(std::shared_ptr svc_ptr) : - _svc_ptr(std::move(svc_ptr)) - {} - - using executor_type = typename ClientService::executor_type; - executor_type get_executor() const noexcept { - return _svc_ptr->get_executor(); - } - - template - void operator()( - Handler&& handler, - const std::vector& topics, const unsubscribe_props& props - ) { - detail::unsubscribe_op { _svc_ptr, std::move(handler) } - .perform(topics, props); - } -}; - - -} // end namespace async_mqtt5::detail - -#endif // !ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP diff --git a/include/async_mqtt5/impl/write_op.hpp b/include/async_mqtt5/impl/write_op.hpp deleted file mode 100644 index 0c3220a..0000000 --- a/include/async_mqtt5/impl/write_op.hpp +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_WRITE_OP_HPP -#define ASYNC_MQTT5_WRITE_OP_HPP - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace async_mqtt5::detail { - -template -class write_op { - struct on_write {}; - struct on_reconnect {}; - - Owner& _owner; - - using handler_type = Handler; - handler_type _handler; - -public: - write_op(Owner& owner, Handler&& handler) : - _owner(owner), _handler(std::move(handler)) - {} - - write_op(write_op&&) = default; - write_op(const write_op&) = delete; - - write_op& operator=(write_op&&) = default; - write_op& operator=(const write_op&) = delete; - - using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept { - return asio::get_associated_allocator(_handler); - } - - using executor_type = asio::associated_executor_t; - executor_type get_executor() const noexcept { - return asio::get_associated_executor(_handler); - } - - template - 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 - asio::post( - _owner.get_executor(), - asio::prepend( - std::move(*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) { - std::move(_handler)(ec, bytes_written); - } - - static bool should_reconnect(error_code ec) { - using namespace asio::error; - // note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved) - return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */ - ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */ - 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 diff --git a/include/async_mqtt5/logger.hpp b/include/async_mqtt5/logger.hpp deleted file mode 100644 index 3aeb18e..0000000 --- a/include/async_mqtt5/logger.hpp +++ /dev/null @@ -1,257 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_LOGGER_HPP -#define ASYNC_MQTT5_LOGGER_HPP - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace async_mqtt5 { - -namespace asio = boost::asio; -using error_code = boost::system::error_code; - -/** - * \brief Represents the severity level of log messages. - */ -enum class log_level : uint8_t { - /** Error messages that indicate serious issues. **/ - error = 1, - - /** Warnings that indicate potential problems or non-critical issues. **/ - warning, - - /** Informational messages that highlight normal application behaviour and events. **/ - info, - - /** Detailed messages useful for diagnosing issues. **/ - debug -}; - -/** - * \brief A logger class that can used by the \ref mqtt_client to output - * the results of operations to stderr. - * - * \details All functions are invoked directly within the \ref mqtt_client using - * its default executor. If the \ref mqtt_client is initialized with an explicit or - * implicit strand, none of the functions will be invoked concurrently. - * - * \par Thread safety - * ['Distinct objects]: unsafe. \n - * ['Shared objects]: unsafe. \n - * This class is [*not thread-safe]. -*/ -class logger { - constexpr static auto prefix = "[Async.MQTT5]"; - - log_level _level; -public: - - /** - * \brief Constructs a logger that filters log messages based on the specified log level. - * - * \param level Messages with a log level higher than the given log level will be suppressed. - */ - logger(log_level level = log_level::warning) : _level(level) {} - - /** - * \brief Outputs the results of the resolve operation. - * - * \param ec Error code returned by the resolve operation. - * \param host Hostname used in the resolve operation. - * \param port Port used in the resolve operation. - * \param eps Endpoints returned by the resolve operation. - */ - void at_resolve( - error_code ec, std::string_view host, std::string_view port, - const asio::ip::tcp::resolver::results_type& eps - ) { - if (!ec && _level < log_level::info) - return; - - output_prefix(); - std::clog - << "resolve: " - << host << ":" << port; - std::clog << " - " << ec.message(); - - if (_level == log_level::debug) { - std::clog << " ["; - for (auto it = eps.begin(); it != eps.end();) { - std::clog << it->endpoint().address().to_string(); - if (++it != eps.end()) - std::clog << ","; - } - std::clog << "]"; - } - std::clog << std::endl; - } - - /** - * \brief Outputs the results of the TCP connect operation. - * - * \param ec Error code returned by the TCP connect operation. - * \param ep The TCP endpoint used to establish the TCP connection. - */ - void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) { - if (!ec && _level < log_level::info) - return; - - output_prefix(); - std::clog - << "TCP connect: " - << ep.address().to_string() << ":" << ep.port() - << " - " << ec.message() - << std::endl; - } - - /** - * \brief Outputs the results of the TLS handshake operation. - * - * \param ec Error code returned by the TLS handshake operation. - * \param ep The TCP endpoint used to establish the TLS handshake. - */ - void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) { - if (!ec && _level < log_level::info) - return; - - output_prefix(); - std::clog - << "TLS handshake: " - << ep.address().to_string() << ":" << ep.port() - << " - " << ec.message() - << std::endl; - } - - /** - * \brief Outputs the results of the WebSocket handshake operation. - * - * \param ec Error code returned by the WebSocket handshake operation. - * \param ep The TCP endpoint used to establish the WebSocket handshake. - */ - void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) { - if (!ec && _level < log_level::info) - return; - - output_prefix(); - std::clog - << "WebSocket handshake: " - << ep.address().to_string() << ":" << ep.port() - << " - " << ec.message() - << std::endl; - } - - /** - * \brief Outputs the contents of the \__CONNACK\__ packet sent by the Broker. - * - * \param rc Reason Code in the received \__CONNACK\__ packet indicating - * the result of the MQTT handshake. - * \param session_present A flag indicating whether the Broker already has a session associated - * with this connection. - * \param ca_props \__CONNACK_PROPS\__ received in the \__CONNACK\__ packet. - */ - void at_connack( - reason_code rc, - bool session_present, const connack_props& ca_props - ) { - if (!rc && _level < log_level::info) - return; - - output_prefix(); - std::clog << "connack: " << rc.message() << "."; - if (_level == log_level::debug) { - std::clog << " session_present:" << session_present << " "; - output_props(ca_props); - } - std::clog << std::endl; - } - - /** - * \brief Outputs the contents of the \__DISCONNECT\__ packet sent by the Broker. - * - * \param rc Reason Code in the received \__DISCONNECT\__ packet indicating - * the reason behind the disconnection. - * \param dc_props \__DISCONNECT_PROPS\__ received in the \__DISCONNECT\__ packet. - */ - void at_disconnect(reason_code rc, const disconnect_props& dc_props) { - output_prefix(); - std::clog << "disconnect: " << rc.message() << "."; - if (_level == log_level::debug) - output_props(dc_props); - std::clog << std::endl; - } - -private: - void output_prefix() { - std::clog << prefix << " "; - } - - template - void output_props(const Props& props) { - props.visit( - [](const auto& prop, const auto& val) -> bool { - if constexpr (detail::is_optional) { - if (val.has_value()) { - std::clog << property_name(prop) << ":"; - using value_type = boost::remove_cv_ref_t; - if constexpr (std::is_same_v) - std::clog << std::to_string(*val) << " "; - else - std::clog << *val << " "; - } - } else { // is vector - if (val.empty()) - return true; - - std::clog << property_name(prop) << ":"; - std::clog << "["; - for (auto i = 0; i < val.size(); i++) { - if constexpr (detail::is_pair) - std::clog << "(" << val[i].first << "," << val[i].second << ")"; - else - std::clog << std::to_string(val[i]); - if (i + 1 < val.size()) - std::clog << ", "; - } - std::clog << "]"; - } - return true; - } - ); - } - - template - static std::string_view property_name(std::integral_constant) { - return prop::name_v

; - } - -}; - -// Verify that the logger class satisfies the LoggerType concept -static_assert(has_at_resolve); -static_assert(has_at_tcp_connect); -static_assert(has_at_tls_handshake); -static_assert(has_at_ws_handshake); -static_assert(has_at_connack); -static_assert(has_at_disconnect); - -} // end namespace async_mqtt5 - - -#endif // !ASYNC_MQTT5_LOGGER_HPP diff --git a/include/async_mqtt5/mqtt_client.hpp b/include/async_mqtt5/mqtt_client.hpp deleted file mode 100644 index f83942e..0000000 --- a/include/async_mqtt5/mqtt_client.hpp +++ /dev/null @@ -1,981 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_MQTT_CLIENT_HPP -#define ASYNC_MQTT5_MQTT_CLIENT_HPP - -#include -#include -#include -#include // std::monostate -#include - -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace async_mqtt5 { - -namespace asio = boost::asio; - -/** - * \brief \__MQTT\__ client used to connect and communicate with a Broker. - * - * \tparam \__StreamType\__ Type of the underlying transport protocol used to transfer - * the stream of bytes between the Client and the Broker. The transport must be - * ordered and lossless. - * \tparam \__TlsContext\__ Type of the context object used in TLS/SSL connections. - * \tparam \__LoggerType\__ Type of object used to log events within the Client. - * - * \par Thread safety - * ['Distinct objects]: safe. \n - * ['Shared objects]: unsafe. \n - * This class is [*not thread-safe]. - * The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand. - */ -template < - typename StreamType, - typename TlsContext = std::monostate, - typename LoggerType = noop_logger -> -class mqtt_client { -public: - /// The executor type associated with the client. - using executor_type = typename StreamType::executor_type; - - /// Rebinds the client type to another executor. - template - struct rebind_executor { - /// The client type when rebound to the specified executor. - using other = mqtt_client< - typename detail::rebind_executor::other, - TlsContext - >; - }; - -private: - using stream_type = StreamType; - using tls_context_type = TlsContext; - using logger_type = LoggerType; - - using client_service_type = detail::client_service< - stream_type, tls_context_type, logger_type - >; - using impl_type = std::shared_ptr; - impl_type _impl; - -public: - /** - * \brief Constructs a Client with given parameters. - * - * \param ex An executor that will be associated with the Client. - * \param tls_context A context object used in TLS/SSL connection. - * \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client. - */ - explicit mqtt_client( - const executor_type& ex, - tls_context_type tls_context = {}, logger_type logger = {} - ) : - _impl(std::make_shared( - ex, std::move(tls_context), std::move(logger) - )) - {} - - /** - * \brief Constructs a Client with given parameters. - * - * \tparam \__ExecutionContext\__ Type of a concrete execution context. - * \param context Execution context whose executor will be associated with the Client. - * \param tls_context A context object used in TLS/SSL connection. - * \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client. - * - * \par Precondition - * \code - * std::is_convertible_v - * \endcode - */ - template < - typename ExecutionContext, - std::enable_if_t< - std::is_convertible_v, - bool - > = true - > - explicit mqtt_client( - ExecutionContext& context, - tls_context_type tls_context = {}, logger_type logger = {} - ) : - mqtt_client( - context.get_executor(), - std::move(tls_context), std::move(logger) - ) - {} - - /** - * \brief Move-construct an mqtt_client from another. - * - * \details Moved-from client can only be destructed - */ - mqtt_client(mqtt_client&&) noexcept = default; - - /** - * \brief Move assignment operator. - * - * \details Cancels this client first. Moved-from client can only be destructed. - */ - mqtt_client& operator=(mqtt_client&& other) noexcept { - _impl->cancel(); - _impl = std::move(other._impl); - return *this; - } - - /** - * \brief Destructor. - * - * \details Automatically calls \ref mqtt_client::cancel. - */ - ~mqtt_client() { - if (_impl) - _impl->cancel(); - } - - /** - * \brief Get the executor associated with the object. - */ - executor_type get_executor() const noexcept { - return _impl->get_executor(); - } - - /** - * \brief Get the context object used in TLS/SSL connection. - * - * \note This function may only be invoked - * when the template parameter \__TlsContext\__ was configured - * with non-default type during the creation of a \ref mqtt_client. - * - * \par Precondition - * \code - * !std::is_same_v - * \endcode - */ - template < - typename Ctx = TlsContext, - std::enable_if_t, bool> = true - > - decltype(auto) tls_context() { - return _impl->tls_context(); - } - - /** - * \brief Start the Client. - * - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void (__ERROR_CODE__) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete with - * `boost::asio::error::operation_aborted` when the client is cancelled by calling - * \ref mqtt_client::async_disconnect, \ref mqtt_client::cancel, destruction or - * if a non-recoverable error happens during a connection attempt (e.g. access denied). - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::asio::error::operation_aborted`\n - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_run(CompletionToken&& token = {}) { - using Signature = void (error_code); - return asio::async_initiate( - detail::initiate_async_run(_impl), token - ); - } - - /** - * \brief Cancel all asynchronous operations. This function has terminal effects. - * - * \details All outstanding operations will complete - * with `boost::asio::error::operation_aborted`. - * - * \attention This function has terminal effects and will close the Client. - * The Client cannot be used before calling \ref mqtt_client::async_run again. - */ - void cancel() { - auto impl = _impl; - _impl = impl->dup(); - impl->cancel(); - } - - /** - * \brief Assign a \ref will Message. - * - * \details The \ref will Message that the Broker should publish - * after the Network Connection is closed and it is not - * closed normally. - * - * \attention This function takes action when the client is in a non-operational state, - * meaning the \ref async_run function has not been invoked. - * Furthermore, you can use this function after the \ref cancel function has been called, - * before the \ref async_run function is invoked again. - */ - mqtt_client& will(will will) { - _impl->will(std::move(will)); - return *this; - } - - /** - * \brief Assign credentials that will be used to connect to a Broker. - * - * \details Credentials consist of a unique Client Identifier and, optionally, - * a User Name and Password. - * - * \attention This function takes action when the client is in a non-operational state, - * meaning the \ref async_run function has not been invoked. - * Furthermore, you can use this function after the \ref cancel function has been called, - * before the \ref async_run function is invoked again. - */ - mqtt_client& credentials( - std::string client_id, - std::string username = "", std::string password = "" - ) { - _impl->credentials( - std::move(client_id), - std::move(username), std::move(password) - ); - return *this; - } - - /** - * \brief Assign a list of Brokers that the Client will attempt to connect to. - * - * \details The Client will cycle through the list of hosts, - * attempting to establish a connection with each - * until it successfully establishes a connection. - * - * \param hosts List of Broker addresses and ports. - * Address and ports are separated with a colon `:` while - * pairs of addresses and ports are separated with a comma `,`. - * \param default_port The default port to connect to in case the port is not - * explicitly specified in the `hosts` list. - * - * \attention This function takes action when the client is in a non-operational state, - * meaning the \ref async_run function has not been invoked. - * Furthermore, you can use this function after the \ref cancel function has been called, - * before the \ref async_run function is invoked again. - * - * \par Example - * Some valid `hosts` string: - * - * \code - * std::string valid_hosts_1 = "broker1:1883, broker2, broker3:1883"; - * std::string valid_hosts_2 = "broker1"; - * \endcode - * - */ - mqtt_client& brokers(std::string hosts, uint16_t default_port = 1883) { - _impl->brokers(std::move(hosts), default_port); - return *this; - } - - /** - * \brief Assign an authenticator that the Client will use for - * \__ENHANCED_AUTH\__ on every connect to a Broker. - * Re-authentication can be initiated by calling \ref re_authenticate. - * - * \param authenticator Object that will be stored (move-constructed or by reference) - * and used for authentication. It needs to satisfy \__is_authenticator\__ concept. - * - * \attention This function takes action when the client is in a non-operational state, - * meaning the \ref async_run function has not been invoked. - * Furthermore, you can use this function after the \ref cancel function has been called, - * before the \ref async_run function is invoked again. - * - */ - template < - typename Authenticator, - std::enable_if_t, bool> = true - > - mqtt_client& authenticator(Authenticator&& authenticator) { - _impl->authenticator(std::forward(authenticator)); - return *this; - } - - /** - * \brief Assign the maximum time interval that is permitted to elapse between - * two transmissions from the Client. - * - * \details A non-zero value initiates a process of sending a \__PINGREQ\__ - * packet every `seconds`. If this function is not invoked, the Client assumes - * a \__KEEP_ALIVE\__ interval of 60 seconds. - * - * \param seconds Time interval in seconds. - * - * \note If the Server sends a \__SERVER_KEEP_ALIVE\__, - * the Client will send a \__PINGREQ\__ packet every \__SERVER_KEEP_ALIVE\__ seconds. - * - * \attention This function takes action when the client is in a non-operational state, - * meaning the \ref async_run function has not been invoked. - * Furthermore, you can use this function after the \ref cancel function has been called, - * before the \ref async_run function is invoked again. - * - */ - mqtt_client& keep_alive(uint16_t seconds) { - _impl->keep_alive(seconds); - return *this; - } - - /** - * \brief Assign \__CONNECT_PROPS\__ that will be sent in a \__CONNECT\__ packet. - * \param props \__CONNECT_PROPS\__ sent in a \__CONNECT\__ packet. - * \see See \__CONNECT_PROPS\__ for all eligible properties. - */ - mqtt_client& connect_properties(connect_props props) { - _impl->connect_properties(std::move(props)); - return *this; - } - - /** - * \brief Assign a property that will be sent in a \__CONNECT\__ packet. - * \param prop The \__CONNECT_PROPS\__ property to set. - * \param value Value that will be assigned to the property. - * - * \par Example - * \code - * client.connect_property(prop::session_expiry_interval, 40); // ok - * client.connect_property(prop::reason_string, "reason"); // does not compile, not a CONNECT prop! - * \endcode - * - * \see See \__CONNECT_PROPS\__ for all eligible properties. - */ - template - mqtt_client& connect_property( - std::integral_constant prop, - prop::value_type_t

value - ) { - _impl->connect_property(prop, std::move(value)); - return *this; - } - - /** - * \brief Initiates [mqttlink 3901257 Re-authentication] - * using the authenticator given in the \ref authenticator method. - * - * \note If \ref authenticator was not called, this method does nothing. - */ - void re_authenticate() { - detail::re_auth_op { _impl }.perform(); - } - - /** - * \brief Retrieves the value of a specific property from the last \__CONNACK\__ packet received. - * - * \details The return type varies according to the property requested. - * For all properties, the return type will be `std::optional` of their respective value type. - * For `async_mqtt5::prop::user_property`, the return type is - * `std::vector>`. - * - * \param prop The \__CONNACK_PROPS\__ property value to retrieve. - * - * \par Example - * \code - * std::optional auth_method = client.connack_property(async_mqtt5::prop::authentication_method); // ok - * std::optional c_type = client.connack_property(async_mqtt5::prop::content_type); // does not compile, not a CONNACK prop! - * \endcode - * - * \see See \__CONNACK_PROPS\__ for all eligible properties. - */ - template - const auto& connack_property( - std::integral_constant prop - ) const { - return _impl->connack_property(prop); - } - - /** - * \brief Retrieves the \__CONNACK_PROPS\__ from the last \__CONNACK\__ packet received. - * - * \see See \__CONNACK_PROPS\__ for all eligible properties. - */ - const connack_props& connack_properties() const { - return _impl->connack_properties(); - } - - /** - * \brief Send a \__PUBLISH\__ packet to Broker to transport an - * Application Message. - * - * \tparam qos_type The \ref qos_e level of assurance for delivery. - * \param topic Identification of the information channel to which - * Payload data is published. - * \param payload The Application Message that is being published. - * \param retain The \ref retain_e flag. - * \param props An instance of \__PUBLISH_PROPS\__. - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * On immediate completion, invocation of the handler will be performed in a manner - * equivalent to using \__ASYNC_IMMEDIATE\__. - * - * \par Handler signature - * The handler signature for this operation depends on the \ref qos_e specified:\n - * - * `qos` == `qos_e::at_most_once`: - * \code - * void ( - * __ERROR_CODE__ // Result of operation - * ) - * \endcode - * - * `qos` == `qos_e::at_least_once`: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * __REASON_CODE__, // Reason Code received from Broker. - * __PUBACK_PROPS__ // Properties received in the PUBACK packet. - * ) - * \endcode - * - * `qos` == `qos_e::exactly_once`: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * __REASON_CODE__, // Reason Code received from Broker. - * __PUBCOMP_PROPS__ // Properties received in the PUBCOMP packet. - * ) - * \endcode - * - * \par Completion condition - * Depending on the \ref qos_e specified, the asynchronous operation will complete - * when one of the following conditions is true:\n - * - If `qos` == `qos_e::at_most_once` and the Client - * has successfully written the packet to the transport. \n - * - If `qos` == `qos_e::at_least_once` and the packet has - * been sent and acknowledged through the reception of a \__PUBACK\__ packet. - * - If `qos` == `qos_e::exactly_once` and the packet has - * been sent and fully acknowledged through the reception of a \__PUBCOMP\__ packet. - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success` \n - * - `boost::asio::error::operation_aborted` \n - * - `boost::asio::error::no_recovery` \n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - \link async_mqtt5::client::error::packet_too_large \endlink - * - \link async_mqtt5::client::error::pid_overrun \endlink - * - \link async_mqtt5::client::error::qos_not_supported \endlink - * - \link async_mqtt5::client::error::retain_not_available \endlink - * - \link async_mqtt5::client::error::topic_alias_maximum_reached \endlink - * - \link async_mqtt5::client::error::invalid_topic \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__PUBLISH\__ packet \n - * - */ - template ::type - > - 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; - return asio::async_initiate( - detail::initiate_async_publish(_impl), - token, - std::move(topic), std::move(payload), retain, props - ); - } - - /** - * \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription - * to one or more Topics of interest. - * - * \details After the subscription has been established, the Broker will send - * PUBLISH packets to the Client to forward Application Messages that were published - * to Topics that the Client subscribed to. The Application Messages can be received - * with \ref mqtt_client::async_receive function. - * - * \param topics A list of \ref subscribe_topic of interest. - * \param props An instance of \__SUBSCRIBE_PROPS\__. - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * On immediate completion, invocation of the handler will be performed in a manner - * equivalent to using \__ASYNC_IMMEDIATE\__. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating - * // the subscription result for each Topic - * // in the SUBSCRIBE packet. - * __SUBACK_PROPS__, // Properties received in the SUBACK packet. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has successfully sent a \__SUBSCRIBE\__ packet - * and has received a \__SUBACK\__ response from the Broker.\n - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success` \n - * - `boost::asio::error::no_recovery` \n - * - `boost::asio::error::operation_aborted` \n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - \link async_mqtt5::client::error::packet_too_large \endlink - * - \link async_mqtt5::client::error::pid_overrun \endlink - * - \link async_mqtt5::client::error::invalid_topic \endlink - * - \link async_mqtt5::client::error::wildcard_subscription_not_available \endlink - * - \link async_mqtt5::client::error::subscription_identifier_not_available \endlink - * - \link async_mqtt5::client::error::shared_subscription_not_available \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n - * - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_subscribe( - const std::vector& topics, - const subscribe_props& props, - CompletionToken&& token = {} - ) { - using Signature = void ( - error_code, std::vector, suback_props - ); - return asio::async_initiate( - detail::initiate_async_subscribe(_impl), token, - topics, props - ); - } - - /** - * \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription - * to one Topic of interest. - * - * \details After the subscription has been established, the Broker will send - * \__PUBLISH\__ packets to the Client to forward Application Messages that were published - * to Topics that the Client subscribed to. The Application Messages can be received - * with \ref mqtt_client::async_receive function. - * - * \param topic A \ref subscribe_topic of interest. - * \param props An instance of \__SUBSCRIBE_PROPS\__. - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * On immediate completion, invocation of the handler will be performed in a manner - * equivalent to using \__ASYNC_IMMEDIATE\__. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * std::vector<__REASON_CODE__>, // Vector of Reason Codes containing the - * // single subscription result for the Topic - * // in the SUBSCRIBE packet. - * __SUBACK_PROPS__, // Properties received in the SUBACK packet. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has successfully sent a \__SUBSCRIBE\__ packet - * and has received a \__SUBACK\__ response from the Broker.\n - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success` \n - * - `boost::asio::error::no_recovery` \n - * - `boost::asio::error::operation_aborted` \n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - \link async_mqtt5::client::error::packet_too_large \endlink - * - \link async_mqtt5::client::error::pid_overrun \endlink - * - \link async_mqtt5::client::error::invalid_topic \endlink - * - \link async_mqtt5::client::error::wildcard_subscription_not_available \endlink - * - \link async_mqtt5::client::error::subscription_identifier_not_available \endlink - * - \link async_mqtt5::client::error::shared_subscription_not_available \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n - * - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_subscribe( - const subscribe_topic& topic, const subscribe_props& props, - CompletionToken&& token = {} - ) { - return async_subscribe( - std::vector { topic }, props, - std::forward(token) - ); - } - - - /** - * \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe from one - * or more Topics. - * - * \note The Client may still receive residual Application Messages - * through the \ref mqtt_client::async_receive function - * from Topics the Client just unsubscribed to. - * - * \param topics List of Topics to unsubscribe from. - * \param props An instance of \__UNSUBSCRIBE_PROPS\__. - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * On immediate completion, invocation of the handler will be performed in a manner - * equivalent to using \__ASYNC_IMMEDIATE\__. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating - * // the result of unsubscribe operation - * // for each Topic in the UNSUBSCRIBE packet. - * __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has successfully sent an \__UNSUBSCRIBE\__ packet - * and has received an \__UNSUBACK\__ response from the Broker.\n - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success` \n - * - `boost::asio::error::no_recovery` \n - * - `boost::asio::error::operation_aborted` \n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - \link async_mqtt5::client::error::packet_too_large \endlink - * - \link async_mqtt5::client::error::pid_overrun \endlink - * - \link async_mqtt5::client::error::invalid_topic \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n - * - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_unsubscribe( - const std::vector& topics, const unsubscribe_props& props, - CompletionToken&& token = {} - ) { - using Signature = void ( - error_code, std::vector, unsuback_props - ); - return asio::async_initiate( - detail::initiate_async_unsubscribe(_impl), token, - topics, props - ); - } - - /** - * \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe - * from one Topic. - * - * \note The Client may still receive residual Application Messages - * through the \ref mqtt_client::async_receive function - * from Topics the Client just unsubscribed to. - * - * \param topic Topic to unsubscribe from. - * \param props An instance of \__UNSUBSCRIBE_PROPS\__. - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * On immediate completion, invocation of the handler will be performed in a manner - * equivalent to using \__ASYNC_IMMEDIATE\__. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * std::vector<__REASON_CODE__>, // Vector of Reason Codes containing - * // the result of unsubscribe operation - * // for the Topic in the UNSUBSCRIBE packet. - * __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has successfully sent an \__UNSUBSCRIBE\__ packet - * and has received an \__UNSUBACK\__ response from the Broker.\n - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success` \n - * - `boost::asio::error::no_recovery` \n - * - `boost::asio::error::operation_aborted` \n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - \link async_mqtt5::client::error::packet_too_large \endlink - * - \link async_mqtt5::client::error::pid_overrun \endlink - * - \link async_mqtt5::client::error::invalid_topic \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n - * - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_unsubscribe( - const std::string& topic, const unsubscribe_props& props, - CompletionToken&& token = {} - ) { - return async_unsubscribe( - std::vector { topic }, props, - std::forward(token) - ); - } - - - /** - * \brief Asynchronously receive an Application Message. - * - * \details The Client will receive and complete deliveries for all the - * \__PUBLISH\__ packets received from the Broker throughout its lifetime. - * The Client will store them internally in the order they were delivered. - * Calling this function will attempt to receive an Application Message - * from internal storage. - * - * \note It is only recommended to call this function if you have established - * a successful subscription to a Topic using the \ref async_subscribe function. - * - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * On immediate completion, invocation of the handler will be performed in a manner - * equivalent to using \__POST\__. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__, // Result of operation. - * std::string, // Topic, the origin of the Application Message. - * std::string, // Payload, the content of the Application Message. - * __PUBLISH_PROPS__, // Properties received in the PUBLISH packet. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has a pending Application Message in its internal storage - * ready to be received. - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n - * - `boost::asio::error::operation_aborted`\n - * - \link async_mqtt5::client::error::session_expired \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` \n - * - `cancellation_type::partial` \n - * - `cancellation_type::total` \n - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_receive(CompletionToken&& token = {}) { - return _impl->async_channel_receive(std::forward(token)); - } - - /** - * \brief Disconnect the Client by sending a \__DISCONNECT\__ packet - * with a specified Reason Code. This function has terminal effects. - * - * \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker - * with a Reason Code describing the reason for disconnection. - * If the \__DISCONNECT\__ packet is successfully transmitted, - * or if `5 seconds` elapsed without a successful send, the Client will terminate the connection. - * - * \attention This function has terminal effects and will close the Client. - * See \ref mqtt_client::cancel. - * - * \param reason_code Reason Code to notify - * the Broker of the reason for the disconnection. - * \param props An instance of \__DISCONNECT_PROPS\__. - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__ // Result of operation. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has sent a \__DISCONNECT\__ packet.\n - * - 5 seconds have elapsed without a successful send.\n - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n - * - `boost::asio::error::operation_aborted`[footnote - This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server. - Regardless, the connection to the Server is terminated, and the Client is cancelled. - ]\n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - * - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_disconnect( - disconnect_rc_e reason_code, const disconnect_props& props, - CompletionToken&& token = {} - ) { - auto impl = _impl; - _impl = impl->dup(); - return detail::async_terminal_disconnect( - detail::disconnect_rc_e(static_cast(reason_code)), - props, impl, std::forward(token) - ); - } - - /** - * \brief Disconnect the Client by sending a \__DISCONNECT\__ packet - * with a Reason Code of reason_codes.normal_disconnection. - * This function has terminal effects. - * - * \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker - * with a Reason Code describing the reason for disconnection. - * If the \__DISCONNECT\__ packet is successfully transmitted, - * or if `5 seconds` elapsed without a successful send, the Client will terminate the connection. - * - * \attention This function has terminal effects and will close the Client. - * See \ref mqtt_client::cancel. - * - * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation completes. - * - * \par Handler signature - * The handler signature for this operation: - * \code - * void ( - * __ERROR_CODE__ // Result of operation. - * ) - * \endcode - * - * \par Completion condition - * The asynchronous operation will complete when one of the following conditions is true:\n - * - The Client has attempted to send a \__DISCONNECT\__ packet, regardless of whether - * the sending was successful or not.\n - * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n - * - * \par Error codes - * The list of all possible error codes that this operation can finish with:\n - * - `boost::system::errc::errc_t::success`\n - * - `boost::asio::error::operation_aborted`[footnote - This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server. - Regardless, the connection to the Server is terminated, and the Client is cancelled. - ]\n - * - \link async_mqtt5::client::error::malformed_packet \endlink - * - * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. - * - * \par Per-Operation Cancellation - * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n - * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n - */ - template < - typename CompletionToken = - typename asio::default_completion_token::type - > - decltype(auto) async_disconnect(CompletionToken&& token = {}) { - return async_disconnect( - disconnect_rc_e::normal_disconnection, - disconnect_props {}, std::forward(token) - ); - } - -}; - -} // end namespace async_mqtt5 - -#endif // !ASYNC_MQTT5_MQTT_CLIENT_HPP diff --git a/include/async_mqtt5/property_types.hpp b/include/async_mqtt5/property_types.hpp deleted file mode 100644 index 6ee87a1..0000000 --- a/include/async_mqtt5/property_types.hpp +++ /dev/null @@ -1,253 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_PROPERTY_TYPES_HPP -#define ASYNC_MQTT5_PROPERTY_TYPES_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace async_mqtt5::prop { - -enum property_type : uint8_t { - payload_format_indicator_t = 0x01, - message_expiry_interval_t = 0x02, - content_type_t = 0x03, - response_topic_t = 0x08, - correlation_data_t = 0x09, - subscription_identifier_t = 0x0b, - session_expiry_interval_t = 0x11, - assigned_client_identifier_t = 0x12, - server_keep_alive_t = 0x13, - authentication_method_t = 0x15, - authentication_data_t = 0x16, - request_problem_information_t = 0x17, - will_delay_interval_t = 0x18, - request_response_information_t = 0x19, - response_information_t = 0x1a, - server_reference_t = 0x1c, - reason_string_t = 0x1f, - receive_maximum_t = 0x21, - topic_alias_maximum_t = 0x22, - topic_alias_t = 0x23, - maximum_qos_t = 0x24, - retain_available_t = 0x25, - user_property_t = 0x26, - maximum_packet_size_t = 0x27, - wildcard_subscription_available_t = 0x28, - subscription_identifier_available_t = 0x29, - shared_subscription_available_t = 0x2a -}; - -class alignas(8) subscription_identifiers : - public boost::container::small_vector -{ - using base_type = boost::container::small_vector; - -public: - using base_type::base_type; - subscription_identifiers(int32_t val) : base_type { val } {} - - bool has_value() const noexcept { - return !empty(); - } - - explicit operator bool() const noexcept { - return !empty(); - } - - int32_t& operator*() noexcept { - return front(); - } - - int32_t operator*() const noexcept { - return front(); - } - - void emplace(int32_t val = 0) { - *this = val; - } - - int32_t value() const { - return front(); - } - - int32_t value_or(int32_t default_val) const noexcept { - return empty() ? default_val : front(); - } - - void reset() noexcept { - clear(); - } -}; - -template -struct property_traits; - -using user_property_value_t = std::vector>; - -#define DEF_PROPERTY_TRAIT(Pname, Ptype) \ -template <> \ -struct property_traits { \ - static constexpr std::string_view name = #Pname; \ - using type = Ptype; \ -}; \ -constexpr std::integral_constant Pname {}; - -DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional); -DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional); -DEF_PROPERTY_TRAIT(content_type, std::optional); -DEF_PROPERTY_TRAIT(response_topic, std::optional); -DEF_PROPERTY_TRAIT(correlation_data, std::optional); -DEF_PROPERTY_TRAIT(subscription_identifier, subscription_identifiers); -DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional); -DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional); -DEF_PROPERTY_TRAIT(server_keep_alive, std::optional); -DEF_PROPERTY_TRAIT(authentication_method, std::optional); -DEF_PROPERTY_TRAIT(authentication_data, std::optional); -DEF_PROPERTY_TRAIT(request_problem_information, std::optional); -DEF_PROPERTY_TRAIT(will_delay_interval, std::optional); -DEF_PROPERTY_TRAIT(request_response_information, std::optional); -DEF_PROPERTY_TRAIT(response_information, std::optional); -DEF_PROPERTY_TRAIT(server_reference, std::optional); -DEF_PROPERTY_TRAIT(reason_string, std::optional); -DEF_PROPERTY_TRAIT(receive_maximum, std::optional); -DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional); -DEF_PROPERTY_TRAIT(topic_alias, std::optional); -DEF_PROPERTY_TRAIT(maximum_qos, std::optional); -DEF_PROPERTY_TRAIT(retain_available, std::optional); -DEF_PROPERTY_TRAIT(user_property, user_property_value_t); -DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional); -DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional); -DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional); -DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional); - -#undef DEF_PROPERTY_TRAIT - -template -using value_type_t = typename property_traits

::type; - -template -constexpr std::string_view name_v = property_traits

::name; - -template -class properties { - - template - struct property { - using key = std::integral_constant; - constexpr static std::string_view name = name_v

; - value_type_t

value; - }; - std::tuple...> _props; - -public: - - template - constexpr auto& operator[](std::integral_constant) - noexcept { - return std::get>(_props).value; - } - - template - constexpr const auto& operator[](std::integral_constant) - const noexcept { - return std::get>(_props).value; - } - - template - using is_apply_on = std::conjunction< - std::is_invocable&>... - >; - - template - using is_nothrow_apply_on = std::conjunction< - std::is_nothrow_invocable&>... - >; - - template < - typename Func, - std::enable_if_t::value, bool> = true - > - constexpr bool apply_on(uint8_t property_id, Func&& func) - noexcept (is_nothrow_apply_on::value) { - return std::apply( - [&func, property_id](auto&... ptype) { - auto pc = [&func, property_id](auto& px) { - using ptype = std::remove_reference_t; - constexpr typename ptype::key prop; - if (prop.value == property_id) - std::invoke(func, px.value); - return prop.value != property_id; - }; - return (pc(ptype) && ...); - }, - _props - ); - } - - template - using is_visitor = std::conjunction< - std::is_invocable_r&>... - >; - - template - using is_nothrow_visitor = std::conjunction< - std::is_nothrow_invocable&>... - >; - - template < - typename Func, - std::enable_if_t::value, bool> = true - > - constexpr bool visit(Func&& func) - const noexcept (is_nothrow_visitor::value) { - return std::apply( - [&func](const auto&... props) { - auto pc = [&func](const auto& px) { - using ptype = std::remove_reference_t; - constexpr typename ptype::key prop; - return std::invoke(func, prop, px.value); - }; - return (pc(props) &&...); - }, - _props - ); - } - - template < - typename Func, - std::enable_if_t::value, bool> = true - > - constexpr bool visit(Func&& func) - noexcept (is_nothrow_visitor::value) { - return std::apply( - [&func](auto&... props) { - auto pc = [&func](auto& px) { - using ptype = std::remove_reference_t; - constexpr typename ptype::key prop; - return std::invoke(func, prop, px.value); - }; - return (pc(props) && ...); - }, - _props - ); - } -}; - - -} // end namespace async_mqtt5::prop - -#endif // !ASYNC_MQTT5_PROPERTY_TYPES_HPP diff --git a/include/async_mqtt5/reason_codes.hpp b/include/async_mqtt5/reason_codes.hpp deleted file mode 100644 index e2cacb6..0000000 --- a/include/async_mqtt5/reason_codes.hpp +++ /dev/null @@ -1,495 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_REASON_CODES_HPP -#define ASYNC_MQTT5_REASON_CODES_HPP - -#include -#include -#include -#include -#include -#include - -namespace async_mqtt5 { - -/// \cond internal -namespace reason_codes { - -enum class category : uint8_t { - none, - connack, puback, pubrec, - pubrel, pubcomp, suback, - unsuback, auth, disconnect -}; - -} // end namespace reason_codes - -/// \endcond - -/** - * \brief A class holding Reason Code values originating from Control Packets. - * - * \details A Reason Code is a one byte unsigned value that indicates the result of an operation. - * Reason Codes less than 0x80 indicate successful completion of an operation. - * The normal Reason Code for success is 0. - * Reason Code values of 0x80 or greater indicate failure. - * The \__CONNACK\__, \__PUBACK\__, \__PUBREC\__, \__PUBREL\__, \__PUBCOMP\__, \__DISCONNECT\__ - * and \__AUTH\__ Control Packets have a single Reason Code as part of the Variable Header. - * The \__SUBACK\__ and \__UNSUBACK\__ packets contain a list of one or more Reason Codes in the Payload. - * - * \see See \__REASON_CODES\__ for a complete list of all possible instances of this class. - */ -class reason_code { - uint8_t _code; - reason_codes::category _category { reason_codes::category::none }; -public: -/// \cond INTERNAL - constexpr reason_code() : _code(0xff) {} - - constexpr reason_code(uint8_t code, reason_codes::category cat) : - _code(code), _category(cat) - {} - - constexpr explicit reason_code(uint8_t code) : _code(code) {} -/// \endcond - - /** - * \brief Indication if the object holds a Reason Code indicating an error. - * - * \details Any Reason Code holding a value equal to or greater than 0x80. - */ - explicit operator bool() const noexcept { - return _code >= 0x80; - } - - /** - * \brief Returns the byte value of the Reason Code. - */ - constexpr uint8_t value() const noexcept { - return _code; - } - - /// Insertion operator. - friend std::ostream& operator<<(std::ostream& os, const reason_code& rc) { - os << rc.message(); - return os; - } - - /// Operator less than. - friend bool operator<(const reason_code& lhs, const reason_code& rhs) { - return lhs._code < rhs._code; - } - - /// Equality operator. - friend bool operator==(const reason_code& lhs, const reason_code& rhs) { - return lhs._code == rhs._code && lhs._category == rhs._category; - } - - /** - * \brief Returns a message describing the meaning behind the Reason Code. - */ - std::string message() const { - switch (_code) { - case 0x00: - if (_category == reason_codes::category::suback) - return "The subscription is accepted with maximum QoS sent at 0"; - if (_category == reason_codes::category::disconnect) - return "Close the connection normally. Do not send the Will Message"; - 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 but requires" - "that the Server also publishes its 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 "The Server does not wish to reveal the reason for the" - "failure or none of the other Reason Codes apply"; - 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 Filer 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 the 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 { - -/** No Reason Code. A \ref client::error occurred.*/ -constexpr reason_code empty {}; - -/** The operation completed successfully. */ -constexpr reason_code success { 0x00 }; - -/** Close the connection normally. Do not send the Will Message. */ -constexpr reason_code normal_disconnection { 0x00, category::disconnect }; - -/** The subscription is accepted with maximum QoS sent at 0. */ -constexpr reason_code granted_qos_0 { 0x00, category::suback }; - -/** The subscription is accepted with maximum QoS sent at 1. */ -constexpr reason_code granted_qos_1 { 0x01 }; - -/** The subscription is accepted with maximum QoS sent at 2 */ -constexpr reason_code granted_qos_2 { 0x02 }; - -/** The Client wishes to disconnect but requires that - the Server also publishes its Will Message. */ -constexpr reason_code disconnect_with_will_message { 0x04 }; - -/** The message is accepted but there are no subscribers. */ -constexpr reason_code no_matching_subscribers { 0x10 }; - -/** No matching Topic Filter is being used by the Client. */ -constexpr reason_code no_subscription_existed { 0x11 }; - -/** Continue the authentication with another step. */ -constexpr reason_code continue_authentication { 0x18 }; - -/** Initiate a re-authentication. */ -constexpr reason_code reauthenticate { 0x19 }; - -/** The Server does not wish to reveal the reason for the - failure or none of the other Reason Codes apply. */ -constexpr reason_code unspecified_error { 0x80 }; - -/** Data within the packet could not be correctly parsed. */ -constexpr reason_code malformed_packet { 0x81 }; - -/** Data in the packet does not conform to this specification. */ -constexpr reason_code protocol_error { 0x82 }; - -/** The packet is valid but not accepted by this Server. */ -constexpr reason_code implementation_specific_error { 0x83 }; - -/** The Server does not support the requested version of the MQTT protocol. */ -constexpr reason_code unsupported_protocol_version { 0x84 }; - -/** The Client ID is valid but not allowed by this Server. */ -constexpr reason_code client_identifier_not_valid { 0x85 }; - -/** The Server does not accept the User Name or Password provided. */ -constexpr reason_code bad_username_or_password { 0x86 }; - -/** The request is not authorized. */ -constexpr reason_code not_authorized { 0x87 }; - -/** The MQTT Server is not available. */ -constexpr reason_code server_unavailable { 0x88 }; - -/** The MQTT Server is busy, try again later. */ -constexpr reason_code server_busy { 0x89 }; - -/** The Client has been banned by administrative action. */ -constexpr reason_code banned { 0x8a }; - -/** The Server is shutting down. */ -constexpr reason_code server_shutting_down { 0x8b }; - -/** The authentication method is not supported or - does not match the method currently in use. */ -constexpr reason_code bad_authentication_method { 0x8c }; - -/** No packet has been received for 1.5 times the Keepalive time. */ -constexpr reason_code keep_alive_timeout { 0x8d }; - -/** Another Connection using the same ClientID has connected - causing this Connection to be closed. */ -constexpr reason_code session_taken_over { 0x8e }; - -/** The Topic Filter is not malformed, but it is not accepted. */ -constexpr reason_code topic_filter_invalid { 0x8f }; - -/** The Topic Name is not malformed, but it is not accepted. */ -constexpr reason_code topic_name_invalid { 0x90 }; - -/** The Packet Identifier is already in use. */ -constexpr reason_code packet_identifier_in_use { 0x91 }; - -/** The Packet Identifier is not known. */ -constexpr reason_code packet_identifier_not_found { 0x92 }; - -/** The Client or Server has received more than the Receive - Maximum publication for which it has not sent PUBACK or PUBCOMP. */ -constexpr reason_code receive_maximum_exceeded { 0x93 }; - -/** The Client or Server received a PUBLISH packet containing - a Topic Alias greater than the Maximum Topic Alias. */ -constexpr reason_code topic_alias_invalid { 0x94 }; - -/** The packet exceeded the maximum permissible size. */ -constexpr reason_code packet_too_large { 0x95 }; - -/** The received data rate is too high. */ -constexpr reason_code message_rate_too_high { 0x96 }; - -/** An implementation or administrative imposed limit has been exceeded. */ -constexpr reason_code quota_exceeded { 0x97 }; - -/** The Connection is closed due to an administrative action. */ -constexpr reason_code administrative_action { 0x98 }; - -/** The Payload does not match the specified Payload Format Indicator. */ -constexpr reason_code payload_format_invalid { 0x99 }; - -/** The Server does not support retained messages. */ -constexpr reason_code retain_not_supported { 0x9a }; - -/** The Server does not support the QoS the Client specified or - it is greater than the Maximum QoS specified. */ -constexpr reason_code qos_not_supported { 0x9b }; - -/** The Client should temporarily use another server. */ -constexpr reason_code use_another_server { 0x9c }; - -/** The Client should permanently use another server. */ -constexpr reason_code server_moved { 0x9d }; - -/** The Server does not support Shared Subscriptions for this Client. */ -constexpr reason_code shared_subscriptions_not_supported { 0x9e }; - -/** The connection rate limit has been exceeded. */ -constexpr reason_code connection_rate_exceeded { 0x9f }; - -/** The maximum connection time authorized for this - connection has been exceeded. */ -constexpr reason_code maximum_connect_time { 0xa0 }; - -/** The Server does not support Subscription Identifiers. */ -constexpr reason_code subscription_ids_not_supported { 0xa1 }; - -/** The Server does not support Wildcard Subscriptions. */ -constexpr reason_code wildcard_subscriptions_not_supported { 0xa2 }; - -namespace detail { - -template < - category cat, - std::enable_if_t = true -> -std::pair valid_codes() { - static reason_code valid_codes[] = { - success, unspecified_error, malformed_packet, - protocol_error, implementation_specific_error, - unsupported_protocol_version, client_identifier_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, - std::enable_if_t = true -> -std::pair valid_codes() { - static reason_code valid_codes[] = { - success, continue_authentication, reauthenticate - }; - static size_t len = sizeof(valid_codes) / sizeof(reason_code); - return std::make_pair(valid_codes, len); -} - -template < - category cat, - std::enable_if_t< - cat == category::puback || cat == category::pubrec, bool - > = true -> -std::pair valid_codes() { - static reason_code valid_codes[] = { - success, no_matching_subscribers, unspecified_error, - implementation_specific_error, not_authorized, - topic_name_invalid, packet_identifier_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, - std::enable_if_t< - cat == category::pubrel || cat == category::pubcomp, bool - > = true -> -std::pair valid_codes() { - static reason_code valid_codes[] = { - success, packet_identifier_not_found - }; - static size_t len = sizeof(valid_codes) / sizeof(reason_code); - return std::make_pair(valid_codes, len); -} - -template < - category cat, - std::enable_if_t = true -> -std::pair valid_codes() { - 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_identifier_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, - std::enable_if_t = true -> -std::pair valid_codes() { - static reason_code valid_codes[] = { - success, no_subscription_existed, - unspecified_error, implementation_specific_error, - not_authorized, topic_filter_invalid, - packet_identifier_in_use - }; - static size_t len = sizeof(valid_codes) / sizeof(reason_code); - return std::make_pair(valid_codes, len); -} - -template < - category cat, - std::enable_if_t = true -> -std::pair valid_codes() { - 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 -std::optional to_reason_code(uint8_t code) { - auto [ptr, len] = reason_codes::detail::valid_codes(); - auto it = std::lower_bound(ptr, ptr + len, reason_code(code)); - - if (it->value() == code) - return *it; - return std::nullopt; -} - -} // end namespace async_mqtt5 - -#endif // !ASYNC_MQTT5_REASON_CODES_HPP diff --git a/include/async_mqtt5/types.hpp b/include/async_mqtt5/types.hpp deleted file mode 100644 index f77039f..0000000 --- a/include/async_mqtt5/types.hpp +++ /dev/null @@ -1,368 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_TYPES_HPP -#define ASYNC_MQTT5_TYPES_HPP - -#include -#include - -#include - -#include - -namespace async_mqtt5 { - -/** An alias for `boost::system::error_code`. */ -using error_code = boost::system::error_code; - -/** - * \brief A data structure used to store information related to an authority - * such as the hostname, port, and path. - */ -struct authority_path { - /** The hostname of the authority as a domain name or an IP address. */ - std::string host; - - /** The port number used for communication. */ - std::string port; - - /** Specifies the endpoint path relevant to WebSocket connections. */ - std::string path; -}; - -/** - * \brief Represents the Quality of Service (\__QOS__\) - * property of the \__PUBLISH\__ packets. - * - * \details Determines how the \__PUBLISH\__ packets are delivered - * from the sender to the receiver. - */ -enum class qos_e : std::uint8_t { - /** The message arrives at the receiver either once or not at all. */ - at_most_once = 0b00, - - /** Ensures the message arrives at the receiver at least once. */ - at_least_once = 0b01, - - /** All messages arrive at the receiver exactly once without - loss or duplication of the messages. */ - exactly_once = 0b10 -}; - -/** - * \brief Represents the \__RETAIN\__ flag in the \__PUBLISH\__ packets. - * - * \details This flag informs the Server about whether or not it should - * store the current message. - */ -enum class retain_e : std::uint8_t { - /** The Server will replace any existing retained message for this Topic - with this message. */ - yes = 0b1, - - /** The Server will not store this message and will not remove or replace - any existing retained message. */ - no = 0b0 -}; - - -enum class dup_e : std::uint8_t { - yes = 0b1, no = 0b0 -}; - -/** - * \brief Represents the stage of \__ENHANCED_AUTH\__ process. - */ -enum class auth_step_e { - /** The Client needs to send initial authentication data. */ - client_initial, - - /** Server responded with reason_codes.continue_authentication and possibly - * authentication data, the Client needs to send further authentication data. - */ - server_challenge, - - /** Server responded with reason_codes.success and final - * authentication data, which the Client validates. - */ - server_final -}; - -/** - * \brief Representation of the No Local Subscribe Option. - * - * \details A Subscribe Option indicating whether or not Application Messages - * will be forwarded to a connection with a ClientID equal to the ClientID of the - * publishing connection. - */ -enum class no_local_e : std::uint8_t { - /** Application Messages can be forwarded to a connection with equal ClientID. */ - no = 0b0, - - /** Application Messages MUST NOT be forwarded to a connection with equal ClientID. */ - yes = 0b1 -}; - -/** - * \brief Representation of the Retain As Published Subscribe Option. - * - * \details A Subscribe Option indicating whether or not Application Messages forwarded - * using this subscription keep the \__RETAIN\__ flag they were published with. - */ -enum class retain_as_published_e : std::uint8_t { - /** Application Messages have the \__RETAIN\__ flag set to 0. */ - dont = 0b0, - - /** Application Messages keep the \__RETAIN\__ flag they were published with. */ - retain = 0b1 -}; - -/** - * \brief Representation of the Retain Handling Subscribe Option. - * - * \details A Subscribe Option specifying whether retained messages are sent - * when the subscription is established. - */ -enum class retain_handling_e : std::uint8_t { - /** Send retained messages at the time of subscribe. */ - send = 0b00, - - /** Send retained message only if the subscription does not currently exist. */ - new_subscription_only = 0b01, - - /** Do not send retained messages at the time of subscribe. */ - not_send = 0b10 -}; - -/** - * \brief Represents the \__SUBSCRIBE_OPTIONS\__ associated with each Subscription. - */ -struct subscribe_options { - /// Maximum \__QOS\__ level at which the Server can send Application Messages to the Client. - qos_e max_qos = qos_e::exactly_once; - - /// Option determining if Application Messages will be forwarded to a connection with an equal ClientID. - no_local_e no_local = no_local_e::yes; - - /// Option determining if Application Message will keep their \__RETAIN\__ flag. - retain_as_published_e retain_as_published = retain_as_published_e::retain; - - /// Option determining if retained messages are sent when the subscription is established. - retain_handling_e retain_handling = retain_handling_e::new_subscription_only; -}; - -/** - * \brief A representation of a Topic Subscription consisting of a Topic Filter and - * Subscribe Options. - */ -struct subscribe_topic { - /// An UTF-8 Encoded String indicating the Topics to which the Client wants to subscribe. - std::string topic_filter; - - /// The \ref subscribe_options associated with the subscription. - subscribe_options sub_opts; -}; - -/// \cond - -class connect_props : public prop::properties< - prop::session_expiry_interval_t, - prop::receive_maximum_t, - prop::maximum_packet_size_t, - prop::topic_alias_maximum_t, - prop::request_response_information_t, - prop::request_problem_information_t, - prop::user_property_t, - prop::authentication_method_t, - prop::authentication_data_t -> {}; - -class connack_props : public prop::properties< - prop::session_expiry_interval_t, - prop::receive_maximum_t, - prop::maximum_qos_t, - prop::retain_available_t, - prop::maximum_packet_size_t, - prop::assigned_client_identifier_t, - prop::topic_alias_maximum_t, - prop::reason_string_t, - prop::user_property_t, - prop::wildcard_subscription_available_t, - prop::subscription_identifier_available_t, - prop::shared_subscription_available_t, - prop::server_keep_alive_t, - prop::response_information_t, - prop::server_reference_t, - prop::authentication_method_t, - prop::authentication_data_t -> {}; - -class publish_props : public prop::properties< - prop::payload_format_indicator_t, - prop::message_expiry_interval_t, - prop::content_type_t, - prop::response_topic_t, - prop::correlation_data_t, - prop::subscription_identifier_t, - prop::topic_alias_t, - prop::user_property_t -> {}; - -// puback, pubcomp -class puback_props : public prop::properties< - prop::reason_string_t, - prop::user_property_t -> {}; - -class pubcomp_props : public prop::properties< - prop::reason_string_t, - prop::user_property_t -> {}; - -class pubrec_props : public prop::properties< - prop::reason_string_t, - prop::user_property_t -> {}; - -class pubrel_props : public prop::properties< - prop::reason_string_t, - prop::user_property_t -> {}; - - -class subscribe_props : public prop::properties< - prop::subscription_identifier_t, - prop::user_property_t -> {}; - -class suback_props : public prop::properties< - prop::reason_string_t, - prop::user_property_t -> {}; - -class unsubscribe_props : public prop::properties< - prop::user_property_t -> {}; - -class unsuback_props : public prop::properties< - prop::reason_string_t, - prop::user_property_t -> {}; - -class disconnect_props : public prop::properties< - prop::session_expiry_interval_t, - prop::reason_string_t, - prop::user_property_t, - prop::server_reference_t -> {}; - -class auth_props : public prop::properties< - prop::authentication_method_t, - prop::authentication_data_t, - prop::reason_string_t, - prop::user_property_t -> {}; - - -class will_props : public prop::properties< - prop::will_delay_interval_t, - prop::payload_format_indicator_t, - prop::message_expiry_interval_t, - prop::content_type_t, - prop::response_topic_t, - prop::correlation_data_t, - prop::user_property_t ->{}; - -/// \endcond - -/** - * \brief Represents the Will Message. - * - * \details A Will Message is an Application Message that - * the Broker should publish after the Network Connection is closed - * in cases where the Network Connection is not closed normally. - */ -class will : public will_props { - std::string _topic; - std::string _message; - qos_e _qos; retain_e _retain; - -public: - /** - * \brief Constructs an empty Will Message. - * - * \attention Do not use! - * An empty Will Message results in an empty Topic Name which is not valid. - * Internal uses only. - */ - will() = default; - - /** - * \brief Construct a Will Message. - * - * \param topic Topic, identification of the information channel to which - * the Will Message will be published. - * \param message The message that will be published. - * \param qos The \ref qos_e level used when publishing the Will Message. - * \param retain The \ref retain_e flag specifying if the Will Message - * is to be retained when it is published. - */ - 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) - {} - - /** - * \brief Construct a Will Message. - * - * \param topic Topic name, identification of the information channel to which - * the Will Message will be published. - * \param message The message that will be published. - * \param qos The \ref qos_e level used when publishing the Will Message. - * \param retain The \ref retain_e flag specifying if the Will Message - * is to be retained when it is published. - * \param props Will properties. - */ - 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) - {} - - /// Get the Topic Name. - std::string_view topic() const { - return _topic; - } - - /// Get the Application Message. - std::string_view message() const { - return _message; - } - - /// Get the \ref qos_e. - constexpr qos_e qos() const { - return _qos; - } - - /// Get the \ref retain_e. - constexpr retain_e retain() const { - return _retain; - } -}; - - -} // end namespace async_mqtt5 - -#endif // !ASYNC_MQTT5_TYPES_HPP diff --git a/include/async_mqtt5/websocket.hpp b/include/async_mqtt5/websocket.hpp deleted file mode 100644 index 9fe68d0..0000000 --- a/include/async_mqtt5/websocket.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef ASYNC_MQTT5_WEBSOCKET_HPP -#define ASYNC_MQTT5_WEBSOCKET_HPP - -#include -#include -#include - -#include - -namespace async_mqtt5 { - -// Trait definition for Beast -template -struct ws_handshake_traits> { - - template - static decltype(auto) async_handshake( - boost::beast::websocket::stream& stream, - authority_path ap, CompletionToken&& token - ) { - using namespace boost::beast; - - // 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, - std::forward(token) - ); - } -}; - -} // end namespace async_mqtt5 - -#endif // !ASYNC_MQTT5_WEBSOCKET_HPP diff --git a/include/boost/mqtt5.hpp b/include/boost/mqtt5.hpp new file mode 100644 index 0000000..3e8f4f4 --- /dev/null +++ b/include/boost/mqtt5.hpp @@ -0,0 +1,19 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_HPP +#define BOOST_MQTT5_HPP + +#include +#include +#include +#include +#include +#include +#include + +#endif // !BOOST_MQTT5_HPP diff --git a/include/boost/mqtt5/detail/any_authenticator.hpp b/include/boost/mqtt5/detail/any_authenticator.hpp new file mode 100644 index 0000000..e41ca96 --- /dev/null +++ b/include/boost/mqtt5/detail/any_authenticator.hpp @@ -0,0 +1,142 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_ANY_AUTHENTICATOR +#define BOOST_MQTT5_ANY_AUTHENTICATOR + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; +using error_code = boost::system::error_code; + +using auth_handler_type = asio::any_completion_handler< + void (error_code, std::string) +>; + +template +using async_auth_sig = decltype( + std::declval().async_auth(std::declval()...) +); + +template +using method_sig = decltype( + std::declval().method() +); + +template +constexpr bool is_authenticator = + boost::is_detected< + async_auth_sig, T, + auth_step_e, std::string, auth_handler_type + >::value && + boost::is_detected_convertible_v; + +class auth_fun_base { + using auth_func = void(*)( + auth_step_e, std::string, auth_handler_type, auth_fun_base* + ); + auth_func _auth_func; + +public: + auth_fun_base(auth_func f) : _auth_func(f) {} + ~auth_fun_base() = default; + + void async_auth( + auth_step_e step, std::string data, + auth_handler_type auth_handler + ) { + _auth_func(step, std::move(data), std::move(auth_handler), this); + } +}; + +template < + typename Authenticator, + typename = std::enable_if_t> +> +class auth_fun : public auth_fun_base { + Authenticator _authenticator; + +public: + auth_fun(Authenticator authenticator) : + auth_fun_base(&async_auth), + _authenticator(std::forward(authenticator)) + {} + + static void async_auth( + auth_step_e step, std::string data, auth_handler_type auth_handler, + auth_fun_base* base_ptr + ) { + auto auth_fun_ptr = static_cast(base_ptr); + auth_fun_ptr->_authenticator.async_auth( + step, std::move(data), std::move(auth_handler) + ); + } +}; + +class any_authenticator { + std::string _method; + std::shared_ptr _auth_fun; + +public: + any_authenticator() = default; + + template < + typename Authenticator, + std::enable_if_t, bool> = true + > + any_authenticator(Authenticator&& a) : + _method(a.method()), + _auth_fun( + new detail::auth_fun( + std::forward(a) + ) + ) + {} + + std::string_view method() const { + return _method; + } + + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using Signature = void (error_code, std::string); + + auto initiation = []( + auto handler, any_authenticator& self, + auth_step_e step, std::string data + ) { + self._auth_fun->async_auth( + step, std::move(data), std::move(handler) + ); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this), step, std::move(data) + ); + } +}; + +} // end namespace boost::mqtt5::detail + + +#endif // !BOOST_MQTT5_ANY_AUTHENTICATOR diff --git a/include/boost/mqtt5/detail/async_mutex.hpp b/include/boost/mqtt5/detail/async_mutex.hpp new file mode 100644 index 0000000..d2ed84b --- /dev/null +++ b/include/boost/mqtt5/detail/async_mutex.hpp @@ -0,0 +1,218 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_ASYNC_MUTEX_HPP +#define BOOST_MQTT5_ASYNC_MUTEX_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost::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 (error_code) + >; + using queue_t = std::deque; + + // Handler with assigned tracking executor. + // Objects of this type are type-erased by any_completion_handler + // and stored in the waiting queue. + template + class tracked_op { + tracking_type _executor; + Handler _handler; + public: + tracked_op(Handler&& h, const Executor& ex) : + _executor(tracking_executor(h, ex)), _handler(std::move(h)) + {} + + tracked_op(tracked_op&&) = default; + tracked_op(const tracked_op&) = delete; + + tracked_op& operator=(tracked_op&&) = default; + tracked_op& operator=(const tracked_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using cancellation_slot_type = + asio::associated_cancellation_slot_t; + cancellation_slot_type get_cancellation_slot() const noexcept { + return asio::get_associated_cancellation_slot(_handler); + } + + using executor_type = tracking_type; + executor_type get_executor() const noexcept { + return _executor; + } + + void operator()(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 { + queue_t::iterator _ihandler; + public: + explicit cancel_waiting_op(queue_t::iterator ih) : _ihandler(ih) {} + + void operator()(asio::cancellation_type_t type) { + if (type == asio::cancellation_type_t::none) + return; + if (*_ihandler) { + auto h = std::move(*_ihandler); + auto ex = asio::get_associated_executor(h); + asio::require(ex, asio::execution::blocking.possibly) + .execute([h = std::move(h)]() mutable { + std::move(h)(asio::error::operation_aborted); + }); + } + } + }; + + bool _locked { false }; + queue_t _waiting; + executor_type _ex; + +public: + template + explicit async_mutex(Executor&& ex) : _ex(std::forward(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; + } + + // 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 + decltype(auto) lock(CompletionToken&& token) noexcept { + using Signature = void (error_code); + + auto initiation = [] (auto handler, async_mutex& self) { + self.execute_or_queue(std::move(handler)); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this) + ); + } + + // 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() { + while (!_waiting.empty()) { + auto op = std::move(_waiting.front()); + _waiting.pop_front(); + if (!op) continue; + op.get_cancellation_slot().clear(); + execute_op(std::move(op)); + return; + } + _locked = false; + } + + // Cancels all outstanding operations waiting on the mutex. + void cancel() { + while (!_waiting.empty()) { + auto op = std::move(_waiting.front()); + _waiting.pop_front(); + if (!op) continue; + op.get_cancellation_slot().clear(); + 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(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 immediately 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. + template + void execute_or_queue(Handler&& handler) noexcept { + tracked_op h { std::move(handler), _ex }; + if (_locked) { + _waiting.emplace_back(std::move(h)); + auto slot = _waiting.back().get_cancellation_slot(); + if (slot.is_connected()) + slot.template emplace( + _waiting.end() - 1 + ); + } + else { + _locked = true; + execute_op(queued_op_t { std::move(h) }); + } + } +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_ASYNC_MUTEX_HPP diff --git a/include/async_mqtt5/detail/async_traits.hpp b/include/boost/mqtt5/detail/async_traits.hpp similarity index 60% rename from include/async_mqtt5/detail/async_traits.hpp rename to include/boost/mqtt5/detail/async_traits.hpp index ede57a9..50762dd 100644 --- a/include/async_mqtt5/detail/async_traits.hpp +++ b/include/boost/mqtt5/detail/async_traits.hpp @@ -5,23 +5,22 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_ASYNC_TRAITS_HPP -#define ASYNC_MQTT5_ASYNC_TRAITS_HPP +#ifndef BOOST_MQTT5_ASYNC_TRAITS_HPP +#define BOOST_MQTT5_ASYNC_TRAITS_HPP -#include +#include #include #include #include #include - #include #include #include -#include +#include -namespace async_mqtt5 { +namespace boost::mqtt5 { namespace asio = boost::asio; @@ -44,19 +43,19 @@ namespace detail { template using tracking_type = std::decay_t< - typename asio::prefer_result< - asio::associated_executor_t, - asio::execution::outstanding_work_t::tracked_t - >::type + typename asio::prefer_result< + asio::associated_executor_t, + asio::execution::outstanding_work_t::tracked_t + >::type >; template tracking_type tracking_executor(const Handler& handler, const DfltExecutor& ex) { - return asio::prefer( - asio::get_associated_executor(handler, ex), - asio::execution::outstanding_work.tracked - ); + return asio::prefer( + asio::get_associated_executor(handler, ex), + asio::execution::outstanding_work.tracked + ); } @@ -72,27 +71,27 @@ using tls_handshake_type_of = boost::detected_or_t; template using async_tls_handshake_sig = decltype( - std::declval().async_handshake(std::declval()...) + std::declval().async_handshake(std::declval()...) ); template constexpr bool has_tls_handshake = boost::is_detected< - async_tls_handshake_sig, T, tls_handshake_type_of, - decltype(handshake_handler_t) + async_tls_handshake_sig, T, tls_handshake_type_of, + decltype(handshake_handler_t) >::value; // websocket handshake template using async_ws_handshake_sig = decltype( - std::declval().async_handshake(std::declval()...) + std::declval().async_handshake(std::declval()...) ); template constexpr bool has_ws_handshake = boost::is_detected< - async_ws_handshake_sig, T, - std::string_view, std::string_view, - decltype(handshake_handler_t) + async_ws_handshake_sig, T, + std::string_view, std::string_view, + decltype(handshake_handler_t) >::value; // next layer @@ -102,57 +101,57 @@ using next_layer_sig = decltype(std::declval().next_layer()); template constexpr bool has_next_layer = boost::is_detected< - next_layer_sig, boost::remove_cv_ref_t + next_layer_sig, boost::remove_cv_ref_t >::value; template struct next_layer_type_impl { - using type = T; + using type = T; }; template struct next_layer_type_impl>> { - using type = typename T::next_layer_type; + using type = typename T::next_layer_type; }; template using next_layer_type = typename next_layer_type_impl< - boost::remove_cv_ref_t + boost::remove_cv_ref_t >::type; template next_layer_type& next_layer(T&& a) { - if constexpr (has_next_layer) - return a.next_layer(); - else - return std::forward(a); + if constexpr (has_next_layer) + return a.next_layer(); + else + return std::forward(a); } // lowest layer template struct lowest_layer_type_impl { - using type = T; + using type = T; }; template struct lowest_layer_type_impl>> { - using type = typename lowest_layer_type_impl< - next_layer_type - >::type; + using type = typename lowest_layer_type_impl< + next_layer_type + >::type; }; template using lowest_layer_type = typename lowest_layer_type_impl< - boost::remove_cv_ref_t + boost::remove_cv_ref_t >::type; template lowest_layer_type& lowest_layer(T&& a) { - if constexpr (has_next_layer) - return lowest_layer(a.next_layer()); - else - return std::forward(a); + if constexpr (has_next_layer) + return lowest_layer(a.next_layer()); + else + return std::forward(a); } // tls layer @@ -162,78 +161,78 @@ struct has_tls_layer_impl : std::false_type {}; template struct has_tls_layer_impl< - T, std::enable_if_t> + T, std::enable_if_t> > : std::true_type {}; template struct has_tls_layer_impl< - T, std::enable_if_t && has_next_layer> + T, std::enable_if_t && has_next_layer> > : has_tls_layer_impl< - boost::remove_cv_ref_t().next_layer())> + boost::remove_cv_ref_t().next_layer())> > {}; template constexpr bool has_tls_layer = has_tls_layer_impl< - boost::remove_cv_ref_t + boost::remove_cv_ref_t >::value; // tls context template using tls_context_sig = decltype( - std::declval().tls_context() + std::declval().tls_context() ); template constexpr bool has_tls_context = boost::is_detected< - tls_context_sig, T + tls_context_sig, T >::value; // setup_tls_sni template void setup_tls_sni(const authority_path& ap, TlsContext& ctx, Stream& s) { - if constexpr (has_tls_handshake) - assign_tls_sni(ap, ctx, s); - else if constexpr (has_next_layer) - setup_tls_sni(ap, ctx, next_layer(s)); + if constexpr (has_tls_handshake) + assign_tls_sni(ap, ctx, s); + else if constexpr (has_next_layer) + setup_tls_sni(ap, ctx, next_layer(s)); } // async_write template using async_write_sig = decltype( - std::declval().async_write(std::declval()...) + std::declval().async_write(std::declval()...) ); constexpr auto write_handler_t = [](error_code, size_t) {}; template constexpr bool has_async_write = boost::is_detected< - async_write_sig, T, B, decltype(write_handler_t) + async_write_sig, T, B, decltype(write_handler_t) >::value; template < - typename Stream, - typename ConstBufferSequence, - typename CompletionToken + typename Stream, + typename ConstBufferSequence, + typename CompletionToken > decltype(auto) async_write( - Stream& stream, const ConstBufferSequence& buff, CompletionToken&& token + Stream& stream, const ConstBufferSequence& buff, CompletionToken&& token ) { - if constexpr (has_async_write) - return stream.async_write( - buff, std::forward(token) - ); - else - return asio::async_write( - stream, buff, std::forward(token) - ); + if constexpr (has_async_write) + return stream.async_write( + buff, std::forward(token) + ); + else + return asio::async_write( + stream, buff, std::forward(token) + ); } } // end namespace detail -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 -#endif // !ASYNC_MQTT5_ASYNC_TRAITS_HPP +#endif // !BOOST_MQTT5_ASYNC_TRAITS_HPP diff --git a/include/boost/mqtt5/detail/cancellable_handler.hpp b/include/boost/mqtt5/detail/cancellable_handler.hpp new file mode 100644 index 0000000..88af7e7 --- /dev/null +++ b/include/boost/mqtt5/detail/cancellable_handler.hpp @@ -0,0 +1,98 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_CANCELLABLE_HANDLER_HPP +#define BOOST_MQTT5_CANCELLABLE_HANDLER_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +template +class cancellable_handler { + Executor _executor; + Handler _handler; + tracking_type _handler_ex; + asio::cancellation_state _cancellation_state; + +public: + cancellable_handler(Handler&& handler, const Executor& ex) : + _executor(ex), + _handler(std::move(handler)), + _handler_ex(tracking_executor(_handler, ex)), + _cancellation_state( + asio::get_associated_cancellation_slot(_handler), + asio::enable_total_cancellation {}, + asio::enable_terminal_cancellation {} + ) + {} + + cancellable_handler(cancellable_handler&&) = default; + cancellable_handler(const cancellable_handler&) = delete; + + cancellable_handler& operator=(cancellable_handler&&) = default; + cancellable_handler& operator=(const cancellable_handler&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using cancellation_slot_type = asio::associated_cancellation_slot_t; + cancellation_slot_type get_cancellation_slot() const noexcept { + return _cancellation_state.slot(); + } + + using executor_type = tracking_type; + executor_type get_executor() const noexcept { + return _handler_ex; + } + + using immediate_executor_type = + asio::associated_immediate_executor_t; + immediate_executor_type get_immediate_executor() const noexcept { + // get_associated_immediate_executor will require asio::execution::blocking.never + // on the default executor. + return asio::get_associated_immediate_executor(_handler, _executor); + } + + asio::cancellation_type_t cancelled() const { + return _cancellation_state.cancelled(); + } + + template + void complete(Args&&... args) { + asio::get_associated_cancellation_slot(_handler).clear(); + asio::dispatch( + _handler_ex, + asio::prepend(std::move(_handler), std::forward(args)...) + ); + } + + template + void complete_immediate(Args&&... args) { + asio::get_associated_cancellation_slot(_handler).clear(); + auto ex = get_immediate_executor(); + asio::dispatch( + ex, + asio::prepend(std::move(_handler), std::forward(args)...) + ); + } + +}; + +} // end boost::mqtt5::detail + +#endif // !BOOST_MQTT5_CANCELLABLE_HANDLER_HPP diff --git a/include/boost/mqtt5/detail/channel_traits.hpp b/include/boost/mqtt5/detail/channel_traits.hpp new file mode 100644 index 0000000..ad77fe5 --- /dev/null +++ b/include/boost/mqtt5/detail/channel_traits.hpp @@ -0,0 +1,103 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_CHANNEL_TRAITS_HPP +#define BOOST_MQTT5_CHANNEL_TRAITS_HPP + +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; +using error_code = boost::system::error_code; + +template +class bounded_deque { + std::deque _buffer; + static constexpr size_t MAX_SIZE = 65535; + +public: + bounded_deque() = default; + bounded_deque(size_t n) : _buffer(n) {} + + size_t size() const { + return _buffer.size(); + } + + template + void push_back(E&& e) { + if (_buffer.size() == MAX_SIZE) + _buffer.pop_front(); + _buffer.push_back(std::forward(e)); + } + + void pop_front() { + _buffer.pop_front(); + } + + void clear() { + _buffer.clear(); + } + + const auto& front() const noexcept { + return _buffer.front(); + } + + auto& front() noexcept { + return _buffer.front(); + } +}; + +template +struct channel_traits { + template + struct rebind { + using other = channel_traits; + }; +}; + +template +struct channel_traits { + static_assert(sizeof...(Args) > 0); + + template + struct rebind { + using other = channel_traits; + }; + + template + struct container { + using type = bounded_deque; + }; + + using receive_cancelled_signature = R(error_code, Args...); + + template + static void invoke_receive_cancelled(F f) { + std::forward(f)( + asio::error::operation_aborted, + typename std::decay_t()... + ); + } + + using receive_closed_signature = R(error_code, Args...); + + template + static void invoke_receive_closed(F f) { + std::forward(f)( + asio::error::operation_aborted, + typename std::decay_t()... + ); + } +}; + +} // namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_CHANNEL_TRAITS_HPP diff --git a/include/boost/mqtt5/detail/control_packet.hpp b/include/boost/mqtt5/detail/control_packet.hpp new file mode 100644 index 0000000..8e12d08 --- /dev/null +++ b/include/boost/mqtt5/detail/control_packet.hpp @@ -0,0 +1,193 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_CONTROL_PACKET_HPP +#define BOOST_MQTT5_CONTROL_PACKET_HPP + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +/* max varint number (268'435'455) + fixed header size (1 + 4) */ +static constexpr int32_t default_max_send_size = 268'435'460; + +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 +class control_packet { + uint16_t _packet_id; + + using alloc_type = Allocator; + using deleter = boost::alloc_deleter; + std::unique_ptr _packet; + + control_packet( + const Allocator& a, + uint16_t packet_id, std::string packet + ) : + _packet_id(packet_id), + _packet(boost::allocate_unique(a, std::move(packet))) + {} + +public: + control_packet(control_packet&&) noexcept = default; + control_packet(const control_packet&) = delete; + + control_packet& operator=(control_packet&&) noexcept = default; + control_packet& operator=(const control_packet&) = 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)...) + }; + } + + template < + typename EncodeFun, + typename ...Args + > + static control_packet of( + no_pid_, const Allocator& alloc, + EncodeFun&& encode, Args&&... args + ) { + return control_packet { + alloc, uint16_t(0), encode(std::forward(args)...) + }; + } + + size_t size() const { + return _packet->size(); + } + + 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; + } + + std::string_view wire_data() const { + return *_packet; + } +}; + +class packet_id_allocator { + struct interval { + uint16_t start, end; + + interval(uint16_t start, uint16_t end) : + start(start), end(end) + {} + }; + + std::vector _free_ids; + static constexpr uint16_t MAX_PACKET_ID = 65535; + +public: + packet_id_allocator() { + _free_ids.emplace_back(MAX_PACKET_ID, uint16_t(0)); + } + + packet_id_allocator(packet_id_allocator&&) noexcept = default; + packet_id_allocator(const packet_id_allocator&) = delete; + + packet_id_allocator& operator=(packet_id_allocator&&) noexcept = default; + packet_id_allocator& operator=(const packet_id_allocator&) = delete; + + uint16_t allocate() { + if (_free_ids.empty()) return 0; + auto& last = _free_ids.back(); + if (last.start == ++last.end) { + auto ret = last.end; + _free_ids.pop_back(); + return ret; + } + return last.end; + } + + void free(uint16_t pid) { + auto it = std::upper_bound( + _free_ids.begin(), _free_ids.end(), pid, + [](const uint16_t x, const interval& i) { return x > i.start; } + ); + uint16_t* end_p = nullptr; + if (it != _free_ids.begin()) { + auto pit = std::prev(it); + if (pit->end == pid) + end_p = &pit->end; + } + if (it != _free_ids.end() && pid - 1 == it->start) { + if (!end_p) + it->start = pid; + else { + *end_p = it->end; + _free_ids.erase(it); + } + } + else { + if (!end_p) + _free_ids.insert(it, interval(pid, pid - 1)); + else + *end_p = pid - 1; + } + } +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_CONTROL_PACKET_HPP diff --git a/include/boost/mqtt5/detail/internal_types.hpp b/include/boost/mqtt5/detail/internal_types.hpp new file mode 100644 index 0000000..2b9f0f8 --- /dev/null +++ b/include/boost/mqtt5/detail/internal_types.hpp @@ -0,0 +1,117 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_INTERNAL_TYPES_HPP +#define BOOST_MQTT5_INTERNAL_TYPES_HPP + +#include +#include + +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +using byte_citer = std::string::const_iterator; + +using time_stamp = std::chrono::time_point; +using duration = time_stamp::duration; + +struct credentials { + std::string client_id; + std::optional username; + std::optional 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); + } +}; + +class session_state { + uint8_t _flags = 0b00; + + static constexpr uint8_t session_present_flag = 0b01; + static constexpr uint8_t subscriptions_present_flag = 0b10; +public: + void session_present(bool present) { + return update_flag(present, session_present_flag); + } + + bool session_present() const { + return _flags & session_present_flag; + } + + void subscriptions_present(bool present) { + return update_flag(present, subscriptions_present_flag); + } + + bool subscriptions_present() const { + return _flags & subscriptions_present_flag; + } + +private: + void update_flag(bool set, uint8_t flag) { + if (set) + _flags |= flag; + else + _flags &= ~flag; + } +}; + +struct mqtt_ctx { + credentials creds; + std::optional will_msg; + uint16_t keep_alive = 60; + connect_props co_props; + connack_props ca_props; + session_state state; + any_authenticator authenticator; + + mqtt_ctx() = default; + + mqtt_ctx(const mqtt_ctx& other) : + creds(other.creds), will_msg(other.will_msg), + keep_alive(other.keep_alive), co_props(other.co_props), + ca_props {}, state {}, + authenticator(other.authenticator) + {} +}; + +struct disconnect_ctx { + 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 boost::mqtt5::detail + +#endif // !BOOST_MQTT5_INTERNAL_TYPES_HPP diff --git a/include/boost/mqtt5/detail/log_invoke.hpp b/include/boost/mqtt5/detail/log_invoke.hpp new file mode 100644 index 0000000..50a5cfc --- /dev/null +++ b/include/boost/mqtt5/detail/log_invoke.hpp @@ -0,0 +1,76 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_LOG_INVOKE_HPP +#define BOOST_MQTT5_LOG_INVOKE_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; +using boost::system::error_code; + +template +class log_invoke { + LoggerType _logger; +public: + explicit log_invoke(LoggerType logger = {}) : + _logger(std::move(logger)) + {} + + void at_resolve( + error_code ec, std::string_view host, std::string_view port, + const asio::ip::tcp::resolver::results_type& eps + ) { + if constexpr (has_at_resolve) + _logger.at_resolve(ec, host, port, eps); + } + + void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) { + if constexpr (has_at_tcp_connect) + _logger.at_tcp_connect(ec, ep); + } + + void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) { + if constexpr (has_at_tls_handshake) + _logger.at_tls_handshake(ec, ep); + } + + void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) { + if constexpr (has_at_ws_handshake) + _logger.at_ws_handshake(ec, ep); + } + + void at_connack( + reason_code rc, + bool session_present, const connack_props& ca_props + ) { + if constexpr (has_at_connack) + _logger.at_connack(rc, session_present, ca_props); + } + + void at_disconnect(reason_code rc, const disconnect_props& dc_props) { + if constexpr (has_at_disconnect) + _logger.at_disconnect(rc, dc_props); + } + +}; + +} // end namespace boost::mqtt5::detail + + +#endif // !BOOST_MQTT5_LOG_INVOKE_HPP diff --git a/include/async_mqtt5/detail/rebind_executor.hpp b/include/boost/mqtt5/detail/rebind_executor.hpp similarity index 60% rename from include/async_mqtt5/detail/rebind_executor.hpp rename to include/boost/mqtt5/detail/rebind_executor.hpp index b44605d..96f30a7 100644 --- a/include/async_mqtt5/detail/rebind_executor.hpp +++ b/include/boost/mqtt5/detail/rebind_executor.hpp @@ -5,8 +5,8 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_REBIND_EXECUTOR_HPP -#define ASYNC_MQTT5_REBIND_EXECUTOR_HPP +#ifndef BOOST_MQTT5_REBIND_EXECUTOR_HPP +#define BOOST_MQTT5_REBIND_EXECUTOR_HPP namespace boost::asio::ssl { @@ -25,34 +25,34 @@ class stream; }// end namespace boost::beast::websocket -namespace async_mqtt5::detail { +namespace boost::mqtt5::detail { namespace asio = boost::asio; template struct rebind_executor { - using other = typename Stream::template rebind_executor::other; + using other = typename Stream::template rebind_executor::other; }; // asio::ssl::stream does not define a rebind_executor member type template struct rebind_executor, Executor> { - using other = typename asio::ssl::stream< - typename rebind_executor::other - >; + using other = typename asio::ssl::stream< + typename rebind_executor::other + >; }; template struct rebind_executor< - boost::beast::websocket::stream, deflate_supported>, - Executor + boost::beast::websocket::stream, deflate_supported>, + Executor > { - using other = typename boost::beast::websocket::stream< - asio::ssl::stream::other>, - deflate_supported - >; + using other = typename boost::beast::websocket::stream< + asio::ssl::stream::other>, + deflate_supported + >; }; -} // end namespace async_mqtt5::detail +} // end namespace boost::mqtt5::detail -#endif // !ASYNC_MQTT5_REBIND_EXECUTOR_HPP +#endif // !BOOST_MQTT5_REBIND_EXECUTOR_HPP diff --git a/include/boost/mqtt5/detail/topic_validation.hpp b/include/boost/mqtt5/detail/topic_validation.hpp new file mode 100644 index 0000000..63fbeb9 --- /dev/null +++ b/include/boost/mqtt5/detail/topic_validation.hpp @@ -0,0 +1,119 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_TOPIC_VALIDATION_HPP +#define BOOST_MQTT5_TOPIC_VALIDATION_HPP + +#include + +#include +#include + +namespace boost::mqtt5::detail { + +static constexpr int32_t min_subscription_identifier = 1; +static constexpr int32_t max_subscription_identifier = 268'435'455; + +static constexpr std::string_view shared_sub_prefix = "$share/"; + +inline bool is_utf8_no_wildcard(validation_result result) { + return result == validation_result::valid; +} + +inline bool is_not_empty(size_t sz) { + return sz != 0; +} + +inline bool is_valid_topic_size(size_t sz) { + return is_not_empty(sz) && is_valid_string_size(sz); +} + +inline validation_result validate_topic_name(std::string_view str) { + return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard); +} + +inline validation_result validate_topic_alias_name(std::string_view str) { + return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard); +} + +inline validation_result validate_shared_topic_name(std::string_view str) { + return validate_impl(str, is_not_empty, is_utf8_no_wildcard); +} + +inline validation_result validate_topic_filter(std::string_view str) { + if (!is_valid_topic_size(str.size())) + return validation_result::invalid; + + constexpr int multi_lvl_wildcard = '#'; + constexpr int single_lvl_wildcard = '+'; + + // must be the last character preceded by '/' or stand alone + // #, .../# + if (str.back() == multi_lvl_wildcard) { + str.remove_suffix(1); + + if (!str.empty() && str.back() != '/') + return validation_result::invalid; + } + + int last_c = -1; + validation_result result; + while (!str.empty()) { + int c = pop_front_unichar(str); + + // can be used at any level, but must occupy an entire level + // +, +/..., .../+/..., .../+ + bool is_valid_single_lvl = (c == single_lvl_wildcard) && + (str.empty() || str.front() == '/') && + (last_c == -1 || last_c == '/'); + + result = validate_mqtt_utf8_char(c); + if ( + result == validation_result::valid || + is_valid_single_lvl + ) { + last_c = c; + continue; + } + + return validation_result::invalid; + } + + return validation_result::valid; +} + +inline validation_result validate_shared_topic_filter( + std::string_view str, bool wildcard_allowed = true +) { + if (!is_valid_topic_size(str.size())) + return validation_result::invalid; + + if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0) + return validation_result::invalid; + + str.remove_prefix(shared_sub_prefix.size()); + + size_t share_name_end = str.find_first_of('/'); + if (share_name_end == std::string::npos) + return validation_result::invalid; + + validation_result result; + result = validate_shared_topic_name(str.substr(0, share_name_end)); + + if (result != validation_result::valid) + return validation_result::invalid; + + auto topic_filter = str.substr(share_name_end + 1); + return wildcard_allowed ? + validate_topic_filter(topic_filter) : + validate_topic_name(topic_filter) + ; +} + +} // end namespace boost::mqtt5::detail + +#endif //BOOST_MQTT5_TOPIC_VALIDATION_HPP diff --git a/include/async_mqtt5/detail/traits.hpp b/include/boost/mqtt5/detail/traits.hpp similarity index 75% rename from include/async_mqtt5/detail/traits.hpp rename to include/boost/mqtt5/detail/traits.hpp index 5a47fa9..783def4 100644 --- a/include/async_mqtt5/detail/traits.hpp +++ b/include/boost/mqtt5/detail/traits.hpp @@ -5,18 +5,18 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TRAITS_HPP -#define ASYNC_MQTT5_TRAITS_HPP - -#include -#include -#include +#ifndef BOOST_MQTT5_TRAITS_HPP +#define BOOST_MQTT5_TRAITS_HPP #include #include #include -namespace async_mqtt5::detail { +#include +#include +#include + +namespace boost::mqtt5::detail { template constexpr bool is_optional_impl = false; @@ -35,29 +35,29 @@ constexpr bool is_specialization, T> = true; template constexpr bool is_vector = is_specialization< - boost::remove_cv_ref_t, std::vector + boost::remove_cv_ref_t, std::vector >; template constexpr std::true_type is_small_vector_impl( - boost::container::small_vector_base const & + boost::container::small_vector_base const & ); constexpr std::false_type is_small_vector_impl( ... ); template constexpr bool is_small_vector = - decltype(is_small_vector_impl(std::declval()))::value; + decltype(is_small_vector_impl(std::declval()))::value; template constexpr bool is_pair = is_specialization< - boost::remove_cv_ref_t, std::pair + boost::remove_cv_ref_t, std::pair >; template constexpr bool is_boost_iterator = is_specialization< - boost::remove_cv_ref_t, boost::iterator_range + boost::remove_cv_ref_t, boost::iterator_range >; -} // end namespace async_mqtt5::detail +} // end namespace boost::mqtt5::detail -#endif // !ASYNC_MQTT5_TRAITS_HPP +#endif // !BOOST_MQTT5_TRAITS_HPP diff --git a/include/boost/mqtt5/detail/utf8_mqtt.hpp b/include/boost/mqtt5/detail/utf8_mqtt.hpp new file mode 100644 index 0000000..358762b --- /dev/null +++ b/include/boost/mqtt5/detail/utf8_mqtt.hpp @@ -0,0 +1,116 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_UTF8_MQTT_HPP +#define BOOST_MQTT5_UTF8_MQTT_HPP + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +enum class validation_result : uint8_t { + valid = 0, + has_wildcard_character, + invalid +}; + +inline int pop_front_unichar(std::string_view& s) { + // assuming that s.length() is > 0 + + int n = s[0] & 0xF0; + int ch = -1; + + if ((n & 0x80) == 0) { + ch = s[0]; + s.remove_prefix(1); + } + else if ((n == 0xC0 || n == 0xD0) && s.size() > 1) { + ch = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F); + s.remove_prefix(2); + } + else if ((n == 0xE0) && s.size() > 2) { + ch = ((s[0] & 0x1F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); + s.remove_prefix(3); + } + else if ((n == 0xF0) && s.size() > 3) { + ch = ((s[0] & 0x1F) << 18) | ((s[1] & 0x3F) << 12) | + ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); + s.remove_prefix(4); + } + + return ch; +} + +inline validation_result validate_mqtt_utf8_char(int c) { + constexpr int fe_flag = 0xFE; + constexpr int ff_flag = 0xFF; + + constexpr int multi_lvl_wildcard = '#'; + constexpr int single_lvl_wildcard = '+'; + + if (c == multi_lvl_wildcard || c == single_lvl_wildcard) + return validation_result::has_wildcard_character; + + if (c > 0x001F && // U+0000...U+001F control characters + (c < 0x007F || c > 0x009F) && // U+007F...0+009F control characters + (c < 0xD800 || c > 0xDFFF) && // U+D800...U+DFFF surrogates + (c < 0xFDD0 || c > 0xFDEF) && // U+FDD0...U+FDEF non-characters + (c & fe_flag) != fe_flag && // non-characters + (c & ff_flag) != ff_flag + ) + return validation_result::valid; + + return validation_result::invalid; +} + +inline bool is_valid_string_size(size_t sz) { + constexpr size_t max_sz = 65535; + return sz <= max_sz; +} + +inline bool is_utf8(validation_result result) { + return result == validation_result::valid || + result == validation_result::has_wildcard_character; +} + +template +validation_result validate_impl( + std::string_view str, + ValidSizeCondition&& size_condition, ValidCondition&& condition +) { + if (!size_condition(str.size())) + return validation_result::invalid; + + validation_result result; + while (!str.empty()) { + int c = pop_front_unichar(str); + + result = validate_mqtt_utf8_char(c); + if (!condition(result)) + return result; + } + + return validation_result::valid; +} + +inline validation_result validate_mqtt_utf8(std::string_view str) { + return validate_impl(str, is_valid_string_size, is_utf8); +} + +inline bool is_valid_string_pair( + const std::pair& str_pair +) { + return validate_mqtt_utf8(str_pair.first) == validation_result::valid && + validate_mqtt_utf8(str_pair.second) == validation_result::valid; +} + +} // namespace boost::mqtt5::detail + +#endif //BOOST_MQTT5_UTF8_MQTT_HPP diff --git a/include/boost/mqtt5/error.hpp b/include/boost/mqtt5/error.hpp new file mode 100644 index 0000000..3ec451f --- /dev/null +++ b/include/boost/mqtt5/error.hpp @@ -0,0 +1,168 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_ERROR_HPP +#define BOOST_MQTT5_ERROR_HPP + +#include + +#include +#include +#include + +namespace boost::mqtt5 { + +/** + * \brief A representation of Disconnect Reason Code. + * + * \details Represents all Reason Codes that the Client can send to the Server + * in the \__DISCONNECT\__ packet as the reason for the disconnection. + */ +enum class disconnect_rc_e : uint8_t { + /** Close the connection normally. Do not send the Will Message. */ + normal_disconnection = 0x00, + + /** The Client wishes to disconnect but requires that + the Server also publishes its Will Message. */ + disconnect_with_will_message = 0x04 +}; + +namespace detail { + +enum class disconnect_rc_e : 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 { +/** + * \brief Defines error codes related to MQTT client. + * + * \details Encapsulates errors that occur on the client side. + */ +enum class error : int { + /** The packet is malformed */ + malformed_packet = 100, + + /** The packet has exceeded the Maximum Packet Size the Server is willing to accept */ + packet_too_large, + + /** The Client's session does not exist or it has expired */ + session_expired, + + /** There are no more available Packet Identifiers to use */ + pid_overrun, + + /** The Topic is invalid and does not conform to the specification */ + invalid_topic, + + // publish + /** The Server does not support the specified \ref qos_e */ + qos_not_supported, + + /** The Server does not support retained messages */ + retain_not_available, + + /** The Client attempted to send a Topic Alias that is greater than Topic Alias Maximum */ + topic_alias_maximum_reached, + + // subscribe + /** The Server does not support Wildcard Subscriptions */ + wildcard_subscription_not_available, + + /** The Server does not support this Subscription Identifier */ + subscription_identifier_not_available, + + /** The Server does not support Shared Subscriptions */ + shared_subscription_not_available +}; + + +inline std::string client_error_to_string(error err) { + switch (err) { + case error::malformed_packet: + return "The packet is malformed"; + case error::packet_too_large: + return "The packet has exceeded the Maximum Packet Size " + "the Server is willing to accept"; + case error::session_expired: + return "The Client's session does not exist or it has expired"; + case error::pid_overrun: + return "There are no more available Packet Identifiers to use"; + case error::invalid_topic: + return "The Topic is invalid and " + "does not conform to the specification"; + case error::qos_not_supported: + return "The Server does not support the specified QoS"; + case error::retain_not_available: + return "The Server does not support retained messages"; + case error::topic_alias_maximum_reached: + return "The Client attempted to send a Topic Alias " + "that is greater than Topic Alias Maximum"; + case error::wildcard_subscription_not_available: + return "The Server does not support Wildcard Subscriptions"; + case error::subscription_identifier_not_available: + return "The Server does not support this Subscription Identifier"; + case error::shared_subscription_not_available: + return "The Server does not support Shared Subscriptions"; + 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(ev)); + } +}; + +/// Returns the error category associated with \ref client::error. +inline const client_ec_category& get_error_code_category() { + static client_ec_category cat; + return cat; +} + +/// Creates an \ref error_code from a \ref client::error. +inline boost::system::error_code make_error_code(error r) { + return { static_cast(r), get_error_code_category() }; +} + +inline std::ostream& operator<<(std::ostream& os, const error& err) { + os << get_error_code_category().name() << ":" << static_cast(err); + return os; +} + +} // end namespace client + +} // end namespace boost::mqtt5 + +namespace boost::system { + +template <> +struct is_error_code_enum : std::true_type {}; + +} // end namespace boost::system + + +#endif // !BOOST_MQTT5_ERROR_HPP diff --git a/include/boost/mqtt5/impl/assemble_op.hpp b/include/boost/mqtt5/impl/assemble_op.hpp new file mode 100644 index 0000000..7626a02 --- /dev/null +++ b/include/boost/mqtt5/impl/assemble_op.hpp @@ -0,0 +1,243 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_ASSEMBLE_OP_HPP +#define BOOST_MQTT5_ASSEMBLE_OP_HPP + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +class data_span : private std::pair { + using base = std::pair; +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 +class assemble_op { + using client_service = ClientService; + using handler_type = Handler; + + struct on_read {}; + + static constexpr uint32_t max_recv_size = 65'536; + + client_service& _svc; + handler_type _handler; + + std::string& _read_buff; + data_span& _data_span; + +public: + assemble_op( + client_service& svc, handler_type&& 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; + + assemble_op& operator=(assemble_op&&) noexcept = default; + assemble_op& operator=(const assemble_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_handler); + } + + template + void perform(CompletionCondition cc) { + _read_buff.erase( + _read_buff.cbegin(), _data_span.first() + ); + _read_buff.resize( + _svc.connect_property(prop::maximum_packet_size).value_or(max_recv_size) + ); + _data_span = { + _read_buff.cbegin(), + _read_buff.cbegin() + _data_span.size() + }; + + if (cc(error_code {}, 0) == 0 && _data_span.size()) { + return asio::post( + _svc.get_executor(), + asio::prepend( + std::move(*this), on_read {}, error_code {}, + 0, 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), compute_read_timeout(), + asio::prepend( + asio::append(std::move(*this), std::move(cc)), + on_read {} + ) + ); + } + + template + void operator()( + on_read, error_code ec, size_t bytes_read, + CompletionCondition cc + ) { + if (ec == asio::error::try_again) { + _svc.update_session_state(); + _svc._async_sender.resend(); + _data_span = { _read_buff.cend(), _read_buff.cend() }; + return perform(std::move(cc)); + } + + if (ec) + return complete(ec, 0, {}, {}); + + _data_span.expand_suffix(bytes_read); + assert(_data_span.size()); + + auto control_byte = uint8_t(*_data_span.first()); + + if ((control_byte & 0b11110000) == 0) + // close the connection, cancel + return complete(client::error::malformed_packet, 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(asio::transfer_at_least(1)); + return complete(client::error::malformed_packet, 0, {}, {}); + } + + auto recv_size = _svc.connect_property(prop::maximum_packet_size) + .value_or(max_recv_size); + if (static_cast(*varlen) > recv_size - std::distance(_data_span.first(), first)) + return complete(client::error::malformed_packet, 0, {}, {}); + + if (std::distance(first, _data_span.last()) < *varlen) + return perform(asio::transfer_at_least(1)); + + _data_span.remove_prefix( + std::distance(_data_span.first(), first) + *varlen + ); + + dispatch(control_byte, first, first + *varlen); + } + +private: + duration compute_read_timeout() const { + auto negotiated_ka = _svc.negotiated_keep_alive(); + return negotiated_ka ? + std::chrono::milliseconds(3 * negotiated_ka * 1000 / 2) : + duration(std::numeric_limits::max()); + } + + static bool valid_header(uint8_t control_byte) { + auto code = control_code_e(control_byte & 0b11110000); + + if (code == control_code_e::publish) + return true; + + auto res = control_byte & 0b00001111; + if (code == control_code_e::pubrel) + return res == 0b00000010; + return res == 0b00000000; + } + + void dispatch( + uint8_t control_byte, byte_citer first, byte_citer last + ) { + using namespace decoders; + + if (!valid_header(control_byte)) + return complete(client::error::malformed_packet, 0, {}, {}); + + auto code = control_code_e(control_byte & 0b11110000); + + if (code == control_code_e::pingresp) + return perform(asio::transfer_at_least(0)); + + bool is_reply = code != control_code_e::publish && + code != control_code_e::auth && + code != control_code_e::disconnect; + + if (is_reply) { + auto packet_id = decoders::decode_packet_id(first).value(); + _svc._replies.dispatch(error_code {}, code, packet_id, first, last); + return perform(asio::transfer_at_least(0)); + } + + complete(error_code {}, control_byte, first, last); + } + + void complete( + error_code ec, uint8_t control_code, + byte_citer first, byte_citer last + ) { + if (ec) + _data_span = { _read_buff.cend(), _read_buff.cend() }; + std::move(_handler)(ec, control_code, first, last); + } +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_ASSEMBLE_OP_HPP diff --git a/include/boost/mqtt5/impl/async_sender.hpp b/include/boost/mqtt5/impl/async_sender.hpp new file mode 100644 index 0000000..1de6d79 --- /dev/null +++ b/include/boost/mqtt5/impl/async_sender.hpp @@ -0,0 +1,303 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_ASYNC_SENDER_HPP +#define BOOST_MQTT5_ASYNC_SENDER_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::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; + + using handler_type = asio::any_completion_handler; + handler_type _handler; + +public: + write_req( + asio::const_buffer buffer, + serial_num_t serial_num, unsigned flags, + handler_type handler + ) : + _buffer(buffer), _serial_num(serial_num), _flags(flags), + _handler(std::move(handler)) + {} + + write_req(write_req&&) = default; + write_req(const write_req&) = delete; + + write_req& operator=(write_req&&) = default; + write_req& operator=(const write_req&) = delete; + + static serial_num_t next_serial_num(serial_num_t last) { + return last + 1; + } + + asio::const_buffer buffer() const { + return _buffer; + } + + void complete(error_code ec) { + std::move(_handler)(ec); + } + + void complete_post(const asio::any_io_executor& ex, error_code ec) { + asio::post( + ex, + asio::prepend(std::move(_handler), ec) + ); + } + + bool empty() const { + return !_handler; + } + + 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) < (1u << (SERIAL_BITS - 1)); + + return (s1 - s2) >= (1u << (SERIAL_BITS - 1)); + } + +private: + bool prioritized() const { + return _flags & send_flag::prioritized; + } +}; + + +template +class async_sender { + using self_type = async_sender; + + using client_service = ClientService; + + using queue_allocator_type = asio::recycling_allocator; + using write_queue_t = std::vector; + + 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: + explicit async_sender(ClientService& svc) : _svc(svc) {} + + async_sender(async_sender&&) = default; + async_sender(const async_sender&) = delete; + + async_sender& operator=(async_sender&&) = default; + async_sender& operator=(const async_sender&) = delete; + + using allocator_type = queue_allocator_type; + allocator_type get_allocator() const noexcept { + return allocator_type {}; + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc.get_executor(); + } + + serial_num_t next_serial_num() { + return _last_serial_num = write_req::next_serial_num(_last_serial_num); + } + + template + decltype(auto) async_send( + const BufferType& buffer, + serial_num_t serial_num, unsigned flags, + CompletionToken&& token + ) { + using Signature = void (error_code); + + auto initiation = []( + auto handler, self_type& self, const BufferType& buffer, + serial_num_t serial_num, unsigned flags + ) { + self._write_queue.emplace_back( + asio::buffer(buffer), serial_num, flags, std::move(handler) + ); + self.do_write(); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this), + buffer, serial_num, flags + ); + } + + void cancel() { + auto ops = std::move(_write_queue); + for (auto& op : ops) + op.complete_post(_svc.get_executor(), 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_property(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) { + _svc.update_session_state(); + _write_queue.insert( + _write_queue.begin(), + std::make_move_iterator(write_queue.begin()), + std::make_move_iterator(write_queue.end()) + ); + return resend(); + } + + if (ec == asio::error::no_recovery) + _svc.cancel(); + + // 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 { + for (write_req& req : _write_queue) + if (!req.throttled()) + write_queue.push_back(std::move(req)); + else if (_quota > 0) { + --_quota; + write_queue.push_back(std::move(req)); + } + + if (write_queue.empty()) { + _write_in_progress = false; + return; + } + + auto it = std::remove_if( + _write_queue.begin(), _write_queue.end(), + [](const write_req& req) { return req.empty(); } + ); + _write_queue.erase(it, _write_queue.end()); + } + + std::vector 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 boost::mqtt5::detail + +#endif // !BOOST_MQTT5_ASYNC_SENDER_HPP diff --git a/include/boost/mqtt5/impl/autoconnect_stream.hpp b/include/boost/mqtt5/impl/autoconnect_stream.hpp new file mode 100644 index 0000000..88860f4 --- /dev/null +++ b/include/boost/mqtt5/impl/autoconnect_stream.hpp @@ -0,0 +1,236 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_AUTOCONNECT_STREAM_HPP +#define BOOST_MQTT5_AUTOCONNECT_STREAM_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; +using error_code = boost::system::error_code; + +template < + typename StreamType, + typename StreamContext = std::monostate, + typename LoggerType = noop_logger +> +class autoconnect_stream { +public: + using self_type = autoconnect_stream; + using stream_type = StreamType; + using stream_context_type = StreamContext; + using logger_type = LoggerType; + using executor_type = typename stream_type::executor_type; +private: + using stream_ptr = std::shared_ptr; + + 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; + + log_invoke& _log; + + template + friend class read_op; + + template + friend class write_op; + + template + friend class reconnect_op; + +public: + autoconnect_stream( + const executor_type& ex, stream_context_type& context, + log_invoke& log + ) : + _stream_executor(ex), + _conn_mtx(_stream_executor), + _read_timer(_stream_executor), _connect_timer(_stream_executor), + _endpoints(_stream_executor, _connect_timer, log), + _stream_context(context), + _log(log) + { + replace_next_layer(construct_next_layer()); + } + + autoconnect_stream(const autoconnect_stream&) = delete; + autoconnect_stream& operator=(const autoconnect_stream&) = delete; + + 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); + } + + void clone_endpoints(const autoconnect_stream& other) { + _endpoints.clone_servers(other._endpoints); + } + + bool is_open() const noexcept { + return lowest_layer(*_stream_ptr).is_open(); + } + + void open() { + open_lowest_layer(_stream_ptr, asio::ip::tcp::v4()); + } + + void cancel() { + error_code ec; + lowest_layer(*_stream_ptr).cancel(ec); + _conn_mtx.cancel(); + _connect_timer.cancel(); + } + + void close() { + error_code ec; + shutdown(asio::ip::tcp::socket::shutdown_both); + lowest_layer(*_stream_ptr).close(ec); + } + + 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 + decltype(auto) async_read_some( + const BufferType& buffer, duration wait_for, CompletionToken&& token + ) { + using Signature = void (error_code, size_t); + + auto initiation = []( + auto handler, self_type& self, + const BufferType& buffer, duration wait_for + ) { + read_op { self, std::move(handler) }.perform(buffer, wait_for); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this), buffer, wait_for + ); + } + + template + decltype(auto) async_write( + const BufferType& buffer, CompletionToken&& token + ) { + using Signature = void (error_code, size_t); + + auto initiation = []( + auto handler, self_type& self, const BufferType& buffer + ) { + write_op { self, std::move(handler) }.perform(buffer); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this), buffer + ); + } + +private: + + log_invoke& log() { + return _log; + } + + static void open_lowest_layer(const stream_ptr& sptr, asio::ip::tcp protocol) { + error_code ec; + auto& layer = lowest_layer(*sptr); + layer.open(protocol, ec); + layer.set_option(asio::socket_base::reuse_address(true), ec); + layer.set_option(asio::ip::tcp::no_delay(true), ec); + } + + stream_ptr construct_next_layer() const { + stream_ptr sptr; + if constexpr (has_tls_context) + sptr = std::make_shared( + _stream_executor, _stream_context.tls_context() + ); + else + sptr = std::make_shared(_stream_executor); + + return sptr; + } + + stream_ptr construct_and_open_next_layer(asio::ip::tcp protocol) const { + auto sptr = construct_next_layer(); + open_lowest_layer(sptr, protocol); + 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 + decltype(auto) async_reconnect(stream_ptr s, CompletionToken&& token) { + using Signature = void (error_code); + + auto initiation = [](auto handler, self_type& self, stream_ptr s) { + reconnect_op { self, std::move(handler) }.perform(std::move(s)); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this), std::move(s) + ); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_AUTOCONNECT_STREAM_HPP diff --git a/include/boost/mqtt5/impl/client_service.hpp b/include/boost/mqtt5/impl/client_service.hpp new file mode 100644 index 0000000..1be451f --- /dev/null +++ b/include/boost/mqtt5/impl/client_service.hpp @@ -0,0 +1,524 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_CLIENT_SERVICE_HPP +#define BOOST_MQTT5_CLIENT_SERVICE_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template < + typename StreamType, typename TlsContext, + typename Enable = void +> +class stream_context; + +template < + typename StreamType, typename TlsContext +> +class stream_context< + StreamType, TlsContext, + std::enable_if_t> +> { + using tls_context_type = TlsContext; + + mqtt_ctx _mqtt_context; + std::shared_ptr _tls_context_ptr; + +public: + explicit stream_context(TlsContext tls_context) : + _tls_context_ptr(std::make_shared(std::move(tls_context))) + {} + + stream_context(const stream_context& other) : + _mqtt_context(other._mqtt_context), _tls_context_ptr(other._tls_context_ptr) + {} + + auto& mqtt_context() { + return _mqtt_context; + } + + const auto& mqtt_context() const { + return _mqtt_context; + } + + auto& tls_context() { + return *_tls_context_ptr; + } + + auto& session_state() { + return _mqtt_context.state; + } + + const auto& session_state() const { + return _mqtt_context.state; + } + + void will(will will) { + _mqtt_context.will_msg = std::move(will); + } + + template + const auto& connack_property( + std::integral_constant prop + ) const { + return _mqtt_context.ca_props[prop]; + } + + const auto& connack_properties() const { + return _mqtt_context.ca_props; + } + + template + const auto& connect_property( + std::integral_constant prop + ) const { + return _mqtt_context.co_props[prop]; + } + + template + auto& connect_property( + std::integral_constant prop + ) { + return _mqtt_context.co_props[prop]; + } + + void connect_properties(connect_props props) { + _mqtt_context.co_props = std::move(props); + } + + void credentials( + std::string client_id, + std::string username = "", std::string password = "" + ) { + _mqtt_context.creds = { + std::move(client_id), + std::move(username), std::move(password) + }; + } + + template + void authenticator(Authenticator&& authenticator) { + _mqtt_context.authenticator = any_authenticator( + std::forward(authenticator) + ); + } +}; + +template +class stream_context< + StreamType, std::monostate, + std::enable_if_t> +> { + mqtt_ctx _mqtt_context; +public: + explicit stream_context(std::monostate) {} + + stream_context(const stream_context& other) : + _mqtt_context(other._mqtt_context) + {} + + auto& mqtt_context() { + return _mqtt_context; + } + + const auto& mqtt_context() const { + return _mqtt_context; + } + + auto& session_state() { + return _mqtt_context.state; + } + + const auto& session_state() const { + return _mqtt_context.state; + } + + void will(will will) { + _mqtt_context.will_msg = std::move(will); + } + + template + const auto& connack_property( + std::integral_constant prop + ) const { + return _mqtt_context.ca_props[prop]; + } + + const auto& connack_properties() const { + return _mqtt_context.ca_props; + } + + template + const auto& connect_property( + std::integral_constant prop + ) const { + return _mqtt_context.co_props[prop]; + } + + template + auto& connect_property( + std::integral_constant prop + ) { + return _mqtt_context.co_props[prop]; + } + + void connect_properties(connect_props props) { + _mqtt_context.co_props = std::move(props); + } + + void credentials( + std::string client_id, + std::string username = "", std::string password = "" + ) { + _mqtt_context.creds = { + std::move(client_id), + std::move(username), std::move(password) + }; + } + + template + void authenticator(Authenticator&& authenticator) { + _mqtt_context.authenticator = any_authenticator( + std::forward(authenticator) + ); + } +}; + +template < + typename StreamType, + typename TlsContext = std::monostate, + typename LoggerType = noop_logger +> +class client_service { + using self_type = client_service; + using stream_context_type = stream_context; + using stream_type = autoconnect_stream< + StreamType, stream_context_type, LoggerType + >; +public: + using executor_type = typename stream_type::executor_type; +private: + using tls_context_type = TlsContext; + using logger_type = LoggerType; + using receive_channel = asio::experimental::basic_channel< + executor_type, + channel_traits<>, + void (error_code, std::string, std::string, publish_props) + >; + + template + friend class run_op; + + template + friend class async_sender; + + template + friend class assemble_op; + + template + friend class ping_op; + + template + friend class sentry_op; + + template + friend class re_auth_op; + + executor_type _executor; + + log_invoke _log; + + stream_context_type _stream_context; + stream_type _stream; + + packet_id_allocator _pid_allocator; + replies _replies; + async_sender _async_sender; + + std::string _read_buff; + data_span _active_span; + + receive_channel _rec_channel; + + asio::steady_timer _ping_timer; + asio::steady_timer _sentry_timer; + + client_service(const client_service& other) : + _executor(other._executor), + _log(other._log), + _stream_context(other._stream_context), + _stream(_executor, _stream_context, _log), + _replies(_executor), + _async_sender(*this), + _active_span(_read_buff.cend(), _read_buff.cend()), + _rec_channel(_executor, std::numeric_limits::max()), + _ping_timer(_executor), + _sentry_timer(_executor) + { + _stream.clone_endpoints(other._stream); + } + +public: + + explicit client_service( + const executor_type& ex, + tls_context_type tls_context = {}, logger_type logger = {} + ) : + _executor(ex), + _log(std::move(logger)), + _stream_context(std::move(tls_context)), + _stream(ex, _stream_context, _log), + _replies(ex), + _async_sender(*this), + _active_span(_read_buff.cend(), _read_buff.cend()), + _rec_channel(ex, std::numeric_limits::max()), + _ping_timer(ex), + _sentry_timer(ex) + {} + + executor_type get_executor() const noexcept { + return _executor; + } + + auto dup() const { + return std::shared_ptr(new client_service(*this)); + } + + template < + typename Ctx = TlsContext, + std::enable_if_t, bool> = true + > + decltype(auto) tls_context() { + 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 Authenticator, + std::enable_if_t, bool> = true + > + void authenticator(Authenticator&& authenticator) { + if (!is_open()) + _stream_context.authenticator( + std::forward(authenticator) + ); + } + + uint16_t negotiated_keep_alive() const { + return connack_property(prop::server_keep_alive) + .value_or(_stream_context.mqtt_context().keep_alive); + } + + void keep_alive(uint16_t seconds) { + if (!is_open()) + _stream_context.mqtt_context().keep_alive = seconds; + } + + template + const auto& connect_property( + std::integral_constant prop + ) const { + return _stream_context.connect_property(prop); + } + + template + void connect_property( + std::integral_constant prop, + prop::value_type_t

value + ){ + if (!is_open()) + _stream_context.connect_property(prop) = value; + } + + void connect_properties(connect_props props) { + if (!is_open()) + _stream_context.connect_properties(std::move(props)); + } + + template + const auto& connack_property( + std::integral_constant prop + ) const { + return _stream_context.connack_property(prop); + } + + const auto& connack_properties() const { + return _stream_context.connack_properties(); + } + + void open_stream() { + _stream.open(); + } + + bool is_open() const { + return _stream.is_open(); + } + + void close_stream() { + _stream.close(); + } + + void cancel() { + if (!_stream.is_open()) return; + + _ping_timer.cancel(); + _sentry_timer.cancel(); + + _rec_channel.close(); + _replies.cancel_unanswered(); + _async_sender.cancel(); + _stream.cancel(); + _stream.close(); + } + + log_invoke& log() { + return _log; + } + + 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(); + } + + bool subscriptions_present() const { + return _stream_context.session_state().subscriptions_present(); + } + + void subscriptions_present(bool present) { + _stream_context.session_state().subscriptions_present(present); + } + + void update_session_state() { + auto& session_state = _stream_context.session_state(); + + if (!session_state.session_present()) { + _replies.clear_pending_pubrels(); + session_state.session_present(true); + + if (session_state.subscriptions_present()) { + channel_store_error(client::error::session_expired); + session_state.subscriptions_present(false); + } + } + + _ping_timer.cancel(); + } + + 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) + ); + } + + bool channel_store_error(error_code ec) { + return _rec_channel.try_send( + ec, std::string {}, std::string {}, publish_props {} + ); + } + + template + 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(token) + ); + } + + template + decltype(auto) async_assemble(CompletionToken&& token) { + using Signature = void (error_code, uint8_t, byte_citer, byte_citer); + + auto initiation = [] ( + auto handler, self_type& self, + std::string& read_buff, data_span& active_span + ) { + assemble_op { + self, std::move(handler), read_buff, active_span + }.perform(asio::transfer_at_least(0)); + }; + + return asio::async_initiate ( + initiation, token, std::ref(*this), + std::ref(_read_buff), std::ref(_active_span) + ); + } + + template + 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(token) + ); + } + + template + decltype(auto) async_channel_receive(CompletionToken&& token) { + return _rec_channel.async_receive(std::forward(token)); + } + +}; + +} // namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_CLIENT_SERVICE_HPP diff --git a/include/boost/mqtt5/impl/codecs/base_decoders.hpp b/include/boost/mqtt5/impl/codecs/base_decoders.hpp new file mode 100644 index 0000000..2510459 --- /dev/null +++ b/include/boost/mqtt5/impl/codecs/base_decoders.hpp @@ -0,0 +1,462 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_BASE_DECODERS_HPP +#define BOOST_MQTT5_BASE_DECODERS_HPP + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost::mqtt5::decoders { + +namespace x3 = boost::spirit::x3; + +template +struct convert { using type = T; }; + +template +struct convert> { + using type = std::tuple::type...>; +}; + +template +struct convert> { + using type = std::optional; +}; + +template +struct convert> { + using type = std::vector::type>; +}; + +template +constexpr auto as(Parser&& p) { + return x3::rule{} = std::forward(p); +} + + +template +auto type_parse(It& first, const It last, const Parser& p) { + using ctx_type = decltype(x3::make_context(std::declval())); + using attr_type = typename x3::traits::attribute_of::type; + + using rv_type = typename convert::type; + + std::optional rv; + rv_type value {}; + if (x3::phrase_parse(first, last, as(p), x3::eps(false), value)) + rv = std::move(value); + return rv; +} + + +template +auto type_parse(It& first, const It last, const Parser& p) { + std::optional rv; + AttributeType value {}; + if (x3::phrase_parse(first, last, as(p), x3::eps(false), value)) + rv = std::move(value); + return rv; +} + +namespace basic { + +template +constexpr auto to(T& arg) { + return [&](auto& ctx) { + using ctx_type = decltype(ctx); + using attr_type = decltype(x3::_attr(std::declval())); + if constexpr (detail::is_boost_iterator) + arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() }; + else + arg = x3::_attr(ctx); + }; +} + +template +class scope_limit {}; + +template +class scope_limit< + LenParser, Subject, + std::enable_if_t::value> +> : + public x3::unary_parser> +{ + using base_type = x3::unary_parser>; + LenParser _lp; +public: + using ctx_type = + decltype(x3::make_context(std::declval())); + using attribute_type = + typename x3::traits::attribute_of::type; + + static bool const has_attribute = true; + + scope_limit(const LenParser& lp, const Subject& subject) : + base_type(subject), _lp(lp) + {} + + template + bool parse( + It& first, const It last, + const Ctx& ctx, RCtx& rctx, Attr& attr + ) const { + + It iter = first; + typename x3::traits::attribute_of::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 +class scope_limit< + Size, Subject, + std::enable_if_t> +> : + public x3::unary_parser> +{ + using base_type = x3::unary_parser>; + size_t _limit; +public: + using ctx_type = + decltype(x3::make_context(std::declval())); + using attribute_type = + typename x3::traits::attribute_of::type; + + static bool const has_attribute = true; + + scope_limit(Size limit, const Subject& subject) : + base_type(subject), _limit(limit) + {} + + template + 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 +struct scope_limit_gen { + template + auto operator[](const Subject& p) const { + return scope_limit { _lp, x3::as_parser(p) }; + } + LenParser _lp; +}; + +template +struct scope_limit_gen< + Size, + std::enable_if_t> +> { + template + auto operator[](const Subject& p) const { + return scope_limit { limit, x3::as_parser(p) }; + } + Size limit; +}; + +template < + typename Parser, + std::enable_if_t::value, bool> = true +> +scope_limit_gen scope_limit_(const Parser& p) { + return { p }; +} + +template < + typename Size, + std::enable_if_t, bool> = true +> +scope_limit_gen scope_limit_(Size limit) { + return { limit }; +} + +struct verbatim_parser : x3::parser { + using attribute_type = std::string; + static bool const has_attribute = true; + + template + 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 { + using attribute_type = int32_t; + static bool const has_attribute = true; + + template + bool parse( + It& first, const It last, + const Ctx& ctx, RCtx&, Attr& attr + ) const { + + It iter = first; + x3::skip_over(iter, last, ctx); + + if (iter == last) + return false; + + int32_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(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 { + using attribute_type = std::string; + static bool const has_attribute = true; + + template + 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::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 +class conditional_parser : + public x3::unary_parser> { + + using base_type = x3::unary_parser>; + bool _condition; +public: + using ctx_type = + decltype(x3::make_context(std::declval())); + using subject_attr_type = + typename x3::traits::attribute_of::type; + + using attribute_type = boost::optional; + static bool const has_attribute = true; + + conditional_parser(const Subject& s, bool condition) : + base_type(s), _condition(condition) {} + + template + 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 + auto operator[](const Subject& p) const { + return conditional_parser { p, _condition }; + } +}; + +inline conditional_gen if_(bool condition) { + return { condition }; +} + +} // end namespace basic + + +namespace prop { + +namespace basic = boost::mqtt5::decoders::basic; + +namespace detail { + +template +bool parse_to_prop( + It& iter, const It last, + const Ctx& ctx, RCtx& rctx, Prop& prop +) { + using namespace boost::mqtt5::detail; + using prop_type = std::remove_reference_t; + + bool rv = false; + + if constexpr (std::is_same_v) + rv = x3::byte_.parse(iter, last, ctx, rctx, prop); + else if constexpr (std::is_same_v) + rv = x3::big_word.parse(iter, last, ctx, rctx, prop); + else if constexpr (std::is_same_v) + rv = basic::varint_.parse(iter, last, ctx, rctx, prop); + else if constexpr (std::is_same_v) + rv = x3::big_dword.parse(iter, last, ctx, rctx, prop); + else if constexpr (std::is_same_v) + rv = basic::utf8_.parse(iter, last, ctx, rctx, prop); + + else if constexpr (is_optional) { + typename prop_type::value_type val; + rv = parse_to_prop(iter, last, ctx, rctx, val); + if (rv) prop.emplace(std::move(val)); + } + + else if constexpr (is_pair) { + rv = parse_to_prop(iter, last, ctx, rctx, prop.first); + rv = parse_to_prop(iter, last, ctx, rctx, prop.second); + } + + else if constexpr (is_vector || is_small_vector) { + typename std::remove_reference_t::value_type value; + rv = parse_to_prop(iter, last, ctx, rctx, value); + if (rv) prop.push_back(std::move(value)); + } + + return rv; +} + +} // end namespace detail + +template +class prop_parser : public x3::parser> { +public: + using attribute_type = Props; + static bool const has_attribute = true; + + template + 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; + + int32_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) + return false; + } + + first = iter; + return true; + } +}; + + +template +constexpr auto props_ = prop_parser {}; + +} // end namespace prop + + +} // end namespace boost::mqtt5::decoders + +#endif // !BOOST_MQTT5_BASE_DECODERS_HPP diff --git a/include/boost/mqtt5/impl/codecs/base_encoders.hpp b/include/boost/mqtt5/impl/codecs/base_encoders.hpp new file mode 100644 index 0000000..28ed886 --- /dev/null +++ b/include/boost/mqtt5/impl/codecs/base_encoders.hpp @@ -0,0 +1,514 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_BASE_ENCODERS_HPP +#define BOOST_MQTT5_BASE_ENCODERS_HPP + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace boost::mqtt5::encoders { + +namespace basic { + +using varint_t = int*; + +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 +class flag_def : public encoder { + template + 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 + > + > + >; + + template + friend class flag_def; + + repr _val { 0 }; + +public: + flag_def(repr val) : _val(val) {} + flag_def() = default; + + template < + typename T, + typename projection = boost::identity, + std::enable_if_t, bool> = true + > + auto operator()(T&& value, projection proj = {}) const { + if constexpr (std::is_same_v) { + repr val = value.has_value(); + return flag_def { val }; + } + else { + repr val = value.has_value() ? + static_cast(std::invoke(proj, *value)) : 0; + return flag_def { val }; + } + } + + template < + typename T, + typename projection = boost::identity, + std::enable_if_t, bool> = true + > + auto operator()(T&& value, projection proj = {}) const { + auto val = static_cast(std::invoke(proj, value)); + return flag_def { val }; + } + + size_t byte_size() const { return sizeof(repr); } + + template + auto operator|(const flag_def& rhs) const { + using res_repr = least_type; + auto val = static_cast((_val << rhs_bits) | rhs._val); + return flag_def { 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(s.data() + sz); + endian_store(p, _val); + return s; + } +}; + +template +constexpr auto flag = flag_def{}; + + +template +class int_val : public encoder { + T _val; +public: + int_val(T val) : _val(val) {} + + size_t byte_size() const { + if constexpr (detail::is_optional) { + if (_val) return val_length(*_val); + return 0; + } + else + return val_length(_val); + } + + std::string& encode(std::string& s) const { + if constexpr (detail::is_optional) { + if (_val) return encode_val(s, *_val); + return s; + } + else + return encode_val(s, _val); + } +private: + template + static size_t val_length(U&& val) { + if constexpr (std::is_same_v) + return variable_length(int32_t(val)); + else + return sizeof(Repr); + } + + template + static std::string& encode_val(std::string& s, U&& val) { + using namespace boost::endian; + if constexpr (std::is_same_v) { + to_variable_bytes(s, int32_t(val)); + return s; + } + else { + size_t sz = s.size(); s.resize(sz + sizeof(Repr)); + auto p = reinterpret_cast(s.data() + sz); + endian_store(p, val); + return s; + } + } +}; + +template +class int_def { +public: + template + auto operator()(T&& val) const { + return int_val { std::forward(val) }; + } + + template + auto operator()(T&& val, projection proj) const { + if constexpr (detail::is_optional) { + using rv_type = std::invoke_result_t< + projection, typename boost::remove_cv_ref_t::value_type + >; + if (val.has_value()) + return (*this)(std::invoke(proj, *val)); + return int_val { rv_type {} }; + } + else { + using rv_type = std::invoke_result_t; + return int_val { std::invoke(proj, val) }; + } + } +}; + +constexpr auto byte_ = int_def {}; +constexpr auto int16_ = int_def {}; +constexpr auto int32_ = int_def {}; +constexpr auto varlen_ = int_def {}; + + +template +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 || std::is_same_v + ); + } + + size_t byte_size() const { + if constexpr (detail::is_optional) + return _val ? _with_length * 2 + val_length(*_val) : 0; + else + return _with_length * 2 + val_length(_val); + } + + std::string& encode(std::string& s) const { + if constexpr (detail::is_optional) { + if (_val) return encode_val(s, *_val); + return s; + } + else + return encode_val(s, _val); + } + +private: + template + using has_size = decltype(std::declval().size()); + + template + static size_t val_length(U&& val) { + if constexpr (std::is_same_v, const char*>) + return std::strlen(val); + + if constexpr (boost::is_detected_exact_v) + return val.size(); + else // fallback to type const char (&)[N] (substract 1 for trailing 0) + return sizeof(val) - 1; + } + + template + std::string& encode_val(std::string& s, U&& u) const { + using namespace boost::endian; + auto byte_len = val_length(std::forward(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(s.data() + sz); + endian_store( + p, int16_t(byte_len) + ); + } + s.append(std::begin(u), std::begin(u) + byte_len); + return s; + } +}; + +template +class array_def { +public: + template + auto operator()(T&& val) const { + return array_val { std::forward(val), with_length }; + } + + template + auto operator()(T&& val, projection proj) const { + if constexpr (detail::is_optional) { + using rv_type = std::invoke_result_t< + projection, typename boost::remove_cv_ref_t::value_type + >; + if (val.has_value()) + return (*this)(std::invoke(proj, *val)); + return array_val { rv_type {}, false }; + } + else { + const auto& av = std::invoke(proj, val); + return array_val { av, true }; + } + } +}; + +using utf8_def = array_def; + +constexpr auto utf8_ = utf8_def {}; +constexpr auto binary_ = array_def {}; // for now +constexpr auto verbatim_ = array_def {}; + + +template +class composed_val : public encoder { + T _lhs; U _rhs; +public: + composed_val(T lhs, U rhs) : + _lhs(std::forward(lhs)), _rhs(std::forward(rhs)) + {} + + size_t byte_size() const { + return _lhs.byte_size() + _rhs.byte_size(); + } + + std::string& encode(std::string& s) const { + _lhs.encode(s); + return _rhs.encode(s); + } +}; + +template < + typename T, typename U, + std::enable_if_t< + std::is_base_of_v> && + std::is_base_of_v>, + bool + > = true +> +inline auto operator&(T&& t, U&& u) { + return composed_val(std::forward(t), std::forward(u)); +} + +template < + typename T, + std::enable_if_t>, bool> = true +> +std::string& operator<<(std::string& s, T&& t) { + return t.encode(s); +} + +} // end namespace basic + +namespace prop { + +namespace pp = boost::mqtt5::prop; + +template +auto encoder_for_prop_value(const T& val) { + if constexpr (std::is_same_v) + return basic::int_def{}(val); + else if constexpr (std::is_same_v) + return basic::int_def{}(val); + else if constexpr (std::is_same_v) + return basic::int_def{}(val); + else if constexpr (std::is_same_v) + return basic::int_def{}(val); + else if constexpr (std::is_same_v) + return basic::utf8_def{}(val); + else if constexpr (detail::is_pair) + return encoder_for_prop_value(val.first) & + encoder_for_prop_value(val.second); +} + +template +class prop_val; + +template < + typename T, pp::property_type p +> +class prop_val< + T, p, + std::enable_if_t && detail::is_optional> +> : public basic::encoder { + // allows T to be reference type to std::optional + static inline boost::remove_cv_ref_t nulltype; + T _val; +public: + prop_val(T val) : _val(val) { + static_assert(std::is_reference_v); + } + prop_val() : _val(nulltype) {} + + size_t byte_size() const { + if (!_val) return 0; + return 1 + encoder_for_prop_value(*_val).byte_size(); + } + + std::string& encode(std::string& s) const { + if (!_val) + return s; + s.push_back(p); + return encoder_for_prop_value(*_val).encode(s); + } +}; + +template < + typename T, pp::property_type p +> +class prop_val< + T, p, + std::enable_if_t || detail::is_small_vector> +> : public basic::encoder { + // allows T to be reference type to std::vector + static inline boost::remove_cv_ref_t nulltype; + T _val; +public: + prop_val(T val) : _val(val) { + static_assert(std::is_reference_v); + } + + prop_val() : _val(nulltype) { } + + size_t byte_size() const { + if (_val.empty()) return 0; + + size_t total_size = 0; + for (const auto& elem : _val) + total_size += 1 + encoder_for_prop_value(elem).byte_size(); + + return total_size; + } + + std::string& encode(std::string& s) const { + if (_val.empty()) + return s; + + for (const auto& elem: _val) { + s.push_back(p); + encoder_for_prop_value(elem).encode(s); + } + + return s; + } +}; + + +template +class props_val : public basic::encoder { + static inline std::decay_t nulltype; + + template + static auto to_prop_val(const T& val) { + return prop_val(val); + } + + template + static auto to_prop_vals(const pp::properties& props) { + return std::make_tuple( + to_prop_val( + props[std::integral_constant {}] + )... + ); + } + + template + 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())) _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_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 +class props_def { +public: + template + auto operator()(T&& prop_container) const { + if constexpr (detail::is_optional) { + if (prop_container.has_value()) + return (*this)(*prop_container); + return props_val< + const typename boost::remove_cv_ref_t::value_type& + >(true); + } + else { + return props_val { prop_container, may_omit }; + } + } +}; + +constexpr auto props_ = props_def {}; +constexpr auto props_may_omit_ = props_def {}; + +} // end namespace prop + +} // end namespace boost::mqtt5::encoders + +#endif // !BOOST_MQTT5_BASE_ENCODERS_HPP diff --git a/include/boost/mqtt5/impl/codecs/message_decoders.hpp b/include/boost/mqtt5/impl/codecs/message_decoders.hpp new file mode 100644 index 0000000..88fa809 --- /dev/null +++ b/include/boost/mqtt5/impl/codecs/message_decoders.hpp @@ -0,0 +1,309 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_MESSAGE_DECODERS_HPP +#define BOOST_MQTT5_MESSAGE_DECODERS_HPP + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +namespace boost::mqtt5::decoders { + +using byte_citer = detail::byte_citer; + +using fixed_header = std::tuple< + uint8_t, // control byte + uint32_t // remaining_length +>; + +inline std::optional 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 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, // user_name, + std::optional, // password, + uint16_t, // keep_alive, + bool, // clean_start, + connect_props, // props, + std::optional // will +>; + +inline std::optional 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_; + + const byte_citer end = it + remain_length; + auto vh = type_parse(it, end, var_header_); + if (!vh) + return std::optional{}; + + auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh; + + if (mqtt_str != "MQTT" || version != 5) + return std::optional{}; + + 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_] >> + 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, end, payload_); + if (!pload) + return std::optional{}; + + std::optional 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 { std::move(retval) }; +} + +using connack_message = std::tuple< + uint8_t, // session_present + uint8_t, // connect reason code + connack_props // props +>; + +inline std::optional decode_connack( + uint32_t remain_length, byte_citer& it +) { + auto connack_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> x3::byte_ >> prop::props_ + ]; + return type_parse(it, it + remain_length, connack_); +} + +using publish_message = std::tuple< + std::string, // topic + std::optional, // packet_id + uint8_t, // dup_e, qos_e, retain_e + publish_props, // publish props + std::string // payload +>; + +inline std::optional 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_ >> 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 decode_puback( + uint32_t remain_length, byte_citer& it +) { + if (remain_length == 0) + return puback_message {}; + auto puback_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> prop::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 decode_pubrec( + uint32_t remain_length, byte_citer& it +) { + if (remain_length == 0) + return pubrec_message {}; + auto pubrec_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> prop::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 decode_pubrel( + uint32_t remain_length, byte_citer& it +) { + if (remain_length == 0) + return pubrel_message {}; + auto pubrel_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> prop::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 decode_pubcomp( + uint32_t remain_length, byte_citer& it +) { + if (remain_length == 0) + return pubcomp_message {}; + auto pubcomp_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> prop::props_ + ]; + return type_parse(it, it + remain_length, pubcomp_); +} + +using subscribe_message = std::tuple< + subscribe_props, + std::vector> // topic filter with opts +>; + +inline std::optional decode_subscribe( + uint32_t remain_length, byte_citer& it +) { + auto subscribe_ = basic::scope_limit_(remain_length)[ + prop::props_ >> +(basic::utf8_ >> x3::byte_) + ]; + return type_parse(it, it + remain_length, subscribe_); +} + +using suback_message = std::tuple< + suback_props, + std::vector // reason_codes +>; + +inline std::optional decode_suback( + uint32_t remain_length, byte_citer& it +) { + auto suback_ = basic::scope_limit_(remain_length)[ + prop::props_ >> +x3::byte_ + ]; + return type_parse(it, it + remain_length, suback_); +} + +using unsubscribe_message = std::tuple< + unsubscribe_props, + std::vector // topics +>; + +inline std::optional decode_unsubscribe( + uint32_t remain_length, byte_citer& it +) { + auto unsubscribe_ = basic::scope_limit_(remain_length)[ + prop::props_ >> +basic::utf8_ + ]; + return type_parse(it, it + remain_length, unsubscribe_); +} + +using unsuback_message = std::tuple< + unsuback_props, + std::vector // reason_codes +>; + +inline std::optional decode_unsuback( + uint32_t remain_length, byte_citer& it +) { + auto unsuback_ = basic::scope_limit_(remain_length)[ + prop::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 decode_disconnect( + uint32_t remain_length, byte_citer& it +) { + if (remain_length == 0) + return disconnect_message {}; + auto disconnect_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> prop::props_ + ]; + return type_parse(it, it + remain_length, disconnect_); +} + +using auth_message = std::tuple< + uint8_t, // reason_code + auth_props +>; + +inline std::optional decode_auth( + uint32_t remain_length, byte_citer& it +) { + if (remain_length == 0) + return auth_message {}; + auto auth_ = basic::scope_limit_(remain_length)[ + x3::byte_ >> prop::props_ + ]; + return type_parse(it, it + remain_length, auth_); +} + + +} // end namespace boost::mqtt5::decoders + +#endif // !BOOST_MQTT5_MESSAGE_DECODERS_HPP diff --git a/include/boost/mqtt5/impl/codecs/message_encoders.hpp b/include/boost/mqtt5/impl/codecs/message_encoders.hpp new file mode 100644 index 0000000..b32d96b --- /dev/null +++ b/include/boost/mqtt5/impl/codecs/message_encoders.hpp @@ -0,0 +1,432 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_MESSAGE_ENCODERS_HPP +#define BOOST_MQTT5_MESSAGE_ENCODERS_HPP + +#include + +#include + +#include +#include +#include +#include +#include + +namespace boost::mqtt5::encoders { + +template +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 user_name, + std::optional password, + uint16_t keep_alive, bool clean_start, + const connect_props& props, + const std::optional& 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 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& 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, + const std::vector& 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& 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, + const std::vector& 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_(uint8_t(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_(uint8_t(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 boost::mqtt5::encoders + +#endif // !BOOST_MQTT5_MESSAGE_ENCODERS_HPP diff --git a/include/boost/mqtt5/impl/connect_op.hpp b/include/boost/mqtt5/impl/connect_op.hpp new file mode 100644 index 0000000..58ebafd --- /dev/null +++ b/include/boost/mqtt5/impl/connect_op.hpp @@ -0,0 +1,436 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_CONNECT_OP_HPP +#define BOOST_MQTT5_CONNECT_OP_HPP + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost::mqtt5::detail { + +template +class connect_op { + static constexpr size_t min_packet_sz = 5; + + struct on_connect {}; + struct on_tls_handshake {}; + struct on_ws_handshake {}; + struct on_send_connect {}; + struct on_fixed_header {}; + struct on_read_packet {}; + struct on_init_auth_data {}; + struct on_auth_data {}; + struct on_send_auth {}; + struct on_complete_auth {}; + + Stream& _stream; + mqtt_ctx& _ctx; + log_invoke& _log; + + using handler_type = asio::any_completion_handler; + handler_type _handler; + + std::unique_ptr _buffer_ptr; + asio::cancellation_state _cancellation_state; + + using endpoint = asio::ip::tcp::endpoint; + +public: + template + connect_op( + Stream& stream, mqtt_ctx& ctx, + log_invoke& log, + Handler&& handler + ) : + _stream(stream), _ctx(ctx), _log(log), + _handler(std::forward(handler)), + _cancellation_state( + asio::get_associated_cancellation_slot(_handler), + asio::enable_total_cancellation {}, + asio::enable_total_cancellation {} + ) + {} + + connect_op(connect_op&&) = default; + connect_op(const connect_op&) = delete; + + connect_op& operator=(connect_op&&) = default; + connect_op& operator=(const connect_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using cancellation_slot_type = + asio::associated_cancellation_slot_t; + cancellation_slot_type get_cancellation_slot() const noexcept { + return _cancellation_state.slot(); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_handler); + } + + void perform(const endpoint& ep, authority_path ap) { + lowest_layer(_stream).async_connect( + ep, + asio::append( + asio::prepend(std::move(*this), on_connect {}), + ep, std::move(ap) + ) + ); + } + + void operator()( + on_connect, error_code ec, endpoint ep, authority_path ap + ) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + _log.at_tcp_connect(ec, ep); + if (ec) + return complete(ec); + + do_tls_handshake(std::move(ep), std::move(ap)); + } + + void do_tls_handshake(endpoint ep, authority_path ap) { + if constexpr (has_tls_handshake) { + _stream.async_handshake( + tls_handshake_type::client, + asio::append( + asio::prepend(std::move(*this), on_tls_handshake {}), + std::move(ep), std::move(ap) + ) + ); + } + else if constexpr ( + has_tls_handshake> + ) { + _stream.next_layer().async_handshake( + tls_handshake_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, endpoint ep, authority_path ap + ) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + _log.at_tls_handshake(ec, ep); + if (ec) + return complete(ec); + + do_ws_handshake(std::move(ep), std::move(ap)); + } + + void do_ws_handshake(endpoint ep, authority_path ap) { + if constexpr (has_ws_handshake) + // If you get a compilation error here, + // it might be because of a missing include + ws_handshake_traits::async_handshake( + _stream, std::move(ap), + asio::append( + asio::prepend(std::move(*this), on_ws_handshake {}), ep + ) + ); + else + (*this)(on_ws_handshake {}, error_code {}, ep); + } + + void operator()(on_ws_handshake, error_code ec, endpoint ep) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if constexpr (has_ws_handshake) + _log.at_ws_handshake(ec, ep); + + if (ec) + return complete(ec); + + auto auth_method = _ctx.authenticator.method(); + if (!auth_method.empty()) { + _ctx.co_props[prop::authentication_method] = auth_method; + return _ctx.authenticator.async_auth( + auth_step_e::client_initial, "", + asio::prepend(std::move(*this), on_init_auth_data {}) + ); + } + + send_connect(); + } + + void operator()(on_init_auth_data, error_code ec, std::string data) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(asio::error::try_again); + + _ctx.co_props[prop::authentication_data] = std::move(data); + send_connect(); + } + + void send_connect() { + auto packet = control_packet::of( + no_pid, get_allocator(), + encoders::encode_connect, + _ctx.creds.client_id, + _ctx.creds.username, _ctx.creds.password, + _ctx.keep_alive, false, _ctx.co_props, _ctx.will_msg + ); + + auto wire_data = packet.wire_data(); + + 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 (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(ec); + + _buffer_ptr = std::make_unique(min_packet_sz, char(0)); + + auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz); + asio::async_read( + _stream, buff, asio::transfer_all(), + asio::prepend(std::move(*this), on_fixed_header {}) + ); + } + + void operator()( + on_fixed_header, error_code ec, size_t num_read + ) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(ec); + + auto code = control_code_e((*_buffer_ptr)[0] & 0b11110000); + + if (code != control_code_e::auth && code != control_code_e::connack) + 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) + return 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); + + if (num_read + remain_len > _buffer_ptr->size()) + _buffer_ptr->resize(num_read + 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::transfer_all(), + asio::prepend( + asio::append(std::move(*this), code, first, last), + on_read_packet {} + ) + ); + } + + void operator()( + on_read_packet, error_code ec, size_t, control_code_e code, + byte_citer first, byte_citer last + ) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(ec); + + if (code == control_code_e::connack) + return on_connack(first, last); + + if (!_ctx.co_props[prop::authentication_method].has_value()) + return complete(client::error::malformed_packet); + + on_auth(first, last); + } + + void on_connack(byte_citer first, byte_citer last) { + auto packet_length = static_cast(std::distance(first, last)); + auto rv = decoders::decode_connack(packet_length, first); + if (!rv.has_value()) + return complete(client::error::malformed_packet); + const auto& [session_present, reason_code, ca_props] = *rv; + + _ctx.ca_props = ca_props; + _ctx.state.session_present(session_present); + + // 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_code); + if (!rc.has_value()) // reason code not allowed in CONNACK + return complete(client::error::malformed_packet); + + _log.at_connack(*rc, session_present, ca_props); + if (*rc) + return complete(asio::error::try_again); + + if (_ctx.co_props[prop::authentication_method].has_value()) + return _ctx.authenticator.async_auth( + auth_step_e::server_final, + ca_props[prop::authentication_data].value_or(""), + asio::prepend(std::move(*this), on_complete_auth {}) + ); + + complete(error_code {}); + } + + void on_auth(byte_citer first, byte_citer last) { + auto packet_length = static_cast(std::distance(first, last)); + auto rv = decoders::decode_auth(packet_length, first); + if (!rv.has_value()) + return complete(client::error::malformed_packet); + const auto& [reason_code, auth_props] = *rv; + + auto rc = to_reason_code(reason_code); + if ( + !rc.has_value() || + auth_props[prop::authentication_method] + != _ctx.co_props[prop::authentication_method] + ) + return complete(client::error::malformed_packet); + + _ctx.authenticator.async_auth( + auth_step_e::server_challenge, + auth_props[prop::authentication_data].value_or(""), + asio::prepend(std::move(*this), on_auth_data {}) + ); + } + + void operator()(on_auth_data, error_code ec, std::string data) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(asio::error::try_again); + + auth_props props; + props[prop::authentication_method] = + _ctx.co_props[prop::authentication_method]; + props[prop::authentication_data] = std::move(data); + + auto packet = control_packet::of( + no_pid, get_allocator(), + encoders::encode_auth, + reason_codes::continue_authentication.value(), props + ); + + auto wire_data = packet.wire_data(); + + detail::async_write( + _stream, asio::buffer(wire_data), + asio::consign( + asio::prepend(std::move(*this), on_send_auth {}), + std::move(packet) + ) + ); + } + + void operator()(on_send_auth, error_code ec, size_t) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(ec); + + auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz); + asio::async_read( + _stream, buff, asio::transfer_all(), + asio::prepend(std::move(*this), on_fixed_header {}) + ); + } + + void operator()(on_complete_auth, error_code ec, std::string) { + if (is_cancelled()) + return complete(asio::error::operation_aborted); + + if (ec) + return complete(asio::error::try_again); + + complete(error_code {}); + } + +private: + bool is_cancelled() const { + return _cancellation_state.cancelled() != asio::cancellation_type::none; + } + + void complete(error_code ec) { + _cancellation_state.slot().clear(); + std::move(_handler)(ec); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_CONNECT_OP_HPP diff --git a/include/boost/mqtt5/impl/disconnect_op.hpp b/include/boost/mqtt5/impl/disconnect_op.hpp new file mode 100644 index 0000000..d822267 --- /dev/null +++ b/include/boost/mqtt5/impl/disconnect_op.hpp @@ -0,0 +1,317 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_DISCONNECT_OP_HPP +#define BOOST_MQTT5_DISCONNECT_OP_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template < + typename ClientService, + typename DisconnectContext +> +class disconnect_op { + using client_service = ClientService; + + struct on_disconnect {}; + + std::shared_ptr _svc_ptr; + DisconnectContext _context; + + using handler_type = cancellable_handler< + asio::any_completion_handler, + typename ClientService::executor_type + >; + handler_type _handler; + +public: + template + disconnect_op( + std::shared_ptr svc_ptr, + DisconnectContext&& context, Handler&& handler + ) : + _svc_ptr(std::move(svc_ptr)), _context(std::move(context)), + _handler(std::move(handler), _svc_ptr->get_executor()) + { + auto slot = asio::get_associated_cancellation_slot(_handler); + if (slot.is_connected()) + slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { + svc.cancel(); + }); + } + + disconnect_op(disconnect_op&&) = default; + disconnect_op(const disconnect_op&) = delete; + + disconnect_op& operator=(disconnect_op&&) = default; + disconnect_op& operator=(const disconnect_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform() { + error_code ec = validate_disconnect(_context.props); + if (ec) + return complete_immediate(ec); + + auto disconnect = control_packet::of( + no_pid, get_allocator(), + encoders::encode_disconnect, + static_cast(_context.reason_code), _context.props + ); + + auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) + .value_or(default_max_send_size); + if (disconnect.size() > max_packet_size) + // drop properties + return send_disconnect(control_packet::of( + no_pid, get_allocator(), + encoders::encode_disconnect, + static_cast(_context.reason_code), disconnect_props {} + )); + + send_disconnect(std::move(disconnect)); + } + + void send_disconnect(control_packet disconnect) { + auto wire_data = disconnect.wire_data(); + _svc_ptr->async_send( + wire_data, + no_serial, send_flag::terminal, + asio::prepend( + std::move(*this), + on_disconnect {}, std::move(disconnect) + ) + ); + } + + void operator()( + on_disconnect, + control_packet 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. + + if ( + ec == asio::error::operation_aborted || + ec == asio::error::no_recovery + ) + return complete(asio::error::operation_aborted); + + if (ec == asio::error::try_again) { + if (_context.terminal) + return send_disconnect(std::move(disconnect)); + return complete(error_code {}); + } + + if (_context.terminal) { + _svc_ptr->cancel(); + return complete(error_code {}); + } + + _svc_ptr->close_stream(); + _svc_ptr->open_stream(); + + complete(error_code {}); + } + +private: + static error_code validate_disconnect(const disconnect_props& props) { + const auto& reason_string = props[prop::reason_string]; + if ( + reason_string && + validate_mqtt_utf8(*reason_string) != validation_result::valid + ) + return client::error::malformed_packet; + + const auto& user_properties = props[prop::user_property]; + for (const auto& user_property: user_properties) + if (!is_valid_string_pair(user_property)) + return client::error::malformed_packet; + return error_code {}; + } + + void complete(error_code ec) { + _handler.complete(ec); + } + + void complete_immediate(error_code ec) { + _handler.complete_immediate(ec); + } +}; + +template +class terminal_disconnect_op { + using client_service = ClientService; + + static constexpr uint8_t seconds = 5; + + std::shared_ptr _svc_ptr; + std::unique_ptr _timer; + + using handler_type = Handler; + handler_type _handler; + +public: + terminal_disconnect_op( + std::shared_ptr svc_ptr, + Handler&& handler + ) : + _svc_ptr(std::move(svc_ptr)), + _timer(new asio::steady_timer(_svc_ptr->get_executor())), + _handler(std::move(handler)) + {} + + terminal_disconnect_op(terminal_disconnect_op&&) = default; + terminal_disconnect_op(const terminal_disconnect_op&) = delete; + + terminal_disconnect_op& operator=(terminal_disconnect_op&&) = default; + terminal_disconnect_op& operator=(const terminal_disconnect_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using cancellation_slot_type = asio::associated_cancellation_slot_t; + cancellation_slot_type get_cancellation_slot() const noexcept { + return asio::get_associated_cancellation_slot(_handler); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_handler); + } + + template + void perform(DisconnectContext&& context) { + namespace asioex = boost::asio::experimental; + + auto init_disconnect = []( + auto handler, disconnect_ctx ctx, + std::shared_ptr svc_ptr + ) { + disconnect_op { + std::move(svc_ptr), std::move(ctx), std::move(handler) + }.perform(); + }; + + _timer->expires_after(std::chrono::seconds(seconds)); + + auto timed_disconnect = asioex::make_parallel_group( + asio::async_initiate( + init_disconnect, asio::deferred, + std::forward(context), _svc_ptr + ), + _timer->async_wait(asio::deferred) + ); + + timed_disconnect.async_wait( + asioex::wait_for_one(), std::move(*this) + ); + } + + void operator()( + std::array /* ord */, + error_code disconnect_ec, error_code /* timer_ec */ + ) { + std::move(_handler)(disconnect_ec); + } +}; + +template +class initiate_async_disconnect { + std::shared_ptr _svc_ptr; +public: + explicit initiate_async_disconnect(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)) + {} + + using executor_type = typename ClientService::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + template + void operator()( + Handler&& handler, + disconnect_rc_e rc, const disconnect_props& props + ) { + auto ctx = disconnect_ctx { rc, props, terminal }; + if constexpr (terminal) + terminal_disconnect_op { _svc_ptr, std::move(handler) } + .perform(std::move(ctx)); + else + disconnect_op { _svc_ptr, std::move(ctx), std::move(handler) } + .perform(); + } +}; + +template +decltype(auto) async_disconnect( + disconnect_rc_e reason_code, const disconnect_props& props, + std::shared_ptr svc_ptr, + CompletionToken&& token +) { + using Signature = void (error_code); + return asio::async_initiate( + initiate_async_disconnect(std::move(svc_ptr)), token, + reason_code, props + ); +} + +template +decltype(auto) async_terminal_disconnect( + disconnect_rc_e reason_code, const disconnect_props& props, + std::shared_ptr svc_ptr, + CompletionToken&& token +) { + using Signature = void (error_code); + return asio::async_initiate( + initiate_async_disconnect(std::move(svc_ptr)), token, + reason_code, props + ); +} + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_DISCONNECT_HPP diff --git a/include/boost/mqtt5/impl/endpoints.hpp b/include/boost/mqtt5/impl/endpoints.hpp new file mode 100644 index 0000000..038a793 --- /dev/null +++ b/include/boost/mqtt5/impl/endpoints.hpp @@ -0,0 +1,242 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_ENDPOINTS_HPP +#define BOOST_MQTT5_ENDPOINTS_HPP + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +using epoints = asio::ip::tcp::resolver::results_type; + +template +class resolve_op { + struct on_resolve {}; + + Owner& _owner; + + using handler_type = Handler; + handler_type _handler; + +public: + resolve_op(Owner& owner, Handler&& handler) : + _owner(owner), _handler(std::move(handler)) + {} + + resolve_op(resolve_op&&) = default; + resolve_op(const resolve_op&) = delete; + + resolve_op& operator=(resolve_op&&) = default; + resolve_op& operator=(const resolve_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using cancellation_slot_type = + asio::associated_cancellation_slot_t; + cancellation_slot_type get_cancellation_slot() const noexcept { + return asio::get_associated_cancellation_slot(_handler); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_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 > static_cast(_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_after(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, std::array 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, {}, {}); + + resolve_ec = timer_ec ? resolve_ec : asio::error::timed_out; + _owner._log.at_resolve(resolve_ec, ap.host, ap.port, epts); + 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) { + std::move(_handler)(ec, std::move(eps), std::move(ap)); + } + + void complete_post(error_code ec, epoints eps, authority_path ap) { + asio::post( + _owner.get_executor(), + asio::prepend( + std::move(_handler), ec, + std::move(eps), std::move(ap) + ) + ); + + } +}; + + +template +class endpoints { + using logger_type = LoggerType; + + asio::ip::tcp::resolver _resolver; + asio::steady_timer& _connect_timer; + + std::vector _servers; + + int _current_host { -1 }; + + log_invoke& _log; + + template + friend class resolve_op; + + template + static constexpr auto to_(T& arg) { + return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); }; + } + + template + static constexpr auto as_(Parser&& p){ + return boost::spirit::x3::rule{} = std::forward(p); + } + +public: + template + endpoints( + Executor ex, asio::steady_timer& timer, + log_invoke& log + ) : + _resolver(std::move(ex)), _connect_timer(timer), + _log(log) + {} + + endpoints(const endpoints&) = delete; + endpoints& operator=(const endpoints&) = delete; + + void clone_servers(const endpoints& other) { + _servers = other._servers; + } + + using executor_type = asio::ip::tcp::resolver::executor_type; + // NOTE: asio::ip::basic_resolver returns executor by value + executor_type get_executor() noexcept { + return _resolver.get_executor(); + } + + template + decltype(auto) async_next_endpoint(CompletionToken&& token) { + using Signature = void (error_code, epoints, authority_path); + + auto initiation = [](auto handler, endpoints& self) { + resolve_op { self, std::move(handler) }.perform(); + }; + + return asio::async_initiate( + initiation, token, std::ref(*this) + ); + } + + 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_(+unreserved_)[to_(host)]; + auto port_ = as_(':' >> +digit_)[to_(port)]; + auto path_ = as_(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 boost::mqtt5::detail + +#endif // !BOOST_MQTT5_ENDPOINTS_HPP diff --git a/include/boost/mqtt5/impl/ping_op.hpp b/include/boost/mqtt5/impl/ping_op.hpp new file mode 100644 index 0000000..96c8b6c --- /dev/null +++ b/include/boost/mqtt5/impl/ping_op.hpp @@ -0,0 +1,110 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_PING_OP_HPP +#define BOOST_MQTT5_PING_OP_HPP + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class ping_op { + using client_service = ClientService; + using handler_type = Handler; + + struct on_timer {}; + struct on_pingreq {}; + + std::shared_ptr _svc_ptr; + handler_type _handler; + +public: + ping_op(std::shared_ptr svc_ptr, Handler&& handler) : + _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler)) + {} + + ping_op(ping_op&&) noexcept = default; + ping_op(const ping_op&) = delete; + + ping_op& operator=(ping_op&&) noexcept = default; + ping_op& operator=(const ping_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform() { + _svc_ptr->_ping_timer.expires_after(compute_wait_time()); + _svc_ptr->_ping_timer.async_wait( + asio::prepend(std::move(*this), on_timer {}) + ); + } + + void operator()(on_timer, error_code ec) { + if (!_svc_ptr->is_open()) + return complete(); + else if (ec == asio::error::operation_aborted) + return perform(); + + auto pingreq = control_packet::of( + no_pid, get_allocator(), encoders::encode_pingreq + ); + + 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) { + if (!ec || ec == asio::error::try_again) + return perform(); + + complete(); + } + +private: + duration compute_wait_time() const { + auto negotiated_ka = _svc_ptr->negotiated_keep_alive(); + return negotiated_ka ? + std::chrono::seconds(negotiated_ka) : + duration(std::numeric_limits::max()); + } + + void complete() { + return std::move(_handler)(); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_PING_OP_HPP diff --git a/include/boost/mqtt5/impl/publish_rec_op.hpp b/include/boost/mqtt5/impl/publish_rec_op.hpp new file mode 100644 index 0000000..2ba30ff --- /dev/null +++ b/include/boost/mqtt5/impl/publish_rec_op.hpp @@ -0,0 +1,219 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_PUBLISH_REC_OP_HPP +#define BOOST_MQTT5_PUBLISH_REC_OP_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class publish_rec_op { + using client_service = ClientService; + + struct on_puback {}; + struct on_pubrec {}; + struct on_pubrel {}; + struct on_pubcomp {}; + + std::shared_ptr _svc_ptr; + decoders::publish_message _message; + +public: + explicit publish_rec_op(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)) + {} + + publish_rec_op(publish_rec_op&&) noexcept = default; + publish_rec_op(const publish_rec_op&) = delete; + + publish_rec_op& operator=(publish_rec_op&&) noexcept = default; + publish_rec_op& operator=(const publish_rec_op&) = delete; + + using allocator_type = asio::recycling_allocator; + allocator_type get_allocator() const noexcept { + return allocator_type {}; + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + 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::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::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 puback) { + 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 pubrec) { + 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 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(static_cast(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_code); + if (!rc) { + on_malformed_packet("Malformed PUBREL received: invalid Reason Code"); + return wait_pubrel(packet_id); + } + + auto pubcomp = control_packet::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 pubcomp) { + 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 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, + _svc_ptr, asio::detached + ); + } + + void complete() { + /* auto rv = */_svc_ptr->channel_store(std::move(_message)); + } +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_PUBLISH_REC_OP_HPP diff --git a/include/boost/mqtt5/impl/publish_send_op.hpp b/include/boost/mqtt5/impl/publish_send_op.hpp new file mode 100644 index 0000000..5b83b90 --- /dev/null +++ b/include/boost/mqtt5/impl/publish_send_op.hpp @@ -0,0 +1,508 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_PUBLISH_SEND_OP_HPP +#define BOOST_MQTT5_PUBLISH_SEND_OP_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +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 +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 +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 _svc_ptr; + + using handler_type = cancellable_handler< + Handler, + typename client_service::executor_type + >; + handler_type _handler; + + serial_num_t _serial_num; + +public: + publish_send_op( + std::shared_ptr svc_ptr, + Handler&& handler + ) : + _svc_ptr(std::move(svc_ptr)), + _handler(std::move(handler), _svc_ptr->get_executor()) + { + auto slot = asio::get_associated_cancellation_slot(_handler); + if (slot.is_connected()) + slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { + svc.cancel(); + }); + } + + publish_send_op(publish_send_op&&) = default; + publish_send_op(const publish_send_op&) = delete; + + publish_send_op& operator=(publish_send_op&&) = default; + publish_send_op& operator=(const publish_send_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform( + std::string topic, std::string payload, + retain_e retain, const publish_props& props + ) { + 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_immediate(client::error::pid_overrun, packet_id); + } + + auto ec = validate_publish(topic, payload, retain, props); + if (ec) + return complete_immediate(ec, packet_id); + + _serial_num = _svc_ptr->next_serial_num(); + + auto publish = control_packet::of( + with_pid, get_allocator(), + encoders::encode_publish, packet_id, + std::move(topic), std::move(payload), + qos_type, retain, dup_e::no, props + ); + + auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) + .value_or(default_max_send_size); + if (publish.size() > max_packet_size) + return complete_immediate(client::error::packet_too_large, packet_id); + + send_publish(std::move(publish)); + } + + void send_publish(control_packet publish) { + 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 resend_publish(control_packet publish) { + if (_handler.cancelled() != asio::cancellation_type_t::none) + return complete( + asio::error::operation_aborted, publish.packet_id() + ); + send_publish(std::move(publish)); + } + + void operator()( + on_publish, control_packet publish, + error_code ec + ) { + if (ec == asio::error::try_again) + return resend_publish(std::move(publish)); + + if constexpr (qos_type == qos_e::at_most_once) + return complete(ec); + + else { + auto packet_id = publish.packet_id(); + + if (ec) + return complete(ec, packet_id); + + if constexpr (qos_type == qos_e::at_least_once) + _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) + _svc_ptr->async_wait_reply( + control_code_e::pubrec, packet_id, + asio::prepend( + std::move(*this), on_pubrec {}, std::move(publish) + ) + ); + } + } + + + template < + qos_e q = qos_type, + std::enable_if_t = true + > + void operator()( + on_puback, control_packet publish, + error_code ec, byte_citer first, byte_citer last + ) { + if (ec == asio::error::try_again) // "resend unanswered" + return resend_publish(std::move(publish.set_dup())); + + uint16_t packet_id = publish.packet_id(); + + if (ec) + return complete(ec, packet_id); + + auto puback = decoders::decode_puback( + static_cast(std::distance(first, last)), first + ); + if (!puback.has_value()) { + on_malformed_packet("Malformed PUBACK: cannot decode"); + return resend_publish(std::move(publish.set_dup())); + } + + auto& [reason_code, props] = *puback; + auto rc = to_reason_code(reason_code); + if (!rc) { + on_malformed_packet("Malformed PUBACK: invalid Reason Code"); + return resend_publish(std::move(publish.set_dup())); + } + + complete(ec, packet_id, *rc, std::move(props)); + } + + template < + qos_e q = qos_type, + std::enable_if_t = true + > + void operator()( + on_pubrec, control_packet publish, + error_code ec, byte_citer first, byte_citer last + ) { + if (ec == asio::error::try_again) // "resend unanswered" + return resend_publish(std::move(publish.set_dup())); + + uint16_t packet_id = publish.packet_id(); + + if (ec) + return complete(ec, packet_id); + + auto pubrec = decoders::decode_pubrec( + static_cast(std::distance(first, last)), first + ); + if (!pubrec.has_value()) { + on_malformed_packet("Malformed PUBREC: cannot decode"); + return resend_publish(std::move(publish.set_dup())); + } + + auto& [reason_code, props] = *pubrec; + + auto rc = to_reason_code(reason_code); + if (!rc) { + on_malformed_packet("Malformed PUBREC: invalid Reason Code"); + return resend_publish(std::move(publish.set_dup())); + } + + if (*rc) + return complete(ec, packet_id, *rc); + + auto pubrel = control_packet::of( + with_pid, get_allocator(), + encoders::encode_pubrel, packet_id, + 0, pubrel_props {} + ); + + send_pubrel(std::move(pubrel), false); + } + + void send_pubrel(control_packet pubrel, bool throttled) { + 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)) + ); + } + + template < + qos_e q = qos_type, + std::enable_if_t = true + > + void operator()( + on_pubrel, control_packet pubrel, error_code ec + ) { + 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, packet_id); + + _svc_ptr->async_wait_reply( + control_code_e::pubcomp, packet_id, + asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubrel)) + ); + } + + template < + qos_e q = qos_type, + std::enable_if_t = true + > + void operator()( + on_pubcomp, control_packet pubrel, + error_code ec, + byte_citer first, byte_citer last + ) { + 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, packet_id); + + auto pubcomp = decoders::decode_pubcomp( + static_cast(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_code); + if (!rc) { + on_malformed_packet("Malformed PUBCOMP: invalid Reason Code"); + return send_pubrel(std::move(pubrel), true); + } + + return complete(ec, pubrel.packet_id(), *rc); + } + +private: + + error_code validate_publish( + const std::string& topic, const std::string& payload, + retain_e retain, const publish_props& props + ) const { + constexpr uint8_t default_retain_available = 1; + constexpr uint8_t default_maximum_qos = 2; + constexpr uint8_t default_payload_format_ind = 0; + + auto topic_name_valid = props[prop::topic_alias].has_value() ? + validate_topic_alias_name(topic) == validation_result::valid : + validate_topic_name(topic) == validation_result::valid + ; + + if (!topic_name_valid) + return client::error::invalid_topic; + + auto max_qos = _svc_ptr->connack_property(prop::maximum_qos) + .value_or(default_maximum_qos); + auto retain_available = _svc_ptr->connack_property(prop::retain_available) + .value_or(default_retain_available); + + if (uint8_t(qos_type) > max_qos) + return client::error::qos_not_supported; + + if (retain_available == 0 && retain == retain_e::yes) + return client::error::retain_not_available; + + auto payload_format_ind = props[prop::payload_format_indicator] + .value_or(default_payload_format_ind); + if ( + payload_format_ind == 1 && + validate_mqtt_utf8(payload) != validation_result::valid + ) + return client::error::malformed_packet; + + return validate_props(props); + } + + error_code validate_props(const publish_props& props) const { + constexpr uint16_t default_topic_alias_max = 0; + + const auto& topic_alias = props[prop::topic_alias]; + if (topic_alias) { + auto topic_alias_max = _svc_ptr->connack_property(prop::topic_alias_maximum) + .value_or(default_topic_alias_max); + + if (topic_alias_max == 0 || *topic_alias > topic_alias_max) + return client::error::topic_alias_maximum_reached; + if (*topic_alias == 0 ) + return client::error::malformed_packet; + } + + const auto& response_topic = props[prop::response_topic]; + if ( + response_topic && + validate_topic_name(*response_topic) != validation_result::valid + ) + return client::error::malformed_packet; + + const auto& user_properties = props[prop::user_property]; + for (const auto& user_property: user_properties) + if (!is_valid_string_pair(user_property)) + return client::error::malformed_packet; + + if (!props[prop::subscription_identifier].empty()) + return client::error::malformed_packet; + + const auto& content_type = props[prop::content_type]; + if ( + content_type && + validate_mqtt_utf8(*content_type) != validation_result::valid + ) + return client::error::malformed_packet; + + return error_code {}; + } + + 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, _svc_ptr, + asio::detached + ); + } + + template < + qos_e q = qos_type, + std::enable_if_t = true + > + void complete(error_code ec, uint16_t = 0) { + _handler.complete(ec); + } + + template < + qos_e q = qos_type, + std::enable_if_t = true + > + void complete_immediate(error_code ec, uint16_t) { + _handler.complete_immediate(ec); + } + + template < + typename Props = on_publish_props_type, + std::enable_if_t< + std::is_same_v || + std::is_same_v, + bool + > = true + > + void complete( + error_code ec, uint16_t packet_id, + reason_code rc = reason_codes::empty, Props&& props = Props {} + ) { + _svc_ptr->free_pid(packet_id, true); + _handler.complete(ec, rc, std::forward(props)); + } + + template < + typename Props = on_publish_props_type, + std::enable_if_t< + std::is_same_v || + std::is_same_v, + bool + > = true + > + void complete_immediate(error_code ec, uint16_t packet_id) { + if (packet_id != 0) + _svc_ptr->free_pid(packet_id, false); + _handler.complete_immediate(ec, reason_codes::empty, Props {}); + } +}; + +template +class initiate_async_publish { + std::shared_ptr _svc_ptr; +public: + explicit initiate_async_publish(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)) + {} + + using executor_type = typename ClientService::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + template + void operator()( + Handler&& handler, + std::string topic, std::string payload, + retain_e retain, const publish_props& props + ) { + detail::publish_send_op { + _svc_ptr, std::move(handler) + }.perform( + std::move(topic), std::move(payload), retain, props + ); + } +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_PUBLISH_SEND_OP_HPP diff --git a/include/boost/mqtt5/impl/re_auth_op.hpp b/include/boost/mqtt5/impl/re_auth_op.hpp new file mode 100644 index 0000000..7a0e58e --- /dev/null +++ b/include/boost/mqtt5/impl/re_auth_op.hpp @@ -0,0 +1,152 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_RE_AUTH_OP_hpp +#define BOOST_MQTT5_RE_AUTH_OP_hpp + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class re_auth_op { + using client_service = ClientService; + struct on_auth_data {}; + + std::shared_ptr _svc_ptr; + any_authenticator& _auth; + +public: + explicit re_auth_op(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)), + _auth(_svc_ptr->_stream_context.mqtt_context().authenticator) + {} + + re_auth_op(re_auth_op&&) noexcept = default; + re_auth_op(const re_auth_op&) = delete; + + re_auth_op& operator=(re_auth_op&&) noexcept = default; + re_auth_op& operator=(const re_auth_op&) = delete; + + using allocator_type = asio::recycling_allocator; + allocator_type get_allocator() const noexcept { + return allocator_type {}; + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform() { + if (_auth.method().empty()) + return; + + auto auth_step = auth_step_e::client_initial; + return _auth.async_auth( + auth_step, "", + asio::prepend(std::move(*this), on_auth_data {}, auth_step) + ); + } + + void perform(decoders::auth_message auth_message) { + if (_auth.method().empty()) + return on_auth_fail( + "Unexpected AUTH received", + disconnect_rc_e::protocol_error + ); + + const auto& [rc, props] = auth_message; + auto auth_rc = to_reason_code(rc); + if (!auth_rc.has_value()) + return on_auth_fail( + "Malformed AUTH received: bad reason code", + disconnect_rc_e::malformed_packet + ); + + const auto& server_auth_method = props[prop::authentication_method]; + if (!server_auth_method || *server_auth_method != _auth.method()) + return on_auth_fail( + "Malformed AUTH received: wrong authentication method", + disconnect_rc_e::protocol_error + ); + + auto auth_step = auth_rc == reason_codes::success ? + auth_step_e::server_final : auth_step_e::server_challenge; + auto data = props[prop::authentication_data].value_or(""); + + return _auth.async_auth( + auth_step, std::move(data), + asio::prepend(std::move(*this), on_auth_data {}, auth_step) + ); + } + + void operator()( + on_auth_data, auth_step_e auth_step, error_code ec, std::string data + ) { + if (ec) + return on_auth_fail( + "Re-authentication: authentication fail", + disconnect_rc_e::unspecified_error + ); + + if (auth_step == auth_step_e::server_final) + return; + + auth_props props; + props[prop::authentication_method] = _auth.method(); + props[prop::authentication_data] = std::move(data); + auto rc = auth_step == auth_step_e::client_initial ? + reason_codes::reauthenticate : reason_codes::continue_authentication; + + auto packet = control_packet::of( + no_pid, get_allocator(), + encoders::encode_auth, + rc.value(), props + ); + + auto wire_data = packet.wire_data(); + + _svc_ptr->async_send( + wire_data, + no_serial, send_flag::none, + asio::consign(asio::detached, std::move(packet)) + ); + } + +private: + void on_auth_fail(std::string message, disconnect_rc_e reason) { + auto props = disconnect_props {}; + props[prop::reason_string] = std::move(message); + + async_disconnect(reason, props, _svc_ptr, asio::detached); + } + +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_RE_AUTH_OP_HPP diff --git a/include/boost/mqtt5/impl/read_message_op.hpp b/include/boost/mqtt5/impl/read_message_op.hpp new file mode 100644 index 0000000..a8c00bb --- /dev/null +++ b/include/boost/mqtt5/impl/read_message_op.hpp @@ -0,0 +1,173 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_READ_MESSAGE_OP_HPP +#define BOOST_MQTT5_READ_MESSAGE_OP_HPP + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class read_message_op { + using client_service = ClientService; + using handler_type = Handler; + + struct on_message {}; + struct on_disconnect {}; + + std::shared_ptr _svc_ptr; + handler_type _handler; + +public: + read_message_op(std::shared_ptr svc_ptr, Handler&& handler) + : _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler)) + {} + + read_message_op(read_message_op&&) noexcept = default; + read_message_op(const read_message_op&) = delete; + + read_message_op& operator=(read_message_op&&) noexcept = default; + read_message_op& operator=(const read_message_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform() { + _svc_ptr->async_assemble( + asio::prepend(std::move(*this), on_message {}) + ); + } + + void operator()( + on_message, error_code ec, + 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::no_recovery) + _svc_ptr->cancel(); + + if (ec) + return complete(); + + dispatch(control_code, first, last); + } + + void operator()(on_disconnect, error_code ec) { + if (ec) + return complete(); + + perform(); + } + +private: + void dispatch( + uint8_t control_byte, + byte_citer first, byte_citer last + ) { + auto code = control_code_e(control_byte & 0b11110000); + + switch (code) { + case control_code_e::publish: { + auto msg = decoders::decode_publish( + control_byte, static_cast(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 control_code_e::disconnect: { + auto rv = decoders::decode_disconnect( + static_cast(std::distance(first, last)), first + ); + if (!rv.has_value()) + return on_malformed_packet( + "Malformed DISCONNECT received: cannot decode" + ); + + const auto& [rc, props] = *rv; + _svc_ptr->log().at_disconnect( + to_reason_code(rc) + .value_or(reason_codes::unspecified_error), + props + ); + _svc_ptr->close_stream(); + _svc_ptr->open_stream(); + } + break; + case control_code_e::auth: { + auto rv = decoders::decode_auth( + static_cast(std::distance(first, last)), first + ); + if (!rv.has_value()) + return on_malformed_packet( + "Malformed AUTH received: cannot decode" + ); + + re_auth_op { _svc_ptr }.perform(std::move(*rv)); + } + break; + default: + assert(false); + } + + 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, svc_ptr, + asio::prepend(std::move(*this), on_disconnect {}) + ); + } + + void complete() { + return std::move(_handler)(); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_READ_MESSAGE_OP_HPP diff --git a/include/boost/mqtt5/impl/read_op.hpp b/include/boost/mqtt5/impl/read_op.hpp new file mode 100644 index 0000000..31b6ffc --- /dev/null +++ b/include/boost/mqtt5/impl/read_op.hpp @@ -0,0 +1,139 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_READ_OP_HPP +#define BOOST_MQTT5_READ_OP_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; +namespace asioex = boost::asio::experimental; + +template +class read_op { + struct on_read {}; + struct on_reconnect {}; + + Owner& _owner; + + using handler_type = Handler; + handler_type _handler; + +public: + read_op(Owner& owner, Handler&& handler) : + _owner(owner), _handler(std::move(handler)) + {} + + read_op(read_op&&) = default; + read_op(const read_op&) = delete; + + read_op& operator=(read_op&&) = default; + read_op& operator=(const read_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_handler); + } + + template + void perform( + const BufferType& buffer, duration wait_for + ) { + auto stream_ptr = _owner._stream_ptr; + + if (_owner.was_connected()) { + _owner._read_timer.expires_after(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 + asio::post( + _owner.get_executor(), + asio::prepend( + std::move(*this), on_read {}, stream_ptr, + std::array { 0, 1 }, + asio::error::not_connected, 0, error_code {} + ) + ); + } + + void operator()( + on_read, typename Owner::stream_ptr stream_ptr, + std::array ord, error_code read_ec, size_t bytes_read, + error_code + ) { + 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) { + std::move(_handler)(ec, bytes_read); + } + + static bool should_reconnect(error_code ec) { + using namespace asio::error; + // note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved) + return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */ + ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */ + ec == connection_aborted || ec == not_connected || + ec == timed_out || ec == connection_reset || + ec == broken_pipe || ec == asio::error::eof; + } + +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_READ_OP_HPP diff --git a/include/boost/mqtt5/impl/reconnect_op.hpp b/include/boost/mqtt5/impl/reconnect_op.hpp new file mode 100644 index 0000000..2ae1bbe --- /dev/null +++ b/include/boost/mqtt5/impl/reconnect_op.hpp @@ -0,0 +1,252 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_RECONNECT_OP_HPP +#define BOOST_MQTT5_RECONNECT_OP_HPP + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +class exponential_backoff { + int _curr_exp { 0 }; + + static constexpr int _base_mulptilier = 1000; + static constexpr int _max_exp = 4; + + // sizeof(_generator) = 8 + boost::random::rand48 _generator { uint32_t(std::time(0)) }; + boost::random::uniform_smallint<> _distribution { -500, 500 }; +public: + exponential_backoff() = default; + + duration generate() { + int exponent = _curr_exp < _max_exp ? _curr_exp++ : _max_exp; + int base = 1 << exponent; + return std::chrono::milliseconds( + base * _base_mulptilier + _distribution(_generator) /* noise */ + ); + } +}; + +namespace asio = boost::asio; + +template +class reconnect_op { + struct on_locked {}; + struct on_next_endpoint {}; + struct on_connect {}; + struct on_backoff {}; + + Owner& _owner; + + using handler_type = asio::any_completion_handler; + handler_type _handler; + + std::unique_ptr _buffer_ptr; + + exponential_backoff _generator; + + using endpoint = asio::ip::tcp::endpoint; + using epoints = asio::ip::tcp::resolver::results_type; + +public: + template + reconnect_op(Owner& owner, Handler&& handler) : + _owner(owner), _handler(std::move(handler)) + {} + + reconnect_op(reconnect_op&&) = default; + reconnect_op(const reconnect_op&) = delete; + + reconnect_op& operator=(reconnect_op&&) = default; + reconnect_op& operator=(const reconnect_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using cancellation_slot_type = + asio::associated_cancellation_slot_t; + cancellation_slot_type get_cancellation_slot() const noexcept { + return asio::get_associated_cancellation_slot(_handler); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_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) + // cancelled without acquiring the lock (by calling client.cancel()) + return std::move(_handler)(ec); + + if (!_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_after(_generator.generate()); + _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 + ) { + // 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); + + connect(eps.cbegin(), std::move(ap)); + } + + void connect(epoints::const_iterator eps, authority_path ap) { + namespace asioex = boost::asio::experimental; + + const auto& ep = eps->endpoint(); + auto sptr = _owner.construct_and_open_next_layer(ep.protocol()); + + if constexpr (has_tls_context) + 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_after(std::chrono::seconds(5)); + + auto init_connect = []( + auto handler, typename Owner::stream_type& stream, + mqtt_ctx& context, log_invoke& log, + endpoint ep, authority_path ap + ) { + connect_op { stream, context, log, std::move(handler) } + .perform(ep, std::move(ap)); + }; + + auto timed_connect = asioex::make_parallel_group( + asio::async_initiate( + init_connect, asio::deferred, std::ref(*sptr), + std::ref(_owner._stream_context.mqtt_context()), + std::ref(_owner.log()), + ep, 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), std::move(eps), std::move(ap) + ) + ); + } + + void operator()( + on_connect, + typename Owner::stream_ptr sptr, epoints::const_iterator eps, authority_path ap, + std::array ord, + error_code connect_ec, error_code timer_ec + ) { + // connect_ec may be any of: + // 1) async_connect error codes + // 2) async_handshake (TLS) error codes + // 3) async_handshake (WebSocket) error codes + // 4) async_write error codes + // 5) async_read error codes + // 5) 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); + + // retry for operation timed out and any other error_code or client::error::malformed_packet + if (ord[0] == 1 || connect_ec) { + // if the hostname resolved into more endpoints, try the next one + if (++eps != epoints::const_iterator()) + return connect(std::move(eps), std::move(ap)); + // try next server + return do_reconnect(); + } + + _owner.replace_next_layer(std::move(sptr)); + complete(error_code {}); + } + +private: + void complete(error_code ec) { + _owner._conn_mtx.unlock(); + std::move(_handler)(ec); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_RECONNECT_OP_HPP diff --git a/include/boost/mqtt5/impl/replies.hpp b/include/boost/mqtt5/impl/replies.hpp new file mode 100644 index 0000000..a26b46b --- /dev/null +++ b/include/boost/mqtt5/impl/replies.hpp @@ -0,0 +1,247 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_REPLIES_HPP +#define BOOST_MQTT5_REPLIES_HPP + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +class replies { +public: + using executor_type = asio::any_io_executor; +private: + using Signature = void (error_code, byte_citer, byte_citer); + + static constexpr auto max_reply_time = std::chrono::seconds(20); + + class reply_handler { + asio::any_completion_handler _handler; + control_code_e _code; + uint16_t _packet_id; + std::chrono::time_point _ts; + public: + template + reply_handler(control_code_e code, uint16_t pid, H&& handler) : + _handler(std::forward(handler)), _code(code), _packet_id(pid), + _ts(std::chrono::system_clock::now()) + {} + + reply_handler(reply_handler&&) = default; + reply_handler(const reply_handler&) = delete; + + reply_handler& operator=(reply_handler&&) = default; + reply_handler& operator=(const reply_handler&) = delete; + + void complete( + error_code ec, + byte_citer first = byte_citer {}, byte_citer last = byte_citer {} + ) { + std::move(_handler)(ec, first, last); + } + + void complete_post(const executor_type& ex, error_code ec) { + asio::post( + ex, + asio::prepend( + std::move(_handler), ec, byte_citer {}, byte_citer {} + ) + ); + } + + uint16_t packet_id() const noexcept { + return _packet_id; + } + + control_code_e code() const noexcept { + return _code; + } + + auto time() const noexcept { + return _ts; + } + }; + + executor_type _ex; + + using handlers = std::vector; + handlers _handlers; + + struct fast_reply { + control_code_e code; + uint16_t packet_id; + std::unique_ptr packet; + }; + using fast_replies = std::vector; + fast_replies _fast_replies; + +public: + template + explicit replies(Executor ex) : _ex(std::move(ex)) {} + + replies(replies&&) = default; + replies(const replies&) = delete; + + replies& operator=(replies&&) = default; + replies& operator=(const replies&) = delete; + + template + 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()) { + dup_handler_ptr->complete_post(_ex, asio::error::operation_aborted); + _handlers.erase(dup_handler_ptr); + } + + auto freply = find_fast_reply(code, packet_id); + + if (freply == _fast_replies.end()) { + auto initiation = []( + auto handler, replies& self, + control_code_e code, uint16_t packet_id + ) { + self._handlers.emplace_back( + code, packet_id, std::move(handler) + ); + }; + return asio::async_initiate( + initiation, token, std::ref(*this), code, packet_id + ); + } + + auto fdata = std::move(*freply); + _fast_replies.erase(freply); + + auto initiation = []( + auto handler, std::unique_ptr packet, + const executor_type& ex + ) { + byte_citer first = packet->cbegin(); + byte_citer last = packet->cend(); + + asio::post( + ex, + asio::consign( + asio::prepend( + std::move(handler), error_code {}, first, last + ), + std::move(packet) + ) + ); + }; + + return asio::async_initiate( + initiation, token, std::move(fdata.packet), _ex + ); + } + + 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(first, last) + }); + return; + } + + auto handler = std::move(*handler_ptr); + _handlers.erase(handler_ptr); + handler.complete(ec, first, last); + } + + void resend_unanswered() { + auto ua = std::move(_handlers); + for (auto& h : ua) + h.complete(asio::error::try_again); + } + + void cancel_unanswered() { + auto ua = std::move(_handlers); + for (auto& h : ua) + h.complete_post(_ex, asio::error::operation_aborted); + } + + 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(); + } + + void clear_pending_pubrels() { + for (auto it = _handlers.begin(); it != _handlers.end();) { + if (it->code() == control_code_e::pubrel) { + it->complete(asio::error::operation_aborted); + it = _handlers.erase(it); + } + else + ++it; + } + } + +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 boost::mqtt5::detail + +#endif // !BOOST_MQTT5_REPLIES_HPP diff --git a/include/boost/mqtt5/impl/run_op.hpp b/include/boost/mqtt5/impl/run_op.hpp new file mode 100644 index 0000000..a747454 --- /dev/null +++ b/include/boost/mqtt5/impl/run_op.hpp @@ -0,0 +1,142 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_RUN_OP_HPP +#define BOOST_MQTT5_RUN_OP_HPP + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class run_op { + using client_service = ClientService; + + std::shared_ptr _svc_ptr; + + using handler_type = cancellable_handler< + Handler, + typename client_service::executor_type + >; + handler_type _handler; + +public: + run_op( + std::shared_ptr svc_ptr, + Handler&& handler + ) : + _svc_ptr(std::move(svc_ptr)), + _handler(std::move(handler), _svc_ptr->get_executor()) + { + auto slot = asio::get_associated_cancellation_slot(_handler); + if (slot.is_connected()) + slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { + svc.cancel(); + }); + } + + run_op(run_op&&) = default; + run_op(const run_op&) = delete; + + run_op& operator=(run_op&&) = default; + run_op& operator=(const run_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform() { + namespace asioex = boost::asio::experimental; + + _svc_ptr->_stream.open(); + _svc_ptr->_rec_channel.reset(); + + auto init_read_message_op = []( + auto handler, std::shared_ptr svc_ptr + ) { + return read_message_op { std::move(svc_ptr), std::move(handler) } + .perform(); + }; + + auto init_ping_op = []( + auto handler, std::shared_ptr svc_ptr + ) { + return ping_op { std::move(svc_ptr), std::move(handler) } + .perform(); + }; + + auto init_senty_op = []( + auto handler, std::shared_ptr svc_ptr + ) { + return sentry_op { std::move(svc_ptr), std::move(handler) } + .perform(); + }; + + asioex::make_parallel_group( + asio::async_initiate( + init_read_message_op, asio::deferred, _svc_ptr + ), + asio::async_initiate( + init_ping_op, asio::deferred, _svc_ptr + ), + asio::async_initiate( + init_senty_op, asio::deferred, _svc_ptr + ) + ).async_wait(asioex::wait_for_all(), std::move(*this)); + } + + void operator()(std::array /* ord */) { + _handler.complete(asio::error::operation_aborted); + } +}; + +template +class initiate_async_run { + std::shared_ptr _svc_ptr; +public: + explicit initiate_async_run(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)) + {} + + using executor_type = typename ClientService::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + template + void operator()(Handler&& handler) { + run_op { + _svc_ptr, std::move(handler) + }.perform(); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_RUN_OP_HPP diff --git a/include/boost/mqtt5/impl/sentry_op.hpp b/include/boost/mqtt5/impl/sentry_op.hpp new file mode 100644 index 0000000..6568dbb --- /dev/null +++ b/include/boost/mqtt5/impl/sentry_op.hpp @@ -0,0 +1,100 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_SENTRY_OP_HPP +#define BOOST_MQTT5_SENTRY_OP_HPP + +#include +#include + +#include + +#include + +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class sentry_op { + using client_service = ClientService; + using handler_type = Handler; + + struct on_timer {}; + struct on_disconnect {}; + + static constexpr auto check_interval = std::chrono::seconds(3); + + std::shared_ptr _svc_ptr; + handler_type _handler; + +public: + sentry_op(std::shared_ptr svc_ptr, Handler&& handler) : + _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler)) + {} + + sentry_op(sentry_op&&) noexcept = default; + sentry_op(const sentry_op&) = delete; + + sentry_op& operator=(sentry_op&&) noexcept = default; + sentry_op& operator=(const sentry_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform() { + _svc_ptr->_sentry_timer.expires_after(check_interval); + _svc_ptr->_sentry_timer.async_wait( + asio::prepend(std::move(*this), on_timer {}) + ); + } + + void operator()(on_timer, error_code) { + if (!_svc_ptr->is_open()) + return complete(); + + 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, svc_ptr, + asio::prepend(std::move(*this), on_disconnect {}) + ); + } + + perform(); + } + + void operator()(on_disconnect, error_code ec) { + if (ec) + return complete(); + + perform(); + } + +private: + void complete() { + return std::move(_handler)(); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_SENTRY_OP_HPP diff --git a/include/boost/mqtt5/impl/subscribe_op.hpp b/include/boost/mqtt5/impl/subscribe_op.hpp new file mode 100644 index 0000000..6e904d1 --- /dev/null +++ b/include/boost/mqtt5/impl/subscribe_op.hpp @@ -0,0 +1,335 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_SUBSCRIBE_OP_HPP +#define BOOST_MQTT5_SUBSCRIBE_OP_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class subscribe_op { + using client_service = ClientService; + + struct on_subscribe {}; + struct on_suback {}; + + std::shared_ptr _svc_ptr; + + using handler_type = cancellable_handler< + Handler, + typename client_service::executor_type + >; + handler_type _handler; + + size_t _num_topics { 0 }; + +public: + subscribe_op( + std::shared_ptr svc_ptr, + Handler&& handler + ) : + _svc_ptr(std::move(svc_ptr)), + _handler(std::move(handler), _svc_ptr->get_executor()) + { + auto slot = asio::get_associated_cancellation_slot(_handler); + if (slot.is_connected()) + slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { + svc.cancel(); + }); + } + + subscribe_op(subscribe_op&&) = default; + subscribe_op(const subscribe_op&) = delete; + + subscribe_op& operator=(subscribe_op&&) = default; + subscribe_op& operator=(const subscribe_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform( + const std::vector& topics, + const subscribe_props& props + ) { + _num_topics = topics.size(); + + uint16_t packet_id = _svc_ptr->allocate_pid(); + if (packet_id == 0) + return complete_immediate(client::error::pid_overrun, packet_id); + + if (_num_topics == 0) + return complete_immediate(client::error::invalid_topic, packet_id); + + auto ec = validate_subscribe(topics, props); + if (ec) + return complete_immediate(ec, packet_id); + + auto subscribe = control_packet::of( + with_pid, get_allocator(), + encoders::encode_subscribe, packet_id, + topics, props + ); + + auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) + .value_or(default_max_send_size); + if (subscribe.size() > max_packet_size) + return complete_immediate(client::error::packet_too_large, packet_id); + + send_subscribe(std::move(subscribe)); + } + + void send_subscribe(control_packet subscribe) { + 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 resend_subscribe(control_packet subscribe) { + if (_handler.cancelled() != asio::cancellation_type_t::none) + return complete( + asio::error::operation_aborted, subscribe.packet_id() + ); + send_subscribe(std::move(subscribe)); + } + + void operator()( + on_subscribe, control_packet packet, + error_code ec + ) { + if (ec == asio::error::try_again) + return resend_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 packet, + error_code ec, byte_citer first, byte_citer last + ) { + if (ec == asio::error::try_again) // "resend unanswered" + return resend_subscribe(std::move(packet)); + + uint16_t packet_id = packet.packet_id(); + + if (ec) + return complete(ec, packet_id); + + auto suback = decoders::decode_suback( + static_cast(std::distance(first, last)), first + ); + if (!suback.has_value()) { + on_malformed_packet("Malformed SUBACK: cannot decode"); + return resend_subscribe(std::move(packet)); + } + + auto& [props, rcs] = *suback; + auto reason_codes = to_reason_codes(std::move(rcs)); + if (reason_codes.size() != _num_topics) { + on_malformed_packet( + "Malformed SUBACK: does not contain a " + "valid Reason Code for every Topic Filter" + ); + return resend_subscribe(std::move(packet)); + } + + complete( + ec, packet_id, std::move(reason_codes), std::move(props) + ); + } + +private: + + error_code validate_subscribe( + const std::vector& topics, const subscribe_props& props + ) const { + error_code ec; + for (const auto& topic: topics) { + ec = validate_topic(topic); + if (ec) + return ec; + } + + ec = validate_props(props); + return ec; + } + + error_code validate_topic(const subscribe_topic& topic) const { + auto wildcard_available = _svc_ptr->connack_property( + prop::wildcard_subscription_available + ).value_or(1); + auto shared_available = _svc_ptr->connack_property( + prop::shared_subscription_available + ).value_or(1); + + std::string_view topic_filter = topic.topic_filter; + + validation_result result = validation_result::valid; + if ( + topic_filter.compare(0, shared_sub_prefix.size(), shared_sub_prefix) == 0 + ) { + if (!shared_available) + return client::error::shared_subscription_not_available; + + result = validate_shared_topic_filter(topic_filter, wildcard_available); + } else + result = wildcard_available ? + validate_topic_filter(topic_filter) : + validate_topic_name(topic_filter); + + if (result == validation_result::invalid) + return client::error::invalid_topic; + if (!wildcard_available && result != validation_result::valid) + return client::error::wildcard_subscription_not_available; + return error_code {}; + } + + error_code validate_props(const subscribe_props& props) const { + const auto& user_properties = props[prop::user_property]; + for (const auto& user_property: user_properties) + if (!is_valid_string_pair(user_property)) + return client::error::malformed_packet; + + const auto& sub_id = props[prop::subscription_identifier]; + if (!sub_id.has_value()) + return error_code {}; + + auto sub_id_available = _svc_ptr->connack_property( + prop::subscription_identifier_available + ).value_or(1); + + if (!sub_id_available) + return client::error::subscription_identifier_not_available; + + return (min_subscription_identifier <= *sub_id && + *sub_id <= max_subscription_identifier) ? + error_code {} : + client::error::malformed_packet; + } + + static std::vector to_reason_codes(std::vector codes) { + std::vector ret; + for (uint8_t code : codes) { + auto rc = to_reason_code(code); + if (rc) + ret.push_back(*rc); + } + 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, _svc_ptr, + asio::detached + ); + } + + void complete_immediate(error_code ec, uint16_t packet_id) { + if (packet_id != 0) + _svc_ptr->free_pid(packet_id); + _handler.complete_immediate( + ec, std::vector(_num_topics, reason_codes::empty), + suback_props {} + ); + } + + void complete( + error_code ec, uint16_t packet_id, + std::vector reason_codes = {}, suback_props props = {} + ) { + if (reason_codes.empty() && _num_topics) + reason_codes = std::vector(_num_topics, reason_codes::empty); + + if (!_svc_ptr->subscriptions_present()) { + bool has_success_rc = std::any_of( + reason_codes.cbegin(), reason_codes.cend(), + [](const reason_code& rc) { return !rc; } + ); + if (has_success_rc) + _svc_ptr->subscriptions_present(true); + } + + _svc_ptr->free_pid(packet_id); + _handler.complete(ec, std::move(reason_codes), std::move(props)); + } +}; + +template +class initiate_async_subscribe { + std::shared_ptr _svc_ptr; +public: + explicit initiate_async_subscribe(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)) + {} + + using executor_type = typename ClientService::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + template + void operator()( + Handler&& handler, + const std::vector& topics, const subscribe_props& props + ) { + detail::subscribe_op { _svc_ptr, std::move(handler) } + .perform(topics, props); + } +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_SUBSCRIBE_OP_HPP diff --git a/include/boost/mqtt5/impl/unsubscribe_op.hpp b/include/boost/mqtt5/impl/unsubscribe_op.hpp new file mode 100644 index 0000000..136b4a3 --- /dev/null +++ b/include/boost/mqtt5/impl/unsubscribe_op.hpp @@ -0,0 +1,277 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_UNSUBSCRIBE_OP_HPP +#define BOOST_MQTT5_UNSUBSCRIBE_OP_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +namespace asio = boost::asio; + +template +class unsubscribe_op { + using client_service = ClientService; + + struct on_unsubscribe {}; + struct on_unsuback {}; + + std::shared_ptr _svc_ptr; + + using handler_type = cancellable_handler< + Handler, + typename client_service::executor_type + >; + handler_type _handler; + + size_t _num_topics { 0 }; + +public: + unsubscribe_op( + std::shared_ptr svc_ptr, + Handler&& handler + ) : + _svc_ptr(std::move(svc_ptr)), + _handler(std::move(handler), _svc_ptr->get_executor()) + { + auto slot = asio::get_associated_cancellation_slot(_handler); + if (slot.is_connected()) + slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) { + svc.cancel(); + }); + } + + unsubscribe_op(unsubscribe_op&&) = default; + unsubscribe_op(const unsubscribe_op&) = delete; + + unsubscribe_op& operator=(unsubscribe_op&&) = default; + unsubscribe_op& operator=(const unsubscribe_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = typename client_service::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + void perform( + const std::vector& topics, + const unsubscribe_props& props + ) { + _num_topics = topics.size(); + + uint16_t packet_id = _svc_ptr->allocate_pid(); + if (packet_id == 0) + return complete_immediate(client::error::pid_overrun, packet_id); + + if (_num_topics == 0) + return complete_immediate(client::error::invalid_topic, packet_id); + + auto ec = validate_unsubscribe(topics, props); + if (ec) + return complete_immediate(ec, packet_id); + + auto unsubscribe = control_packet::of( + with_pid, get_allocator(), + encoders::encode_unsubscribe, packet_id, + topics, props + ); + + auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size) + .value_or(default_max_send_size); + if (unsubscribe.size() > max_packet_size) + return complete_immediate(client::error::packet_too_large, packet_id); + + send_unsubscribe(std::move(unsubscribe)); + } + + void send_unsubscribe(control_packet unsubscribe) { + 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 resend_unsubscribe(control_packet subscribe) { + if (_handler.cancelled() != asio::cancellation_type_t::none) + return complete( + asio::error::operation_aborted, subscribe.packet_id() + ); + send_unsubscribe(std::move(subscribe)); + } + + void operator()( + on_unsubscribe, control_packet packet, + error_code ec + ) { + if (ec == asio::error::try_again) + return resend_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 packet, + error_code ec, byte_citer first, byte_citer last + ) { + if (ec == asio::error::try_again) // "resend unanswered" + return resend_unsubscribe(std::move(packet)); + + uint16_t packet_id = packet.packet_id(); + + if (ec) + return complete(ec, packet_id); + + auto unsuback = decoders::decode_unsuback( + static_cast(std::distance(first, last)), first + ); + if (!unsuback.has_value()) { + on_malformed_packet("Malformed UNSUBACK: cannot decode"); + return resend_unsubscribe(std::move(packet)); + } + + auto& [props, rcs] = *unsuback; + auto reason_codes = to_reason_codes(std::move(rcs)); + if (reason_codes.size() != _num_topics) { + on_malformed_packet( + "Malformed UNSUBACK: does not contain a " + "valid Reason Code for every Topic Filter" + ); + return resend_unsubscribe(std::move(packet)); + } + + complete( + ec, packet_id, std::move(reason_codes), std::move(props) + ); + } + +private: + + static error_code validate_unsubscribe( + const std::vector& topics, + const unsubscribe_props& props + ) { + for (const auto& topic : topics) + if (validate_topic_filter(topic) != validation_result::valid) + return client::error::invalid_topic; + + const auto& user_properties = props[prop::user_property]; + for (const auto& user_property: user_properties) + if (!is_valid_string_pair(user_property)) + return client::error::malformed_packet; + return error_code {}; + } + + static std::vector to_reason_codes(std::vector codes) { + std::vector ret; + for (uint8_t code : codes) { + auto rc = to_reason_code(code); + if (rc) + ret.push_back(*rc); + } + 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, _svc_ptr, + asio::detached + ); + } + + void complete_immediate(error_code ec, uint16_t packet_id) { + if (packet_id != 0) + _svc_ptr->free_pid(packet_id); + _handler.complete_immediate( + ec, std::vector(_num_topics, reason_codes::empty), + unsuback_props {} + ); + } + + void complete( + error_code ec, uint16_t packet_id, + std::vector reason_codes = {}, unsuback_props props = {} + ) { + if (reason_codes.empty() && _num_topics) + reason_codes = std::vector(_num_topics, reason_codes::empty); + + _svc_ptr->free_pid(packet_id); + _handler.complete(ec, std::move(reason_codes), std::move(props)); + } +}; + +template +class initiate_async_unsubscribe { + std::shared_ptr _svc_ptr; +public: + explicit initiate_async_unsubscribe(std::shared_ptr svc_ptr) : + _svc_ptr(std::move(svc_ptr)) + {} + + using executor_type = typename ClientService::executor_type; + executor_type get_executor() const noexcept { + return _svc_ptr->get_executor(); + } + + template + void operator()( + Handler&& handler, + const std::vector& topics, const unsubscribe_props& props + ) { + detail::unsubscribe_op { _svc_ptr, std::move(handler) } + .perform(topics, props); + } +}; + + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_UNSUBSCRIBE_OP_HPP diff --git a/include/boost/mqtt5/impl/write_op.hpp b/include/boost/mqtt5/impl/write_op.hpp new file mode 100644 index 0000000..5757760 --- /dev/null +++ b/include/boost/mqtt5/impl/write_op.hpp @@ -0,0 +1,118 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_WRITE_OP_HPP +#define BOOST_MQTT5_WRITE_OP_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost::mqtt5::detail { + +template +class write_op { + struct on_write {}; + struct on_reconnect {}; + + Owner& _owner; + + using handler_type = Handler; + handler_type _handler; + +public: + write_op(Owner& owner, Handler&& handler) : + _owner(owner), _handler(std::move(handler)) + {} + + write_op(write_op&&) = default; + write_op(const write_op&) = delete; + + write_op& operator=(write_op&&) = default; + write_op& operator=(const write_op&) = delete; + + using allocator_type = asio::associated_allocator_t; + allocator_type get_allocator() const noexcept { + return asio::get_associated_allocator(_handler); + } + + using executor_type = asio::associated_executor_t; + executor_type get_executor() const noexcept { + return asio::get_associated_executor(_handler); + } + + template + 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 + asio::post( + _owner.get_executor(), + asio::prepend( + std::move(*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) { + std::move(_handler)(ec, bytes_written); + } + + static bool should_reconnect(error_code ec) { + using namespace asio::error; + // note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved) + return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */ + ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */ + ec == connection_aborted || ec == not_connected || + ec == timed_out || ec == connection_reset || + ec == broken_pipe || ec == asio::error::eof; + } + +}; + +} // end namespace boost::mqtt5::detail + +#endif // !BOOST_MQTT5_WRITE_OP_HPP diff --git a/include/boost/mqtt5/logger.hpp b/include/boost/mqtt5/logger.hpp new file mode 100644 index 0000000..630f418 --- /dev/null +++ b/include/boost/mqtt5/logger.hpp @@ -0,0 +1,258 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_LOGGER_HPP +#define BOOST_MQTT5_LOGGER_HPP + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace boost::mqtt5 { + +namespace asio = boost::asio; +using error_code = boost::system::error_code; + +/** + * \brief Represents the severity level of log messages. + */ +enum class log_level : uint8_t { + /** Error messages that indicate serious issues. **/ + error = 1, + + /** Warnings that indicate potential problems or non-critical issues. **/ + warning, + + /** Informational messages that highlight normal application behaviour and events. **/ + info, + + /** Detailed messages useful for diagnosing issues. **/ + debug +}; + +/** + * \brief A logger class that can used by the \ref mqtt_client to output + * the results of operations to stderr. + * + * \details All functions are invoked directly within the \ref mqtt_client using + * its default executor. If the \ref mqtt_client is initialized with an explicit or + * implicit strand, none of the functions will be invoked concurrently. + * + * \par Thread safety + * ['Distinct objects]: unsafe. \n + * ['Shared objects]: unsafe. \n + * This class is [*not thread-safe]. +*/ +class logger { + constexpr static auto prefix = "[Boost.MQTT5]"; + + log_level _level; +public: + + /** + * \brief Constructs a logger that filters log messages based on the specified log level. + * + * \param level Messages with a log level higher than the given log level will be suppressed. + */ + logger(log_level level = log_level::warning) : _level(level) {} + + /** + * \brief Outputs the results of the resolve operation. + * + * \param ec Error code returned by the resolve operation. + * \param host Hostname used in the resolve operation. + * \param port Port used in the resolve operation. + * \param eps Endpoints returned by the resolve operation. + */ + void at_resolve( + error_code ec, std::string_view host, std::string_view port, + const asio::ip::tcp::resolver::results_type& eps + ) { + if (!ec && _level < log_level::info) + return; + + output_prefix(); + std::clog + << "resolve: " + << host << ":" << port; + std::clog << " - " << ec.message(); + + if (_level == log_level::debug) { + std::clog << " ["; + for (auto it = eps.begin(); it != eps.end();) { + std::clog << it->endpoint().address().to_string(); + if (++it != eps.end()) + std::clog << ","; + } + std::clog << "]"; + } + std::clog << std::endl; + } + + /** + * \brief Outputs the results of the TCP connect operation. + * + * \param ec Error code returned by the TCP connect operation. + * \param ep The TCP endpoint used to establish the TCP connection. + */ + void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) { + if (!ec && _level < log_level::info) + return; + + output_prefix(); + std::clog + << "TCP connect: " + << ep.address().to_string() << ":" << ep.port() + << " - " << ec.message() + << std::endl; + } + + /** + * \brief Outputs the results of the TLS handshake operation. + * + * \param ec Error code returned by the TLS handshake operation. + * \param ep The TCP endpoint used to establish the TLS handshake. + */ + void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) { + if (!ec && _level < log_level::info) + return; + + output_prefix(); + std::clog + << "TLS handshake: " + << ep.address().to_string() << ":" << ep.port() + << " - " << ec.message() + << std::endl; + } + + /** + * \brief Outputs the results of the WebSocket handshake operation. + * + * \param ec Error code returned by the WebSocket handshake operation. + * \param ep The TCP endpoint used to establish the WebSocket handshake. + */ + void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) { + if (!ec && _level < log_level::info) + return; + + output_prefix(); + std::clog + << "WebSocket handshake: " + << ep.address().to_string() << ":" << ep.port() + << " - " << ec.message() + << std::endl; + } + + /** + * \brief Outputs the contents of the \__CONNACK\__ packet sent by the Broker. + * + * \param rc Reason Code in the received \__CONNACK\__ packet indicating + * the result of the MQTT handshake. + * \param session_present A flag indicating whether the Broker already has a session associated + * with this connection. + * \param ca_props \__CONNACK_PROPS\__ received in the \__CONNACK\__ packet. + */ + void at_connack( + reason_code rc, + bool session_present, const connack_props& ca_props + ) { + if (!rc && _level < log_level::info) + return; + + output_prefix(); + std::clog << "connack: " << rc.message() << "."; + if (_level == log_level::debug) { + std::clog << " session_present:" << session_present << " "; + output_props(ca_props); + } + std::clog << std::endl; + } + + /** + * \brief Outputs the contents of the \__DISCONNECT\__ packet sent by the Broker. + * + * \param rc Reason Code in the received \__DISCONNECT\__ packet indicating + * the reason behind the disconnection. + * \param dc_props \__DISCONNECT_PROPS\__ received in the \__DISCONNECT\__ packet. + */ + void at_disconnect(reason_code rc, const disconnect_props& dc_props) { + output_prefix(); + std::clog << "disconnect: " << rc.message() << "."; + if (_level == log_level::debug) + output_props(dc_props); + std::clog << std::endl; + } + +private: + void output_prefix() { + std::clog << prefix << " "; + } + + template + void output_props(const Props& props) { + props.visit( + [](const auto& prop, const auto& val) -> bool { + if constexpr (detail::is_optional) { + if (val.has_value()) { + std::clog << property_name(prop) << ":"; + using value_type = boost::remove_cv_ref_t; + if constexpr (std::is_same_v) + std::clog << std::to_string(*val) << " "; + else + std::clog << *val << " "; + } + } else { // is vector + if (val.empty()) + return true; + + std::clog << property_name(prop) << ":"; + std::clog << "["; + for (auto i = 0; i < val.size(); i++) { + if constexpr (detail::is_pair) + std::clog << "(" << val[i].first << "," << val[i].second << ")"; + else + std::clog << std::to_string(val[i]); + if (i + 1 < val.size()) + std::clog << ", "; + } + std::clog << "]"; + } + return true; + } + ); + } + + template + static std::string_view property_name(std::integral_constant) { + return prop::name_v

; + } + +}; + +// Verify that the logger class satisfies the LoggerType concept +static_assert(has_at_resolve); +static_assert(has_at_tcp_connect); +static_assert(has_at_tls_handshake); +static_assert(has_at_ws_handshake); +static_assert(has_at_connack); +static_assert(has_at_disconnect); + +} // end namespace boost::mqtt5 + + +#endif // !BOOST_MQTT5_LOGGER_HPP diff --git a/include/async_mqtt5/logger_traits.hpp b/include/boost/mqtt5/logger_traits.hpp similarity index 58% rename from include/async_mqtt5/logger_traits.hpp rename to include/boost/mqtt5/logger_traits.hpp index 94554b3..3cc0a9d 100644 --- a/include/async_mqtt5/logger_traits.hpp +++ b/include/boost/mqtt5/logger_traits.hpp @@ -5,22 +5,22 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_LOGGER_TRAITS_HPP -#define ASYNC_MQTT5_LOGGER_TRAITS_HPP +#ifndef BOOST_MQTT5_LOGGER_TRAITS_HPP +#define BOOST_MQTT5_LOGGER_TRAITS_HPP -#include -#include -#include +#include +#include +#include #include #include #include -#include -#include -#include +#include +#include +#include -namespace async_mqtt5 { +namespace boost::mqtt5 { namespace asio = boost::asio; using boost::system::error_code; @@ -32,11 +32,11 @@ class noop_logger {}; template using at_resolve_sig = decltype( - std::declval().at_resolve( - std::declval(), - std::declval(), std::declval(), - std::declval() - ) + std::declval().at_resolve( + std::declval(), + std::declval(), std::declval(), + std::declval() + ) ); template constexpr bool has_at_resolve = boost::is_detected::value; @@ -45,9 +45,9 @@ constexpr bool has_at_resolve = boost::is_detected::value; template using at_tcp_connect_sig = decltype( - std::declval().at_tcp_connect( - std::declval(), std::declval() - ) + std::declval().at_tcp_connect( + std::declval(), std::declval() + ) ); template constexpr bool has_at_tcp_connect = boost::is_detected::value; @@ -56,9 +56,9 @@ constexpr bool has_at_tcp_connect = boost::is_detected::v template using at_tls_handshake_sig = decltype( - std::declval().at_tls_handshake( - std::declval(), std::declval() - ) + std::declval().at_tls_handshake( + std::declval(), std::declval() + ) ); template constexpr bool has_at_tls_handshake = boost::is_detected::value; @@ -67,9 +67,9 @@ constexpr bool has_at_tls_handshake = boost::is_detected using at_ws_handshake_sig = decltype( - std::declval().at_ws_handshake( - std::declval(), std::declval() - ) + std::declval().at_ws_handshake( + std::declval(), std::declval() + ) ); template constexpr bool has_at_ws_handshake = boost::is_detected::value; @@ -78,10 +78,10 @@ constexpr bool has_at_ws_handshake = boost::is_detected: template using at_connack_sig = decltype( - std::declval().at_connack( - std::declval(), - std::declval(), std::declval() - ) + std::declval().at_connack( + std::declval(), + std::declval(), std::declval() + ) ); template constexpr bool has_at_connack = boost::is_detected::value; @@ -90,14 +90,14 @@ constexpr bool has_at_connack = boost::is_detected::value; template using at_disconnect_sig = decltype( - std::declval().at_disconnect( - std::declval(), std::declval() - ) + std::declval().at_disconnect( + std::declval(), std::declval() + ) ); template constexpr bool has_at_disconnect = boost::is_detected::value; -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 -#endif // !ASYNC_MQTT5_LOGGER_TRAITS_HPP +#endif // !BOOST_MQTT5_LOGGER_TRAITS_HPP diff --git a/include/boost/mqtt5/mqtt_client.hpp b/include/boost/mqtt5/mqtt_client.hpp new file mode 100644 index 0000000..048a7fc --- /dev/null +++ b/include/boost/mqtt5/mqtt_client.hpp @@ -0,0 +1,981 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_MQTT_CLIENT_HPP +#define BOOST_MQTT5_MQTT_CLIENT_HPP + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include // std::monostate +#include + +namespace boost::mqtt5 { + +namespace asio = boost::asio; + +/** + * \brief \__MQTT\__ client used to connect and communicate with a Broker. + * + * \tparam \__StreamType\__ Type of the underlying transport protocol used to transfer + * the stream of bytes between the Client and the Broker. The transport must be + * ordered and lossless. + * \tparam \__TlsContext\__ Type of the context object used in TLS/SSL connections. + * \tparam \__LoggerType\__ Type of object used to log events within the Client. + * + * \par Thread safety + * ['Distinct objects]: safe. \n + * ['Shared objects]: unsafe. \n + * This class is [*not thread-safe]. + * The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand. + */ +template < + typename StreamType, + typename TlsContext = std::monostate, + typename LoggerType = noop_logger +> +class mqtt_client { +public: + /// The executor type associated with the client. + using executor_type = typename StreamType::executor_type; + + /// Rebinds the client type to another executor. + template + struct rebind_executor { + /// The client type when rebound to the specified executor. + using other = mqtt_client< + typename detail::rebind_executor::other, + TlsContext + >; + }; + +private: + using stream_type = StreamType; + using tls_context_type = TlsContext; + using logger_type = LoggerType; + + using client_service_type = detail::client_service< + stream_type, tls_context_type, logger_type + >; + using impl_type = std::shared_ptr; + impl_type _impl; + +public: + /** + * \brief Constructs a Client with given parameters. + * + * \param ex An executor that will be associated with the Client. + * \param tls_context A context object used in TLS/SSL connection. + * \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client. + */ + explicit mqtt_client( + const executor_type& ex, + tls_context_type tls_context = {}, logger_type logger = {} + ) : + _impl(std::make_shared( + ex, std::move(tls_context), std::move(logger) + )) + {} + + /** + * \brief Constructs a Client with given parameters. + * + * \tparam \__ExecutionContext\__ Type of a concrete execution context. + * \param context Execution context whose executor will be associated with the Client. + * \param tls_context A context object used in TLS/SSL connection. + * \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client. + * + * \par Precondition + * \code + * std::is_convertible_v + * \endcode + */ + template < + typename ExecutionContext, + std::enable_if_t< + std::is_convertible_v, + bool + > = true + > + explicit mqtt_client( + ExecutionContext& context, + tls_context_type tls_context = {}, logger_type logger = {} + ) : + mqtt_client( + context.get_executor(), + std::move(tls_context), std::move(logger) + ) + {} + + /** + * \brief Move-construct an mqtt_client from another. + * + * \details Moved-from client can only be destructed + */ + mqtt_client(mqtt_client&&) noexcept = default; + + /** + * \brief Move assignment operator. + * + * \details Cancels this client first. Moved-from client can only be destructed. + */ + mqtt_client& operator=(mqtt_client&& other) noexcept { + _impl->cancel(); + _impl = std::move(other._impl); + return *this; + } + + /** + * \brief Destructor. + * + * \details Automatically calls \ref mqtt_client::cancel. + */ + ~mqtt_client() { + if (_impl) + _impl->cancel(); + } + + /** + * \brief Get the executor associated with the object. + */ + executor_type get_executor() const noexcept { + return _impl->get_executor(); + } + + /** + * \brief Get the context object used in TLS/SSL connection. + * + * \note This function may only be invoked + * when the template parameter \__TlsContext\__ was configured + * with non-default type during the creation of a \ref mqtt_client. + * + * \par Precondition + * \code + * !std::is_same_v + * \endcode + */ + template < + typename Ctx = TlsContext, + std::enable_if_t, bool> = true + > + decltype(auto) tls_context() { + return _impl->tls_context(); + } + + /** + * \brief Start the Client. + * + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void (__ERROR_CODE__) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete with + * `boost::asio::error::operation_aborted` when the client is cancelled by calling + * \ref mqtt_client::async_disconnect, \ref mqtt_client::cancel, destruction or + * if a non-recoverable error happens during a connection attempt (e.g. access denied). + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::asio::error::operation_aborted`\n + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_run(CompletionToken&& token = {}) { + using Signature = void (error_code); + return asio::async_initiate( + detail::initiate_async_run(_impl), token + ); + } + + /** + * \brief Cancel all asynchronous operations. This function has terminal effects. + * + * \details All outstanding operations will complete + * with `boost::asio::error::operation_aborted`. + * + * \attention This function has terminal effects and will close the Client. + * The Client cannot be used before calling \ref mqtt_client::async_run again. + */ + void cancel() { + auto impl = _impl; + _impl = impl->dup(); + impl->cancel(); + } + + /** + * \brief Assign a \ref will Message. + * + * \details The \ref will Message that the Broker should publish + * after the Network Connection is closed and it is not + * closed normally. + * + * \attention This function takes action when the client is in a non-operational state, + * meaning the \ref async_run function has not been invoked. + * Furthermore, you can use this function after the \ref cancel function has been called, + * before the \ref async_run function is invoked again. + */ + mqtt_client& will(will will) { + _impl->will(std::move(will)); + return *this; + } + + /** + * \brief Assign credentials that will be used to connect to a Broker. + * + * \details Credentials consist of a unique Client Identifier and, optionally, + * a User Name and Password. + * + * \attention This function takes action when the client is in a non-operational state, + * meaning the \ref async_run function has not been invoked. + * Furthermore, you can use this function after the \ref cancel function has been called, + * before the \ref async_run function is invoked again. + */ + mqtt_client& credentials( + std::string client_id, + std::string username = "", std::string password = "" + ) { + _impl->credentials( + std::move(client_id), + std::move(username), std::move(password) + ); + return *this; + } + + /** + * \brief Assign a list of Brokers that the Client will attempt to connect to. + * + * \details The Client will cycle through the list of hosts, + * attempting to establish a connection with each + * until it successfully establishes a connection. + * + * \param hosts List of Broker addresses and ports. + * Address and ports are separated with a colon `:` while + * pairs of addresses and ports are separated with a comma `,`. + * \param default_port The default port to connect to in case the port is not + * explicitly specified in the `hosts` list. + * + * \attention This function takes action when the client is in a non-operational state, + * meaning the \ref async_run function has not been invoked. + * Furthermore, you can use this function after the \ref cancel function has been called, + * before the \ref async_run function is invoked again. + * + * \par Example + * Some valid `hosts` string: + * + * \code + * std::string valid_hosts_1 = "broker1:1883, broker2, broker3:1883"; + * std::string valid_hosts_2 = "broker1"; + * \endcode + * + */ + mqtt_client& brokers(std::string hosts, uint16_t default_port = 1883) { + _impl->brokers(std::move(hosts), default_port); + return *this; + } + + /** + * \brief Assign an authenticator that the Client will use for + * \__ENHANCED_AUTH\__ on every connect to a Broker. + * Re-authentication can be initiated by calling \ref re_authenticate. + * + * \param authenticator Object that will be stored (move-constructed or by reference) + * and used for authentication. It needs to satisfy \__is_authenticator\__ concept. + * + * \attention This function takes action when the client is in a non-operational state, + * meaning the \ref async_run function has not been invoked. + * Furthermore, you can use this function after the \ref cancel function has been called, + * before the \ref async_run function is invoked again. + * + */ + template < + typename Authenticator, + std::enable_if_t, bool> = true + > + mqtt_client& authenticator(Authenticator&& authenticator) { + _impl->authenticator(std::forward(authenticator)); + return *this; + } + + /** + * \brief Assign the maximum time interval that is permitted to elapse between + * two transmissions from the Client. + * + * \details A non-zero value initiates a process of sending a \__PINGREQ\__ + * packet every `seconds`. If this function is not invoked, the Client assumes + * a \__KEEP_ALIVE\__ interval of 60 seconds. + * + * \param seconds Time interval in seconds. + * + * \note If the Server sends a \__SERVER_KEEP_ALIVE\__, + * the Client will send a \__PINGREQ\__ packet every \__SERVER_KEEP_ALIVE\__ seconds. + * + * \attention This function takes action when the client is in a non-operational state, + * meaning the \ref async_run function has not been invoked. + * Furthermore, you can use this function after the \ref cancel function has been called, + * before the \ref async_run function is invoked again. + * + */ + mqtt_client& keep_alive(uint16_t seconds) { + _impl->keep_alive(seconds); + return *this; + } + + /** + * \brief Assign \__CONNECT_PROPS\__ that will be sent in a \__CONNECT\__ packet. + * \param props \__CONNECT_PROPS\__ sent in a \__CONNECT\__ packet. + * \see See \__CONNECT_PROPS\__ for all eligible properties. + */ + mqtt_client& connect_properties(connect_props props) { + _impl->connect_properties(std::move(props)); + return *this; + } + + /** + * \brief Assign a property that will be sent in a \__CONNECT\__ packet. + * \param prop The \__CONNECT_PROPS\__ property to set. + * \param value Value that will be assigned to the property. + * + * \par Example + * \code + * client.connect_property(prop::session_expiry_interval, 40); // ok + * client.connect_property(prop::reason_string, "reason"); // does not compile, not a CONNECT prop! + * \endcode + * + * \see See \__CONNECT_PROPS\__ for all eligible properties. + */ + template + mqtt_client& connect_property( + std::integral_constant prop, + prop::value_type_t

value + ) { + _impl->connect_property(prop, std::move(value)); + return *this; + } + + /** + * \brief Initiates [mqttlink 3901257 Re-authentication] + * using the authenticator given in the \ref authenticator method. + * + * \note If \ref authenticator was not called, this method does nothing. + */ + void re_authenticate() { + detail::re_auth_op { _impl }.perform(); + } + + /** + * \brief Retrieves the value of a specific property from the last \__CONNACK\__ packet received. + * + * \details The return type varies according to the property requested. + * For all properties, the return type will be `std::optional` of their respective value type. + * For `boost::mqtt5::prop::user_property`, the return type is + * `std::vector>`. + * + * \param prop The \__CONNACK_PROPS\__ property value to retrieve. + * + * \par Example + * \code + * std::optional auth_method = client.connack_property(boost::mqtt5::prop::authentication_method); // ok + * std::optional c_type = client.connack_property(boost::mqtt5::prop::content_type); // does not compile, not a CONNACK prop! + * \endcode + * + * \see See \__CONNACK_PROPS\__ for all eligible properties. + */ + template + const auto& connack_property( + std::integral_constant prop + ) const { + return _impl->connack_property(prop); + } + + /** + * \brief Retrieves the \__CONNACK_PROPS\__ from the last \__CONNACK\__ packet received. + * + * \see See \__CONNACK_PROPS\__ for all eligible properties. + */ + const connack_props& connack_properties() const { + return _impl->connack_properties(); + } + + /** + * \brief Send a \__PUBLISH\__ packet to Broker to transport an + * Application Message. + * + * \tparam qos_type The \ref qos_e level of assurance for delivery. + * \param topic Identification of the information channel to which + * Payload data is published. + * \param payload The Application Message that is being published. + * \param retain The \ref retain_e flag. + * \param props An instance of \__PUBLISH_PROPS\__. + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__ASYNC_IMMEDIATE\__. + * + * \par Handler signature + * The handler signature for this operation depends on the \ref qos_e specified:\n + * + * `qos` == `qos_e::at_most_once`: + * \code + * void ( + * __ERROR_CODE__ // Result of operation + * ) + * \endcode + * + * `qos` == `qos_e::at_least_once`: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * __REASON_CODE__, // Reason Code received from Broker. + * __PUBACK_PROPS__ // Properties received in the PUBACK packet. + * ) + * \endcode + * + * `qos` == `qos_e::exactly_once`: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * __REASON_CODE__, // Reason Code received from Broker. + * __PUBCOMP_PROPS__ // Properties received in the PUBCOMP packet. + * ) + * \endcode + * + * \par Completion condition + * Depending on the \ref qos_e specified, the asynchronous operation will complete + * when one of the following conditions is true:\n + * - If `qos` == `qos_e::at_most_once` and the Client + * has successfully written the packet to the transport. \n + * - If `qos` == `qos_e::at_least_once` and the packet has + * been sent and acknowledged through the reception of a \__PUBACK\__ packet. + * - If `qos` == `qos_e::exactly_once` and the packet has + * been sent and fully acknowledged through the reception of a \__PUBCOMP\__ packet. + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::operation_aborted` \n + * - `boost::asio::error::no_recovery` \n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * - \link boost::mqtt5::client::error::packet_too_large \endlink + * - \link boost::mqtt5::client::error::pid_overrun \endlink + * - \link boost::mqtt5::client::error::qos_not_supported \endlink + * - \link boost::mqtt5::client::error::retain_not_available \endlink + * - \link boost::mqtt5::client::error::topic_alias_maximum_reached \endlink + * - \link boost::mqtt5::client::error::invalid_topic \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__PUBLISH\__ packet \n + * + */ + template ::type + > + 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; + return asio::async_initiate( + detail::initiate_async_publish(_impl), + token, + std::move(topic), std::move(payload), retain, props + ); + } + + /** + * \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription + * to one or more Topics of interest. + * + * \details After the subscription has been established, the Broker will send + * PUBLISH packets to the Client to forward Application Messages that were published + * to Topics that the Client subscribed to. The Application Messages can be received + * with \ref mqtt_client::async_receive function. + * + * \param topics A list of \ref subscribe_topic of interest. + * \param props An instance of \__SUBSCRIBE_PROPS\__. + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__ASYNC_IMMEDIATE\__. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating + * // the subscription result for each Topic + * // in the SUBSCRIBE packet. + * __SUBACK_PROPS__, // Properties received in the SUBACK packet. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has successfully sent a \__SUBSCRIBE\__ packet + * and has received a \__SUBACK\__ response from the Broker.\n + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n + * - `boost::asio::error::operation_aborted` \n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * - \link boost::mqtt5::client::error::packet_too_large \endlink + * - \link boost::mqtt5::client::error::pid_overrun \endlink + * - \link boost::mqtt5::client::error::invalid_topic \endlink + * - \link boost::mqtt5::client::error::wildcard_subscription_not_available \endlink + * - \link boost::mqtt5::client::error::subscription_identifier_not_available \endlink + * - \link boost::mqtt5::client::error::shared_subscription_not_available \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_subscribe( + const std::vector& topics, + const subscribe_props& props, + CompletionToken&& token = {} + ) { + using Signature = void ( + error_code, std::vector, suback_props + ); + return asio::async_initiate( + detail::initiate_async_subscribe(_impl), token, + topics, props + ); + } + + /** + * \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription + * to one Topic of interest. + * + * \details After the subscription has been established, the Broker will send + * \__PUBLISH\__ packets to the Client to forward Application Messages that were published + * to Topics that the Client subscribed to. The Application Messages can be received + * with \ref mqtt_client::async_receive function. + * + * \param topic A \ref subscribe_topic of interest. + * \param props An instance of \__SUBSCRIBE_PROPS\__. + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__ASYNC_IMMEDIATE\__. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * std::vector<__REASON_CODE__>, // Vector of Reason Codes containing the + * // single subscription result for the Topic + * // in the SUBSCRIBE packet. + * __SUBACK_PROPS__, // Properties received in the SUBACK packet. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has successfully sent a \__SUBSCRIBE\__ packet + * and has received a \__SUBACK\__ response from the Broker.\n + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n + * - `boost::asio::error::operation_aborted` \n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * - \link boost::mqtt5::client::error::packet_too_large \endlink + * - \link boost::mqtt5::client::error::pid_overrun \endlink + * - \link boost::mqtt5::client::error::invalid_topic \endlink + * - \link boost::mqtt5::client::error::wildcard_subscription_not_available \endlink + * - \link boost::mqtt5::client::error::subscription_identifier_not_available \endlink + * - \link boost::mqtt5::client::error::shared_subscription_not_available \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_subscribe( + const subscribe_topic& topic, const subscribe_props& props, + CompletionToken&& token = {} + ) { + return async_subscribe( + std::vector { topic }, props, + std::forward(token) + ); + } + + + /** + * \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe from one + * or more Topics. + * + * \note The Client may still receive residual Application Messages + * through the \ref mqtt_client::async_receive function + * from Topics the Client just unsubscribed to. + * + * \param topics List of Topics to unsubscribe from. + * \param props An instance of \__UNSUBSCRIBE_PROPS\__. + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__ASYNC_IMMEDIATE\__. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating + * // the result of unsubscribe operation + * // for each Topic in the UNSUBSCRIBE packet. + * __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has successfully sent an \__UNSUBSCRIBE\__ packet + * and has received an \__UNSUBACK\__ response from the Broker.\n + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n + * - `boost::asio::error::operation_aborted` \n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * - \link boost::mqtt5::client::error::packet_too_large \endlink + * - \link boost::mqtt5::client::error::pid_overrun \endlink + * - \link boost::mqtt5::client::error::invalid_topic \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_unsubscribe( + const std::vector& topics, const unsubscribe_props& props, + CompletionToken&& token = {} + ) { + using Signature = void ( + error_code, std::vector, unsuback_props + ); + return asio::async_initiate( + detail::initiate_async_unsubscribe(_impl), token, + topics, props + ); + } + + /** + * \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe + * from one Topic. + * + * \note The Client may still receive residual Application Messages + * through the \ref mqtt_client::async_receive function + * from Topics the Client just unsubscribed to. + * + * \param topic Topic to unsubscribe from. + * \param props An instance of \__UNSUBSCRIBE_PROPS\__. + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__ASYNC_IMMEDIATE\__. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * std::vector<__REASON_CODE__>, // Vector of Reason Codes containing + * // the result of unsubscribe operation + * // for the Topic in the UNSUBSCRIBE packet. + * __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has successfully sent an \__UNSUBSCRIBE\__ packet + * and has received an \__UNSUBACK\__ response from the Broker.\n + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success` \n + * - `boost::asio::error::no_recovery` \n + * - `boost::asio::error::operation_aborted` \n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * - \link boost::mqtt5::client::error::packet_too_large \endlink + * - \link boost::mqtt5::client::error::pid_overrun \endlink + * - \link boost::mqtt5::client::error::invalid_topic \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_unsubscribe( + const std::string& topic, const unsubscribe_props& props, + CompletionToken&& token = {} + ) { + return async_unsubscribe( + std::vector { topic }, props, + std::forward(token) + ); + } + + + /** + * \brief Asynchronously receive an Application Message. + * + * \details The Client will receive and complete deliveries for all the + * \__PUBLISH\__ packets received from the Broker throughout its lifetime. + * The Client will store them internally in the order they were delivered. + * Calling this function will attempt to receive an Application Message + * from internal storage. + * + * \note It is only recommended to call this function if you have established + * a successful subscription to a Topic using the \ref async_subscribe function. + * + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * On immediate completion, invocation of the handler will be performed in a manner + * equivalent to using \__POST\__. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__, // Result of operation. + * std::string, // Topic, the origin of the Application Message. + * std::string, // Payload, the content of the Application Message. + * __PUBLISH_PROPS__, // Properties received in the PUBLISH packet. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has a pending Application Message in its internal storage + * ready to be received. + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::error::operation_aborted`\n + * - \link boost::mqtt5::client::error::session_expired \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` \n + * - `cancellation_type::partial` \n + * - `cancellation_type::total` \n + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_receive(CompletionToken&& token = {}) { + return _impl->async_channel_receive(std::forward(token)); + } + + /** + * \brief Disconnect the Client by sending a \__DISCONNECT\__ packet + * with a specified Reason Code. This function has terminal effects. + * + * \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker + * with a Reason Code describing the reason for disconnection. + * If the \__DISCONNECT\__ packet is successfully transmitted, + * or if `5 seconds` elapsed without a successful send, the Client will terminate the connection. + * + * \attention This function has terminal effects and will close the Client. + * See \ref mqtt_client::cancel. + * + * \param reason_code Reason Code to notify + * the Broker of the reason for the disconnection. + * \param props An instance of \__DISCONNECT_PROPS\__. + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__ // Result of operation. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has sent a \__DISCONNECT\__ packet.\n + * - 5 seconds have elapsed without a successful send.\n + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::error::operation_aborted`[footnote + This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server. + Regardless, the connection to the Server is terminated, and the Client is cancelled. + ]\n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_disconnect( + disconnect_rc_e reason_code, const disconnect_props& props, + CompletionToken&& token = {} + ) { + auto impl = _impl; + _impl = impl->dup(); + return detail::async_terminal_disconnect( + detail::disconnect_rc_e(static_cast(reason_code)), + props, impl, std::forward(token) + ); + } + + /** + * \brief Disconnect the Client by sending a \__DISCONNECT\__ packet + * with a Reason Code of reason_codes.normal_disconnection. + * This function has terminal effects. + * + * \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker + * with a Reason Code describing the reason for disconnection. + * If the \__DISCONNECT\__ packet is successfully transmitted, + * or if `5 seconds` elapsed without a successful send, the Client will terminate the connection. + * + * \attention This function has terminal effects and will close the Client. + * See \ref mqtt_client::cancel. + * + * \param token Completion token that will be used to produce a + * completion handler. The handler will be invoked when the operation completes. + * + * \par Handler signature + * The handler signature for this operation: + * \code + * void ( + * __ERROR_CODE__ // Result of operation. + * ) + * \endcode + * + * \par Completion condition + * The asynchronous operation will complete when one of the following conditions is true:\n + * - The Client has attempted to send a \__DISCONNECT\__ packet, regardless of whether + * the sending was successful or not.\n + * - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n + * + * \par Error codes + * The list of all possible error codes that this operation can finish with:\n + * - `boost::system::errc::errc_t::success`\n + * - `boost::asio::error::operation_aborted`[footnote + This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server. + Regardless, the connection to the Server is terminated, and the Client is cancelled. + ]\n + * - \link boost::mqtt5::client::error::malformed_packet \endlink + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_disconnect(CompletionToken&& token = {}) { + return async_disconnect( + disconnect_rc_e::normal_disconnection, + disconnect_props {}, std::forward(token) + ); + } + +}; + +} // end namespace boost::mqtt5 + +#endif // !BOOST_MQTT5_MQTT_CLIENT_HPP diff --git a/include/boost/mqtt5/property_types.hpp b/include/boost/mqtt5/property_types.hpp new file mode 100644 index 0000000..023d8b7 --- /dev/null +++ b/include/boost/mqtt5/property_types.hpp @@ -0,0 +1,253 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_PROPERTY_TYPES_HPP +#define BOOST_MQTT5_PROPERTY_TYPES_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::mqtt5::prop { + +enum property_type : uint8_t { + payload_format_indicator_t = 0x01, + message_expiry_interval_t = 0x02, + content_type_t = 0x03, + response_topic_t = 0x08, + correlation_data_t = 0x09, + subscription_identifier_t = 0x0b, + session_expiry_interval_t = 0x11, + assigned_client_identifier_t = 0x12, + server_keep_alive_t = 0x13, + authentication_method_t = 0x15, + authentication_data_t = 0x16, + request_problem_information_t = 0x17, + will_delay_interval_t = 0x18, + request_response_information_t = 0x19, + response_information_t = 0x1a, + server_reference_t = 0x1c, + reason_string_t = 0x1f, + receive_maximum_t = 0x21, + topic_alias_maximum_t = 0x22, + topic_alias_t = 0x23, + maximum_qos_t = 0x24, + retain_available_t = 0x25, + user_property_t = 0x26, + maximum_packet_size_t = 0x27, + wildcard_subscription_available_t = 0x28, + subscription_identifier_available_t = 0x29, + shared_subscription_available_t = 0x2a +}; + +class alignas(8) subscription_identifiers : + public boost::container::small_vector +{ + using base_type = boost::container::small_vector; + +public: + using base_type::base_type; + subscription_identifiers(int32_t val) : base_type { val } {} + + bool has_value() const noexcept { + return !empty(); + } + + explicit operator bool() const noexcept { + return !empty(); + } + + int32_t& operator*() noexcept { + return front(); + } + + int32_t operator*() const noexcept { + return front(); + } + + void emplace(int32_t val = 0) { + *this = val; + } + + int32_t value() const { + return front(); + } + + int32_t value_or(int32_t default_val) const noexcept { + return empty() ? default_val : front(); + } + + void reset() noexcept { + clear(); + } +}; + +template +struct property_traits; + +using user_property_value_t = std::vector>; + +#define DEF_PROPERTY_TRAIT(Pname, Ptype) \ +template <> \ +struct property_traits { \ + static constexpr std::string_view name = #Pname; \ + using type = Ptype; \ +}; \ +constexpr std::integral_constant Pname {}; + +DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional); +DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional); +DEF_PROPERTY_TRAIT(content_type, std::optional); +DEF_PROPERTY_TRAIT(response_topic, std::optional); +DEF_PROPERTY_TRAIT(correlation_data, std::optional); +DEF_PROPERTY_TRAIT(subscription_identifier, subscription_identifiers); +DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional); +DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional); +DEF_PROPERTY_TRAIT(server_keep_alive, std::optional); +DEF_PROPERTY_TRAIT(authentication_method, std::optional); +DEF_PROPERTY_TRAIT(authentication_data, std::optional); +DEF_PROPERTY_TRAIT(request_problem_information, std::optional); +DEF_PROPERTY_TRAIT(will_delay_interval, std::optional); +DEF_PROPERTY_TRAIT(request_response_information, std::optional); +DEF_PROPERTY_TRAIT(response_information, std::optional); +DEF_PROPERTY_TRAIT(server_reference, std::optional); +DEF_PROPERTY_TRAIT(reason_string, std::optional); +DEF_PROPERTY_TRAIT(receive_maximum, std::optional); +DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional); +DEF_PROPERTY_TRAIT(topic_alias, std::optional); +DEF_PROPERTY_TRAIT(maximum_qos, std::optional); +DEF_PROPERTY_TRAIT(retain_available, std::optional); +DEF_PROPERTY_TRAIT(user_property, user_property_value_t); +DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional); +DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional); +DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional); +DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional); + +#undef DEF_PROPERTY_TRAIT + +template +using value_type_t = typename property_traits

::type; + +template +constexpr std::string_view name_v = property_traits

::name; + +template +class properties { + + template + struct property { + using key = std::integral_constant; + constexpr static std::string_view name = name_v

; + value_type_t

value; + }; + std::tuple...> _props; + +public: + + template + constexpr auto& operator[](std::integral_constant) + noexcept { + return std::get>(_props).value; + } + + template + constexpr const auto& operator[](std::integral_constant) + const noexcept { + return std::get>(_props).value; + } + + template + using is_apply_on = std::conjunction< + std::is_invocable&>... + >; + + template + using is_nothrow_apply_on = std::conjunction< + std::is_nothrow_invocable&>... + >; + + template < + typename Func, + std::enable_if_t::value, bool> = true + > + constexpr bool apply_on(uint8_t property_id, Func&& func) + noexcept (is_nothrow_apply_on::value) { + return std::apply( + [&func, property_id](auto&... ptype) { + auto pc = [&func, property_id](auto& px) { + using ptype = std::remove_reference_t; + constexpr typename ptype::key prop; + if (prop.value == property_id) + std::invoke(func, px.value); + return prop.value != property_id; + }; + return (pc(ptype) && ...); + }, + _props + ); + } + + template + using is_visitor = std::conjunction< + std::is_invocable_r&>... + >; + + template + using is_nothrow_visitor = std::conjunction< + std::is_nothrow_invocable&>... + >; + + template < + typename Func, + std::enable_if_t::value, bool> = true + > + constexpr bool visit(Func&& func) + const noexcept (is_nothrow_visitor::value) { + return std::apply( + [&func](const auto&... props) { + auto pc = [&func](const auto& px) { + using ptype = std::remove_reference_t; + constexpr typename ptype::key prop; + return std::invoke(func, prop, px.value); + }; + return (pc(props) &&...); + }, + _props + ); + } + + template < + typename Func, + std::enable_if_t::value, bool> = true + > + constexpr bool visit(Func&& func) + noexcept (is_nothrow_visitor::value) { + return std::apply( + [&func](auto&... props) { + auto pc = [&func](auto& px) { + using ptype = std::remove_reference_t; + constexpr typename ptype::key prop; + return std::invoke(func, prop, px.value); + }; + return (pc(props) && ...); + }, + _props + ); + } +}; + + +} // end namespace boost::mqtt5::prop + +#endif // !BOOST_MQTT5_PROPERTY_TYPES_HPP diff --git a/include/boost/mqtt5/reason_codes.hpp b/include/boost/mqtt5/reason_codes.hpp new file mode 100644 index 0000000..d8005ba --- /dev/null +++ b/include/boost/mqtt5/reason_codes.hpp @@ -0,0 +1,495 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_REASON_CODES_HPP +#define BOOST_MQTT5_REASON_CODES_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost::mqtt5 { + +/// \cond internal +namespace reason_codes { + +enum class category : uint8_t { + none, + connack, puback, pubrec, + pubrel, pubcomp, suback, + unsuback, auth, disconnect +}; + +} // end namespace reason_codes + +/// \endcond + +/** + * \brief A class holding Reason Code values originating from Control Packets. + * + * \details A Reason Code is a one byte unsigned value that indicates the result of an operation. + * Reason Codes less than 0x80 indicate successful completion of an operation. + * The normal Reason Code for success is 0. + * Reason Code values of 0x80 or greater indicate failure. + * The \__CONNACK\__, \__PUBACK\__, \__PUBREC\__, \__PUBREL\__, \__PUBCOMP\__, \__DISCONNECT\__ + * and \__AUTH\__ Control Packets have a single Reason Code as part of the Variable Header. + * The \__SUBACK\__ and \__UNSUBACK\__ packets contain a list of one or more Reason Codes in the Payload. + * + * \see See \__REASON_CODES\__ for a complete list of all possible instances of this class. + */ +class reason_code { + uint8_t _code; + reason_codes::category _category { reason_codes::category::none }; +public: +/// \cond INTERNAL + constexpr reason_code() : _code(0xff) {} + + constexpr reason_code(uint8_t code, reason_codes::category cat) : + _code(code), _category(cat) + {} + + constexpr explicit reason_code(uint8_t code) : _code(code) {} +/// \endcond + + /** + * \brief Indication if the object holds a Reason Code indicating an error. + * + * \details Any Reason Code holding a value equal to or greater than 0x80. + */ + explicit operator bool() const noexcept { + return _code >= 0x80; + } + + /** + * \brief Returns the byte value of the Reason Code. + */ + constexpr uint8_t value() const noexcept { + return _code; + } + + /// Insertion operator. + friend std::ostream& operator<<(std::ostream& os, const reason_code& rc) { + os << rc.message(); + return os; + } + + /// Operator less than. + friend bool operator<(const reason_code& lhs, const reason_code& rhs) { + return lhs._code < rhs._code; + } + + /// Equality operator. + friend bool operator==(const reason_code& lhs, const reason_code& rhs) { + return lhs._code == rhs._code && lhs._category == rhs._category; + } + + /** + * \brief Returns a message describing the meaning behind the Reason Code. + */ + std::string message() const { + switch (_code) { + case 0x00: + if (_category == reason_codes::category::suback) + return "The subscription is accepted with maximum QoS sent at 0"; + if (_category == reason_codes::category::disconnect) + return "Close the connection normally. Do not send the Will Message"; + 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 but requires" + "that the Server also publishes its 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 "The Server does not wish to reveal the reason for the" + "failure or none of the other Reason Codes apply"; + 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 Filer 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 the 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 { + +/** No Reason Code. A \ref client::error occurred.*/ +constexpr reason_code empty {}; + +/** The operation completed successfully. */ +constexpr reason_code success { 0x00 }; + +/** Close the connection normally. Do not send the Will Message. */ +constexpr reason_code normal_disconnection { 0x00, category::disconnect }; + +/** The subscription is accepted with maximum QoS sent at 0. */ +constexpr reason_code granted_qos_0 { 0x00, category::suback }; + +/** The subscription is accepted with maximum QoS sent at 1. */ +constexpr reason_code granted_qos_1 { 0x01 }; + +/** The subscription is accepted with maximum QoS sent at 2 */ +constexpr reason_code granted_qos_2 { 0x02 }; + +/** The Client wishes to disconnect but requires that + the Server also publishes its Will Message. */ +constexpr reason_code disconnect_with_will_message { 0x04 }; + +/** The message is accepted but there are no subscribers. */ +constexpr reason_code no_matching_subscribers { 0x10 }; + +/** No matching Topic Filter is being used by the Client. */ +constexpr reason_code no_subscription_existed { 0x11 }; + +/** Continue the authentication with another step. */ +constexpr reason_code continue_authentication { 0x18 }; + +/** Initiate a re-authentication. */ +constexpr reason_code reauthenticate { 0x19 }; + +/** The Server does not wish to reveal the reason for the + failure or none of the other Reason Codes apply. */ +constexpr reason_code unspecified_error { 0x80 }; + +/** Data within the packet could not be correctly parsed. */ +constexpr reason_code malformed_packet { 0x81 }; + +/** Data in the packet does not conform to this specification. */ +constexpr reason_code protocol_error { 0x82 }; + +/** The packet is valid but not accepted by this Server. */ +constexpr reason_code implementation_specific_error { 0x83 }; + +/** The Server does not support the requested version of the MQTT protocol. */ +constexpr reason_code unsupported_protocol_version { 0x84 }; + +/** The Client ID is valid but not allowed by this Server. */ +constexpr reason_code client_identifier_not_valid { 0x85 }; + +/** The Server does not accept the User Name or Password provided. */ +constexpr reason_code bad_username_or_password { 0x86 }; + +/** The request is not authorized. */ +constexpr reason_code not_authorized { 0x87 }; + +/** The MQTT Server is not available. */ +constexpr reason_code server_unavailable { 0x88 }; + +/** The MQTT Server is busy, try again later. */ +constexpr reason_code server_busy { 0x89 }; + +/** The Client has been banned by administrative action. */ +constexpr reason_code banned { 0x8a }; + +/** The Server is shutting down. */ +constexpr reason_code server_shutting_down { 0x8b }; + +/** The authentication method is not supported or + does not match the method currently in use. */ +constexpr reason_code bad_authentication_method { 0x8c }; + +/** No packet has been received for 1.5 times the Keepalive time. */ +constexpr reason_code keep_alive_timeout { 0x8d }; + +/** Another Connection using the same ClientID has connected + causing this Connection to be closed. */ +constexpr reason_code session_taken_over { 0x8e }; + +/** The Topic Filter is not malformed, but it is not accepted. */ +constexpr reason_code topic_filter_invalid { 0x8f }; + +/** The Topic Name is not malformed, but it is not accepted. */ +constexpr reason_code topic_name_invalid { 0x90 }; + +/** The Packet Identifier is already in use. */ +constexpr reason_code packet_identifier_in_use { 0x91 }; + +/** The Packet Identifier is not known. */ +constexpr reason_code packet_identifier_not_found { 0x92 }; + +/** The Client or Server has received more than the Receive + Maximum publication for which it has not sent PUBACK or PUBCOMP. */ +constexpr reason_code receive_maximum_exceeded { 0x93 }; + +/** The Client or Server received a PUBLISH packet containing + a Topic Alias greater than the Maximum Topic Alias. */ +constexpr reason_code topic_alias_invalid { 0x94 }; + +/** The packet exceeded the maximum permissible size. */ +constexpr reason_code packet_too_large { 0x95 }; + +/** The received data rate is too high. */ +constexpr reason_code message_rate_too_high { 0x96 }; + +/** An implementation or administrative imposed limit has been exceeded. */ +constexpr reason_code quota_exceeded { 0x97 }; + +/** The Connection is closed due to an administrative action. */ +constexpr reason_code administrative_action { 0x98 }; + +/** The Payload does not match the specified Payload Format Indicator. */ +constexpr reason_code payload_format_invalid { 0x99 }; + +/** The Server does not support retained messages. */ +constexpr reason_code retain_not_supported { 0x9a }; + +/** The Server does not support the QoS the Client specified or + it is greater than the Maximum QoS specified. */ +constexpr reason_code qos_not_supported { 0x9b }; + +/** The Client should temporarily use another server. */ +constexpr reason_code use_another_server { 0x9c }; + +/** The Client should permanently use another server. */ +constexpr reason_code server_moved { 0x9d }; + +/** The Server does not support Shared Subscriptions for this Client. */ +constexpr reason_code shared_subscriptions_not_supported { 0x9e }; + +/** The connection rate limit has been exceeded. */ +constexpr reason_code connection_rate_exceeded { 0x9f }; + +/** The maximum connection time authorized for this + connection has been exceeded. */ +constexpr reason_code maximum_connect_time { 0xa0 }; + +/** The Server does not support Subscription Identifiers. */ +constexpr reason_code subscription_ids_not_supported { 0xa1 }; + +/** The Server does not support Wildcard Subscriptions. */ +constexpr reason_code wildcard_subscriptions_not_supported { 0xa2 }; + +namespace detail { + +template < + category cat, + std::enable_if_t = true +> +std::pair valid_codes() { + static reason_code valid_codes[] = { + success, unspecified_error, malformed_packet, + protocol_error, implementation_specific_error, + unsupported_protocol_version, client_identifier_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, + std::enable_if_t = true +> +std::pair valid_codes() { + static reason_code valid_codes[] = { + success, continue_authentication, reauthenticate + }; + static size_t len = sizeof(valid_codes) / sizeof(reason_code); + return std::make_pair(valid_codes, len); +} + +template < + category cat, + std::enable_if_t< + cat == category::puback || cat == category::pubrec, bool + > = true +> +std::pair valid_codes() { + static reason_code valid_codes[] = { + success, no_matching_subscribers, unspecified_error, + implementation_specific_error, not_authorized, + topic_name_invalid, packet_identifier_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, + std::enable_if_t< + cat == category::pubrel || cat == category::pubcomp, bool + > = true +> +std::pair valid_codes() { + static reason_code valid_codes[] = { + success, packet_identifier_not_found + }; + static size_t len = sizeof(valid_codes) / sizeof(reason_code); + return std::make_pair(valid_codes, len); +} + +template < + category cat, + std::enable_if_t = true +> +std::pair valid_codes() { + 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_identifier_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, + std::enable_if_t = true +> +std::pair valid_codes() { + static reason_code valid_codes[] = { + success, no_subscription_existed, + unspecified_error, implementation_specific_error, + not_authorized, topic_filter_invalid, + packet_identifier_in_use + }; + static size_t len = sizeof(valid_codes) / sizeof(reason_code); + return std::make_pair(valid_codes, len); +} + +template < + category cat, + std::enable_if_t = true +> +std::pair valid_codes() { + 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 +std::optional to_reason_code(uint8_t code) { + auto [ptr, len] = reason_codes::detail::valid_codes(); + auto it = std::lower_bound(ptr, ptr + len, reason_code(code)); + + if (it->value() == code) + return *it; + return std::nullopt; +} + +} // end namespace boost::mqtt5 + +#endif // !BOOST_MQTT5_REASON_CODES_HPP diff --git a/include/boost/mqtt5/types.hpp b/include/boost/mqtt5/types.hpp new file mode 100644 index 0000000..c23cf85 --- /dev/null +++ b/include/boost/mqtt5/types.hpp @@ -0,0 +1,368 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_TYPES_HPP +#define BOOST_MQTT5_TYPES_HPP + +#include + +#include + +#include +#include + +namespace boost::mqtt5 { + +/** An alias for `boost::system::error_code`. */ +using error_code = boost::system::error_code; + +/** + * \brief A data structure used to store information related to an authority + * such as the hostname, port, and path. + */ +struct authority_path { + /** The hostname of the authority as a domain name or an IP address. */ + std::string host; + + /** The port number used for communication. */ + std::string port; + + /** Specifies the endpoint path relevant to WebSocket connections. */ + std::string path; +}; + +/** + * \brief Represents the Quality of Service (\__QOS__\) + * property of the \__PUBLISH\__ packets. + * + * \details Determines how the \__PUBLISH\__ packets are delivered + * from the sender to the receiver. + */ +enum class qos_e : std::uint8_t { + /** The message arrives at the receiver either once or not at all. */ + at_most_once = 0b00, + + /** Ensures the message arrives at the receiver at least once. */ + at_least_once = 0b01, + + /** All messages arrive at the receiver exactly once without + loss or duplication of the messages. */ + exactly_once = 0b10 +}; + +/** + * \brief Represents the \__RETAIN\__ flag in the \__PUBLISH\__ packets. + * + * \details This flag informs the Server about whether or not it should + * store the current message. + */ +enum class retain_e : std::uint8_t { + /** The Server will replace any existing retained message for this Topic + with this message. */ + yes = 0b1, + + /** The Server will not store this message and will not remove or replace + any existing retained message. */ + no = 0b0 +}; + + +enum class dup_e : std::uint8_t { + yes = 0b1, no = 0b0 +}; + +/** + * \brief Represents the stage of \__ENHANCED_AUTH\__ process. + */ +enum class auth_step_e { + /** The Client needs to send initial authentication data. */ + client_initial, + + /** Server responded with reason_codes.continue_authentication and possibly + * authentication data, the Client needs to send further authentication data. + */ + server_challenge, + + /** Server responded with reason_codes.success and final + * authentication data, which the Client validates. + */ + server_final +}; + +/** + * \brief Representation of the No Local Subscribe Option. + * + * \details A Subscribe Option indicating whether or not Application Messages + * will be forwarded to a connection with a ClientID equal to the ClientID of the + * publishing connection. + */ +enum class no_local_e : std::uint8_t { + /** Application Messages can be forwarded to a connection with equal ClientID. */ + no = 0b0, + + /** Application Messages MUST NOT be forwarded to a connection with equal ClientID. */ + yes = 0b1 +}; + +/** + * \brief Representation of the Retain As Published Subscribe Option. + * + * \details A Subscribe Option indicating whether or not Application Messages forwarded + * using this subscription keep the \__RETAIN\__ flag they were published with. + */ +enum class retain_as_published_e : std::uint8_t { + /** Application Messages have the \__RETAIN\__ flag set to 0. */ + dont = 0b0, + + /** Application Messages keep the \__RETAIN\__ flag they were published with. */ + retain = 0b1 +}; + +/** + * \brief Representation of the Retain Handling Subscribe Option. + * + * \details A Subscribe Option specifying whether retained messages are sent + * when the subscription is established. + */ +enum class retain_handling_e : std::uint8_t { + /** Send retained messages at the time of subscribe. */ + send = 0b00, + + /** Send retained message only if the subscription does not currently exist. */ + new_subscription_only = 0b01, + + /** Do not send retained messages at the time of subscribe. */ + not_send = 0b10 +}; + +/** + * \brief Represents the \__SUBSCRIBE_OPTIONS\__ associated with each Subscription. + */ +struct subscribe_options { + /// Maximum \__QOS\__ level at which the Server can send Application Messages to the Client. + qos_e max_qos = qos_e::exactly_once; + + /// Option determining if Application Messages will be forwarded to a connection with an equal ClientID. + no_local_e no_local = no_local_e::yes; + + /// Option determining if Application Message will keep their \__RETAIN\__ flag. + retain_as_published_e retain_as_published = retain_as_published_e::retain; + + /// Option determining if retained messages are sent when the subscription is established. + retain_handling_e retain_handling = retain_handling_e::new_subscription_only; +}; + +/** + * \brief A representation of a Topic Subscription consisting of a Topic Filter and + * Subscribe Options. + */ +struct subscribe_topic { + /// An UTF-8 Encoded String indicating the Topics to which the Client wants to subscribe. + std::string topic_filter; + + /// The \ref subscribe_options associated with the subscription. + subscribe_options sub_opts; +}; + +/// \cond + +class connect_props : public prop::properties< + prop::session_expiry_interval_t, + prop::receive_maximum_t, + prop::maximum_packet_size_t, + prop::topic_alias_maximum_t, + prop::request_response_information_t, + prop::request_problem_information_t, + prop::user_property_t, + prop::authentication_method_t, + prop::authentication_data_t +> {}; + +class connack_props : public prop::properties< + prop::session_expiry_interval_t, + prop::receive_maximum_t, + prop::maximum_qos_t, + prop::retain_available_t, + prop::maximum_packet_size_t, + prop::assigned_client_identifier_t, + prop::topic_alias_maximum_t, + prop::reason_string_t, + prop::user_property_t, + prop::wildcard_subscription_available_t, + prop::subscription_identifier_available_t, + prop::shared_subscription_available_t, + prop::server_keep_alive_t, + prop::response_information_t, + prop::server_reference_t, + prop::authentication_method_t, + prop::authentication_data_t +> {}; + +class publish_props : public prop::properties< + prop::payload_format_indicator_t, + prop::message_expiry_interval_t, + prop::content_type_t, + prop::response_topic_t, + prop::correlation_data_t, + prop::subscription_identifier_t, + prop::topic_alias_t, + prop::user_property_t +> {}; + +// puback, pubcomp +class puback_props : public prop::properties< + prop::reason_string_t, + prop::user_property_t +> {}; + +class pubcomp_props : public prop::properties< + prop::reason_string_t, + prop::user_property_t +> {}; + +class pubrec_props : public prop::properties< + prop::reason_string_t, + prop::user_property_t +> {}; + +class pubrel_props : public prop::properties< + prop::reason_string_t, + prop::user_property_t +> {}; + + +class subscribe_props : public prop::properties< + prop::subscription_identifier_t, + prop::user_property_t +> {}; + +class suback_props : public prop::properties< + prop::reason_string_t, + prop::user_property_t +> {}; + +class unsubscribe_props : public prop::properties< + prop::user_property_t +> {}; + +class unsuback_props : public prop::properties< + prop::reason_string_t, + prop::user_property_t +> {}; + +class disconnect_props : public prop::properties< + prop::session_expiry_interval_t, + prop::reason_string_t, + prop::user_property_t, + prop::server_reference_t +> {}; + +class auth_props : public prop::properties< + prop::authentication_method_t, + prop::authentication_data_t, + prop::reason_string_t, + prop::user_property_t +> {}; + + +class will_props : public prop::properties< + prop::will_delay_interval_t, + prop::payload_format_indicator_t, + prop::message_expiry_interval_t, + prop::content_type_t, + prop::response_topic_t, + prop::correlation_data_t, + prop::user_property_t +>{}; + +/// \endcond + +/** + * \brief Represents the Will Message. + * + * \details A Will Message is an Application Message that + * the Broker should publish after the Network Connection is closed + * in cases where the Network Connection is not closed normally. + */ +class will : public will_props { + std::string _topic; + std::string _message; + qos_e _qos; retain_e _retain; + +public: + /** + * \brief Constructs an empty Will Message. + * + * \attention Do not use! + * An empty Will Message results in an empty Topic Name which is not valid. + * Internal uses only. + */ + will() = default; + + /** + * \brief Construct a Will Message. + * + * \param topic Topic, identification of the information channel to which + * the Will Message will be published. + * \param message The message that will be published. + * \param qos The \ref qos_e level used when publishing the Will Message. + * \param retain The \ref retain_e flag specifying if the Will Message + * is to be retained when it is published. + */ + 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) + {} + + /** + * \brief Construct a Will Message. + * + * \param topic Topic name, identification of the information channel to which + * the Will Message will be published. + * \param message The message that will be published. + * \param qos The \ref qos_e level used when publishing the Will Message. + * \param retain The \ref retain_e flag specifying if the Will Message + * is to be retained when it is published. + * \param props Will properties. + */ + 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) + {} + + /// Get the Topic Name. + std::string_view topic() const { + return _topic; + } + + /// Get the Application Message. + std::string_view message() const { + return _message; + } + + /// Get the \ref qos_e. + constexpr qos_e qos() const { + return _qos; + } + + /// Get the \ref retain_e. + constexpr retain_e retain() const { + return _retain; + } +}; + + +} // end namespace boost::mqtt5 + +#endif // !BOOST_MQTT5_TYPES_HPP diff --git a/include/boost/mqtt5/websocket.hpp b/include/boost/mqtt5/websocket.hpp new file mode 100644 index 0000000..5491902 --- /dev/null +++ b/include/boost/mqtt5/websocket.hpp @@ -0,0 +1,54 @@ +// +// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_WEBSOCKET_HPP +#define BOOST_MQTT5_WEBSOCKET_HPP + +#include + +#include +#include +#include + +namespace boost::mqtt5 { + +// Trait definition for Beast +template +struct ws_handshake_traits> { + + template + static decltype(auto) async_handshake( + boost::beast::websocket::stream& stream, + authority_path ap, CompletionToken&& token + ) { + using namespace boost::beast; + + // 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, + std::forward(token) + ); + } +}; + +} // end namespace boost::mqtt5 + +#endif // !BOOST_MQTT5_WEBSOCKET_HPP diff --git a/test/include/test_common/delayed_op.hpp b/test/include/test_common/delayed_op.hpp index a5f45aa..cd97886 100644 --- a/test/include/test_common/delayed_op.hpp +++ b/test/include/test_common/delayed_op.hpp @@ -5,10 +5,8 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_DELAYED_OP_HPP -#define ASYNC_MQTT5_TEST_DELAYED_OP_HPP - -#include +#ifndef BOOST_MQTT5_TEST_DELAYED_OP_HPP +#define BOOST_MQTT5_TEST_DELAYED_OP_HPP #include #include @@ -17,10 +15,11 @@ #include #include #include - #include -namespace async_mqtt5::test { +#include + +namespace boost::mqtt5::test { namespace asio = boost::asio; @@ -30,96 +29,96 @@ using duration = time_stamp::duration; template class delayed_op { - struct on_timer {}; + struct on_timer {}; - std::unique_ptr _timer; - time_stamp::duration _delay; - asio::cancellation_slot _cancel_slot; + std::unique_ptr _timer; + time_stamp::duration _delay; + asio::cancellation_slot _cancel_slot; - std::tuple _args; + std::tuple _args; public: - template - delayed_op( - const Executor& ex, time_stamp::duration delay, Args&& ...args - ) : - _timer(new asio::steady_timer(ex)), _delay(delay), - _args(std::move(args)...) - {} + template + 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&&) = default; - delayed_op(const delayed_op&) = delete; + delayed_op(delayed_op&&) = default; + delayed_op(const delayed_op&) = delete; - delayed_op& operator=(delayed_op&&) = default; - delayed_op& operator=(const delayed_op&) = delete; + delayed_op& operator=(delayed_op&&) = default; + delayed_op& operator=(const delayed_op&) = delete; - using allocator_type = asio::recycling_allocator; - allocator_type get_allocator() const noexcept { - return allocator_type {}; - } + using allocator_type = asio::recycling_allocator; + 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; - } + using cancellation_slot_type = asio::cancellation_slot; + asio::cancellation_slot get_cancellation_slot() const noexcept { + return _cancel_slot; + } - using executor_type = asio::steady_timer::executor_type; - executor_type get_executor() const noexcept { - return _timer->get_executor(); - } + using executor_type = asio::steady_timer::executor_type; + executor_type get_executor() const noexcept { + return _timer->get_executor(); + } - template - void perform(CompletionHandler&& handler) { - _cancel_slot = asio::get_associated_cancellation_slot(handler); + template + void perform(CompletionHandler&& handler) { + _cancel_slot = asio::get_associated_cancellation_slot(handler); - _timer->expires_after(_delay); - _timer->async_wait( - asio::prepend(std::move(*this), on_timer {}, std::move(handler)) - ); - } + _timer->expires_after(_delay); + _timer->async_wait( + asio::prepend(std::move(*this), on_timer {}, std::move(handler)) + ); + } - template - void operator()(on_timer, CompletionHandler&& h, error_code ec) { - // The timer places a handler into the cancellation slot - // and does not clear it. Therefore, we need to clear it explicitly - // to properly remove the corresponding cancellation signal - // in the test_broker. - get_cancellation_slot().clear(); + template + void operator()(on_timer, CompletionHandler&& h, error_code ec) { + // The timer places a handler into the cancellation slot + // and does not clear it. Therefore, we need to clear it explicitly + // to properly remove the corresponding cancellation signal + // in the test_broker. + 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 - ); + 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)); - } + asio::dispatch(asio::prepend(std::move(bh), ec)); + } }; template decltype(auto) async_delay( - asio::cancellation_slot cancel_slot, - delayed_op&& op, - CompletionToken&& token + asio::cancellation_slot cancel_slot, + delayed_op&& op, + CompletionToken&& token ) { - using Signature = void (error_code, boost::remove_cv_ref_t...); + using Signature = void (error_code, boost::remove_cv_ref_t...); - auto initiation = []( - auto handler, asio::cancellation_slot cancel_slot, - delayed_op op - ) { - op.perform( - asio::bind_cancellation_slot(cancel_slot, std::move(handler)) - ); - }; + auto initiation = []( + auto handler, asio::cancellation_slot cancel_slot, + delayed_op op + ) { + op.perform( + asio::bind_cancellation_slot(cancel_slot, std::move(handler)) + ); + }; - return asio::async_initiate( - std::move(initiation), token, cancel_slot, std::move(op) - ); + return asio::async_initiate( + std::move(initiation), token, cancel_slot, std::move(op) + ); } -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_DELAYED_OP_HPP +#endif // BOOST_MQTT5_TEST_DELAYED_OP_HPP diff --git a/test/include/test_common/message_exchange.hpp b/test/include/test_common/message_exchange.hpp index e6bcfac..8a49530 100644 --- a/test/include/test_common/message_exchange.hpp +++ b/test/include/test_common/message_exchange.hpp @@ -5,8 +5,11 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_MESSAGE_EXCHANGE_HPP -#define ASYNC_MQTT5_TEST_MESSAGE_EXCHANGE_HPP +#ifndef BOOST_MQTT5_TEST_MESSAGE_EXCHANGE_HPP +#define BOOST_MQTT5_TEST_MESSAGE_EXCHANGE_HPP + +#include +#include #include #include @@ -17,12 +20,9 @@ #include #include -#include -#include - #include "test_common/delayed_op.hpp" -namespace async_mqtt5::test { +namespace boost::mqtt5::test { using error_code = boost::system::error_code; using time_stamp = std::chrono::time_point; @@ -38,273 +38,273 @@ using namespace std::chrono_literals; namespace detail { class stream_message { - error_code _ec; - duration _after { 0 }; - std::vector _content; + error_code _ec; + duration _after { 0 }; + std::vector _content; public: - template - stream_message(error_code ec, duration after, Args&& ...args) : - _ec(ec), _after(after) - { - (_content.insert(_content.end(), args.begin(), args.end()), ...); - } + template + 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; + stream_message(const stream_message&) = delete; + stream_message(stream_message&&) = default; - stream_message& operator=(stream_message&&) = default; - stream_message& operator=(const stream_message&) = delete; + stream_message& operator=(stream_message&&) = default; + stream_message& operator=(const stream_message&) = delete; - template - auto to_operation(const Executor& ex) { - return delayed_op> { - ex, _after, _ec, std::move(_content) - }; - } + template + auto to_operation(const Executor& ex) { + return delayed_op> { + ex, _after, _ec, std::move(_content) + }; + } }; } // end namespace detail class client_message { - msg_exchange* _owner; + msg_exchange* _owner; - error_code _write_ec; - duration _complete_after { 0 }; - std::vector _expected_packets; + error_code _write_ec; + duration _complete_after { 0 }; + std::vector _expected_packets; - std::vector _replies; + std::vector _replies; public: - template - client_message(msg_exchange* owner, Args&&... args) : - _owner(owner), - _expected_packets({ std::forward(args)... }) - {} + template + client_message(msg_exchange* owner, Args&&... args) : + _owner(owner), + _expected_packets({ std::forward(args)... }) + {} - client_message(client_message&&) = default; - client_message(const client_message&) = delete; + client_message(client_message&&) = default; + client_message(const client_message&) = delete; - client_message& operator=(client_message&&) = default; - client_message& operator=(const client_message&) = delete; + client_message& operator=(client_message&&) = default; + client_message& operator=(const client_message&) = delete; - client_message& complete_with(error_code ec, duration af) { - _write_ec = ec; - _complete_after = af; - return *this; - } + client_message& complete_with(error_code ec, duration af) { + _write_ec = ec; + _complete_after = af; + return *this; + } - template - client_message& reply_with(Args&& ...args) { - return reply_with_dur(std::make_tuple(std::forward(args)...)); - } + template + client_message& reply_with(Args&& ...args) { + return reply_with_dur(std::make_tuple(std::forward(args)...)); + } - template - client_message& expect(Args&& ...args); + template + client_message& expect(Args&& ...args); - template - broker_message& send(Args&& ...args); + template + broker_message& send(Args&& ...args); - template - decltype(auto) write_completion(const Executor& ex) const { - return delayed_op(ex, _complete_after, _write_ec); - } + template + decltype(auto) write_completion(const Executor& ex) const { + return delayed_op(ex, _complete_after, _write_ec); + } - template - decltype(auto) pop_reply_ops(const Executor& ex) { - std::vector>> ret; - std::transform( - _replies.begin(), _replies.end(), std::back_inserter(ret), - [&ex](auto& r) { return r.to_operation(ex); } - ); - _replies.clear(); - return ret; - } + template + decltype(auto) pop_reply_ops(const Executor& ex) { + std::vector>> ret; + std::transform( + _replies.begin(), _replies.end(), std::back_inserter(ret), + [&ex](auto& r) { return r.to_operation(ex); } + ); + _replies.clear(); + return ret; + } - decltype(auto) expected_packets() { - return _expected_packets; - } + decltype(auto) expected_packets() { + return _expected_packets; + } private: - template - client_message& reply_with_dur(const Tuple& t) { - using indices = std::make_index_sequence - 1>; - return reply_with_dur(t, indices {}); - } + template + client_message& reply_with_dur(const Tuple& t) { + using indices = std::make_index_sequence - 1>; + return reply_with_dur(t, indices {}); + } - template - client_message& reply_with_dur(const Tuple& t, std::index_sequence) { - return reply_with_impl( - std::get - 1>(t), - std::get(t)... - ); - } + template + client_message& reply_with_dur(const Tuple& t, std::index_sequence) { + return reply_with_impl( + std::get - 1>(t), + std::get(t)... + ); + } - template < - typename ...Args, - std::enable_if_t< - (std::is_same_v, std::string> && ...), - bool - > = true - > - client_message& reply_with_impl(duration af, Args&& ...args) { - _replies.emplace_back( - error_code {}, af, std::forward(args)... - ); - return *this; - } + template < + typename ...Args, + std::enable_if_t< + (std::is_same_v, std::string> && ...), + bool + > = true + > + client_message& reply_with_impl(duration af, Args&& ...args) { + _replies.emplace_back( + error_code {}, af, std::forward(args)... + ); + return *this; + } - client_message& reply_with_impl(duration af, error_code ec) { - _replies.emplace_back(ec, af); - 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; + msg_exchange* _owner; + detail::stream_message _message; public: - template - broker_message( - msg_exchange* owner, error_code ec, duration af, Args&&... args - ) : - _owner(owner), _message(ec, af, std::forward(args) ...) - {} + template + broker_message( + msg_exchange* owner, error_code ec, duration af, Args&&... args + ) : + _owner(owner), _message(ec, af, std::forward(args) ...) + {} - broker_message(broker_message&&) = default; - broker_message(const broker_message&) = delete; + broker_message(broker_message&&) = default; + broker_message(const broker_message&) = delete; - broker_message& operator=(broker_message&&) = default; - broker_message& operator=(const broker_message&) = delete; + broker_message& operator=(broker_message&&) = default; + broker_message& operator=(const broker_message&) = delete; - template - client_message& expect(Args&& ...args); + template + client_message& expect(Args&& ...args); - template - broker_message& send(Args&& ...args); + template + broker_message& send(Args&& ...args); - template - decltype(auto) pop_send_op(const Executor& ex) { - return _message.to_operation(ex); - } + template + decltype(auto) pop_send_op(const Executor& ex) { + return _message.to_operation(ex); + } }; class msg_exchange { - std::deque _to_broker; - std::vector _from_broker; + std::deque _to_broker; + std::vector _from_broker; public: - msg_exchange() = default; + msg_exchange() = default; - msg_exchange(msg_exchange&&) = default; - msg_exchange(const msg_exchange&) = delete; + msg_exchange(msg_exchange&&) = default; + msg_exchange(const msg_exchange&) = delete; - msg_exchange& operator=(msg_exchange&&) = default; - msg_exchange& operator=(const msg_exchange&) = delete; + msg_exchange& operator=(msg_exchange&&) = default; + msg_exchange& operator=(const msg_exchange&) = delete; - template < - typename ...Args, - std::enable_if_t< - (std::is_same_v, std::string> && ...), - bool - > = true - > - client_message& expect(Args&& ...args) { - _to_broker.emplace_back(this, std::forward(args)...); - return _to_broker.back(); - } + template < + typename ...Args, + std::enable_if_t< + (std::is_same_v, std::string> && ...), + bool + > = true + > + client_message& expect(Args&& ...args) { + _to_broker.emplace_back(this, std::forward(args)...); + return _to_broker.back(); + } - template - broker_message& send(Args&& ...args) { - return send_with_dur(std::make_tuple(std::forward(args)...)); - } + template + broker_message& send(Args&& ...args) { + return send_with_dur(std::make_tuple(std::forward(args)...)); + } - std::optional pop_reply_action() { - if (_to_broker.empty()) - return std::nullopt; + std::optional pop_reply_action() { + if (_to_broker.empty()) + return std::nullopt; - auto rv = std::move(_to_broker.front()); - _to_broker.pop_front(); - return rv; - } + auto rv = std::move(_to_broker.front()); + _to_broker.pop_front(); + return rv; + } - template - auto pop_broker_ops(const Executor& ex) { - std::vector>> 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; - } + template + auto pop_broker_ops(const Executor& ex) { + std::vector>> 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; + } - bool has_remaining_messages() const { - return !_to_broker.empty() || !_from_broker.empty(); - } + bool has_remaining_messages() const { + return !_to_broker.empty() || !_from_broker.empty(); + } private: - template - broker_message& send_with_dur(const Tuple& t) { - using indices = std::make_index_sequence - 1>; - return send_with_dur(t, indices {}); - } + template + broker_message& send_with_dur(const Tuple& t) { + using indices = std::make_index_sequence - 1>; + return send_with_dur(t, indices {}); + } - template - broker_message& send_with_dur(const Tuple& t, std::index_sequence) { - return send_impl( - std::get - 1>(t), - std::get(t)... - ); - } + template + broker_message& send_with_dur(const Tuple& t, std::index_sequence) { + return send_impl( + std::get - 1>(t), + std::get(t)... + ); + } - template < - typename ...Args, - std::enable_if_t< - (std::is_same_v, std::string> && ...), - bool - > = true - > - broker_message& send_impl(duration after, Args&& ...args) { - _from_broker.emplace_back( - this, error_code {}, after, std::forward(args)... - ); - return _from_broker.back(); - } + template < + typename ...Args, + std::enable_if_t< + (std::is_same_v, std::string> && ...), + bool + > = true + > + broker_message& send_impl(duration after, Args&& ...args) { + _from_broker.emplace_back( + this, error_code {}, after, std::forward(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(); - } + broker_message& send_impl(duration after, error_code ec) { + _from_broker.emplace_back(this, ec, after); + return _from_broker.back(); + } }; template client_message& client_message::expect(Args&& ...args) { - return _owner->expect(std::forward(args)...); + return _owner->expect(std::forward(args)...); } template broker_message& client_message::send(Args&& ...args) { - return _owner->send(std::forward(args)...); + return _owner->send(std::forward(args)...); } template client_message& broker_message::expect(Args&& ...args) { - return _owner->expect(std::forward(args)...); + return _owner->expect(std::forward(args)...); } template broker_message& broker_message::send(Args&& ...args) { - return _owner->send(std::forward(args)...); + return _owner->send(std::forward(args)...); } -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_MESSAGE_EXCHANGE_HPP +#endif // BOOST_MQTT5_TEST_MESSAGE_EXCHANGE_HPP diff --git a/test/include/test_common/packet_util.hpp b/test/include/test_common/packet_util.hpp index 3408a23..0c210f2 100644 --- a/test/include/test_common/packet_util.hpp +++ b/test/include/test_common/packet_util.hpp @@ -5,8 +5,18 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_PACKET_UTIL_HPP -#define ASYNC_MQTT5_TEST_PACKET_UTIL_HPP +#ifndef BOOST_MQTT5_TEST_PACKET_UTIL_HPP +#define BOOST_MQTT5_TEST_PACKET_UTIL_HPP + +#include +#include + +#include +#include + +#include +#include +#include #include #include @@ -15,383 +25,374 @@ #include #include -#include -#include -#include +namespace boost::mqtt5::test { -#include -#include -#include -#include - -namespace async_mqtt5::test { - -using control_code_e = async_mqtt5::detail::control_code_e; +using control_code_e = boost::mqtt5::detail::control_code_e; template std::string concat_strings(Strings&&... strings) { - std::ostringstream stream; - (stream << ... << std::forward(strings)); - return stream.str(); + std::ostringstream stream; + (stream << ... << std::forward(strings)); + return stream.str(); } namespace detail { inline qos_e extract_qos(uint8_t flags) { - auto byte = (flags & 0b0110) >> 1; - return qos_e(byte); + auto byte = (flags & 0b0110) >> 1; + return qos_e(byte); } inline control_code_e extract_code(uint8_t control_byte) { - 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[] = { - control_code_e::pubrel, control_code_e::subscribe, - control_code_e::unsubscribe - }; + 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[] = { + control_code_e::pubrel, control_code_e::subscribe, + control_code_e::unsubscribe + }; - if ((control_byte >> 4) == publish_bits) - return control_code_e::publish; - if ((control_byte & mask) == control_byte) - return control_code_e(control_byte & mask); + if ((control_byte >> 4) == publish_bits) + return control_code_e::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; + for (const auto& special_code : codes_with_non_zero_end) + if (control_byte == (uint8_t(special_code) | special_mask)) + return special_code; - return control_code_e::no_packet; + return control_code_e::no_packet; } inline std::string_view code_to_str(control_code_e code) { - switch (code) { - case control_code_e::connect: return "CONNECT"; - case control_code_e::connack: return "CONNACK"; - case control_code_e::publish: return "PUBLISH"; - case control_code_e::puback: return "PUBACK"; - case control_code_e::pubrec: return "PUBREC"; - case control_code_e::pubrel: return "PUBREL"; - case control_code_e::pubcomp: return "PUBCOMP"; - case control_code_e::subscribe: return "SUBSCRIBE"; - case control_code_e::suback: return "SUBACK"; - case control_code_e::unsubscribe: return "UNSUBSCRIBE"; - case control_code_e::unsuback: return "UNSUBACK"; - case control_code_e::auth: return "AUTH"; - case control_code_e::disconnect: return "DISCONNECT"; - case control_code_e::pingreq: return "PINGREQ"; - case control_code_e::pingresp: return "PINGRESP"; - default: return "NO PACKET"; - } + switch (code) { + case control_code_e::connect: return "CONNECT"; + case control_code_e::connack: return "CONNACK"; + case control_code_e::publish: return "PUBLISH"; + case control_code_e::puback: return "PUBACK"; + case control_code_e::pubrec: return "PUBREC"; + case control_code_e::pubrel: return "PUBREL"; + case control_code_e::pubcomp: return "PUBCOMP"; + case control_code_e::subscribe: return "SUBSCRIBE"; + case control_code_e::suback: return "SUBACK"; + case control_code_e::unsubscribe: return "UNSUBSCRIBE"; + case control_code_e::unsuback: return "UNSUBACK"; + case control_code_e::auth: return "AUTH"; + case control_code_e::disconnect: return "DISCONNECT"; + case control_code_e::pingreq: return "PINGREQ"; + case control_code_e::pingresp: return "PINGRESP"; + default: return "NO PACKET"; + } } template inline std::string to_readable_props(Props props) { - std::ostringstream stream; - props.visit([&stream](const auto&, const auto& v) -> bool { - using namespace async_mqtt5::detail; - if constexpr (is_optional) - if (v.has_value()) - stream << *v << " "; - if constexpr (is_vector) - for (size_t i = 0; i < v.size(); i++) { - if constexpr (is_pair) - stream << "(" << v[i].first << ", " << v[i].second << ")"; - else - stream << v[i]; - if (i + 1 < v.size()) - stream << ", "; - } - return true; - }); - return stream.str(); + std::ostringstream stream; + props.visit([&stream](const auto&, const auto& v) -> bool { + using namespace boost::mqtt5::detail; + if constexpr (is_optional) + if (v.has_value()) + stream << *v << " "; + if constexpr (is_vector) + for (size_t i = 0; i < v.size(); i++) { + if constexpr (is_pair) + stream << "(" << v[i].first << ", " << v[i].second << ")"; + else + stream << v[i]; + if (i + 1 < v.size()) + stream << ", "; + } + return true; + }); + return stream.str(); } using byte_citer = std::string::const_iterator; template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - auto connect = decoders::decode_connect(remain_length, it); - if (!connect.has_value()) - return "Cannot decode Connect packet!"; - auto& [cli_id, uname, pwd, keep_alive, clean_start, props, will] = *connect; + auto connect = decoders::decode_connect(remain_length, it); + if (!connect.has_value()) + return "Cannot decode Connect packet!"; + auto& [cli_id, uname, pwd, keep_alive, clean_start, props, will] = *connect; - return concat_strings( - code_to_str(code), - " uname: ", uname.value_or(""), " pwd: ", pwd.value_or(""), - " keep_alive: ", keep_alive, " clean_start: ", clean_start, - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " uname: ", uname.value_or(""), " pwd: ", pwd.value_or(""), + " keep_alive: ", keep_alive, " clean_start: ", clean_start, + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - auto connack = decoders::decode_connack(remain_length, it); - if (!connack.has_value()) - return "Cannot decode Connack packet!"; - auto& [session_present, reason_code, props] = *connack; + auto connack = decoders::decode_connack(remain_length, it); + if (!connack.has_value()) + return "Cannot decode Connack packet!"; + auto& [session_present, reason_code, props] = *connack; - return concat_strings( - code_to_str(code), - " session_present: ", session_present, " reason_code: ", reason_code, - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " session_present: ", session_present, " reason_code: ", reason_code, + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - auto disconnect = decoders::decode_disconnect(remain_length, it); - if (!disconnect.has_value()) - return "Cannot decode Disconnect packet!"; - auto& [reason_code, props] = *disconnect; + auto disconnect = decoders::decode_disconnect(remain_length, it); + if (!disconnect.has_value()) + return "Cannot decode Disconnect packet!"; + auto& [reason_code, props] = *disconnect; - return concat_strings( - code_to_str(code), - " reason_code: ", std::to_string(uint8_t(reason_code)), - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " reason_code: ", std::to_string(uint8_t(reason_code)), + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string( - uint8_t control_byte, uint32_t remain_length, byte_citer& it + uint8_t control_byte, uint32_t remain_length, byte_citer& it ) { - auto publish = decoders::decode_publish(control_byte, remain_length, it); - if (!publish.has_value()) - return "Cannot decode Publish packet!"; - auto& [topic, packet_id, flags, props, payload] = *publish; + auto publish = decoders::decode_publish(control_byte, remain_length, it); + if (!publish.has_value()) + return "Cannot decode Publish packet!"; + auto& [topic, packet_id, flags, props, payload] = *publish; - return concat_strings( - code_to_str(code), (packet_id ? " " + std::to_string(*packet_id) : ""), - " flags: ", std::bitset<8>(flags), - " topic: ", topic, " payload: ", payload, - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), (packet_id ? " " + std::to_string(*packet_id) : ""), + " flags: ", std::bitset<8>(flags), + " topic: ", topic, " payload: ", payload, + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t< - code == control_code_e::puback || code == control_code_e::pubrec || - code == control_code_e::pubrel || code == control_code_e::pubcomp, - bool> = true + control_code_e code, + std::enable_if_t< + code == control_code_e::puback || code == control_code_e::pubrec || + code == control_code_e::pubrel || code == control_code_e::pubcomp, + bool> = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - const auto packet_id = decoders::decode_packet_id(it).value_or(0); - remain_length -= sizeof(uint16_t); - uint8_t reason_code = remain_length == 0 ? 0 : uint8_t(*it); - return concat_strings( - code_to_str(code), - " packet_id: ", packet_id, " reason_code: ", std::to_string(reason_code) - ); + const auto packet_id = decoders::decode_packet_id(it).value_or(0); + remain_length -= sizeof(uint16_t); + uint8_t reason_code = remain_length == 0 ? 0 : uint8_t(*it); + return concat_strings( + code_to_str(code), + " packet_id: ", packet_id, " reason_code: ", std::to_string(reason_code) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - auto auth = decoders::decode_auth(remain_length, it); - if (!auth.has_value()) - return "Cannot decode Auth packet!"; - auto& [reason_code, props] = *auth; + auto auth = decoders::decode_auth(remain_length, it); + if (!auth.has_value()) + return "Cannot decode Auth packet!"; + auto& [reason_code, props] = *auth; - return concat_strings( - code_to_str(code), - " reason_code: ", std::to_string(uint8_t(reason_code)), - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " reason_code: ", std::to_string(uint8_t(reason_code)), + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - const auto packet_id = decoders::decode_packet_id(it).value_or(0); - remain_length -= sizeof(uint16_t); - auto subscribe = decoders::decode_subscribe(remain_length, it); - if (!subscribe.has_value()) - return "Cannot decode Subscribe packet!"; - auto& [props, topics] = *subscribe; + const auto packet_id = decoders::decode_packet_id(it).value_or(0); + remain_length -= sizeof(uint16_t); + auto subscribe = decoders::decode_subscribe(remain_length, it); + if (!subscribe.has_value()) + return "Cannot decode Subscribe packet!"; + auto& [props, topics] = *subscribe; - std::vector topics_str; - topics_str.resize(topics.size()); - boost::transform( - boost::make_iterator_range(topics.cbegin(), topics.cend()), - topics_str.begin(), - [](const auto& tuple) { - auto& [topic, options] = tuple; - return concat_strings(topic, " ", std::bitset<8>(options)); - } - ); - return concat_strings( - code_to_str(code), - " packet_id: ", packet_id, - " topics: ", boost::algorithm::join(topics_str, ","), - " props: ", to_readable_props(props) - ); + std::vector topics_str; + topics_str.resize(topics.size()); + boost::transform( + boost::make_iterator_range(topics.cbegin(), topics.cend()), + topics_str.begin(), + [](const auto& tuple) { + auto& [topic, options] = tuple; + return concat_strings(topic, " ", std::bitset<8>(options)); + } + ); + return concat_strings( + code_to_str(code), + " packet_id: ", packet_id, + " topics: ", boost::algorithm::join(topics_str, ","), + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - const auto packet_id = decoders::decode_packet_id(it).value_or(0); - remain_length -= sizeof(uint16_t); - auto unsubscribe = decoders::decode_unsubscribe(remain_length, it); - if (!unsubscribe.has_value()) - return "Cannot decode Unsubscribe packet!"; - auto& [props, topics] = *unsubscribe; + const auto packet_id = decoders::decode_packet_id(it).value_or(0); + remain_length -= sizeof(uint16_t); + auto unsubscribe = decoders::decode_unsubscribe(remain_length, it); + if (!unsubscribe.has_value()) + return "Cannot decode Unsubscribe packet!"; + auto& [props, topics] = *unsubscribe; - return concat_strings( - code_to_str(code), - " packet_id: ", packet_id, - " topics: ", boost::algorithm::join(topics, ","), - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " packet_id: ", packet_id, + " topics: ", boost::algorithm::join(topics, ","), + " props: ", to_readable_props(props) + ); } inline std::string reason_codes_to_string(const std::vector& rcs) { - std::vector rcs_str; - rcs_str.resize(rcs.size()); - boost::transform( - boost::make_iterator_range(rcs.cbegin(), rcs.cend()), - rcs_str.begin(), - [](const auto& rc) { return std::to_string(rc); } - ); - return boost::algorithm::join(rcs_str, ","); + std::vector rcs_str; + rcs_str.resize(rcs.size()); + boost::transform( + boost::make_iterator_range(rcs.cbegin(), rcs.cend()), + rcs_str.begin(), + [](const auto& rc) { return std::to_string(rc); } + ); + return boost::algorithm::join(rcs_str, ","); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - const auto packet_id = decoders::decode_packet_id(it).value_or(0); - remain_length -= sizeof(uint16_t); - auto suback = decoders::decode_suback(remain_length, it); - if (!suback.has_value()) - return "Cannot decode Suback packet!"; - auto& [props, reason_codes] = *suback; + const auto packet_id = decoders::decode_packet_id(it).value_or(0); + remain_length -= sizeof(uint16_t); + auto suback = decoders::decode_suback(remain_length, it); + if (!suback.has_value()) + return "Cannot decode Suback packet!"; + auto& [props, reason_codes] = *suback; - return concat_strings( - code_to_str(code), - " packet_id: ", packet_id, - " reason_codes: ", reason_codes_to_string(reason_codes), - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " packet_id: ", packet_id, + " reason_codes: ", reason_codes_to_string(reason_codes), + " props: ", to_readable_props(props) + ); } template < - control_code_e code, - std::enable_if_t = true + control_code_e code, + std::enable_if_t = true > inline std::string to_string(uint32_t remain_length, byte_citer& it) { - const auto packet_id = decoders::decode_packet_id(it).value_or(0); - remain_length -= sizeof(uint16_t); - auto unsuback = decoders::decode_unsuback(remain_length, it); - if (!unsuback.has_value()) - return "Cannot decode Unuback packet!"; - auto& [props, reason_codes] = *unsuback; + const auto packet_id = decoders::decode_packet_id(it).value_or(0); + remain_length -= sizeof(uint16_t); + auto unsuback = decoders::decode_unsuback(remain_length, it); + if (!unsuback.has_value()) + return "Cannot decode Unuback packet!"; + auto& [props, reason_codes] = *unsuback; - return concat_strings( - code_to_str(code), - " packet_id: ", packet_id, - " reason_codes: ", reason_codes_to_string(reason_codes), - " props: ", to_readable_props(props) - ); + return concat_strings( + code_to_str(code), + " packet_id: ", packet_id, + " reason_codes: ", reason_codes_to_string(reason_codes), + " props: ", to_readable_props(props) + ); } } // end namespace detail inline std::string to_readable_packet(std::string packet) { - auto control_byte = uint8_t(*packet.data()); - auto code = detail::extract_code(control_byte); + auto control_byte = uint8_t(*packet.data()); + auto code = detail::extract_code(control_byte); - if ( - code == control_code_e::pingreq || - code == control_code_e::pingresp - ) { - return concat_strings(detail::code_to_str(code)); - } + if ( + code == control_code_e::pingreq || + code == control_code_e::pingresp + ) { + return concat_strings(detail::code_to_str(code)); + } - auto begin = ++packet.cbegin(); - auto varlen = decoders::type_parse( - begin, packet.cend(), decoders::basic::varint_ - ); + auto begin = ++packet.cbegin(); + auto varlen = decoders::type_parse( + begin, packet.cend(), decoders::basic::varint_ + ); - switch (code) { - case control_code_e::connect: - return detail::to_string(*varlen, begin); - case control_code_e::connack: - return detail::to_string(*varlen, begin); - case control_code_e::disconnect: - return detail::to_string(*varlen, begin); - case control_code_e::publish: - return detail::to_string( - control_byte, *varlen, begin - ); - case control_code_e::puback: - return detail::to_string(*varlen, begin); - case control_code_e::pubrec: - return detail::to_string(*varlen, begin); - case control_code_e::pubrel: - return detail::to_string(*varlen, begin); - case control_code_e::pubcomp: - return detail::to_string(*varlen, begin); - case control_code_e::auth: - return detail::to_string(*varlen, begin); - case control_code_e::subscribe: - return detail::to_string(*varlen, begin); - case control_code_e::suback: - return detail::to_string(*varlen, begin); - case control_code_e::unsubscribe: - return detail::to_string(*varlen, begin); - case control_code_e::unsuback: - return detail::to_string(*varlen, begin); - default: - return ""; - } + switch (code) { + case control_code_e::connect: + return detail::to_string(*varlen, begin); + case control_code_e::connack: + return detail::to_string(*varlen, begin); + case control_code_e::disconnect: + return detail::to_string(*varlen, begin); + case control_code_e::publish: + return detail::to_string( + control_byte, *varlen, begin + ); + case control_code_e::puback: + return detail::to_string(*varlen, begin); + case control_code_e::pubrec: + return detail::to_string(*varlen, begin); + case control_code_e::pubrel: + return detail::to_string(*varlen, begin); + case control_code_e::pubcomp: + return detail::to_string(*varlen, begin); + case control_code_e::auth: + return detail::to_string(*varlen, begin); + case control_code_e::subscribe: + return detail::to_string(*varlen, begin); + case control_code_e::suback: + return detail::to_string(*varlen, begin); + case control_code_e::unsubscribe: + return detail::to_string(*varlen, begin); + case control_code_e::unsuback: + return detail::to_string(*varlen, begin); + default: + return ""; + } } template std::vector to_readable_packets(const ConstBufferSequence& buffers) { - namespace asio = boost::asio; + namespace asio = boost::asio; - std::vector content; - - for ( - auto it = asio::buffer_sequence_begin(buffers); - it != asio::buffer_sequence_end(buffers); - it++ - ) - content.push_back( - to_readable_packet(std::string { (const char*)it->data(), it->size() }) - ); + std::vector content; + + for ( + auto it = asio::buffer_sequence_begin(buffers); + it != asio::buffer_sequence_end(buffers); + it++ + ) + content.push_back( + to_readable_packet(std::string { (const char*)it->data(), it->size() }) + ); - return content; + return content; } inline disconnect_props dprops_with_reason_string(const std::string& reason_string) { - disconnect_props dprops; - dprops[prop::reason_string] = reason_string; - return dprops; + disconnect_props dprops; + dprops[prop::reason_string] = reason_string; + return dprops; } -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_PACKET_UTIL_HPP +#endif // BOOST_MQTT5_TEST_PACKET_UTIL_HPP diff --git a/test/include/test_common/test_authenticators.hpp b/test/include/test_common/test_authenticators.hpp index 1818c35..e220341 100644 --- a/test/include/test_common/test_authenticators.hpp +++ b/test/include/test_common/test_authenticators.hpp @@ -5,82 +5,82 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_TEST_AUTHENTICATORS_HPP -#define ASYNC_MQTT5_TEST_TEST_AUTHENTICATORS_HPP +#ifndef BOOST_MQTT5_TEST_TEST_AUTHENTICATORS_HPP +#define BOOST_MQTT5_TEST_TEST_AUTHENTICATORS_HPP -#include -#include +#include #include #include #include #include -#include +#include +#include -namespace async_mqtt5::test { +namespace boost::mqtt5::test { namespace asio = boost::asio; struct test_authenticator { - test_authenticator() = default; + test_authenticator() = default; - template - decltype(auto) async_auth( - auth_step_e step, std::string data, - CompletionToken&& token - ) { - using error_code = boost::system::error_code; - using Signature = void (error_code, std::string); + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); - auto initiate = [](auto handler, auth_step_e, std::string) { - asio::dispatch( - asio::prepend(std::move(handler), error_code {}, "") - ); - }; + auto initiate = [](auto handler, auth_step_e, std::string) { + asio::dispatch( + asio::prepend(std::move(handler), error_code {}, "") + ); + }; - return asio::async_initiate( - initiate, token, step, std::move(data) - ); - } + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } - std::string_view method() const { - return "method"; - } + std::string_view method() const { + return "method"; + } }; template struct fail_test_authenticator { - fail_test_authenticator() = default; + fail_test_authenticator() = default; - template - decltype(auto) async_auth( - auth_step_e step, std::string data, - CompletionToken&& token - ) { - using error_code = boost::system::error_code; - using Signature = void (error_code, std::string); + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); - auto initiate = [](auto handler, auth_step_e step, std::string) { - error_code ec; - if (fail_on_step == step) - ec = asio::error::no_recovery; + auto initiate = [](auto handler, auth_step_e step, std::string) { + error_code ec; + if (fail_on_step == step) + ec = asio::error::no_recovery; - asio::dispatch( - asio::prepend(std::move(handler), ec, "") - ); - }; + asio::dispatch( + asio::prepend(std::move(handler), ec, "") + ); + }; - return asio::async_initiate( - initiate, token, step, std::move(data) - ); - } + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } - std::string_view method() const { - return "method"; - } + std::string_view method() const { + return "method"; + } }; -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_TEST_AUTHENTICATORS_HPP +#endif // BOOST_MQTT5_TEST_TEST_AUTHENTICATORS_HPP diff --git a/test/include/test_common/test_autoconnect_stream.hpp b/test/include/test_common/test_autoconnect_stream.hpp index c4aa107..f45df44 100644 --- a/test/include/test_common/test_autoconnect_stream.hpp +++ b/test/include/test_common/test_autoconnect_stream.hpp @@ -4,131 +4,131 @@ // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_AUTOCONNECT_STREAM_HPP -#define ASYNC_MQTT5_TEST_AUTOCONNECT_STREAM_HPP +#ifndef BOOST_MQTT5_TEST_AUTOCONNECT_STREAM_HPP +#define BOOST_MQTT5_TEST_AUTOCONNECT_STREAM_HPP + +#include +#include +#include + +#include + +#include +#include +#include #include #include #include #include -#include -#include -#include - -#include -#include -#include - -#include - -namespace async_mqtt5::test { +namespace boost::mqtt5::test { namespace asio = boost::asio; using error_code = boost::system::error_code; template < - typename StreamType, - typename StreamContext = std::monostate, - typename LoggerType = async_mqtt5::noop_logger + typename StreamType, + typename StreamContext = std::monostate, + typename LoggerType = boost::mqtt5::noop_logger > class test_autoconnect_stream { public: - using stream_type = StreamType; - using stream_ptr = std::shared_ptr; - using stream_context_type = StreamContext; - using executor_type = typename stream_type::executor_type; - using logger_type = LoggerType; + using stream_type = StreamType; + using stream_ptr = std::shared_ptr; + using stream_context_type = StreamContext; + using executor_type = typename stream_type::executor_type; + using logger_type = LoggerType; private: - executor_type _stream_executor; - detail::async_mutex _conn_mtx; - asio::steady_timer _connect_timer; - detail::endpoints _endpoints; + executor_type _stream_executor; + detail::async_mutex _conn_mtx; + asio::steady_timer _connect_timer; + detail::endpoints _endpoints; - stream_ptr _stream_ptr; - stream_context_type& _stream_context; + stream_ptr _stream_ptr; + stream_context_type& _stream_context; - detail::log_invoke _log; + detail::log_invoke _log; - template - friend class async_mqtt5::detail::reconnect_op; + template + friend class boost::mqtt5::detail::reconnect_op; public: - test_autoconnect_stream( - const executor_type& ex, - stream_context_type& context, - detail::log_invoke& log - ) : - _stream_executor(ex), - _conn_mtx(_stream_executor), - _connect_timer(_stream_executor), - _endpoints(_stream_executor, _connect_timer, log), - _stream_context(context), - _log(log) - { - replace_next_layer(construct_next_layer()); - open_lowest_layer(_stream_ptr, asio::ip::tcp::v4()); - } + test_autoconnect_stream( + const executor_type& ex, + stream_context_type& context, + detail::log_invoke& log + ) : + _stream_executor(ex), + _conn_mtx(_stream_executor), + _connect_timer(_stream_executor), + _endpoints(_stream_executor, _connect_timer, log), + _stream_context(context), + _log(log) + { + replace_next_layer(construct_next_layer()); + open_lowest_layer(_stream_ptr, asio::ip::tcp::v4()); + } - test_autoconnect_stream(const test_autoconnect_stream&) = delete; - test_autoconnect_stream& operator=(const test_autoconnect_stream&) = delete; + test_autoconnect_stream(const test_autoconnect_stream&) = delete; + test_autoconnect_stream& operator=(const test_autoconnect_stream&) = delete; - stream_ptr stream_pointer() const { - return _stream_ptr; - } + stream_ptr stream_pointer() const { + return _stream_ptr; + } - bool is_open() const noexcept { - return detail::lowest_layer(*_stream_ptr).is_open(); - } + bool is_open() const noexcept { + return detail::lowest_layer(*_stream_ptr).is_open(); + } - void brokers(std::string hosts, uint16_t default_port) { - _endpoints.brokers(std::move(hosts), default_port); - } + void brokers(std::string hosts, uint16_t default_port) { + _endpoints.brokers(std::move(hosts), default_port); + } - static void open_lowest_layer(const stream_ptr& sptr, asio::ip::tcp protocol) { - error_code ec; - auto& layer = detail::lowest_layer(*sptr); - layer.open(protocol, ec); - layer.set_option(asio::socket_base::reuse_address(true), ec); - layer.set_option(asio::ip::tcp::no_delay(true), ec); - } + static void open_lowest_layer(const stream_ptr& sptr, asio::ip::tcp protocol) { + error_code ec; + auto& layer = detail::lowest_layer(*sptr); + layer.open(protocol, ec); + layer.set_option(asio::socket_base::reuse_address(true), ec); + layer.set_option(asio::ip::tcp::no_delay(true), ec); + } - void close() { - error_code ec; - detail::lowest_layer(*_stream_ptr).shutdown(asio::ip::tcp::socket::shutdown_both, ec); - detail::lowest_layer(*_stream_ptr).close(ec); - } + void close() { + error_code ec; + detail::lowest_layer(*_stream_ptr).shutdown(asio::ip::tcp::socket::shutdown_both, ec); + detail::lowest_layer(*_stream_ptr).close(ec); + } - stream_ptr construct_next_layer() const { - stream_ptr sptr; - if constexpr (detail::has_tls_context) - sptr = std::make_shared( - _stream_executor, _stream_context.tls_context() - ); - else - sptr = std::make_shared(_stream_executor); + stream_ptr construct_next_layer() const { + stream_ptr sptr; + if constexpr (detail::has_tls_context) + sptr = std::make_shared( + _stream_executor, _stream_context.tls_context() + ); + else + sptr = std::make_shared(_stream_executor); - return sptr; - } + return sptr; + } - stream_ptr construct_and_open_next_layer(asio::ip::tcp protocol) const { - auto sptr = construct_next_layer(); - open_lowest_layer(sptr, protocol); - return sptr; - } + stream_ptr construct_and_open_next_layer(asio::ip::tcp protocol) const { + auto sptr = construct_next_layer(); + open_lowest_layer(sptr, protocol); + return sptr; + } - void replace_next_layer(stream_ptr sptr) { - if (_stream_ptr) - close(); - std::exchange(_stream_ptr, std::move(sptr)); - } + void replace_next_layer(stream_ptr sptr) { + if (_stream_ptr) + close(); + std::exchange(_stream_ptr, std::move(sptr)); + } private: - detail::log_invoke& log() { - return _log; - } + detail::log_invoke& log() { + return _log; + } }; -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // !ASYNC_MQTT5_TEST_AUTOCONNECT_STREAM_HPP +#endif // !BOOST_MQTT5_TEST_AUTOCONNECT_STREAM_HPP diff --git a/test/include/test_common/test_broker.hpp b/test/include/test_common/test_broker.hpp index ba64749..1fe76d2 100644 --- a/test/include/test_common/test_broker.hpp +++ b/test/include/test_common/test_broker.hpp @@ -5,11 +5,27 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_TEST_BROKER_HPP -#define ASYNC_MQTT5_TEST_TEST_BROKER_HPP +#ifndef BOOST_MQTT5_TEST_TEST_BROKER_HPP +#define BOOST_MQTT5_TEST_TEST_BROKER_HPP -#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include @@ -19,309 +35,292 @@ #include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" -namespace async_mqtt5::test { +namespace boost::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 _handler {}; + void* _buffer_data { nullptr }; + size_t _buffer_size { 0 }; + asio::any_completion_handler _handler {}; public: - template - pending_read(const MutableBuffer& buffer, Handler&& handler) : - _buffer_data(buffer.data()), - _buffer_size(buffer.size()), - _handler(std::move(handler)) - {} + template + pending_read(const MutableBuffer& buffer, Handler&& handler) : + _buffer_data(buffer.data()), + _buffer_size(buffer.size()), + _handler(std::move(handler)) + {} - pending_read() = default; + pending_read() = default; - pending_read(pending_read&&) = default; - pending_read(const pending_read&) = delete; + pending_read(pending_read&&) = default; + pending_read(const pending_read&) = delete; - pending_read& operator=(pending_read&&) = default; - pending_read& operator=(const pending_read&) = delete; + pending_read& operator=(pending_read&&) = default; + pending_read& operator=(const pending_read&) = delete; - size_t consume(const std::vector& data) { - size_t num_bytes = std::min(_buffer_size, data.size()); - if (num_bytes == 0) - return 0; - std::memcpy(_buffer_data, data.data(), num_bytes); - return num_bytes; - } + size_t consume(const std::vector& data) { + size_t num_bytes = std::min(_buffer_size, data.size()); + if (num_bytes == 0) + return 0; + std::memcpy(_buffer_data, data.data(), num_bytes); + return num_bytes; + } - template - 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)); - } + template + 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; - } + 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; + 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 {}; + static inline asio::execution_context::id id {}; private: - using base = asio::execution_context::service; + using base = asio::execution_context::service; - struct on_receive {}; - struct on_delayed_complete {}; + struct on_receive {}; + struct on_delayed_complete {}; - struct broker_data { - error_code ec; - std::vector bytes; - }; + struct broker_data { + error_code ec; + std::vector bytes; + }; - executor_type _ex; - std::deque _broker_data; - pending_read _pending_read; + executor_type _ex; + std::deque _broker_data; + pending_read _pending_read; - msg_exchange _broker_side; - std::vector> _cancel_signals; + msg_exchange _broker_side; + std::vector> _cancel_signals; public: - explicit 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(); - } + explicit 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; - test_broker& operator=(const test_broker&) = delete; + test_broker(const test_broker&) = delete; + test_broker& operator=(const test_broker&) = delete; - executor_type get_executor() const noexcept { - return _ex; - } + executor_type get_executor() const noexcept { + return _ex; + } - void close_connection() { - _pending_read.complete( - get_executor(), asio::error::operation_aborted, 0 - ); + void close_connection() { + _pending_read.complete( + get_executor(), asio::error::operation_aborted, 0 + ); - for (auto& cs : _cancel_signals) - cs->emit(asio::cancellation_type::terminal); + for (auto& cs : _cancel_signals) + cs->emit(asio::cancellation_type::terminal); - _broker_data.clear(); - } + _broker_data.clear(); + } - bool received_all_expected() { - return !_broker_side.has_remaining_messages(); - } + bool received_all_expected() { + return !_broker_side.has_remaining_messages(); + } - template - 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(); + template + 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( - asio::buffer_sequence_begin(buffers), - asio::buffer_sequence_end(buffers), - size_t(0), [](size_t a, const auto& b) { return a + b.size(); } - ); + size_t bytes_written = std::accumulate( + asio::buffer_sequence_begin(buffers), + asio::buffer_sequence_end(buffers), + size_t(0), [](size_t a, const auto& b) { return a + b.size(); } + ); - executor_type ex = get_executor(); + executor_type ex = get_executor(); - if (reply_action) { - const auto& expected = reply_action->expected_packets(); + if (reply_action) { + const auto& expected = reply_action->expected_packets(); - size_t buffers_size = std::distance( - asio::buffer_sequence_begin(buffers), asio::buffer_sequence_end(buffers) - ); - BOOST_TEST(buffers_size == expected.size()); + size_t buffers_size = std::distance( + asio::buffer_sequence_begin(buffers), asio::buffer_sequence_end(buffers) + ); + BOOST_TEST(buffers_size == expected.size()); - size_t num_packets = std::min(buffers_size, expected.size()); - auto it = asio::buffer_sequence_begin(buffers); - for (size_t i = 0; i < num_packets; ++i, ++it) { - BOOST_TEST(it->size() == expected[i].size()); - size_t len = std::min(it->size(), expected[i].size()); - if (memcmp(it->data(), expected[i].data(), len)) - BOOST_TEST_MESSAGE( - concat_strings( - "Packet mismatch!\nExpected: ", - to_readable_packet(expected[i]), - "\nReceived: ", - to_readable_packet(std::string((const char*)it->data(), it->size())) - ) - ); - } - } else - BOOST_TEST_MESSAGE( - "Broker side did not expect: " << - boost::algorithm::join(to_readable_packets(buffers), ",") - ); + size_t num_packets = std::min(buffers_size, expected.size()); + auto it = asio::buffer_sequence_begin(buffers); + for (size_t i = 0; i < num_packets; ++i, ++it) { + BOOST_TEST(it->size() == expected[i].size()); + size_t len = std::min(it->size(), expected[i].size()); + if (memcmp(it->data(), expected[i].data(), len)) + BOOST_TEST_MESSAGE( + concat_strings( + "Packet mismatch!\nExpected: ", + to_readable_packet(expected[i]), + "\nReceived: ", + to_readable_packet(std::string((const char*)it->data(), it->size())) + ) + ); + } + } else + BOOST_TEST_MESSAGE( + "Broker side did not expect: " << + boost::algorithm::join(to_readable_packets(buffers), ",") + ); - auto complete_op = reply_action ? - reply_action->write_completion(ex) : - delayed_op(ex, 0ms, error_code {}); + auto complete_op = reply_action ? + reply_action->write_completion(ex) : + delayed_op(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 - ) - ); + 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; + 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 {}) - ); - }; + 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( - std::move(initiation), token, buffers - ); - } + return asio::async_initiate( + std::move(initiation), token, buffers + ); + } - template - 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(); - }; + template + 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( - std::move(initiation), token, buffer - ); - } + return asio::async_initiate( + std::move(initiation), token, buffer + ); + } - void operator()( - on_receive, error_code delay_ec, - error_code ec, std::vector bytes - ) { - remove_cancel_signal(); + void operator()( + on_receive, error_code delay_ec, + error_code ec, std::vector bytes + ) { + remove_cancel_signal(); - if (delay_ec) // asio::operation_aborted - return; + if (delay_ec) // asio::operation_aborted + return; - _broker_data.push_back({ ec, std::move(bytes) }); - complete_read(); - } + _broker_data.push_back({ ec, std::move(bytes) }); + complete_read(); + } - template - void operator()( - on_delayed_complete, Handler handler, size_t bytes, - error_code delay_ec, error_code ec - ) { - remove_cancel_signal(); + template + 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; - } + if (delay_ec) { // asio::operation_aborted + ec = delay_ec; + bytes = 0; + } - asio::dispatch(asio::prepend(std::move(handler), ec, bytes)); - } + asio::dispatch(asio::prepend(std::move(handler), ec, bytes)); + } - void cancel_pending_read() { - _pending_read.complete(get_executor(), asio::error::operation_aborted, 0); - } + void cancel_pending_read() { + _pending_read.complete(get_executor(), asio::error::operation_aborted, 0); + } private: - void shutdown() override { - cancel_pending_read(); - } + void shutdown() override { + cancel_pending_read(); + } - 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 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; + void complete_read() { + if (_pending_read.empty()) + return; - error_code ec = {}; - size_t bytes_read = 0; + 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 (!_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); - } + 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); - } + _pending_read.complete(get_executor(), ec, bytes_read); + } - asio::cancellation_slot make_cancel_slot() { - _cancel_signals.push_back( - std::make_unique() - ); - return _cancel_signals.back()->slot(); - } + asio::cancellation_slot make_cancel_slot() { + _cancel_signals.push_back( + std::make_unique() + ); + 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() - ); - } + 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 +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_TEST_BROKER_HPP +#endif // BOOST_MQTT5_TEST_TEST_BROKER_HPP diff --git a/test/include/test_common/test_service.hpp b/test/include/test_common/test_service.hpp index 6682dd0..e0309e6 100644 --- a/test/include/test_common/test_service.hpp +++ b/test/include/test_common/test_service.hpp @@ -5,11 +5,12 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_TEST_SERVICE_HPP -#define ASYNC_MQTT5_TEST_TEST_SERVICE_HPP +#ifndef BOOST_MQTT5_TEST_TEST_SERVICE_HPP +#define BOOST_MQTT5_TEST_TEST_SERVICE_HPP -#include -#include +#include + +#include #include #include @@ -17,77 +18,76 @@ #include #include -#include +#include +#include -#include - -namespace async_mqtt5::test { +namespace boost::mqtt5::test { namespace asio = boost::asio; template < - typename StreamType, - typename TlsContext = std::monostate + typename StreamType, + typename TlsContext = std::monostate > -class test_service : public async_mqtt5::detail::client_service { - using error_code = boost::system::error_code; - using base = async_mqtt5::detail::client_service; +class test_service : public boost::mqtt5::detail::client_service { + using error_code = boost::system::error_code; + using base = boost::mqtt5::detail::client_service; - asio::any_io_executor _ex; - connack_props _test_props; + asio::any_io_executor _ex; + connack_props _test_props; public: - explicit test_service(const asio::any_io_executor& ex) - : base(ex), _ex(ex) - {} + explicit test_service(const asio::any_io_executor& ex) + : base(ex), _ex(ex) + {} - test_service(const asio::any_io_executor& ex, connack_props props) - : base(ex), _ex(ex), _test_props(std::move(props)) - {} + test_service(const asio::any_io_executor& ex, connack_props props) + : base(ex), _ex(ex), _test_props(std::move(props)) + {} - template - decltype(auto) async_send( - const BufferType&, uint32_t, unsigned, - CompletionToken&& token - ) { - auto initiation = [this](auto handler) { - asio::post(_ex, - asio::prepend(std::move(handler), error_code {}) - ); - }; + template + decltype(auto) async_send( + const BufferType&, uint32_t, unsigned, + CompletionToken&& token + ) { + auto initiation = [this](auto handler) { + asio::post(_ex, + asio::prepend(std::move(handler), error_code {}) + ); + }; - return asio::async_initiate( - std::move(initiation), token - ); - } + return asio::async_initiate( + std::move(initiation), token + ); + } - template - const auto& connack_property(Prop p) const { - return _test_props[p]; - } + template + const auto& connack_property(Prop p) const { + return _test_props[p]; + } - const auto& connack_properties() { - return _test_props; - } + const auto& connack_properties() { + return _test_props; + } }; template < - typename StreamType, - typename TlsContext = std::monostate + typename StreamType, + typename TlsContext = std::monostate > -class overrun_client : public async_mqtt5::detail::client_service { +class overrun_client : public boost::mqtt5::detail::client_service { public: - explicit overrun_client(const asio::any_io_executor& ex) : - async_mqtt5::detail::client_service(ex) - {} + explicit overrun_client(const asio::any_io_executor& ex) : + boost::mqtt5::detail::client_service(ex) + {} - uint16_t allocate_pid() { - return 0; - } + uint16_t allocate_pid() { + return 0; + } }; -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_TEST_SERVICE_HPP +#endif // BOOST_MQTT5_TEST_TEST_SERVICE_HPP diff --git a/test/include/test_common/test_stream.hpp b/test/include/test_common/test_stream.hpp index 062fe1b..d077d8e 100644 --- a/test/include/test_common/test_stream.hpp +++ b/test/include/test_common/test_stream.hpp @@ -5,28 +5,28 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef ASYNC_MQTT5_TEST_TEST_STREAM_HPP -#define ASYNC_MQTT5_TEST_TEST_STREAM_HPP +#ifndef BOOST_MQTT5_TEST_TEST_STREAM_HPP +#define BOOST_MQTT5_TEST_TEST_STREAM_HPP -#include -#include +#include #include #include #include #include +#include #include #include -#include #include -#include +#include #include -#include +#include +#include #include "test_common/test_broker.hpp" -namespace async_mqtt5::test { +namespace boost::mqtt5::test { namespace asio = boost::asio; @@ -38,337 +38,337 @@ 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; + 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; + executor_type _ex; + test_broker* _test_broker { nullptr }; + endpoint_type _remote_ep; - template - friend class read_op; + template + friend class read_op; - template - friend class write_op; + template + friend class write_op; public: - explicit test_stream_impl(executor_type ex) : _ex(std::move(ex)) {} + explicit test_stream_impl(executor_type ex) : _ex(std::move(ex)) {} - test_stream_impl(test_stream_impl&&) = default; - test_stream_impl(const test_stream_impl&) = delete; + test_stream_impl(test_stream_impl&&) = default; + test_stream_impl(const test_stream_impl&) = delete; - test_stream_impl& operator=(test_stream_impl&&) = default; - test_stream_impl& operator=(const test_stream_impl&) = delete; + test_stream_impl& operator=(test_stream_impl&&) = default; + test_stream_impl& operator=(const test_stream_impl&) = delete; - executor_type get_executor() const noexcept { - return _ex; - } + executor_type get_executor() const noexcept { + return _ex; + } - void open(const protocol_type&, error_code& ec) { - ec = {}; - _test_broker = &asio::use_service(_ex.context()); - } + void open(const protocol_type&, error_code& ec) { + ec = {}; + _test_broker = &asio::use_service(_ex.context()); + } - void cancel(error_code&) {} + void cancel(error_code&) {} - void close(error_code& ec) { - disconnect(); - ec = {}; - _test_broker = nullptr; - } + void close(error_code& ec) { + disconnect(); + ec = {}; + _test_broker = nullptr; + } - void shutdown(asio::ip::tcp::socket::shutdown_type, error_code& ec) { - ec = {}; - } + 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 connect(const endpoint_type& ep, error_code& ec) { + ec = {}; + _remote_ep = ep; + } - void disconnect() { - _remote_ep = {}; - if (_test_broker) - _test_broker->close_connection(); - } + 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; - } + 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_open() const { + return _test_broker != nullptr; + } - bool is_connected() const { - return _remote_ep != endpoint_type {}; - } + bool is_connected() const { + return _remote_ep != endpoint_type {}; + } }; template class read_op { - struct on_read {}; - std::shared_ptr _stream_impl; + struct on_read {}; + std::shared_ptr _stream_impl; - using handler_type = async_mqtt5::detail::cancellable_handler< - Handler, - typename test_stream_impl::executor_type - >; - handler_type _handler; + using handler_type = boost::mqtt5::detail::cancellable_handler< + Handler, + typename test_stream_impl::executor_type + >; + handler_type _handler; public: - read_op( - std::shared_ptr stream_impl, Handler handler - ) : - _stream_impl(std::move(stream_impl)), - _handler(std::move(handler), _stream_impl->get_executor()) - { - auto slot = asio::get_associated_cancellation_slot(_handler); - if (slot.is_connected()) - slot.assign([stream_impl = _stream_impl](asio::cancellation_type_t) { - stream_impl->_test_broker->cancel_pending_read(); - }); - } + read_op( + std::shared_ptr stream_impl, Handler handler + ) : + _stream_impl(std::move(stream_impl)), + _handler(std::move(handler), _stream_impl->get_executor()) + { + auto slot = asio::get_associated_cancellation_slot(_handler); + if (slot.is_connected()) + slot.assign([stream_impl = _stream_impl](asio::cancellation_type_t) { + stream_impl->_test_broker->cancel_pending_read(); + }); + } - read_op(read_op&&) = default; - read_op(const read_op&) = delete; + read_op(read_op&&) = default; + read_op(const read_op&) = delete; - read_op& operator=(read_op&&) noexcept = default; - read_op& operator=(const read_op&) = delete; + read_op& operator=(read_op&&) noexcept = default; + read_op& operator=(const read_op&) = delete; - using allocator_type = asio::recycling_allocator; - allocator_type get_allocator() const noexcept { - return allocator_type {}; - } + using allocator_type = asio::recycling_allocator; + allocator_type get_allocator() const noexcept { + return allocator_type {}; + } - using executor_type = test_stream_impl::executor_type; - executor_type get_executor() const noexcept { - return _stream_impl->get_executor(); - } + using executor_type = test_stream_impl::executor_type; + executor_type get_executor() const noexcept { + return _stream_impl->get_executor(); + } - template - void perform(const BufferType& buffer) { - if (!_stream_impl->is_open() || !_stream_impl->is_connected()) - return complete_immediate(asio::error::not_connected, 0); + template + void perform(const BufferType& buffer) { + if (!_stream_impl->is_open() || !_stream_impl->is_connected()) + return complete_immediate(asio::error::not_connected, 0); - _stream_impl->_test_broker->read_from_network( - buffer, - asio::prepend(std::move(*this), on_read {}) - ); - } + _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); - } + void operator()(on_read, error_code ec, size_t bytes_read) { + if (ec) + _stream_impl->disconnect(); + complete(ec, bytes_read); + } private: - void complete_immediate(error_code ec, size_t bytes_read) { - _handler.complete_immediate(ec, bytes_read); - } + void complete_immediate(error_code ec, size_t bytes_read) { + _handler.complete_immediate(ec, bytes_read); + } - void complete(error_code ec, size_t bytes_read) { - _handler.complete(ec, bytes_read); - } + void complete(error_code ec, size_t bytes_read) { + _handler.complete(ec, bytes_read); + } }; template class write_op { - struct on_write {}; + struct on_write {}; - std::shared_ptr _stream_impl; - Handler _handler; + std::shared_ptr _stream_impl; + Handler _handler; public: - write_op( - std::shared_ptr stream_impl, Handler handler - ) : - _stream_impl(std::move(stream_impl)), - _handler(std::move(handler)) - {} + write_op( + std::shared_ptr stream_impl, Handler handler + ) : + _stream_impl(std::move(stream_impl)), + _handler(std::move(handler)) + {} - write_op(write_op&&) = default; - write_op(const write_op&) = delete; + write_op(write_op&&) = default; + write_op(const write_op&) = delete; - write_op& operator=(write_op&&) noexcept = default; - write_op& operator=(const write_op&) = delete; + write_op& operator=(write_op&&) noexcept = default; + write_op& operator=(const write_op&) = delete; - using allocator_type = asio::recycling_allocator; - allocator_type get_allocator() const noexcept { - return allocator_type {}; - } + using allocator_type = asio::recycling_allocator; + allocator_type get_allocator() const noexcept { + return allocator_type {}; + } - using executor_type = test_stream_impl::executor_type; - executor_type get_executor() const noexcept { - return _stream_impl->get_executor(); - } + using executor_type = test_stream_impl::executor_type; + executor_type get_executor() const noexcept { + return _stream_impl->get_executor(); + } - template - void perform(const BufferType& buffers) { - if (!_stream_impl->is_open() || !_stream_impl->is_connected()) - return complete_post(asio::error::not_connected, 0); + template + 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 {}) - ); - } + _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); - } + 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_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) - ); - } + 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; + 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 _impl; + std::shared_ptr _impl; public: - explicit test_stream(executor_type ex) : - _impl(std::make_shared(std::move(ex))) - {} + explicit test_stream(executor_type ex) : + _impl(std::make_shared(std::move(ex))) + {} - test_stream(const test_stream&) = delete; - test_stream& operator=(const test_stream&) = delete; + test_stream(const test_stream&) = delete; + test_stream& operator=(const test_stream&) = delete; - ~test_stream() { - error_code ec; - close(ec); // cancel() would be more appropriate - } + ~test_stream() { + error_code ec; + close(ec); // cancel() would be more appropriate + } - executor_type get_executor() const noexcept { - return _impl->get_executor(); - } + executor_type get_executor() const noexcept { + return _impl->get_executor(); + } - void open(const protocol_type& p, error_code& ec) { - _impl->open(p, ec); - } + void open(const protocol_type& p, error_code& ec) { + _impl->open(p, ec); + } - void cancel(error_code& ec) { - _impl->cancel(ec); - } + void cancel(error_code& ec) { + _impl->cancel(ec); + } - void close(error_code& ec) { - _impl->close(ec); - } + void close(error_code& ec) { + _impl->close(ec); + } - void connect(const endpoint_type& ep, error_code& ec) { - _impl->connect(ep, ec); - } + void connect(const endpoint_type& ep, error_code& ec) { + _impl->connect(ep, ec); + } - void disconnect() { - _impl->disconnect(); - } + void disconnect() { + _impl->disconnect(); + } - bool is_open() const { - return _impl->is_open(); - } + bool is_open() const { + return _impl->is_open(); + } - bool is_connected() const { - return _impl->is_connected(); - } + 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); - } + 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); - } + endpoint_type remote_endpoint(error_code& ec) { + return _impl->remote_endpoint(ec); + } - template - void set_option(const SettableSocketOption&, error_code&) {} + template + void set_option(const SettableSocketOption&, error_code&) {} - template - decltype(auto) async_connect( - const endpoint_type& ep, ConnectToken&& token - ) { + template + 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); + auto initiation = [this](auto handler, const endpoint_type& ep) { + error_code ec; + open(asio::ip::tcp::v4(), ec); - if (!ec) - connect(ep, ec); + if (!ec) + connect(ep, ec); - asio::post(get_executor(), asio::prepend(std::move(handler), ec)); - }; + asio::post(get_executor(), asio::prepend(std::move(handler), ec)); + }; - return asio::async_initiate( - std::move(initiation), token, ep - ); - } + return asio::async_initiate( + std::move(initiation), token, ep + ); + } - template - decltype(auto) async_write_some( - const ConstBufferSequence& buffers, WriteToken&& token - ) { - using Signature = void (error_code, size_t); + template + 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); - }; + auto initiation = [this]( + auto handler, const ConstBufferSequence& buffers + ) { + detail::write_op { _impl, std::move(handler) }.perform(buffers); + }; - return asio::async_initiate( - std::move(initiation), token, buffers - ); - } + return asio::async_initiate( + std::move(initiation), token, buffers + ); + } - template - decltype(auto) async_read_some( - const MutableBufferSequence& buffers, - ReadToken&& token - ) { - using Signature = void (error_code, size_t); + template + 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); - }; + auto initiation = [this]( + auto handler, const MutableBufferSequence& buffers + ) { + detail::read_op { _impl, std::move(handler) }.perform(buffers); + }; - return asio::async_initiate( - std::move(initiation), token, buffers - ); - } + return asio::async_initiate( + std::move(initiation), token, buffers + ); + } }; -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test -#endif // ASYNC_MQTT5_TEST_TEST_STREAM_HPP +#endif // BOOST_MQTT5_TEST_TEST_STREAM_HPP diff --git a/test/integration/async_sender.cpp b/test/integration/async_sender.cpp index c9eca3b..2ecc23c 100644 --- a/test/integration/async_sender.cpp +++ b/test/integration/async_sender.cpp @@ -5,413 +5,412 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include +#include +#include + #include #include #include #include #include -#include - -#include -#include -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(async_sender/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string topic = "topic"; - const std::string payload = "payload"; + const std::string topic = "topic"; + const std::string payload = "payload"; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); - std::string connack_rm; + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); + std::string connack_rm; - const std::string publish_qos1 = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - const std::string publish_qos1_dup = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} - ); + const std::string publish_qos1 = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + const std::string publish_qos1_dup = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} + ); - const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); + const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); - shared_test_data() { - connack_props props; - props[prop::receive_maximum] = uint16_t(1); + shared_test_data() { + connack_props props; + props[prop::receive_maximum] = uint16_t(1); - connack_rm = encoders::encode_connack( - false, reason_codes::success.value(), props - ); - } + connack_rm = encoders::encode_connack( + false, reason_codes::success.value(), props + ); + } }; using test::after; using namespace std::chrono_literals; BOOST_FIXTURE_TEST_CASE(publish_ordering_after_reconnect, shared_test_data) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - // packets - auto publish_qos2 = encoders::encode_publish( - 2, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} - ); + // packets + auto publish_qos2 = encoders::encode_publish( + 2, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); - auto pubrec = encoders::encode_pubrec(2, uint8_t(0x00), {}); - auto pubrel = encoders::encode_pubrel(2, uint8_t(0x00), {}); - auto pubcomp = encoders::encode_pubcomp(2, uint8_t(0x00), {}); + auto pubrec = encoders::encode_pubrec(2, uint8_t(0x00), {}); + auto pubrel = encoders::encode_pubrel(2, uint8_t(0x00), {}); + auto pubcomp = encoders::encode_pubcomp(2, uint8_t(0x00), {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1, publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(pubrel, publish_qos1_dup) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1, publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(pubrel, publish_qos1_dup) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, puback, after(2ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - c.async_publish( - topic, payload, retain_e::no, publish_props {}, - [&](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + c.async_publish( + topic, payload, retain_e::no, publish_props {}, + [&](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); - if (handlers_called == expected_handlers_called) - c.cancel(); - } - ); + if (handlers_called == expected_handlers_called) + c.cancel(); + } + ); - c.async_publish( - topic, payload, retain_e::no, publish_props{}, - [&](error_code ec, reason_code rc, pubcomp_props) { - ++handlers_called; + c.async_publish( + topic, payload, retain_e::no, publish_props{}, + [&](error_code ec, reason_code rc, pubcomp_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); - if (handlers_called == expected_handlers_called) - c.cancel(); - } - ); + if (handlers_called == expected_handlers_called) + c.cancel(); + } + ); - ioc.run_for(1s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(1s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(sub_unsub_ordering_after_reconnect, shared_test_data) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - // data - std::vector sub_topics = { - subscribe_topic { "topic", subscribe_options {} } - }; - std::vector sub_reason_codes = { - reason_codes::granted_qos_2.value() - }; - std::vector unsub_topics = { "topic" }; - std::vector unsub_reason_codes = { reason_codes::success.value() }; + // data + std::vector sub_topics = { + subscribe_topic { "topic", subscribe_options {} } + }; + std::vector sub_reason_codes = { + reason_codes::granted_qos_2.value() + }; + std::vector unsub_topics = { "topic" }; + std::vector unsub_reason_codes = { reason_codes::success.value() }; - // packets - auto subscribe = encoders::encode_subscribe( - 1, sub_topics, subscribe_props {} - ); - auto suback = encoders::encode_suback(1, sub_reason_codes, suback_props {}); - auto unsubscribe = encoders::encode_unsubscribe( - 2, unsub_topics, unsubscribe_props {} - ); - auto unsuback = encoders::encode_unsuback(2, unsub_reason_codes, unsuback_props {}); - auto disconnect = encoders::encode_disconnect(0x00, {}); + // packets + auto subscribe = encoders::encode_subscribe( + 1, sub_topics, subscribe_props {} + ); + auto suback = encoders::encode_suback(1, sub_reason_codes, suback_props {}); + auto unsubscribe = encoders::encode_unsubscribe( + 2, unsub_topics, unsubscribe_props {} + ); + auto unsuback = encoders::encode_unsuback(2, unsub_reason_codes, unsuback_props {}); + auto disconnect = encoders::encode_disconnect(0x00, {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe, unsubscribe) - .complete_with(success, after(1ms)) - .send(disconnect, after(50ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe, unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(suback, unsuback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe, unsubscribe) + .complete_with(success, after(1ms)) + .send(disconnect, after(50ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe, unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(suback, unsuback, after(2ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - c.async_subscribe( - sub_topics, subscribe_props {}, - [&](error_code ec, std::vector rcs, suback_props) { - ++handlers_called; + c.async_subscribe( + sub_topics, subscribe_props {}, + [&](error_code ec, std::vector rcs, suback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST_REQUIRE(rcs.size() == 1u); - BOOST_TEST(rcs[0] == reason_codes::granted_qos_2); - } - ); - c.async_unsubscribe( - unsub_topics, unsubscribe_props {}, - [&](error_code ec, std::vector rcs, unsuback_props) { - ++handlers_called; + BOOST_TEST(!ec); + BOOST_TEST_REQUIRE(rcs.size() == 1u); + BOOST_TEST(rcs[0] == reason_codes::granted_qos_2); + } + ); + c.async_unsubscribe( + unsub_topics, unsubscribe_props {}, + [&](error_code ec, std::vector rcs, unsuback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST_REQUIRE(rcs.size() == 1u); - BOOST_TEST(rcs[0] == reason_codes::success); + BOOST_TEST(!ec); + BOOST_TEST_REQUIRE(rcs.size() == 1u); + BOOST_TEST(rcs[0] == reason_codes::success); - c.cancel(); - } - ); + c.cancel(); + } + ); - ioc.run_for(1s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(1s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(throttling, shared_test_data) { - constexpr int expected_handlers_called = 3; - int handlers_called = 0; + constexpr int expected_handlers_called = 3; + int handlers_called = 0; - // packets - auto publish_1 = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - auto publish_2 = encoders::encode_publish( - 2, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - auto publish_3 = encoders::encode_publish( - 3, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); + // packets + auto publish_1 = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + auto publish_2 = encoders::encode_publish( + 2, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + auto publish_3 = encoders::encode_publish( + 3, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); - auto puback_1 = encoders::encode_puback(1, uint8_t(0x00), {}); - auto puback_2 = encoders::encode_puback(2, uint8_t(0x00), {}); - auto puback_3 = encoders::encode_puback(3, uint8_t(0x00), {}); + auto puback_1 = encoders::encode_puback(1, uint8_t(0x00), {}); + auto puback_2 = encoders::encode_puback(2, uint8_t(0x00), {}); + auto puback_3 = encoders::encode_puback(3, uint8_t(0x00), {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack_rm, after(2ms)) - .expect(publish_1) - .complete_with(success, after(1ms)) - .reply_with(puback_1, after(2ms)) - .expect(publish_2) - .complete_with(success, after(1ms)) - .reply_with(puback_2, after(2ms)) - .expect(publish_3) - .complete_with(success, after(1ms)) - .reply_with(puback_3, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack_rm, after(2ms)) + .expect(publish_1) + .complete_with(success, after(1ms)) + .reply_with(puback_1, after(2ms)) + .expect(publish_2) + .complete_with(success, after(1ms)) + .reply_with(puback_2, after(2ms)) + .expect(publish_3) + .complete_with(success, after(1ms)) + .reply_with(puback_3, after(2ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1") - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1") + .async_run(asio::detached); - for (auto i = 0; i < 3; i++) - c.async_publish( - topic, payload, retain_e::no, publish_props {}, - [&c, &handlers_called, i](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + for (auto i = 0; i < 3; i++) + c.async_publish( + topic, payload, retain_e::no, publish_props {}, + [&c, &handlers_called, i](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - BOOST_TEST(handlers_called == i + 1); + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + BOOST_TEST(handlers_called == i + 1); - if (i == 2) - c.cancel(); - } - ); + if (i == 2) + c.cancel(); + } + ); - ioc.run_for(1s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(1s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(throttling_ordering, shared_test_data) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - // packets - auto publish_qos0 = encoders::encode_publish( - 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); + // packets + auto publish_qos0 = encoders::encode_publish( + 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1, publish_qos0) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1, publish_qos0) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - c.async_publish( - topic, payload, retain_e::no, publish_props {}, - [&](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + c.async_publish( + topic, payload, retain_e::no, publish_props {}, + [&](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); - if (handlers_called == expected_handlers_called) - c.cancel(); - } - ); + if (handlers_called == expected_handlers_called) + c.cancel(); + } + ); - c.async_publish( - topic, payload, retain_e::no, publish_props{}, - [&](error_code ec) { - ++handlers_called; + c.async_publish( + topic, payload, retain_e::no, publish_props{}, + [&](error_code ec) { + ++handlers_called; - BOOST_TEST(!ec); + BOOST_TEST(!ec); - if (handlers_called == expected_handlers_called) - c.cancel(); - } - ); + if (handlers_called == expected_handlers_called) + c.cancel(); + } + ); - ioc.run_for(1s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(1s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(prioritize_disconnect, shared_test_data) { - constexpr int expected_handlers_called = 3; - int handlers_called = 0; + constexpr int expected_handlers_called = 3; + int handlers_called = 0; - // data - std::vector sub_topics = { - subscribe_topic { "topic", subscribe_options {} } - }; - std::vector reason_codes = { uint8_t(0x00) }; + // data + std::vector sub_topics = { + subscribe_topic { "topic", subscribe_options {} } + }; + std::vector reason_codes = { uint8_t(0x00) }; - // packets - auto disconnect = encoders::encode_disconnect(uint8_t(0x00), {}); - auto subscribe = encoders::encode_subscribe( - 1, sub_topics, subscribe_props {} - ); - auto suback = encoders::encode_suback(1, reason_codes, suback_props {}); + // packets + auto disconnect = encoders::encode_disconnect(uint8_t(0x00), {}); + auto subscribe = encoders::encode_subscribe( + 1, sub_topics, subscribe_props {} + ); + auto suback = encoders::encode_suback(1, reason_codes, suback_props {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) // first one goes alone - .complete_with(success, after(3ms)) - .expect(disconnect) // disconnect overrides a subscribe request - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) // first one goes alone + .complete_with(success, after(3ms)) + .expect(disconnect) // disconnect overrides a subscribe request + .complete_with(success, after(1ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - // give time to establish a connection - asio::steady_timer timer(executor); - timer.expires_after(100ms); - timer.async_wait([&](error_code) { - c.async_publish( - topic, payload, retain_e::no, publish_props {}, - [&](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + // give time to establish a connection + asio::steady_timer timer(executor); + timer.expires_after(100ms); + timer.async_wait([&](error_code) { + c.async_publish( + topic, payload, retain_e::no, publish_props {}, + [&](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST(rc == reason_codes::empty); - } - ); + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST(rc == reason_codes::empty); + } + ); - c.async_subscribe( - sub_topics, subscribe_props {}, - [&](error_code ec, std::vector rcs, suback_props) { - ++handlers_called; + c.async_subscribe( + sub_topics, subscribe_props {}, + [&](error_code ec, std::vector rcs, suback_props) { + ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1u); - BOOST_TEST(rcs[0] == reason_codes::empty); - } - ); + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST_REQUIRE(rcs.size() == 1u); + BOOST_TEST(rcs[0] == reason_codes::empty); + } + ); - c.async_disconnect([&](error_code ec) { - ++handlers_called; - BOOST_TEST(!ec); - }); - }); + c.async_disconnect([&](error_code ec) { + ++handlers_called; + BOOST_TEST(!ec); + }); + }); - ioc.run_for(2s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(2s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/cancellation.cpp b/test/integration/cancellation.cpp index b21947a..4208349 100644 --- a/test/integration/cancellation.cpp +++ b/test/integration/cancellation.cpp @@ -5,273 +5,271 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include - -#include - #include #include #include #include #include #include +#include #include #include -#include +#include +#include -#include +#include #include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; -namespace async_mqtt5::test { +namespace boost::mqtt5::test { enum operation_type { - async_run = 1, - publish, - receive, - subscribe, - unsubscribe, - disconnect + async_run = 1, + publish, + receive, + subscribe, + unsubscribe, + disconnect }; enum cancel_type { - client_cancel = 1, - signal_emit + client_cancel = 1, + signal_emit }; -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test using stream_type = asio::ip::tcp::socket; using client_type = mqtt_client; template < - test::operation_type op_type, - std::enable_if_t = true + test::operation_type op_type, + std::enable_if_t = true > void setup_cancel_op_test_case( - client_type& c, asio::cancellation_signal& signal, int& handlers_called + client_type& c, asio::cancellation_signal& signal, int& handlers_called ) { - c.async_run( - asio::bind_cancellation_slot( - signal.slot(), - [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ) - ); + c.async_run( + asio::bind_cancellation_slot( + signal.slot(), + [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ) + ); } template < - test::operation_type op_type, - std::enable_if_t = true + test::operation_type op_type, + std::enable_if_t = true > void setup_cancel_op_test_case( - client_type& c, asio::cancellation_signal& signal, int& handlers_called + client_type& c, asio::cancellation_signal& signal, int& handlers_called ) { - c.async_run(asio::detached); - c.async_publish("topic", "payload", retain_e::no, publish_props {}, - asio::bind_cancellation_slot( - signal.slot(), - [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ) - ); + c.async_run(asio::detached); + c.async_publish("topic", "payload", retain_e::no, publish_props {}, + asio::bind_cancellation_slot( + signal.slot(), + [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ) + ); } template < - test::operation_type op_type, - std::enable_if_t = true + test::operation_type op_type, + std::enable_if_t = true > void setup_cancel_op_test_case( - client_type& c, asio::cancellation_signal& signal, int& handlers_called + client_type& c, asio::cancellation_signal& signal, int& handlers_called ) { - c.async_run(asio::detached); - c.async_receive( - asio::bind_cancellation_slot( - signal.slot(), - [&c, &handlers_called]( - error_code ec, std::string t, std::string p, publish_props - ) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST(t == ""); - BOOST_TEST(p == ""); + c.async_run(asio::detached); + c.async_receive( + asio::bind_cancellation_slot( + signal.slot(), + [&c, &handlers_called]( + error_code ec, std::string t, std::string p, publish_props + ) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST(t == ""); + BOOST_TEST(p == ""); - // right now, emitting a terminal signal on async_receive - // does NOT cancel the client - c.cancel(); - } - ) - ); + // right now, emitting a terminal signal on async_receive + // does NOT cancel the client + c.cancel(); + } + ) + ); } template < - test::operation_type op_type, - std::enable_if_t = true + test::operation_type op_type, + std::enable_if_t = true > void setup_cancel_op_test_case( - client_type& c, asio::cancellation_signal& signal, int& handlers_called + client_type& c, asio::cancellation_signal& signal, int& handlers_called ) { - c.async_run(asio::detached); - c.async_subscribe( - subscribe_topic { "topic", subscribe_options {} }, subscribe_props {}, - asio::bind_cancellation_slot( - signal.slot(), - [&handlers_called]( - error_code ec, std::vector rcs, suback_props - ) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1u); - BOOST_TEST(rcs[0] == reason_codes::empty); - } - ) - ); + c.async_run(asio::detached); + c.async_subscribe( + subscribe_topic { "topic", subscribe_options {} }, subscribe_props {}, + asio::bind_cancellation_slot( + signal.slot(), + [&handlers_called]( + error_code ec, std::vector rcs, suback_props + ) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST_REQUIRE(rcs.size() == 1u); + BOOST_TEST(rcs[0] == reason_codes::empty); + } + ) + ); } template < - test::operation_type op_type, - std::enable_if_t = true + test::operation_type op_type, + std::enable_if_t = true > void setup_cancel_op_test_case( - client_type& c, asio::cancellation_signal& signal, int& handlers_called + client_type& c, asio::cancellation_signal& signal, int& handlers_called ) { - c.async_run(asio::detached); - c.async_unsubscribe( - "topic" ,unsubscribe_props {}, - asio::bind_cancellation_slot( - signal.slot(), - [&handlers_called]( - error_code ec, std::vector rcs, unsuback_props - ) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1u); - BOOST_TEST(rcs[0] == reason_codes::empty); - } - ) - ); + c.async_run(asio::detached); + c.async_unsubscribe( + "topic" ,unsubscribe_props {}, + asio::bind_cancellation_slot( + signal.slot(), + [&handlers_called]( + error_code ec, std::vector rcs, unsuback_props + ) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST_REQUIRE(rcs.size() == 1u); + BOOST_TEST(rcs[0] == reason_codes::empty); + } + ) + ); } template < - test::operation_type op_type, - std::enable_if_t = true + test::operation_type op_type, + std::enable_if_t = true > void setup_cancel_op_test_case( - client_type& c, asio::cancellation_signal& signal, int& handlers_called + client_type& c, asio::cancellation_signal& signal, int& handlers_called ) { - c.async_run(asio::detached); - c.async_disconnect( - asio::bind_cancellation_slot( - signal.slot(), - [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ) - ); + c.async_run(asio::detached); + c.async_disconnect( + asio::bind_cancellation_slot( + signal.slot(), + [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ) + ); } template void run_cancel_op_test() { - using namespace test; + using namespace test; - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - asio::cancellation_signal signal; - client_type c(ioc); - c.brokers("127.0.0.1"); + asio::io_context ioc; + asio::cancellation_signal signal; + client_type c(ioc); + c.brokers("127.0.0.1"); - setup_cancel_op_test_case(c, signal, handlers_called); + setup_cancel_op_test_case(c, signal, handlers_called); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(std::chrono::milliseconds(100)); - timer.async_wait([&](error_code) { - if constexpr (c_type == client_cancel) - c.cancel(); - else if constexpr (c_type == signal_emit) - signal.emit(asio::cancellation_type_t::terminal); - }); + asio::steady_timer timer(c.get_executor()); + timer.expires_after(std::chrono::milliseconds(100)); + timer.async_wait([&](error_code) { + if constexpr (c_type == client_cancel) + c.cancel(); + else if constexpr (c_type == signal_emit) + signal.emit(asio::cancellation_type_t::terminal); + }); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_SUITE(cancellation/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(client_cancel_async_run) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(signal_emit_async_run) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(client_cancel_async_publish) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(signal_emit_async_publish) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(client_cancel_async_receive) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(signal_emit_async_receive) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(client_cancel_async_subscribe) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(signal_emit_async_subscribe) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(client_cancel_async_unsubscribe) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(signal_emit_async_unsubscribe) { - run_cancel_op_test(); + run_cancel_op_test(); } BOOST_AUTO_TEST_CASE(signal_emit_async_disconnect) { - run_cancel_op_test(); + run_cancel_op_test(); } struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); - const std::string topic = "topic"; - const std::string payload = "payload"; - const publish_props pub_props {}; + const std::string topic = "topic"; + const std::string payload = "payload"; + const publish_props pub_props {}; - const std::string publish_qos1 = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); + const std::string publish_qos1 = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); - const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); + const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); }; using test::after; @@ -282,70 +280,70 @@ using namespace std::chrono_literals; constexpr auto use_nothrow_awaitable = asio::as_tuple(asio::use_awaitable); BOOST_FIXTURE_TEST_CASE(rerunning_the_client, shared_test_data) { - // packets - auto disconnect = encoders::encode_disconnect(uint8_t(0x00), {}); + // packets + auto disconnect = encoders::encode_disconnect(uint8_t(0x00), {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)) - .expect(disconnect); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)) + .expect(disconnect); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - co_spawn(ioc, - [&]() -> asio::awaitable { - mqtt_client c(ioc); - c.brokers("127.0.0.1,127.0.0.1", 1883) // to avoid reconnect backoff - .async_run(asio::detached); + co_spawn(ioc, + [&]() -> asio::awaitable { + mqtt_client c(ioc); + c.brokers("127.0.0.1,127.0.0.1", 1883) // to avoid reconnect backoff + .async_run(asio::detached); - // Note: Older versions of GCC compilers may not handle temporaries - // correctly in co_await expressions. - // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98401) - auto [ec, rc, props] = co_await c.async_publish( - topic, payload, retain_e::no, pub_props, use_nothrow_awaitable - ); - BOOST_TEST(!ec); - BOOST_TEST(!rc); + // Note: Older versions of GCC compilers may not handle temporaries + // correctly in co_await expressions. + // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98401) + auto [ec, rc, props] = co_await c.async_publish( + topic, payload, retain_e::no, pub_props, use_nothrow_awaitable + ); + BOOST_TEST(!ec); + BOOST_TEST(!rc); - c.cancel(); + c.cancel(); - auto [cec, crc, cprops] = co_await c.async_publish( - topic, payload, retain_e::no, pub_props, use_nothrow_awaitable - ); - BOOST_TEST(cec == asio::error::operation_aborted); - BOOST_TEST(crc == reason_codes::empty); + auto [cec, crc, cprops] = co_await c.async_publish( + topic, payload, retain_e::no, pub_props, use_nothrow_awaitable + ); + BOOST_TEST(cec == asio::error::operation_aborted); + BOOST_TEST(crc == reason_codes::empty); - c.async_run(asio::detached); + c.async_run(asio::detached); - auto [rec, rrc, rprops] = co_await c.async_publish( - topic, payload, retain_e::no, pub_props, use_nothrow_awaitable - ); - BOOST_TEST(!rec); - BOOST_TEST(!rrc); + auto [rec, rrc, rprops] = co_await c.async_publish( + topic, payload, retain_e::no, pub_props, use_nothrow_awaitable + ); + BOOST_TEST(!rec); + BOOST_TEST(!rrc); - co_await c.async_disconnect(use_nothrow_awaitable); - co_return; - }, - asio::detached - ); + co_await c.async_disconnect(use_nothrow_awaitable); + co_return; + }, + asio::detached + ); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } #endif diff --git a/test/integration/client.cpp b/test/integration/client.cpp index e1f0239..37c75c0 100644 --- a/test/integration/client.cpp +++ b/test/integration/client.cpp @@ -10,240 +10,238 @@ #include #ifdef BOOST_ASIO_HAS_CO_AWAIT -#include +#include +#include #include #include #include #include -#include #include - -#include +#include #include // async_teardown specialization for websocket ssl stream - +#include #include -#include -#include +#include -namespace async_mqtt5 { +namespace boost::mqtt5 { template struct tls_handshake_type> { - static constexpr auto client = asio::ssl::stream_base::client; - static constexpr auto server = asio::ssl::stream_base::server; + static constexpr auto client = asio::ssl::stream_base::client; + static constexpr auto server = asio::ssl::stream_base::server; }; template void assign_tls_sni( - const authority_path& ap, - asio::ssl::context& /* ctx */, - asio::ssl::stream& stream + const authority_path& ap, + asio::ssl::context& /* ctx */, + asio::ssl::stream& stream ) { - SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); + SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); } -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 BOOST_AUTO_TEST_SUITE(client/*, *boost::unit_test::disabled()*/) -using namespace async_mqtt5; +using namespace boost::mqtt5; namespace asio = boost::asio; constexpr auto use_nothrow_awaitable = asio::as_tuple(asio::use_awaitable); template asio::awaitable test_client(mqtt_client& c) { - // Note: Older versions of GCC compilers may not handle temporaries - // correctly in co_await expressions. - // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98401) - publish_props pub_props; - auto [ec_0] = co_await c.template async_publish( - "test/mqtt-test", "hello world with qos0!", retain_e::yes, pub_props, - use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_0); + // Note: Older versions of GCC compilers may not handle temporaries + // correctly in co_await expressions. + // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98401) + publish_props pub_props; + auto [ec_0] = co_await c.template async_publish( + "test/mqtt-test", "hello world with qos0!", retain_e::yes, pub_props, + use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_0); - auto [ec_1, puback_rc, puback_props] = co_await c.template async_publish( - "test/mqtt-test", "hello world with qos1!", - retain_e::yes, pub_props, - use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_1); - BOOST_TEST_WARN(!puback_rc); + auto [ec_1, puback_rc, puback_props] = co_await c.template async_publish( + "test/mqtt-test", "hello world with qos1!", + retain_e::yes, pub_props, + use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_1); + BOOST_TEST_WARN(!puback_rc); - auto [ec_2, pubcomp_rc, pubcomp_props] = co_await c.template async_publish( - "test/mqtt-test", "hello world with qos2!", - retain_e::yes, pub_props, - use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_2); - BOOST_TEST_WARN(!pubcomp_rc); + auto [ec_2, pubcomp_rc, pubcomp_props] = co_await c.template async_publish( + "test/mqtt-test", "hello world with qos2!", + retain_e::yes, pub_props, + use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_2); + BOOST_TEST_WARN(!pubcomp_rc); - subscribe_topic sub_topic = subscribe_topic { - "test/mqtt-test", async_mqtt5::subscribe_options { - qos_e::at_least_once, - no_local_e::no, - retain_as_published_e::retain, - retain_handling_e::send - } - }; - - subscribe_props sub_props; - auto [sub_ec, sub_codes, suback_props] = co_await c.async_subscribe( - sub_topic, sub_props, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!sub_ec); - if (!sub_codes[0]) - auto [rec, topic, payload, publish_props] = co_await c.async_receive(use_nothrow_awaitable); + subscribe_topic sub_topic = subscribe_topic { + "test/mqtt-test", boost::mqtt5::subscribe_options { + qos_e::at_least_once, + no_local_e::no, + retain_as_published_e::retain, + retain_handling_e::send + } + }; + + subscribe_props sub_props; + auto [sub_ec, sub_codes, suback_props] = co_await c.async_subscribe( + sub_topic, sub_props, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!sub_ec); + if (!sub_codes[0]) + auto [rec, topic, payload, publish_props] = co_await c.async_receive(use_nothrow_awaitable); - unsubscribe_props unsub_props; - auto [unsub_ec, unsub_codes, unsuback_props] = co_await c.async_unsubscribe( - "test/mqtt-test", unsub_props, - use_nothrow_awaitable - ); - BOOST_TEST_WARN(!unsub_ec); - BOOST_TEST_WARN(!unsub_codes[0]); + unsubscribe_props unsub_props; + auto [unsub_ec, unsub_codes, unsuback_props] = co_await c.async_unsubscribe( + "test/mqtt-test", unsub_props, + use_nothrow_awaitable + ); + BOOST_TEST_WARN(!unsub_ec); + BOOST_TEST_WARN(!unsub_codes[0]); - co_await c.async_disconnect(use_nothrow_awaitable); - co_return; + co_await c.async_disconnect(use_nothrow_awaitable); + co_return; } BOOST_AUTO_TEST_CASE(tcp_client_check) { - asio::io_context ioc; + asio::io_context ioc; - using stream_type = asio::ip::tcp::socket; - using client_type = mqtt_client; - client_type c(ioc); + using stream_type = asio::ip::tcp::socket; + using client_type = mqtt_client; + client_type c(ioc); - c.brokers("broker.hivemq.com", 1883) - .async_run(asio::detached); + c.brokers("broker.hivemq.com", 1883) + .async_run(asio::detached); - asio::steady_timer timer(ioc); - timer.expires_after(std::chrono::seconds(5)); + asio::steady_timer timer(ioc); + timer.expires_after(std::chrono::seconds(5)); - timer.async_wait( - [&](boost::system::error_code ec) { - BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); - c.cancel(); - } - ); + timer.async_wait( + [&](boost::system::error_code ec) { + BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); + c.cancel(); + } + ); - co_spawn(ioc, - [&]() -> asio::awaitable { - co_await test_client(c); - timer.cancel(); - }, - asio::detached - ); + co_spawn(ioc, + [&]() -> asio::awaitable { + co_await test_client(c); + timer.cancel(); + }, + asio::detached + ); - ioc.run(); + ioc.run(); } BOOST_AUTO_TEST_CASE(websocket_tcp_client_check) { - asio::io_context ioc; + asio::io_context ioc; - using stream_type = boost::beast::websocket::stream< - asio::ip::tcp::socket - >; + using stream_type = boost::beast::websocket::stream< + asio::ip::tcp::socket + >; - using client_type = mqtt_client; - client_type c(ioc); + using client_type = mqtt_client; + client_type c(ioc); - c.brokers("broker.hivemq.com/mqtt", 8000) - .async_run(asio::detached); + c.brokers("broker.hivemq.com/mqtt", 8000) + .async_run(asio::detached); - asio::steady_timer timer(ioc); - timer.expires_after(std::chrono::seconds(5)); + asio::steady_timer timer(ioc); + timer.expires_after(std::chrono::seconds(5)); - timer.async_wait( - [&](boost::system::error_code ec) { - BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); - c.cancel(); - } - ); + timer.async_wait( + [&](boost::system::error_code ec) { + BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); + c.cancel(); + } + ); - co_spawn(ioc, - [&]() -> asio::awaitable { - co_await test_client(c); - timer.cancel(); - }, - asio::detached - ); + co_spawn(ioc, + [&]() -> asio::awaitable { + co_await test_client(c); + timer.cancel(); + }, + asio::detached + ); - ioc.run(); + ioc.run(); } BOOST_AUTO_TEST_CASE(openssl_tls_client_check) { - asio::io_context ioc; + asio::io_context ioc; - using stream_type = asio::ssl::stream; - asio::ssl::context tls_context(asio::ssl::context::tls_client); + using stream_type = asio::ssl::stream; + asio::ssl::context tls_context(asio::ssl::context::tls_client); - using client_type = mqtt_client; - client_type c(ioc, std::move(tls_context)); + using client_type = mqtt_client; + client_type c(ioc, std::move(tls_context)); - c.brokers("broker.hivemq.com", 8883) - .async_run(asio::detached); + c.brokers("broker.hivemq.com", 8883) + .async_run(asio::detached); - asio::steady_timer timer(ioc); - timer.expires_after(std::chrono::seconds(5)); + asio::steady_timer timer(ioc); + timer.expires_after(std::chrono::seconds(5)); - timer.async_wait( - [&](boost::system::error_code ec) { - BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); - c.cancel(); - } - ); + timer.async_wait( + [&](boost::system::error_code ec) { + BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); + c.cancel(); + } + ); - co_spawn(ioc, - [&]() -> asio::awaitable { - co_await test_client(c); - timer.cancel(); - }, - asio::detached - ); + co_spawn(ioc, + [&]() -> asio::awaitable { + co_await test_client(c); + timer.cancel(); + }, + asio::detached + ); - ioc.run(); + ioc.run(); } BOOST_AUTO_TEST_CASE(websocket_tls_client_check) { - asio::io_context ioc; + asio::io_context ioc; - using stream_type = boost::beast::websocket::stream< - asio::ssl::stream - >; + using stream_type = boost::beast::websocket::stream< + asio::ssl::stream + >; - asio::ssl::context tls_context(asio::ssl::context::tls_client); + asio::ssl::context tls_context(asio::ssl::context::tls_client); - using client_type = mqtt_client; - client_type c(ioc, std::move(tls_context)); + using client_type = mqtt_client; + client_type c(ioc, std::move(tls_context)); - c.brokers("broker.hivemq.com/mqtt", 8884) - .async_run(asio::detached); + c.brokers("broker.hivemq.com/mqtt", 8884) + .async_run(asio::detached); - asio::steady_timer timer(ioc); - timer.expires_after(std::chrono::seconds(5)); + asio::steady_timer timer(ioc); + timer.expires_after(std::chrono::seconds(5)); - timer.async_wait( - [&](boost::system::error_code ec) { - BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); - c.cancel(); - } - ); + timer.async_wait( + [&](boost::system::error_code ec) { + BOOST_TEST_WARN(ec, "Failed to receive all the expected replies!"); + c.cancel(); + } + ); - co_spawn(ioc, - [&]() -> asio::awaitable { - co_await test_client(c); - timer.cancel(); - }, - asio::detached - ); + co_spawn(ioc, + [&]() -> asio::awaitable { + co_await test_client(c); + timer.cancel(); + }, + asio::detached + ); - ioc.run(); + ioc.run(); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/integration/client_functions.cpp b/test/integration/client_functions.cpp index 5319a68..74c16c8 100644 --- a/test/integration/client_functions.cpp +++ b/test/integration/client_functions.cpp @@ -5,459 +5,457 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include + +#include +#include +#include +#include +#include #include +#include #include #include #include #include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" #include "test_common/test_authenticators.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; using namespace std::chrono_literals; BOOST_AUTO_TEST_SUITE(client_functions/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; + error_code success {}; - const std::string connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); + const std::string connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); }; using client_type = mqtt_client; template void run_test( - test::msg_exchange broker_side, TestingClientFun&& client_fun + test::msg_exchange broker_side, TestingClientFun&& client_fun ) { - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - client_type c(executor); - client_fun(c); - c.brokers("127.0.0.1") - .async_run(asio::detached); + client_type c(executor); + client_fun(c); + c.brokers("127.0.0.1") + .async_run(asio::detached); - asio::steady_timer timer(executor); - timer.expires_after(700ms); - timer.async_wait( - [&c](error_code) { c.cancel(); } - ); + asio::steady_timer timer(executor); + timer.expires_after(700ms); + timer.async_wait( + [&c](error_code) { c.cancel(); } + ); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } using test::after; BOOST_AUTO_TEST_CASE(create_client_with_executor) { - asio::io_context ioc; - client_type c(ioc.get_executor()); - BOOST_CHECK(c.get_executor() == ioc.get_executor()); + asio::io_context ioc; + client_type c(ioc.get_executor()); + BOOST_CHECK(c.get_executor() == ioc.get_executor()); } BOOST_AUTO_TEST_CASE(create_client_with_execution_context) { - asio::io_context ioc; - client_type c(ioc); - BOOST_CHECK(c.get_executor() == ioc.get_executor()); + asio::io_context ioc; + client_type c(ioc); + BOOST_CHECK(c.get_executor() == ioc.get_executor()); } void assign_tls_context() { - // Tests if the tls_context function compiles + // Tests if the tls_context function compiles - asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); + asio::io_context ioc; + asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > tls_client(ioc.get_executor(), std::move(ctx)); - tls_client.tls_context(); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > tls_client(ioc.get_executor(), std::move(ctx)); + tls_client.tls_context(); } BOOST_FIXTURE_TEST_CASE(assign_credentials, shared_test_data) { - std::string client_id = "client_id"; - std::string username = "username"; - std::string password = "password"; + std::string client_id = "client_id"; + std::string username = "username"; + std::string password = "password"; - auto connect = encoders::encode_connect( - client_id, username, password, 60, false, {}, std::nullopt - ); + auto connect = encoders::encode_connect( + client_id, username, password, 60, false, {}, std::nullopt + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test( - std::move(broker_side), - [&](client_type& c) { - c.credentials(client_id, username, password); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c) { + c.credentials(client_id, username, password); + } + ); } void assign_credentials_tls_client() { - // Tests if the assign credentials function compiles + // Tests if the assign credentials function compiles - std::string client_id = "client_id"; - std::string username = "username"; - std::string password = "password"; + std::string client_id = "client_id"; + std::string username = "username"; + std::string password = "password"; - asio::io_context ioc; + asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); - ts.credentials(client_id, username, password); + ts.credentials(client_id, username, password); } BOOST_FIXTURE_TEST_CASE(assign_will, shared_test_data) { - will w("topic", "message"); - std::optional will_opt { std::move(w) }; + will w("topic", "message"); + std::optional will_opt { std::move(w) }; - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, will_opt - ); + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, will_opt + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test( - std::move(broker_side), - [](client_type& c) { - // because copying is deleted - will w("topic", "message"); - will w_moved = std::move(w); // move assignment coverage - c.will(std::move(w_moved)); - } - ); + run_test( + std::move(broker_side), + [](client_type& c) { + // because copying is deleted + will w("topic", "message"); + will w_moved = std::move(w); // move assignment coverage + c.will(std::move(w_moved)); + } + ); } void assign_authenticator() { - // Tests if the authenticator function compiles + // Tests if the authenticator function compiles - asio::io_context ioc; - client_type c(ioc); - c.authenticator(test::test_authenticator()); + asio::io_context ioc; + client_type c(ioc); + c.authenticator(test::test_authenticator()); } void assign_authenticator_tls_client() { - // Tests if the authenticator function compiles + // Tests if the authenticator function compiles - asio::io_context ioc; + asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); - ts.authenticator(test::test_authenticator()); + ts.authenticator(test::test_authenticator()); } BOOST_FIXTURE_TEST_CASE(assign_keep_alive, shared_test_data) { - uint16_t keep_alive = 120; + uint16_t keep_alive = 120; - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, keep_alive, false, {}, std::nullopt - ); + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, keep_alive, false, {}, std::nullopt + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test( - std::move(broker_side), - [&](client_type& c) { - c.keep_alive(keep_alive); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c) { + c.keep_alive(keep_alive); + } + ); } struct shared_connect_prop_test_data : shared_test_data { - const int session_expiry_interval = 40; - const int16_t receive_maximum = 10123; - const int maximum_packet_size = 1024; - const uint16_t topic_alias_maximum = 12345; - const uint8_t request_response_information = 1; - const uint8_t request_problem_information = 0; - const std::vector> user_properties = - { { "key", "val" } }; + const int session_expiry_interval = 40; + const int16_t receive_maximum = 10123; + const int maximum_packet_size = 1024; + const uint16_t topic_alias_maximum = 12345; + const uint8_t request_response_information = 1; + const uint8_t request_problem_information = 0; + const std::vector> user_properties = + { { "key", "val" } }; - connect_props cprops = create_connect_props(); + connect_props cprops = create_connect_props(); private: - connect_props create_connect_props() { - connect_props ret_props; - ret_props[prop::session_expiry_interval] = session_expiry_interval; - ret_props[prop::receive_maximum] = receive_maximum; - ret_props[prop::maximum_packet_size] = maximum_packet_size; - ret_props[prop::topic_alias_maximum] = topic_alias_maximum; - ret_props[prop::request_response_information] = request_response_information; - ret_props[prop::request_problem_information] = request_problem_information; - ret_props[prop::user_property] = user_properties; - return ret_props; - } + connect_props create_connect_props() { + connect_props ret_props; + ret_props[prop::session_expiry_interval] = session_expiry_interval; + ret_props[prop::receive_maximum] = receive_maximum; + ret_props[prop::maximum_packet_size] = maximum_packet_size; + ret_props[prop::topic_alias_maximum] = topic_alias_maximum; + ret_props[prop::request_response_information] = request_response_information; + ret_props[prop::request_problem_information] = request_problem_information; + ret_props[prop::user_property] = user_properties; + return ret_props; + } }; BOOST_FIXTURE_TEST_CASE(connect_properties, shared_connect_prop_test_data) { - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, cprops, std::nullopt - ); + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, cprops, std::nullopt + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test( - std::move(broker_side), - [&](client_type& c) { - c.connect_properties(cprops); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c) { + c.connect_properties(cprops); + } + ); } BOOST_FIXTURE_TEST_CASE(connect_property, shared_connect_prop_test_data) { - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, cprops, std::nullopt - ); + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, cprops, std::nullopt + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test( - std::move(broker_side), - [&](client_type& c) { - c.connect_property(prop::session_expiry_interval, session_expiry_interval); - c.connect_property(prop::receive_maximum, receive_maximum); - c.connect_property(prop::maximum_packet_size, maximum_packet_size); - c.connect_property(prop::topic_alias_maximum, topic_alias_maximum); - c.connect_property(prop::request_response_information, request_response_information); - c.connect_property(prop::request_problem_information, request_problem_information); - c.connect_property(prop::user_property, user_properties); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c) { + c.connect_property(prop::session_expiry_interval, session_expiry_interval); + c.connect_property(prop::receive_maximum, receive_maximum); + c.connect_property(prop::maximum_packet_size, maximum_packet_size); + c.connect_property(prop::topic_alias_maximum, topic_alias_maximum); + c.connect_property(prop::request_response_information, request_response_information); + c.connect_property(prop::request_problem_information, request_problem_information); + c.connect_property(prop::user_property, user_properties); + } + ); } BOOST_FIXTURE_TEST_CASE(connect_properties_tls_client, shared_connect_prop_test_data) { - // Tests if the connect_properties function compiles + // Tests if the connect_properties function compiles - asio::io_context ioc; + asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); - ts.connect_properties(cprops); + ts.connect_properties(cprops); } BOOST_FIXTURE_TEST_CASE(connect_property_tls_client, shared_connect_prop_test_data) { - // Tests if the connect_property functions compile + // Tests if the connect_property functions compile - asio::io_context ioc; + asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); - ts.connect_property(prop::session_expiry_interval, session_expiry_interval); - ts.connect_property(prop::receive_maximum, receive_maximum); - ts.connect_property(prop::maximum_packet_size, maximum_packet_size); - ts.connect_property(prop::topic_alias_maximum, topic_alias_maximum); - ts.connect_property(prop::request_response_information, request_response_information); - ts.connect_property(prop::request_problem_information, request_problem_information); - ts.connect_property(prop::user_property, user_properties); + ts.connect_property(prop::session_expiry_interval, session_expiry_interval); + ts.connect_property(prop::receive_maximum, receive_maximum); + ts.connect_property(prop::maximum_packet_size, maximum_packet_size); + ts.connect_property(prop::topic_alias_maximum, topic_alias_maximum); + ts.connect_property(prop::request_response_information, request_response_information); + ts.connect_property(prop::request_problem_information, request_problem_information); + ts.connect_property(prop::user_property, user_properties); } struct shared_connack_prop_test_data { - error_code success {}; + error_code success {}; - // data - const uint8_t session_present = 1; + // data + const uint8_t session_present = 1; - const int32_t session_expiry_interval = 20; - const int16_t receive_maximum = 2000; - const uint8_t max_qos = 2; - const uint8_t retain_available = 0; - const int32_t maximum_packet_sz = 1024; - const std::string assigned_client_id = "client_id"; - const uint16_t topic_alias_max = 128; - const std::string reason_string = "reason string"; - const std::vector> user_properties = - { { "key", "val" } }; - const uint8_t wildcard_sub = 1; - const uint8_t sub_id = 1; - const uint8_t shared_sub = 0; - const int16_t server_keep_alive = 25; - const std::string response_information = "info"; - const std::string server_reference = "server reference"; - const std::string authentication_method = "method"; - const std::string authentication_data = "data"; + const int32_t session_expiry_interval = 20; + const int16_t receive_maximum = 2000; + const uint8_t max_qos = 2; + const uint8_t retain_available = 0; + const int32_t maximum_packet_sz = 1024; + const std::string assigned_client_id = "client_id"; + const uint16_t topic_alias_max = 128; + const std::string reason_string = "reason string"; + const std::vector> user_properties = + { { "key", "val" } }; + const uint8_t wildcard_sub = 1; + const uint8_t sub_id = 1; + const uint8_t shared_sub = 0; + const int16_t server_keep_alive = 25; + const std::string response_information = "info"; + const std::string server_reference = "server reference"; + const std::string authentication_method = "method"; + const std::string authentication_data = "data"; - connack_props cprops = create_connack_props(); + connack_props cprops = create_connack_props(); - // packets - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - false, reason_codes::success.value(), cprops - ); + // packets + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + false, reason_codes::success.value(), cprops + ); private: - connack_props create_connack_props() { - connack_props ret_props; - ret_props[prop::session_expiry_interval] = session_expiry_interval; - ret_props[prop::receive_maximum] = receive_maximum; - ret_props[prop::maximum_qos] = max_qos; - ret_props[prop::retain_available] = retain_available; - ret_props[prop::maximum_packet_size] = maximum_packet_sz; - ret_props[prop::assigned_client_identifier] = assigned_client_id; - ret_props[prop::topic_alias_maximum] = topic_alias_max; - ret_props[prop::reason_string] = reason_string; - ret_props[prop::user_property] = user_properties; - ret_props[prop::wildcard_subscription_available] = wildcard_sub; - ret_props[prop::subscription_identifier_available] = sub_id; - ret_props[prop::shared_subscription_available] = shared_sub; - ret_props[prop::server_keep_alive] = server_keep_alive; - ret_props[prop::response_information] = response_information; - ret_props[prop::server_reference] = server_reference; - ret_props[prop::authentication_method] = authentication_method; - ret_props[prop::authentication_data] = authentication_data; - return ret_props; - } + connack_props create_connack_props() { + connack_props ret_props; + ret_props[prop::session_expiry_interval] = session_expiry_interval; + ret_props[prop::receive_maximum] = receive_maximum; + ret_props[prop::maximum_qos] = max_qos; + ret_props[prop::retain_available] = retain_available; + ret_props[prop::maximum_packet_size] = maximum_packet_sz; + ret_props[prop::assigned_client_identifier] = assigned_client_id; + ret_props[prop::topic_alias_maximum] = topic_alias_max; + ret_props[prop::reason_string] = reason_string; + ret_props[prop::user_property] = user_properties; + ret_props[prop::wildcard_subscription_available] = wildcard_sub; + ret_props[prop::subscription_identifier_available] = sub_id; + ret_props[prop::shared_subscription_available] = shared_sub; + ret_props[prop::server_keep_alive] = server_keep_alive; + ret_props[prop::response_information] = response_information; + ret_props[prop::server_reference] = server_reference; + ret_props[prop::authentication_method] = authentication_method; + ret_props[prop::authentication_data] = authentication_data; + return ret_props; + } }; template void run_test_with_post_fun( - test::msg_exchange broker_side, TestingClientFun&& client_fun + test::msg_exchange broker_side, TestingClientFun&& client_fun ) { - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - client_type c(executor); - c.brokers("127.0.0.1") - .async_run(asio::detached); + client_type c(executor); + c.brokers("127.0.0.1") + .async_run(asio::detached); - asio::steady_timer timer(executor); - timer.expires_after(700ms); - timer.async_wait( - [&c, fun = std::forward(client_fun)](error_code) { - fun(c); - c.cancel(); - } - ); + asio::steady_timer timer(executor); + timer.expires_after(700ms); + timer.async_wait( + [&c, fun = std::forward(client_fun)](error_code) { + fun(c); + c.cancel(); + } + ); - ioc.run(); + ioc.run(); - BOOST_TEST(broker.received_all_expected()); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(connack_properties, shared_connack_prop_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test_with_post_fun( - std::move(broker_side), - [&](client_type& c) { - connack_props cprops_ = c.connack_properties(); - cprops_.visit([&](const auto& p, const auto& val) -> bool { - BOOST_TEST_REQUIRE(p); - if constexpr (detail::is_vector) - BOOST_TEST(val == cprops[p]); - else { - BOOST_TEST_REQUIRE(val.has_value()); - BOOST_TEST(*val == *cprops[p]); - } - return true; - }); - } - ); + run_test_with_post_fun( + std::move(broker_side), + [&](client_type& c) { + connack_props cprops_ = c.connack_properties(); + cprops_.visit([&](const auto& p, const auto& val) -> bool { + BOOST_TEST_REQUIRE(p); + if constexpr (detail::is_vector) + BOOST_TEST(val == cprops[p]); + else { + BOOST_TEST_REQUIRE(val.has_value()); + BOOST_TEST(*val == *cprops[p]); + } + return true; + }); + } + ); } BOOST_FIXTURE_TEST_CASE(connack_property, shared_connack_prop_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test_with_post_fun( - std::move(broker_side), - [&](client_type& c) { - cprops.visit([&](const auto& p, const auto& val) -> bool{ - if constexpr (detail::is_vector) - BOOST_TEST(val == c.connack_property(p)); - else { - BOOST_TEST_REQUIRE(val.has_value()); - BOOST_TEST(*val == *c.connack_property(p)); - } - return true; - }); - } - ); + run_test_with_post_fun( + std::move(broker_side), + [&](client_type& c) { + cprops.visit([&](const auto& p, const auto& val) -> bool{ + if constexpr (detail::is_vector) + BOOST_TEST(val == c.connack_property(p)); + else { + BOOST_TEST_REQUIRE(val.has_value()); + BOOST_TEST(*val == *c.connack_property(p)); + } + return true; + }); + } + ); } void connack_property_with_tls_client() { - // Tests if the connack_properties & connack_property functions compile + // Tests if the connack_properties & connack_property functions compile - asio::io_context ioc; + asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); - auto cprops_ = ts.connack_properties(); - cprops_.visit([&ts](const auto& p, auto&) -> bool { - using ptype = boost::remove_cv_ref_t; - [[maybe_unused]] prop::value_type_t value = ts.connack_property(p); - return true; - }); + auto cprops_ = ts.connack_properties(); + cprops_.visit([&ts](const auto& p, auto&) -> bool { + using ptype = boost::remove_cv_ref_t; + [[maybe_unused]] prop::value_type_t value = ts.connack_property(p); + return true; + }); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/disconnect.cpp b/test/integration/disconnect.cpp index 6b4b8da..a087aa7 100644 --- a/test/integration/disconnect.cpp +++ b/test/integration/disconnect.cpp @@ -5,39 +5,38 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include +#include #include #include #include -#include -#include -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/test_broker.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(disconnect/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - true, reason_codes::success.value(), {} - ); - const std::string disconnect = encoders::encode_disconnect( - reason_codes::normal_disconnection.value(), disconnect_props {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); + const std::string disconnect = encoders::encode_disconnect( + reason_codes::normal_disconnection.value(), disconnect_props {} + ); }; using test::after; @@ -46,270 +45,270 @@ using client_type = mqtt_client; template void run_test(test::msg_exchange broker_side, TestCase&& test_case) { - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - asio::steady_timer timer(executor); - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + asio::steady_timer timer(executor); + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - test_case(c, timer); + test_case(c, timer); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(successful_disconnect, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(disconnect) - .complete_with(success, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(disconnect) + .complete_with(success, after(0ms)); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer& timer) { - timer.expires_after(100ms); - timer.async_wait([&](error_code) { - c.async_disconnect( - [&](error_code ec) { - handlers_called++; - BOOST_TEST(!ec); - } - ); - }); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer& timer) { + timer.expires_after(100ms); + timer.async_wait([&](error_code) { + c.async_disconnect( + [&](error_code ec) { + handlers_called++; + BOOST_TEST(!ec); + } + ); + }); + } + ); - BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_FIXTURE_TEST_CASE(successful_disconnect_in_queue, shared_test_data) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - // packets - auto publish_qos0 = encoders::encode_publish( - 0, "topic", "payload", qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); + // packets + auto publish_qos0 = encoders::encode_publish( + 0, "topic", "payload", qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos0) - .complete_with(success, after(1ms)) - .expect(disconnect) - .complete_with(success, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos0) + .complete_with(success, after(1ms)) + .expect(disconnect) + .complete_with(success, after(0ms)); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer& timer) { - timer.expires_after(50ms); - timer.async_wait([&](error_code) { - c.async_publish( - "topic", "payload", retain_e::no, {}, - [&handlers_called](error_code ec) { - BOOST_TEST(handlers_called == 0); - handlers_called++; - BOOST_TEST(!ec); - } - ); - c.async_disconnect( - [&](error_code ec) { - BOOST_TEST(handlers_called == 1); - handlers_called++; - BOOST_TEST(!ec); - } - ); - }); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer& timer) { + timer.expires_after(50ms); + timer.async_wait([&](error_code) { + c.async_publish( + "topic", "payload", retain_e::no, {}, + [&handlers_called](error_code ec) { + BOOST_TEST(handlers_called == 0); + handlers_called++; + BOOST_TEST(!ec); + } + ); + c.async_disconnect( + [&](error_code ec) { + BOOST_TEST(handlers_called == 1); + handlers_called++; + BOOST_TEST(!ec); + } + ); + }); + } + ); - BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_FIXTURE_TEST_CASE(disconnect_on_disconnected_client, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .expect(connect); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .expect(connect); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer& timer) { - timer.expires_after(50ms); - timer.async_wait([&](error_code) { - c.async_disconnect( - [&](error_code ec) { - handlers_called++; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ); - }); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer& timer) { + timer.expires_after(50ms); + timer.async_wait([&](error_code) { + c.async_disconnect( + [&](error_code ec) { + handlers_called++; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ); + }); + } + ); - BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_FIXTURE_TEST_CASE(disconnect_in_queue_on_disconnected_client, shared_test_data) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .expect(connect); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .expect(connect); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer& timer) { - timer.expires_after(50ms); - timer.async_wait([&](error_code) { - c.async_publish( - "topic", "payload", retain_e::no, {}, - [&handlers_called](error_code ec) { - BOOST_TEST(handlers_called == 1); - handlers_called++; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ); - c.async_disconnect( - [&](error_code ec) { - BOOST_TEST(handlers_called == 0); - handlers_called++; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ); - }); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer& timer) { + timer.expires_after(50ms); + timer.async_wait([&](error_code) { + c.async_publish( + "topic", "payload", retain_e::no, {}, + [&handlers_called](error_code ec) { + BOOST_TEST(handlers_called == 1); + handlers_called++; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ); + c.async_disconnect( + [&](error_code ec) { + BOOST_TEST(handlers_called == 0); + handlers_called++; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ); + }); + } + ); - BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_FIXTURE_TEST_CASE(resend_terminal_disconnect, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(disconnect) - .complete_with(success, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(disconnect) + .complete_with(success, after(0ms)); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer&) { - c.async_disconnect( - [&](error_code ec) { - handlers_called++; - BOOST_TEST(!ec); - } - ); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer&) { + c.async_disconnect( + [&](error_code ec) { + handlers_called++; + BOOST_TEST(!ec); + } + ); + } + ); - BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_FIXTURE_TEST_CASE(dont_resend_non_terminal_disconnect, shared_test_data) { - auto malformed_publish_1 = encoders::encode_publish( - 1, "malformed topic", "malformed payload", - static_cast(0b11), retain_e::yes, dup_e::no, {} - ); - auto malformed_publish_2 = encoders::encode_publish( - 2, "malformed topic", "malformed payload", - static_cast(0b11), retain_e::yes, dup_e::no, {} - ); + auto malformed_publish_1 = encoders::encode_publish( + 1, "malformed topic", "malformed payload", + static_cast(0b11), retain_e::yes, dup_e::no, {} + ); + auto malformed_publish_2 = encoders::encode_publish( + 2, "malformed topic", "malformed payload", + static_cast(0b11), retain_e::yes, dup_e::no, {} + ); - auto disconnect_malformed_publish = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBLISH received: QoS bits set to 0b11") - ); + auto disconnect_malformed_publish = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBLISH received: QoS bits set to 0b11") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(malformed_publish_1, malformed_publish_2, after(10ms)) - .expect(disconnect_malformed_publish) - .complete_with(success, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(malformed_publish_1, malformed_publish_2, after(10ms)) + .expect(disconnect_malformed_publish) + .complete_with(success, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer& timer) { - timer.expires_after(50ms); - timer.async_wait([&](error_code) { - c.cancel(); - }); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer& timer) { + timer.expires_after(50ms); + timer.async_wait([&](error_code) { + c.cancel(); + }); + } + ); } BOOST_FIXTURE_TEST_CASE(omit_props, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - connack_props co_props; - co_props[prop::maximum_packet_size] = 20; + connack_props co_props; + co_props[prop::maximum_packet_size] = 20; - // packets - auto connack_with_max_packet = encoders::encode_connack( - false, reason_codes::success.value(), co_props - ); + // packets + auto connack_with_max_packet = encoders::encode_connack( + false, reason_codes::success.value(), co_props + ); - disconnect_props props; - props[prop::reason_string] = std::string(50, 'a'); - auto big_disconnect = encoders::encode_disconnect( - reason_codes::normal_disconnection.value(), props - ); + disconnect_props props; + props[prop::reason_string] = std::string(50, 'a'); + auto big_disconnect = encoders::encode_disconnect( + reason_codes::normal_disconnection.value(), props + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack_with_max_packet, after(0ms)) - .expect(disconnect) - .complete_with(success, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack_with_max_packet, after(0ms)) + .expect(disconnect) + .complete_with(success, after(0ms)); - run_test( - std::move(broker_side), - [&](client_type& c, asio::steady_timer& timer) { - timer.expires_after(50ms); - timer.async_wait([&](error_code) { - c.async_disconnect( - disconnect_rc_e::normal_disconnection, props, - [&](error_code ec) { - handlers_called++; - BOOST_TEST(!ec); - } - ); - }); - } - ); + run_test( + std::move(broker_side), + [&](client_type& c, asio::steady_timer& timer) { + timer.expires_after(50ms); + timer.async_wait([&](error_code) { + c.async_disconnect( + disconnect_rc_e::normal_disconnection, props, + [&](error_code ec) { + handlers_called++; + BOOST_TEST(!ec); + } + ); + }); + } + ); - BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/integration/executors.cpp b/test/integration/executors.cpp index 04bb93c..4ac0d1c 100644 --- a/test/integration/executors.cpp +++ b/test/integration/executors.cpp @@ -5,30 +5,27 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include - -#include -#include -#include -#include +#include #include #include #include #include -#include -#include - #include +#include +#include +#include +#include -#include - -#include +#include +#include +#include +#include #include "test_common/message_exchange.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; using strand_type = asio::strand; @@ -36,294 +33,294 @@ BOOST_AUTO_TEST_SUITE(executors) template void run_test( - asio::io_context& ioc, strand_type io_ex, - AsyncRunOp&& bind_async_run, AsyncOp&& bind_async_op + asio::io_context& ioc, strand_type io_ex, + AsyncRunOp&& bind_async_run, AsyncOp&& bind_async_op ) { - using test::after; - using namespace std::chrono_literals; + using test::after; + using namespace std::chrono_literals; - constexpr int expected_handlers_called = 8; - int handlers_called = 0; + constexpr int expected_handlers_called = 8; + int handlers_called = 0; - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); - auto publish_0 = encoders::encode_publish( - 0, "t_0", "p_0", qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); - auto publish_1 = encoders::encode_publish( - 1, "t_1", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); - auto publish_2 = encoders::encode_publish( - 2, "t_2", "p_2", qos_e::exactly_once, retain_e::no, dup_e::no, {} - ); - auto pubrec = encoders::encode_pubrec(2, reason_codes::success.value(), {}); - auto pubrel = encoders::encode_pubrel(2, reason_codes::success.value(), {}); - auto pubcomp = encoders::encode_pubcomp(2, reason_codes::success.value(), {}); - auto subscribe = encoders::encode_subscribe( - 3, std::vector { { "t_0", {} } }, {} - ); - auto suback = encoders::encode_suback( - 3, std::vector { reason_codes::granted_qos_2.value() }, {} - ); - auto unsubscribe = encoders::encode_unsubscribe( - 1, std::vector { "t_0" }, {} - ); - auto unsuback = encoders::encode_unsuback( - 1, std::vector { reason_codes::success.value() }, {} - ); - auto disconnect = encoders::encode_disconnect( - reason_codes::normal_disconnection.value(), {} - ); + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, reason_codes::success.value(), {}); + auto publish_0 = encoders::encode_publish( + 0, "t_0", "p_0", qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); + auto publish_1 = encoders::encode_publish( + 1, "t_1", "p_1", qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + auto puback = encoders::encode_puback(1, reason_codes::success.value(), {}); + auto publish_2 = encoders::encode_publish( + 2, "t_2", "p_2", qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); + auto pubrec = encoders::encode_pubrec(2, reason_codes::success.value(), {}); + auto pubrel = encoders::encode_pubrel(2, reason_codes::success.value(), {}); + auto pubcomp = encoders::encode_pubcomp(2, reason_codes::success.value(), {}); + auto subscribe = encoders::encode_subscribe( + 3, std::vector { { "t_0", {} } }, {} + ); + auto suback = encoders::encode_suback( + 3, std::vector { reason_codes::granted_qos_2.value() }, {} + ); + auto unsubscribe = encoders::encode_unsubscribe( + 1, std::vector { "t_0" }, {} + ); + auto unsuback = encoders::encode_unsuback( + 1, std::vector { reason_codes::success.value() }, {} + ); + auto disconnect = encoders::encode_disconnect( + reason_codes::normal_disconnection.value(), {} + ); - test::msg_exchange broker_side; - error_code success {}; + test::msg_exchange broker_side; + error_code success {}; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(subscribe, publish_0, publish_1, publish_2) - .complete_with(success, after(0ms)) - .reply_with(puback, pubrec, suback, after(0ms)) - .expect(pubrel) - .complete_with(success, after(0ms)) - .reply_with(pubcomp, after(0ms)) - .send(publish_0, after(50ms)) - .expect(unsubscribe) - .complete_with(success, after(0ms)) - .reply_with(unsuback, after(0ms)) - .expect(disconnect) - .complete_with(success, after(0ms)) - ; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(subscribe, publish_0, publish_1, publish_2) + .complete_with(success, after(0ms)) + .reply_with(puback, pubrec, suback, after(0ms)) + .expect(pubrel) + .complete_with(success, after(0ms)) + .reply_with(pubcomp, after(0ms)) + .send(publish_0, after(50ms)) + .expect(unsubscribe) + .complete_with(success, after(0ms)) + .reply_with(unsuback, after(0ms)) + .expect(disconnect) + .complete_with(success, after(0ms)) + ; - auto& broker = asio::make_service( - ioc, io_ex, std::move(broker_side) - ); + auto& broker = asio::make_service( + ioc, io_ex, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(io_ex); - c.brokers("127.0.0.1") - .async_run( - bind_async_run( - [&](strand_type strand, error_code ec) { - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; - } - ) - ); + using client_type = mqtt_client; + client_type c(io_ex); + c.brokers("127.0.0.1") + .async_run( + bind_async_run( + [&](strand_type strand, error_code ec) { + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; + } + ) + ); - c.async_publish( - "t_0", "p_0", retain_e::no, {}, - bind_async_op( - [&](strand_type strand, error_code ec) { - BOOST_TEST(!ec); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; - } - ) - ); + c.async_publish( + "t_0", "p_0", retain_e::no, {}, + bind_async_op( + [&](strand_type strand, error_code ec) { + BOOST_TEST(!ec); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; + } + ) + ); - c.async_publish( - "t_1", "p_1", retain_e::no, {}, - bind_async_op( - [&](strand_type strand, error_code ec, reason_code rc, auto) { - BOOST_TEST(!ec); - BOOST_TEST(!rc); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; - } - ) - ); + c.async_publish( + "t_1", "p_1", retain_e::no, {}, + bind_async_op( + [&](strand_type strand, error_code ec, reason_code rc, auto) { + BOOST_TEST(!ec); + BOOST_TEST(!rc); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; + } + ) + ); - c.async_publish( - "t_2", "p_2", retain_e::no, {}, - bind_async_op( - [&](strand_type strand, error_code ec, reason_code rc, auto) { - BOOST_TEST(!ec); - BOOST_TEST(!rc); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; - } - ) - ); + c.async_publish( + "t_2", "p_2", retain_e::no, {}, + bind_async_op( + [&](strand_type strand, error_code ec, reason_code rc, auto) { + BOOST_TEST(!ec); + BOOST_TEST(!rc); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; + } + ) + ); - c.async_subscribe( - subscribe_topic { "t_0", {} }, {}, - bind_async_op( - [&]( - strand_type strand, - error_code ec, std::vector rcs, auto - ) { - BOOST_TEST(!ec); - BOOST_TEST(!rcs[0]); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; - } - ) - ); + c.async_subscribe( + subscribe_topic { "t_0", {} }, {}, + bind_async_op( + [&]( + strand_type strand, + error_code ec, std::vector rcs, auto + ) { + BOOST_TEST(!ec); + BOOST_TEST(!rcs[0]); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; + } + ) + ); - c.async_receive( - bind_async_op( - [&]( - strand_type strand, error_code ec, - std::string rec_topic, std::string rec_payload, - publish_props - ) { - BOOST_TEST(!ec); - BOOST_TEST("t_0" == rec_topic); - BOOST_TEST("p_0" == rec_payload); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; + c.async_receive( + bind_async_op( + [&]( + strand_type strand, error_code ec, + std::string rec_topic, std::string rec_payload, + publish_props + ) { + BOOST_TEST(!ec); + BOOST_TEST("t_0" == rec_topic); + BOOST_TEST("p_0" == rec_payload); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; - c.async_unsubscribe( - "t_0", {}, - bind_async_op( - [&]( - strand_type strand, - error_code ec, std::vector rcs, auto - ) { - BOOST_TEST(!ec); - BOOST_TEST(!rcs[0]); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; + c.async_unsubscribe( + "t_0", {}, + bind_async_op( + [&]( + strand_type strand, + error_code ec, std::vector rcs, auto + ) { + BOOST_TEST(!ec); + BOOST_TEST(!rcs[0]); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; - c.async_disconnect( - bind_async_op( - [&](strand_type strand, error_code ec) { - BOOST_TEST(!ec); - BOOST_TEST(strand.running_in_this_thread()); - ++handlers_called; - } - ) - ); - } - ) - ); - } - ) - ); + c.async_disconnect( + bind_async_op( + [&](strand_type strand, error_code ec) { + BOOST_TEST(!ec); + BOOST_TEST(strand.running_in_this_thread()); + ++handlers_called; + } + ) + ); + } + ) + ); + } + ) + ); - ioc.run_for(500ms); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(500ms); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_AUTO_TEST_CASE(different_bound_executors) { - asio::io_context ioc; - auto bind_async_op = [&](auto h) { - auto strand = asio::make_strand(ioc); - return asio::bind_executor( - strand, - asio::prepend(std::move(h), strand) - ); - }; - run_test(ioc, asio::make_strand(ioc), bind_async_op, bind_async_op); + asio::io_context ioc; + auto bind_async_op = [&](auto h) { + auto strand = asio::make_strand(ioc); + return asio::bind_executor( + strand, + asio::prepend(std::move(h), strand) + ); + }; + run_test(ioc, asio::make_strand(ioc), bind_async_op, bind_async_op); } BOOST_AUTO_TEST_CASE(default_executor) { - asio::io_context ioc; - auto io_ex = asio::make_strand(ioc); - auto bind_async_run = [&](auto h) { - auto strand = asio::make_strand(ioc); - return asio::bind_executor( - strand, - asio::prepend(std::move(h), strand) - ); - }; - auto bind_async_op = [&](auto h) { - return asio::prepend(std::move(h), io_ex); - }; - run_test(ioc, io_ex, bind_async_run, bind_async_op); + asio::io_context ioc; + auto io_ex = asio::make_strand(ioc); + auto bind_async_run = [&](auto h) { + auto strand = asio::make_strand(ioc); + return asio::bind_executor( + strand, + asio::prepend(std::move(h), strand) + ); + }; + auto bind_async_op = [&](auto h) { + return asio::prepend(std::move(h), io_ex); + }; + run_test(ioc, io_ex, bind_async_run, bind_async_op); } BOOST_AUTO_TEST_CASE(immediate_executor_async_publish) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; + asio::io_context ioc; - using client_type = mqtt_client; - client_type c(ioc.get_executor()); + using client_type = mqtt_client; + client_type c(ioc.get_executor()); - auto strand = asio::make_strand(ioc); + auto strand = asio::make_strand(ioc); - auto handler = asio::bind_immediate_executor( - strand, - [&handlers_called, &strand](error_code ec) { - ++handlers_called; - BOOST_TEST(strand.running_in_this_thread()); - BOOST_TEST(ec); - } - ); - c.async_publish( - "invalid/#", "", retain_e::no, publish_props {}, std::move(handler) - ); + auto handler = asio::bind_immediate_executor( + strand, + [&handlers_called, &strand](error_code ec) { + ++handlers_called; + BOOST_TEST(strand.running_in_this_thread()); + BOOST_TEST(ec); + } + ); + c.async_publish( + "invalid/#", "", retain_e::no, publish_props {}, std::move(handler) + ); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_CASE(immediate_executor_async_subscribe) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; + asio::io_context ioc; - using client_type = mqtt_client; - client_type c(ioc.get_executor()); + using client_type = mqtt_client; + client_type c(ioc.get_executor()); - auto strand = asio::make_strand(ioc); + auto strand = asio::make_strand(ioc); - auto handler = asio::bind_immediate_executor( - strand, - [&handlers_called, &strand]( - error_code ec, std::vector, suback_props - ) { - ++handlers_called; - BOOST_TEST(strand.running_in_this_thread()); - BOOST_TEST(ec); - } - ); - c.async_subscribe( - { "+topic", subscribe_options {} }, subscribe_props{}, std::move(handler) - ); + auto handler = asio::bind_immediate_executor( + strand, + [&handlers_called, &strand]( + error_code ec, std::vector, suback_props + ) { + ++handlers_called; + BOOST_TEST(strand.running_in_this_thread()); + BOOST_TEST(ec); + } + ); + c.async_subscribe( + { "+topic", subscribe_options {} }, subscribe_props{}, std::move(handler) + ); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_CASE(immediate_executor_async_unsubscribe) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; + asio::io_context ioc; - using client_type = mqtt_client; - client_type c(ioc.get_executor()); + using client_type = mqtt_client; + client_type c(ioc.get_executor()); - auto strand = asio::make_strand(ioc); + auto strand = asio::make_strand(ioc); - auto handler = asio::bind_immediate_executor( - strand, - [&handlers_called, &strand]( - error_code ec, std::vector, unsuback_props - ) { - ++handlers_called; - BOOST_TEST(strand.running_in_this_thread()); - BOOST_TEST(ec); - } - ); - c.async_unsubscribe( - "some/topic#", unsubscribe_props {}, std::move(handler) - ); + auto handler = asio::bind_immediate_executor( + strand, + [&handlers_called, &strand]( + error_code ec, std::vector, unsuback_props + ) { + ++handlers_called; + BOOST_TEST(strand.running_in_this_thread()); + BOOST_TEST(ec); + } + ); + c.async_unsubscribe( + "some/topic#", unsubscribe_props {}, std::move(handler) + ); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); } diff --git a/test/integration/mqtt_features.cpp b/test/integration/mqtt_features.cpp index 0d67ce6..c3825e6 100644 --- a/test/integration/mqtt_features.cpp +++ b/test/integration/mqtt_features.cpp @@ -10,27 +10,26 @@ #include #ifdef BOOST_ASIO_HAS_CO_AWAIT -#include -#include -#include +#include +#include #include #include #include -#include -#include -#include - #include - +#include +#include +#include #include -#include -#include +#include +#include +#include + BOOST_AUTO_TEST_SUITE(mqtt_features/*, *boost::unit_test::disabled()*/) -using namespace async_mqtt5; +using namespace boost::mqtt5; namespace asio = boost::asio; constexpr auto use_nothrow_awaitable = asio::as_tuple(asio::use_awaitable); @@ -47,149 +46,149 @@ constexpr auto payload = "hello from async-mqtt5"; template void run_test(TestCase&& test_case) { - using namespace asio::experimental::awaitable_operators; + using namespace asio::experimental::awaitable_operators; - asio::io_context ioc; - co_spawn( - ioc, - [&ioc, test_case = std::forward(test_case)]() -> asio::awaitable { - asio::steady_timer test_timer(ioc, test_duration); - co_await(test_case() || test_timer.async_wait(use_nothrow_awaitable)); - }, - asio::detached - ); - ioc.run(); + asio::io_context ioc; + co_spawn( + ioc, + [&ioc, test_case = std::forward(test_case)]() -> asio::awaitable { + asio::steady_timer test_timer(ioc, test_duration); + co_await(test_case() || test_timer.async_wait(use_nothrow_awaitable)); + }, + asio::detached + ); + ioc.run(); } asio::awaitable test_manual_use_topic_alias() { - auto ex = co_await asio::this_coro::executor; + auto ex = co_await asio::this_coro::executor; - mqtt_client client(ex); - client.brokers(broker, 8000) - .connect_property(prop::topic_alias_maximum, uint16_t(10)) - .async_run(asio::detached); + mqtt_client client(ex); + client.brokers(broker, 8000) + .connect_property(prop::topic_alias_maximum, uint16_t(10)) + .async_run(asio::detached); - asio::steady_timer connect_timer(ex, connect_wait_dur); - co_await connect_timer.async_wait(use_nothrow_awaitable); + asio::steady_timer connect_timer(ex, connect_wait_dur); + co_await connect_timer.async_wait(use_nothrow_awaitable); - uint16_t topic_alias = 1; - publish_props pprops; - pprops[prop::topic_alias] = topic_alias; + uint16_t topic_alias = 1; + publish_props pprops; + pprops[prop::topic_alias] = topic_alias; - auto&& [ec_1, rc_1, _] = co_await client.async_publish( - topic, payload, retain_e::no, pprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_1); - BOOST_TEST_WARN(!rc_1); + auto&& [ec_1, rc_1, _] = co_await client.async_publish( + topic, payload, retain_e::no, pprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_1); + BOOST_TEST_WARN(!rc_1); - auto&& [ec_2, rc_2, __] = co_await client.async_publish( - "", payload, retain_e::no, pprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_2); - BOOST_TEST_WARN(!rc_2); + auto&& [ec_2, rc_2, __] = co_await client.async_publish( + "", payload, retain_e::no, pprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_2); + BOOST_TEST_WARN(!rc_2); } BOOST_AUTO_TEST_CASE(manual_use_topic_alias) { - run_test(test_manual_use_topic_alias); + run_test(test_manual_use_topic_alias); } asio::awaitable test_subscription_identifiers() { - auto ex = co_await asio::this_coro::executor; + auto ex = co_await asio::this_coro::executor; - mqtt_client client(ex); - client.brokers(broker, 8000) - .async_run(asio::detached); + mqtt_client client(ex); + client.brokers(broker, 8000) + .async_run(asio::detached); - publish_props pprops; - auto&& [ec_1, rc_1, _] = co_await client.async_publish( - topic, payload, retain_e::yes, pprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_1); - BOOST_TEST_WARN(!rc_1); + publish_props pprops; + auto&& [ec_1, rc_1, _] = co_await client.async_publish( + topic, payload, retain_e::yes, pprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_1); + BOOST_TEST_WARN(!rc_1); - int32_t sub_id = 123; - subscribe_props sprops; - sprops[prop::subscription_identifier] = sub_id; + int32_t sub_id = 123; + subscribe_props sprops; + sprops[prop::subscription_identifier] = sub_id; - subscribe_options sub_opts = { .no_local = no_local_e::no }; - subscribe_topic sub_topic = { topic, sub_opts }; - auto&& [ec_2, rcs, __] = co_await client.async_subscribe( - sub_topic, sprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_2); - BOOST_TEST_WARN(!rcs[0]); + subscribe_options sub_opts = { .no_local = no_local_e::no }; + subscribe_topic sub_topic = { topic, sub_opts }; + auto&& [ec_2, rcs, __] = co_await client.async_subscribe( + sub_topic, sprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_2); + BOOST_TEST_WARN(!rcs[0]); - auto&& [ec_3, rec_topic, rec_payload, rec_props] = - co_await client.async_receive(use_nothrow_awaitable); - BOOST_TEST_WARN(!ec_3); - BOOST_TEST_WARN(rec_topic == topic); - BOOST_TEST_WARN(rec_payload == payload); - const auto& sub_ids = rec_props[prop::subscription_identifier]; - BOOST_TEST_WARN(!sub_ids.empty()); - if (!sub_ids.empty()) - BOOST_TEST_WARN(sub_ids[0] == sub_id); + auto&& [ec_3, rec_topic, rec_payload, rec_props] = + co_await client.async_receive(use_nothrow_awaitable); + BOOST_TEST_WARN(!ec_3); + BOOST_TEST_WARN(rec_topic == topic); + BOOST_TEST_WARN(rec_payload == payload); + const auto& sub_ids = rec_props[prop::subscription_identifier]; + BOOST_TEST_WARN(!sub_ids.empty()); + if (!sub_ids.empty()) + BOOST_TEST_WARN(sub_ids[0] == sub_id); } BOOST_AUTO_TEST_CASE(subscription_identifiers) { - run_test(test_subscription_identifiers); + run_test(test_subscription_identifiers); } asio::awaitable test_shared_subscription() { - auto ex = co_await asio::this_coro::executor; + auto ex = co_await asio::this_coro::executor; - mqtt_client client(ex); - client.brokers(broker, 8000) - .async_run(asio::detached); + mqtt_client client(ex); + client.brokers(broker, 8000) + .async_run(asio::detached); - subscribe_options sub_opts = { .no_local = no_local_e::no }; - subscribe_topic sub_topic = { share_topic, sub_opts }; - subscribe_props sprops; - auto&& [ec_1, rcs, __] = co_await client.async_subscribe( - sub_topic, sprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_1); - BOOST_TEST_WARN(!rcs[0]); + subscribe_options sub_opts = { .no_local = no_local_e::no }; + subscribe_topic sub_topic = { share_topic, sub_opts }; + subscribe_props sprops; + auto&& [ec_1, rcs, __] = co_await client.async_subscribe( + sub_topic, sprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_1); + BOOST_TEST_WARN(!rcs[0]); - publish_props pprops; - // shared subscriptions do not send Retained Messages on first subscribe - auto&& [ec_2, rc_2, _] = co_await client.async_publish( - topic, payload, retain_e::no, pprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_2); - BOOST_TEST_WARN(!rc_2); + publish_props pprops; + // shared subscriptions do not send Retained Messages on first subscribe + auto&& [ec_2, rc_2, _] = co_await client.async_publish( + topic, payload, retain_e::no, pprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_2); + BOOST_TEST_WARN(!rc_2); - auto&& [ec_3, rec_topic, rec_payload, ___] = - co_await client.async_receive(use_nothrow_awaitable); - BOOST_TEST_WARN(!ec_3); - BOOST_TEST_WARN(rec_topic == topic); - BOOST_TEST_WARN(rec_payload == payload); + auto&& [ec_3, rec_topic, rec_payload, ___] = + co_await client.async_receive(use_nothrow_awaitable); + BOOST_TEST_WARN(!ec_3); + BOOST_TEST_WARN(rec_topic == topic); + BOOST_TEST_WARN(rec_payload == payload); } BOOST_AUTO_TEST_CASE(shared_subscription) { - run_test(test_shared_subscription); + run_test(test_shared_subscription); } asio::awaitable test_user_property() { - auto ex = co_await asio::this_coro::executor; + auto ex = co_await asio::this_coro::executor; - mqtt_client client(ex); - client.brokers(broker, 8000) - .async_run(asio::detached); + mqtt_client client(ex); + client.brokers(broker, 8000) + .async_run(asio::detached); - publish_props pprops; - pprops[prop::user_property].push_back({ "key_1", "value_1" }); - pprops[prop::user_property].push_back({ "key_2", "value_2" }); - pprops[prop::user_property].push_back({ "key_3", "value_3" }); + publish_props pprops; + pprops[prop::user_property].push_back({ "key_1", "value_1" }); + pprops[prop::user_property].push_back({ "key_2", "value_2" }); + pprops[prop::user_property].push_back({ "key_3", "value_3" }); - auto&& [ec_1, rc_1, _] = co_await client.async_publish( - topic, payload, retain_e::no, pprops, use_nothrow_awaitable - ); - BOOST_TEST_WARN(!ec_1); - BOOST_TEST_WARN(!rc_1); + auto&& [ec_1, rc_1, _] = co_await client.async_publish( + topic, payload, retain_e::no, pprops, use_nothrow_awaitable + ); + BOOST_TEST_WARN(!ec_1); + BOOST_TEST_WARN(!rc_1); } BOOST_AUTO_TEST_CASE(user_property) { - run_test(test_user_property); + run_test(test_user_property); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/integration/ping.cpp b/test/integration/ping.cpp index ca3d1ff..6256aa0 100644 --- a/test/integration/ping.cpp +++ b/test/integration/ping.cpp @@ -5,6 +5,11 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include #include #include @@ -13,205 +18,199 @@ #include #include -#include -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(ping/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connack_no_ka = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); + const std::string connack_no_ka = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); - const std::string pingreq = encoders::encode_pingreq(); - const std::string pingresp = encoders::encode_pingresp(); + const std::string pingreq = encoders::encode_pingreq(); + const std::string pingresp = encoders::encode_pingresp(); }; using test::after; using namespace std::chrono_literals; std::string connect_with_keep_alive(uint16_t keep_alive) { - return encoders::encode_connect( - "", std::nullopt, std::nullopt, keep_alive, false, {}, std::nullopt - ); + return encoders::encode_connect( + "", std::nullopt, std::nullopt, keep_alive, false, {}, std::nullopt + ); } std::string connack_with_keep_alive(uint16_t keep_alive) { - connack_props cprops; - cprops[prop::server_keep_alive] = keep_alive; + connack_props cprops; + cprops[prop::server_keep_alive] = keep_alive; - return encoders::encode_connack( - false, reason_codes::success.value(), cprops - ); + return encoders::encode_connack( + false, reason_codes::success.value(), cprops + ); } void run_test( - test::msg_exchange broker_side, - std::chrono::milliseconds cancel_timeout, - uint16_t keep_alive = std::numeric_limits::max() + test::msg_exchange broker_side, + std::chrono::milliseconds cancel_timeout, + uint16_t keep_alive = std::numeric_limits::max() ) { - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .keep_alive(keep_alive) - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .keep_alive(keep_alive) + .async_run(asio::detached); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(cancel_timeout); - timer.async_wait([&c](error_code) { - c.cancel(); - }); + asio::steady_timer timer(c.get_executor()); + timer.expires_after(cancel_timeout); + timer.async_wait([&c](error_code) { + c.cancel(); + }); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(ping_pong_client_ka, shared_test_data) { - // data - uint16_t keep_alive = 1; + // data + uint16_t keep_alive = 1; - test::msg_exchange broker_side; - broker_side - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_no_ka, after(2ms)) - .expect(pingreq) - .complete_with(success, after(1ms)) - .reply_with(pingresp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_no_ka, after(2ms)) + .expect(pingreq) + .complete_with(success, after(1ms)) + .reply_with(pingresp, after(2ms)); - run_test( - std::move(broker_side), - std::chrono::milliseconds(keep_alive * 1000 + 100), - keep_alive - ); + run_test( + std::move(broker_side), + std::chrono::milliseconds(keep_alive * 1000 + 100), + keep_alive + ); } BOOST_FIXTURE_TEST_CASE(ping_pong_server_ka, shared_test_data) { - // data - uint16_t keep_alive = 10; - uint16_t server_keep_alive = 1; + // data + uint16_t keep_alive = 10; + uint16_t server_keep_alive = 1; - test::msg_exchange broker_side; - broker_side - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_with_keep_alive(server_keep_alive), after(2ms)) - .expect(pingreq) - .complete_with(success, after(1ms)) - .reply_with(pingresp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_with_keep_alive(server_keep_alive), after(2ms)) + .expect(pingreq) + .complete_with(success, after(1ms)) + .reply_with(pingresp, after(2ms)); - run_test( - std::move(broker_side), - std::chrono::milliseconds(server_keep_alive * 1000 + 100), - keep_alive - ); + run_test( + std::move(broker_side), + std::chrono::milliseconds(server_keep_alive * 1000 + 100), + keep_alive + ); } BOOST_FIXTURE_TEST_CASE(disable_ping, shared_test_data) { - // data - uint16_t keep_alive = 0; + // data + uint16_t keep_alive = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_no_ka, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_no_ka, after(2ms)); - run_test( - std::move(broker_side), - std::chrono::milliseconds(1000), - keep_alive - ); + run_test( + std::move(broker_side), + std::chrono::milliseconds(1000), + keep_alive + ); } BOOST_FIXTURE_TEST_CASE(ping_timeout, shared_test_data) { - // observation in test cases with a real broker: - // old stream_ptr will receive disconnect with rc: session taken over - // when the new stream_ptr sends a connect packet + // observation in test cases with a real broker: + // old stream_ptr will receive disconnect with rc: session taken over + // when the new stream_ptr sends a connect packet - // data - uint16_t keep_alive = 1; + // data + uint16_t keep_alive = 1; - test::msg_exchange broker_side; - broker_side - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_no_ka, after(2ms)) - .expect(pingreq) - .complete_with(success, after(1ms)) - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_no_ka, after(2ms)) - .expect(pingreq) - .complete_with(success, after(1ms)) - .reply_with(pingresp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_no_ka, after(2ms)) + .expect(pingreq) + .complete_with(success, after(1ms)) + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_no_ka, after(2ms)) + .expect(pingreq) + .complete_with(success, after(1ms)) + .reply_with(pingresp, after(2ms)); - run_test( - std::move(broker_side), - std::chrono::milliseconds(2700), - keep_alive - ); + run_test( + std::move(broker_side), + std::chrono::milliseconds(2700), + keep_alive + ); } BOOST_FIXTURE_TEST_CASE(keep_alive_change_while_waiting, shared_test_data) { - // data - uint16_t keep_alive = 0; - uint16_t server_keep_alive = 1; + // data + uint16_t keep_alive = 0; + uint16_t server_keep_alive = 1; - test::msg_exchange broker_side; - broker_side - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_with_keep_alive(server_keep_alive), after(2ms)) - .expect(pingreq) - .complete_with(success, after(1ms)) - .reply_with(fail, after(2ms)) - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_no_ka, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_with_keep_alive(server_keep_alive), after(2ms)) + .expect(pingreq) + .complete_with(success, after(1ms)) + .reply_with(fail, after(2ms)) + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_no_ka, after(2ms)); - run_test( - std::move(broker_side), - std::chrono::milliseconds(1500), keep_alive - ); + run_test( + std::move(broker_side), + std::chrono::milliseconds(1500), keep_alive + ); } BOOST_FIXTURE_TEST_CASE(keep_alive_change_during_writing, shared_test_data) { - // data - uint16_t keep_alive = 1; - uint16_t server_keep_alive = 1; + // data + uint16_t keep_alive = 1; + uint16_t server_keep_alive = 1; - test::msg_exchange broker_side; - broker_side - .expect(connect_with_keep_alive(keep_alive)) - .complete_with(success, after(1ms)) - .reply_with(connack_with_keep_alive(server_keep_alive), after(1500ms)) - .expect(pingreq) - .complete_with(success, after(1ms)) - .reply_with(pingresp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_with_keep_alive(keep_alive)) + .complete_with(success, after(1ms)) + .reply_with(connack_with_keep_alive(server_keep_alive), after(1500ms)) + .expect(pingreq) + .complete_with(success, after(1ms)) + .reply_with(pingresp, after(2ms)); - run_test( - std::move(broker_side), - std::chrono::milliseconds(2700), keep_alive - ); + run_test( + std::move(broker_side), + std::chrono::milliseconds(2700), keep_alive + ); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/re_authentication.cpp b/test/integration/re_authentication.cpp index aacffce..65c3c53 100644 --- a/test/integration/re_authentication.cpp +++ b/test/integration/re_authentication.cpp @@ -5,6 +5,12 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include +#include #include #include @@ -13,57 +19,50 @@ #include #include -#include -#include -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" #include "test_common/test_authenticators.hpp" #include "test_common/test_broker.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(re_authentication/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, init_connect_props(), std::nullopt - ); - const std::string connack = encoders::encode_connack( - true, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, init_connect_props(), std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); - const std::string auth_challenge = encoders::encode_auth( - reason_codes::reauthenticate.value(), init_auth_props() - ); - const std::string auth_response = encoders::encode_auth( - reason_codes::continue_authentication.value(), init_auth_props() - ); - const std::string auth_success = encoders::encode_auth( - reason_codes::success.value(), init_auth_props() - ); + const std::string auth_challenge = encoders::encode_auth( + reason_codes::reauthenticate.value(), init_auth_props() + ); + const std::string auth_response = encoders::encode_auth( + reason_codes::continue_authentication.value(), init_auth_props() + ); + const std::string auth_success = encoders::encode_auth( + reason_codes::success.value(), init_auth_props() + ); - connect_props init_connect_props() { - connect_props cprops; - cprops[prop::authentication_method] = "method"; - cprops[prop::authentication_data] = ""; - return cprops; - } + connect_props init_connect_props() { + connect_props cprops; + cprops[prop::authentication_method] = "method"; + cprops[prop::authentication_data] = ""; + return cprops; + } - auth_props init_auth_props() { - auth_props aprops; - aprops[prop::authentication_method] = "method"; - aprops[prop::authentication_data] = ""; - return aprops; - } + auth_props init_auth_props() { + auth_props aprops; + aprops[prop::authentication_method] = "method"; + aprops[prop::authentication_data] = ""; + return aprops; + } }; using test::after; @@ -71,195 +70,195 @@ using namespace std::chrono_literals; template void run_test( - test::msg_exchange broker_side, Authenticator&& authenticator = {} + test::msg_exchange broker_side, Authenticator&& authenticator = {} ) { - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1"); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1"); - if constexpr (!std::is_same_v) - c.authenticator(std::forward(authenticator)); + if constexpr (!std::is_same_v) + c.authenticator(std::forward(authenticator)); - c.async_run(asio::detached); + c.async_run(asio::detached); - asio::steady_timer timer(c.get_executor()); - // wait until the connection is established - timer.expires_after(20ms); - timer.async_wait([&](error_code) { - c.re_authenticate(); + asio::steady_timer timer(c.get_executor()); + // wait until the connection is established + timer.expires_after(20ms); + timer.async_wait([&](error_code) { + c.re_authenticate(); - timer.expires_after(150ms); - timer.async_wait([&c](error_code) { c.cancel(); }); - }); + timer.expires_after(150ms); + timer.async_wait([&c](error_code) { c.cancel(); }); + }); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(successful_re_auth, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .expect(auth_challenge) - .complete_with(success, after(2ms)) - .reply_with(auth_success, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(auth_success, after(4ms)); - run_test(std::move(broker_side), test::test_authenticator()); + run_test(std::move(broker_side), test::test_authenticator()); } BOOST_FIXTURE_TEST_CASE(successful_re_auth_multi_step, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .expect(auth_challenge) - .complete_with(success, after(2ms)) - .reply_with(auth_response, after(4ms)) - .expect(auth_response) - .complete_with(success, after(2ms)) - .reply_with(auth_success, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(auth_response, after(4ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(auth_success, after(4ms)); - run_test(std::move(broker_side), test::test_authenticator()); + run_test(std::move(broker_side), test::test_authenticator()); } BOOST_FIXTURE_TEST_CASE(malformed_auth_rc, shared_test_data) { - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed AUTH received: bad reason code") - ); - auto malformed_auth = encoders::encode_auth( - reason_codes::administrative_action.value(), init_auth_props() - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed AUTH received: bad reason code") + ); + auto malformed_auth = encoders::encode_auth( + reason_codes::administrative_action.value(), init_auth_props() + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .expect(auth_challenge) - .complete_with(success, after(2ms)) - .reply_with(malformed_auth, after(4ms)) - .expect(disconnect) - .complete_with(success, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(malformed_auth, after(4ms)) + .expect(disconnect) + .complete_with(success, after(2ms)); - run_test(std::move(broker_side), test::test_authenticator()); + run_test(std::move(broker_side), test::test_authenticator()); } BOOST_FIXTURE_TEST_CASE(mismatched_auth_method, shared_test_data) { - auth_props aprops; - aprops[prop::authentication_method] = "wrong method"; + auth_props aprops; + aprops[prop::authentication_method] = "wrong method"; - auto mismatched_auth_response = encoders::encode_auth( - reason_codes::continue_authentication.value(), aprops - ); + auto mismatched_auth_response = encoders::encode_auth( + reason_codes::continue_authentication.value(), aprops + ); - auto disconnect = encoders::encode_disconnect( - reason_codes::protocol_error.value(), - test::dprops_with_reason_string("Malformed AUTH received: wrong authentication method") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::protocol_error.value(), + test::dprops_with_reason_string("Malformed AUTH received: wrong authentication method") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .expect(auth_challenge) - .complete_with(success, after(2ms)) - .reply_with(mismatched_auth_response, after(4ms)) - .expect(disconnect) - .complete_with(success, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(mismatched_auth_response, after(4ms)) + .expect(disconnect) + .complete_with(success, after(2ms)); - run_test(std::move(broker_side), test::test_authenticator()); + run_test(std::move(broker_side), test::test_authenticator()); } BOOST_FIXTURE_TEST_CASE(malformed_auth_received, shared_test_data) { - auto malformed_auth = std::string { -16, 3, 24, 15, 1, 0 }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed AUTH received: cannot decode") - ); + auto malformed_auth = std::string { -16, 3, 24, 15, 1, 0 }; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed AUTH received: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .expect(auth_challenge) - .complete_with(success, after(2ms)) - .reply_with(malformed_auth, after(4ms)) - .expect(disconnect) - .complete_with(success, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(malformed_auth, after(4ms)) + .expect(disconnect) + .complete_with(success, after(2ms)); - run_test(std::move(broker_side), test::test_authenticator()); + run_test(std::move(broker_side), test::test_authenticator()); } BOOST_FIXTURE_TEST_CASE(async_auth_fail, shared_test_data) { - auto disconnect = encoders::encode_disconnect( - reason_codes::unspecified_error.value(), - test::dprops_with_reason_string("Re-authentication: authentication fail") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::unspecified_error.value(), + test::dprops_with_reason_string("Re-authentication: authentication fail") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .expect(auth_challenge) - .complete_with(success, after(2ms)) - .reply_with(auth_response, after(4ms)) - .expect(disconnect) - .complete_with(success, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(auth_response, after(4ms)) + .expect(disconnect) + .complete_with(success, after(2ms)); - run_test( - std::move(broker_side), - test::fail_test_authenticator() - ); + run_test( + std::move(broker_side), + test::fail_test_authenticator() + ); } BOOST_FIXTURE_TEST_CASE(unexpected_auth, shared_test_data) { - auto connect_no_auth = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - auto disconnect = encoders::encode_disconnect( - reason_codes::protocol_error.value(), - test::dprops_with_reason_string("Unexpected AUTH received") - ); + auto connect_no_auth = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + auto disconnect = encoders::encode_disconnect( + reason_codes::protocol_error.value(), + test::dprops_with_reason_string("Unexpected AUTH received") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect_no_auth) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)) - .send(auth_challenge, after(50ms)) - .expect(disconnect) - .complete_with(success, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_no_auth) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .send(auth_challenge, after(50ms)) + .expect(disconnect) + .complete_with(success, after(2ms)); - run_test( - std::move(broker_side) - ); + run_test( + std::move(broker_side) + ); } BOOST_FIXTURE_TEST_CASE(re_auth_without_authenticator, shared_test_data) { - auto connect_no_auth = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); + auto connect_no_auth = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); - test::msg_exchange broker_side; - broker_side - .expect(connect_no_auth) - .complete_with(success, after(0ms)) - .reply_with(connack, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect_no_auth) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/read_message.cpp b/test/integration/read_message.cpp index 57076dd..18af78a 100644 --- a/test/integration/read_message.cpp +++ b/test/integration/read_message.cpp @@ -5,25 +5,24 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include - -#include -#include -#include +#include +#include #include #include #include #include +#include -#include -#include +#include +#include +#include #include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(read_message/*, *boost::unit_test::disabled()*/) @@ -31,257 +30,257 @@ using test::after; using namespace std::chrono_literals; void test_receive_malformed_packet( - std::string malformed_packet, std::string reason_string + std::string malformed_packet, std::string reason_string ) { - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - connack_props co_props; - co_props[prop::maximum_packet_size] = 2000; - auto connack = encoders::encode_connack(false, reason_codes::success.value(), co_props); + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + connack_props co_props; + co_props[prop::maximum_packet_size] = 2000; + auto connack = encoders::encode_connack(false, reason_codes::success.value(), co_props); - disconnect_props dc_props; - dc_props[prop::reason_string] = reason_string; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), dc_props - ); + disconnect_props dc_props; + dc_props[prop::reason_string] = reason_string; + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dc_props + ); - error_code success {}; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(malformed_packet, after(5ms)) - .expect(disconnect) - .complete_with(success, after(0ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)); + error_code success {}; + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(malformed_packet, after(5ms)) + .expect(disconnect) + .complete_with(success, after(0ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(100ms); - timer.async_wait([&c](error_code) { c.cancel(); }); + asio::steady_timer timer(c.get_executor()); + timer.expires_after(100ms); + timer.async_wait([&c](error_code) { c.cancel(); }); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } BOOST_AUTO_TEST_CASE(forbidden_packet_type) { - test_receive_malformed_packet( - std::string({ 0x00 }), - "Malformed Packet received from the Server" - ); + test_receive_malformed_packet( + std::string({ 0x00 }), + "Malformed Packet received from the Server" + ); } BOOST_AUTO_TEST_CASE(malformed_varint) { - test_receive_malformed_packet( - std::string({ 0x10, -1 /* 0xFF */, -1, -1, -1 }), - "Malformed Packet received from the Server" - ); + test_receive_malformed_packet( + std::string({ 0x10, -1 /* 0xFF */, -1, -1, -1 }), + "Malformed Packet received from the Server" + ); } BOOST_AUTO_TEST_CASE(malformed_fixed_header) { - test_receive_malformed_packet( - std::string({ 0x60, 1, 0 }), - "Malformed Packet received from the Server" - ); + test_receive_malformed_packet( + std::string({ 0x60, 1, 0 }), + "Malformed Packet received from the Server" + ); } BOOST_AUTO_TEST_CASE(packet_larger_than_allowed) { - test_receive_malformed_packet( - std::string({ 0x10, -1, -1, -1, 0 }), - "Malformed Packet received from the Server" - ); + test_receive_malformed_packet( + std::string({ 0x10, -1, -1, -1, 0 }), + "Malformed Packet received from the Server" + ); } BOOST_AUTO_TEST_CASE(receive_malformed_publish) { - test_receive_malformed_packet( - std::string({ 0x30, 1, -1 }), - "Malformed PUBLISH received: cannot decode" - ); + test_receive_malformed_packet( + std::string({ 0x30, 1, -1 }), + "Malformed PUBLISH received: cannot decode" + ); } struct shared_test_data { - error_code success{}; - error_code fail = asio::error::not_connected; + error_code success{}; + error_code fail = asio::error::not_connected; - const std::string topic = "topic"; - const std::string payload = "payload"; + const std::string topic = "topic"; + const std::string payload = "payload"; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack(false, uint8_t(0x00), {}); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack(false, uint8_t(0x00), {}); - const std::string publish = encoders::encode_publish( - 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); + const std::string publish = encoders::encode_publish( + 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); - std::string encode_disconnect_with_rs(std::string_view reason_string) { - disconnect_props dc_props; - dc_props[prop::reason_string] = reason_string; - return encoders::encode_disconnect( - reason_codes::malformed_packet.value(), dc_props - ); - } + std::string encode_disconnect_with_rs(std::string_view reason_string) { + disconnect_props dc_props; + dc_props[prop::reason_string] = reason_string; + return encoders::encode_disconnect( + reason_codes::malformed_packet.value(), dc_props + ); + } }; BOOST_FIXTURE_TEST_CASE(receive_disconnect, shared_test_data) { - // packets - auto disconnect = encoders::encode_disconnect(0x00, {}); + // packets + auto disconnect = encoders::encode_disconnect(0x00, {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(disconnect, after(50ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(disconnect, after(50ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(100ms); - timer.async_wait([&c](error_code) { c.cancel(); }); + asio::steady_timer timer(c.get_executor()); + timer.expires_after(100ms); + timer.async_wait([&c](error_code) { c.cancel(); }); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(broker.received_all_expected()); } template void run_receive_test( - test::msg_exchange broker_side, int num_of_receives, - VerifyFun&& verify_fun + test::msg_exchange broker_side, int num_of_receives, + VerifyFun&& verify_fun ) { - const int expected_handlers_called = num_of_receives; - int handlers_called = 0; + const int expected_handlers_called = num_of_receives; + int handlers_called = 0; - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - for (int i = 0; i < num_of_receives; ++i) - c.async_receive([&]( - error_code ec, - std::string topic, std::string payload, publish_props props - ) { - handlers_called++; - verify_fun(ec, topic, payload, props); + for (int i = 0; i < num_of_receives; ++i) + c.async_receive([&]( + error_code ec, + std::string topic, std::string payload, publish_props props + ) { + handlers_called++; + verify_fun(ec, topic, payload, props); - if (handlers_called == expected_handlers_called) - c.cancel(); - }); + if (handlers_called == expected_handlers_called) + c.cancel(); + }); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(receive_byte_by_byte, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); - for (size_t i = 0; i < publish.size(); i++) - broker_side.send( - std::string { publish[i] }, after(std::chrono::milliseconds(i + 7)) - ); + for (size_t i = 0; i < publish.size(); i++) + broker_side.send( + std::string { publish[i] }, after(std::chrono::milliseconds(i + 7)) + ); - auto verify_fun = [&]( - error_code ec, std::string topic_, std::string payload_, publish_props - ) { - BOOST_TEST(!ec); - BOOST_TEST(topic == topic_); - BOOST_TEST(payload == payload_); - }; + auto verify_fun = [&]( + error_code ec, std::string topic_, std::string payload_, publish_props + ) { + BOOST_TEST(!ec); + BOOST_TEST(topic == topic_); + BOOST_TEST(payload == payload_); + }; - run_receive_test(std::move(broker_side), 1, std::move(verify_fun)); + run_receive_test(std::move(broker_side), 1, std::move(verify_fun)); } BOOST_FIXTURE_TEST_CASE(receive_multiple_packets_at_once, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .send(publish, publish, publish, publish, publish, after(100ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .send(publish, publish, publish, publish, publish, after(100ms)); - auto verify_fun = [&]( - error_code ec, std::string topic_, std::string payload_, publish_props - ) { - BOOST_TEST(!ec); - BOOST_TEST(topic == topic_); - BOOST_TEST(payload == payload_); - }; + auto verify_fun = [&]( + error_code ec, std::string topic_, std::string payload_, publish_props + ) { + BOOST_TEST(!ec); + BOOST_TEST(topic == topic_); + BOOST_TEST(payload == payload_); + }; - run_receive_test(std::move(broker_side), 5, std::move(verify_fun)); + run_receive_test(std::move(broker_side), 5, std::move(verify_fun)); } BOOST_FIXTURE_TEST_CASE(receive_multiple_packets_with_malformed, shared_test_data) { - // packets - // ghost publishes need to be cleared once the client reads the malformed packet - auto ghost_publish = encoders::encode_publish( - 0, topic, "should not be received!", qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); + // packets + // ghost publishes need to be cleared once the client reads the malformed packet + auto ghost_publish = encoders::encode_publish( + 0, topic, "should not be received!", qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .send( - publish, publish, std::string({ 0x00 }) /* malformed */, - ghost_publish, ghost_publish, after(100ms) - ) - .expect(encode_disconnect_with_rs("Malformed Packet received from the Server")) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .send(publish, after(300ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .send( + publish, publish, std::string({ 0x00 }) /* malformed */, + ghost_publish, ghost_publish, after(100ms) + ) + .expect(encode_disconnect_with_rs("Malformed Packet received from the Server")) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .send(publish, after(300ms)); - auto verify_fun = [&]( - error_code ec, std::string topic_, std::string payload_, publish_props - ) { - BOOST_TEST(!ec); - BOOST_TEST(topic == topic_); - BOOST_TEST(payload == payload_); - }; + auto verify_fun = [&]( + error_code ec, std::string topic_, std::string payload_, publish_props + ) { + BOOST_TEST(!ec); + BOOST_TEST(topic == topic_); + BOOST_TEST(payload == payload_); + }; - run_receive_test(std::move(broker_side), 3, std::move(verify_fun)); + run_receive_test(std::move(broker_side), 3, std::move(verify_fun)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/receive_publish.cpp b/test/integration/receive_publish.cpp index df9f0d4..68a141c 100644 --- a/test/integration/receive_publish.cpp +++ b/test/integration/receive_publish.cpp @@ -5,494 +5,492 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include +#include +#include #include #include #include #include -#include -#include -#include - -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" #include "test_common/test_broker.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(receive_publish/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - true, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); - const std::string topic = "topic"; - const std::string payload = "payload"; + const std::string topic = "topic"; + const std::string payload = "payload"; - const std::string publish_qos0 = encoders::encode_publish( - 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); - const std::string publish_qos1 = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - const std::string publish_qos2 = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} - ); + const std::string publish_qos0 = encoders::encode_publish( + 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); + const std::string publish_qos1 = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + const std::string publish_qos2 = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); - const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); + const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); - const std::string pubrec = encoders::encode_pubrec(1, uint8_t(0x00), {}); - const std::string pubrel = encoders::encode_pubrel(1, uint8_t(0x00), {}); - const std::string pubcomp = encoders::encode_pubcomp(1, uint8_t(0x00), {}); + const std::string pubrec = encoders::encode_pubrec(1, uint8_t(0x00), {}); + const std::string pubrel = encoders::encode_pubrel(1, uint8_t(0x00), {}); + const std::string pubcomp = encoders::encode_pubcomp(1, uint8_t(0x00), {}); }; using test::after; using namespace std::chrono_literals; void run_test( - test::msg_exchange broker_side, connect_props cprops = {} + test::msg_exchange broker_side, connect_props cprops = {} ) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .connect_properties(cprops) - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .connect_properties(cprops) + .async_run(asio::detached); - c.async_receive([&handlers_called, &c]( - error_code ec, std::string rec_topic, std::string rec_payload, publish_props - ){ - ++handlers_called; - auto data = shared_test_data(); - BOOST_TEST(!ec); - BOOST_TEST(data.topic == rec_topic); - BOOST_TEST(data.payload == rec_payload); - c.cancel(); - } - ); + c.async_receive([&handlers_called, &c]( + error_code ec, std::string rec_topic, std::string rec_payload, publish_props + ){ + ++handlers_called; + auto data = shared_test_data(); + BOOST_TEST(!ec); + BOOST_TEST(data.topic == rec_topic); + BOOST_TEST(data.payload == rec_payload); + c.cancel(); + } + ); - ioc.run_for(3s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(3s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(receive_publish_qos0, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos0, after(10ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos0, after(10ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_publish_qos1, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos1, after(10ms)) - .expect(puback) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos1, after(10ms)) + .expect(puback) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_publish_qos2, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2, after(10ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(pubrel, after(2ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2, after(10ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(pubrel, after(2ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_publish_properties, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - publish_props pprops; + publish_props pprops; - pprops[prop::payload_format_indicator] = uint8_t(0); - pprops[prop::message_expiry_interval] = 102u; - pprops[prop::content_type] = "content/type"; - pprops[prop::response_topic] = "response/topic"; - pprops[prop::correlation_data] = std::string { - static_cast(0x00), static_cast(0x01), static_cast(0xFF) - }; - pprops[prop::subscription_identifier] = { 40, 41, 42 }; - pprops[prop::topic_alias] = uint16_t(103); - pprops[prop::user_property] = { { "name1", "value1" }, { "name2", "value2 "} }; + pprops[prop::payload_format_indicator] = uint8_t(0); + pprops[prop::message_expiry_interval] = 102u; + pprops[prop::content_type] = "content/type"; + pprops[prop::response_topic] = "response/topic"; + pprops[prop::correlation_data] = std::string { + static_cast(0x00), static_cast(0x01), static_cast(0xFF) + }; + pprops[prop::subscription_identifier] = { 40, 41, 42 }; + pprops[prop::topic_alias] = uint16_t(103); + pprops[prop::user_property] = { { "name1", "value1" }, { "name2", "value2 "} }; - auto publish = encoders::encode_publish( - 1, topic, payload, - qos_e::at_most_once, retain_e::no, dup_e::no, - pprops - ); + auto publish = encoders::encode_publish( + 1, topic, payload, + qos_e::at_most_once, retain_e::no, dup_e::no, + pprops + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish, after(10ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish, after(10ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1") - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1") + .async_run(asio::detached); - c.async_receive([&handlers_called, &pprops, &c]( - error_code ec, std::string rec_topic, std::string rec_payload, - publish_props rec_pprops - ){ - ++handlers_called; - auto data = shared_test_data(); - BOOST_TEST(!ec); - BOOST_TEST(data.topic == rec_topic); - BOOST_TEST(data.payload == rec_payload); - BOOST_TEST(*pprops[prop::payload_format_indicator] - == *rec_pprops[prop::payload_format_indicator]); - BOOST_TEST(*pprops[prop::message_expiry_interval] - == *rec_pprops[prop::message_expiry_interval]); - BOOST_TEST(*pprops[prop::content_type] - == *rec_pprops[prop::content_type]); - BOOST_TEST(*pprops[prop::response_topic] - == *rec_pprops[prop::response_topic]); - BOOST_TEST(*pprops[prop::correlation_data] - == *rec_pprops[prop::correlation_data]); - BOOST_TEST(pprops[prop::subscription_identifier] - == rec_pprops[prop::subscription_identifier]); - BOOST_TEST(*pprops[prop::topic_alias] - == *rec_pprops[prop::topic_alias]); - BOOST_TEST(pprops[prop::user_property] - == rec_pprops[prop::user_property]); - c.cancel(); - } - ); + c.async_receive([&handlers_called, &pprops, &c]( + error_code ec, std::string rec_topic, std::string rec_payload, + publish_props rec_pprops + ){ + ++handlers_called; + auto data = shared_test_data(); + BOOST_TEST(!ec); + BOOST_TEST(data.topic == rec_topic); + BOOST_TEST(data.payload == rec_payload); + BOOST_TEST(*pprops[prop::payload_format_indicator] + == *rec_pprops[prop::payload_format_indicator]); + BOOST_TEST(*pprops[prop::message_expiry_interval] + == *rec_pprops[prop::message_expiry_interval]); + BOOST_TEST(*pprops[prop::content_type] + == *rec_pprops[prop::content_type]); + BOOST_TEST(*pprops[prop::response_topic] + == *rec_pprops[prop::response_topic]); + BOOST_TEST(*pprops[prop::correlation_data] + == *rec_pprops[prop::correlation_data]); + BOOST_TEST(pprops[prop::subscription_identifier] + == rec_pprops[prop::subscription_identifier]); + BOOST_TEST(*pprops[prop::topic_alias] + == *rec_pprops[prop::topic_alias]); + BOOST_TEST(pprops[prop::user_property] + == rec_pprops[prop::user_property]); + c.cancel(); + } + ); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(receive_malformed_publish, shared_test_data) { - // packets - auto malformed_publish = encoders::encode_publish( - 1, "malformed topic", "malformed payload", - static_cast(0b11), retain_e::yes, dup_e::no, {} - ); + // packets + auto malformed_publish = encoders::encode_publish( + 1, "malformed topic", "malformed payload", + static_cast(0b11), retain_e::yes, dup_e::no, {} + ); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBLISH received: QoS bits set to 0b11") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBLISH received: QoS bits set to 0b11") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(malformed_publish, after(10ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos0, after(50ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(malformed_publish, after(10ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos0, after(50ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_malformed_pubrel, shared_test_data) { - // packets - auto malformed_pubrel = std::string { 98, 6, 0, 1, 0, 2, 31, 1 }; + // packets + auto malformed_pubrel = std::string { 98, 6, 0, 1, 0, 2, 31, 1 }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBREL received: cannot decode") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBREL received: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2, after(10ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubrel, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(pubrel, after(100ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2, after(10ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubrel, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(pubrel, after(100ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_pubrel, shared_test_data) { - // packets - auto malformed_pubrel = encoders::encode_pubrel(1, uint8_t(0x04), {}); + // packets + auto malformed_pubrel = encoders::encode_pubrel(1, uint8_t(0x04), {}); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBREL received: invalid Reason Code") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBREL received: invalid Reason Code") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2, after(10ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubrel, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(pubrel, after(100ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2, after(10ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubrel, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(pubrel, after(100ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(fail_to_send_puback, shared_test_data) { - // packets - auto publish_qos1_dup = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} - ); + // packets + auto publish_qos1_dup = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos1, after(3ms)) - .expect(puback) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .send(publish_qos1_dup, after(100ms)) - .expect(puback) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos1, after(3ms)) + .expect(puback) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .send(publish_qos1_dup, after(100ms)) + .expect(puback) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(fail_to_send_pubrec, shared_test_data) { - // packets - auto publish_qos2_dup = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} - ); + // packets + auto publish_qos2_dup = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2, after(3ms)) - .expect(pubrec) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2_dup, after(100ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(pubrel, after(2ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2, after(3ms)) + .expect(pubrec) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2_dup, after(100ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(pubrel, after(2ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(broker_fails_to_receive_pubrec, shared_test_data) { - // packets - auto publish_dup = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} - ); + // packets + auto publish_dup = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2, after(3ms)) - // write completed, but the broker did not actually - // receive pubrec, it will resend publish again - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(fail, after(3ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_dup, after(100ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(pubrel, after(2ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2, after(3ms)) + // write completed, but the broker did not actually + // receive pubrec, it will resend publish again + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(fail, after(3ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_dup, after(100ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(pubrel, after(2ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(fail_to_send_pubcomp, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(publish_qos2, after(10ms)) - .expect(pubrec) - .complete_with(success, after(1ms)) - .reply_with(pubrel, after(2ms)) - .expect(pubcomp) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .send(pubrel, after(10ms)) - .expect(pubcomp) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(publish_qos2, after(10ms)) + .expect(pubrec) + .complete_with(success, after(1ms)) + .reply_with(pubrel, after(2ms)) + .expect(pubcomp) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .send(pubrel, after(10ms)) + .expect(pubcomp) + .complete_with(success, after(1ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_big_publish, shared_test_data) { - // data - connect_props cprops; - cprops[prop::maximum_packet_size] = 10'000'000; + // data + connect_props cprops; + cprops[prop::maximum_packet_size] = 10'000'000; - publish_props big_props; - for (int i = 0; i < 50; i++) - big_props[prop::user_property].emplace_back( - std::string(65534, 'u'), std::string(65534, 'v') - ); + publish_props big_props; + for (int i = 0; i < 50; i++) + big_props[prop::user_property].emplace_back( + std::string(65534, 'u'), std::string(65534, 'v') + ); - // packets - auto connect_big_packets = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, cprops, std::nullopt - ); - auto big_publish = encoders::encode_publish( - 1, topic, payload, - qos_e::at_most_once, retain_e::no, dup_e::no, - std::move(big_props) - ); + // packets + auto connect_big_packets = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, cprops, std::nullopt + ); + auto big_publish = encoders::encode_publish( + 1, topic, payload, + qos_e::at_most_once, retain_e::no, dup_e::no, + std::move(big_props) + ); - test::msg_exchange broker_side; - broker_side - .expect(connect_big_packets) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .send(big_publish, after(1s)); + test::msg_exchange broker_side; + broker_side + .expect(connect_big_packets) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .send(big_publish, after(1s)); - run_test(std::move(broker_side), cprops); + run_test(std::move(broker_side), cprops); } BOOST_FIXTURE_TEST_CASE(receive_buffer_overflow, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)); - std::vector buffers; - buffers.reserve(65536); + std::vector buffers; + buffers.reserve(65536); - for (int i = 0; i < 65536; ++i) - buffers.push_back( - encoders::encode_publish( - 0, "topic_" + std::to_string(i), - "payload", qos_e::at_most_once, - retain_e::no, dup_e::no, {} - ) - ); + for (int i = 0; i < 65536; ++i) + buffers.push_back( + encoders::encode_publish( + 0, "topic_" + std::to_string(i), + "payload", qos_e::at_most_once, + retain_e::no, dup_e::no, {} + ) + ); - broker_side.send(boost::algorithm::join(buffers, ""), after(20ms)); + broker_side.send(boost::algorithm::join(buffers, ""), after(20ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1") - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1") + .async_run(asio::detached); - asio::steady_timer timer(executor); - timer.expires_after(10s); - timer.async_wait( - [&](error_code) { - c.async_receive([&]( - error_code ec, std::string rec_topic, - std::string rec_payload, publish_props - ){ - ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST("topic_1" == rec_topic); - BOOST_TEST(payload == rec_payload); - c.cancel(); - } - ); - } - ); + asio::steady_timer timer(executor); + timer.expires_after(10s); + timer.async_wait( + [&](error_code) { + c.async_receive([&]( + error_code ec, std::string rec_topic, + std::string rec_payload, publish_props + ){ + ++handlers_called; + BOOST_TEST(!ec); + BOOST_TEST("topic_1" == rec_topic); + BOOST_TEST(payload == rec_payload); + c.cancel(); + } + ); + } + ); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/send_publish.cpp b/test/integration/send_publish.cpp index 0b7e94b..7db2b28 100644 --- a/test/integration/send_publish.cpp +++ b/test/integration/send_publish.cpp @@ -5,59 +5,58 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include - -#include -#include -#include +#include +#include #include #include #include #include #include +#include -#include -#include +#include +#include +#include #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(send_publish/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); - const std::string topic = "topic"; - const std::string payload = "payload"; - - const std::string publish_qos0 = encoders::encode_publish( - 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} - ); - const std::string publish_qos1 = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} - ); - const std::string publish_qos2 = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} - ); + const std::string topic = "topic"; + const std::string payload = "payload"; + + const std::string publish_qos0 = encoders::encode_publish( + 0, topic, payload, qos_e::at_most_once, retain_e::no, dup_e::no, {} + ); + const std::string publish_qos1 = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::no, {} + ); + const std::string publish_qos2 = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::no, {} + ); - const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); + const std::string puback = encoders::encode_puback(1, uint8_t(0x00), {}); - const std::string pubrec = encoders::encode_pubrec(1, uint8_t(0x00), {}); - const std::string pubrel = encoders::encode_pubrel(1, uint8_t(0x00), {}); - const std::string pubcomp = encoders::encode_pubcomp(1, uint8_t(0x00), {}); + const std::string pubrec = encoders::encode_pubrec(1, uint8_t(0x00), {}); + const std::string pubrel = encoders::encode_pubrel(1, uint8_t(0x00), {}); + const std::string pubcomp = encoders::encode_pubcomp(1, uint8_t(0x00), {}); }; using test::after; @@ -65,557 +64,557 @@ using namespace std::chrono_literals; template void run_test( - test::msg_exchange broker_side, - asio::any_completion_handler> op_handler + test::msg_exchange broker_side, + asio::any_completion_handler> op_handler ) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - auto shared_data = shared_test_data(); - if constexpr (qos == qos_e::at_most_once) - c.async_publish( - shared_data.topic, shared_data.payload, retain_e::no, publish_props {}, - [&handlers_called, &c, h = std::move(op_handler)](error_code ec) mutable { - ++handlers_called; - std::move(h)(ec); - c.cancel(); - } - ); - else - c.async_publish( - shared_data.topic, shared_data.payload, retain_e::no, publish_props {}, - [&handlers_called, &c, h = std::move(op_handler)] - (error_code ec, reason_code rc, detail::on_publish_props_type props) mutable { - ++handlers_called; - std::move(h)(ec, rc, props); - c.cancel(); - } - ); + auto shared_data = shared_test_data(); + if constexpr (qos == qos_e::at_most_once) + c.async_publish( + shared_data.topic, shared_data.payload, retain_e::no, publish_props {}, + [&handlers_called, &c, h = std::move(op_handler)](error_code ec) mutable { + ++handlers_called; + std::move(h)(ec); + c.cancel(); + } + ); + else + c.async_publish( + shared_data.topic, shared_data.payload, retain_e::no, publish_props {}, + [&handlers_called, &c, h = std::move(op_handler)] + (error_code ec, reason_code rc, detail::on_publish_props_type props) mutable { + ++handlers_called; + std::move(h)(ec, rc, props); + c.cancel(); + } + ); - ioc.run_for(2s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(2s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(send_publish_qos0, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos0) - .complete_with(success, after(1ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos0) + .complete_with(success, after(1ms)); - run_test( - std::move(broker_side), - [](error_code ec) { - BOOST_TEST(!ec); - } - ); + run_test( + std::move(broker_side), + [](error_code ec) { + BOOST_TEST(!ec); + } + ); } BOOST_FIXTURE_TEST_CASE(send_publish_qos1, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, puback_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, puback_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(send_publish_qos2, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(fail_to_send_publish, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, puback_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, puback_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(fail_to_send_pubrel, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(fail_to_receive_pubcomp, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .send(fail, after(100ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .send(fail, after(100ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_malformed_puback, shared_test_data) { - // packets - auto publish_qos1_dup = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} - ); - auto malformed_puback = std::string { 64, 6, 0, 1, 0, 2, 31, 1 }; + // packets + auto publish_qos1_dup = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} + ); + auto malformed_puback = std::string { 64, 6, 0, 1, 0, 2, 31, 1 }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBACK: cannot decode") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBACK: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(success, after(1ms)) - .reply_with(malformed_puback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1_dup) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(success, after(1ms)) + .reply_with(malformed_puback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1_dup) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, puback_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, puback_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_puback, shared_test_data) { - // packets - auto publish_qos1_dup = encoders::encode_publish( - 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} - ); - auto malformed_puback = encoders::encode_puback(1, uint8_t(0x04), {}); + // packets + auto publish_qos1_dup = encoders::encode_publish( + 1, topic, payload, qos_e::at_least_once, retain_e::no, dup_e::yes, {} + ); + auto malformed_puback = encoders::encode_puback(1, uint8_t(0x04), {}); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBACK: invalid Reason Code") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBACK: invalid Reason Code") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1) - .complete_with(success, after(1ms)) - .reply_with(malformed_puback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos1_dup) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1) + .complete_with(success, after(1ms)) + .reply_with(malformed_puback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos1_dup) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, puback_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, puback_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_malformed_pubrec, shared_test_data) { - // packets - auto publish_qos2_dup = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} - ); + // packets + auto publish_qos2_dup = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} + ); - auto malformed_pubrec = std::string { 80, 6, 0, 1, 0, 2, 31, 1 }; + auto malformed_pubrec = std::string { 80, 6, 0, 1, 0, 2, 31, 1 }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBREC: cannot decode") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBREC: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubrec, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2_dup) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubrec, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2_dup) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_pubrec, shared_test_data) { - // packets - auto publish_qos2_dup = encoders::encode_publish( - 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} - ); + // packets + auto publish_qos2_dup = encoders::encode_publish( + 1, topic, payload, qos_e::exactly_once, retain_e::no, dup_e::yes, {} + ); - auto malformed_pubrec = encoders::encode_pubrec(1, uint8_t(0x04), {}); + auto malformed_pubrec = encoders::encode_pubrec(1, uint8_t(0x04), {}); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBREC: invalid Reason Code") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBREC: invalid Reason Code") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubrec, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2_dup) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubrec, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2_dup) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_malformed_pubcomp, shared_test_data) { - // packets - auto malformed_pubcomp = std::string { 112, 6, 0, 1, 0, 2, 31, 1 }; + // packets + auto malformed_pubcomp = std::string { 112, 6, 0, 1, 0, 2, 31, 1 }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBCOMP: cannot decode") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBCOMP: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubcomp, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubcomp, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_pubcomp, shared_test_data) { - // packets - auto malformed_pubcomp = encoders::encode_pubcomp(1, uint8_t(0x04), {}); + // packets + auto malformed_pubcomp = encoders::encode_pubcomp(1, uint8_t(0x04), {}); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed PUBCOMP: invalid Reason Code") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed PUBCOMP: invalid Reason Code") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(publish_qos2) - .complete_with(success, after(1ms)) - .reply_with(pubrec, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(malformed_pubcomp, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(pubrel) - .complete_with(success, after(1ms)) - .reply_with(pubcomp, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(publish_qos2) + .complete_with(success, after(1ms)) + .reply_with(pubrec, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(malformed_pubcomp, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(pubrel) + .complete_with(success, after(1ms)) + .reply_with(pubcomp, after(2ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); + } + ); } BOOST_FIXTURE_TEST_CASE(receive_pubrec_with_rc, shared_test_data) { - // packets - auto pubrec_with_rc = encoders::encode_pubrec( - 1, reason_codes::unspecified_error.value(), {} - ); + // packets + auto pubrec_with_rc = encoders::encode_pubrec( + 1, reason_codes::unspecified_error.value(), {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(0ms)) - .reply_with(connack, after(0ms)) - .expect(publish_qos2) - .complete_with(success, after(0ms)) - .reply_with(pubrec_with_rc, after(0ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(0ms)) + .expect(publish_qos2) + .complete_with(success, after(0ms)) + .reply_with(pubrec_with_rc, after(0ms)); - run_test( - std::move(broker_side), - [](error_code ec, reason_code rc, pubcomp_props) { - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::unspecified_error); - } - ); + run_test( + std::move(broker_side), + [](error_code ec, reason_code rc, pubcomp_props) { + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::unspecified_error); + } + ); } BOOST_FIXTURE_TEST_CASE(cancel_resending_publish, shared_test_data) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - asio::cancellation_signal cancel_signal; - c.async_publish( - topic, payload, retain_e::no, publish_props{}, - asio::bind_cancellation_slot( - cancel_signal.slot(), - [&handlers_called, &c](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + asio::cancellation_signal cancel_signal; + c.async_publish( + topic, payload, retain_e::no, publish_props{}, + asio::bind_cancellation_slot( + cancel_signal.slot(), + [&handlers_called, &c](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(ec = asio::error::operation_aborted); - BOOST_TEST(rc == reason_codes::empty); + BOOST_TEST(ec = asio::error::operation_aborted); + BOOST_TEST(rc == reason_codes::empty); - c.cancel(); - } - ) - ); - cancel_signal.emit(asio::cancellation_type::total); + c.cancel(); + } + ) + ); + cancel_signal.emit(asio::cancellation_type::total); - ioc.run_for(1s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(1s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(send_big_publish, shared_test_data) { - // currently broken in test environment + // currently broken in test environment - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - // data - connack_props cprops; - cprops[prop::maximum_packet_size] = 10'000'000; + // data + connack_props cprops; + cprops[prop::maximum_packet_size] = 10'000'000; - const std::string big_topic = std::string(65534, 't'); - const std::string big_payload = std::string(65534, 'p'); + const std::string big_topic = std::string(65534, 't'); + const std::string big_payload = std::string(65534, 'p'); - // packets - auto allow_big_connack = encoders::encode_connack(false, uint8_t(0x00), cprops); - auto big_publish = encoders::encode_publish( - 1, big_topic, big_payload, - qos_e::at_least_once, retain_e::no, dup_e::no, - publish_props{} - ); - auto pingreq = encoders::encode_pingreq(); - auto pingresp = encoders::encode_pingresp(); + // packets + auto allow_big_connack = encoders::encode_connack(false, uint8_t(0x00), cprops); + auto big_publish = encoders::encode_publish( + 1, big_topic, big_payload, + qos_e::at_least_once, retain_e::no, dup_e::no, + publish_props{} + ); + auto pingreq = encoders::encode_pingreq(); + auto pingresp = encoders::encode_pingresp(); - std::vector buffers; - for (size_t i = 0; i < big_publish.size(); i += 65536) - buffers.push_back(big_publish.substr(i, 65536)); - BOOST_TEST_REQUIRE(buffers.size() == 3u); - // this single async_send will result in 3 calls to async_write_some in our stream + std::vector buffers; + for (size_t i = 0; i < big_publish.size(); i += 65536) + buffers.push_back(big_publish.substr(i, 65536)); + BOOST_TEST_REQUIRE(buffers.size() == 3u); + // this single async_send will result in 3 calls to async_write_some in our stream - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(allow_big_connack, after(2ms)) - .expect(buffers[0]) - .complete_with(success, after(1ms)) - .expect(buffers[1]) - .complete_with(success, after(1ms)) - .expect(buffers[2]) - .complete_with(success, after(1ms)) - .reply_with(puback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(allow_big_connack, after(2ms)) + .expect(buffers[0]) + .complete_with(success, after(1ms)) + .expect(buffers[1]) + .complete_with(success, after(1ms)) + .expect(buffers[2]) + .complete_with(success, after(1ms)) + .reply_with(puback, after(2ms)); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - c.async_publish( - big_topic, big_payload, retain_e::no, publish_props{}, - [&handlers_called, &c](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + c.async_publish( + big_topic, big_payload, retain_e::no, publish_props{}, + [&handlers_called, &c](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(rc == reason_codes::success); + BOOST_TEST(!ec); + BOOST_TEST(rc == reason_codes::success); - c.cancel(); - } - ); + c.cancel(); + } + ); - ioc.run_for(2s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(2s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/sub_unsub.cpp b/test/integration/sub_unsub.cpp index 1e4b872..209e5dd 100644 --- a/test/integration/sub_unsub.cpp +++ b/test/integration/sub_unsub.cpp @@ -5,6 +5,14 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include +#include +#include +#include #include #include @@ -12,59 +20,50 @@ #include #include -#include -#include -#include -#include -#include - -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; -namespace async_mqtt5::test { +namespace boost::mqtt5::test { enum operation_type { - subscribe = 1, - unsubscribe + subscribe = 1, + unsubscribe }; -} // end namespace async_mqtt5::test +} // end namespace boost::mqtt5::test BOOST_AUTO_TEST_SUITE(sub_unsub/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - false, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + false, reason_codes::success.value(), {} + ); - std::vector sub_topics = { - subscribe_topic { "topic", subscribe_options {} } - }; - std::vector unsub_topics = { "topic" }; - std::vector reason_codes = { uint8_t(0x00) }; + std::vector sub_topics = { + subscribe_topic { "topic", subscribe_options {} } + }; + std::vector unsub_topics = { "topic" }; + std::vector reason_codes = { uint8_t(0x00) }; - const std::string subscribe = encoders::encode_subscribe( - 1, sub_topics, subscribe_props {} - ); - const std::string suback = encoders::encode_suback(1, reason_codes, suback_props {}); + const std::string subscribe = encoders::encode_subscribe( + 1, sub_topics, subscribe_props {} + ); + const std::string suback = encoders::encode_suback(1, reason_codes, suback_props {}); - const std::string unsubscribe = encoders::encode_unsubscribe( - 1, unsub_topics, unsubscribe_props {} - ); - const std::string unsuback = encoders::encode_unsuback(1, reason_codes, unsuback_props {}); + const std::string unsubscribe = encoders::encode_unsubscribe( + 1, unsub_topics, unsubscribe_props {} + ); + const std::string unsuback = encoders::encode_unsuback(1, reason_codes, unsuback_props {}); }; using test::after; @@ -72,407 +71,407 @@ using namespace std::chrono_literals; template void run_test(test::msg_exchange broker_side) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - auto data = shared_test_data(); - if constexpr (op_type == test::operation_type::subscribe) - c.async_subscribe( - data.sub_topics, subscribe_props {}, - [&handlers_called, &c](error_code ec, std::vector rcs, suback_props) { - ++handlers_called; + auto data = shared_test_data(); + if constexpr (op_type == test::operation_type::subscribe) + c.async_subscribe( + data.sub_topics, subscribe_props {}, + [&handlers_called, &c](error_code ec, std::vector rcs, suback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST_REQUIRE(rcs.size() == 1); - BOOST_TEST(rcs[0] == reason_codes::granted_qos_0); + BOOST_TEST(!ec); + BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST(rcs[0] == reason_codes::granted_qos_0); - c.cancel(); - } - ); - else - c.async_unsubscribe( - data.unsub_topics, unsubscribe_props {}, - [&handlers_called, &c](error_code ec, std::vector rcs, unsuback_props) { - ++handlers_called; + c.cancel(); + } + ); + else + c.async_unsubscribe( + data.unsub_topics, unsubscribe_props {}, + [&handlers_called, &c](error_code ec, std::vector rcs, unsuback_props) { + ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST_REQUIRE(rcs.size() == 1); - BOOST_TEST(rcs[0] == reason_codes::success); + BOOST_TEST(!ec); + BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST(rcs[0] == reason_codes::success); - c.cancel(); - } - ); + c.cancel(); + } + ); - ioc.run_for(5s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(5s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } // subscribe BOOST_FIXTURE_TEST_CASE(fail_to_send_subscribe, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(suback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(suback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(fail_to_receive_suback, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .send(fail, after(15ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(suback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .send(fail, after(15ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(suback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_malformed_suback, shared_test_data) { - // packets - const char malformed_bytes[] = { - -112, 7, 0, 1, 4, 31, 0, 2, 32 - }; - std::string malformed_suback { malformed_bytes, sizeof(malformed_bytes) / sizeof(char) }; + // packets + const char malformed_bytes[] = { + -112, 7, 0, 1, 4, 31, 0, 2, 32 + }; + std::string malformed_suback { malformed_bytes, sizeof(malformed_bytes) / sizeof(char) }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed SUBACK: cannot decode") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed SUBACK: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(malformed_suback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(suback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(malformed_suback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(suback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_suback, shared_test_data) { - // packets - auto malformed_suback = encoders::encode_suback( - 1, { uint8_t(0x04) }, suback_props {} - ); + // packets + auto malformed_suback = encoders::encode_suback( + 1, { uint8_t(0x04) }, suback_props {} + ); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string( - "Malformed SUBACK: does not contain a " - "valid Reason Code for every Topic Filter" - ) - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string( + "Malformed SUBACK: does not contain a " + "valid Reason Code for every Topic Filter" + ) + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(malformed_suback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(suback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(malformed_suback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(suback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(mismatched_num_of_suback_rcs, shared_test_data) { - // packets - auto malformed_suback = encoders::encode_suback( - 1, { uint8_t(0x00), uint8_t(0x00) }, suback_props {} - ); + // packets + auto malformed_suback = encoders::encode_suback( + 1, { uint8_t(0x00), uint8_t(0x00) }, suback_props {} + ); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string( - "Malformed SUBACK: does not contain a " - "valid Reason Code for every Topic Filter" - ) - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string( + "Malformed SUBACK: does not contain a " + "valid Reason Code for every Topic Filter" + ) + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(malformed_suback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(subscribe) - .complete_with(success, after(1ms)) - .reply_with(suback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(malformed_suback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(subscribe) + .complete_with(success, after(1ms)) + .reply_with(suback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } // unsubscribe BOOST_FIXTURE_TEST_CASE(fail_to_send_unsubscribe, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(fail, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(unsuback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(fail, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(unsuback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(fail_to_receive_unsuback, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .send(fail, after(15ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(unsuback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .send(fail, after(15ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(unsuback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_malformed_unsuback, shared_test_data) { - // packets - const char malformed_bytes[] = { - -80, 7, 0, 1, 4, 31, 0, 2, 32 - }; - std::string malformed_unsuback { malformed_bytes, sizeof(malformed_bytes) / sizeof(char) }; + // packets + const char malformed_bytes[] = { + -80, 7, 0, 1, 4, 31, 0, 2, 32 + }; + std::string malformed_unsuback { malformed_bytes, sizeof(malformed_bytes) / sizeof(char) }; - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string("Malformed UNSUBACK: cannot decode") - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string("Malformed UNSUBACK: cannot decode") + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(malformed_unsuback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(unsuback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(malformed_unsuback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(unsuback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_unsuback, shared_test_data) { - // packets - auto malformed_unsuback = encoders::encode_unsuback( - 1, { uint8_t(0x04) }, unsuback_props {} - ); + // packets + auto malformed_unsuback = encoders::encode_unsuback( + 1, { uint8_t(0x04) }, unsuback_props {} + ); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string( - "Malformed UNSUBACK: does not contain a " - "valid Reason Code for every Topic Filter" - ) - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string( + "Malformed UNSUBACK: does not contain a " + "valid Reason Code for every Topic Filter" + ) + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(malformed_unsuback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(unsuback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(malformed_unsuback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(unsuback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(mismatched_num_of_unsuback_rcs, shared_test_data) { - // packets - auto malformed_unsuback = encoders::encode_unsuback( - 1, { uint8_t(0x00), uint8_t(0x00)}, unsuback_props {} - ); + // packets + auto malformed_unsuback = encoders::encode_unsuback( + 1, { uint8_t(0x00), uint8_t(0x00)}, unsuback_props {} + ); - auto disconnect = encoders::encode_disconnect( - reason_codes::malformed_packet.value(), - test::dprops_with_reason_string( - "Malformed UNSUBACK: does not contain a " - "valid Reason Code for every Topic Filter" - ) - ); + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + test::dprops_with_reason_string( + "Malformed UNSUBACK: does not contain a " + "valid Reason Code for every Topic Filter" + ) + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(malformed_unsuback, after(2ms)) - .expect(disconnect) - .complete_with(success, after(1ms)) - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)) - .expect(unsubscribe) - .complete_with(success, after(1ms)) - .reply_with(unsuback, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(malformed_unsuback, after(2ms)) + .expect(disconnect) + .complete_with(success, after(1ms)) + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)) + .expect(unsubscribe) + .complete_with(success, after(1ms)) + .reply_with(unsuback, after(2ms)); - run_test(std::move(broker_side)); + run_test(std::move(broker_side)); } template void run_cancellation_test(test::msg_exchange broker_side) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - using client_type = mqtt_client; - client_type c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + using client_type = mqtt_client; + client_type c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - asio::cancellation_signal cancel_signal; - auto data = shared_test_data(); - if constexpr (op_type == test::operation_type::subscribe) - c.async_subscribe( - data.sub_topics, subscribe_props {}, - asio::bind_cancellation_slot( - cancel_signal.slot(), - [&handlers_called, &c](error_code ec, std::vector rcs, suback_props) { - ++handlers_called; + asio::cancellation_signal cancel_signal; + auto data = shared_test_data(); + if constexpr (op_type == test::operation_type::subscribe) + c.async_subscribe( + data.sub_topics, subscribe_props {}, + asio::bind_cancellation_slot( + cancel_signal.slot(), + [&handlers_called, &c](error_code ec, std::vector rcs, suback_props) { + ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1); - BOOST_TEST(rcs[0] == reason_codes::empty); + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST(rcs[0] == reason_codes::empty); - c.cancel(); - } - ) - ); - else - c.async_unsubscribe( - data.unsub_topics, unsubscribe_props {}, - asio::bind_cancellation_slot( - cancel_signal.slot(), - [&handlers_called, &c](error_code ec, std::vector rcs, unsuback_props) { - ++handlers_called; + c.cancel(); + } + ) + ); + else + c.async_unsubscribe( + data.unsub_topics, unsubscribe_props {}, + asio::bind_cancellation_slot( + cancel_signal.slot(), + [&handlers_called, &c](error_code ec, std::vector rcs, unsuback_props) { + ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1); - BOOST_TEST(rcs[0] == reason_codes::empty); + BOOST_TEST(ec == asio::error::operation_aborted); + BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST(rcs[0] == reason_codes::empty); - c.cancel(); - } - ) - ); + c.cancel(); + } + ) + ); - cancel_signal.emit(asio::cancellation_type::total); + cancel_signal.emit(asio::cancellation_type::total); - ioc.run_for(2s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(2s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_FIXTURE_TEST_CASE(cancel_resending_subscribe, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)); - run_cancellation_test(std::move(broker_side)); + run_cancellation_test(std::move(broker_side)); } BOOST_FIXTURE_TEST_CASE(cancel_resending_unsubscribe, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(1ms)) - .reply_with(connack, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(1ms)) + .reply_with(connack, after(2ms)); - run_cancellation_test(std::move(broker_side)); + run_cancellation_test(std::move(broker_side)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/src/run_tests.cpp b/test/src/run_tests.cpp index 700d06d..111fc46 100644 --- a/test/src/run_tests.cpp +++ b/test/src/run_tests.cpp @@ -5,18 +5,18 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#define BOOST_TEST_MODULE async_mqtt5_tests +#define BOOST_TEST_MODULE boost::mqtt5_tests #include boost::unit_test::test_suite* init_tests( - int /*argc*/, char* /*argv*/[] + int /*argc*/, char* /*argv*/[] ) { - return nullptr; + return nullptr; } int main(int argc, char* argv[]) { - return boost::unit_test::unit_test_main(&init_tests, argc, argv); + return boost::unit_test::unit_test_main(&init_tests, argc, argv); } /* diff --git a/test/unit/async_mutex.cpp b/test/unit/async_mutex.cpp index 1d31cdd..3a612e9 100644 --- a/test/unit/async_mutex.cpp +++ b/test/unit/async_mutex.cpp @@ -5,184 +5,184 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include +#include -#include -#include -#include +#include #include #include #include #include #include +#include -#include -#include +#include +#include +#include -using namespace async_mqtt5; +using namespace boost::mqtt5; using error_code = boost::system::error_code; using async_mutex = detail::async_mutex; BOOST_AUTO_TEST_SUITE(async_mutex_unit/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(lock_mutex) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::thread_pool tp(1); - async_mutex mutex(tp.executor()); + asio::thread_pool tp(1); + async_mutex mutex(tp.executor()); - mutex.lock([&mutex, &handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(mutex.is_locked()); - mutex.unlock(); - BOOST_TEST(!mutex.is_locked()); - }); + mutex.lock([&mutex, &handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(!ec); + BOOST_TEST(mutex.is_locked()); + mutex.unlock(); + BOOST_TEST(!mutex.is_locked()); + }); - tp.wait(); - BOOST_TEST(handlers_called == expected_handlers_called); + tp.wait(); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_CASE(get_executor) { - asio::thread_pool tp(1); - auto ex = tp.get_executor(); - async_mutex mutex(ex); - BOOST_CHECK(mutex.get_executor() == ex); + asio::thread_pool tp(1); + auto ex = tp.get_executor(); + async_mutex mutex(ex); + BOOST_CHECK(mutex.get_executor() == ex); } BOOST_AUTO_TEST_CASE(bind_executor) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - asio::thread_pool tp(1); + asio::thread_pool tp(1); - async_mutex mutex(tp.get_executor()); - auto s1 = asio::make_strand(tp.get_executor()); - auto s2 = asio::make_strand(tp.get_executor()); + 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, - [&](error_code ec) mutable { - ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(s1.running_in_this_thread()); - BOOST_TEST(!s2.running_in_this_thread()); - mutex.unlock(); - } - ) - ); + mutex.lock( + asio::bind_executor( + s1, + [&](error_code ec) mutable { + ++handlers_called; + BOOST_TEST(!ec); + BOOST_TEST(s1.running_in_this_thread()); + BOOST_TEST(!s2.running_in_this_thread()); + mutex.unlock(); + } + ) + ); - mutex.lock( - asio::bind_executor( - s2, - [&](error_code ec) mutable { - ++handlers_called; - BOOST_TEST(!ec); - BOOST_TEST(!s1.running_in_this_thread()); - BOOST_TEST(s2.running_in_this_thread()); - mutex.unlock(); - } - ) - ); + mutex.lock( + asio::bind_executor( + s2, + [&](error_code ec) mutable { + ++handlers_called; + BOOST_TEST(!ec); + BOOST_TEST(!s1.running_in_this_thread()); + BOOST_TEST(s2.running_in_this_thread()); + mutex.unlock(); + } + ) + ); - tp.wait(); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(!mutex.is_locked()); + tp.wait(); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(!mutex.is_locked()); } BOOST_AUTO_TEST_CASE(per_op_cancellation) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - asio::io_context ioc; - asio::cancellation_signal cs; + asio::io_context ioc; + asio::cancellation_signal cs; - async_mutex mutex(asio::make_strand(ioc.get_executor())); + async_mutex mutex(asio::make_strand(ioc.get_executor())); - // mutex must be locked in order to cancel a pending operation - mutex.lock( - [&mutex, &handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(!ec); - mutex.unlock(); - } - ); + // mutex must be locked in order to cancel a pending operation + mutex.lock( + [&mutex, &handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(!ec); + mutex.unlock(); + } + ); - mutex.lock( - asio::bind_cancellation_slot( - cs.slot(), - [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(ec == asio::error::operation_aborted); - } - ) - ); - cs.emit(asio::cancellation_type_t::terminal); - cs.slot().clear(); + mutex.lock( + asio::bind_cancellation_slot( + cs.slot(), + [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(ec == asio::error::operation_aborted); + } + ) + ); + cs.emit(asio::cancellation_type_t::terminal); + cs.slot().clear(); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(!mutex.is_locked()); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(!mutex.is_locked()); } BOOST_AUTO_TEST_CASE(cancel_ops_by_destructor) { - constexpr int expected_handlers_called = 2; - int handlers_called = 0; + constexpr int expected_handlers_called = 2; + int handlers_called = 0; - asio::io_context ioc; + asio::io_context ioc; - { - async_mutex mutex(ioc.get_executor()); + { + async_mutex mutex(ioc.get_executor()); - auto op = [&handlers_called](error_code ec) { - handlers_called++; - BOOST_TEST(!ec); - }; + auto op = [&handlers_called](error_code ec) { + handlers_called++; + BOOST_TEST(!ec); + }; - auto cancelled_op = [&handlers_called](error_code ec) { - handlers_called++; - BOOST_TEST(ec == asio::error::operation_aborted); - }; + auto cancelled_op = [&handlers_called](error_code ec) { + handlers_called++; + BOOST_TEST(ec == asio::error::operation_aborted); + }; - mutex.lock(std::move(op)); // will be immediately posted with ec = success - mutex.lock(std::move(cancelled_op)); - } + mutex.lock(std::move(op)); // will be immediately posted with ec = success + mutex.lock(std::move(cancelled_op)); + } - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_CASE(cancel_ops) { - constexpr int expected_handlers_called = 5; - int handlers_called = 0; + constexpr int expected_handlers_called = 5; + int handlers_called = 0; - asio::io_context ioc; - async_mutex mutex(ioc.get_executor()); + asio::io_context ioc; + async_mutex mutex(ioc.get_executor()); - auto op = [&mutex, &handlers_called](error_code ec) { - handlers_called++; - BOOST_TEST(!ec); - mutex.unlock(); - }; + auto op = [&mutex, &handlers_called](error_code ec) { + handlers_called++; + BOOST_TEST(!ec); + mutex.unlock(); + }; - auto cancelled_op = [&handlers_called](error_code ec) { - handlers_called++; - BOOST_TEST(ec == asio::error::operation_aborted); - }; + auto cancelled_op = [&handlers_called](error_code ec) { + handlers_called++; + BOOST_TEST(ec == asio::error::operation_aborted); + }; - mutex.lock(std::move(op)); + mutex.lock(std::move(op)); - // pending operations that will be cancelled - for (int i = 0; i < expected_handlers_called - 1; ++i) - mutex.lock(cancelled_op); + // pending operations that will be cancelled + for (int i = 0; i < expected_handlers_called - 1; ++i) + mutex.lock(cancelled_op); - mutex.cancel(); - ioc.run(); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(!mutex.is_locked()); + mutex.cancel(); + ioc.run(); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(!mutex.is_locked()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/connect_op.cpp b/test/unit/connect_op.cpp index 1554a03..6af31d5 100644 --- a/test/unit/connect_op.cpp +++ b/test/unit/connect_op.cpp @@ -5,415 +5,416 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include +#include +#include -#include -#include -#include -#include +#include +#include + +#include #include -#include #include +#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include "test_common/test_authenticators.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(connect_op/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - true, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); }; using test::after; using namespace std::chrono_literals; void run_unit_test( - detail::mqtt_ctx mqtt_ctx, test::msg_exchange broker_side, - asio::any_completion_handler op_handler + detail::mqtt_ctx mqtt_ctx, test::msg_exchange broker_side, + asio::any_completion_handler op_handler ) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); - test::test_stream stream(executor); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + test::test_stream stream(executor); - authority_path ap; - auto eps = asio::ip::tcp::resolver(executor).resolve("127.0.0.1", ""); + authority_path ap; + auto eps = asio::ip::tcp::resolver(executor).resolve("127.0.0.1", ""); - auto handler = [&handlers_called, h = std::move(op_handler)](error_code ec) mutable { - handlers_called++; - std::move(h)(ec); - }; + auto handler = [&handlers_called, h = std::move(op_handler)](error_code ec) mutable { + handlers_called++; + std::move(h)(ec); + }; - detail::log_invoke d; - detail::connect_op( - stream, mqtt_ctx, d, std::move(handler) - ).perform(*std::begin(eps), std::move(ap)); + detail::log_invoke d; + detail::connect_op( + stream, mqtt_ctx, d, std::move(handler) + ).perform(*std::begin(eps), std::move(ap)); - ioc.run_for(1s); - BOOST_TEST(handlers_called == expected_handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run_for(1s); + BOOST_TEST(handlers_called == expected_handlers_called); + BOOST_TEST(broker.received_all_expected()); } void run_unit_test( - test::msg_exchange broker_side, - asio::any_completion_handler op_handler + test::msg_exchange broker_side, + asio::any_completion_handler op_handler ) { - return run_unit_test( - detail::mqtt_ctx(), std::move(broker_side), std::move(op_handler) - ); + return run_unit_test( + detail::mqtt_ctx(), std::move(broker_side), std::move(op_handler) + ); } BOOST_FIXTURE_TEST_CASE(successfully_connect, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(connack, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == success); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == success); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(connack_with_fail_rc, shared_test_data) { - auto denied_connack = encoders::encode_connack( - true, reason_codes::bad_username_or_password.value(), {} - ); + auto denied_connack = encoders::encode_connack( + true, reason_codes::bad_username_or_password.value(), {} + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(denied_connack, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(denied_connack, after(4ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == asio::error::try_again); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == asio::error::try_again); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(fail_to_send_connect, shared_test_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(fail, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(fail, after(2ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == fail); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == fail); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(receive_wrong_packet, shared_test_data) { - // packets - auto unexpected_packet = encoders::encode_puback(1, uint8_t(0x00), {}); + // packets + auto unexpected_packet = encoders::encode_puback(1, uint8_t(0x00), {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(unexpected_packet, after(3ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(unexpected_packet, after(3ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == asio::error::try_again); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == asio::error::try_again); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(malformed_connack_varlen, shared_test_data) { - // packets - auto malformed_connack = std::string({ 0x20, -1 /* 0xFF */, -1, -1, -1 }); + // packets + auto malformed_connack = std::string({ 0x20, -1 /* 0xFF */, -1, -1, -1 }); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(malformed_connack, after(3ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(malformed_connack, after(3ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == asio::error::try_again); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == asio::error::try_again); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(malformed_connack_rc, shared_test_data) { - // packets - auto malformed_connack = encoders::encode_connack(true, uint8_t(0x04), {}); + // packets + auto malformed_connack = encoders::encode_connack(true, uint8_t(0x04), {}); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(malformed_connack, after(3ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(malformed_connack, after(3ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == client::error::malformed_packet); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == client::error::malformed_packet); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(fail_reading_connack_payload, shared_test_data) { - // packets - connack_props cprops; - cprops[prop::reason_string] = std::string(256, 'a'); + // packets + connack_props cprops; + cprops[prop::reason_string] = std::string(256, 'a'); - auto big_connack = encoders::encode_connack( - true, uint8_t(0x00), cprops - ); + auto big_connack = encoders::encode_connack( + true, uint8_t(0x00), cprops + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .send(big_connack.substr(0, 5), after(5ms)) - .send(fail, after(7ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .send(big_connack.substr(0, 5), after(5ms)) + .send(fail, after(7ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == fail); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == fail); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(receive_unexpected_auth, shared_test_data) { - auth_props aprops; - aprops[prop::authentication_method] = "method"; - aprops[prop::authentication_data] = "data"; + auth_props aprops; + aprops[prop::authentication_method] = "method"; + aprops[prop::authentication_data] = "data"; - auto auth = encoders::encode_auth(uint8_t(0x19), aprops); + auto auth = encoders::encode_auth(uint8_t(0x19), aprops); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .send(auth, after(5ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .send(auth, after(5ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == client::error::malformed_packet); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == client::error::malformed_packet); + }; - run_unit_test(std::move(broker_side), std::move(handler)); + run_unit_test(std::move(broker_side), std::move(handler)); } // enhanced auth struct shared_test_auth_data { - error_code success {}; - error_code fail = asio::error::not_connected; + error_code success {}; + error_code fail = asio::error::not_connected; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, init_connect_props(), std::nullopt - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, init_connect_props(), std::nullopt + ); - const std::string connack = encoders::encode_connack( - true, reason_codes::success.value(), {} - ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); - const std::string auth_challenge = encoders::encode_auth( - reason_codes::continue_authentication.value(), init_auth_props() - ); + const std::string auth_challenge = encoders::encode_auth( + reason_codes::continue_authentication.value(), init_auth_props() + ); - const std::string auth_response = auth_challenge; + const std::string auth_response = auth_challenge; - connect_props init_connect_props() { - connect_props cprops; - cprops[prop::authentication_method] = "method"; - cprops[prop::authentication_data] = ""; - return cprops; - } + connect_props init_connect_props() { + connect_props cprops; + cprops[prop::authentication_method] = "method"; + cprops[prop::authentication_data] = ""; + return cprops; + } - auth_props init_auth_props() { - auth_props aprops; - aprops[prop::authentication_method] = "method"; - aprops[prop::authentication_data] = ""; - return aprops; - } + auth_props init_auth_props() { + auth_props aprops; + aprops[prop::authentication_method] = "method"; + aprops[prop::authentication_data] = ""; + return aprops; + } }; BOOST_FIXTURE_TEST_CASE(successful_auth, shared_test_auth_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(auth_challenge, after(3ms)) - .expect(auth_response) - .complete_with(success, after(2ms)) - .reply_with(connack, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == success); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == success); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(successful_auth_multi_step, shared_test_auth_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(auth_challenge, after(3ms)) - .expect(auth_response) - .complete_with(success, after(2ms)) - .reply_with(auth_challenge, after(4ms)) - .expect(auth_response) - .complete_with(success, after(2ms)) - .reply_with(connack, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(4ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == success); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == success); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(malformed_auth_rc, shared_test_auth_data) { - auto malformed_auth_challenge = encoders::encode_auth( - reason_codes::server_busy.value(), init_auth_props() - ); + auto malformed_auth_challenge = encoders::encode_auth( + reason_codes::server_busy.value(), init_auth_props() + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(malformed_auth_challenge, after(3ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(malformed_auth_challenge, after(3ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == client::error::malformed_packet); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == client::error::malformed_packet); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(mismatched_auth_method, shared_test_auth_data) { - auth_props aprops; - aprops[prop::authentication_method] = "wrong method"; + auth_props aprops; + aprops[prop::authentication_method] = "wrong method"; - auto mismatched_auth_challenge = encoders::encode_auth( - reason_codes::continue_authentication.value(), aprops - ); + auto mismatched_auth_challenge = encoders::encode_auth( + reason_codes::continue_authentication.value(), aprops + ); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(mismatched_auth_challenge, after(3ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(mismatched_auth_challenge, after(3ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == client::error::malformed_packet); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == client::error::malformed_packet); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(fail_to_send_auth, shared_test_auth_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(auth_challenge, after(3ms)) - .expect(auth_response) - .complete_with(fail, after(2ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(fail, after(2ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == fail); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == fail); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(auth_step_client_initial_fail, shared_test_auth_data) { - test::msg_exchange broker_side; + test::msg_exchange broker_side; - auto handler = [&](error_code ec) { - BOOST_TEST(ec == asio::error::try_again); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == asio::error::try_again); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::fail_test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::fail_test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(auth_step_server_challenge_fail, shared_test_auth_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(auth_challenge, after(3ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == asio::error::try_again); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == asio::error::try_again); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::fail_test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::fail_test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(auth_step_server_final_fail, shared_test_auth_data) { - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(auth_challenge, after(3ms)) - .expect(auth_response) - .complete_with(success, after(2ms)) - .reply_with(connack, after(4ms)); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); - auto handler = [&](error_code ec) { - BOOST_TEST(ec == asio::error::try_again); - }; + auto handler = [&](error_code ec) { + BOOST_TEST(ec == asio::error::try_again); + }; - detail::mqtt_ctx mqtt_ctx; - mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test::fail_test_authenticator(); - run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::fail_test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/default_completion_tokens.cpp b/test/unit/default_completion_tokens.cpp index 3458821..968af34 100644 --- a/test/unit/default_completion_tokens.cpp +++ b/test/unit/default_completion_tokens.cpp @@ -10,36 +10,36 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT +#include +#include + +#include +#include +#include +#include +#include // async_teardown for asio::ssl::socket +#include + #include #include #include // std::monostate #include -#include -#include -#include - -#include -#include // async_teardown for asio::ssl::socket - -#include -#include - -namespace async_mqtt5 { +namespace boost::mqtt5 { namespace asio = boost::asio; template struct tls_handshake_type> { - static constexpr auto client = asio::ssl::stream_base::client; - static constexpr auto server = asio::ssl::stream_base::server; + static constexpr auto client = asio::ssl::stream_base::client; + static constexpr auto server = asio::ssl::stream_base::server; }; template void assign_tls_sni( - const authority_path& /* ap */, - asio::ssl::context& /* ctx */, - asio::ssl::stream& /* stream */ + const authority_path& /* ap */, + asio::ssl::context& /* ctx */, + asio::ssl::stream& /* stream */ ) {} namespace test { @@ -48,60 +48,60 @@ namespace test { template asio::awaitable test_default_completion_tokens_impl( - TlsContextType tls_context = {} + TlsContextType tls_context = {} ) { - asio::io_context ioc; + asio::io_context ioc; - using client_type = asio::use_awaitable_t<>::as_default_on_t< - mqtt_client - >; - client_type c(ioc, std::move(tls_context)); + using client_type = asio::use_awaitable_t<>::as_default_on_t< + mqtt_client + >; + client_type c(ioc, std::move(tls_context)); - co_await c.async_run(); - - auto pub_props = publish_props {}; - co_await c.template async_publish( - "topic", "payload", retain_e::no, pub_props - ); + co_await c.async_run(); + + auto pub_props = publish_props {}; + co_await c.template async_publish( + "topic", "payload", retain_e::no, pub_props + ); - auto sub_topic = subscribe_topic {}; - auto sub_topics = std::vector { sub_topic }; - auto sub_props = subscribe_props {}; - co_await c.async_subscribe(sub_topics, sub_props); - co_await c.async_subscribe(sub_topic, sub_props); + auto sub_topic = subscribe_topic {}; + auto sub_topics = std::vector { sub_topic }; + auto sub_props = subscribe_props {}; + co_await c.async_subscribe(sub_topics, sub_props); + co_await c.async_subscribe(sub_topic, sub_props); - auto unsub_topics = std::vector {}; - auto unsub_props = unsubscribe_props {}; - co_await c.async_unsubscribe(unsub_topics, unsub_props); - co_await c.async_unsubscribe("topic", unsub_props); + auto unsub_topics = std::vector {}; + auto unsub_props = unsubscribe_props {}; + co_await c.async_unsubscribe(unsub_topics, unsub_props); + co_await c.async_unsubscribe("topic", unsub_props); - co_await c.async_receive(); + co_await c.async_receive(); - auto dc_props = disconnect_props {}; - co_await c.async_disconnect(); - co_await c.async_disconnect(disconnect_rc_e::normal_disconnection, dc_props); + auto dc_props = disconnect_props {}; + co_await c.async_disconnect(); + co_await c.async_disconnect(disconnect_rc_e::normal_disconnection, dc_props); } asio::awaitable test_default_completion_tokens() { - co_await test_default_completion_tokens_impl(); + co_await test_default_completion_tokens_impl(); - co_await test_default_completion_tokens_impl< - asio::ssl::stream, - asio::ssl::context - >(asio::ssl::context(asio::ssl::context::tls_client)); + co_await test_default_completion_tokens_impl< + asio::ssl::stream, + asio::ssl::context + >(asio::ssl::context(asio::ssl::context::tls_client)); - co_await test_default_completion_tokens_impl< - boost::beast::websocket::stream - >(); + co_await test_default_completion_tokens_impl< + boost::beast::websocket::stream + >(); - co_await test_default_completion_tokens_impl< - boost::beast::websocket::stream>, - asio::ssl::context - >(asio::ssl::context(asio::ssl::context::tls_client)); + co_await test_default_completion_tokens_impl< + boost::beast::websocket::stream>, + asio::ssl::context + >(asio::ssl::context(asio::ssl::context::tls_client)); } } // end namespace test -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 #endif // BOOST_ASIO_HAS_CO_AWAIT diff --git a/test/unit/disconnect_op.cpp b/test/unit/disconnect_op.cpp index d18baa2..57fdc9c 100644 --- a/test/unit/disconnect_op.cpp +++ b/test/unit/disconnect_op.cpp @@ -5,69 +5,69 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include +#include -#include -#include -#include -#include +#include #include #include +#include -#include -#include +#include +#include +#include +#include #include "test_common/test_service.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(disconnect_op/*, *boost::unit_test::disabled()*/) void run_malformed_props_test(const disconnect_props& dprops) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::test_service; - auto svc_ptr = std::make_shared(ioc.get_executor()); + asio::io_context ioc; + using client_service_type = test::test_service; + auto svc_ptr = std::make_shared(ioc.get_executor()); - auto handler = [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(ec == client::error::malformed_packet); - }; + auto handler = [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(ec == client::error::malformed_packet); + }; - detail::disconnect_ctx ctx; - ctx.props = dprops; + detail::disconnect_ctx ctx; + ctx.props = dprops; - detail::disconnect_op< - client_service_type, detail::disconnect_ctx - > { svc_ptr, std::move(ctx), std::move(handler) } - .perform(); + detail::disconnect_op< + client_service_type, detail::disconnect_ctx + > { svc_ptr, std::move(ctx), std::move(handler) } + .perform(); - ioc.run_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_CASE(malformed_reason_string) { - disconnect_props dprops; - dprops[prop::reason_string] = std::string { 0x01 }; + disconnect_props dprops; + dprops[prop::reason_string] = std::string { 0x01 }; - run_malformed_props_test(dprops); + run_malformed_props_test(dprops); } BOOST_AUTO_TEST_CASE(malformed_user_property_key) { - disconnect_props dprops; - dprops[prop::user_property].emplace_back(std::string { 0x01 }, "value"); + disconnect_props dprops; + dprops[prop::user_property].emplace_back(std::string { 0x01 }, "value"); - run_malformed_props_test(dprops); + run_malformed_props_test(dprops); } BOOST_AUTO_TEST_CASE(malformed_user_property_value) { - disconnect_props dprops; - dprops[prop::user_property].emplace_back("key", std::string { 0x01 }); + disconnect_props dprops; + dprops[prop::user_property].emplace_back("key", std::string { 0x01 }); - run_malformed_props_test(dprops); + run_malformed_props_test(dprops); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/error.cpp b/test/unit/error.cpp index 262c775..cf4ae6e 100644 --- a/test/unit/error.cpp +++ b/test/unit/error.cpp @@ -5,102 +5,102 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + #include #include #include #include -#include -#include - -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(error/*, *boost::unit_test::disabled()*/) struct client_error_codes { - const client::client_ec_category& cat = client::get_error_code_category(); + const client::client_ec_category& cat = client::get_error_code_category(); - const std::vector ecs = { - client::error::malformed_packet, - client::error::packet_too_large, - client::error::session_expired, - client::error::pid_overrun, - client::error::invalid_topic, - client::error::qos_not_supported, - client::error::retain_not_available, - client::error::topic_alias_maximum_reached, - client::error::wildcard_subscription_not_available, - client::error::subscription_identifier_not_available, - client::error::shared_subscription_not_available - }; + const std::vector ecs = { + client::error::malformed_packet, + client::error::packet_too_large, + client::error::session_expired, + client::error::pid_overrun, + client::error::invalid_topic, + client::error::qos_not_supported, + client::error::retain_not_available, + client::error::topic_alias_maximum_reached, + client::error::wildcard_subscription_not_available, + client::error::subscription_identifier_not_available, + client::error::shared_subscription_not_available + }; }; BOOST_FIXTURE_TEST_CASE(client_ec_to_string, client_error_codes) { - // Ensure that all branches of the switch/case are covered - BOOST_TEST(cat.name()); + // Ensure that all branches of the switch/case are covered + BOOST_TEST(cat.name()); - constexpr auto default_output = "Unknown client error"; - for (auto ec : ecs) - BOOST_TEST(cat.message(static_cast(ec)) != default_output); + constexpr auto default_output = "Unknown client error"; + for (auto ec : ecs) + BOOST_TEST(cat.message(static_cast(ec)) != default_output); - // default branch - BOOST_TEST(cat.message(1) == default_output); + // default branch + BOOST_TEST(cat.message(1) == default_output); } BOOST_FIXTURE_TEST_CASE(client_ec_to_stream, client_error_codes) { - for (auto ec : ecs) { - std::ostringstream stream; - stream << ec; - std::string expected = std::string(cat.name()) + ":" + - std::to_string(static_cast(ec)); - BOOST_TEST(stream.str() == expected); - } + for (auto ec : ecs) { + std::ostringstream stream; + stream << ec; + std::string expected = std::string(cat.name()) + ":" + + std::to_string(static_cast(ec)); + BOOST_TEST(stream.str() == expected); + } } using namespace reason_codes; struct client_reason_codes { - const std::vector rcs = { - empty, success, normal_disconnection, - granted_qos_0, granted_qos_1, granted_qos_2, - disconnect_with_will_message, no_matching_subscribers, - no_subscription_existed, continue_authentication, reauthenticate, - unspecified_error, malformed_packet, protocol_error, - implementation_specific_error, unsupported_protocol_version, - client_identifier_not_valid,bad_username_or_password, - not_authorized, server_unavailable, server_busy, banned, - server_shutting_down, bad_authentication_method, keep_alive_timeout, - session_taken_over, topic_filter_invalid, topic_name_invalid, - packet_identifier_in_use, packet_identifier_not_found, 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 - }; + const std::vector rcs = { + empty, success, normal_disconnection, + granted_qos_0, granted_qos_1, granted_qos_2, + disconnect_with_will_message, no_matching_subscribers, + no_subscription_existed, continue_authentication, reauthenticate, + unspecified_error, malformed_packet, protocol_error, + implementation_specific_error, unsupported_protocol_version, + client_identifier_not_valid,bad_username_or_password, + not_authorized, server_unavailable, server_busy, banned, + server_shutting_down, bad_authentication_method, keep_alive_timeout, + session_taken_over, topic_filter_invalid, topic_name_invalid, + packet_identifier_in_use, packet_identifier_not_found, 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 + }; }; BOOST_FIXTURE_TEST_CASE(reason_code_to_string, client_reason_codes) { - // Ensure that all branches of the switch/case are covered - BOOST_TEST(rcs.size() == 46u); + // Ensure that all branches of the switch/case are covered + BOOST_TEST(rcs.size() == 46u); - constexpr auto default_output = "Invalid reason code"; - for (const auto& rc: rcs) - BOOST_TEST(rc.message() != "Invalid reason code"); + constexpr auto default_output = "Invalid reason code"; + for (const auto& rc: rcs) + BOOST_TEST(rc.message() != "Invalid reason code"); - // default branch - BOOST_TEST( - reason_code(0x05, reason_codes::category::suback).message() == default_output - ); + // default branch + BOOST_TEST( + reason_code(0x05, reason_codes::category::suback).message() == default_output + ); } BOOST_FIXTURE_TEST_CASE(reason_code_to_stream, client_reason_codes) { - for (const auto& rc : rcs) { - std::ostringstream stream; - stream << rc; - BOOST_TEST(stream.str() == rc.message()); - } + for (const auto& rc : rcs) { + std::ostringstream stream; + stream << rc; + BOOST_TEST(stream.str() == rc.message()); + } } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/logger.cpp b/test/unit/logger.cpp index ce2d071..ef91907 100644 --- a/test/unit/logger.cpp +++ b/test/unit/logger.cpp @@ -5,67 +5,65 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include // async_teardown specialization for websocket ssl stream +#include #include +#include #include #include #include #include -#include -#include -#include -#include - -#include -#include // async_teardown specialization for websocket ssl stream - -#include - -#include -#include -#include -#include - #include "test_common/message_exchange.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; namespace asio = boost::asio; -namespace async_mqtt5 { +namespace boost::mqtt5 { template struct tls_handshake_type> { - static constexpr auto client = asio::ssl::stream_base::client; - static constexpr auto server = asio::ssl::stream_base::server; + static constexpr auto client = asio::ssl::stream_base::client; + static constexpr auto server = asio::ssl::stream_base::server; }; template void assign_tls_sni( - const authority_path& ap, - asio::ssl::context& /* ctx */, - asio::ssl::stream& stream + const authority_path& ap, + asio::ssl::context& /* ctx */, + asio::ssl::stream& stream ) { - SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); + SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); } -} // end namespace async_mqtt5 +} // end namespace boost::mqtt5 void logger_test() { - BOOST_STATIC_ASSERT(has_at_resolve); - BOOST_STATIC_ASSERT(has_at_tcp_connect); - BOOST_STATIC_ASSERT(has_at_tls_handshake); - BOOST_STATIC_ASSERT(has_at_ws_handshake); - BOOST_STATIC_ASSERT(has_at_connack); - BOOST_STATIC_ASSERT(has_at_disconnect); + BOOST_STATIC_ASSERT(has_at_resolve); + BOOST_STATIC_ASSERT(has_at_tcp_connect); + BOOST_STATIC_ASSERT(has_at_tls_handshake); + BOOST_STATIC_ASSERT(has_at_ws_handshake); + BOOST_STATIC_ASSERT(has_at_connack); + BOOST_STATIC_ASSERT(has_at_disconnect); } using stream_type = boost::beast::websocket::stream< - asio::ssl::stream + asio::ssl::stream >; using context_type = asio::ssl::context; using logger_type = logger; @@ -74,127 +72,127 @@ using client_type = mqtt_client; BOOST_AUTO_TEST_SUITE(logger_tests) class clog_redirect { - std::streambuf* _old_buffer; + std::streambuf* _old_buffer; public: - clog_redirect( - std::streambuf* new_buffer - ) : - _old_buffer(std::clog.rdbuf(new_buffer)) - {} + clog_redirect( + std::streambuf* new_buffer + ) : + _old_buffer(std::clog.rdbuf(new_buffer)) + {} - ~clog_redirect() { - std::clog.rdbuf(_old_buffer); - } + ~clog_redirect() { + std::clog.rdbuf(_old_buffer); + } }; bool contains(const std::string& str, const std::string& substr) { - return str.find(substr) != std::string::npos; + return str.find(substr) != std::string::npos; } BOOST_AUTO_TEST_CASE(successful_connect_debug) { - boost::test_tools::output_test_stream output; + boost::test_tools::output_test_stream output; - { - clog_redirect guard(output.rdbuf()); - asio::io_context ioc; + { + clog_redirect guard(output.rdbuf()); + asio::io_context ioc; - asio::ssl::context tls_context(asio::ssl::context::tls_client); - client_type c( - ioc, std::move(tls_context), logger(log_level::debug) - ); + asio::ssl::context tls_context(asio::ssl::context::tls_client); + client_type c( + ioc, std::move(tls_context), logger(log_level::debug) + ); - c.brokers("broker.hivemq.com/mqtt", 8884) - .async_run(asio::detached); + c.brokers("broker.hivemq.com/mqtt", 8884) + .async_run(asio::detached); - c.async_disconnect([](error_code) {}); + c.async_disconnect([](error_code) {}); - ioc.run(); - } + ioc.run(); + } - std::string log = output.rdbuf()->str(); - BOOST_TEST_MESSAGE(log); - BOOST_TEST_WARN(contains(log, "resolve")); - BOOST_TEST_WARN(contains(log, "TCP connect")); - BOOST_TEST_WARN(contains(log, "TLS handshake")); - BOOST_TEST_WARN(contains(log, "WebSocket handshake")); - BOOST_TEST_WARN(contains(log, "connack")); + std::string log = output.rdbuf()->str(); + BOOST_TEST_MESSAGE(log); + BOOST_TEST_WARN(contains(log, "resolve")); + BOOST_TEST_WARN(contains(log, "TCP connect")); + BOOST_TEST_WARN(contains(log, "TLS handshake")); + BOOST_TEST_WARN(contains(log, "WebSocket handshake")); + BOOST_TEST_WARN(contains(log, "connack")); } BOOST_AUTO_TEST_CASE(successful_connect_warning) { - boost::test_tools::output_test_stream output; + boost::test_tools::output_test_stream output; - { - clog_redirect guard(output.rdbuf()); + { + clog_redirect guard(output.rdbuf()); - asio::io_context ioc; - asio::ssl::context tls_context(asio::ssl::context::tls_client); - client_type c( - ioc, std::move(tls_context), logger(log_level::warning) - ); + asio::io_context ioc; + asio::ssl::context tls_context(asio::ssl::context::tls_client); + client_type c( + ioc, std::move(tls_context), logger(log_level::warning) + ); - c.brokers("broker.hivemq.com/mqtt", 8884) - .async_run(asio::detached); + c.brokers("broker.hivemq.com/mqtt", 8884) + .async_run(asio::detached); - c.async_disconnect([](error_code) {}); + c.async_disconnect([](error_code) {}); - ioc.run(); - } + ioc.run(); + } - // If connection is successful, nothing should be printed. - // However if the Broker is down or overloaded, this will cause logs to be printed. - // We should not fail the test because of it. - BOOST_TEST_WARN(output.is_empty()); + // If connection is successful, nothing should be printed. + // However if the Broker is down or overloaded, this will cause logs to be printed. + // We should not fail the test because of it. + BOOST_TEST_WARN(output.is_empty()); } BOOST_AUTO_TEST_CASE(disconnect) { - using test::after; - using namespace std::chrono_literals; + using test::after; + using namespace std::chrono_literals; - boost::test_tools::output_test_stream output; - { - clog_redirect guard(output.rdbuf()); + boost::test_tools::output_test_stream output; + { + clog_redirect guard(output.rdbuf()); - // packets - auto connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - auto connack = encoders::encode_connack(false, uint8_t(0x00), {}); + // packets + auto connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + auto connack = encoders::encode_connack(false, uint8_t(0x00), {}); - disconnect_props dc_props; - dc_props[prop::reason_string] = "No reason."; - auto disconnect = encoders::encode_disconnect(0x00, dc_props); + disconnect_props dc_props; + dc_props[prop::reason_string] = "No reason."; + auto disconnect = encoders::encode_disconnect(0x00, dc_props); - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(error_code{}, after(0ms)) - .reply_with(connack, after(0ms)) - .send(disconnect, after(50ms)) - .expect(connect); + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(error_code{}, after(0ms)) + .reply_with(connack, after(0ms)) + .send(disconnect, after(50ms)) + .expect(connect); - asio::io_context ioc; - auto executor = ioc.get_executor(); - auto& broker = asio::make_service( - ioc, executor, std::move(broker_side) - ); + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); - mqtt_client c(executor); - c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff - .async_run(asio::detached); + mqtt_client c(executor); + c.brokers("127.0.0.1,127.0.0.1") // to avoid reconnect backoff + .async_run(asio::detached); - asio::steady_timer timer(c.get_executor()); - timer.expires_after(100ms); - timer.async_wait([&c](error_code) { c.cancel(); }); + asio::steady_timer timer(c.get_executor()); + timer.expires_after(100ms); + timer.async_wait([&c](error_code) { c.cancel(); }); - ioc.run(); - BOOST_TEST(broker.received_all_expected()); - } + ioc.run(); + BOOST_TEST(broker.received_all_expected()); + } - std::string log = output.rdbuf()->str(); - BOOST_TEST_MESSAGE(log); - BOOST_TEST(contains(log, "disconnect")); + std::string log = output.rdbuf()->str(); + BOOST_TEST_MESSAGE(log); + BOOST_TEST(contains(log, "disconnect")); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/publish_send_op.cpp b/test/unit/publish_send_op.cpp index 5332d6a..f19d7cb 100644 --- a/test/unit/publish_send_op.cpp +++ b/test/unit/publish_send_op.cpp @@ -5,217 +5,216 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include +#include -#include -#include -#include -#include -#include +#include +#include #include #include #include -#include #include +#include +#include -#include - -#include -#include +#include +#include +#include +#include +#include #include "test_common/test_service.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(publish_send_op/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(pid_overrun) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::overrun_client; - auto svc_ptr = std::make_shared(ioc.get_executor()); + asio::io_context ioc; + using client_service_type = test::overrun_client; + auto svc_ptr = std::make_shared(ioc.get_executor()); - auto handler = [&handlers_called](error_code ec, reason_code rc, puback_props) { - ++handlers_called; - BOOST_TEST(ec == client::error::pid_overrun); - BOOST_TEST(rc == reason_codes::empty); - }; + auto handler = [&handlers_called](error_code ec, reason_code rc, puback_props) { + ++handlers_called; + BOOST_TEST(ec == client::error::pid_overrun); + BOOST_TEST(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, {} - ); + 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_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } void run_test( - error_code expected_ec, - const std::string& topic_name, const std::string& payload = {}, - const publish_props& pprops = {}, const connack_props& cprops = {} + error_code expected_ec, + const std::string& topic_name, const std::string& payload = {}, + const publish_props& pprops = {}, const connack_props& cprops = {} ) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::test_service; - auto svc_ptr = std::make_shared(ioc.get_executor(), cprops); + asio::io_context ioc; + using client_service_type = test::test_service; + auto svc_ptr = std::make_shared(ioc.get_executor(), cprops); - auto handler = [&handlers_called, expected_ec](error_code ec, reason_code rc, puback_props) { - ++handlers_called; + auto handler = [&handlers_called, expected_ec](error_code ec, reason_code rc, puback_props) { + ++handlers_called; - BOOST_TEST(ec == expected_ec); - BOOST_TEST(rc == reason_codes::empty); - }; + BOOST_TEST(ec == expected_ec); + BOOST_TEST(rc == reason_codes::empty); + }; - detail::publish_send_op< - client_service_type, decltype(handler), qos_e::at_least_once - > { svc_ptr, std::move(handler) } - .perform(topic_name, payload, retain_e::yes, pprops); + detail::publish_send_op< + client_service_type, decltype(handler), qos_e::at_least_once + > { svc_ptr, std::move(handler) } + .perform(topic_name, payload, retain_e::yes, pprops); - ioc.run_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } BOOST_AUTO_TEST_CASE(invalid_topic_name_1) { - run_test(client::error::invalid_topic, ""); + run_test(client::error::invalid_topic, ""); } BOOST_AUTO_TEST_CASE(invalid_topic_name_2) { - run_test(client::error::invalid_topic, "+"); + run_test(client::error::invalid_topic, "+"); } BOOST_AUTO_TEST_CASE(invalid_topic_name_3) { - run_test(client::error::invalid_topic, "#"); + run_test(client::error::invalid_topic, "#"); } BOOST_AUTO_TEST_CASE(invalid_topic_name_4) { - run_test(client::error::invalid_topic, "invalid+"); + run_test(client::error::invalid_topic, "invalid+"); } BOOST_AUTO_TEST_CASE(invalid_topic_name_5) { - run_test(client::error::invalid_topic, "invalid#"); + run_test(client::error::invalid_topic, "invalid#"); } BOOST_AUTO_TEST_CASE(invalid_topic_name_6) { - run_test(client::error::invalid_topic, "invalid/#"); + run_test(client::error::invalid_topic, "invalid/#"); } BOOST_AUTO_TEST_CASE(invalid_topic_name_7) { - run_test(client::error::invalid_topic, "invalid/+"); + run_test(client::error::invalid_topic, "invalid/+"); } void run_malformed_props_test( - const publish_props& pprops, const connack_props& cprops = {} + const publish_props& pprops, const connack_props& cprops = {} ) { - return run_test(client::error::malformed_packet, "topic", "payload", pprops, cprops); + return run_test(client::error::malformed_packet, "topic", "payload", pprops, cprops); } BOOST_AUTO_TEST_CASE(malformed_props_1) { - publish_props pprops; - pprops[prop::response_topic] = "response#topic"; + publish_props pprops; + pprops[prop::response_topic] = "response#topic"; - run_malformed_props_test(pprops); + run_malformed_props_test(pprops); } BOOST_AUTO_TEST_CASE(malformed_props_2) { - publish_props pprops; - pprops[prop::user_property].emplace_back(std::string { 0x01 }, "value"); + publish_props pprops; + pprops[prop::user_property].emplace_back(std::string { 0x01 }, "value"); - run_malformed_props_test(pprops); + run_malformed_props_test(pprops); } BOOST_AUTO_TEST_CASE(malformed_props_3) { - publish_props pprops; - pprops[prop::content_type] = std::string { 0x01 }; + publish_props pprops; + pprops[prop::content_type] = std::string { 0x01 }; - run_malformed_props_test(pprops); + run_malformed_props_test(pprops); } BOOST_AUTO_TEST_CASE(malformed_props_4) { - connack_props cprops; - cprops[prop::topic_alias_maximum] = uint16_t(10); + connack_props cprops; + cprops[prop::topic_alias_maximum] = uint16_t(10); - publish_props pprops; - pprops[prop::topic_alias] = uint16_t(0); + publish_props pprops; + pprops[prop::topic_alias] = uint16_t(0); - run_malformed_props_test(pprops, cprops); + run_malformed_props_test(pprops, cprops); } BOOST_AUTO_TEST_CASE(malformed_props_5) { - publish_props pprops; - pprops[prop::subscription_identifier].push_back(40); + publish_props pprops; + pprops[prop::subscription_identifier].push_back(40); - run_malformed_props_test(pprops); + run_malformed_props_test(pprops); } BOOST_AUTO_TEST_CASE(malformed_utf8_payload) { - publish_props pprops; - pprops[prop::payload_format_indicator] = uint8_t(1); + publish_props pprops; + pprops[prop::payload_format_indicator] = uint8_t(1); - run_test(client::error::malformed_packet, "topic", std::string { 0x01 }, pprops); + run_test(client::error::malformed_packet, "topic", std::string { 0x01 }, pprops); } BOOST_AUTO_TEST_CASE(packet_too_large) { - connack_props cprops; - cprops[prop::maximum_packet_size] = 10; + connack_props cprops; + cprops[prop::maximum_packet_size] = 10; - run_test( - client::error::packet_too_large, - "very large topic", "very large payload", - publish_props {}, cprops - ); + run_test( + client::error::packet_too_large, + "very large topic", "very large payload", + publish_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(qos_not_supported) { - connack_props cprops; - cprops[prop::maximum_qos] = uint8_t(0); + connack_props cprops; + cprops[prop::maximum_qos] = uint8_t(0); - run_test( - client::error::qos_not_supported, - "topic", "payload", publish_props {}, cprops - ); + run_test( + client::error::qos_not_supported, + "topic", "payload", publish_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(retain_not_available) { - connack_props cprops; - cprops[prop::retain_available] = uint8_t(0); + connack_props cprops; + cprops[prop::retain_available] = uint8_t(0); - run_test( - client::error::retain_not_available, - "topic", "payload", publish_props {}, cprops - ); + run_test( + client::error::retain_not_available, + "topic", "payload", publish_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(topic_alias_maximum_reached_1) { - connack_props cprops; - cprops[prop::topic_alias_maximum] = uint16_t(10); + connack_props cprops; + cprops[prop::topic_alias_maximum] = uint16_t(10); - publish_props pprops; - pprops[prop::topic_alias] = uint16_t(12); + publish_props pprops; + pprops[prop::topic_alias] = uint16_t(12); - run_test( - client::error::topic_alias_maximum_reached, - "topic", "payload", pprops, cprops - ); + run_test( + client::error::topic_alias_maximum_reached, + "topic", "payload", pprops, cprops + ); } BOOST_AUTO_TEST_CASE(topic_alias_maximum_reached_2) { - connack_props cprops; /* not allowed */ + connack_props cprops; /* not allowed */ - publish_props pprops; - pprops[prop::topic_alias] = uint16_t(12); + publish_props pprops; + pprops[prop::topic_alias] = uint16_t(12); - run_test( - client::error::topic_alias_maximum_reached, - "topic", "payload", pprops, cprops - ); + run_test( + client::error::topic_alias_maximum_reached, + "topic", "payload", pprops, cprops + ); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/reconnect_op.cpp b/test/unit/reconnect_op.cpp index 3cf63a5..8fe1368 100644 --- a/test/unit/reconnect_op.cpp +++ b/test/unit/reconnect_op.cpp @@ -5,83 +5,83 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include + +#include + +#include +#include + +#include +#include +#include #include #include #include -#include -#include -#include - -#include -#include - -#include -#include - #include "test_common/test_autoconnect_stream.hpp" #include "test_common/test_broker.hpp" #include "test_common/test_stream.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; using namespace std::chrono_literals; BOOST_AUTO_TEST_SUITE(reconnect_op/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(exponential_backoff) { - detail::exponential_backoff generator; + detail::exponential_backoff generator; - auto first_iter = generator.generate(); - BOOST_TEST((first_iter >= 500ms && first_iter <= 1500ms)); + auto first_iter = generator.generate(); + BOOST_TEST((first_iter >= 500ms && first_iter <= 1500ms)); - auto second_iter = generator.generate(); - BOOST_TEST((second_iter >= 1500ms && first_iter <= 2500ms)); + auto second_iter = generator.generate(); + BOOST_TEST((second_iter >= 1500ms && first_iter <= 2500ms)); - auto third_iter = generator.generate(); - BOOST_TEST((third_iter >= 3500ms && third_iter <= 4500ms)); + auto third_iter = generator.generate(); + BOOST_TEST((third_iter >= 3500ms && third_iter <= 4500ms)); - auto fourth_iter = generator.generate(); - BOOST_TEST((fourth_iter >= 7500ms && fourth_iter <= 8500ms)); + auto fourth_iter = generator.generate(); + BOOST_TEST((fourth_iter >= 7500ms && fourth_iter <= 8500ms)); - auto fifth_iter = generator.generate(); - BOOST_TEST((fifth_iter >= 15500ms && fourth_iter <= 16500ms)); + auto fifth_iter = generator.generate(); + BOOST_TEST((fifth_iter >= 15500ms && fourth_iter <= 16500ms)); - auto sixth_iter = generator.generate(); - BOOST_TEST((sixth_iter >= 15500ms && sixth_iter <= 16500ms)); + auto sixth_iter = generator.generate(); + BOOST_TEST((sixth_iter >= 15500ms && sixth_iter <= 16500ms)); } struct test_tcp_stream : public test::test_stream { - test_tcp_stream( - typename test::test_stream::executor_type ex - ) : - test::test_stream(std::move(ex)) - {} + test_tcp_stream( + typename test::test_stream::executor_type ex + ) : + test::test_stream(std::move(ex)) + {} - static int& succeed_after() { - static int _succed_after = 0; - return _succed_after; - } + static int& succeed_after() { + static int _succed_after = 0; + return _succed_after; + } - template - decltype(auto) async_connect( - const endpoint_type& ep, ConnectToken&& token - ) { - auto initiation = [this](auto handler, const endpoint_type& ep) { - error_code ec = --succeed_after() < 0 ? error_code {} : asio::error::connection_refused; - if (!ec) { - error_code cec; - test::test_stream::open(ep.protocol(), cec); - test::test_stream::connect(ep, cec); - } - asio::post(get_executor(), asio::prepend(std::move(handler), ec)); - }; + template + decltype(auto) async_connect( + const endpoint_type& ep, ConnectToken&& token + ) { + auto initiation = [this](auto handler, const endpoint_type& ep) { + error_code ec = --succeed_after() < 0 ? error_code {} : asio::error::connection_refused; + if (!ec) { + error_code cec; + test::test_stream::open(ep.protocol(), cec); + test::test_stream::connect(ep, cec); + } + asio::post(get_executor(), asio::prepend(std::move(handler), ec)); + }; - return asio::async_initiate( - std::move(initiation), token, ep - ); - } + return asio::async_initiate( + std::move(initiation), token, ep + ); + } }; using underlying_stream = test_tcp_stream; @@ -89,79 +89,79 @@ using stream_context = detail::stream_context using astream = test::test_autoconnect_stream; void run_connect_to_localhost_test(int succeed_after) { - using test::after; - error_code success {}; + using test::after; + error_code success {}; - const std::string connect = encoders::encode_connect( - "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt - ); - const std::string connack = encoders::encode_connack( - true, reason_codes::success.value(), {} - ); + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 60, false, {}, std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - test::msg_exchange broker_side; - broker_side - .expect(connect) - .complete_with(success, after(2ms)) - .reply_with(connack, after(4ms)); + asio::io_context ioc; + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); - auto& broker = asio::make_service( - ioc, ioc.get_executor(), std::move(broker_side) - ); + auto& broker = asio::make_service( + ioc, ioc.get_executor(), std::move(broker_side) + ); - auto stream_ctx = stream_context(std::monostate {}); - auto log = detail::log_invoke(); - auto auto_stream = astream(ioc.get_executor(), stream_ctx, log); - auto_stream.brokers("localhost", 1883); + auto stream_ctx = stream_context(std::monostate {}); + auto log = detail::log_invoke(); + auto auto_stream = astream(ioc.get_executor(), stream_ctx, log); + auto_stream.brokers("localhost", 1883); - auto handler = [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(!ec); - }; + auto handler = [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(!ec); + }; - test_tcp_stream::succeed_after() = succeed_after; - detail::reconnect_op(auto_stream, std::move(handler)) - .perform(auto_stream.stream_pointer()); + test_tcp_stream::succeed_after() = succeed_after; + detail::reconnect_op(auto_stream, std::move(handler)) + .perform(auto_stream.stream_pointer()); - ioc.run(); - BOOST_TEST(expected_handlers_called == handlers_called); - BOOST_TEST(broker.received_all_expected()); + ioc.run(); + BOOST_TEST(expected_handlers_called == handlers_called); + BOOST_TEST(broker.received_all_expected()); } BOOST_AUTO_TEST_CASE(connect_to_first_localhost) { - // connect to first in the resolver list - run_connect_to_localhost_test(2); + // connect to first in the resolver list + run_connect_to_localhost_test(2); } BOOST_AUTO_TEST_CASE(connect_to_second_localhost) { - // connect to second in the resolver list - run_connect_to_localhost_test(3); + // connect to second in the resolver list + run_connect_to_localhost_test(3); } BOOST_AUTO_TEST_CASE(no_servers) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - auto stream_ctx = stream_context(std::monostate{}); - auto log = detail::log_invoke(); - auto auto_stream = astream(ioc.get_executor(), stream_ctx, log); - auto_stream.brokers("", 1883); + asio::io_context ioc; + auto stream_ctx = stream_context(std::monostate{}); + auto log = detail::log_invoke(); + auto auto_stream = astream(ioc.get_executor(), stream_ctx, log); + auto_stream.brokers("", 1883); - auto handler = [&handlers_called](error_code ec) { - ++handlers_called; - BOOST_TEST(ec == asio::error::no_recovery); - }; + auto handler = [&handlers_called](error_code ec) { + ++handlers_called; + BOOST_TEST(ec == asio::error::no_recovery); + }; - detail::reconnect_op(auto_stream, std::move(handler)) - .perform(auto_stream.stream_pointer()); + detail::reconnect_op(auto_stream, std::move(handler)) + .perform(auto_stream.stream_pointer()); - ioc.run(); - BOOST_TEST(expected_handlers_called == handlers_called); + ioc.run(); + BOOST_TEST(expected_handlers_called == handlers_called); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/serialization.cpp b/test/unit/serialization.cpp index 30317df..8ec6bd9 100644 --- a/test/unit/serialization.cpp +++ b/test/unit/serialization.cpp @@ -5,6 +5,12 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + +#include +#include + #include #include @@ -13,806 +19,800 @@ #include #include -#include -#include - -#include -#include - -using namespace async_mqtt5; +using namespace boost::mqtt5; using byte_citer = detail::byte_citer; BOOST_AUTO_TEST_SUITE(serialization/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(test_connect) { - // testing variables - // connect - std::string_view client_id = "async_mqtt_client_id"; - std::string_view uname = "username"; - std::optional password = std::nullopt; - uint16_t keep_alive = 60; - bool clean_start = true; - // connect properties - uint32_t session_expiry_interval = 29; - uint16_t receive_max = 100; - uint32_t maximum_packet_size = 20000; - uint16_t topic_alias_max = 1200; - uint8_t request_response_information = 1; - uint8_t request_problem_information = 0; - std::string_view user_property_1 = "user prop"; - std::string_view user_property_2 = "user prop val"; - std::string_view auth_method = "method"; - std::string_view auth_data = "data"; - // will - std::string will_topic = "will_topic"; - std::string will_message = "will_message"; - // will properties - uint32_t will_delay_interval = 200; - uint8_t will_payload_format_indicator = 0; - uint32_t will_message_expiry_interval = 2000; - std::string_view will_content_type = "will content type"; - std::string_view will_response_topic = "response_topic"; - std::string_view will_correlation_data = "correlation data"; - std::string_view will_user_property_1 = "will prop"; - std::string_view will_user_property_2 = "will prop val"; + // testing variables + // connect + std::string_view client_id = "async_mqtt_client_id"; + std::string_view uname = "username"; + std::optional password = std::nullopt; + uint16_t keep_alive = 60; + bool clean_start = true; + // connect properties + uint32_t session_expiry_interval = 29; + uint16_t receive_max = 100; + uint32_t maximum_packet_size = 20000; + uint16_t topic_alias_max = 1200; + uint8_t request_response_information = 1; + uint8_t request_problem_information = 0; + std::string_view user_property_1 = "user prop"; + std::string_view user_property_2 = "user prop val"; + std::string_view auth_method = "method"; + std::string_view auth_data = "data"; + // will + std::string will_topic = "will_topic"; + std::string will_message = "will_message"; + // will properties + uint32_t will_delay_interval = 200; + uint8_t will_payload_format_indicator = 0; + uint32_t will_message_expiry_interval = 2000; + std::string_view will_content_type = "will content type"; + std::string_view will_response_topic = "response_topic"; + std::string_view will_correlation_data = "correlation data"; + std::string_view will_user_property_1 = "will prop"; + std::string_view will_user_property_2 = "will prop val"; - connect_props cprops; - cprops[prop::session_expiry_interval] = session_expiry_interval; - cprops[prop::receive_maximum] = receive_max; - cprops[prop::maximum_packet_size] = maximum_packet_size; - cprops[prop::topic_alias_maximum] = topic_alias_max; - cprops[prop::request_response_information] = request_response_information; - cprops[prop::request_problem_information] = request_problem_information; - cprops[prop::user_property].emplace_back(user_property_1, user_property_2); - cprops[prop::authentication_method] = auth_method; - cprops[prop::authentication_data] = auth_data; + connect_props cprops; + cprops[prop::session_expiry_interval] = session_expiry_interval; + cprops[prop::receive_maximum] = receive_max; + cprops[prop::maximum_packet_size] = maximum_packet_size; + cprops[prop::topic_alias_maximum] = topic_alias_max; + cprops[prop::request_response_information] = request_response_information; + cprops[prop::request_problem_information] = request_problem_information; + cprops[prop::user_property].emplace_back(user_property_1, user_property_2); + cprops[prop::authentication_method] = auth_method; + cprops[prop::authentication_data] = auth_data; - will w { will_topic, will_message }; - w[prop::will_delay_interval] = will_delay_interval; - w[prop::payload_format_indicator] = will_payload_format_indicator; - w[prop::message_expiry_interval] = will_message_expiry_interval; - w[prop::content_type] = will_content_type; - w[prop::response_topic] = will_response_topic; - w[prop::correlation_data] = will_correlation_data; - w[prop::user_property].emplace_back(will_user_property_1, will_user_property_2); - std::optional will_opt { std::move(w) }; + will w { will_topic, will_message }; + w[prop::will_delay_interval] = will_delay_interval; + w[prop::payload_format_indicator] = will_payload_format_indicator; + w[prop::message_expiry_interval] = will_message_expiry_interval; + w[prop::content_type] = will_content_type; + w[prop::response_topic] = will_response_topic; + w[prop::correlation_data] = will_correlation_data; + w[prop::user_property].emplace_back(will_user_property_1, will_user_property_2); + std::optional will_opt { std::move(w) }; - auto msg = encoders::encode_connect( - client_id, uname, password, keep_alive, clean_start, cprops, will_opt - ); + 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_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_connect(remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_connect(remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [client_id_, uname_, password_, keep_alive_, clean_start_, cprops_, w_] = *rv; - BOOST_TEST(client_id_ == client_id); - BOOST_TEST_REQUIRE(uname_.has_value()); - BOOST_TEST(*uname_ == uname); - BOOST_CHECK(password_ == password); - BOOST_TEST(keep_alive_ == keep_alive); - BOOST_TEST(clean_start_ == clean_start); + const auto& [client_id_, uname_, password_, keep_alive_, clean_start_, cprops_, w_] = *rv; + BOOST_TEST(client_id_ == client_id); + BOOST_TEST_REQUIRE(uname_.has_value()); + BOOST_TEST(*uname_ == uname); + BOOST_CHECK(password_ == password); + BOOST_TEST(keep_alive_ == keep_alive); + BOOST_TEST(clean_start_ == clean_start); - cprops_.visit([](const auto& prop, const auto&) { - (void)prop; BOOST_TEST_REQUIRE(prop); - return true; - }); - BOOST_TEST(*cprops_[prop::session_expiry_interval] == session_expiry_interval); - BOOST_TEST(*cprops_[prop::receive_maximum] == receive_max); - BOOST_TEST(*cprops_[prop::maximum_packet_size] == maximum_packet_size); - BOOST_TEST(*cprops_[prop::topic_alias_maximum] == topic_alias_max); - BOOST_TEST(*cprops_[prop::request_response_information] == request_response_information); - BOOST_TEST(*cprops_[prop::request_problem_information] == request_problem_information); - BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1); - BOOST_TEST(cprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(cprops_[prop::user_property][0].second == user_property_2); - BOOST_TEST(*cprops_[prop::authentication_method] == auth_method); - BOOST_TEST(*cprops_[prop::authentication_data] == auth_data); + cprops_.visit([](const auto& prop, const auto&) { + (void)prop; BOOST_TEST_REQUIRE(prop); + return true; + }); + BOOST_TEST(*cprops_[prop::session_expiry_interval] == session_expiry_interval); + BOOST_TEST(*cprops_[prop::receive_maximum] == receive_max); + BOOST_TEST(*cprops_[prop::maximum_packet_size] == maximum_packet_size); + BOOST_TEST(*cprops_[prop::topic_alias_maximum] == topic_alias_max); + BOOST_TEST(*cprops_[prop::request_response_information] == request_response_information); + BOOST_TEST(*cprops_[prop::request_problem_information] == request_problem_information); + BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1); + BOOST_TEST(cprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(cprops_[prop::user_property][0].second == user_property_2); + BOOST_TEST(*cprops_[prop::authentication_method] == auth_method); + BOOST_TEST(*cprops_[prop::authentication_data] == auth_data); - BOOST_TEST_REQUIRE(w_.has_value()); - const auto& will = *w_; - BOOST_TEST((will).topic() == will_topic); - BOOST_TEST((will).message() == will_message); + BOOST_TEST_REQUIRE(w_.has_value()); + const auto& will = *w_; + BOOST_TEST((will).topic() == will_topic); + BOOST_TEST((will).message() == will_message); - (will).visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*(will)[prop::will_delay_interval] == will_delay_interval); - BOOST_TEST(*(will)[prop::payload_format_indicator] == will_payload_format_indicator); - BOOST_TEST(*(will)[prop::message_expiry_interval] == will_message_expiry_interval); - BOOST_TEST(*(will)[prop::content_type] == will_content_type); - BOOST_TEST(*(will)[prop::response_topic] == will_response_topic); - BOOST_TEST(*(will)[prop::correlation_data] == will_correlation_data); - BOOST_TEST_REQUIRE((will)[prop::user_property].size() == 1); - BOOST_TEST((will)[prop::user_property][0].first == will_user_property_1); - BOOST_TEST((will)[prop::user_property][0].second == will_user_property_2); + (will).visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*(will)[prop::will_delay_interval] == will_delay_interval); + BOOST_TEST(*(will)[prop::payload_format_indicator] == will_payload_format_indicator); + BOOST_TEST(*(will)[prop::message_expiry_interval] == will_message_expiry_interval); + BOOST_TEST(*(will)[prop::content_type] == will_content_type); + BOOST_TEST(*(will)[prop::response_topic] == will_response_topic); + BOOST_TEST(*(will)[prop::correlation_data] == will_correlation_data); + BOOST_TEST_REQUIRE((will)[prop::user_property].size() == 1); + BOOST_TEST((will)[prop::user_property][0].first == will_user_property_1); + BOOST_TEST((will)[prop::user_property][0].second == will_user_property_2); } BOOST_AUTO_TEST_CASE(test_connack) { - // testing variables - uint8_t session_present = 1; - uint8_t reason_code = reason_codes::server_busy.value(); + // testing variables + uint8_t session_present = 1; + uint8_t reason_code = reason_codes::server_busy.value(); - uint32_t session_expiry_interval = 20; - uint16_t receive_maximum = 2000; - uint8_t max_qos = 2; - uint8_t retain_available = 0; - uint32_t maximum_packet_sz = 1024; - std::string assigned_client_id = "client_id"; - uint16_t topic_alias_max = 128; - std::string reason_string = "some reason string"; - std::string user_property_1 = "property"; - std::string user_property_2 = "property val"; - uint8_t wildcard_sub = 1; - uint8_t sub_id = 1; - uint8_t shared_sub = 0; - uint16_t server_keep_alive = 25; - std::string response_information = "info"; - std::string server_reference = "srv"; - std::string authentication_method = "method"; - std::string authentication_data = "data"; + uint32_t session_expiry_interval = 20; + uint16_t receive_maximum = 2000; + uint8_t max_qos = 2; + uint8_t retain_available = 0; + uint32_t maximum_packet_sz = 1024; + std::string assigned_client_id = "client_id"; + uint16_t topic_alias_max = 128; + std::string reason_string = "some reason string"; + std::string user_property_1 = "property"; + std::string user_property_2 = "property val"; + uint8_t wildcard_sub = 1; + uint8_t sub_id = 1; + uint8_t shared_sub = 0; + uint16_t server_keep_alive = 25; + std::string response_information = "info"; + std::string server_reference = "srv"; + std::string authentication_method = "method"; + std::string authentication_data = "data"; - connack_props cprops; - cprops[prop::session_expiry_interval] = session_expiry_interval; - cprops[prop::receive_maximum] = receive_maximum; - cprops[prop::maximum_qos] = max_qos; - cprops[prop::retain_available] = retain_available; - cprops[prop::maximum_packet_size] = maximum_packet_sz; - cprops[prop::assigned_client_identifier] = assigned_client_id; - cprops[prop::topic_alias_maximum] = topic_alias_max; - cprops[prop::reason_string] = reason_string; - cprops[prop::user_property].emplace_back(user_property_1, user_property_2); - cprops[prop::wildcard_subscription_available] = wildcard_sub; - cprops[prop::subscription_identifier_available] = sub_id; - cprops[prop::shared_subscription_available] = shared_sub; - cprops[prop::server_keep_alive] = server_keep_alive; - cprops[prop::response_information] = response_information; - cprops[prop::server_reference] = server_reference; - cprops[prop::authentication_method] = authentication_method; - cprops[prop::authentication_data] = authentication_data; + connack_props cprops; + cprops[prop::session_expiry_interval] = session_expiry_interval; + cprops[prop::receive_maximum] = receive_maximum; + cprops[prop::maximum_qos] = max_qos; + cprops[prop::retain_available] = retain_available; + cprops[prop::maximum_packet_size] = maximum_packet_sz; + cprops[prop::assigned_client_identifier] = assigned_client_id; + cprops[prop::topic_alias_maximum] = topic_alias_max; + cprops[prop::reason_string] = reason_string; + cprops[prop::user_property].emplace_back(user_property_1, user_property_2); + cprops[prop::wildcard_subscription_available] = wildcard_sub; + cprops[prop::subscription_identifier_available] = sub_id; + cprops[prop::shared_subscription_available] = shared_sub; + cprops[prop::server_keep_alive] = server_keep_alive; + cprops[prop::response_information] = response_information; + cprops[prop::server_reference] = server_reference; + cprops[prop::authentication_method] = authentication_method; + cprops[prop::authentication_data] = authentication_data; - auto msg = encoders::encode_connack(session_present, reason_code, cprops); + auto msg = encoders::encode_connack(session_present, reason_code, cprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_connack(remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_connack(remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [session_present_, reason_code_, cprops_] = *rv; - BOOST_TEST(session_present_ == session_present); - BOOST_TEST(reason_code_ == reason_code); + const auto& [session_present_, reason_code_, cprops_] = *rv; + BOOST_TEST(session_present_ == session_present); + BOOST_TEST(reason_code_ == reason_code); - cprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*cprops_[prop::session_expiry_interval] == session_expiry_interval); - BOOST_TEST(*cprops_[prop::receive_maximum] == receive_maximum); - BOOST_TEST(*cprops_[prop::maximum_qos] == max_qos); - BOOST_TEST(*cprops_[prop::retain_available] == retain_available); - BOOST_TEST(*cprops_[prop::maximum_packet_size] == maximum_packet_sz); - BOOST_TEST(*cprops_[prop::assigned_client_identifier] == assigned_client_id); - BOOST_TEST(*cprops_[prop::topic_alias_maximum] == topic_alias_max); - BOOST_TEST(*cprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1); - BOOST_TEST(cprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(cprops_[prop::user_property][0].second == user_property_2); - BOOST_TEST(*cprops_[prop::wildcard_subscription_available] == wildcard_sub); - BOOST_TEST(*cprops_[prop::subscription_identifier_available] == sub_id); - BOOST_TEST(*cprops_[prop::shared_subscription_available] == shared_sub); - BOOST_TEST(*cprops_[prop::server_keep_alive] == server_keep_alive); - BOOST_TEST(*cprops_[prop::response_information] == response_information); - BOOST_TEST(*cprops_[prop::server_reference] == server_reference); - BOOST_TEST(*cprops_[prop::authentication_method] == authentication_method); - BOOST_TEST(*cprops_[prop::authentication_data] == authentication_data); + cprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*cprops_[prop::session_expiry_interval] == session_expiry_interval); + BOOST_TEST(*cprops_[prop::receive_maximum] == receive_maximum); + BOOST_TEST(*cprops_[prop::maximum_qos] == max_qos); + BOOST_TEST(*cprops_[prop::retain_available] == retain_available); + BOOST_TEST(*cprops_[prop::maximum_packet_size] == maximum_packet_sz); + BOOST_TEST(*cprops_[prop::assigned_client_identifier] == assigned_client_id); + BOOST_TEST(*cprops_[prop::topic_alias_maximum] == topic_alias_max); + BOOST_TEST(*cprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1); + BOOST_TEST(cprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(cprops_[prop::user_property][0].second == user_property_2); + BOOST_TEST(*cprops_[prop::wildcard_subscription_available] == wildcard_sub); + BOOST_TEST(*cprops_[prop::subscription_identifier_available] == sub_id); + BOOST_TEST(*cprops_[prop::shared_subscription_available] == shared_sub); + BOOST_TEST(*cprops_[prop::server_keep_alive] == server_keep_alive); + BOOST_TEST(*cprops_[prop::response_information] == response_information); + BOOST_TEST(*cprops_[prop::server_reference] == server_reference); + BOOST_TEST(*cprops_[prop::authentication_method] == authentication_method); + BOOST_TEST(*cprops_[prop::authentication_data] == authentication_data); } 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!"; + // 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 payload_format_indicator = 1; - uint32_t message_expiry_interval = 70; - uint16_t topic_alias = 16; - std::string response_topic = "topic/response"; - std::string correlation_data = "correlation data"; - std::string publish_prop_1 = "key"; - std::string publish_prop_2 = "val"; - int32_t subscription_identifier = 123456; - std::string content_type = "application/octet-stream"; + uint8_t payload_format_indicator = 1; + uint32_t message_expiry_interval = 70; + uint16_t topic_alias = 16; + std::string response_topic = "topic/response"; + std::string correlation_data = "correlation data"; + std::string publish_prop_1 = "key"; + std::string publish_prop_2 = "val"; + int32_t subscription_identifier = 123456; + std::string content_type = "application/octet-stream"; - publish_props pprops; - pprops[prop::payload_format_indicator] = payload_format_indicator; - pprops[prop::message_expiry_interval] = message_expiry_interval; - pprops[prop::topic_alias] = topic_alias; - pprops[prop::response_topic] = response_topic; - pprops[prop::correlation_data] = correlation_data; - pprops[prop::user_property].emplace_back(publish_prop_1, publish_prop_2); - pprops[prop::subscription_identifier].push_back(subscription_identifier); - pprops[prop::content_type] = content_type; + publish_props pprops; + pprops[prop::payload_format_indicator] = payload_format_indicator; + pprops[prop::message_expiry_interval] = message_expiry_interval; + pprops[prop::topic_alias] = topic_alias; + pprops[prop::response_topic] = response_topic; + pprops[prop::correlation_data] = correlation_data; + pprops[prop::user_property].emplace_back(publish_prop_1, publish_prop_2); + pprops[prop::subscription_identifier].push_back(subscription_identifier); + pprops[prop::content_type] = content_type; - auto msg = encoders::encode_publish( - packet_id, topic, payload, - qos_e::at_least_once, retain_e::yes, dup_e::no, - pprops - ); + auto msg = encoders::encode_publish( + packet_id, topic, payload, + qos_e::at_least_once, retain_e::yes, dup_e::no, + pprops + ); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_publish(control_byte, remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_publish(control_byte, remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); - BOOST_TEST(topic_ == topic); - BOOST_TEST(payload_ == payload); + const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); + BOOST_TEST(topic_ == topic); + BOOST_TEST(payload_ == payload); - pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*pprops_[prop::payload_format_indicator] == payload_format_indicator); - BOOST_TEST(*pprops_[prop::message_expiry_interval] == message_expiry_interval); - BOOST_TEST(*pprops_[prop::topic_alias] == topic_alias); - BOOST_TEST(*pprops_[prop::response_topic] == response_topic); - BOOST_TEST(*pprops_[prop::correlation_data] == correlation_data); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); - BOOST_TEST(pprops_[prop::user_property][0].first == publish_prop_1); - BOOST_TEST(pprops_[prop::user_property][0].second == publish_prop_2); - BOOST_TEST_REQUIRE(pprops_[prop::subscription_identifier].size() == 1); - BOOST_TEST(pprops_[prop::subscription_identifier][0] == subscription_identifier); - BOOST_TEST(*pprops_[prop::content_type] == content_type); + pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*pprops_[prop::payload_format_indicator] == payload_format_indicator); + BOOST_TEST(*pprops_[prop::message_expiry_interval] == message_expiry_interval); + BOOST_TEST(*pprops_[prop::topic_alias] == topic_alias); + BOOST_TEST(*pprops_[prop::response_topic] == response_topic); + BOOST_TEST(*pprops_[prop::correlation_data] == correlation_data); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST(pprops_[prop::user_property][0].first == publish_prop_1); + BOOST_TEST(pprops_[prop::user_property][0].second == publish_prop_2); + BOOST_TEST_REQUIRE(pprops_[prop::subscription_identifier].size() == 1); + BOOST_TEST(pprops_[prop::subscription_identifier][0] == subscription_identifier); + BOOST_TEST(*pprops_[prop::content_type] == content_type); } BOOST_AUTO_TEST_CASE(test_large_publish) { - // testing variables - uint16_t packet_id = 40001; - std::string_view topic = "publish_topic"; - std::string large_payload(1'000'000, 'a'); + // testing variables + uint16_t packet_id = 40001; + std::string_view topic = "publish_topic"; + std::string large_payload(1'000'000, 'a'); - auto msg = encoders::encode_publish( - packet_id, topic, large_payload, - qos_e::at_least_once, retain_e::yes, dup_e::no, - publish_props {} - ); + auto msg = encoders::encode_publish( + packet_id, topic, large_payload, + qos_e::at_least_once, retain_e::yes, dup_e::no, + publish_props {} + ); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_publish(control_byte, remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_publish(control_byte, remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [topic_, packet_id_, flags, pprops, payload_] = *rv; - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); - BOOST_TEST(topic_ == topic); - BOOST_TEST(payload_ == large_payload); + const auto& [topic_, packet_id_, flags, pprops, payload_] = *rv; + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); + BOOST_TEST(topic_ == topic); + BOOST_TEST(payload_ == large_payload); } BOOST_AUTO_TEST_CASE(test_puback) { - // testing variables - uint16_t packet_id = 9199; - uint8_t reason_code = 0x93; + // testing variables + uint16_t packet_id = 9199; + uint8_t reason_code = 0x93; - std::string reason_string = "PUBACK reason string"; - std::string user_property_1 = "PUBACK user prop"; - std::string user_property_2 = "PUBACK user prop val"; + std::string reason_string = "PUBACK reason string"; + std::string user_property_1 = "PUBACK user prop"; + std::string user_property_2 = "PUBACK user prop val"; - puback_props pprops; - pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_property_1, user_property_2); + puback_props pprops; + pprops[prop::reason_string] = reason_string; + pprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_puback(packet_id, reason_code, pprops); + auto msg = encoders::encode_puback(packet_id, reason_code, pprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(reason_code_ == reason_code); - BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); - BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); + const auto& [reason_code_, pprops_] = *rv; + pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(reason_code_ == reason_code); + BOOST_TEST(*pprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_pubrec) { - // testing variables - uint16_t packet_id = 8534; - uint8_t reason_code = 0x92; + // testing variables + uint16_t packet_id = 8534; + uint8_t reason_code = 0x92; - std::string reason_string = "PUBREC reason string"; - std::string user_property_1 = "PUBREC user prop"; - std::string user_property_2 = "PUBREC user prop val"; + std::string reason_string = "PUBREC reason string"; + std::string user_property_1 = "PUBREC user prop"; + std::string user_property_2 = "PUBREC user prop val"; - pubrec_props pprops; - pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_property_1, user_property_2); + pubrec_props pprops; + pprops[prop::reason_string] = reason_string; + pprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_pubrec(packet_id, reason_code, pprops); + auto msg = encoders::encode_pubrec(packet_id, reason_code, pprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_pubrec(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_pubrec(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(reason_code_ == reason_code); - BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); - BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); + const auto& [reason_code_, pprops_] = *rv; + pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(reason_code_ == reason_code); + BOOST_TEST(*pprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_pubrel) { - // testing variables - uint16_t packet_id = 21455; - uint8_t reason_code = 0x00; + // testing variables + uint16_t packet_id = 21455; + uint8_t reason_code = 0x00; - std::string reason_string = "PUBREL reason string"; - std::string user_property_1 = "PUBREL user prop"; - std::string user_property_2 = "PUBREL user prop val"; + std::string reason_string = "PUBREL reason string"; + std::string user_property_1 = "PUBREL user prop"; + std::string user_property_2 = "PUBREL user prop val"; - pubrel_props pprops; - pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_property_1, user_property_2); + pubrel_props pprops; + pprops[prop::reason_string] = reason_string; + pprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_pubrel(packet_id, reason_code, pprops); + auto msg = encoders::encode_pubrel(packet_id, reason_code, pprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_pubrel(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_pubrel(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(reason_code_ == reason_code); - BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); - BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); + const auto& [reason_code_, pprops_] = *rv; + pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(reason_code_ == reason_code); + BOOST_TEST(*pprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_pubcomp) { - // testing variables - uint16_t packet_id = 21455; - uint8_t reason_code = 0x00; + // testing variables + uint16_t packet_id = 21455; + uint8_t reason_code = 0x00; - std::string reason_string = "PUBCOMP reason string"; - std::string user_property_1 = "PUBCOMP user prop"; - std::string user_property_2 = "PUBCOMP user prop val"; + std::string reason_string = "PUBCOMP reason string"; + std::string user_property_1 = "PUBCOMP user prop"; + std::string user_property_2 = "PUBCOMP user prop val"; - pubcomp_props pprops; - pprops[prop::reason_string] = reason_string; - pprops[prop::user_property].emplace_back(user_property_1, user_property_2); + pubcomp_props pprops; + pprops[prop::reason_string] = reason_string; + pprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_pubcomp(packet_id, reason_code, pprops); + auto msg = encoders::encode_pubcomp(packet_id, reason_code, pprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_pubcomp(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_pubcomp(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, pprops_] = *rv; - pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(reason_code_ == reason_code); - BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); - BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); + const auto& [reason_code_, pprops_] = *rv; + pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(reason_code_ == reason_code); + BOOST_TEST(*pprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_subscribe) { - //testing variables - int32_t sub_id = 1'234'567; - std::string user_property_1 = "SUBSCRIBE user prop"; - std::string user_property_2 = "SUBSCRIBE user prop val"; + //testing variables + int32_t sub_id = 1'234'567; + std::string user_property_1 = "SUBSCRIBE user prop"; + std::string user_property_2 = "SUBSCRIBE user prop val"; - qos_e qos = qos_e::at_least_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::not_send; + qos_e qos = qos_e::at_least_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::not_send; - subscribe_props sprops; - sprops[prop::subscription_identifier] = sub_id; - sprops[prop::user_property].emplace_back(user_property_1, user_property_2); + subscribe_props sprops; + sprops[prop::subscription_identifier] = sub_id; + sprops[prop::user_property].emplace_back(user_property_1, user_property_2); - std::vector filters { - { - "subscribe topic", - { qos, no_local, retain_as_published, retain_handling } - } - }; - uint16_t packet_id = 65535; + std::vector filters { + { + "subscribe topic", + { qos, no_local, retain_as_published, retain_handling } + } + }; + uint16_t packet_id = 65535; - auto msg = encoders::encode_subscribe(packet_id, filters, sprops); + auto msg = encoders::encode_subscribe(packet_id, filters, sprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); - auto rv = decoders::decode_subscribe(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); + auto rv = decoders::decode_subscribe(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [sprops_, filters_] = *rv; - const auto& filter_ = filters_[0]; - BOOST_TEST(std::get<0>(filter_) == filters[0].topic_filter); + const auto& [sprops_, filters_] = *rv; + const auto& filter_ = filters_[0]; + BOOST_TEST(std::get<0>(filter_) == filters[0].topic_filter); - uint8_t options_ = std::get<1>(filter_); - uint8_t mask = (static_cast(retain_handling) << 4) | - (static_cast(retain_as_published) << 3) | - (static_cast(no_local) << 2) | - static_cast(qos); - BOOST_TEST(options_ == mask); + uint8_t options_ = std::get<1>(filter_); + uint8_t mask = (static_cast(retain_handling) << 4) | + (static_cast(retain_as_published) << 3) | + (static_cast(no_local) << 2) | + static_cast(qos); + BOOST_TEST(options_ == mask); - sprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*sprops_[prop::subscription_identifier] == sub_id); - BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1); - BOOST_TEST(sprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(sprops_[prop::user_property][0].second == user_property_2); + sprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*sprops_[prop::subscription_identifier] == sub_id); + BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1); + BOOST_TEST(sprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(sprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_suback) { - //testing variables - uint16_t packet_id = 142; - std::vector reason_codes { 48, 28 }; + //testing variables + uint16_t packet_id = 142; + std::vector reason_codes { 48, 28 }; - std::string reason_string = "subscription accepted"; - std::string user_property_1 = "SUBACK user prop"; - std::string user_property_2 = "SUBACK user prop val"; + std::string reason_string = "subscription accepted"; + std::string user_property_1 = "SUBACK user prop"; + std::string user_property_2 = "SUBACK user prop val"; - suback_props sprops; - sprops[prop::reason_string] = reason_string; - sprops[prop::user_property].emplace_back(user_property_1, user_property_2); + suback_props sprops; + sprops[prop::reason_string] = reason_string; + sprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_suback(packet_id, reason_codes, sprops); + auto msg = encoders::encode_suback(packet_id, reason_codes, sprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); - auto rv = decoders::decode_suback(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); + auto rv = decoders::decode_suback(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [sprops_, reason_codes_] = *rv; - BOOST_TEST(reason_codes_ == reason_codes); + const auto& [sprops_, reason_codes_] = *rv; + BOOST_TEST(reason_codes_ == reason_codes); - sprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*sprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1); - BOOST_TEST(sprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(sprops_[prop::user_property][0].second == user_property_2); + sprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*sprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1); + BOOST_TEST(sprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(sprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_unsubscribe) { - // testing variables - uint16_t packet_id = 14423; - std::vector topics { "first topic", "second/topic" }; + // testing variables + uint16_t packet_id = 14423; + std::vector topics { "first topic", "second/topic" }; - std::string user_property_1 = "UNSUBSCRIBE user prop"; - std::string user_property_2 = "UNSUBSCRIBE user prop val"; + std::string user_property_1 = "UNSUBSCRIBE user prop"; + std::string user_property_2 = "UNSUBSCRIBE user prop val"; - unsubscribe_props uprops; - uprops[prop::user_property].emplace_back(user_property_1, user_property_2); + unsubscribe_props uprops; + uprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_unsubscribe(packet_id, topics, uprops); + auto msg = encoders::encode_unsubscribe(packet_id, topics, uprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); - auto rv = decoders::decode_unsubscribe(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); + auto rv = decoders::decode_unsubscribe(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [uprops_, topics_] = *rv; - BOOST_TEST(topics_ == topics); + const auto& [uprops_, topics_] = *rv; + BOOST_TEST(topics_ == topics); - uprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1); - BOOST_TEST(uprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(uprops_[prop::user_property][0].second == user_property_2); + uprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1); + BOOST_TEST(uprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(uprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_unsuback) { - // testing variables - uint16_t packet_id = 42; - std::vector reason_codes{ 48, 28 }; + // testing variables + uint16_t packet_id = 42; + std::vector reason_codes{ 48, 28 }; - std::string reason_string = "some unsuback reason string"; - std::string user_property_1 = "SUBACK user prop"; - std::string user_property_2 = "SUBACK user prop val"; + std::string reason_string = "some unsuback reason string"; + std::string user_property_1 = "SUBACK user prop"; + std::string user_property_2 = "SUBACK user prop val"; - unsuback_props uprops; - uprops[prop::reason_string] = reason_string; - uprops[prop::user_property].emplace_back(user_property_1, user_property_2); + unsuback_props uprops; + uprops[prop::reason_string] = reason_string; + uprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_unsuback(packet_id, reason_codes, uprops); + auto msg = encoders::encode_unsuback(packet_id, reason_codes, uprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - BOOST_TEST(*packet_id_ == packet_id); - auto rv = decoders::decode_unsuback(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + BOOST_TEST(*packet_id_ == packet_id); + auto rv = decoders::decode_unsuback(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [uprops_, reason_codes_] = *rv; - BOOST_TEST(reason_codes_ == reason_codes); + const auto& [uprops_, reason_codes_] = *rv; + BOOST_TEST(reason_codes_ == reason_codes); - uprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*uprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1); - BOOST_TEST(uprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(uprops_[prop::user_property][0].second == user_property_2); + uprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*uprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1); + BOOST_TEST(uprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(uprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_disconnect) { - // testing variables - uint8_t reason_code = 0x04; + // testing variables + uint8_t reason_code = 0x04; - uint32_t session_expiry_interval = 50; - std::string reason_string = "a reason"; - std::string user_property_1 = "DISCONNECT user prop"; - std::string user_property_2 = "DISCONNECT user prop val"; - std::string server_reference = "server"; + uint32_t session_expiry_interval = 50; + std::string reason_string = "a reason"; + std::string user_property_1 = "DISCONNECT user prop"; + std::string user_property_2 = "DISCONNECT user prop val"; + std::string server_reference = "server"; - disconnect_props dprops; - dprops[prop::session_expiry_interval] = session_expiry_interval; - dprops[prop::reason_string] = reason_string; - dprops[prop::user_property].emplace_back(user_property_1, user_property_2); - dprops[prop::server_reference] = server_reference; + disconnect_props dprops; + dprops[prop::session_expiry_interval] = session_expiry_interval; + dprops[prop::reason_string] = reason_string; + dprops[prop::user_property].emplace_back(user_property_1, user_property_2); + dprops[prop::server_reference] = server_reference; - auto msg = encoders::encode_disconnect(reason_code, dprops); + auto msg = encoders::encode_disconnect(reason_code, dprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_disconnect(remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_disconnect(remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, dprops_] = *rv; - BOOST_TEST(reason_code_ == reason_code); + const auto& [reason_code_, dprops_] = *rv; + BOOST_TEST(reason_code_ == reason_code); - dprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*dprops_[prop::session_expiry_interval] == session_expiry_interval); - BOOST_TEST(*dprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(dprops_[prop::user_property].size() == 1); - BOOST_TEST(dprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(dprops_[prop::user_property][0].second == user_property_2); - BOOST_TEST(*dprops_[prop::server_reference] == server_reference); + dprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*dprops_[prop::session_expiry_interval] == session_expiry_interval); + BOOST_TEST(*dprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(dprops_[prop::user_property].size() == 1); + BOOST_TEST(dprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(dprops_[prop::user_property][0].second == user_property_2); + BOOST_TEST(*dprops_[prop::server_reference] == server_reference); } BOOST_AUTO_TEST_CASE(test_auth) { - // testing variables - uint8_t reason_code = 0x18; + // testing variables + uint8_t reason_code = 0x18; - std::string authentication_method = "method"; - std::string authentication_data = "data"; + std::string authentication_method = "method"; + std::string authentication_data = "data"; - std::string reason_string = "reason"; - std::string user_property_1 = "AUTH user propety"; - std::string user_property_2 = "AUTH user propety val"; + std::string reason_string = "reason"; + std::string user_property_1 = "AUTH user propety"; + std::string user_property_2 = "AUTH user propety val"; - auth_props aprops; - aprops[prop::authentication_method] = authentication_method; - aprops[prop::authentication_data] = authentication_data; - aprops[prop::reason_string] = reason_string; - aprops[prop::user_property].emplace_back(user_property_1, user_property_2); + auth_props aprops; + aprops[prop::authentication_method] = authentication_method; + aprops[prop::authentication_data] = authentication_data; + aprops[prop::reason_string] = reason_string; + aprops[prop::user_property].emplace_back(user_property_1, user_property_2); - auto msg = encoders::encode_auth(reason_code, aprops); + auto msg = encoders::encode_auth(reason_code, aprops); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_auth(remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_auth(remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, aprops_] = *rv; - BOOST_TEST(reason_code_ == reason_code); + const auto& [reason_code_, aprops_] = *rv; + BOOST_TEST(reason_code_ == reason_code); - aprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST(*aprops_[prop::authentication_method] == authentication_method); - BOOST_TEST(*aprops_[prop::authentication_data] == authentication_data); - BOOST_TEST(*aprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(aprops_[prop::user_property].size() == 1); - BOOST_TEST(aprops_[prop::user_property][0].first == user_property_1); - BOOST_TEST(aprops_[prop::user_property][0].second == user_property_2); + aprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); + BOOST_TEST(*aprops_[prop::authentication_method] == authentication_method); + BOOST_TEST(*aprops_[prop::authentication_data] == authentication_data); + BOOST_TEST(*aprops_[prop::reason_string] == reason_string); + BOOST_TEST_REQUIRE(aprops_[prop::user_property].size() == 1); + BOOST_TEST(aprops_[prop::user_property][0].first == user_property_1); + BOOST_TEST(aprops_[prop::user_property][0].second == user_property_2); } BOOST_AUTO_TEST_CASE(test_pingreq) { - auto msg = encoders::encode_pingreq(); + auto msg = encoders::encode_pingreq(); - auto encoded_pingreq = std::string({ -64 /* 192 */, 0 }); - BOOST_TEST(msg == encoded_pingreq); + auto encoded_pingreq = std::string({ -64 /* 192 */, 0 }); + BOOST_TEST(msg == encoded_pingreq); } BOOST_AUTO_TEST_CASE(test_pingresp) { - auto msg = encoders::encode_pingresp(); + auto msg = encoders::encode_pingresp(); - auto encoded_pingresp = std::string({ -48 /* 208 */, 0 }); - BOOST_TEST(msg == encoded_pingresp); + auto encoded_pingresp = std::string({ -48 /* 208 */, 0 }); + BOOST_TEST(msg == encoded_pingresp); } BOOST_AUTO_TEST_CASE(subscription_identifiers) { - // check boost::container::small_vector interface - BOOST_TEST_REQUIRE(detail::is_small_vector); + // check boost::container::small_vector interface + BOOST_TEST_REQUIRE(detail::is_small_vector); - // check optional interface - prop::subscription_identifiers sub_ids; - sub_ids.emplace(40); - BOOST_TEST_REQUIRE(sub_ids.has_value()); - BOOST_TEST(*sub_ids == 40); - *sub_ids = 41; - BOOST_TEST(sub_ids.value() == 41); - BOOST_TEST(sub_ids.value_or(-1) == 41); - sub_ids.reset(); - BOOST_TEST(!sub_ids); - BOOST_TEST(sub_ids.value_or(-1) == -1); - sub_ids.emplace(); - BOOST_TEST(*sub_ids == 0); + // check optional interface + prop::subscription_identifiers sub_ids; + sub_ids.emplace(40); + BOOST_TEST_REQUIRE(sub_ids.has_value()); + BOOST_TEST(*sub_ids == 40); + *sub_ids = 41; + BOOST_TEST(sub_ids.value() == 41); + BOOST_TEST(sub_ids.value_or(-1) == 41); + sub_ids.reset(); + BOOST_TEST(!sub_ids); + BOOST_TEST(sub_ids.value_or(-1) == -1); + sub_ids.emplace(); + BOOST_TEST(*sub_ids == 0); } BOOST_AUTO_TEST_CASE(empty_user_property) { - publish_props pprops; - pprops[prop::user_property].emplace_back("", ""); + publish_props pprops; + pprops[prop::user_property].emplace_back("", ""); - auto msg = encoders::encode_publish( - 1, "topic", "payload", - qos_e::at_least_once, retain_e::yes, dup_e::no, - pprops - ); + auto msg = encoders::encode_publish( + 1, "topic", "payload", + qos_e::at_least_once, retain_e::yes, dup_e::no, + pprops + ); - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_publish(control_byte, remain_length, it); - BOOST_TEST_REQUIRE(rv.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_publish(control_byte, remain_length, it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; + const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; - auto user_props_ = pprops_[prop::user_property]; - BOOST_TEST_REQUIRE(user_props_.size() == 1); - BOOST_TEST(user_props_[0].first == ""); - BOOST_TEST(user_props_[0].second == ""); + auto user_props_ = pprops_[prop::user_property]; + BOOST_TEST_REQUIRE(user_props_.size() == 1); + BOOST_TEST(user_props_[0].first == ""); + BOOST_TEST(user_props_[0].second == ""); } BOOST_AUTO_TEST_CASE(deserialize_user_property) { - // testing variables - const char puback[] = { - 64, 15, 0, 1, 0, 11, 38, 0, 3, 'k', 'e', 'y', 0, 3, 'v', 'a', 'l' - }; - std::string msg { puback, sizeof(puback) / sizeof(char) }; + // testing variables + const char puback[] = { + 64, 15, 0, 1, 0, 11, 38, 0, 3, 'k', 'e', 'y', 0, 3, 'v', 'a', 'l' + }; + std::string msg { puback, sizeof(puback) / sizeof(char) }; - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, pprops_] = *rv; - auto user_props_ = pprops_[prop::user_property]; - BOOST_TEST_REQUIRE(user_props_.size() == 1); - BOOST_TEST(user_props_[0].first == "key"); - BOOST_TEST(user_props_[0].second == "val"); + const auto& [reason_code_, pprops_] = *rv; + auto user_props_ = pprops_[prop::user_property]; + BOOST_TEST_REQUIRE(user_props_.size() == 1); + BOOST_TEST(user_props_[0].first == "key"); + BOOST_TEST(user_props_[0].second == "val"); } BOOST_AUTO_TEST_CASE(deserialize_empty_user_property) { - // testing variables - const char puback[] = { - 64, 9, 0, 1, 0, 5, 38, 0, 0, 0, 0 - }; - std::string msg { puback, sizeof(puback) / sizeof(char) }; + // testing variables + const char puback[] = { + 64, 9, 0, 1, 0, 5, 38, 0, 0, 0, 0 + }; + std::string msg { puback, sizeof(puback) / sizeof(char) }; - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); - BOOST_TEST_REQUIRE(rv.has_value()); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_TEST_REQUIRE(rv.has_value()); - const auto& [reason_code_, pprops_] = *rv; - auto user_props_ = pprops_[prop::user_property]; - BOOST_TEST_REQUIRE(user_props_.size() == 1); - BOOST_TEST(user_props_[0].first == ""); - BOOST_TEST(user_props_[0].second == ""); + const auto& [reason_code_, pprops_] = *rv; + auto user_props_ = pprops_[prop::user_property]; + BOOST_TEST_REQUIRE(user_props_.size() == 1); + BOOST_TEST(user_props_[0].first == ""); + BOOST_TEST(user_props_[0].second == ""); } BOOST_AUTO_TEST_CASE(malformed_user_property) { - // testing variables - const char malformed_puback[] = { - 64, 10, 0, 1, 0, 6, 38, 0, 3, 'k', 'e', 'y' // missing value - }; - std::string msg { malformed_puback, sizeof(malformed_puback) / sizeof(char) }; + // testing variables + const char malformed_puback[] = { + 64, 10, 0, 1, 0, 6, 38, 0, 3, 'k', 'e', 'y' // missing value + }; + std::string msg { malformed_puback, sizeof(malformed_puback) / sizeof(char) }; - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); - BOOST_TEST(!rv); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_TEST(!rv); } BOOST_AUTO_TEST_CASE(malformed_reason_string) { - // testing variables - const char malformed_puback[] = { - 64, 6, 0, 1, 0, 2, 31, 1 - }; - std::string msg { malformed_puback, sizeof(malformed_puback) / sizeof(char) }; + // testing variables + const char malformed_puback[] = { + 64, 6, 0, 1, 0, 2, 31, 1 + }; + std::string msg { malformed_puback, sizeof(malformed_puback) / sizeof(char) }; - byte_citer it = msg.cbegin(), last = msg.cend(); - auto header = decoders::decode_fixed_header(it, last); - BOOST_TEST_REQUIRE(header.has_value()); - auto packet_id_ = decoders::decode_packet_id(it); - BOOST_TEST_REQUIRE(packet_id_.has_value()); - const auto& [control_byte, remain_length] = *header; - auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); - BOOST_TEST(!rv); + byte_citer it = msg.cbegin(), last = msg.cend(); + auto header = decoders::decode_fixed_header(it, last); + BOOST_TEST_REQUIRE(header.has_value()); + auto packet_id_ = decoders::decode_packet_id(it); + BOOST_TEST_REQUIRE(packet_id_.has_value()); + const auto& [control_byte, remain_length] = *header; + auto rv = decoders::decode_puback(remain_length - sizeof(uint16_t), it); + BOOST_TEST(!rv); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/session.cpp b/test/unit/session.cpp index 609e6c8..78e9538 100644 --- a/test/unit/session.cpp +++ b/test/unit/session.cpp @@ -5,62 +5,61 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include +#include +#include #include -#include #include +#include #include -#include -#include -#include +#include "test_common/test_service.hpp" -#include -#include - -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(session/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(session_state_session_present) { - detail::session_state session_state; + detail::session_state session_state; - BOOST_TEST(session_state.session_present() == false); - session_state.session_present(true); - BOOST_TEST(session_state.session_present() == true); - session_state.session_present(false); - BOOST_TEST(session_state.session_present() == false); + BOOST_TEST(session_state.session_present() == false); + session_state.session_present(true); + BOOST_TEST(session_state.session_present() == true); + session_state.session_present(false); + BOOST_TEST(session_state.session_present() == false); - BOOST_TEST(session_state.subscriptions_present() == false); - session_state.subscriptions_present(true); - BOOST_TEST(session_state.subscriptions_present() == true); - session_state.subscriptions_present(false); - BOOST_TEST(session_state.subscriptions_present() == false); + BOOST_TEST(session_state.subscriptions_present() == false); + session_state.subscriptions_present(true); + BOOST_TEST(session_state.subscriptions_present() == true); + session_state.subscriptions_present(false); + BOOST_TEST(session_state.subscriptions_present() == false); } BOOST_AUTO_TEST_CASE(clear_waiting_on_pubrel) { - asio::io_context ioc; - using client_service_type = test::test_service; - auto svc_ptr = std::make_shared(ioc.get_executor()); - svc_ptr->open_stream(); + asio::io_context ioc; + using client_service_type = test::test_service; + auto svc_ptr = std::make_shared(ioc.get_executor()); + svc_ptr->open_stream(); - decoders::publish_message pub_msg = std::make_tuple( - "topic", uint16_t(1), uint8_t(0b0100), publish_props {}, "payload" - ); + decoders::publish_message pub_msg = std::make_tuple( + "topic", uint16_t(1), uint8_t(0b0100), publish_props {}, "payload" + ); - detail::publish_rec_op { svc_ptr }.perform(pub_msg); + detail::publish_rec_op { svc_ptr }.perform(pub_msg); - // let publish_rec_op reach wait_on_pubrel stage - asio::steady_timer timer(ioc.get_executor()); - timer.expires_after(std::chrono::milliseconds(50)); - timer.async_wait([&svc_ptr](error_code) { - BOOST_TEST(svc_ptr.use_count() == 2); - svc_ptr->update_session_state(); // session_present = false - // publish_rec_op should complete - BOOST_TEST(svc_ptr.use_count() == 1); - }); + // let publish_rec_op reach wait_on_pubrel stage + asio::steady_timer timer(ioc.get_executor()); + timer.expires_after(std::chrono::milliseconds(50)); + timer.async_wait([&svc_ptr](error_code) { + BOOST_TEST(svc_ptr.use_count() == 2); + svc_ptr->update_session_state(); // session_present = false + // publish_rec_op should complete + BOOST_TEST(svc_ptr.use_count() == 1); + }); - ioc.run(); + ioc.run(); } diff --git a/test/unit/string_validation.cpp b/test/unit/string_validation.cpp index 269cd98..c408821 100644 --- a/test/unit/string_validation.cpp +++ b/test/unit/string_validation.cpp @@ -5,148 +5,148 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#include + #include #include -#include -#include - BOOST_AUTO_TEST_SUITE(utf8_mqtt/*, *boost::unit_test::disabled()*/) std::string to_str(int utf8ch) { - if (utf8ch < 0x80) - return { char(utf8ch) }; - if (utf8ch < 0x800) - return { - char((utf8ch >> 6) | 0xC0), - char((utf8ch & 0x3F) | 0x80) - }; - if (utf8ch < 0xFFFF) - return { - char((utf8ch >> 12) | 0xE0), - char(((utf8ch >> 6) & 0x3F) | 0x80), - char((utf8ch & 0x3F) | 0x80) - }; - return { - char((utf8ch >> 18) | 0xF0), - char(((utf8ch >> 12) & 0x3F) | 0x80), - char(((utf8ch >> 6) & 0x3F) | 0x80), - char((utf8ch & 0x3F) | 0x80) - }; + if (utf8ch < 0x80) + return { char(utf8ch) }; + if (utf8ch < 0x800) + return { + char((utf8ch >> 6) | 0xC0), + char((utf8ch & 0x3F) | 0x80) + }; + if (utf8ch < 0xFFFF) + return { + char((utf8ch >> 12) | 0xE0), + char(((utf8ch >> 6) & 0x3F) | 0x80), + char((utf8ch & 0x3F) | 0x80) + }; + return { + char((utf8ch >> 18) | 0xF0), + char(((utf8ch >> 12) & 0x3F) | 0x80), + char(((utf8ch >> 6) & 0x3F) | 0x80), + char((utf8ch & 0x3F) | 0x80) + }; } BOOST_AUTO_TEST_CASE(utf8_string_validation) { - using namespace async_mqtt5::detail; + using namespace boost::mqtt5::detail; - BOOST_CHECK(validate_mqtt_utf8("stringy") == validation_result::valid); - BOOST_CHECK(validate_mqtt_utf8("") == validation_result::valid); - BOOST_CHECK(validate_mqtt_utf8(std::string(75000, 'a')) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8("stringy") == validation_result::valid); + BOOST_CHECK(validate_mqtt_utf8("") == validation_result::valid); + BOOST_CHECK(validate_mqtt_utf8(std::string(75000, 'a')) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x1)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x1F)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x20)) == validation_result::valid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x7E)) == validation_result::valid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x7F)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x9F)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0xA0)) == validation_result::valid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0xD800)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0xDFFF)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0xFDD0)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0xFDEF)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0xFDF0)) == validation_result::valid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x1FFFE)) == validation_result::invalid); - BOOST_CHECK(validate_mqtt_utf8(to_str(0x1FFFF)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x1)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x1F)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x20)) == validation_result::valid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x7E)) == validation_result::valid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x7F)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x9F)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0xA0)) == validation_result::valid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0xD800)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0xDFFF)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0xFDD0)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0xFDEF)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0xFDF0)) == validation_result::valid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x1FFFE)) == validation_result::invalid); + BOOST_CHECK(validate_mqtt_utf8(to_str(0x1FFFF)) == validation_result::invalid); } BOOST_AUTO_TEST_CASE(topic_filter_validation) { - using namespace async_mqtt5::detail; + using namespace boost::mqtt5::detail; - BOOST_CHECK(validate_topic_filter("") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("topic") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("topic/subtopic") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("topic") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("topic/subtopic") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("#") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("#sport") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport#") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport/#/tennis") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("#/sport") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("spo#rt/#") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport/#") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("sport/tennis/#") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("sport/tennis#") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("#") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("#sport") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport#") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport/#/tennis") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("#/sport") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("spo#rt/#") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport/#") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("sport/tennis/#") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("sport/tennis#") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("+") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("+/") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("/+") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("+/+") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("+/+/+") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("+sport") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport+") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport+/player1") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport/+player1") == validation_result::invalid); - BOOST_CHECK(validate_topic_filter("sport/+") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("sport/+/player1") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("+/sport/+/player1/+") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+/") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("/+") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+/+") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+/+/+") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+sport") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport+") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport+/player1") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport/+player1") == validation_result::invalid); + BOOST_CHECK(validate_topic_filter("sport/+") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("sport/+/player1") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+/sport/+/player1/+") == validation_result::valid); - BOOST_CHECK(validate_topic_filter("+/tennis/#") == validation_result::valid); + BOOST_CHECK(validate_topic_filter("+/tennis/#") == validation_result::valid); } BOOST_AUTO_TEST_CASE(topic_name_validation) { - using namespace async_mqtt5::detail; + using namespace boost::mqtt5::detail; - BOOST_CHECK(validate_topic_name("") == validation_result::invalid); - BOOST_CHECK(validate_topic_name("topic") == validation_result::valid); - BOOST_CHECK(validate_topic_name("topic/subtopic") == validation_result::valid); + BOOST_CHECK(validate_topic_name("") == validation_result::invalid); + BOOST_CHECK(validate_topic_name("topic") == validation_result::valid); + BOOST_CHECK(validate_topic_name("topic/subtopic") == validation_result::valid); - BOOST_CHECK(validate_topic_name("#") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("sport#") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("sport/#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("sport#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("sport/#") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("+") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("+sport") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("sport+") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("sport/+/player1") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("+") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("+sport") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("sport+") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("sport/+/player1") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_name("+/tennis/#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_name("+/tennis/#") == validation_result::has_wildcard_character); } BOOST_AUTO_TEST_CASE(topic_alias_name_validation) { - using namespace async_mqtt5::detail; + using namespace boost::mqtt5::detail; - BOOST_CHECK(validate_topic_alias_name("") == validation_result::valid); - BOOST_CHECK(validate_topic_alias_name("topic") == validation_result::valid); - BOOST_CHECK(validate_topic_alias_name("topic/subtopic") == validation_result::valid); + BOOST_CHECK(validate_topic_alias_name("") == validation_result::valid); + BOOST_CHECK(validate_topic_alias_name("topic") == validation_result::valid); + BOOST_CHECK(validate_topic_alias_name("topic/subtopic") == validation_result::valid); - BOOST_CHECK(validate_topic_alias_name("#") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("sport#") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("sport/#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("sport#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("sport/#") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("+") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("+sport") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("sport+") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("sport/+/player1") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("+") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("+sport") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("sport+") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("sport/+/player1") == validation_result::has_wildcard_character); - BOOST_CHECK(validate_topic_alias_name("+/tennis/#") == validation_result::has_wildcard_character); + BOOST_CHECK(validate_topic_alias_name("+/tennis/#") == validation_result::has_wildcard_character); } BOOST_AUTO_TEST_CASE(shared_topic_filter_validation) { - using namespace async_mqtt5::detail; + using namespace boost::mqtt5::detail; - BOOST_CHECK(validate_shared_topic_filter("") == validation_result::invalid); - BOOST_CHECK(validate_shared_topic_filter("$shared/grp/topic") == validation_result::invalid); - BOOST_CHECK(validate_shared_topic_filter("$share//grp/topic") == validation_result::invalid); - BOOST_CHECK(validate_shared_topic_filter("$share/grp+/topic") == validation_result::invalid); - BOOST_CHECK(validate_shared_topic_filter("$share/#grp/topic") == validation_result::invalid); + BOOST_CHECK(validate_shared_topic_filter("") == validation_result::invalid); + BOOST_CHECK(validate_shared_topic_filter("$shared/grp/topic") == validation_result::invalid); + BOOST_CHECK(validate_shared_topic_filter("$share//grp/topic") == validation_result::invalid); + BOOST_CHECK(validate_shared_topic_filter("$share/grp+/topic") == validation_result::invalid); + BOOST_CHECK(validate_shared_topic_filter("$share/#grp/topic") == validation_result::invalid); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic") == validation_result::valid); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/#") == validation_result::valid); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/+/topic/#") == validation_result::valid); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/+") == validation_result::valid); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic") == validation_result::valid); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/#") == validation_result::valid); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/+/topic/#") == validation_result::valid); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/+") == validation_result::valid); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/#", false) == validation_result::has_wildcard_character); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/+/topic/#", false) == validation_result::has_wildcard_character); - BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/+", false) == validation_result::has_wildcard_character); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/#", false) == validation_result::has_wildcard_character); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/+/topic/#", false) == validation_result::has_wildcard_character); + BOOST_CHECK(validate_shared_topic_filter("$share/grp/topic/+", false) == validation_result::has_wildcard_character); } BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/subscribe_op.cpp b/test/unit/subscribe_op.cpp index e8046df..f4f13f4 100644 --- a/test/unit/subscribe_op.cpp +++ b/test/unit/subscribe_op.cpp @@ -5,196 +5,196 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include + +#include + +#include +#include #include -#include #include +#include #include #include #include -#include -#include - -#include -#include - #include "test_common/test_service.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(subscribe_op/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(pid_overrun) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::overrun_client; - auto svc_ptr = std::make_shared(ioc.get_executor()); + asio::io_context ioc; + using client_service_type = test::overrun_client; + auto svc_ptr = std::make_shared(ioc.get_executor()); - auto handler = [&handlers_called](error_code ec, std::vector rcs, suback_props) { - ++handlers_called; - BOOST_TEST(ec == client::error::pid_overrun); - BOOST_TEST_REQUIRE(rcs.size() == 1); - BOOST_TEST(rcs[0] == reason_codes::empty); - }; + auto handler = [&handlers_called](error_code ec, std::vector rcs, suback_props) { + ++handlers_called; + BOOST_TEST(ec == client::error::pid_overrun); + BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST(rcs[0] == reason_codes::empty); + }; - detail::subscribe_op< - client_service_type, decltype(handler) - > { svc_ptr, std::move(handler) } - .perform( - { { "topic", { qos_e::exactly_once } } }, subscribe_props {} - ); + detail::subscribe_op< + client_service_type, decltype(handler) + > { svc_ptr, std::move(handler) } + .perform( + { { "topic", { qos_e::exactly_once } } }, subscribe_props {} + ); - ioc.run_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } void run_test( - error_code expected_ec, const std::vector& topics, - const subscribe_props& sprops = {}, const connack_props& cprops = {} + error_code expected_ec, const std::vector& topics, + const subscribe_props& sprops = {}, const connack_props& cprops = {} ) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::test_service; - auto svc_ptr = std::make_shared(ioc.get_executor(), cprops); + asio::io_context ioc; + using client_service_type = test::test_service; + auto svc_ptr = std::make_shared(ioc.get_executor(), cprops); - auto handler = [&handlers_called, expected_ec, num_tp = topics.size()] - (error_code ec, std::vector rcs, suback_props) { - ++handlers_called; + auto handler = [&handlers_called, expected_ec, num_tp = topics.size()] + (error_code ec, std::vector rcs, suback_props) { + ++handlers_called; - BOOST_TEST(ec == expected_ec); - BOOST_TEST_REQUIRE(rcs.size() == num_tp); + BOOST_TEST(ec == expected_ec); + BOOST_TEST_REQUIRE(rcs.size() == num_tp); - for (size_t i = 0; i < rcs.size(); ++i) - BOOST_TEST(rcs[i] == reason_codes::empty); - }; + for (size_t i = 0; i < rcs.size(); ++i) + BOOST_TEST(rcs[i] == reason_codes::empty); + }; - detail::subscribe_op< - client_service_type, decltype(handler) - > { svc_ptr, std::move(handler) } - .perform(topics, sprops); + detail::subscribe_op< + client_service_type, decltype(handler) + > { svc_ptr, std::move(handler) } + .perform(topics, sprops); - ioc.run_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } void run_test( - error_code expected_ec, const std::string& topic, - const subscribe_props& sprops = {}, const connack_props& cprops = {} + error_code expected_ec, const std::string& topic, + const subscribe_props& sprops = {}, const connack_props& cprops = {} ) { - auto sub_topic = subscribe_topic { topic, subscribe_options() }; - return run_test( - expected_ec, - std::vector { std::move(sub_topic) }, - sprops, cprops - ); + auto sub_topic = subscribe_topic { topic, subscribe_options() }; + return run_test( + expected_ec, + std::vector { std::move(sub_topic) }, + sprops, cprops + ); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_1) { - run_test(client::error::invalid_topic, ""); + run_test(client::error::invalid_topic, ""); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_2) { - run_test(client::error::invalid_topic, "+topic"); + run_test(client::error::invalid_topic, "+topic"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_3) { - run_test(client::error::invalid_topic, "topic+"); + run_test(client::error::invalid_topic, "topic+"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_4) { - run_test(client::error::invalid_topic, "#topic"); + run_test(client::error::invalid_topic, "#topic"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_5) { - run_test(client::error::invalid_topic, "some/#/topic"); + run_test(client::error::invalid_topic, "some/#/topic"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_6) { - run_test(client::error::invalid_topic, "$share//topic#"); + run_test(client::error::invalid_topic, "$share//topic#"); } BOOST_AUTO_TEST_CASE(malformed_user_property_1) { - subscribe_props sprops; - sprops[prop::user_property].emplace_back("key", std::string(10, char(0x01))); + subscribe_props sprops; + sprops[prop::user_property].emplace_back("key", std::string(10, char(0x01))); - run_test(client::error::malformed_packet, "topic", sprops); + run_test(client::error::malformed_packet, "topic", sprops); } BOOST_AUTO_TEST_CASE(malformed_user_property_2) { - subscribe_props sprops; - sprops[prop::user_property].emplace_back("key", std::string(75000, 'a')); + subscribe_props sprops; + sprops[prop::user_property].emplace_back("key", std::string(75000, 'a')); - run_test(client::error::malformed_packet, "topic", sprops); + run_test(client::error::malformed_packet, "topic", sprops); } BOOST_AUTO_TEST_CASE(wildcard_subscriptions_not_available_1) { - connack_props cprops; - cprops[prop::wildcard_subscription_available] = uint8_t(0); + connack_props cprops; + cprops[prop::wildcard_subscription_available] = uint8_t(0); - run_test( - client::error::wildcard_subscription_not_available, "topic/#", - subscribe_props {}, cprops - ); + run_test( + client::error::wildcard_subscription_not_available, "topic/#", + subscribe_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(wildcard_subscriptions_not_available_2) { - connack_props cprops; - cprops[prop::wildcard_subscription_available] = uint8_t(0); + connack_props cprops; + cprops[prop::wildcard_subscription_available] = uint8_t(0); - run_test( - client::error::wildcard_subscription_not_available, "$share/grp/topic/#", - subscribe_props {}, cprops - ); + run_test( + client::error::wildcard_subscription_not_available, "$share/grp/topic/#", + subscribe_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(shared_subscriptions_not_available) { - connack_props cprops; - cprops[prop::shared_subscription_available] = uint8_t(0); + connack_props cprops; + cprops[prop::shared_subscription_available] = uint8_t(0); - run_test( - client::error::shared_subscription_not_available, "$share/group/topic", - subscribe_props {}, cprops - ); + run_test( + client::error::shared_subscription_not_available, "$share/group/topic", + subscribe_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(subscription_id_not_available) { - connack_props cprops; - cprops[prop::subscription_identifier_available] = uint8_t(0); + connack_props cprops; + cprops[prop::subscription_identifier_available] = uint8_t(0); - subscribe_props sprops; - sprops[prop::subscription_identifier] = 23; + subscribe_props sprops; + sprops[prop::subscription_identifier] = 23; - run_test( - client::error::subscription_identifier_not_available, "topic", sprops, cprops - ); + run_test( + client::error::subscription_identifier_not_available, "topic", sprops, cprops + ); } BOOST_AUTO_TEST_CASE(large_subscription_id) { - connack_props cprops; - cprops[prop::subscription_identifier_available] = uint8_t(1); + connack_props cprops; + cprops[prop::subscription_identifier_available] = uint8_t(1); - subscribe_props sprops; - sprops[prop::subscription_identifier] = std::numeric_limits::max(); + subscribe_props sprops; + sprops[prop::subscription_identifier] = std::numeric_limits::max(); - run_test(client::error::malformed_packet, "topic", sprops, cprops); + run_test(client::error::malformed_packet, "topic", sprops, cprops); } BOOST_AUTO_TEST_CASE(packet_too_large) { - connack_props cprops; - cprops[prop::maximum_packet_size] = 10; + connack_props cprops; + cprops[prop::maximum_packet_size] = 10; - run_test( - client::error::packet_too_large, "very large topic", subscribe_props {}, cprops - ); + run_test( + client::error::packet_too_large, "very large topic", subscribe_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(zero_topic_filters) { - run_test(client::error::invalid_topic, std::vector {}); + run_test(client::error::invalid_topic, std::vector {}); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/traits.cpp b/test/unit/traits.cpp index 49598d4..991b85f 100644 --- a/test/unit/traits.cpp +++ b/test/unit/traits.cpp @@ -5,59 +5,56 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include +#include -#include -#include -#include +#include +#include #include #include #include #include - #include - +#include #include -#include -#include +#include +#include +#include -#include - -using namespace async_mqtt5; +using namespace boost::mqtt5; struct good_authenticator { - good_authenticator() = default; + good_authenticator() = default; - template - decltype(auto) async_auth( - auth_step_e step, std::string data, - CompletionToken&& token - ) { - using error_code = boost::system::error_code; - using Signature = void (error_code, std::string); + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); - auto initiate = [](auto, auth_step_e, std::string) {}; + auto initiate = [](auto, auth_step_e, std::string) {}; - return asio::async_initiate( - initiate, token, step, std::move(data) - ); - } + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } - std::string_view method() const { - return "method"; - } + std::string_view method() const { + return "method"; + } }; struct bad_authenticator { - bad_authenticator() = default; + bad_authenticator() = default; - void async_auth(std::string /* data */) {} + void async_auth(std::string /* data */) {} - std::string_view method() const { - return "method"; - } + std::string_view method() const { + return "method"; + } }; @@ -109,47 +106,47 @@ BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); void tcp_layers_test() { - asio::system_executor ex; - tcp_layer layer(ex); + asio::system_executor ex; + tcp_layer layer(ex); - detail::next_layer_type& nlayer = detail::next_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::next_layer_type& nlayer = detail::next_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); - detail::lowest_layer_type& llayer = detail::lowest_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::lowest_layer_type& llayer = detail::lowest_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); } void tls_layers_test() { - asio::system_executor ex; - asio::ssl::context ctx(asio::ssl::context::tls_client); - tls_layer layer(ex, ctx); + asio::system_executor ex; + asio::ssl::context ctx(asio::ssl::context::tls_client); + tls_layer layer(ex, ctx); - detail::next_layer_type& nlayer = detail::next_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::next_layer_type& nlayer = detail::next_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); - detail::lowest_layer_type& llayer = detail::lowest_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::lowest_layer_type& llayer = detail::lowest_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); } void websocket_tcp_layers_test() { - asio::system_executor ex; - websocket_tcp_layer layer(ex); + asio::system_executor ex; + websocket_tcp_layer layer(ex); - detail::next_layer_type& nlayer = detail::next_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::next_layer_type& nlayer = detail::next_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); - detail::lowest_layer_type& llayer = detail::lowest_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::lowest_layer_type& llayer = detail::lowest_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); } void websocket_tls_layers_test() { - asio::system_executor ex; - asio::ssl::context ctx(asio::ssl::context::tls_client); - websocket_tls_layer layer(ex, ctx); + asio::system_executor ex; + asio::ssl::context ctx(asio::ssl::context::tls_client); + websocket_tls_layer layer(ex, ctx); - detail::next_layer_type& nlayer = detail::next_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tls_layer>); + detail::next_layer_type& nlayer = detail::next_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tls_layer>); - detail::lowest_layer_type& llayer = detail::lowest_layer(layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + detail::lowest_layer_type& llayer = detail::lowest_layer(layer); + BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); } diff --git a/test/unit/unsubscribe_op.cpp b/test/unit/unsubscribe_op.cpp index 059c69c..61f553c 100644 --- a/test/unit/unsubscribe_op.cpp +++ b/test/unit/unsubscribe_op.cpp @@ -5,140 +5,140 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include + +#include + +#include +#include #include -#include #include +#include #include #include #include -#include -#include - -#include -#include - #include "test_common/test_service.hpp" -using namespace async_mqtt5; +using namespace boost::mqtt5; BOOST_AUTO_TEST_SUITE(unsubscribe_op/*, *boost::unit_test::disabled()*/) BOOST_AUTO_TEST_CASE(pid_overrun) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::overrun_client; - auto svc_ptr = std::make_shared(ioc.get_executor()); + asio::io_context ioc; + using client_service_type = test::overrun_client; + auto svc_ptr = std::make_shared(ioc.get_executor()); - auto handler = [&handlers_called](error_code ec, std::vector rcs, unsuback_props) { - ++handlers_called; - BOOST_TEST(ec == client::error::pid_overrun); - BOOST_TEST_REQUIRE(rcs.size() == 1); - BOOST_TEST(rcs[0] == reason_codes::empty); - }; + auto handler = [&handlers_called](error_code ec, std::vector rcs, unsuback_props) { + ++handlers_called; + BOOST_TEST(ec == client::error::pid_overrun); + BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST(rcs[0] == reason_codes::empty); + }; - detail::unsubscribe_op< - client_service_type, decltype(handler) - > { svc_ptr, std::move(handler) } - .perform({ "topic" }, unsubscribe_props {}); + detail::unsubscribe_op< + client_service_type, decltype(handler) + > { svc_ptr, std::move(handler) } + .perform({ "topic" }, unsubscribe_props {}); - ioc.run_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } void run_test( - error_code expected_ec, const std::vector& topics, - const unsubscribe_props& uprops = {}, const connack_props& cprops = {} + error_code expected_ec, const std::vector& topics, + const unsubscribe_props& uprops = {}, const connack_props& cprops = {} ) { - constexpr int expected_handlers_called = 1; - int handlers_called = 0; + constexpr int expected_handlers_called = 1; + int handlers_called = 0; - asio::io_context ioc; - using client_service_type = test::test_service; - auto svc_ptr = std::make_shared(ioc.get_executor(), cprops); + asio::io_context ioc; + using client_service_type = test::test_service; + auto svc_ptr = std::make_shared(ioc.get_executor(), cprops); - auto handler = [&handlers_called, expected_ec, num_tp = topics.size()] - (error_code ec, std::vector rcs, unsuback_props) { - ++handlers_called; + auto handler = [&handlers_called, expected_ec, num_tp = topics.size()] + (error_code ec, std::vector rcs, unsuback_props) { + ++handlers_called; - BOOST_TEST(ec == expected_ec); - BOOST_TEST_REQUIRE(rcs.size() == num_tp); + BOOST_TEST(ec == expected_ec); + BOOST_TEST_REQUIRE(rcs.size() == num_tp); - for (size_t i = 0; i < rcs.size(); ++i) - BOOST_TEST(rcs[i] == reason_codes::empty); - }; + for (size_t i = 0; i < rcs.size(); ++i) + BOOST_TEST(rcs[i] == reason_codes::empty); + }; - detail::unsubscribe_op< - client_service_type, decltype(handler) - > { svc_ptr, std::move(handler) } - .perform(topics, uprops); + detail::unsubscribe_op< + client_service_type, decltype(handler) + > { svc_ptr, std::move(handler) } + .perform(topics, uprops); - ioc.run_for(std::chrono::milliseconds(500)); - BOOST_TEST(handlers_called == expected_handlers_called); + ioc.run_for(std::chrono::milliseconds(500)); + BOOST_TEST(handlers_called == expected_handlers_called); } void run_test( - error_code expected_ec, const std::string& topic, - const unsubscribe_props& uprops = {}, const connack_props& cprops = {} + error_code expected_ec, const std::string& topic, + const unsubscribe_props& uprops = {}, const connack_props& cprops = {} ) { - return run_test( - expected_ec, std::vector { topic }, uprops, cprops - ); + return run_test( + expected_ec, std::vector { topic }, uprops, cprops + ); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_1) { - run_test(client::error::invalid_topic, ""); + run_test(client::error::invalid_topic, ""); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_2) { - run_test(client::error::invalid_topic, "+topic"); + run_test(client::error::invalid_topic, "+topic"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_3) { - run_test(client::error::invalid_topic, "topic+"); + run_test(client::error::invalid_topic, "topic+"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_4) { - run_test(client::error::invalid_topic, "#topic"); + run_test(client::error::invalid_topic, "#topic"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_5) { - run_test(client::error::invalid_topic, "some/#/topic"); + run_test(client::error::invalid_topic, "some/#/topic"); } BOOST_AUTO_TEST_CASE(invalid_topic_filter_6) { - run_test(client::error::invalid_topic, "some/topic#"); + run_test(client::error::invalid_topic, "some/topic#"); } BOOST_AUTO_TEST_CASE(malformed_user_property_1) { - unsubscribe_props uprops; - uprops[prop::user_property].emplace_back("key", std::string(10, char(0x01))); + unsubscribe_props uprops; + uprops[prop::user_property].emplace_back("key", std::string(10, char(0x01))); - run_test(client::error::malformed_packet, "topic", uprops); + run_test(client::error::malformed_packet, "topic", uprops); } BOOST_AUTO_TEST_CASE(malformed_user_property_2) { - unsubscribe_props uprops; - uprops[prop::user_property].emplace_back("key", std::string(75000, 'a')); + unsubscribe_props uprops; + uprops[prop::user_property].emplace_back("key", std::string(75000, 'a')); - run_test(client::error::malformed_packet, "topic", uprops); + run_test(client::error::malformed_packet, "topic", uprops); } BOOST_AUTO_TEST_CASE(packet_too_large) { - connack_props cprops; - cprops[prop::maximum_packet_size] = 10; + connack_props cprops; + cprops[prop::maximum_packet_size] = 10; - run_test( - client::error::packet_too_large, "very large topic", - unsubscribe_props {}, cprops - ); + run_test( + client::error::packet_too_large, "very large topic", + unsubscribe_props {}, cprops + ); } BOOST_AUTO_TEST_CASE(zero_topic_filters) { - run_test(client::error::invalid_topic, std::vector {}); + run_test(client::error::invalid_topic, std::vector {}); } BOOST_AUTO_TEST_SUITE_END()