Async.MQTT5 -> Boost.MQTT5

Summary:
related to T15996

folder structure include/async_mqtt5 -> include/boost/mqtt5
namespace async_mqtt5 -> namespace boost::mqtt5
all tabs replaced with 4 spaces (because tabs are banned)
boost-like order of includes

TODO: fix all docs

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33152
This commit is contained in:
Korina Šimičević
2025-01-13 16:11:41 +01:00
parent 1225cc778a
commit afc270f10e
136 changed files with 17834 additions and 17859 deletions

View File

@ -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 <iostream>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
#include <iostream>
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.brokers("<your-mqtt-broker>", 1883)
.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.async_run(boost::asio::detached);
c.async_publish<async_mqtt5::qos_e::at_most_once>(
c.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "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
}

View File

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

View File

@ -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 <iostream>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.brokers("<your-mqtt-broker>", 1883)
c.brokers("<your-mqtt-broker>", 1883)
.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.async_run(boost::asio::detached);
c.async_publish<async_mqtt5::qos_e::at_most_once>(
c.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "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();
}

View File

@ -9,97 +9,97 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[hello_world_in_coro_multithreaded_env
#include <vector>
#include <string>
#include <thread>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
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<boost::asio::ip::tcp::socket>;
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
// 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<void> publish_hello_world(
const config& cfg, client_type& client,
const boost::asio::strand<boost::asio::thread_pool::executor_type>& strand
const config& cfg, client_type& client,
const boost::asio::strand<boost::asio::thread_pool::executor_type>& 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::qos_e::at_least_once>(
"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<boost::mqtt5::qos_e::at_least_once>(
"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 <iostream>
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

View File

@ -6,101 +6,101 @@
//
//[hello_world_in_multithreaded_env
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/thread_pool.hpp>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
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::qos_e::at_least_once>(
"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<boost::mqtt5::qos_e::at_least_once>(
"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;
}
//]

View File

@ -6,64 +6,64 @@
//
//[hello_world_over_tcp
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <string>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
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<boost::asio::ip::tcp::socket> client(ioc);
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> 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::qos_e::at_most_once>(
"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<boost::mqtt5::qos_e::at_most_once>(
"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();
}
//]

View File

@ -6,96 +6,96 @@
//
//[hello_world_over_tls
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>
#include <string>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
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 <typename StreamBase>
struct tls_handshake_type<boost::asio::ssl::stream<StreamBase>> {
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 <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& /* ctx */,
boost::asio::ssl::stream<StreamBase>& stream
const authority_path& ap,
boost::asio::ssl::context& /* ctx */,
boost::asio::ssl::stream<StreamBase>& 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::ip::tcp::socket>,
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::ip::tcp::socket>,
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::ip::tcp::socket>,
// 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::ip::tcp::socket>,
// 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::qos_e::at_most_once>(
"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<boost/mqtt5::qos_e::at_most_once>(
"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();
}
//]

View File

@ -6,62 +6,60 @@
//
//[hello_world_over_websocket_tcp
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/websocket.hpp> // WebSocket traits
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>
#include <string>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/websocket.hpp> // 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<boost::asio::ip::tcp::socket>,
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<boost::asio::ip::tcp::socket>,
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<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> client(ioc);
// 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::ip::tcp::socket>> 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::qos_e::at_most_once>(
"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<boost::mqtt5::qos_e::at_most_once>(
"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();
}
//]

View File

@ -6,99 +6,97 @@
//
//[hello_world_over_websocket_tls
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/websocket.hpp> // WebSocket traits
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/ssl/ssl_stream.hpp> // async_teardown specialization for WebSocket SSL stream
#include <boost/beast/websocket.hpp>
#include <iostream>
#include <string>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/ssl/ssl_stream.hpp> // async_teardown specialization for WebSocket SSL stream
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/websocket.hpp> // 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 <typename StreamBase>
struct tls_handshake_type<boost::asio::ssl::stream<StreamBase>> {
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 <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& /*ctx*/,
boost::asio::ssl::stream<StreamBase>& stream
const authority_path& ap,
boost::asio::ssl::context& /*ctx*/,
boost::asio::ssl::stream<StreamBase>& 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::stream<boost::asio::ip::tcp::socket>>,
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::stream<boost::asio::ip::tcp::socket>>,
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::stream<boost::asio::ip::tcp::socket>>,
// 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::stream<boost::asio::ip::tcp::socket>>,
// 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::qos_e::at_most_once>(
"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<boost::mqtt5::qos_e::at_most_once>(
"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();
}
//]

View File

@ -6,65 +6,65 @@
//
//[multiflight_client
#include <iostream>
#include <string>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <iostream>
#include <string>
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::qos_e::exactly_once>(
"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<boost::mqtt5::qos_e::exactly_once>(
"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();
}
//]

View File

@ -9,131 +9,130 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[publisher
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <string>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <string>
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<boost::asio::ip::tcp::socket>;
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
int next_sensor_reading() {
srand(static_cast<unsigned int>(std::time(0)));
return rand() % 100;
srand(static_cast<unsigned int>(std::time(0)));
return rand() % 100;
}
boost::asio::awaitable<void> 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::qos_e::at_least_once>(
"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<boost::mqtt5::qos_e::at_least_once>(
"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 <iostream>
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

View File

@ -9,142 +9,142 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[receiver
#include <iostream>
#include <string>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <iostream>
#include <string>
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<boost::asio::ip::tcp::socket>;
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
boost::asio::awaitable<bool> 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<void> 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 <iostream>
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

View File

@ -9,104 +9,106 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[timeout_with_awaitable_operators
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/mysql/any_connection.hpp>
#include <chrono>
#include <iostream>
#include <string>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
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<boost::asio::ip::tcp::socket>;
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
boost::asio::awaitable<void> 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::qos_e::at_least_once>(
"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<boost::mqtt5::qos_e::at_least_once>(
"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<void> {
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc);
co_spawn(
ioc,
[&ioc, &cfg]() -> boost::asio::awaitable<void> {
// 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 <iostream>
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

View File

@ -6,94 +6,94 @@
//
//[timeout_with_parallel_group
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <array>
#include <chrono>
#include <iostream>
#include <string>
#include <vector>
#include <boost/asio/io_context.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
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<boost::asio::ip::tcp::socket> client(ioc);
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> 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<async_mqtt5::reason_code> 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<boost::mqtt5::reason_code> 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<std::size_t, 2> 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<std::size_t, 2> 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();
}
//]

View File

@ -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 <async_mqtt5/error.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#endif // !ASYNC_MQTT5_HPP

View File

@ -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 <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <boost/type_traits/is_detected_convertible.hpp>
#include <async_mqtt5/types.hpp>
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 <typename T, typename ...Ts>
using async_auth_sig = decltype(
std::declval<T>().async_auth(std::declval<Ts>()...)
);
template <typename T>
using method_sig = decltype(
std::declval<T>().method()
);
template <typename T>
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<std::string_view, method_sig, T>;
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<is_authenticator<Authenticator>>
>
class auth_fun : public auth_fun_base {
Authenticator _authenticator;
public:
auth_fun(Authenticator authenticator) :
auth_fun_base(&async_auth),
_authenticator(std::forward<Authenticator>(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<auth_fun*>(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<detail::auth_fun_base> _auth_fun;
public:
any_authenticator() = default;
template <
typename Authenticator,
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
>
any_authenticator(Authenticator&& a) :
_method(a.method()),
_auth_fun(
new detail::auth_fun<Authenticator>(
std::forward<Authenticator>(a)
)
)
{}
std::string_view method() const {
return _method;
}
template <typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), step, std::move(data)
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ANY_AUTHENTICATOR

View File

@ -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 <atomic>
#include <deque>
#include <mutex>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/execution.hpp>
#include <boost/asio/require.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
class async_mutex {
public:
using executor_type = asio::any_io_executor;
private:
using queued_op_t = asio::any_completion_handler<
void (error_code)
>;
using queue_t = std::deque<queued_op_t>;
// Handler with assigned tracking executor.
// Objects of this type are type-erased by any_completion_handler
// and stored in the waiting queue.
template <typename Handler, typename Executor>
class tracked_op {
tracking_type<Handler, Executor> _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<Handler>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<Handler>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = tracking_type<Handler, Executor>;
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 <typename Executor>
explicit async_mutex(Executor&& ex) : _ex(std::forward<Executor>(ex)) {}
async_mutex(const async_mutex&) = delete;
async_mutex& operator=(const async_mutex&) = delete;
~async_mutex() {
cancel();
}
const executor_type& get_executor() const noexcept {
return _ex;
}
bool is_locked() const noexcept {
return _locked;
}
// Schedules mutex for lock operation and return immediately.
// Calls given completion handler when mutex is locked.
// It's the responsibility of the completion handler to unlock the mutex.
template <typename CompletionToken>
decltype(auto) lock(CompletionToken&& token) noexcept {
using Signature = void (error_code);
auto initiation = [] (auto handler, async_mutex& self) {
self.execute_or_queue(std::move(handler));
};
return asio::async_initiate<CompletionToken, Signature>(
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 <typename Handler>
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<cancel_waiting_op>(
_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

View File

@ -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 <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_immediate_executor.hpp>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
namespace async_mqtt5::detail {
template <typename Handler, typename Executor>
class cancellable_handler {
Executor _executor;
Handler _handler;
tracking_type<Handler, Executor> _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<Handler>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type = asio::associated_cancellation_slot_t<Handler>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
using executor_type = tracking_type<Handler, Executor>;
executor_type get_executor() const noexcept {
return _handler_ex;
}
using immediate_executor_type =
asio::associated_immediate_executor_t<Handler, Executor>;
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 <typename... Args>
void complete(Args&&... args) {
asio::get_associated_cancellation_slot(_handler).clear();
asio::dispatch(
_handler_ex,
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
);
}
template <typename... Args>
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>(args)...)
);
}
};
} // end async_mqtt5::detail
#endif // !ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP

View File

@ -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 <deque>
#include <type_traits>
#include <boost/asio/error.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
template <typename Element>
class bounded_deque {
std::deque<Element> _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 <typename E>
void push_back(E&& e) {
if (_buffer.size() == MAX_SIZE)
_buffer.pop_front();
_buffer.push_back(std::forward<E>(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 <typename... Signatures>
struct channel_traits {
template <typename... NewSignatures>
struct rebind {
using other = channel_traits<NewSignatures...>;
};
};
template <typename R, typename... Args>
struct channel_traits<R(error_code, Args...)> {
static_assert(sizeof...(Args) > 0);
template <typename... NewSignatures>
struct rebind {
using other = channel_traits<NewSignatures...>;
};
template <typename Element>
struct container {
using type = bounded_deque<Element>;
};
using receive_cancelled_signature = R(error_code, Args...);
template <typename F>
static void invoke_receive_cancelled(F f) {
std::forward<F>(f)(
asio::error::operation_aborted,
typename std::decay_t<Args>()...
);
}
using receive_closed_signature = R(error_code, Args...);
template <typename F>
static void invoke_receive_closed(F f) {
std::forward<F>(f)(
asio::error::operation_aborted,
typename std::decay_t<Args>()...
);
}
};
} // namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_CHANNEL_TRAITS_HPP

View File

@ -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 <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <boost/smart_ptr/allocate_unique.hpp>
#include <async_mqtt5/types.hpp>
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 <typename Allocator>
class control_packet {
uint16_t _packet_id;
using alloc_type = Allocator;
using deleter = boost::alloc_deleter<std::string, alloc_type>;
std::unique_ptr<std::string, deleter> _packet;
control_packet(
const Allocator& a,
uint16_t packet_id, std::string packet
) :
_packet_id(packet_id),
_packet(boost::allocate_unique<std::string>(a, std::move(packet)))
{}
public:
control_packet(control_packet&&) noexcept = default;
control_packet(const control_packet&) = 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>(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>(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<interval> _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

View File

@ -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 <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <async_mqtt5/detail/any_authenticator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5::detail {
using byte_citer = std::string::const_iterator;
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
using duration = time_stamp::duration;
struct credentials {
std::string client_id;
std::optional<std::string> username;
std::optional<std::string> password;
credentials() = default;
credentials(
std::string client_id,
std::string username, std::string password
) :
client_id(std::move(client_id))
{
if (!username.empty())
this->username = std::move(username);
if (!password.empty())
this->password = std::move(password);
}
};
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> 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

View File

@ -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 <string_view>
#include <type_traits>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using boost::system::error_code;
template <typename LoggerType>
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<LoggerType>)
_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<LoggerType>)
_logger.at_tcp_connect(ec, ep);
}
void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
if constexpr (has_at_tls_handshake<LoggerType>)
_logger.at_tls_handshake(ec, ep);
}
void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
if constexpr (has_at_ws_handshake<LoggerType>)
_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<LoggerType>)
_logger.at_connack(rc, session_present, ca_props);
}
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
if constexpr (has_at_disconnect<LoggerType>)
_logger.at_disconnect(rc, dc_props);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_LOG_INVOKE_HPP

View File

@ -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 <cstdint>
#include <string_view>
#include <async_mqtt5/detail/utf8_mqtt.hpp>
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

View File

@ -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 <cstdint>
#include <string>
#include <string_view>
#include <utility>
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 <typename ValidSizeCondition, typename ValidCondition>
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<std::string, std::string>& 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

View File

@ -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 <cstdint>
#include <ostream>
#include <string>
#include <boost/asio/error.hpp>
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<error>(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<int>(r), get_error_code_category() };
}
inline std::ostream& operator<<(std::ostream& os, const error& err) {
os << get_error_code_category().name() << ":" << static_cast<int>(err);
return os;
}
} // end namespace client
} // end namespace async_mqtt5
namespace boost::system {
template <>
struct is_error_code_enum <async_mqtt5::client::error> : std::true_type {};
} // end namespace boost::system
#endif // !ASYNC_MQTT5_ERROR_HPP

View File

@ -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 <chrono>
#include <cstdint>
#include <string>
#include <utility>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
class data_span : private std::pair<byte_citer, byte_citer> {
using base = std::pair<byte_citer, byte_citer>;
public:
using base::base;
auto first() const {
return base::first;
}
auto last() const {
return base::second;
}
void expand_suffix(size_t num_chars) {
base::second += num_chars;
}
void remove_prefix(size_t num_chars) {
base::first += num_chars;
}
size_t size() const {
return std::distance(base::first, base::second);
}
};
template <typename ClientService, typename Handler>
class assemble_op {
using client_service = ClientService;
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename CompletionCondition>
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 <typename CompletionCondition>
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<uint32_t>(*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<duration::rep>::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

View File

@ -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 <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/bind_allocator.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
class write_req {
static constexpr unsigned SERIAL_BITS = sizeof(serial_num_t) * 8;
asio::const_buffer _buffer;
serial_num_t _serial_num;
unsigned _flags;
using handler_type = asio::any_completion_handler<void (error_code)>;
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 <typename ClientService>
class async_sender {
using self_type = async_sender<ClientService>;
using client_service = ClientService;
using queue_allocator_type = asio::recycling_allocator<write_req>;
using write_queue_t = std::vector<write_req, queue_allocator_type>;
ClientService& _svc;
write_queue_t _write_queue;
bool _write_in_progress { false };
static constexpr uint16_t MAX_LIMIT = 65535;
uint16_t _limit { MAX_LIMIT };
uint16_t _quota { MAX_LIMIT };
serial_num_t _last_serial_num { 0 };
public:
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 <typename CompletionToken, typename BufferType>
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<CompletionToken, Signature>(
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<asio::const_buffer> buffers;
buffers.reserve(write_queue.size());
for (const auto& op : write_queue)
buffers.push_back(op.buffer());
_svc._replies.clear_fast_replies();
_svc._stream.async_write(
buffers,
asio::prepend(std::ref(*this), std::move(write_queue))
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ASYNC_SENDER_HPP

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <boost/asio/async_result.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/async_mutex.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
#include <async_mqtt5/impl/endpoints.hpp>
#include <async_mqtt5/impl/read_op.hpp>
#include <async_mqtt5/impl/reconnect_op.hpp>
#include <async_mqtt5/impl/write_op.hpp>
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<StreamType, StreamContext, LoggerType>;
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<stream_type>;
executor_type _stream_executor;
async_mutex _conn_mtx;
asio::steady_timer _read_timer, _connect_timer;
endpoints<logger_type> _endpoints;
stream_ptr _stream_ptr;
stream_context_type& _stream_context;
log_invoke<logger_type>& _log;
template <typename Owner, typename Handler>
friend class read_op;
template <typename Owner, typename Handler>
friend class write_op;
template <typename Owner>
friend class reconnect_op;
public:
autoconnect_stream(
const executor_type& ex, stream_context_type& context,
log_invoke<logger_type>& 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 <typename BufferType, typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), buffer, wait_for
);
}
template <typename BufferType, typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), buffer
);
}
private:
log_invoke<logger_type>& 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<StreamContext>)
sptr = std::make_shared<stream_type>(
_stream_executor, _stream_context.tls_context()
);
else
sptr = std::make_shared<stream_type>(_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 <typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), std::move(s)
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <boost/asio/async_result.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/experimental/basic_channel.hpp>
#include <async_mqtt5/detail/channel_traits.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/assemble_op.hpp>
#include <async_mqtt5/impl/async_sender.hpp>
#include <async_mqtt5/impl/autoconnect_stream.hpp>
#include <async_mqtt5/impl/replies.hpp>
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<has_tls_layer<StreamType>>
> {
using tls_context_type = TlsContext;
mqtt_ctx _mqtt_context;
std::shared_ptr<tls_context_type> _tls_context_ptr;
public:
explicit stream_context(TlsContext tls_context) :
_tls_context_ptr(std::make_shared<tls_context_type>(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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.ca_props[prop];
}
const auto& connack_properties() const {
return _mqtt_context.ca_props;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.co_props[prop];
}
template <prop::property_type p>
auto& connect_property(
std::integral_constant<prop::property_type, p> 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 <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <typename StreamType>
class stream_context<
StreamType, std::monostate,
std::enable_if_t<!has_tls_layer<StreamType>>
> {
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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.ca_props[prop];
}
const auto& connack_properties() const {
return _mqtt_context.ca_props;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.co_props[prop];
}
template <prop::property_type p>
auto& connect_property(
std::integral_constant<prop::property_type, p> 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 <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <
typename StreamType,
typename TlsContext = std::monostate,
typename LoggerType = noop_logger
>
class client_service {
using self_type = client_service<StreamType, TlsContext, LoggerType>;
using stream_context_type = stream_context<StreamType, TlsContext>;
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 <typename ClientService, typename Handler>
friend class run_op;
template <typename ClientService>
friend class async_sender;
template <typename ClientService, typename Handler>
friend class assemble_op;
template <typename ClientService, typename Handler>
friend class ping_op;
template <typename ClientService, typename Handler>
friend class sentry_op;
template <typename ClientService>
friend class re_auth_op;
executor_type _executor;
log_invoke<logger_type> _log;
stream_context_type _stream_context;
stream_type _stream;
packet_id_allocator _pid_allocator;
replies _replies;
async_sender<client_service> _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<size_t>::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<size_t>::max()),
_ping_timer(ex),
_sentry_timer(ex)
{}
executor_type get_executor() const noexcept {
return _executor;
}
auto dup() const {
return std::shared_ptr<client_service>(new client_service(*this));
}
template <
typename Ctx = TlsContext,
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, 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<is_authenticator<Authenticator>, bool> = true
>
void authenticator(Authenticator&& authenticator) {
if (!is_open())
_stream_context.authenticator(
std::forward<Authenticator>(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 <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _stream_context.connect_property(prop);
}
template <prop::property_type p>
void connect_property(
std::integral_constant<prop::property_type, p> prop,
prop::value_type_t<p> 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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> 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<LoggerType>& 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 <typename BufferType, typename CompletionToken>
decltype(auto) async_send(
const BufferType& buffer,
serial_num_t serial_num, unsigned flags,
CompletionToken&& token
) {
return _async_sender.async_send(
buffer, serial_num, flags, std::forward<CompletionToken>(token)
);
}
template <typename CompletionToken>
decltype(auto) async_assemble(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<CompletionToken, Signature> (
initiation, token, std::ref(*this),
std::ref(_read_buff), std::ref(_active_span)
);
}
template <typename CompletionToken>
decltype(auto) async_wait_reply(
control_code_e code, uint16_t packet_id, CompletionToken&& token
) {
return _replies.async_wait_reply(
code, packet_id, std::forward<CompletionToken>(token)
);
}
template <typename CompletionToken>
decltype(auto) async_channel_receive(CompletionToken&& token) {
return _rec_channel.async_receive(std::forward<CompletionToken>(token));
}
};
} // namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_CLIENT_SERVICE_HPP

View File

@ -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 <cstdint>
#include <string>
#include <utility>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/binary/binary.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/container/deque.hpp>
#include <boost/optional/optional.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/detail/traits.hpp>
namespace async_mqtt5::decoders {
namespace x3 = boost::spirit::x3;
template <typename T>
struct convert { using type = T; };
template <typename ...Args>
struct convert<boost::fusion::deque<Args...>> {
using type = std::tuple<typename convert<Args>::type...>;
};
template <typename T>
struct convert<boost::optional<T>> {
using type = std::optional<T>;
};
template <typename T>
struct convert<std::vector<T>> {
using type = std::vector<typename convert<T>::type>;
};
template <typename T, typename Parser>
constexpr auto as(Parser&& p) {
return x3::rule<struct _, T>{} = std::forward<Parser>(p);
}
template <typename It, typename Parser>
auto type_parse(It& first, const It last, const Parser& p) {
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Parser&>()));
using attr_type = typename x3::traits::attribute_of<Parser, ctx_type>::type;
using rv_type = typename convert<attr_type>::type;
std::optional<rv_type> rv;
rv_type value {};
if (x3::phrase_parse(first, last, as<rv_type>(p), x3::eps(false), value))
rv = std::move(value);
return rv;
}
template <typename AttributeType, typename It, typename Parser>
auto type_parse(It& first, const It last, const Parser& p) {
std::optional<AttributeType> rv;
AttributeType value {};
if (x3::phrase_parse(first, last, as<AttributeType>(p), x3::eps(false), value))
rv = std::move(value);
return rv;
}
namespace basic {
template <typename T>
constexpr auto to(T& arg) {
return [&](auto& ctx) {
using ctx_type = decltype(ctx);
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
if constexpr (detail::is_boost_iterator<attr_type>)
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
else
arg = x3::_attr(ctx);
};
}
template <typename LenParser, typename Subject, typename Enable = void>
class scope_limit {};
template <typename LenParser, typename Subject>
class scope_limit<
LenParser, Subject,
std::enable_if_t<x3::traits::is_parser<LenParser>::value>
> :
public x3::unary_parser<Subject, scope_limit<LenParser, Subject>>
{
using base_type = x3::unary_parser<Subject, scope_limit<LenParser, Subject>>;
LenParser _lp;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using attribute_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
static bool const has_attribute = true;
scope_limit(const LenParser& lp, const Subject& subject) :
base_type(subject), _lp(lp)
{}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
typename x3::traits::attribute_of<LenParser, Ctx>::type len;
if (!_lp.parse(iter, last, ctx, rctx, len))
return false;
if (iter + len > last)
return false;
if (!base_type::subject.parse(iter, iter + len, ctx, rctx, attr))
return false;
first = iter;
return true;
}
};
template <typename Size, typename Subject>
class scope_limit<
Size, Subject,
std::enable_if_t<std::is_arithmetic_v<Size>>
> :
public x3::unary_parser<Subject, scope_limit<Size, Subject>>
{
using base_type = x3::unary_parser<Subject, scope_limit<Size, Subject>>;
size_t _limit;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using attribute_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
static bool const has_attribute = true;
scope_limit(Size limit, const Subject& subject) :
base_type(subject), _limit(limit)
{}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
if (iter + _limit > last)
return false;
if (!base_type::subject.parse(iter, iter + _limit, ctx, rctx, attr))
return false;
first = iter;
return true;
}
};
template <typename LenParser, typename Enable = void>
struct scope_limit_gen {
template <typename Subject>
auto operator[](const Subject& p) const {
return scope_limit<LenParser, Subject> { _lp, x3::as_parser(p) };
}
LenParser _lp;
};
template <typename Size>
struct scope_limit_gen<
Size,
std::enable_if_t<std::is_arithmetic_v<Size>>
> {
template <typename Subject>
auto operator[](const Subject& p) const {
return scope_limit<Size, Subject> { limit, x3::as_parser(p) };
}
Size limit;
};
template <
typename Parser,
std::enable_if_t<x3::traits::is_parser<Parser>::value, bool> = true
>
scope_limit_gen<Parser> scope_limit_(const Parser& p) {
return { p };
}
template <
typename Size,
std::enable_if_t<std::is_arithmetic_v<Size>, bool> = true
>
scope_limit_gen<Size> scope_limit_(Size limit) {
return { limit };
}
struct verbatim_parser : x3::parser<verbatim_parser> {
using attribute_type = std::string;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(It& first, const It last, const Ctx&, RCtx&, Attr& attr) const {
attr = std::string { first, last };
first = last;
return true;
}
};
constexpr auto verbatim_ = verbatim_parser{};
struct varint_parser : x3::parser<varint_parser> {
using attribute_type = int32_t;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx&, 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<int32_t>(val) << bit_shift);
bit_shift = 0;
break;
}
}
if (bit_shift)
return false;
attr = result;
first = ++iter;
return true;
}
};
constexpr varint_parser varint_{};
struct len_prefix_parser : x3::parser<len_prefix_parser> {
using attribute_type = std::string;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
typename x3::traits::attribute_of<decltype(x3::big_word), Ctx>::type len;
if (x3::big_word.parse(iter, last, ctx, rctx, len)) {
if (std::distance(iter, last) < len)
return false;
}
else
return false;
attr = std::string(iter, iter + len);
first = iter + len;
return true;
}
};
constexpr len_prefix_parser utf8_ {};
constexpr len_prefix_parser binary_ {};
/*
Boost Spirit incorrectly deduces atribute type for a parser of the form
(eps(a) | parser1) >> (eps(b) | parser)
and we had to create if_ parser to remedy the issue
*/
template <typename Subject>
class conditional_parser :
public x3::unary_parser<Subject, conditional_parser<Subject>> {
using base_type = x3::unary_parser<Subject, conditional_parser<Subject>>;
bool _condition;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using subject_attr_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
using attribute_type = boost::optional<subject_attr_type>;
static bool const has_attribute = true;
conditional_parser(const Subject& s, bool condition) :
base_type(s), _condition(condition) {}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
if (!_condition)
return true;
It iter = first;
subject_attr_type sattr {};
if (!base_type::subject.parse(iter, last, ctx, rctx, sattr))
return false;
attr.emplace(std::move(sattr));
first = iter;
return true;
}
};
struct conditional_gen {
bool _condition;
template <typename Subject>
auto operator[](const Subject& p) const {
return conditional_parser<Subject> { p, _condition };
}
};
inline conditional_gen if_(bool condition) {
return { condition };
}
} // end namespace basic
namespace prop {
namespace basic = async_mqtt5::decoders::basic;
namespace detail {
template <typename It, typename Ctx, typename RCtx, typename Prop>
bool parse_to_prop(
It& iter, const It last,
const Ctx& ctx, RCtx& rctx, Prop& prop
) {
using namespace async_mqtt5::detail;
using prop_type = std::remove_reference_t<decltype(prop)>;
bool rv = false;
if constexpr (std::is_same_v<prop_type, uint8_t>)
rv = x3::byte_.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, uint16_t>)
rv = x3::big_word.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, int32_t>)
rv = basic::varint_.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, uint32_t>)
rv = x3::big_dword.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, std::string>)
rv = basic::utf8_.parse(iter, last, ctx, rctx, prop);
else if constexpr (is_optional<prop_type>) {
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<prop_type>) {
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<prop_type> || is_small_vector<prop_type>) {
typename std::remove_reference_t<prop_type>::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 <typename Props>
class prop_parser : public x3::parser<prop_parser<Props>> {
public:
using attribute_type = Props;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
if (iter == last)
return true;
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 <typename Props>
constexpr auto props_ = prop_parser<Props> {};
} // end namespace prop
} // end namespace async_mqtt5::decoders
#endif // !ASYNC_MQTT5_BASE_DECODERS_HPP

View File

@ -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 <cstddef>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <boost/core/identity.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/type_traits/is_detected_exact.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/detail/traits.hpp>
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 <size_t bits, typename repr = uint8_t>
class flag_def : public encoder {
template <size_t num_bits>
using least_type = std::conditional_t<
num_bits <= 8, uint8_t,
std::conditional_t<
num_bits <= 16, uint16_t,
std::conditional_t<
num_bits <= 32, uint32_t,
std::conditional_t<num_bits <= 64, uint64_t, void>
>
>
>;
template <size_t oth_bits, typename oth_repr>
friend class flag_def;
repr _val { 0 };
public:
flag_def(repr val) : _val(val) {}
flag_def() = default;
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<detail::is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
if constexpr (std::is_same_v<projection, boost::identity>) {
repr val = value.has_value();
return flag_def<bits, repr> { val };
}
else {
repr val = value.has_value() ?
static_cast<repr>(std::invoke(proj, *value)) : 0;
return flag_def<bits, repr> { val };
}
}
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<!detail::is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
auto val = static_cast<repr>(std::invoke(proj, value));
return flag_def<bits, repr> { val };
}
size_t byte_size() const { return sizeof(repr); }
template <size_t rhs_bits, typename rhs_repr>
auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
using res_repr = least_type<bits + rhs_bits>;
auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
return flag_def<bits + rhs_bits, res_repr> { val };
}
std::string& encode(std::string& s) const {
using namespace boost::endian;
size_t sz = s.size(); s.resize(sz + sizeof(repr));
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<repr, sizeof(repr), order::big>(p, _val);
return s;
}
};
template <size_t bits, typename repr = uint8_t>
constexpr auto flag = flag_def<bits, repr>{};
template <typename T, typename Repr>
class int_val : public encoder {
T _val;
public:
int_val(T val) : _val(val) {}
size_t byte_size() const {
if constexpr (detail::is_optional<T>) {
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<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
else
return encode_val(s, _val);
}
private:
template <typename U>
static size_t val_length(U&& val) {
if constexpr (std::is_same_v<Repr, varint_t>)
return variable_length(int32_t(val));
else
return sizeof(Repr);
}
template <typename U>
static std::string& encode_val(std::string& s, U&& val) {
using namespace boost::endian;
if constexpr (std::is_same_v<Repr, varint_t>) {
to_variable_bytes(s, int32_t(val));
return s;
}
else {
size_t sz = s.size(); s.resize(sz + sizeof(Repr));
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<Repr, sizeof(Repr), order::big>(p, val);
return s;
}
}
};
template <typename Repr>
class int_def {
public:
template <typename T>
auto operator()(T&& val) const {
return int_val<T, Repr> { std::forward<T>(val) };
}
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (detail::is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
if (val.has_value())
return (*this)(std::invoke(proj, *val));
return int_val<rv_type, Repr> { rv_type {} };
}
else {
using rv_type = std::invoke_result_t<projection, T>;
return int_val<rv_type, Repr> { std::invoke(proj, val) };
}
}
};
constexpr auto byte_ = int_def<uint8_t> {};
constexpr auto int16_ = int_def<uint16_t> {};
constexpr auto int32_ = int_def<uint32_t> {};
constexpr auto varlen_ = int_def<varint_t> {};
template <typename T>
class array_val : public encoder {
T _val;
bool _with_length;
public:
array_val(T val, bool with_length) :
_val(val), _with_length(with_length)
{
static_assert(
std::is_reference_v<T> || std::is_same_v<T, std::string_view>
);
}
size_t byte_size() const {
if constexpr (detail::is_optional<T>)
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<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
else
return encode_val(s, _val);
}
private:
template <typename V>
using has_size = decltype(std::declval<V&>().size());
template <typename U>
static size_t val_length(U&& val) {
if constexpr (std::is_same_v<boost::remove_cv_ref_t<U>, const char*>)
return std::strlen(val);
if constexpr (boost::is_detected_exact_v<size_t, has_size, U>)
return val.size();
else // fallback to type const char (&)[N] (substract 1 for trailing 0)
return sizeof(val) - 1;
}
template <typename U>
std::string& encode_val(std::string& s, U&& u) const {
using namespace boost::endian;
auto byte_len = val_length(std::forward<U>(u));
if (byte_len == 0 && !_with_length) return s;
if (_with_length) {
size_t sz = s.size(); s.resize(sz + 2);
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<int16_t, sizeof(int16_t), order::big>(
p, int16_t(byte_len)
);
}
s.append(std::begin(u), std::begin(u) + byte_len);
return s;
}
};
template <bool with_length = true>
class array_def {
public:
template <typename T>
auto operator()(T&& val) const {
return array_val<T> { std::forward<T>(val), with_length };
}
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (detail::is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
if (val.has_value())
return (*this)(std::invoke(proj, *val));
return array_val<rv_type> { rv_type {}, false };
}
else {
const auto& av = std::invoke(proj, val);
return array_val<T> { av, true };
}
}
};
using utf8_def = array_def<true>;
constexpr auto utf8_ = utf8_def {};
constexpr auto binary_ = array_def<true> {}; // for now
constexpr auto verbatim_ = array_def<false> {};
template <typename T, typename U>
class composed_val : public encoder {
T _lhs; U _rhs;
public:
composed_val(T lhs, U rhs) :
_lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs))
{}
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<encoder, std::decay_t<T>> &&
std::is_base_of_v<encoder, std::decay_t<U>>,
bool
> = true
>
inline auto operator&(T&& t, U&& u) {
return composed_val(std::forward<T>(t), std::forward<U>(u));
}
template <
typename T,
std::enable_if_t<std::is_base_of_v<encoder, std::decay_t<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 <typename T>
auto encoder_for_prop_value(const T& val) {
if constexpr (std::is_same_v<T, uint8_t>)
return basic::int_def<uint8_t>{}(val);
else if constexpr (std::is_same_v<T, uint16_t>)
return basic::int_def<uint16_t>{}(val);
else if constexpr (std::is_same_v<T, int32_t>)
return basic::int_def<basic::varint_t>{}(val);
else if constexpr (std::is_same_v<T, uint32_t>)
return basic::int_def<uint32_t>{}(val);
else if constexpr (std::is_same_v<T, std::string>)
return basic::utf8_def{}(val);
else if constexpr (detail::is_pair<T>)
return encoder_for_prop_value(val.first) &
encoder_for_prop_value(val.second);
}
template <typename T, pp::property_type p, typename Enable = void>
class prop_val;
template <
typename T, pp::property_type p
>
class prop_val<
T, p,
std::enable_if_t<!detail::is_vector<T> && detail::is_optional<T>>
> : public basic::encoder {
// allows T to be reference type to std::optional
static inline boost::remove_cv_ref_t<T> nulltype;
T _val;
public:
prop_val(T val) : _val(val) {
static_assert(std::is_reference_v<T>);
}
prop_val() : _val(nulltype) {}
size_t byte_size() const {
if (!_val) 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_vector<T> || detail::is_small_vector<T>>
> : public basic::encoder {
// allows T to be reference type to std::vector
static inline boost::remove_cv_ref_t<T> nulltype;
T _val;
public:
prop_val(T val) : _val(val) {
static_assert(std::is_reference_v<T>);
}
prop_val() : _val(nulltype) { }
size_t byte_size() const {
if (_val.empty()) return 0;
size_t total_size = 0;
for (const auto& 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 <typename Props>
class props_val : public basic::encoder {
static inline std::decay_t<Props> nulltype;
template <pp::property_type P, typename T>
static auto to_prop_val(const T& val) {
return prop_val<const T&, P>(val);
}
template <pp::property_type ...Ps>
static auto to_prop_vals(const pp::properties<Ps...>& props) {
return std::make_tuple(
to_prop_val<Ps>(
props[std::integral_constant<pp::property_type, Ps> {}]
)...
);
}
template <typename Func>
auto apply_each(Func&& func) const {
return std::apply([&func](const auto&... props) {
return (std::invoke(func, props), ...);
}, _prop_vals);
}
decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
bool _may_omit;
public:
props_val(Props val, bool may_omit) :
_prop_vals(to_prop_vals(val)), _may_omit(may_omit)
{
static_assert(std::is_reference_v<Props>);
}
props_val(bool may_omit) :
_prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit)
{}
size_t byte_size() const {
size_t psize = props_size();
if (_may_omit && psize == 0) return 0;
return psize + basic::varlen_(psize).byte_size();
}
std::string& encode(std::string& s) const {
size_t psize = props_size();
if (_may_omit && psize == 0) return s;
basic::varlen_(psize).encode(s);
apply_each([&s](const auto& pv) { return pv.encode(s); });
return s;
}
private:
size_t props_size() const {
size_t retval = 0;
apply_each([&retval](const auto& pv) {
return retval += pv.byte_size();
});
return retval;
}
};
template <bool may_omit>
class props_def {
public:
template <typename T>
auto operator()(T&& prop_container) const {
if constexpr (detail::is_optional<T>) {
if (prop_container.has_value())
return (*this)(*prop_container);
return props_val<
const typename boost::remove_cv_ref_t<T>::value_type&
>(true);
}
else {
return props_val<T> { prop_container, may_omit };
}
}
};
constexpr auto props_ = props_def<false> {};
constexpr auto props_may_omit_ = props_def<true> {};
} // end namespace prop
} // end namespace async_mqtt5::encoders
#endif // !ASYNC_MQTT5_BASE_ENCODERS_HPP

View File

@ -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 <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/base_decoders.hpp>
namespace async_mqtt5::decoders {
using byte_citer = detail::byte_citer;
using fixed_header = std::tuple<
uint8_t, // control byte
uint32_t // remaining_length
>;
inline std::optional<fixed_header> decode_fixed_header(
byte_citer& it, const byte_citer last
) {
auto fixed_header_ = x3::byte_ >> basic::varint_;
return type_parse(it, last, fixed_header_);
}
using packet_id = uint16_t;
inline std::optional<packet_id> decode_packet_id(
byte_citer& it
) {
auto packet_id_ = x3::big_word;
return type_parse(it, it + sizeof(uint16_t), packet_id_);
}
using connect_message = std::tuple<
std::string, // client_id,
std::optional<std::string>, // user_name,
std::optional<std::string>, // password,
uint16_t, // keep_alive,
bool, // clean_start,
connect_props, // props,
std::optional<will> // will
>;
inline std::optional<connect_message> decode_connect(
uint32_t remain_length, byte_citer& it
) {
auto var_header_ =
basic::utf8_ >> // MQTT
x3::byte_ >> // (num 5)
x3::byte_ >> // conn_flags_
x3::big_word >> // keep_alive
prop::props_<connect_props>;
const byte_citer end = it + remain_length;
auto vh = type_parse(it, end, var_header_);
if (!vh)
return std::optional<connect_message>{};
auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh;
if (mqtt_str != "MQTT" || version != 5)
return std::optional<connect_message>{};
bool has_will = (flags & 0b00000100);
bool has_uname = (flags & 0b10000000);
bool has_pwd = (flags & 0b01000000);
auto payload_ =
basic::utf8_ >> // client_id
basic::if_(has_will)[prop::props_<will_props>] >>
basic::if_(has_will)[basic::utf8_] >> // will topic
basic::if_(has_will)[basic::binary_] >> // will message
basic::if_(has_uname)[basic::utf8_] >> // username
basic::if_(has_pwd)[basic::utf8_]; // password
auto pload = type_parse(it, end, payload_);
if (!pload)
return std::optional<connect_message>{};
std::optional<will> w;
if (has_will)
w.emplace(
std::move(*std::get<2>(*pload)), // will_topic
std::move(*std::get<3>(*pload)), // will_message
qos_e((flags & 0b00011000) >> 3),
retain_e((flags & 0b00100000) >> 5),
std::move(*std::get<1>(*pload)) // will props
);
connect_message retval = {
std::move(std::get<0>(*pload)), // client_id
std::move(std::get<4>(*pload)), // user_name
std::move(std::get<5>(*pload)), // password
keep_alive,
flags & 0b00000010, // clean_start
std::move(cprops), // connect_props
std::move(w) // will
};
return std::optional<connect_message> { std::move(retval) };
}
using connack_message = std::tuple<
uint8_t, // session_present
uint8_t, // connect reason code
connack_props // props
>;
inline std::optional<connack_message> decode_connack(
uint32_t remain_length, byte_citer& it
) {
auto connack_ = basic::scope_limit_(remain_length)[
x3::byte_ >> x3::byte_ >> prop::props_<connack_props>
];
return type_parse(it, it + remain_length, connack_);
}
using publish_message = std::tuple<
std::string, // topic
std::optional<uint16_t>, // packet_id
uint8_t, // dup_e, qos_e, retain_e
publish_props, // publish props
std::string // payload
>;
inline std::optional<publish_message> decode_publish(
uint8_t control_byte, uint32_t remain_length, byte_citer& it
) {
uint8_t flags = control_byte & 0b1111;
auto qos = qos_e((flags >> 1) & 0b11);
auto publish_ = basic::scope_limit_(remain_length)[
basic::utf8_ >> basic::if_(qos != qos_e::at_most_once)[x3::big_word] >>
x3::attr(flags) >> prop::props_<publish_props> >> basic::verbatim_
];
return type_parse(it, it + remain_length, publish_);
}
using puback_message = std::tuple<
uint8_t, // puback reason code
puback_props // props
>;
inline std::optional<puback_message> decode_puback(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return puback_message {};
auto puback_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<puback_props>
];
return type_parse(it, it + remain_length, puback_);
}
using pubrec_message = std::tuple<
uint8_t, // puback reason code
pubrec_props // props
>;
inline std::optional<pubrec_message> decode_pubrec(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubrec_message {};
auto pubrec_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubrec_props>
];
return type_parse(it, it + remain_length, pubrec_);
}
using pubrel_message = std::tuple<
uint8_t, // puback reason code
pubrel_props // props
>;
inline std::optional<pubrel_message> decode_pubrel(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubrel_message {};
auto pubrel_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubrel_props>
];
return type_parse(it, it + remain_length, pubrel_);
}
using pubcomp_message = std::tuple<
uint8_t, // puback reason code
pubcomp_props // props
>;
inline std::optional<pubcomp_message> decode_pubcomp(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubcomp_message {};
auto pubcomp_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubcomp_props>
];
return type_parse(it, it + remain_length, pubcomp_);
}
using subscribe_message = std::tuple<
subscribe_props,
std::vector<std::tuple<std::string, uint8_t>> // topic filter with opts
>;
inline std::optional<subscribe_message> decode_subscribe(
uint32_t remain_length, byte_citer& it
) {
auto subscribe_ = basic::scope_limit_(remain_length)[
prop::props_<subscribe_props> >> +(basic::utf8_ >> x3::byte_)
];
return type_parse(it, it + remain_length, subscribe_);
}
using suback_message = std::tuple<
suback_props,
std::vector<uint8_t> // reason_codes
>;
inline std::optional<suback_message> decode_suback(
uint32_t remain_length, byte_citer& it
) {
auto suback_ = basic::scope_limit_(remain_length)[
prop::props_<suback_props> >> +x3::byte_
];
return type_parse(it, it + remain_length, suback_);
}
using unsubscribe_message = std::tuple<
unsubscribe_props,
std::vector<std::string> // topics
>;
inline std::optional<unsubscribe_message> decode_unsubscribe(
uint32_t remain_length, byte_citer& it
) {
auto unsubscribe_ = basic::scope_limit_(remain_length)[
prop::props_<unsubscribe_props> >> +basic::utf8_
];
return type_parse(it, it + remain_length, unsubscribe_);
}
using unsuback_message = std::tuple<
unsuback_props,
std::vector<uint8_t> // reason_codes
>;
inline std::optional<unsuback_message> decode_unsuback(
uint32_t remain_length, byte_citer& it
) {
auto unsuback_ = basic::scope_limit_(remain_length)[
prop::props_<unsuback_props> >> +x3::byte_
];
return type_parse(it, it + remain_length, unsuback_);
}
using disconnect_message = std::tuple<
uint8_t, // reason_code
disconnect_props
>;
inline std::optional<disconnect_message> decode_disconnect(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return disconnect_message {};
auto disconnect_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<disconnect_props>
];
return type_parse(it, it + remain_length, disconnect_);
}
using auth_message = std::tuple<
uint8_t, // reason_code
auth_props
>;
inline std::optional<auth_message> decode_auth(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return auth_message {};
auto auth_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<auth_props>
];
return type_parse(it, it + remain_length, auth_);
}
} // end namespace async_mqtt5::decoders
#endif // !ASYNC_MQTT5_MESSAGE_DECODERS_HPP

View File

@ -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 <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/impl/codecs/base_encoders.hpp>
namespace async_mqtt5::encoders {
template <typename encoder>
std::string encode(const encoder& e) {
std::string s;
s.reserve(e.byte_size());
s << e;
return s;
}
inline std::string encode_connect(
std::string_view client_id,
std::optional<std::string_view> user_name,
std::optional<std::string_view> password,
uint16_t keep_alive, bool clean_start,
const connect_props& props,
const std::optional<will>& w
) {
auto packet_type_ =
basic::flag<4>(0b0001) |
basic::flag<4>(0);
auto conn_flags_ =
basic::flag<1>(user_name) |
basic::flag<1>(password) |
basic::flag<1>(w, &will::retain) |
basic::flag<2>(w, &will::qos) |
basic::flag<1>(w) |
basic::flag<1>(clean_start) |
basic::flag<1>(0);
auto var_header_ =
basic::utf8_("MQTT") &
basic::byte_(uint8_t(5)) &
conn_flags_ &
basic::int16_(keep_alive) &
prop::props_(props);
auto payload_ =
basic::utf8_(client_id) &
prop::props_(w) &
basic::utf8_(w, &will::topic) &
basic::binary_(w, &will::message) &
basic::utf8_(user_name) &
basic::utf8_(password);
auto message_body_ = var_header_ & payload_;
auto fixed_header_ =
packet_type_ &
basic::varlen_(message_body_.byte_size());
auto connect_message_ = fixed_header_ & message_body_;
return encode(connect_message_);
}
inline std::string encode_connack(
bool session_present,
uint8_t reason_code,
const connack_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0010) |
basic::flag<4>(0);
auto var_header_ =
basic::flag<1>(session_present) &
basic::byte_(reason_code) &
prop::props_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto connack_message_ = fixed_header_ & var_header_;
return encode(connack_message_);
}
inline std::string encode_publish(
uint16_t packet_id,
std::string_view topic_name,
std::string_view payload,
qos_e qos, retain_e retain, dup_e dup,
const publish_props& props
) {
std::optional<uint16_t> used_packet_id;
if (qos != qos_e::at_most_once) used_packet_id.emplace(packet_id);
auto packet_type_ =
basic::flag<4>(0b0011) |
basic::flag<1>(dup) |
basic::flag<2>(qos) |
basic::flag<1>(retain);
auto var_header_ =
basic::utf8_(topic_name) &
basic::int16_(used_packet_id) &
prop::props_(props);
auto message_body_ = var_header_ & basic::verbatim_(payload);
auto fixed_header_ =
packet_type_ &
basic::varlen_(message_body_.byte_size());
auto publish_message_ = fixed_header_ & message_body_;
return encode(publish_message_);
}
inline std::string encode_puback(
uint16_t packet_id,
uint8_t reason_code,
const puback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0100) |
basic::flag<4>(0);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto puback_message_ = fixed_header_ & var_header_;
return encode(puback_message_);
}
inline std::string encode_pubrec(
uint16_t packet_id,
uint8_t reason_code,
const pubrec_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0101) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubrec_message_ = fixed_header_ & var_header_;
return encode(pubrec_message_);
}
inline std::string encode_pubrel(
uint16_t packet_id,
uint8_t reason_code,
const pubrel_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0110) |
basic::flag<4>(0b0010);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubrel_message_ = fixed_header_ & var_header_;
return encode(pubrel_message_);
}
inline std::string encode_pubcomp(
uint16_t packet_id,
uint8_t reason_code,
const pubcomp_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0111) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubcomp_message_ = fixed_header_ & var_header_;
return encode(pubcomp_message_);
}
inline std::string encode_subscribe(
uint16_t packet_id,
const std::vector<subscribe_topic>& topics,
const subscribe_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1000) |
basic::flag<4>(0b0010);
size_t payload_size = 0;
for (const auto& [topic_filter, _]: topics)
payload_size += basic::utf8_(topic_filter).byte_size() + 1;
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + payload_size) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + payload_size);
for (const auto& [topic_filter, sub_opts]: topics) {
auto opts_ =
basic::flag<2>(sub_opts.retain_handling) |
basic::flag<1>(sub_opts.retain_as_published) |
basic::flag<1>(sub_opts.no_local) |
basic::flag<2>(sub_opts.max_qos);
auto filter_ = basic::utf8_(topic_filter) & opts_;
s << filter_;
}
return s;
}
inline std::string encode_suback(
uint16_t packet_id,
const std::vector<uint8_t>& reason_codes,
const suback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1001) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + reason_codes.size());
for (auto reason_code: reason_codes)
s << basic::byte_(reason_code);
return s;
}
inline std::string encode_unsubscribe(
uint16_t packet_id,
const std::vector<std::string>& topics,
const unsubscribe_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1010) |
basic::flag<4>(0b0010);
size_t payload_size = 0;
for (const auto& topic: topics)
payload_size += basic::utf8_(topic).byte_size();
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + payload_size) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + payload_size);
for (const auto& topic: topics)
s << basic::utf8_(topic);
return s;
}
inline std::string encode_unsuback(
uint16_t packet_id,
const std::vector<uint8_t>& reason_codes,
const unsuback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1011) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + reason_codes.size());
for (auto reason_code: reason_codes)
s << basic::byte_(reason_code);
return s;
}
inline std::string encode_pingreq() {
auto packet_type_ =
basic::flag<4>(0b1100) |
basic::flag<4>(0);
auto remaining_len_ =
basic::byte_(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

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
template <typename Stream, typename LoggerType>
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<LoggerType>& _log;
using handler_type = asio::any_completion_handler<void (error_code)>;
handler_type _handler;
std::unique_ptr<std::string> _buffer_ptr;
asio::cancellation_state _cancellation_state;
using endpoint = asio::ip::tcp::endpoint;
public:
template <typename Handler>
connect_op(
Stream& stream, mqtt_ctx& ctx,
log_invoke<LoggerType>& log,
Handler&& handler
) :
_stream(stream), _ctx(ctx), _log(log),
_handler(std::forward<Handler>(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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
using executor_type = asio::associated_executor_t<handler_type>;
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>) {
_stream.async_handshake(
tls_handshake_type<Stream>::client,
asio::append(
asio::prepend(std::move(*this), on_tls_handshake {}),
std::move(ep), std::move(ap)
)
);
}
else if constexpr (
has_tls_handshake<next_layer_type<Stream>>
) {
_stream.next_layer().async_handshake(
tls_handshake_type<next_layer_type<Stream>>::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<Stream>)
// If you get a compilation error here,
// it might be because of a missing <async_mqtt5/websocket.hpp> include
ws_handshake_traits<Stream>::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<Stream>)
_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<allocator_type>::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<std::string>(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<uint32_t>(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_codes::category::connack>(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<uint32_t>(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_codes::category::auth>(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<allocator_type>::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

View File

@ -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 <cstdint>
#include <memory>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/topic_validation.hpp>
#include <async_mqtt5/detail/utf8_mqtt.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
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<client_service> _svc_ptr;
DisconnectContext _context;
using handler_type = cancellable_handler<
asio::any_completion_handler<void (error_code)>,
typename ClientService::executor_type
>;
handler_type _handler;
public:
template <typename Handler>
disconnect_op(
std::shared_ptr<client_service> 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<handler_type>;
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<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_disconnect,
static_cast<uint8_t>(_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<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_disconnect,
static_cast<uint8_t>(_context.reason_code), disconnect_props {}
));
send_disconnect(std::move(disconnect));
}
void send_disconnect(control_packet<allocator_type> 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<allocator_type> 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 <typename ClientService, typename Handler>
class terminal_disconnect_op {
using client_service = ClientService;
static constexpr uint8_t seconds = 5;
std::shared_ptr<client_service> _svc_ptr;
std::unique_ptr<asio::steady_timer> _timer;
using handler_type = Handler;
handler_type _handler;
public:
terminal_disconnect_op(
std::shared_ptr<client_service> 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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type = asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename DisconnectContext>
void perform(DisconnectContext&& context) {
namespace asioex = boost::asio::experimental;
auto init_disconnect = [](
auto handler, disconnect_ctx ctx,
std::shared_ptr<ClientService> 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<const asio::deferred_t, void (error_code)>(
init_disconnect, asio::deferred,
std::forward<DisconnectContext>(context), _svc_ptr
),
_timer->async_wait(asio::deferred)
);
timed_disconnect.async_wait(
asioex::wait_for_one(), std::move(*this)
);
}
void operator()(
std::array<std::size_t, 2> /* ord */,
error_code disconnect_ec, error_code /* timer_ec */
) {
std::move(_handler)(disconnect_ec);
}
};
template <typename ClientService, bool terminal>
class initiate_async_disconnect {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_disconnect(std::shared_ptr<ClientService> 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 <typename Handler>
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 <typename ClientService, typename CompletionToken>
decltype(auto) async_disconnect(
disconnect_rc_e reason_code, const disconnect_props& props,
std::shared_ptr<ClientService> svc_ptr,
CompletionToken&& token
) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
initiate_async_disconnect<ClientService, false>(std::move(svc_ptr)), token,
reason_code, props
);
}
template <typename ClientService, typename CompletionToken>
decltype(auto) async_terminal_disconnect(
disconnect_rc_e reason_code, const disconnect_props& props,
std::shared_ptr<ClientService> svc_ptr,
CompletionToken&& token
) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
initiate_async_disconnect<ClientService, true>(std::move(svc_ptr)), token,
reason_code, props
);
}
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_DISCONNECT_HPP

View File

@ -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 <array>
#include <chrono>
#include <string>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/spirit/home/x3.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using epoints = asio::ip::tcp::resolver::results_type;
template <typename Owner, typename Handler>
class resolve_op {
struct on_resolve {};
Owner& _owner;
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
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<int>(_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<std::size_t, 2> 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 <typename LoggerType>
class endpoints {
using logger_type = LoggerType;
asio::ip::tcp::resolver _resolver;
asio::steady_timer& _connect_timer;
std::vector<authority_path> _servers;
int _current_host { -1 };
log_invoke<logger_type>& _log;
template <typename Owner, typename Handler>
friend class resolve_op;
template <typename T>
static constexpr auto to_(T& arg) {
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
}
template <typename T, typename Parser>
static constexpr auto as_(Parser&& p){
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
}
public:
template <typename Executor>
endpoints(
Executor ex, asio::steady_timer& timer,
log_invoke<logger_type>& 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 <typename CompletionToken>
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<CompletionToken, Signature>(
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_<std::string>(+unreserved_)[to_(host)];
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
auto path_ = as_<std::string>(x3::char_('/') >> *unreserved_)[to_(path)];
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
for (auto b = hosts.begin(); b != hosts.end(); ) {
host.clear(); port.clear(); path.clear();
if (phrase_parse(b, hosts.end(), uri_, x3::eps(false))) {
_servers.push_back({
std::move(host),
port.empty()
? std::to_string(default_port)
: std::move(port),
std::move(path)
});
}
else b = hosts.end();
}
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ENDPOINTS_HPP

View File

@ -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 <limits>
#include <chrono>
#include <boost/asio/error.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class ping_op {
using client_service = ClientService;
using handler_type = Handler;
struct on_timer {};
struct on_pingreq {};
std::shared_ptr<client_service> _svc_ptr;
handler_type _handler;
public:
ping_op(std::shared_ptr<client_service> 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<handler_type>;
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<allocator_type>::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<duration::rep>::max());
}
void complete() {
return std::move(_handler)();
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_PING_OP_HPP

View File

@ -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 <cstdint>
#include <string>
#include <memory>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
class publish_rec_op {
using client_service = ClientService;
struct on_puback {};
struct on_pubrec {};
struct on_pubrel {};
struct on_pubcomp {};
std::shared_ptr<client_service> _svc_ptr;
decoders::publish_message _message;
public:
explicit publish_rec_op(std::shared_ptr<client_service> 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<void>;
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<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_puback, *packet_id,
uint8_t(0), puback_props {}
);
return send_puback(std::move(puback));
}
// qos == qos_e::exactly_once
auto pubrec = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubrec, *packet_id,
uint8_t(0), pubrec_props {}
);
return send_pubrec(std::move(pubrec));
}
void send_puback(control_packet<allocator_type> puback) {
auto wire_data = puback.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::consign(
asio::prepend(std::move(*this), on_puback {}),
std::move(puback)
)
);
}
void operator()(on_puback, error_code ec) {
if (ec)
return;
complete();
}
void send_pubrec(control_packet<allocator_type> pubrec) {
auto wire_data = pubrec.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::prepend(std::move(*this), on_pubrec {}, std::move(pubrec))
);
}
void operator()(
on_pubrec, control_packet<allocator_type> packet,
error_code ec
) {
if (ec)
return;
wait_pubrel(packet.packet_id());
}
void wait_pubrel(uint16_t packet_id) {
_svc_ptr->async_wait_reply(
control_code_e::pubrel, packet_id,
asio::prepend(std::move(*this), on_pubrel {}, packet_id)
);
}
void operator()(
on_pubrel, uint16_t packet_id,
error_code ec, byte_citer first, byte_citer last
) {
if (ec == asio::error::try_again) // "resend unanswered"
return wait_pubrel(packet_id);
if (ec)
return;
auto pubrel = decoders::decode_pubrel(static_cast<uint32_t>(std::distance(first, last)), first);
if (!pubrel.has_value()) {
on_malformed_packet("Malformed PUBREL received: cannot decode");
return wait_pubrel(packet_id);
}
auto& [reason_code, props] = *pubrel;
auto rc = to_reason_code<reason_codes::category::pubrel>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBREL received: invalid Reason Code");
return wait_pubrel(packet_id);
}
auto pubcomp = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubcomp, packet_id,
uint8_t(0), pubcomp_props{}
);
send_pubcomp(std::move(pubcomp));
}
void send_pubcomp(control_packet<allocator_type> pubcomp) {
auto wire_data = pubcomp.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubcomp))
);
}
void operator()(
on_pubcomp, control_packet<allocator_type> packet,
error_code ec
) {
if (ec == asio::error::try_again)
return wait_pubrel(packet.packet_id());
if (ec)
return;
complete();
}
private:
void on_malformed_packet(const std::string& reason) {
auto props = disconnect_props {};
props[prop::reason_string] = reason;
return async_disconnect(
disconnect_rc_e::malformed_packet, props,
_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

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/topic_validation.hpp>
#include <async_mqtt5/detail/utf8_mqtt.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <qos_e qos_type>
using on_publish_signature = std::conditional_t<
qos_type == qos_e::at_most_once,
void (error_code),
std::conditional_t<
qos_type == qos_e::at_least_once,
void (error_code, reason_code, puback_props),
void (error_code, reason_code, pubcomp_props)
>
>;
template <qos_e qos_type>
using on_publish_props_type = std::conditional_t<
qos_type == qos_e::at_most_once,
void,
std::conditional_t<
qos_type == qos_e::at_least_once,
puback_props,
pubcomp_props
>
>;
template <typename ClientService, typename Handler, qos_e qos_type>
class publish_send_op {
using client_service = ClientService;
struct on_publish {};
struct on_puback {};
struct on_pubrec {};
struct on_pubrel {};
struct on_pubcomp {};
std::shared_ptr<client_service> _svc_ptr;
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<client_service> 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<handler_type>;
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<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_publish, packet_id,
std::move(topic), std::move(payload),
qos_type, retain, dup_e::no, props
);
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<allocator_type> 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<allocator_type> 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<allocator_type> 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<q == qos_e::at_least_once, bool> = true
>
void operator()(
on_puback, control_packet<allocator_type> 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<uint32_t>(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_codes::category::puback>(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<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubrec, control_packet<allocator_type> 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<uint32_t>(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_codes::category::pubrec>(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<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubrel, packet_id,
0, pubrel_props {}
);
send_pubrel(std::move(pubrel), false);
}
void send_pubrel(control_packet<allocator_type> pubrel, bool throttled) {
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<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubrel, control_packet<allocator_type> 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<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubcomp, control_packet<allocator_type> 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<uint32_t>(std::distance(first, last)), first
);
if (!pubcomp.has_value()) {
on_malformed_packet("Malformed PUBCOMP: cannot decode");
return send_pubrel(std::move(pubrel), true);
}
auto& [reason_code, props] = *pubcomp;
auto rc = to_reason_code<reason_codes::category::pubcomp>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBCOMP: invalid Reason Code");
return send_pubrel(std::move(pubrel), true);
}
return complete(ec, 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<q == qos_e::at_most_once, bool> = true
>
void complete(error_code ec, uint16_t = 0) {
_handler.complete(ec);
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::at_most_once, bool> = true
>
void complete_immediate(error_code ec, uint16_t) {
_handler.complete_immediate(ec);
}
template <
typename Props = on_publish_props_type<qos_type>,
std::enable_if_t<
std::is_same_v<Props, puback_props> ||
std::is_same_v<Props, pubcomp_props>,
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>(props));
}
template <
typename Props = on_publish_props_type<qos_type>,
std::enable_if_t<
std::is_same_v<Props, puback_props> ||
std::is_same_v<Props, pubcomp_props>,
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 <typename ClientService, qos_e qos_type>
class initiate_async_publish {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_publish(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(
Handler&& handler,
std::string topic, std::string payload,
retain_e retain, const publish_props& props
) {
detail::publish_send_op<ClientService, Handler, qos_type> {
_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

View File

@ -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 <memory>
#include <string>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/any_authenticator.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
class re_auth_op {
using client_service = ClientService;
struct on_auth_data {};
std::shared_ptr<client_service> _svc_ptr;
any_authenticator& _auth;
public:
explicit re_auth_op(std::shared_ptr<client_service> 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<void>;
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<reason_codes::category::auth>(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<allocator_type>::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

View File

@ -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 <cstdint>
#include <memory>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/publish_rec_op.hpp>
#include <async_mqtt5/impl/re_auth_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class read_message_op {
using client_service = ClientService;
using handler_type = Handler;
struct on_message {};
struct on_disconnect {};
std::shared_ptr<client_service> _svc_ptr;
handler_type _handler;
public:
read_message_op(std::shared_ptr<client_service> 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<handler_type>;
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<uint32_t>(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<uint32_t>(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<reason_codes::category::disconnect>(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<uint32_t>(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

View File

@ -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 <array>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
namespace asioex = boost::asio::experimental;
template <typename Owner, typename Handler>
class read_op {
struct on_read {};
struct on_reconnect {};
Owner& _owner;
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename BufferType>
void perform(
const BufferType& buffer, duration wait_for
) {
auto stream_ptr = _owner._stream_ptr;
if (_owner.was_connected()) {
_owner._read_timer.expires_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<size_t, 2> { 0, 1 },
asio::error::not_connected, 0, error_code {}
)
);
}
void operator()(
on_read, typename Owner::stream_ptr stream_ptr,
std::array<std::size_t, 2> ord, error_code read_ec, size_t bytes_read,
error_code
) {
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

View File

@ -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 <array>
#include <chrono>
#include <memory>
#include <string>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_smallint.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/impl/connect_op.hpp>
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 <typename Owner>
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<void (error_code)>;
handler_type _handler;
std::unique_ptr<std::string> _buffer_ptr;
exponential_backoff _generator;
using endpoint = asio::ip::tcp::endpoint;
using epoints = asio::ip::tcp::resolver::results_type;
public:
template <typename Handler>
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
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<typename Owner::stream_context_type>)
setup_tls_sni(
ap, _owner._stream_context.tls_context(), *sptr
);
// wait max 5 seconds for the connect (handshake) op to finish
_owner._connect_timer.expires_after(std::chrono::seconds(5));
auto init_connect = [](
auto handler, typename Owner::stream_type& stream,
mqtt_ctx& context, log_invoke<typename Owner::logger_type>& 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<const asio::deferred_t, void(error_code)>(
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<std::size_t, 2> 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

View File

@ -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 <algorithm>
#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
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<Signature> _handler;
control_code_e _code;
uint16_t _packet_id;
std::chrono::time_point<std::chrono::system_clock> _ts;
public:
template <typename H>
reply_handler(control_code_e code, uint16_t pid, H&& handler) :
_handler(std::forward<H>(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<reply_handler>;
handlers _handlers;
struct fast_reply {
control_code_e code;
uint16_t packet_id;
std::unique_ptr<std::string> packet;
};
using fast_replies = std::vector<fast_reply>;
fast_replies _fast_replies;
public:
template <typename Executor>
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 <typename CompletionToken>
decltype(auto) async_wait_reply(
control_code_e code, uint16_t packet_id, CompletionToken&& token
) {
auto dup_handler_ptr = find_handler(code, packet_id);
if (dup_handler_ptr != _handlers.end()) {
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), code, packet_id
);
}
auto fdata = std::move(*freply);
_fast_replies.erase(freply);
auto initiation = [](
auto handler, std::unique_ptr<std::string> 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<CompletionToken, Signature>(
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<std::string>(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

View File

@ -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 <memory>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/read_message_op.hpp>
#include <async_mqtt5/impl/ping_op.hpp>
#include <async_mqtt5/impl/sentry_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class run_op {
using client_service = ClientService;
std::shared_ptr<client_service> _svc_ptr;
using handler_type = cancellable_handler<
Handler,
typename client_service::executor_type
>;
handler_type _handler;
public:
run_op(
std::shared_ptr<client_service> 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<handler_type>;
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<client_service> svc_ptr
) {
return read_message_op { std::move(svc_ptr), std::move(handler) }
.perform();
};
auto init_ping_op = [](
auto handler, std::shared_ptr<client_service> svc_ptr
) {
return ping_op { std::move(svc_ptr), std::move(handler) }
.perform();
};
auto init_senty_op = [](
auto handler, std::shared_ptr<client_service> svc_ptr
) {
return sentry_op { std::move(svc_ptr), std::move(handler) }
.perform();
};
asioex::make_parallel_group(
asio::async_initiate<const asio::deferred_t, void ()>(
init_read_message_op, asio::deferred, _svc_ptr
),
asio::async_initiate<const asio::deferred_t, void ()>(
init_ping_op, asio::deferred, _svc_ptr
),
asio::async_initiate<const asio::deferred_t, void ()>(
init_senty_op, asio::deferred, _svc_ptr
)
).async_wait(asioex::wait_for_all(), std::move(*this));
}
void operator()(std::array<std::size_t, 3> /* ord */) {
_handler.complete(asio::error::operation_aborted);
}
};
template <typename ClientService>
class initiate_async_run {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_run(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(Handler&& handler) {
run_op<ClientService, Handler> {
_svc_ptr, std::move(handler)
}.perform();
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_RUN_OP_HPP

View File

@ -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 <chrono>
#include <memory>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
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<client_service> _svc_ptr;
handler_type _handler;
public:
sentry_op(std::shared_ptr<client_service> 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<handler_type>;
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

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/topic_validation.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class subscribe_op {
using client_service = ClientService;
struct on_subscribe {};
struct on_suback {};
std::shared_ptr<client_service> _svc_ptr;
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<client_service> 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<handler_type>;
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<subscribe_topic>& 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<allocator_type>::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<allocator_type> 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<allocator_type> 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<allocator_type> 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<allocator_type> 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<uint32_t>(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<subscribe_topic>& 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<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
std::vector<reason_code> ret;
for (uint8_t code : codes) {
auto rc = to_reason_code<reason_codes::category::suback>(code);
if (rc)
ret.push_back(*rc);
}
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<reason_code>(_num_topics, reason_codes::empty),
suback_props {}
);
}
void complete(
error_code ec, uint16_t packet_id,
std::vector<reason_code> reason_codes = {}, suback_props props = {}
) {
if (reason_codes.empty() && _num_topics)
reason_codes = std::vector<reason_code>(_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 <typename ClientService>
class initiate_async_subscribe {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_subscribe(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(
Handler&& handler,
const std::vector<subscribe_topic>& 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

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/topic_validation.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class unsubscribe_op {
using client_service = ClientService;
struct on_unsubscribe {};
struct on_unsuback {};
std::shared_ptr<client_service> _svc_ptr;
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<client_service> 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<handler_type>;
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<std::string>& 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<allocator_type>::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<allocator_type> 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<allocator_type> 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<allocator_type> 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<allocator_type> 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<uint32_t>(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<std::string>& 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<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
std::vector<reason_code> ret;
for (uint8_t code : codes) {
auto rc = to_reason_code<reason_codes::category::unsuback>(code);
if (rc)
ret.push_back(*rc);
}
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<reason_code>(_num_topics, reason_codes::empty),
unsuback_props {}
);
}
void complete(
error_code ec, uint16_t packet_id,
std::vector<reason_code> reason_codes = {}, unsuback_props props = {}
) {
if (reason_codes.empty() && _num_topics)
reason_codes = std::vector<reason_code>(_num_topics, reason_codes::empty);
_svc_ptr->free_pid(packet_id);
_handler.complete(ec, std::move(reason_codes), std::move(props));
}
};
template <typename ClientService>
class initiate_async_unsubscribe {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_unsubscribe(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(
Handler&& handler,
const std::vector<std::string>& 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

View File

@ -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 <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/write.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
namespace async_mqtt5::detail {
template <typename Owner, typename Handler>
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename BufferType>
void perform(BufferType& buffer) {
auto stream_ptr = _owner._stream_ptr;
if (_owner.was_connected())
// note: write operation should not be time-limited
detail::async_write(
*stream_ptr, buffer,
asio::prepend(std::move(*this), on_write {}, stream_ptr)
);
else
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

View File

@ -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 <cstdint>
#include <iostream>
#include <string_view>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/traits.hpp>
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 <typename Props>
void output_props(const Props& props) {
props.visit(
[](const auto& prop, const auto& val) -> bool {
if constexpr (detail::is_optional<decltype(val)>) {
if (val.has_value()) {
std::clog << property_name(prop) << ":";
using value_type = boost::remove_cv_ref_t<decltype(*val)>;
if constexpr (std::is_same_v<value_type, uint8_t>)
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<decltype(val[i])>)
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 <prop::property_type p>
static std::string_view property_name(std::integral_constant<prop::property_type, p>) {
return prop::name_v<p>;
}
};
// Verify that the logger class satisfies the LoggerType concept
static_assert(has_at_resolve<logger>);
static_assert(has_at_tcp_connect<logger>);
static_assert(has_at_tls_handshake<logger>);
static_assert(has_at_ws_handshake<logger>);
static_assert(has_at_connack<logger>);
static_assert(has_at_disconnect<logger>);
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_LOGGER_HPP

View File

@ -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 <memory>
#include <string>
#include <type_traits>
#include <variant> // std::monostate
#include <vector>
#include <boost/asio/async_result.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
#include <async_mqtt5/detail/rebind_executor.hpp>
#include <async_mqtt5/impl/client_service.hpp>
#include <async_mqtt5/impl/run_op.hpp>
#include <async_mqtt5/impl/publish_send_op.hpp>
#include <async_mqtt5/impl/re_auth_op.hpp>
#include <async_mqtt5/impl/subscribe_op.hpp>
#include <async_mqtt5/impl/unsubscribe_op.hpp>
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 <typename Executor>
struct rebind_executor {
/// The client type when rebound to the specified executor.
using other = mqtt_client<
typename detail::rebind_executor<StreamType, 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<client_service_type>;
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<client_service_type>(
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<ExecutionContext&, asio::execution_context&>
* \endcode
*/
template <
typename ExecutionContext,
std::enable_if_t<
std::is_convertible_v<ExecutionContext&, asio::execution_context&>,
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<TlsContext, std::monostate>
* \endcode
*/
template <
typename Ctx = TlsContext,
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, 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<executor_type>::type
>
decltype(auto) async_run(CompletionToken&& token = {}) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
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<detail::is_authenticator<Authenticator>, bool> = true
>
mqtt_client& authenticator(Authenticator&& authenticator) {
_impl->authenticator(std::forward<Authenticator>(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 <prop::property_type p>
mqtt_client& connect_property(
std::integral_constant<prop::property_type, p> prop,
prop::value_type_t<p> 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<std::pair<std::string, std::string>>`.
*
* \param prop The \__CONNACK_PROPS\__ property value to retrieve.
*
* \par Example
* \code
* std::optional<std::string> auth_method = client.connack_property(async_mqtt5::prop::authentication_method); // ok
* std::optional<std::string> 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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> 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 <qos_e qos_type,
typename CompletionToken =
typename asio::default_completion_token<executor_type>::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<qos_type>;
return asio::async_initiate<CompletionToken, Signature>(
detail::initiate_async_publish<client_service_type, qos_type>(_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<executor_type>::type
>
decltype(auto) async_subscribe(
const std::vector<subscribe_topic>& topics,
const subscribe_props& props,
CompletionToken&& token = {}
) {
using Signature = void (
error_code, std::vector<reason_code>, suback_props
);
return asio::async_initiate<CompletionToken, Signature>(
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<executor_type>::type
>
decltype(auto) async_subscribe(
const subscribe_topic& topic, const subscribe_props& props,
CompletionToken&& token = {}
) {
return async_subscribe(
std::vector<subscribe_topic> { topic }, props,
std::forward<CompletionToken>(token)
);
}
/**
* \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<executor_type>::type
>
decltype(auto) async_unsubscribe(
const std::vector<std::string>& topics, const unsubscribe_props& props,
CompletionToken&& token = {}
) {
using Signature = void (
error_code, std::vector<reason_code>, unsuback_props
);
return asio::async_initiate<CompletionToken, Signature>(
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<executor_type>::type
>
decltype(auto) async_unsubscribe(
const std::string& topic, const unsubscribe_props& props,
CompletionToken&& token = {}
) {
return async_unsubscribe(
std::vector<std::string> { topic }, props,
std::forward<CompletionToken>(token)
);
}
/**
* \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<executor_type>::type
>
decltype(auto) async_receive(CompletionToken&& token = {}) {
return _impl->async_channel_receive(std::forward<CompletionToken>(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<executor_type>::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<uint8_t>(reason_code)),
props, impl, std::forward<CompletionToken>(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<executor_type>::type
>
decltype(auto) async_disconnect(CompletionToken&& token = {}) {
return async_disconnect(
disconnect_rc_e::normal_disconnection,
disconnect_props {}, std::forward<CompletionToken>(token)
);
}
};
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_MQTT_CLIENT_HPP

View File

@ -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 <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>
#include <boost/container/small_vector.hpp>
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<int32_t, 1>
{
using base_type = boost::container::small_vector<int32_t, 1>;
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 <property_type p>
struct property_traits;
using user_property_value_t = std::vector<std::pair<std::string, std::string>>;
#define DEF_PROPERTY_TRAIT(Pname, Ptype) \
template <> \
struct property_traits<Pname##_t> { \
static constexpr std::string_view name = #Pname; \
using type = Ptype; \
}; \
constexpr std::integral_constant<property_type, Pname##_t> Pname {};
DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(content_type, std::optional<std::string>);
DEF_PROPERTY_TRAIT(response_topic, std::optional<std::string>);
DEF_PROPERTY_TRAIT(correlation_data, std::optional<std::string>);
DEF_PROPERTY_TRAIT(subscription_identifier, subscription_identifiers);
DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional<std::string>);
DEF_PROPERTY_TRAIT(server_keep_alive, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(authentication_method, std::optional<std::string>);
DEF_PROPERTY_TRAIT(authentication_data, std::optional<std::string>);
DEF_PROPERTY_TRAIT(request_problem_information, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(will_delay_interval, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(request_response_information, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(response_information, std::optional<std::string>);
DEF_PROPERTY_TRAIT(server_reference, std::optional<std::string>);
DEF_PROPERTY_TRAIT(reason_string, std::optional<std::string>);
DEF_PROPERTY_TRAIT(receive_maximum, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(topic_alias, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(maximum_qos, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(retain_available, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(user_property, user_property_value_t);
DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional<uint8_t>);
#undef DEF_PROPERTY_TRAIT
template <property_type p>
using value_type_t = typename property_traits<p>::type;
template <property_type p>
constexpr std::string_view name_v = property_traits<p>::name;
template <property_type ...Ps>
class properties {
template <property_type p>
struct property {
using key = std::integral_constant<property_type, p>;
constexpr static std::string_view name = name_v<p>;
value_type_t<p> value;
};
std::tuple<property<Ps>...> _props;
public:
template <property_type v>
constexpr auto& operator[](std::integral_constant<property_type, v>)
noexcept {
return std::get<property<v>>(_props).value;
}
template <property_type v>
constexpr const auto& operator[](std::integral_constant<property_type, v>)
const noexcept {
return std::get<property<v>>(_props).value;
}
template <typename Func>
using is_apply_on = std::conjunction<
std::is_invocable<Func, value_type_t<Ps>&>...
>;
template <typename Func>
using is_nothrow_apply_on = std::conjunction<
std::is_nothrow_invocable<Func, value_type_t<Ps>&>...
>;
template <
typename Func,
std::enable_if_t<is_apply_on<Func>::value, bool> = true
>
constexpr bool apply_on(uint8_t property_id, Func&& func)
noexcept (is_nothrow_apply_on<Func>::value) {
return std::apply(
[&func, property_id](auto&... ptype) {
auto pc = [&func, property_id](auto& px) {
using ptype = std::remove_reference_t<decltype(px)>;
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 <typename Func>
using is_visitor = std::conjunction<
std::is_invocable_r<bool, Func, decltype(Ps), value_type_t<Ps>&>...
>;
template <typename Func>
using is_nothrow_visitor = std::conjunction<
std::is_nothrow_invocable<Func, decltype(Ps), value_type_t<Ps>&>...
>;
template <
typename Func,
std::enable_if_t<is_visitor<Func>::value, bool> = true
>
constexpr bool visit(Func&& func)
const noexcept (is_nothrow_visitor<Func>::value) {
return std::apply(
[&func](const auto&... props) {
auto pc = [&func](const auto& px) {
using ptype = std::remove_reference_t<decltype(px)>;
constexpr typename ptype::key prop;
return std::invoke(func, prop, px.value);
};
return (pc(props) &&...);
},
_props
);
}
template <
typename Func,
std::enable_if_t<is_visitor<Func>::value, bool> = true
>
constexpr bool visit(Func&& func)
noexcept (is_nothrow_visitor<Func>::value) {
return std::apply(
[&func](auto&... props) {
auto pc = [&func](auto& px) {
using ptype = std::remove_reference_t<decltype(px)>;
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

View File

@ -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 <algorithm>
#include <cstdint>
#include <optional>
#include <ostream>
#include <type_traits>
#include <utility>
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<cat == category::connack, bool> = true
>
std::pair<reason_code*, size_t> 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<cat == category::auth, bool> = true
>
std::pair<reason_code*, size_t> 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<reason_code*, size_t> 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<reason_code*, size_t> 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<cat == category::suback, bool> = true
>
std::pair<reason_code*, size_t> 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<cat == category::unsuback, bool> = true
>
std::pair<reason_code*, size_t> 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<cat == category::disconnect, bool> = true
>
std::pair<reason_code*, size_t> 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 <reason_codes::category cat>
std::optional<reason_code> to_reason_code(uint8_t code) {
auto [ptr, len] = reason_codes::detail::valid_codes<cat>();
auto it = std::lower_bound(ptr, ptr + len, reason_code(code));
if (it->value() == code)
return *it;
return std::nullopt;
}
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_REASON_CODES_HPP

View File

@ -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 <cstdint>
#include <string>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/property_types.hpp>
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

View File

@ -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 <boost/beast/http/field.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5 {
// Trait definition for Beast
template <typename Stream>
struct ws_handshake_traits<boost::beast::websocket::stream<Stream>> {
template <typename CompletionToken>
static decltype(auto) async_handshake(
boost::beast::websocket::stream<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<CompletionToken>(token)
);
}
};
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_WEBSOCKET_HPP

19
include/boost/mqtt5.hpp Normal file
View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/logger_traits.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#endif // !BOOST_MQTT5_HPP

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <boost/type_traits/is_detected_convertible.hpp>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
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 <typename T, typename ...Ts>
using async_auth_sig = decltype(
std::declval<T>().async_auth(std::declval<Ts>()...)
);
template <typename T>
using method_sig = decltype(
std::declval<T>().method()
);
template <typename T>
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<std::string_view, method_sig, T>;
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<is_authenticator<Authenticator>>
>
class auth_fun : public auth_fun_base {
Authenticator _authenticator;
public:
auth_fun(Authenticator authenticator) :
auth_fun_base(&async_auth),
_authenticator(std::forward<Authenticator>(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<auth_fun*>(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<detail::auth_fun_base> _auth_fun;
public:
any_authenticator() = default;
template <
typename Authenticator,
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
>
any_authenticator(Authenticator&& a) :
_method(a.method()),
_auth_fun(
new detail::auth_fun<Authenticator>(
std::forward<Authenticator>(a)
)
)
{}
std::string_view method() const {
return _method;
}
template <typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), step, std::move(data)
);
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_ANY_AUTHENTICATOR

View File

@ -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 <boost/mqtt5/detail/async_traits.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/execution.hpp>
#include <boost/asio/require.hpp>
#include <boost/system/error_code.hpp>
#include <atomic>
#include <deque>
#include <mutex>
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<queued_op_t>;
// Handler with assigned tracking executor.
// Objects of this type are type-erased by any_completion_handler
// and stored in the waiting queue.
template <typename Handler, typename Executor>
class tracked_op {
tracking_type<Handler, Executor> _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<Handler>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<Handler>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = tracking_type<Handler, Executor>;
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 <typename Executor>
explicit async_mutex(Executor&& ex) : _ex(std::forward<Executor>(ex)) {}
async_mutex(const async_mutex&) = delete;
async_mutex& operator=(const async_mutex&) = delete;
~async_mutex() {
cancel();
}
const executor_type& get_executor() const noexcept {
return _ex;
}
bool is_locked() const noexcept {
return _locked;
}
// Schedules mutex for lock operation and return immediately.
// Calls given completion handler when mutex is locked.
// It's the responsibility of the completion handler to unlock the mutex.
template <typename CompletionToken>
decltype(auto) lock(CompletionToken&& token) noexcept {
using Signature = void (error_code);
auto initiation = [] (auto handler, async_mutex& self) {
self.execute_or_queue(std::move(handler));
};
return asio::async_initiate<CompletionToken, Signature>(
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 <typename Handler>
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<cancel_waiting_op>(
_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

View File

@ -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 <type_traits>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/execution.hpp>
#include <boost/asio/prefer.hpp>
#include <boost/asio/write.hpp>
#include <boost/type_traits/detected_or.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/types.hpp>
#include <type_traits>
namespace async_mqtt5 {
namespace boost::mqtt5 {
namespace asio = boost::asio;
@ -44,19 +43,19 @@ namespace detail {
template <typename Handler, typename DfltExecutor>
using tracking_type = std::decay_t<
typename asio::prefer_result<
asio::associated_executor_t<Handler, DfltExecutor>,
asio::execution::outstanding_work_t::tracked_t
>::type
typename asio::prefer_result<
asio::associated_executor_t<Handler, DfltExecutor>,
asio::execution::outstanding_work_t::tracked_t
>::type
>;
template <typename Handler, typename DfltExecutor>
tracking_type<Handler, DfltExecutor>
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<void, tls_handshake_t, T>;
template <typename T, typename ...Ts>
using async_tls_handshake_sig = decltype(
std::declval<T&>().async_handshake(std::declval<Ts>()...)
std::declval<T&>().async_handshake(std::declval<Ts>()...)
);
template <typename T>
constexpr bool has_tls_handshake = boost::is_detected<
async_tls_handshake_sig, T, tls_handshake_type_of<T>,
decltype(handshake_handler_t)
async_tls_handshake_sig, T, tls_handshake_type_of<T>,
decltype(handshake_handler_t)
>::value;
// websocket handshake
template <typename T, typename ...Ts>
using async_ws_handshake_sig = decltype(
std::declval<T&>().async_handshake(std::declval<Ts>()...)
std::declval<T&>().async_handshake(std::declval<Ts>()...)
);
template <typename T>
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<T&>().next_layer());
template <typename T>
constexpr bool has_next_layer = boost::is_detected<
next_layer_sig, boost::remove_cv_ref_t<T>
next_layer_sig, boost::remove_cv_ref_t<T>
>::value;
template <typename T, typename Enable = void>
struct next_layer_type_impl {
using type = T;
using type = T;
};
template <typename T>
struct next_layer_type_impl<T, std::enable_if_t<has_next_layer<T>>> {
using type = typename T::next_layer_type;
using type = typename T::next_layer_type;
};
template <typename T>
using next_layer_type = typename next_layer_type_impl<
boost::remove_cv_ref_t<T>
boost::remove_cv_ref_t<T>
>::type;
template <typename T>
next_layer_type<T>& next_layer(T&& a) {
if constexpr (has_next_layer<T>)
return a.next_layer();
else
return std::forward<T>(a);
if constexpr (has_next_layer<T>)
return a.next_layer();
else
return std::forward<T>(a);
}
// lowest layer
template <typename T, typename Enable = void>
struct lowest_layer_type_impl {
using type = T;
using type = T;
};
template <typename T>
struct lowest_layer_type_impl<T, std::enable_if_t<has_next_layer<T>>> {
using type = typename lowest_layer_type_impl<
next_layer_type<T>
>::type;
using type = typename lowest_layer_type_impl<
next_layer_type<T>
>::type;
};
template <typename T>
using lowest_layer_type = typename lowest_layer_type_impl<
boost::remove_cv_ref_t<T>
boost::remove_cv_ref_t<T>
>::type;
template <typename T>
lowest_layer_type<T>& lowest_layer(T&& a) {
if constexpr (has_next_layer<T>)
return lowest_layer(a.next_layer());
else
return std::forward<T>(a);
if constexpr (has_next_layer<T>)
return lowest_layer(a.next_layer());
else
return std::forward<T>(a);
}
// tls layer
@ -162,78 +161,78 @@ struct has_tls_layer_impl : std::false_type {};
template <typename T>
struct has_tls_layer_impl<
T, std::enable_if_t<has_tls_handshake<T>>
T, std::enable_if_t<has_tls_handshake<T>>
> : std::true_type {};
template <typename T>
struct has_tls_layer_impl<
T, std::enable_if_t<!has_tls_handshake<T> && has_next_layer<T>>
T, std::enable_if_t<!has_tls_handshake<T> && has_next_layer<T>>
> : has_tls_layer_impl<
boost::remove_cv_ref_t<decltype(std::declval<T&>().next_layer())>
boost::remove_cv_ref_t<decltype(std::declval<T&>().next_layer())>
> {};
template <typename T>
constexpr bool has_tls_layer = has_tls_layer_impl<
boost::remove_cv_ref_t<T>
boost::remove_cv_ref_t<T>
>::value;
// tls context
template <typename T>
using tls_context_sig = decltype(
std::declval<T&>().tls_context()
std::declval<T&>().tls_context()
);
template <typename T>
constexpr bool has_tls_context = boost::is_detected<
tls_context_sig, T
tls_context_sig, T
>::value;
// setup_tls_sni
template <typename TlsContext, typename Stream>
void setup_tls_sni(const authority_path& ap, TlsContext& ctx, Stream& s) {
if constexpr (has_tls_handshake<Stream>)
assign_tls_sni(ap, ctx, s);
else if constexpr (has_next_layer<Stream>)
setup_tls_sni(ap, ctx, next_layer(s));
if constexpr (has_tls_handshake<Stream>)
assign_tls_sni(ap, ctx, s);
else if constexpr (has_next_layer<Stream>)
setup_tls_sni(ap, ctx, next_layer(s));
}
// async_write
template <typename T, typename ...Ts>
using async_write_sig = decltype(
std::declval<T&>().async_write(std::declval<Ts>()...)
std::declval<T&>().async_write(std::declval<Ts>()...)
);
constexpr auto write_handler_t = [](error_code, size_t) {};
template <typename T, typename B>
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<Stream, ConstBufferSequence>)
return stream.async_write(
buff, std::forward<CompletionToken>(token)
);
else
return asio::async_write(
stream, buff, std::forward<CompletionToken>(token)
);
if constexpr (has_async_write<Stream, ConstBufferSequence>)
return stream.async_write(
buff, std::forward<CompletionToken>(token)
);
else
return asio::async_write(
stream, buff, std::forward<CompletionToken>(token)
);
}
} // end namespace detail
} // end namespace async_mqtt5
} // end namespace boost::mqtt5
#endif // !ASYNC_MQTT5_ASYNC_TRAITS_HPP
#endif // !BOOST_MQTT5_ASYNC_TRAITS_HPP

View File

@ -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 <boost/mqtt5/detail/async_traits.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_immediate_executor.hpp>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/prepend.hpp>
namespace boost::mqtt5::detail {
template <typename Handler, typename Executor>
class cancellable_handler {
Executor _executor;
Handler _handler;
tracking_type<Handler, Executor> _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<Handler>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type = asio::associated_cancellation_slot_t<Handler>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
using executor_type = tracking_type<Handler, Executor>;
executor_type get_executor() const noexcept {
return _handler_ex;
}
using immediate_executor_type =
asio::associated_immediate_executor_t<Handler, Executor>;
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 <typename... Args>
void complete(Args&&... args) {
asio::get_associated_cancellation_slot(_handler).clear();
asio::dispatch(
_handler_ex,
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
);
}
template <typename... Args>
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>(args)...)
);
}
};
} // end boost::mqtt5::detail
#endif // !BOOST_MQTT5_CANCELLABLE_HANDLER_HPP

View File

@ -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 <boost/asio/error.hpp>
#include <deque>
#include <type_traits>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
template <typename Element>
class bounded_deque {
std::deque<Element> _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 <typename E>
void push_back(E&& e) {
if (_buffer.size() == MAX_SIZE)
_buffer.pop_front();
_buffer.push_back(std::forward<E>(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 <typename... Signatures>
struct channel_traits {
template <typename... NewSignatures>
struct rebind {
using other = channel_traits<NewSignatures...>;
};
};
template <typename R, typename... Args>
struct channel_traits<R(error_code, Args...)> {
static_assert(sizeof...(Args) > 0);
template <typename... NewSignatures>
struct rebind {
using other = channel_traits<NewSignatures...>;
};
template <typename Element>
struct container {
using type = bounded_deque<Element>;
};
using receive_cancelled_signature = R(error_code, Args...);
template <typename F>
static void invoke_receive_cancelled(F f) {
std::forward<F>(f)(
asio::error::operation_aborted,
typename std::decay_t<Args>()...
);
}
using receive_closed_signature = R(error_code, Args...);
template <typename F>
static void invoke_receive_closed(F f) {
std::forward<F>(f)(
asio::error::operation_aborted,
typename std::decay_t<Args>()...
);
}
};
} // namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_CHANNEL_TRAITS_HPP

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/smart_ptr/allocate_unique.hpp>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
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 <typename Allocator>
class control_packet {
uint16_t _packet_id;
using alloc_type = Allocator;
using deleter = boost::alloc_deleter<std::string, alloc_type>;
std::unique_ptr<std::string, deleter> _packet;
control_packet(
const Allocator& a,
uint16_t packet_id, std::string packet
) :
_packet_id(packet_id),
_packet(boost::allocate_unique<std::string>(a, std::move(packet)))
{}
public:
control_packet(control_packet&&) noexcept = default;
control_packet(const control_packet&) = 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>(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>(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<interval> _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

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/any_authenticator.hpp>
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
namespace boost::mqtt5::detail {
using byte_citer = std::string::const_iterator;
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
using duration = time_stamp::duration;
struct credentials {
std::string client_id;
std::optional<std::string> username;
std::optional<std::string> password;
credentials() = default;
credentials(
std::string client_id,
std::string username, std::string password
) :
client_id(std::move(client_id))
{
if (!username.empty())
this->username = std::move(username);
if (!password.empty())
this->password = std::move(password);
}
};
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> 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

View File

@ -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 <boost/mqtt5/logger_traits.hpp>
#include <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <string_view>
#include <type_traits>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
using boost::system::error_code;
template <typename LoggerType>
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<LoggerType>)
_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<LoggerType>)
_logger.at_tcp_connect(ec, ep);
}
void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
if constexpr (has_at_tls_handshake<LoggerType>)
_logger.at_tls_handshake(ec, ep);
}
void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
if constexpr (has_at_ws_handshake<LoggerType>)
_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<LoggerType>)
_logger.at_connack(rc, session_present, ca_props);
}
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
if constexpr (has_at_disconnect<LoggerType>)
_logger.at_disconnect(rc, dc_props);
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_LOG_INVOKE_HPP

View File

@ -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 <typename Stream, typename Executor>
struct rebind_executor {
using other = typename Stream::template rebind_executor<Executor>::other;
using other = typename Stream::template rebind_executor<Executor>::other;
};
// asio::ssl::stream does not define a rebind_executor member type
template <typename Stream, typename Executor>
struct rebind_executor<asio::ssl::stream<Stream>, Executor> {
using other = typename asio::ssl::stream<
typename rebind_executor<Stream, Executor>::other
>;
using other = typename asio::ssl::stream<
typename rebind_executor<Stream, Executor>::other
>;
};
template <typename Stream, bool deflate_supported, typename Executor>
struct rebind_executor<
boost::beast::websocket::stream<asio::ssl::stream<Stream>, deflate_supported>,
Executor
boost::beast::websocket::stream<asio::ssl::stream<Stream>, deflate_supported>,
Executor
> {
using other = typename boost::beast::websocket::stream<
asio::ssl::stream<typename rebind_executor<Stream, Executor>::other>,
deflate_supported
>;
using other = typename boost::beast::websocket::stream<
asio::ssl::stream<typename rebind_executor<Stream, Executor>::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

View File

@ -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 <boost/mqtt5/detail/utf8_mqtt.hpp>
#include <cstdint>
#include <string_view>
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

View File

@ -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 <optional>
#include <vector>
#include <utility>
#ifndef BOOST_MQTT5_TRAITS_HPP
#define BOOST_MQTT5_TRAITS_HPP
#include <boost/container/small_vector.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
namespace async_mqtt5::detail {
#include <optional>
#include <utility>
#include <vector>
namespace boost::mqtt5::detail {
template <typename>
constexpr bool is_optional_impl = false;
@ -35,29 +35,29 @@ constexpr bool is_specialization<T<Args...>, T> = true;
template <typename T>
constexpr bool is_vector = is_specialization<
boost::remove_cv_ref_t<T>, std::vector
boost::remove_cv_ref_t<T>, std::vector
>;
template <typename... Args>
constexpr std::true_type is_small_vector_impl(
boost::container::small_vector_base<Args...> const &
boost::container::small_vector_base<Args...> const &
);
constexpr std::false_type is_small_vector_impl( ... );
template <typename T>
constexpr bool is_small_vector =
decltype(is_small_vector_impl(std::declval<T>()))::value;
decltype(is_small_vector_impl(std::declval<T>()))::value;
template <typename T>
constexpr bool is_pair = is_specialization<
boost::remove_cv_ref_t<T>, std::pair
boost::remove_cv_ref_t<T>, std::pair
>;
template <typename T>
constexpr bool is_boost_iterator = is_specialization<
boost::remove_cv_ref_t<T>, boost::iterator_range
boost::remove_cv_ref_t<T>, boost::iterator_range
>;
} // end namespace async_mqtt5::detail
} // end namespace boost::mqtt5::detail
#endif // !ASYNC_MQTT5_TRAITS_HPP
#endif // !BOOST_MQTT5_TRAITS_HPP

View File

@ -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 <cstdint>
#include <string>
#include <string_view>
#include <utility>
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 <typename ValidSizeCondition, typename ValidCondition>
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<std::string, std::string>& 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

View File

@ -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 <boost/asio/error.hpp>
#include <cstdint>
#include <ostream>
#include <string>
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<error>(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<int>(r), get_error_code_category() };
}
inline std::ostream& operator<<(std::ostream& os, const error& err) {
os << get_error_code_category().name() << ":" << static_cast<int>(err);
return os;
}
} // end namespace client
} // end namespace boost::mqtt5
namespace boost::system {
template <>
struct is_error_code_enum <boost::mqtt5::client::error> : std::true_type {};
} // end namespace boost::system
#endif // !BOOST_MQTT5_ERROR_HPP

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <cstdint>
#include <string>
#include <utility>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
class data_span : private std::pair<byte_citer, byte_citer> {
using base = std::pair<byte_citer, byte_citer>;
public:
using base::base;
auto first() const {
return base::first;
}
auto last() const {
return base::second;
}
void expand_suffix(size_t num_chars) {
base::second += num_chars;
}
void remove_prefix(size_t num_chars) {
base::first += num_chars;
}
size_t size() const {
return std::distance(base::first, base::second);
}
};
template <typename ClientService, typename Handler>
class assemble_op {
using client_service = ClientService;
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename CompletionCondition>
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 <typename CompletionCondition>
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<uint32_t>(*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<duration::rep>::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

View File

@ -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 <boost/mqtt5/detail/internal_types.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/bind_allocator.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <boost/system/error_code.hpp>
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
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<void (error_code)>;
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 <typename ClientService>
class async_sender {
using self_type = async_sender<ClientService>;
using client_service = ClientService;
using queue_allocator_type = asio::recycling_allocator<write_req>;
using write_queue_t = std::vector<write_req, queue_allocator_type>;
ClientService& _svc;
write_queue_t _write_queue;
bool _write_in_progress { false };
static constexpr uint16_t MAX_LIMIT = 65535;
uint16_t _limit { MAX_LIMIT };
uint16_t _quota { MAX_LIMIT };
serial_num_t _last_serial_num { 0 };
public:
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 <typename CompletionToken, typename BufferType>
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<CompletionToken, Signature>(
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<asio::const_buffer> buffers;
buffers.reserve(write_queue.size());
for (const auto& op : write_queue)
buffers.push_back(op.buffer());
_svc._replies.clear_fast_replies();
_svc._stream.async_write(
buffers,
asio::prepend(std::ref(*this), std::move(write_queue))
);
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_ASYNC_SENDER_HPP

View File

@ -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 <boost/mqtt5/detail/async_mutex.hpp>
#include <boost/mqtt5/detail/async_traits.hpp>
#include <boost/mqtt5/detail/log_invoke.hpp>
#include <boost/mqtt5/impl/endpoints.hpp>
#include <boost/mqtt5/impl/read_op.hpp>
#include <boost/mqtt5/impl/reconnect_op.hpp>
#include <boost/mqtt5/impl/write_op.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/system/error_code.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
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<StreamType, StreamContext, LoggerType>;
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<stream_type>;
executor_type _stream_executor;
async_mutex _conn_mtx;
asio::steady_timer _read_timer, _connect_timer;
endpoints<logger_type> _endpoints;
stream_ptr _stream_ptr;
stream_context_type& _stream_context;
log_invoke<logger_type>& _log;
template <typename Owner, typename Handler>
friend class read_op;
template <typename Owner, typename Handler>
friend class write_op;
template <typename Owner>
friend class reconnect_op;
public:
autoconnect_stream(
const executor_type& ex, stream_context_type& context,
log_invoke<logger_type>& 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 <typename BufferType, typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), buffer, wait_for
);
}
template <typename BufferType, typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), buffer
);
}
private:
log_invoke<logger_type>& 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<StreamContext>)
sptr = std::make_shared<stream_type>(
_stream_executor, _stream_context.tls_context()
);
else
sptr = std::make_shared<stream_type>(_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 <typename CompletionToken>
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), std::move(s)
);
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_AUTOCONNECT_STREAM_HPP

View File

@ -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 <boost/mqtt5/detail/channel_traits.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/detail/log_invoke.hpp>
#include <boost/mqtt5/impl/assemble_op.hpp>
#include <boost/mqtt5/impl/async_sender.hpp>
#include <boost/mqtt5/impl/autoconnect_stream.hpp>
#include <boost/mqtt5/impl/replies.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/experimental/basic_channel.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
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<has_tls_layer<StreamType>>
> {
using tls_context_type = TlsContext;
mqtt_ctx _mqtt_context;
std::shared_ptr<tls_context_type> _tls_context_ptr;
public:
explicit stream_context(TlsContext tls_context) :
_tls_context_ptr(std::make_shared<tls_context_type>(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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.ca_props[prop];
}
const auto& connack_properties() const {
return _mqtt_context.ca_props;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.co_props[prop];
}
template <prop::property_type p>
auto& connect_property(
std::integral_constant<prop::property_type, p> 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 <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <typename StreamType>
class stream_context<
StreamType, std::monostate,
std::enable_if_t<!has_tls_layer<StreamType>>
> {
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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.ca_props[prop];
}
const auto& connack_properties() const {
return _mqtt_context.ca_props;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.co_props[prop];
}
template <prop::property_type p>
auto& connect_property(
std::integral_constant<prop::property_type, p> 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 <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <
typename StreamType,
typename TlsContext = std::monostate,
typename LoggerType = noop_logger
>
class client_service {
using self_type = client_service<StreamType, TlsContext, LoggerType>;
using stream_context_type = stream_context<StreamType, TlsContext>;
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 <typename ClientService, typename Handler>
friend class run_op;
template <typename ClientService>
friend class async_sender;
template <typename ClientService, typename Handler>
friend class assemble_op;
template <typename ClientService, typename Handler>
friend class ping_op;
template <typename ClientService, typename Handler>
friend class sentry_op;
template <typename ClientService>
friend class re_auth_op;
executor_type _executor;
log_invoke<logger_type> _log;
stream_context_type _stream_context;
stream_type _stream;
packet_id_allocator _pid_allocator;
replies _replies;
async_sender<client_service> _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<size_t>::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<size_t>::max()),
_ping_timer(ex),
_sentry_timer(ex)
{}
executor_type get_executor() const noexcept {
return _executor;
}
auto dup() const {
return std::shared_ptr<client_service>(new client_service(*this));
}
template <
typename Ctx = TlsContext,
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, 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<is_authenticator<Authenticator>, bool> = true
>
void authenticator(Authenticator&& authenticator) {
if (!is_open())
_stream_context.authenticator(
std::forward<Authenticator>(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 <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _stream_context.connect_property(prop);
}
template <prop::property_type p>
void connect_property(
std::integral_constant<prop::property_type, p> prop,
prop::value_type_t<p> 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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> 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<LoggerType>& 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 <typename BufferType, typename CompletionToken>
decltype(auto) async_send(
const BufferType& buffer,
serial_num_t serial_num, unsigned flags,
CompletionToken&& token
) {
return _async_sender.async_send(
buffer, serial_num, flags, std::forward<CompletionToken>(token)
);
}
template <typename CompletionToken>
decltype(auto) async_assemble(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<CompletionToken, Signature> (
initiation, token, std::ref(*this),
std::ref(_read_buff), std::ref(_active_span)
);
}
template <typename CompletionToken>
decltype(auto) async_wait_reply(
control_code_e code, uint16_t packet_id, CompletionToken&& token
) {
return _replies.async_wait_reply(
code, packet_id, std::forward<CompletionToken>(token)
);
}
template <typename CompletionToken>
decltype(auto) async_channel_receive(CompletionToken&& token) {
return _rec_channel.async_receive(std::forward<CompletionToken>(token));
}
};
} // namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_CLIENT_SERVICE_HPP

View File

@ -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 <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/detail/traits.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/container/deque.hpp>
#include <boost/optional/optional.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/binary/binary.hpp>
#include <cstdint>
#include <string>
#include <utility>
namespace boost::mqtt5::decoders {
namespace x3 = boost::spirit::x3;
template <typename T>
struct convert { using type = T; };
template <typename ...Args>
struct convert<boost::fusion::deque<Args...>> {
using type = std::tuple<typename convert<Args>::type...>;
};
template <typename T>
struct convert<boost::optional<T>> {
using type = std::optional<T>;
};
template <typename T>
struct convert<std::vector<T>> {
using type = std::vector<typename convert<T>::type>;
};
template <typename T, typename Parser>
constexpr auto as(Parser&& p) {
return x3::rule<struct _, T>{} = std::forward<Parser>(p);
}
template <typename It, typename Parser>
auto type_parse(It& first, const It last, const Parser& p) {
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Parser&>()));
using attr_type = typename x3::traits::attribute_of<Parser, ctx_type>::type;
using rv_type = typename convert<attr_type>::type;
std::optional<rv_type> rv;
rv_type value {};
if (x3::phrase_parse(first, last, as<rv_type>(p), x3::eps(false), value))
rv = std::move(value);
return rv;
}
template <typename AttributeType, typename It, typename Parser>
auto type_parse(It& first, const It last, const Parser& p) {
std::optional<AttributeType> rv;
AttributeType value {};
if (x3::phrase_parse(first, last, as<AttributeType>(p), x3::eps(false), value))
rv = std::move(value);
return rv;
}
namespace basic {
template <typename T>
constexpr auto to(T& arg) {
return [&](auto& ctx) {
using ctx_type = decltype(ctx);
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
if constexpr (detail::is_boost_iterator<attr_type>)
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
else
arg = x3::_attr(ctx);
};
}
template <typename LenParser, typename Subject, typename Enable = void>
class scope_limit {};
template <typename LenParser, typename Subject>
class scope_limit<
LenParser, Subject,
std::enable_if_t<x3::traits::is_parser<LenParser>::value>
> :
public x3::unary_parser<Subject, scope_limit<LenParser, Subject>>
{
using base_type = x3::unary_parser<Subject, scope_limit<LenParser, Subject>>;
LenParser _lp;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using attribute_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
static bool const has_attribute = true;
scope_limit(const LenParser& lp, const Subject& subject) :
base_type(subject), _lp(lp)
{}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
typename x3::traits::attribute_of<LenParser, Ctx>::type len;
if (!_lp.parse(iter, last, ctx, rctx, len))
return false;
if (iter + len > last)
return false;
if (!base_type::subject.parse(iter, iter + len, ctx, rctx, attr))
return false;
first = iter;
return true;
}
};
template <typename Size, typename Subject>
class scope_limit<
Size, Subject,
std::enable_if_t<std::is_arithmetic_v<Size>>
> :
public x3::unary_parser<Subject, scope_limit<Size, Subject>>
{
using base_type = x3::unary_parser<Subject, scope_limit<Size, Subject>>;
size_t _limit;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using attribute_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
static bool const has_attribute = true;
scope_limit(Size limit, const Subject& subject) :
base_type(subject), _limit(limit)
{}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
if (iter + _limit > last)
return false;
if (!base_type::subject.parse(iter, iter + _limit, ctx, rctx, attr))
return false;
first = iter;
return true;
}
};
template <typename LenParser, typename Enable = void>
struct scope_limit_gen {
template <typename Subject>
auto operator[](const Subject& p) const {
return scope_limit<LenParser, Subject> { _lp, x3::as_parser(p) };
}
LenParser _lp;
};
template <typename Size>
struct scope_limit_gen<
Size,
std::enable_if_t<std::is_arithmetic_v<Size>>
> {
template <typename Subject>
auto operator[](const Subject& p) const {
return scope_limit<Size, Subject> { limit, x3::as_parser(p) };
}
Size limit;
};
template <
typename Parser,
std::enable_if_t<x3::traits::is_parser<Parser>::value, bool> = true
>
scope_limit_gen<Parser> scope_limit_(const Parser& p) {
return { p };
}
template <
typename Size,
std::enable_if_t<std::is_arithmetic_v<Size>, bool> = true
>
scope_limit_gen<Size> scope_limit_(Size limit) {
return { limit };
}
struct verbatim_parser : x3::parser<verbatim_parser> {
using attribute_type = std::string;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(It& first, const It last, const Ctx&, RCtx&, Attr& attr) const {
attr = std::string { first, last };
first = last;
return true;
}
};
constexpr auto verbatim_ = verbatim_parser{};
struct varint_parser : x3::parser<varint_parser> {
using attribute_type = int32_t;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx&, 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<int32_t>(val) << bit_shift);
bit_shift = 0;
break;
}
}
if (bit_shift)
return false;
attr = result;
first = ++iter;
return true;
}
};
constexpr varint_parser varint_{};
struct len_prefix_parser : x3::parser<len_prefix_parser> {
using attribute_type = std::string;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
typename x3::traits::attribute_of<decltype(x3::big_word), Ctx>::type len;
if (x3::big_word.parse(iter, last, ctx, rctx, len)) {
if (std::distance(iter, last) < len)
return false;
}
else
return false;
attr = std::string(iter, iter + len);
first = iter + len;
return true;
}
};
constexpr len_prefix_parser utf8_ {};
constexpr len_prefix_parser binary_ {};
/*
Boost Spirit incorrectly deduces atribute type for a parser of the form
(eps(a) | parser1) >> (eps(b) | parser)
and we had to create if_ parser to remedy the issue
*/
template <typename Subject>
class conditional_parser :
public x3::unary_parser<Subject, conditional_parser<Subject>> {
using base_type = x3::unary_parser<Subject, conditional_parser<Subject>>;
bool _condition;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using subject_attr_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
using attribute_type = boost::optional<subject_attr_type>;
static bool const has_attribute = true;
conditional_parser(const Subject& s, bool condition) :
base_type(s), _condition(condition) {}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
if (!_condition)
return true;
It iter = first;
subject_attr_type sattr {};
if (!base_type::subject.parse(iter, last, ctx, rctx, sattr))
return false;
attr.emplace(std::move(sattr));
first = iter;
return true;
}
};
struct conditional_gen {
bool _condition;
template <typename Subject>
auto operator[](const Subject& p) const {
return conditional_parser<Subject> { p, _condition };
}
};
inline conditional_gen if_(bool condition) {
return { condition };
}
} // end namespace basic
namespace prop {
namespace basic = boost::mqtt5::decoders::basic;
namespace detail {
template <typename It, typename Ctx, typename RCtx, typename Prop>
bool parse_to_prop(
It& iter, const It last,
const Ctx& ctx, RCtx& rctx, Prop& prop
) {
using namespace boost::mqtt5::detail;
using prop_type = std::remove_reference_t<decltype(prop)>;
bool rv = false;
if constexpr (std::is_same_v<prop_type, uint8_t>)
rv = x3::byte_.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, uint16_t>)
rv = x3::big_word.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, int32_t>)
rv = basic::varint_.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, uint32_t>)
rv = x3::big_dword.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, std::string>)
rv = basic::utf8_.parse(iter, last, ctx, rctx, prop);
else if constexpr (is_optional<prop_type>) {
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<prop_type>) {
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<prop_type> || is_small_vector<prop_type>) {
typename std::remove_reference_t<prop_type>::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 <typename Props>
class prop_parser : public x3::parser<prop_parser<Props>> {
public:
using attribute_type = Props;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
if (iter == last)
return true;
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 <typename Props>
constexpr auto props_ = prop_parser<Props> {};
} // end namespace prop
} // end namespace boost::mqtt5::decoders
#endif // !BOOST_MQTT5_BASE_DECODERS_HPP

View File

@ -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 <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/detail/traits.hpp>
#include <boost/core/identity.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/type_traits/is_detected_exact.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <cstddef>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
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 <size_t bits, typename repr = uint8_t>
class flag_def : public encoder {
template <size_t num_bits>
using least_type = std::conditional_t<
num_bits <= 8, uint8_t,
std::conditional_t<
num_bits <= 16, uint16_t,
std::conditional_t<
num_bits <= 32, uint32_t,
std::conditional_t<num_bits <= 64, uint64_t, void>
>
>
>;
template <size_t oth_bits, typename oth_repr>
friend class flag_def;
repr _val { 0 };
public:
flag_def(repr val) : _val(val) {}
flag_def() = default;
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<detail::is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
if constexpr (std::is_same_v<projection, boost::identity>) {
repr val = value.has_value();
return flag_def<bits, repr> { val };
}
else {
repr val = value.has_value() ?
static_cast<repr>(std::invoke(proj, *value)) : 0;
return flag_def<bits, repr> { val };
}
}
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<!detail::is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
auto val = static_cast<repr>(std::invoke(proj, value));
return flag_def<bits, repr> { val };
}
size_t byte_size() const { return sizeof(repr); }
template <size_t rhs_bits, typename rhs_repr>
auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
using res_repr = least_type<bits + rhs_bits>;
auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
return flag_def<bits + rhs_bits, res_repr> { val };
}
std::string& encode(std::string& s) const {
using namespace boost::endian;
size_t sz = s.size(); s.resize(sz + sizeof(repr));
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<repr, sizeof(repr), order::big>(p, _val);
return s;
}
};
template <size_t bits, typename repr = uint8_t>
constexpr auto flag = flag_def<bits, repr>{};
template <typename T, typename Repr>
class int_val : public encoder {
T _val;
public:
int_val(T val) : _val(val) {}
size_t byte_size() const {
if constexpr (detail::is_optional<T>) {
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<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
else
return encode_val(s, _val);
}
private:
template <typename U>
static size_t val_length(U&& val) {
if constexpr (std::is_same_v<Repr, varint_t>)
return variable_length(int32_t(val));
else
return sizeof(Repr);
}
template <typename U>
static std::string& encode_val(std::string& s, U&& val) {
using namespace boost::endian;
if constexpr (std::is_same_v<Repr, varint_t>) {
to_variable_bytes(s, int32_t(val));
return s;
}
else {
size_t sz = s.size(); s.resize(sz + sizeof(Repr));
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<Repr, sizeof(Repr), order::big>(p, val);
return s;
}
}
};
template <typename Repr>
class int_def {
public:
template <typename T>
auto operator()(T&& val) const {
return int_val<T, Repr> { std::forward<T>(val) };
}
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (detail::is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
if (val.has_value())
return (*this)(std::invoke(proj, *val));
return int_val<rv_type, Repr> { rv_type {} };
}
else {
using rv_type = std::invoke_result_t<projection, T>;
return int_val<rv_type, Repr> { std::invoke(proj, val) };
}
}
};
constexpr auto byte_ = int_def<uint8_t> {};
constexpr auto int16_ = int_def<uint16_t> {};
constexpr auto int32_ = int_def<uint32_t> {};
constexpr auto varlen_ = int_def<varint_t> {};
template <typename T>
class array_val : public encoder {
T _val;
bool _with_length;
public:
array_val(T val, bool with_length) :
_val(val), _with_length(with_length)
{
static_assert(
std::is_reference_v<T> || std::is_same_v<T, std::string_view>
);
}
size_t byte_size() const {
if constexpr (detail::is_optional<T>)
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<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
else
return encode_val(s, _val);
}
private:
template <typename V>
using has_size = decltype(std::declval<V&>().size());
template <typename U>
static size_t val_length(U&& val) {
if constexpr (std::is_same_v<boost::remove_cv_ref_t<U>, const char*>)
return std::strlen(val);
if constexpr (boost::is_detected_exact_v<size_t, has_size, U>)
return val.size();
else // fallback to type const char (&)[N] (substract 1 for trailing 0)
return sizeof(val) - 1;
}
template <typename U>
std::string& encode_val(std::string& s, U&& u) const {
using namespace boost::endian;
auto byte_len = val_length(std::forward<U>(u));
if (byte_len == 0 && !_with_length) return s;
if (_with_length) {
size_t sz = s.size(); s.resize(sz + 2);
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<int16_t, sizeof(int16_t), order::big>(
p, int16_t(byte_len)
);
}
s.append(std::begin(u), std::begin(u) + byte_len);
return s;
}
};
template <bool with_length = true>
class array_def {
public:
template <typename T>
auto operator()(T&& val) const {
return array_val<T> { std::forward<T>(val), with_length };
}
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (detail::is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
if (val.has_value())
return (*this)(std::invoke(proj, *val));
return array_val<rv_type> { rv_type {}, false };
}
else {
const auto& av = std::invoke(proj, val);
return array_val<T> { av, true };
}
}
};
using utf8_def = array_def<true>;
constexpr auto utf8_ = utf8_def {};
constexpr auto binary_ = array_def<true> {}; // for now
constexpr auto verbatim_ = array_def<false> {};
template <typename T, typename U>
class composed_val : public encoder {
T _lhs; U _rhs;
public:
composed_val(T lhs, U rhs) :
_lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs))
{}
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<encoder, std::decay_t<T>> &&
std::is_base_of_v<encoder, std::decay_t<U>>,
bool
> = true
>
inline auto operator&(T&& t, U&& u) {
return composed_val(std::forward<T>(t), std::forward<U>(u));
}
template <
typename T,
std::enable_if_t<std::is_base_of_v<encoder, std::decay_t<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 <typename T>
auto encoder_for_prop_value(const T& val) {
if constexpr (std::is_same_v<T, uint8_t>)
return basic::int_def<uint8_t>{}(val);
else if constexpr (std::is_same_v<T, uint16_t>)
return basic::int_def<uint16_t>{}(val);
else if constexpr (std::is_same_v<T, int32_t>)
return basic::int_def<basic::varint_t>{}(val);
else if constexpr (std::is_same_v<T, uint32_t>)
return basic::int_def<uint32_t>{}(val);
else if constexpr (std::is_same_v<T, std::string>)
return basic::utf8_def{}(val);
else if constexpr (detail::is_pair<T>)
return encoder_for_prop_value(val.first) &
encoder_for_prop_value(val.second);
}
template <typename T, pp::property_type p, typename Enable = void>
class prop_val;
template <
typename T, pp::property_type p
>
class prop_val<
T, p,
std::enable_if_t<!detail::is_vector<T> && detail::is_optional<T>>
> : public basic::encoder {
// allows T to be reference type to std::optional
static inline boost::remove_cv_ref_t<T> nulltype;
T _val;
public:
prop_val(T val) : _val(val) {
static_assert(std::is_reference_v<T>);
}
prop_val() : _val(nulltype) {}
size_t byte_size() const {
if (!_val) 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_vector<T> || detail::is_small_vector<T>>
> : public basic::encoder {
// allows T to be reference type to std::vector
static inline boost::remove_cv_ref_t<T> nulltype;
T _val;
public:
prop_val(T val) : _val(val) {
static_assert(std::is_reference_v<T>);
}
prop_val() : _val(nulltype) { }
size_t byte_size() const {
if (_val.empty()) return 0;
size_t total_size = 0;
for (const auto& 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 <typename Props>
class props_val : public basic::encoder {
static inline std::decay_t<Props> nulltype;
template <pp::property_type P, typename T>
static auto to_prop_val(const T& val) {
return prop_val<const T&, P>(val);
}
template <pp::property_type ...Ps>
static auto to_prop_vals(const pp::properties<Ps...>& props) {
return std::make_tuple(
to_prop_val<Ps>(
props[std::integral_constant<pp::property_type, Ps> {}]
)...
);
}
template <typename Func>
auto apply_each(Func&& func) const {
return std::apply([&func](const auto&... props) {
return (std::invoke(func, props), ...);
}, _prop_vals);
}
decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
bool _may_omit;
public:
props_val(Props val, bool may_omit) :
_prop_vals(to_prop_vals(val)), _may_omit(may_omit)
{
static_assert(std::is_reference_v<Props>);
}
props_val(bool may_omit) :
_prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit)
{}
size_t byte_size() const {
size_t psize = props_size();
if (_may_omit && psize == 0) return 0;
return psize + basic::varlen_(psize).byte_size();
}
std::string& encode(std::string& s) const {
size_t psize = props_size();
if (_may_omit && psize == 0) return s;
basic::varlen_(psize).encode(s);
apply_each([&s](const auto& pv) { return pv.encode(s); });
return s;
}
private:
size_t props_size() const {
size_t retval = 0;
apply_each([&retval](const auto& pv) {
return retval += pv.byte_size();
});
return retval;
}
};
template <bool may_omit>
class props_def {
public:
template <typename T>
auto operator()(T&& prop_container) const {
if constexpr (detail::is_optional<T>) {
if (prop_container.has_value())
return (*this)(*prop_container);
return props_val<
const typename boost::remove_cv_ref_t<T>::value_type&
>(true);
}
else {
return props_val<T> { prop_container, may_omit };
}
}
};
constexpr auto props_ = props_def<false> {};
constexpr auto props_may_omit_ = props_def<true> {};
} // end namespace prop
} // end namespace boost::mqtt5::encoders
#endif // !BOOST_MQTT5_BASE_ENCODERS_HPP

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/impl/codecs/base_decoders.hpp>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
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<fixed_header> decode_fixed_header(
byte_citer& it, const byte_citer last
) {
auto fixed_header_ = x3::byte_ >> basic::varint_;
return type_parse(it, last, fixed_header_);
}
using packet_id = uint16_t;
inline std::optional<packet_id> decode_packet_id(
byte_citer& it
) {
auto packet_id_ = x3::big_word;
return type_parse(it, it + sizeof(uint16_t), packet_id_);
}
using connect_message = std::tuple<
std::string, // client_id,
std::optional<std::string>, // user_name,
std::optional<std::string>, // password,
uint16_t, // keep_alive,
bool, // clean_start,
connect_props, // props,
std::optional<will> // will
>;
inline std::optional<connect_message> decode_connect(
uint32_t remain_length, byte_citer& it
) {
auto var_header_ =
basic::utf8_ >> // MQTT
x3::byte_ >> // (num 5)
x3::byte_ >> // conn_flags_
x3::big_word >> // keep_alive
prop::props_<connect_props>;
const byte_citer end = it + remain_length;
auto vh = type_parse(it, end, var_header_);
if (!vh)
return std::optional<connect_message>{};
auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh;
if (mqtt_str != "MQTT" || version != 5)
return std::optional<connect_message>{};
bool has_will = (flags & 0b00000100);
bool has_uname = (flags & 0b10000000);
bool has_pwd = (flags & 0b01000000);
auto payload_ =
basic::utf8_ >> // client_id
basic::if_(has_will)[prop::props_<will_props>] >>
basic::if_(has_will)[basic::utf8_] >> // will topic
basic::if_(has_will)[basic::binary_] >> // will message
basic::if_(has_uname)[basic::utf8_] >> // username
basic::if_(has_pwd)[basic::utf8_]; // password
auto pload = type_parse(it, end, payload_);
if (!pload)
return std::optional<connect_message>{};
std::optional<will> w;
if (has_will)
w.emplace(
std::move(*std::get<2>(*pload)), // will_topic
std::move(*std::get<3>(*pload)), // will_message
qos_e((flags & 0b00011000) >> 3),
retain_e((flags & 0b00100000) >> 5),
std::move(*std::get<1>(*pload)) // will props
);
connect_message retval = {
std::move(std::get<0>(*pload)), // client_id
std::move(std::get<4>(*pload)), // user_name
std::move(std::get<5>(*pload)), // password
keep_alive,
flags & 0b00000010, // clean_start
std::move(cprops), // connect_props
std::move(w) // will
};
return std::optional<connect_message> { std::move(retval) };
}
using connack_message = std::tuple<
uint8_t, // session_present
uint8_t, // connect reason code
connack_props // props
>;
inline std::optional<connack_message> decode_connack(
uint32_t remain_length, byte_citer& it
) {
auto connack_ = basic::scope_limit_(remain_length)[
x3::byte_ >> x3::byte_ >> prop::props_<connack_props>
];
return type_parse(it, it + remain_length, connack_);
}
using publish_message = std::tuple<
std::string, // topic
std::optional<uint16_t>, // packet_id
uint8_t, // dup_e, qos_e, retain_e
publish_props, // publish props
std::string // payload
>;
inline std::optional<publish_message> decode_publish(
uint8_t control_byte, uint32_t remain_length, byte_citer& it
) {
uint8_t flags = control_byte & 0b1111;
auto qos = qos_e((flags >> 1) & 0b11);
auto publish_ = basic::scope_limit_(remain_length)[
basic::utf8_ >> basic::if_(qos != qos_e::at_most_once)[x3::big_word] >>
x3::attr(flags) >> prop::props_<publish_props> >> basic::verbatim_
];
return type_parse(it, it + remain_length, publish_);
}
using puback_message = std::tuple<
uint8_t, // puback reason code
puback_props // props
>;
inline std::optional<puback_message> decode_puback(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return puback_message {};
auto puback_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<puback_props>
];
return type_parse(it, it + remain_length, puback_);
}
using pubrec_message = std::tuple<
uint8_t, // puback reason code
pubrec_props // props
>;
inline std::optional<pubrec_message> decode_pubrec(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubrec_message {};
auto pubrec_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubrec_props>
];
return type_parse(it, it + remain_length, pubrec_);
}
using pubrel_message = std::tuple<
uint8_t, // puback reason code
pubrel_props // props
>;
inline std::optional<pubrel_message> decode_pubrel(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubrel_message {};
auto pubrel_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubrel_props>
];
return type_parse(it, it + remain_length, pubrel_);
}
using pubcomp_message = std::tuple<
uint8_t, // puback reason code
pubcomp_props // props
>;
inline std::optional<pubcomp_message> decode_pubcomp(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubcomp_message {};
auto pubcomp_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubcomp_props>
];
return type_parse(it, it + remain_length, pubcomp_);
}
using subscribe_message = std::tuple<
subscribe_props,
std::vector<std::tuple<std::string, uint8_t>> // topic filter with opts
>;
inline std::optional<subscribe_message> decode_subscribe(
uint32_t remain_length, byte_citer& it
) {
auto subscribe_ = basic::scope_limit_(remain_length)[
prop::props_<subscribe_props> >> +(basic::utf8_ >> x3::byte_)
];
return type_parse(it, it + remain_length, subscribe_);
}
using suback_message = std::tuple<
suback_props,
std::vector<uint8_t> // reason_codes
>;
inline std::optional<suback_message> decode_suback(
uint32_t remain_length, byte_citer& it
) {
auto suback_ = basic::scope_limit_(remain_length)[
prop::props_<suback_props> >> +x3::byte_
];
return type_parse(it, it + remain_length, suback_);
}
using unsubscribe_message = std::tuple<
unsubscribe_props,
std::vector<std::string> // topics
>;
inline std::optional<unsubscribe_message> decode_unsubscribe(
uint32_t remain_length, byte_citer& it
) {
auto unsubscribe_ = basic::scope_limit_(remain_length)[
prop::props_<unsubscribe_props> >> +basic::utf8_
];
return type_parse(it, it + remain_length, unsubscribe_);
}
using unsuback_message = std::tuple<
unsuback_props,
std::vector<uint8_t> // reason_codes
>;
inline std::optional<unsuback_message> decode_unsuback(
uint32_t remain_length, byte_citer& it
) {
auto unsuback_ = basic::scope_limit_(remain_length)[
prop::props_<unsuback_props> >> +x3::byte_
];
return type_parse(it, it + remain_length, unsuback_);
}
using disconnect_message = std::tuple<
uint8_t, // reason_code
disconnect_props
>;
inline std::optional<disconnect_message> decode_disconnect(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return disconnect_message {};
auto disconnect_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<disconnect_props>
];
return type_parse(it, it + remain_length, disconnect_);
}
using auth_message = std::tuple<
uint8_t, // reason_code
auth_props
>;
inline std::optional<auth_message> decode_auth(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return auth_message {};
auto auth_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<auth_props>
];
return type_parse(it, it + remain_length, auth_);
}
} // end namespace boost::mqtt5::decoders
#endif // !BOOST_MQTT5_MESSAGE_DECODERS_HPP

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/mqtt5/impl/codecs/base_encoders.hpp>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace boost::mqtt5::encoders {
template <typename encoder>
std::string encode(const encoder& e) {
std::string s;
s.reserve(e.byte_size());
s << e;
return s;
}
inline std::string encode_connect(
std::string_view client_id,
std::optional<std::string_view> user_name,
std::optional<std::string_view> password,
uint16_t keep_alive, bool clean_start,
const connect_props& props,
const std::optional<will>& w
) {
auto packet_type_ =
basic::flag<4>(0b0001) |
basic::flag<4>(0);
auto conn_flags_ =
basic::flag<1>(user_name) |
basic::flag<1>(password) |
basic::flag<1>(w, &will::retain) |
basic::flag<2>(w, &will::qos) |
basic::flag<1>(w) |
basic::flag<1>(clean_start) |
basic::flag<1>(0);
auto var_header_ =
basic::utf8_("MQTT") &
basic::byte_(uint8_t(5)) &
conn_flags_ &
basic::int16_(keep_alive) &
prop::props_(props);
auto payload_ =
basic::utf8_(client_id) &
prop::props_(w) &
basic::utf8_(w, &will::topic) &
basic::binary_(w, &will::message) &
basic::utf8_(user_name) &
basic::utf8_(password);
auto message_body_ = var_header_ & payload_;
auto fixed_header_ =
packet_type_ &
basic::varlen_(message_body_.byte_size());
auto connect_message_ = fixed_header_ & message_body_;
return encode(connect_message_);
}
inline std::string encode_connack(
bool session_present,
uint8_t reason_code,
const connack_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0010) |
basic::flag<4>(0);
auto var_header_ =
basic::flag<1>(session_present) &
basic::byte_(reason_code) &
prop::props_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto connack_message_ = fixed_header_ & var_header_;
return encode(connack_message_);
}
inline std::string encode_publish(
uint16_t packet_id,
std::string_view topic_name,
std::string_view payload,
qos_e qos, retain_e retain, dup_e dup,
const publish_props& props
) {
std::optional<uint16_t> used_packet_id;
if (qos != qos_e::at_most_once) used_packet_id.emplace(packet_id);
auto packet_type_ =
basic::flag<4>(0b0011) |
basic::flag<1>(dup) |
basic::flag<2>(qos) |
basic::flag<1>(retain);
auto var_header_ =
basic::utf8_(topic_name) &
basic::int16_(used_packet_id) &
prop::props_(props);
auto message_body_ = var_header_ & basic::verbatim_(payload);
auto fixed_header_ =
packet_type_ &
basic::varlen_(message_body_.byte_size());
auto publish_message_ = fixed_header_ & message_body_;
return encode(publish_message_);
}
inline std::string encode_puback(
uint16_t packet_id,
uint8_t reason_code,
const puback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0100) |
basic::flag<4>(0);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto puback_message_ = fixed_header_ & var_header_;
return encode(puback_message_);
}
inline std::string encode_pubrec(
uint16_t packet_id,
uint8_t reason_code,
const pubrec_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0101) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubrec_message_ = fixed_header_ & var_header_;
return encode(pubrec_message_);
}
inline std::string encode_pubrel(
uint16_t packet_id,
uint8_t reason_code,
const pubrel_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0110) |
basic::flag<4>(0b0010);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubrel_message_ = fixed_header_ & var_header_;
return encode(pubrel_message_);
}
inline std::string encode_pubcomp(
uint16_t packet_id,
uint8_t reason_code,
const pubcomp_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0111) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubcomp_message_ = fixed_header_ & var_header_;
return encode(pubcomp_message_);
}
inline std::string encode_subscribe(
uint16_t packet_id,
const std::vector<subscribe_topic>& topics,
const subscribe_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1000) |
basic::flag<4>(0b0010);
size_t payload_size = 0;
for (const auto& [topic_filter, _]: topics)
payload_size += basic::utf8_(topic_filter).byte_size() + 1;
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + payload_size) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + payload_size);
for (const auto& [topic_filter, sub_opts]: topics) {
auto opts_ =
basic::flag<2>(sub_opts.retain_handling) |
basic::flag<1>(sub_opts.retain_as_published) |
basic::flag<1>(sub_opts.no_local) |
basic::flag<2>(sub_opts.max_qos);
auto filter_ = basic::utf8_(topic_filter) & opts_;
s << filter_;
}
return s;
}
inline std::string encode_suback(
uint16_t packet_id,
const std::vector<uint8_t>& reason_codes,
const suback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1001) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + reason_codes.size());
for (auto reason_code: reason_codes)
s << basic::byte_(reason_code);
return s;
}
inline std::string encode_unsubscribe(
uint16_t packet_id,
const std::vector<std::string>& topics,
const unsubscribe_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1010) |
basic::flag<4>(0b0010);
size_t payload_size = 0;
for (const auto& topic: topics)
payload_size += basic::utf8_(topic).byte_size();
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + payload_size) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + payload_size);
for (const auto& topic: topics)
s << basic::utf8_(topic);
return s;
}
inline std::string encode_unsuback(
uint16_t packet_id,
const std::vector<uint8_t>& reason_codes,
const unsuback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1011) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + reason_codes.size());
for (auto reason_code: reason_codes)
s << basic::byte_(reason_code);
return s;
}
inline std::string encode_pingreq() {
auto packet_type_ =
basic::flag<4>(0b1100) |
basic::flag<4>(0);
auto remaining_len_ =
basic::byte_(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

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/detail/async_traits.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/detail/log_invoke.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <cstdint>
#include <memory>
#include <string>
namespace boost::mqtt5::detail {
template <typename Stream, typename LoggerType>
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<LoggerType>& _log;
using handler_type = asio::any_completion_handler<void (error_code)>;
handler_type _handler;
std::unique_ptr<std::string> _buffer_ptr;
asio::cancellation_state _cancellation_state;
using endpoint = asio::ip::tcp::endpoint;
public:
template <typename Handler>
connect_op(
Stream& stream, mqtt_ctx& ctx,
log_invoke<LoggerType>& log,
Handler&& handler
) :
_stream(stream), _ctx(ctx), _log(log),
_handler(std::forward<Handler>(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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
using executor_type = asio::associated_executor_t<handler_type>;
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>) {
_stream.async_handshake(
tls_handshake_type<Stream>::client,
asio::append(
asio::prepend(std::move(*this), on_tls_handshake {}),
std::move(ep), std::move(ap)
)
);
}
else if constexpr (
has_tls_handshake<next_layer_type<Stream>>
) {
_stream.next_layer().async_handshake(
tls_handshake_type<next_layer_type<Stream>>::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<Stream>)
// If you get a compilation error here,
// it might be because of a missing <boost/mqtt5/websocket.hpp> include
ws_handshake_traits<Stream>::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<Stream>)
_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<allocator_type>::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<std::string>(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<uint32_t>(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_codes::category::connack>(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<uint32_t>(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_codes::category::auth>(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<allocator_type>::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

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/cancellable_handler.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/detail/topic_validation.hpp>
#include <boost/mqtt5/detail/utf8_mqtt.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/steady_timer.hpp>
#include <cstdint>
#include <memory>
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<client_service> _svc_ptr;
DisconnectContext _context;
using handler_type = cancellable_handler<
asio::any_completion_handler<void (error_code)>,
typename ClientService::executor_type
>;
handler_type _handler;
public:
template <typename Handler>
disconnect_op(
std::shared_ptr<client_service> 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<handler_type>;
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<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_disconnect,
static_cast<uint8_t>(_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<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_disconnect,
static_cast<uint8_t>(_context.reason_code), disconnect_props {}
));
send_disconnect(std::move(disconnect));
}
void send_disconnect(control_packet<allocator_type> 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<allocator_type> 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 <typename ClientService, typename Handler>
class terminal_disconnect_op {
using client_service = ClientService;
static constexpr uint8_t seconds = 5;
std::shared_ptr<client_service> _svc_ptr;
std::unique_ptr<asio::steady_timer> _timer;
using handler_type = Handler;
handler_type _handler;
public:
terminal_disconnect_op(
std::shared_ptr<client_service> 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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type = asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename DisconnectContext>
void perform(DisconnectContext&& context) {
namespace asioex = boost::asio::experimental;
auto init_disconnect = [](
auto handler, disconnect_ctx ctx,
std::shared_ptr<ClientService> 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<const asio::deferred_t, void (error_code)>(
init_disconnect, asio::deferred,
std::forward<DisconnectContext>(context), _svc_ptr
),
_timer->async_wait(asio::deferred)
);
timed_disconnect.async_wait(
asioex::wait_for_one(), std::move(*this)
);
}
void operator()(
std::array<std::size_t, 2> /* ord */,
error_code disconnect_ec, error_code /* timer_ec */
) {
std::move(_handler)(disconnect_ec);
}
};
template <typename ClientService, bool terminal>
class initiate_async_disconnect {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_disconnect(std::shared_ptr<ClientService> 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 <typename Handler>
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 <typename ClientService, typename CompletionToken>
decltype(auto) async_disconnect(
disconnect_rc_e reason_code, const disconnect_props& props,
std::shared_ptr<ClientService> svc_ptr,
CompletionToken&& token
) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
initiate_async_disconnect<ClientService, false>(std::move(svc_ptr)), token,
reason_code, props
);
}
template <typename ClientService, typename CompletionToken>
decltype(auto) async_terminal_disconnect(
disconnect_rc_e reason_code, const disconnect_props& props,
std::shared_ptr<ClientService> svc_ptr,
CompletionToken&& token
) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
initiate_async_disconnect<ClientService, true>(std::move(svc_ptr)), token,
reason_code, props
);
}
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_DISCONNECT_HPP

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/log_invoke.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/spirit/home/x3.hpp>
#include <array>
#include <chrono>
#include <string>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
using epoints = asio::ip::tcp::resolver::results_type;
template <typename Owner, typename Handler>
class resolve_op {
struct on_resolve {};
Owner& _owner;
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
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<int>(_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<std::size_t, 2> 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 <typename LoggerType>
class endpoints {
using logger_type = LoggerType;
asio::ip::tcp::resolver _resolver;
asio::steady_timer& _connect_timer;
std::vector<authority_path> _servers;
int _current_host { -1 };
log_invoke<logger_type>& _log;
template <typename Owner, typename Handler>
friend class resolve_op;
template <typename T>
static constexpr auto to_(T& arg) {
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
}
template <typename T, typename Parser>
static constexpr auto as_(Parser&& p){
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
}
public:
template <typename Executor>
endpoints(
Executor ex, asio::steady_timer& timer,
log_invoke<logger_type>& 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 <typename CompletionToken>
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<CompletionToken, Signature>(
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_<std::string>(+unreserved_)[to_(host)];
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
auto path_ = as_<std::string>(x3::char_('/') >> *unreserved_)[to_(path)];
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
for (auto b = hosts.begin(); b != hosts.end(); ) {
host.clear(); port.clear(); path.clear();
if (phrase_parse(b, hosts.end(), uri_, x3::eps(false))) {
_servers.push_back({
std::move(host),
port.empty()
? std::to_string(default_port)
: std::move(port),
std::move(path)
});
}
else b = hosts.end();
}
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_ENDPOINTS_HPP

View File

@ -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 <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <chrono>
#include <limits>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class ping_op {
using client_service = ClientService;
using handler_type = Handler;
struct on_timer {};
struct on_pingreq {};
std::shared_ptr<client_service> _svc_ptr;
handler_type _handler;
public:
ping_op(std::shared_ptr<client_service> 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<handler_type>;
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<allocator_type>::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<duration::rep>::max());
}
void complete() {
return std::move(_handler)();
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_PING_OP_HPP

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <cstdint>
#include <memory>
#include <string>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
class publish_rec_op {
using client_service = ClientService;
struct on_puback {};
struct on_pubrec {};
struct on_pubrel {};
struct on_pubcomp {};
std::shared_ptr<client_service> _svc_ptr;
decoders::publish_message _message;
public:
explicit publish_rec_op(std::shared_ptr<client_service> 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<void>;
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<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_puback, *packet_id,
uint8_t(0), puback_props {}
);
return send_puback(std::move(puback));
}
// qos == qos_e::exactly_once
auto pubrec = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubrec, *packet_id,
uint8_t(0), pubrec_props {}
);
return send_pubrec(std::move(pubrec));
}
void send_puback(control_packet<allocator_type> puback) {
auto wire_data = puback.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::consign(
asio::prepend(std::move(*this), on_puback {}),
std::move(puback)
)
);
}
void operator()(on_puback, error_code ec) {
if (ec)
return;
complete();
}
void send_pubrec(control_packet<allocator_type> pubrec) {
auto wire_data = pubrec.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::prepend(std::move(*this), on_pubrec {}, std::move(pubrec))
);
}
void operator()(
on_pubrec, control_packet<allocator_type> packet,
error_code ec
) {
if (ec)
return;
wait_pubrel(packet.packet_id());
}
void wait_pubrel(uint16_t packet_id) {
_svc_ptr->async_wait_reply(
control_code_e::pubrel, packet_id,
asio::prepend(std::move(*this), on_pubrel {}, packet_id)
);
}
void operator()(
on_pubrel, uint16_t packet_id,
error_code ec, byte_citer first, byte_citer last
) {
if (ec == asio::error::try_again) // "resend unanswered"
return wait_pubrel(packet_id);
if (ec)
return;
auto pubrel = decoders::decode_pubrel(static_cast<uint32_t>(std::distance(first, last)), first);
if (!pubrel.has_value()) {
on_malformed_packet("Malformed PUBREL received: cannot decode");
return wait_pubrel(packet_id);
}
auto& [reason_code, props] = *pubrel;
auto rc = to_reason_code<reason_codes::category::pubrel>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBREL received: invalid Reason Code");
return wait_pubrel(packet_id);
}
auto pubcomp = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubcomp, packet_id,
uint8_t(0), pubcomp_props{}
);
send_pubcomp(std::move(pubcomp));
}
void send_pubcomp(control_packet<allocator_type> pubcomp) {
auto wire_data = pubcomp.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubcomp))
);
}
void operator()(
on_pubcomp, control_packet<allocator_type> packet,
error_code ec
) {
if (ec == asio::error::try_again)
return wait_pubrel(packet.packet_id());
if (ec)
return;
complete();
}
private:
void on_malformed_packet(const std::string& reason) {
auto props = disconnect_props {};
props[prop::reason_string] = reason;
return async_disconnect(
disconnect_rc_e::malformed_packet, props,
_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

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/cancellable_handler.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/detail/topic_validation.hpp>
#include <boost/mqtt5/detail/utf8_mqtt.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <qos_e qos_type>
using on_publish_signature = std::conditional_t<
qos_type == qos_e::at_most_once,
void (error_code),
std::conditional_t<
qos_type == qos_e::at_least_once,
void (error_code, reason_code, puback_props),
void (error_code, reason_code, pubcomp_props)
>
>;
template <qos_e qos_type>
using on_publish_props_type = std::conditional_t<
qos_type == qos_e::at_most_once,
void,
std::conditional_t<
qos_type == qos_e::at_least_once,
puback_props,
pubcomp_props
>
>;
template <typename ClientService, typename Handler, qos_e qos_type>
class publish_send_op {
using client_service = ClientService;
struct on_publish {};
struct on_puback {};
struct on_pubrec {};
struct on_pubrel {};
struct on_pubcomp {};
std::shared_ptr<client_service> _svc_ptr;
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<client_service> 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<handler_type>;
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<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_publish, packet_id,
std::move(topic), std::move(payload),
qos_type, retain, dup_e::no, props
);
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<allocator_type> 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<allocator_type> 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<allocator_type> 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<q == qos_e::at_least_once, bool> = true
>
void operator()(
on_puback, control_packet<allocator_type> 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<uint32_t>(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_codes::category::puback>(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<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubrec, control_packet<allocator_type> 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<uint32_t>(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_codes::category::pubrec>(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<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubrel, packet_id,
0, pubrel_props {}
);
send_pubrel(std::move(pubrel), false);
}
void send_pubrel(control_packet<allocator_type> pubrel, bool throttled) {
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<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubrel, control_packet<allocator_type> 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<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubcomp, control_packet<allocator_type> 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<uint32_t>(std::distance(first, last)), first
);
if (!pubcomp.has_value()) {
on_malformed_packet("Malformed PUBCOMP: cannot decode");
return send_pubrel(std::move(pubrel), true);
}
auto& [reason_code, props] = *pubcomp;
auto rc = to_reason_code<reason_codes::category::pubcomp>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBCOMP: invalid Reason Code");
return send_pubrel(std::move(pubrel), true);
}
return complete(ec, 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<q == qos_e::at_most_once, bool> = true
>
void complete(error_code ec, uint16_t = 0) {
_handler.complete(ec);
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::at_most_once, bool> = true
>
void complete_immediate(error_code ec, uint16_t) {
_handler.complete_immediate(ec);
}
template <
typename Props = on_publish_props_type<qos_type>,
std::enable_if_t<
std::is_same_v<Props, puback_props> ||
std::is_same_v<Props, pubcomp_props>,
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>(props));
}
template <
typename Props = on_publish_props_type<qos_type>,
std::enable_if_t<
std::is_same_v<Props, puback_props> ||
std::is_same_v<Props, pubcomp_props>,
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 <typename ClientService, qos_e qos_type>
class initiate_async_publish {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_publish(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(
Handler&& handler,
std::string topic, std::string payload,
retain_e retain, const publish_props& props
) {
detail::publish_send_op<ClientService, Handler, qos_type> {
_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

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/any_authenticator.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <memory>
#include <string>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
class re_auth_op {
using client_service = ClientService;
struct on_auth_data {};
std::shared_ptr<client_service> _svc_ptr;
any_authenticator& _auth;
public:
explicit re_auth_op(std::shared_ptr<client_service> 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<void>;
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<reason_codes::category::auth>(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<allocator_type>::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

View File

@ -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 <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/mqtt5/impl/publish_rec_op.hpp>
#include <boost/mqtt5/impl/re_auth_op.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <cstdint>
#include <memory>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class read_message_op {
using client_service = ClientService;
using handler_type = Handler;
struct on_message {};
struct on_disconnect {};
std::shared_ptr<client_service> _svc_ptr;
handler_type _handler;
public:
read_message_op(std::shared_ptr<client_service> 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<handler_type>;
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<uint32_t>(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<uint32_t>(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<reason_codes::category::disconnect>(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<uint32_t>(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

View File

@ -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 <boost/mqtt5/detail/internal_types.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <array>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
namespace asioex = boost::asio::experimental;
template <typename Owner, typename Handler>
class read_op {
struct on_read {};
struct on_reconnect {};
Owner& _owner;
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename BufferType>
void perform(
const BufferType& buffer, duration wait_for
) {
auto stream_ptr = _owner._stream_ptr;
if (_owner.was_connected()) {
_owner._read_timer.expires_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<size_t, 2> { 0, 1 },
asio::error::not_connected, 0, error_code {}
)
);
}
void operator()(
on_read, typename Owner::stream_ptr stream_ptr,
std::array<std::size_t, 2> ord, error_code read_ec, size_t bytes_read,
error_code
) {
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

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/async_traits.hpp>
#include <boost/mqtt5/impl/connect_op.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_smallint.hpp>
#include <array>
#include <chrono>
#include <memory>
#include <string>
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 <typename Owner>
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<void (error_code)>;
handler_type _handler;
std::unique_ptr<std::string> _buffer_ptr;
exponential_backoff _generator;
using endpoint = asio::ip::tcp::endpoint;
using epoints = asio::ip::tcp::resolver::results_type;
public:
template <typename Handler>
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
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<typename Owner::stream_context_type>)
setup_tls_sni(
ap, _owner._stream_context.tls_context(), *sptr
);
// wait max 5 seconds for the connect (handshake) op to finish
_owner._connect_timer.expires_after(std::chrono::seconds(5));
auto init_connect = [](
auto handler, typename Owner::stream_type& stream,
mqtt_ctx& context, log_invoke<typename Owner::logger_type>& 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<const asio::deferred_t, void(error_code)>(
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<std::size_t, 2> 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

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
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<Signature> _handler;
control_code_e _code;
uint16_t _packet_id;
std::chrono::time_point<std::chrono::system_clock> _ts;
public:
template <typename H>
reply_handler(control_code_e code, uint16_t pid, H&& handler) :
_handler(std::forward<H>(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<reply_handler>;
handlers _handlers;
struct fast_reply {
control_code_e code;
uint16_t packet_id;
std::unique_ptr<std::string> packet;
};
using fast_replies = std::vector<fast_reply>;
fast_replies _fast_replies;
public:
template <typename Executor>
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 <typename CompletionToken>
decltype(auto) async_wait_reply(
control_code_e code, uint16_t packet_id, CompletionToken&& token
) {
auto dup_handler_ptr = find_handler(code, packet_id);
if (dup_handler_ptr != _handlers.end()) {
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<CompletionToken, Signature>(
initiation, token, std::ref(*this), code, packet_id
);
}
auto fdata = std::move(*freply);
_fast_replies.erase(freply);
auto initiation = [](
auto handler, std::unique_ptr<std::string> 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<CompletionToken, Signature>(
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<std::string>(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

View File

@ -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 <boost/mqtt5/detail/cancellable_handler.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/impl/ping_op.hpp>
#include <boost/mqtt5/impl/read_message_op.hpp>
#include <boost/mqtt5/impl/sentry_op.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <memory>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class run_op {
using client_service = ClientService;
std::shared_ptr<client_service> _svc_ptr;
using handler_type = cancellable_handler<
Handler,
typename client_service::executor_type
>;
handler_type _handler;
public:
run_op(
std::shared_ptr<client_service> 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<handler_type>;
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<client_service> svc_ptr
) {
return read_message_op { std::move(svc_ptr), std::move(handler) }
.perform();
};
auto init_ping_op = [](
auto handler, std::shared_ptr<client_service> svc_ptr
) {
return ping_op { std::move(svc_ptr), std::move(handler) }
.perform();
};
auto init_senty_op = [](
auto handler, std::shared_ptr<client_service> svc_ptr
) {
return sentry_op { std::move(svc_ptr), std::move(handler) }
.perform();
};
asioex::make_parallel_group(
asio::async_initiate<const asio::deferred_t, void ()>(
init_read_message_op, asio::deferred, _svc_ptr
),
asio::async_initiate<const asio::deferred_t, void ()>(
init_ping_op, asio::deferred, _svc_ptr
),
asio::async_initiate<const asio::deferred_t, void ()>(
init_senty_op, asio::deferred, _svc_ptr
)
).async_wait(asioex::wait_for_all(), std::move(*this));
}
void operator()(std::array<std::size_t, 3> /* ord */) {
_handler.complete(asio::error::operation_aborted);
}
};
template <typename ClientService>
class initiate_async_run {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_run(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(Handler&& handler) {
run_op<ClientService, Handler> {
_svc_ptr, std::move(handler)
}.perform();
}
};
} // end namespace boost::mqtt5::detail
#endif // !BOOST_MQTT5_RUN_OP_HPP

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/asio/prepend.hpp>
#include <chrono>
#include <memory>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
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<client_service> _svc_ptr;
handler_type _handler;
public:
sentry_op(std::shared_ptr<client_service> 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<handler_type>;
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

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/cancellable_handler.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/detail/topic_validation.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class subscribe_op {
using client_service = ClientService;
struct on_subscribe {};
struct on_suback {};
std::shared_ptr<client_service> _svc_ptr;
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<client_service> 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<handler_type>;
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<subscribe_topic>& 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<allocator_type>::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<allocator_type> 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<allocator_type> 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<allocator_type> 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<allocator_type> 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<uint32_t>(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<subscribe_topic>& 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<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
std::vector<reason_code> ret;
for (uint8_t code : codes) {
auto rc = to_reason_code<reason_codes::category::suback>(code);
if (rc)
ret.push_back(*rc);
}
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<reason_code>(_num_topics, reason_codes::empty),
suback_props {}
);
}
void complete(
error_code ec, uint16_t packet_id,
std::vector<reason_code> reason_codes = {}, suback_props props = {}
) {
if (reason_codes.empty() && _num_topics)
reason_codes = std::vector<reason_code>(_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 <typename ClientService>
class initiate_async_subscribe {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_subscribe(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(
Handler&& handler,
const std::vector<subscribe_topic>& 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

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/cancellable_handler.hpp>
#include <boost/mqtt5/detail/control_packet.hpp>
#include <boost/mqtt5/detail/internal_types.hpp>
#include <boost/mqtt5/detail/topic_validation.hpp>
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
#include <boost/mqtt5/impl/disconnect_op.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace boost::mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Handler>
class unsubscribe_op {
using client_service = ClientService;
struct on_unsubscribe {};
struct on_unsuback {};
std::shared_ptr<client_service> _svc_ptr;
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<client_service> 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<handler_type>;
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<std::string>& 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<allocator_type>::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<allocator_type> 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<allocator_type> 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<allocator_type> 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<allocator_type> 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<uint32_t>(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<std::string>& 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<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
std::vector<reason_code> ret;
for (uint8_t code : codes) {
auto rc = to_reason_code<reason_codes::category::unsuback>(code);
if (rc)
ret.push_back(*rc);
}
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<reason_code>(_num_topics, reason_codes::empty),
unsuback_props {}
);
}
void complete(
error_code ec, uint16_t packet_id,
std::vector<reason_code> reason_codes = {}, unsuback_props props = {}
) {
if (reason_codes.empty() && _num_topics)
reason_codes = std::vector<reason_code>(_num_topics, reason_codes::empty);
_svc_ptr->free_pid(packet_id);
_handler.complete(ec, std::move(reason_codes), std::move(props));
}
};
template <typename ClientService>
class initiate_async_unsubscribe {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_unsubscribe(std::shared_ptr<ClientService> 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 <typename Handler>
void operator()(
Handler&& handler,
const std::vector<std::string>& 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

View File

@ -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 <boost/mqtt5/detail/async_traits.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/write.hpp>
#include <boost/system/error_code.hpp>
namespace boost::mqtt5::detail {
template <typename Owner, typename Handler>
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<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename BufferType>
void perform(BufferType& buffer) {
auto stream_ptr = _owner._stream_ptr;
if (_owner.was_connected())
// note: write operation should not be time-limited
detail::async_write(
*stream_ptr, buffer,
asio::prepend(std::move(*this), on_write {}, stream_ptr)
);
else
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

View File

@ -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 <boost/mqtt5/logger_traits.hpp>
#include <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/traits.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <cstdint>
#include <iostream>
#include <string_view>
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 <typename Props>
void output_props(const Props& props) {
props.visit(
[](const auto& prop, const auto& val) -> bool {
if constexpr (detail::is_optional<decltype(val)>) {
if (val.has_value()) {
std::clog << property_name(prop) << ":";
using value_type = boost::remove_cv_ref_t<decltype(*val)>;
if constexpr (std::is_same_v<value_type, uint8_t>)
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<decltype(val[i])>)
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 <prop::property_type p>
static std::string_view property_name(std::integral_constant<prop::property_type, p>) {
return prop::name_v<p>;
}
};
// Verify that the logger class satisfies the LoggerType concept
static_assert(has_at_resolve<logger>);
static_assert(has_at_tcp_connect<logger>);
static_assert(has_at_tls_handshake<logger>);
static_assert(has_at_ws_handshake<logger>);
static_assert(has_at_connack<logger>);
static_assert(has_at_disconnect<logger>);
} // end namespace boost::mqtt5
#endif // !BOOST_MQTT5_LOGGER_HPP

View File

@ -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 <iostream>
#include <string_view>
#include <type_traits>
#include <boost/mqtt5/property_types.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
#include <iostream>
#include <string_view>
#include <type_traits>
namespace async_mqtt5 {
namespace boost::mqtt5 {
namespace asio = boost::asio;
using boost::system::error_code;
@ -32,11 +32,11 @@ class noop_logger {};
template <typename T>
using at_resolve_sig = decltype(
std::declval<T&>().at_resolve(
std::declval<error_code>(),
std::declval<std::string_view>(), std::declval<std::string_view>(),
std::declval<const asio::ip::tcp::resolver::results_type&>()
)
std::declval<T&>().at_resolve(
std::declval<error_code>(),
std::declval<std::string_view>(), std::declval<std::string_view>(),
std::declval<const asio::ip::tcp::resolver::results_type&>()
)
);
template <typename T>
constexpr bool has_at_resolve = boost::is_detected<at_resolve_sig, T>::value;
@ -45,9 +45,9 @@ constexpr bool has_at_resolve = boost::is_detected<at_resolve_sig, T>::value;
template <typename T>
using at_tcp_connect_sig = decltype(
std::declval<T&>().at_tcp_connect(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
std::declval<T&>().at_tcp_connect(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_tcp_connect = boost::is_detected<at_tcp_connect_sig, T>::value;
@ -56,9 +56,9 @@ constexpr bool has_at_tcp_connect = boost::is_detected<at_tcp_connect_sig, T>::v
template <typename T>
using at_tls_handshake_sig = decltype(
std::declval<T&>().at_tls_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
std::declval<T&>().at_tls_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_tls_handshake = boost::is_detected<at_tls_handshake_sig, T>::value;
@ -67,9 +67,9 @@ constexpr bool has_at_tls_handshake = boost::is_detected<at_tls_handshake_sig, T
template <typename T>
using at_ws_handshake_sig = decltype(
std::declval<T&>().at_ws_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
std::declval<T&>().at_ws_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_ws_handshake = boost::is_detected<at_ws_handshake_sig, T>::value;
@ -78,10 +78,10 @@ constexpr bool has_at_ws_handshake = boost::is_detected<at_ws_handshake_sig, T>:
template <typename T>
using at_connack_sig = decltype(
std::declval<T&>().at_connack(
std::declval<reason_code>(),
std::declval<bool>(), std::declval<const connack_props&>()
)
std::declval<T&>().at_connack(
std::declval<reason_code>(),
std::declval<bool>(), std::declval<const connack_props&>()
)
);
template <typename T>
constexpr bool has_at_connack = boost::is_detected<at_connack_sig, T>::value;
@ -90,14 +90,14 @@ constexpr bool has_at_connack = boost::is_detected<at_connack_sig, T>::value;
template <typename T>
using at_disconnect_sig = decltype(
std::declval<T&>().at_disconnect(
std::declval<reason_code>(), std::declval<const disconnect_props&>()
)
std::declval<T&>().at_disconnect(
std::declval<reason_code>(), std::declval<const disconnect_props&>()
)
);
template <typename T>
constexpr bool has_at_disconnect = boost::is_detected<at_disconnect_sig, T>::value;
} // end namespace async_mqtt5
} // end namespace boost::mqtt5
#endif // !ASYNC_MQTT5_LOGGER_TRAITS_HPP
#endif // !BOOST_MQTT5_LOGGER_TRAITS_HPP

View File

@ -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 <boost/mqtt5/error.hpp>
#include <boost/mqtt5/logger_traits.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/detail/log_invoke.hpp>
#include <boost/mqtt5/detail/rebind_executor.hpp>
#include <boost/mqtt5/impl/client_service.hpp>
#include <boost/mqtt5/impl/publish_send_op.hpp>
#include <boost/mqtt5/impl/re_auth_op.hpp>
#include <boost/mqtt5/impl/run_op.hpp>
#include <boost/mqtt5/impl/subscribe_op.hpp>
#include <boost/mqtt5/impl/unsubscribe_op.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/system/error_code.hpp>
#include <memory>
#include <string>
#include <type_traits>
#include <variant> // std::monostate
#include <vector>
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 <typename Executor>
struct rebind_executor {
/// The client type when rebound to the specified executor.
using other = mqtt_client<
typename detail::rebind_executor<StreamType, 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<client_service_type>;
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<client_service_type>(
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<ExecutionContext&, asio::execution_context&>
* \endcode
*/
template <
typename ExecutionContext,
std::enable_if_t<
std::is_convertible_v<ExecutionContext&, asio::execution_context&>,
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<TlsContext, std::monostate>
* \endcode
*/
template <
typename Ctx = TlsContext,
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, 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<executor_type>::type
>
decltype(auto) async_run(CompletionToken&& token = {}) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
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<detail::is_authenticator<Authenticator>, bool> = true
>
mqtt_client& authenticator(Authenticator&& authenticator) {
_impl->authenticator(std::forward<Authenticator>(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 <prop::property_type p>
mqtt_client& connect_property(
std::integral_constant<prop::property_type, p> prop,
prop::value_type_t<p> 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<std::pair<std::string, std::string>>`.
*
* \param prop The \__CONNACK_PROPS\__ property value to retrieve.
*
* \par Example
* \code
* std::optional<std::string> auth_method = client.connack_property(boost::mqtt5::prop::authentication_method); // ok
* std::optional<std::string> 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 <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> 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 <qos_e qos_type,
typename CompletionToken =
typename asio::default_completion_token<executor_type>::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<qos_type>;
return asio::async_initiate<CompletionToken, Signature>(
detail::initiate_async_publish<client_service_type, qos_type>(_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<executor_type>::type
>
decltype(auto) async_subscribe(
const std::vector<subscribe_topic>& topics,
const subscribe_props& props,
CompletionToken&& token = {}
) {
using Signature = void (
error_code, std::vector<reason_code>, suback_props
);
return asio::async_initiate<CompletionToken, Signature>(
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<executor_type>::type
>
decltype(auto) async_subscribe(
const subscribe_topic& topic, const subscribe_props& props,
CompletionToken&& token = {}
) {
return async_subscribe(
std::vector<subscribe_topic> { topic }, props,
std::forward<CompletionToken>(token)
);
}
/**
* \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<executor_type>::type
>
decltype(auto) async_unsubscribe(
const std::vector<std::string>& topics, const unsubscribe_props& props,
CompletionToken&& token = {}
) {
using Signature = void (
error_code, std::vector<reason_code>, unsuback_props
);
return asio::async_initiate<CompletionToken, Signature>(
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<executor_type>::type
>
decltype(auto) async_unsubscribe(
const std::string& topic, const unsubscribe_props& props,
CompletionToken&& token = {}
) {
return async_unsubscribe(
std::vector<std::string> { topic }, props,
std::forward<CompletionToken>(token)
);
}
/**
* \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<executor_type>::type
>
decltype(auto) async_receive(CompletionToken&& token = {}) {
return _impl->async_channel_receive(std::forward<CompletionToken>(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<executor_type>::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<uint8_t>(reason_code)),
props, impl, std::forward<CompletionToken>(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<executor_type>::type
>
decltype(auto) async_disconnect(CompletionToken&& token = {}) {
return async_disconnect(
disconnect_rc_e::normal_disconnection,
disconnect_props {}, std::forward<CompletionToken>(token)
);
}
};
} // end namespace boost::mqtt5
#endif // !BOOST_MQTT5_MQTT_CLIENT_HPP

View File

@ -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 <boost/container/small_vector.hpp>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>
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<int32_t, 1>
{
using base_type = boost::container::small_vector<int32_t, 1>;
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 <property_type p>
struct property_traits;
using user_property_value_t = std::vector<std::pair<std::string, std::string>>;
#define DEF_PROPERTY_TRAIT(Pname, Ptype) \
template <> \
struct property_traits<Pname##_t> { \
static constexpr std::string_view name = #Pname; \
using type = Ptype; \
}; \
constexpr std::integral_constant<property_type, Pname##_t> Pname {};
DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(content_type, std::optional<std::string>);
DEF_PROPERTY_TRAIT(response_topic, std::optional<std::string>);
DEF_PROPERTY_TRAIT(correlation_data, std::optional<std::string>);
DEF_PROPERTY_TRAIT(subscription_identifier, subscription_identifiers);
DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional<std::string>);
DEF_PROPERTY_TRAIT(server_keep_alive, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(authentication_method, std::optional<std::string>);
DEF_PROPERTY_TRAIT(authentication_data, std::optional<std::string>);
DEF_PROPERTY_TRAIT(request_problem_information, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(will_delay_interval, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(request_response_information, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(response_information, std::optional<std::string>);
DEF_PROPERTY_TRAIT(server_reference, std::optional<std::string>);
DEF_PROPERTY_TRAIT(reason_string, std::optional<std::string>);
DEF_PROPERTY_TRAIT(receive_maximum, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(topic_alias, std::optional<uint16_t>);
DEF_PROPERTY_TRAIT(maximum_qos, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(retain_available, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(user_property, user_property_value_t);
DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional<uint32_t>);
DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional<uint8_t>);
DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional<uint8_t>);
#undef DEF_PROPERTY_TRAIT
template <property_type p>
using value_type_t = typename property_traits<p>::type;
template <property_type p>
constexpr std::string_view name_v = property_traits<p>::name;
template <property_type ...Ps>
class properties {
template <property_type p>
struct property {
using key = std::integral_constant<property_type, p>;
constexpr static std::string_view name = name_v<p>;
value_type_t<p> value;
};
std::tuple<property<Ps>...> _props;
public:
template <property_type v>
constexpr auto& operator[](std::integral_constant<property_type, v>)
noexcept {
return std::get<property<v>>(_props).value;
}
template <property_type v>
constexpr const auto& operator[](std::integral_constant<property_type, v>)
const noexcept {
return std::get<property<v>>(_props).value;
}
template <typename Func>
using is_apply_on = std::conjunction<
std::is_invocable<Func, value_type_t<Ps>&>...
>;
template <typename Func>
using is_nothrow_apply_on = std::conjunction<
std::is_nothrow_invocable<Func, value_type_t<Ps>&>...
>;
template <
typename Func,
std::enable_if_t<is_apply_on<Func>::value, bool> = true
>
constexpr bool apply_on(uint8_t property_id, Func&& func)
noexcept (is_nothrow_apply_on<Func>::value) {
return std::apply(
[&func, property_id](auto&... ptype) {
auto pc = [&func, property_id](auto& px) {
using ptype = std::remove_reference_t<decltype(px)>;
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 <typename Func>
using is_visitor = std::conjunction<
std::is_invocable_r<bool, Func, decltype(Ps), value_type_t<Ps>&>...
>;
template <typename Func>
using is_nothrow_visitor = std::conjunction<
std::is_nothrow_invocable<Func, decltype(Ps), value_type_t<Ps>&>...
>;
template <
typename Func,
std::enable_if_t<is_visitor<Func>::value, bool> = true
>
constexpr bool visit(Func&& func)
const noexcept (is_nothrow_visitor<Func>::value) {
return std::apply(
[&func](const auto&... props) {
auto pc = [&func](const auto& px) {
using ptype = std::remove_reference_t<decltype(px)>;
constexpr typename ptype::key prop;
return std::invoke(func, prop, px.value);
};
return (pc(props) &&...);
},
_props
);
}
template <
typename Func,
std::enable_if_t<is_visitor<Func>::value, bool> = true
>
constexpr bool visit(Func&& func)
noexcept (is_nothrow_visitor<Func>::value) {
return std::apply(
[&func](auto&... props) {
auto pc = [&func](auto& px) {
using ptype = std::remove_reference_t<decltype(px)>;
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

View File

@ -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 <algorithm>
#include <cstdint>
#include <optional>
#include <ostream>
#include <type_traits>
#include <utility>
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<cat == category::connack, bool> = true
>
std::pair<reason_code*, size_t> 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<cat == category::auth, bool> = true
>
std::pair<reason_code*, size_t> 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<reason_code*, size_t> 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<reason_code*, size_t> 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<cat == category::suback, bool> = true
>
std::pair<reason_code*, size_t> 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<cat == category::unsuback, bool> = true
>
std::pair<reason_code*, size_t> 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<cat == category::disconnect, bool> = true
>
std::pair<reason_code*, size_t> 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 <reason_codes::category cat>
std::optional<reason_code> to_reason_code(uint8_t code) {
auto [ptr, len] = reason_codes::detail::valid_codes<cat>();
auto it = std::lower_bound(ptr, ptr + len, reason_code(code));
if (it->value() == code)
return *it;
return std::nullopt;
}
} // end namespace boost::mqtt5
#endif // !BOOST_MQTT5_REASON_CODES_HPP

View File

@ -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 <boost/mqtt5/property_types.hpp>
#include <boost/system/error_code.hpp>
#include <cstdint>
#include <string>
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

View File

@ -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 <boost/mqtt5/types.hpp>
#include <boost/beast/http/field.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/stream.hpp>
namespace boost::mqtt5 {
// Trait definition for Beast
template <typename Stream>
struct ws_handshake_traits<boost::beast::websocket::stream<Stream>> {
template <typename CompletionToken>
static decltype(auto) async_handshake(
boost::beast::websocket::stream<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<CompletionToken>(token)
);
}
};
} // end namespace boost::mqtt5
#endif // !BOOST_MQTT5_WEBSOCKET_HPP

Some files were not shown because too many files have changed in this diff Show More