From c8036c0d4695dedfd5342ddb7de3bd01dc0d3460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korina=20=C5=A0imi=C4=8Devi=C4=87?= Date: Fri, 3 Nov 2023 13:40:03 +0100 Subject: [PATCH] [mqtt-client] added chapter on different ways to establish network connection Summary: related to T12804 - added chapter on network connection - added code examples - remove any_authenticator from doc targets - manually create table of contents Reviewers: ivica Reviewed By: ivica Subscribers: miljen, iljazovic Differential Revision: https://repo.mireo.local/D26401 --- doc/Jamfile | 5 +- doc/qbk/00_main.qbk | 27 +++- doc/qbk/02_examples.qbk | 41 ++---- doc/qbk/examples/Asio_compatibility.qbk | 40 ++++++ doc/qbk/examples/Network_connection.qbk | 43 ++++++ doc/qbk/reference/concepts/StreamType.qbk | 7 +- doc/reference.dox | 1 - example/cpp20_coroutines.cpp | 4 +- example/futures.cpp | 2 +- example/network_connection.cpp | 164 ++++++++++++++++++++++ include/async_mqtt5/mqtt_client.hpp | 15 +- 11 files changed, 301 insertions(+), 48 deletions(-) create mode 100644 doc/qbk/examples/Asio_compatibility.qbk create mode 100644 doc/qbk/examples/Network_connection.qbk create mode 100644 example/network_connection.cpp diff --git a/doc/Jamfile b/doc/Jamfile index 1337b4a..ec0b936 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -34,7 +34,6 @@ make xml/index.xml ../include/async_mqtt5/error.hpp ../include/async_mqtt5/types.hpp ../include/async_mqtt5/mqtt_client.hpp - ../include/async_mqtt5/detail/any_authenticator.hpp : @call-doxygen ; @@ -130,9 +129,7 @@ boostbook async_mqtt5 chapter.autolabel=1 chunk.section.depth=8 chunk.first.sections=1 - toc.section.depth=2 - toc.max.depth=1 - generate.toc="chapter toc,title section nop reference nop part toc" + generate.toc="" html.stylesheet=boostbook.css : stylesheets diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk index f1e1125..d730661 100644 --- a/doc/qbk/00_main.qbk +++ b/doc/qbk/00_main.qbk @@ -29,6 +29,8 @@ [template beastconceptslink[id term][@boost:/libs/beast/doc/html/beast/concepts/[id].html [term]]] [template mqttlink[id text][@https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc[id] [text]]] +[def __OPENSSL__ [@https://www.openssl.org/ OpenSSL]] + [def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers CompletionToken]] [def __ExecutionContext__ [reflink ExecutionContext]] [def __StreamType__ [reflink StreamType]] @@ -37,11 +39,17 @@ [def __Boost__ [@https://www.boost.org/ Boost]] [def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]] +[def __Beast__ [@boost:/libs/beast/index.html Boost.Beast]] [def __ASIO_PER_OP_CANCELLATION__ [@boost:/doc/html/boost_asio/overview/core/cancellation.html Per-Operation Cancellation]] [def __POST__ [@boost:doc/html/boost_asio/reference/post.html `boost::asio::post`]] [def __CO_SPAWN__ [@boost:/doc/html/boost_asio/reference/co_spawn.html `boost::asio::co_spawn`]] [def __USE_AWAITABLE__ [@boost:/doc/html/boost_asio/reference/use_awaitable.html `boost::asio::use_awaitable`]] [def __USE_FUTURE__ [@boost:/doc/html/boost_asio/reference/use_future.html `boost::asio::use_future`]] +[def __SSL__ [@boost:/doc/html/boost_asio/overview/ssl.html `SSL`]] +[def __TCP_SOCKET__ [asioreflink ip__tcp/socket ip::tcp::socket]] +[def __SSL_CONTEXT__ [asioreflink ssl__context ssl::context]] +[def __SSL_STREAM__ [asioreflink ssl__stream ssl::stream<__TCP_SOCKET__>]] +[def __WEBSOCKET_STREAM__ [beastreflink boost__beast__websocket__stream websocket::stream]] [def __Self__ [reflink2 mqtt_client `mqtt_client`]] @@ -95,11 +103,24 @@ [def __REASON_CODES__ [reflink2 Reason_codes `Reason Codes`]] [def __ERROR_HANDLING__ [reflink2 Error_handling `Error handling`]] -[def __EXAMPLE_CALLBACK__ [link async_mqtt5.async_examples.callbacks Async functions with callbacks]] -[def __EXAMPLE_COROUTINE__ [link async_mqtt5.async_examples.cpp20_coroutines Async functions with C++20 coroutines]] -[def __EXAMPLE_FUTURE__ [link async_mqtt5.async_examples.futures Async functions with futures]] +[def __EXAMPLE_CALLBACK__ [link async_mqtt5.examples.asio.callbacks Async functions with callbacks]] +[def __EXAMPLE_COROUTINE__ [link async_mqtt5.examples.asio.cpp20_coroutines Async functions with C++20 coroutines]] +[def __EXAMPLE_FUTURE__ [link async_mqtt5.examples.asio.futures Async functions with futures]] [include 01_intro.qbk] [include 02_examples.qbk] [include reference/reference.qbk] + +[h3 Table of Contents] +* [link async_mqtt5.intro Introduction] +* [link async_mqtt5.examples Examples] + * [link async_mqtt5.examples.network_connection Establishing a network connection with different protocols] + * [link async_mqtt5.examples.network_connection.tcp_ip_connection TCP/IP connection] + * [link async_mqtt5.examples.network_connection.tls_ssl_connection TLS/SSL connection] + * [link async_mqtt5.examples.network_connection.websocket_connection WebSocket connection] + * [link async_mqtt5.examples.asio Compatibility with Boost.Asio] + * [link async_mqtt5.examples.asio.callbacks Async functions with callbacks] + * [link async_mqtt5.examples.asio.cpp20_coroutines Async functions with C++20 coroutines] + * [link async_mqtt5.examples.asio.futures Async functions with futures] +* [link async_mqtt5.ref Reference] diff --git a/doc/qbk/02_examples.qbk b/doc/qbk/02_examples.qbk index 992fe41..14ad3e4 100644 --- a/doc/qbk/02_examples.qbk +++ b/doc/qbk/02_examples.qbk @@ -5,36 +5,23 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:async_examples Compatibility with Boost.Asio] -The __Self__ is built upon __Asio__ and thus follows the same principles. -This section illustrates the usage of __Self__ async -functions with different __CompletionToken__. +[section:examples Examples] +The following examples demonstrate how to use __Self__ in different scenarios. -# [link async_mqtt5.async_examples.callbacks Async functions with callbacks] -# [link async_mqtt5.async_examples.cpp20_coroutines Async functions with C++20 coroutines] -# [link async_mqtt5.async_examples.futures Async functions with futures] +The first section will show how to use different underlying transport protocols (such as TCP, SSL and WebSocket) +to establish a connection to a MQTT Broker. +* [link async_mqtt5.examples.network_connection Establishing a network connection with different protocols] -[section:callbacks Async functions with callbacks] -This example demonstrates how to use __Self__ asynchrous functions with callbacks. +The second section will showcase how to use asynchronous functions in __Self__ +with different __CompletionToken__. -[import ../../example/callbacks.cpp] -[callbacks_examples] -[endsect] - -[section:cpp20_coroutines Async functions with C++20 coroutines] -This example demonstrates how to use __Self__ asynchrous functions with C++20 coroutines -using __USE_AWAITABLE__ and __CO_SPAWN__. - -[import ../../example/cpp20_coroutines.cpp] -[cpp20_coroutines_examples] -[endsect] - -[section:futures Async functions with futures] -This example demonstrates how to use __Self__ asynchrous functions with __USE_FUTURE__ -completion token. -[import ../../example/futures.cpp] -[futures_examples] -[endsect] +* [link async_mqtt5.examples.asio Compatibility with Boost.Asio] + * [link async_mqtt5.examples.asio.callbacks Async functions with callbacks] + * [link async_mqtt5.examples.asio.cpp20_coroutines Async functions with C++20 coroutines] + * [link async_mqtt5.examples.asio.futures Async functions with futures] + +[include examples/Network_connection.qbk] +[include examples/Asio_compatibility.qbk] [endsect] diff --git a/doc/qbk/examples/Asio_compatibility.qbk b/doc/qbk/examples/Asio_compatibility.qbk new file mode 100644 index 0000000..130dae9 --- /dev/null +++ b/doc/qbk/examples/Asio_compatibility.qbk @@ -0,0 +1,40 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:asio Compatibility with Boost.Asio] +The __Self__ is built upon __Asio__ and thus follows the same principles. +This section illustrates the usage of __Self__ async +functions with different __CompletionToken__. + +# [link async_mqtt5.examples.asio.callbacks Async functions with callbacks] +# [link async_mqtt5.examples.asio.cpp20_coroutines Async functions with C++20 coroutines] +# [link async_mqtt5.examples.asio.futures Async functions with futures] + + +[section:callbacks Async functions with callbacks] +This example demonstrates how to use __Self__ asynchrous functions with callbacks. + +[import ../../../example/callbacks.cpp] +[callbacks_examples] +[endsect] + +[section:cpp20_coroutines Async functions with C++20 coroutines] +This example demonstrates how to use __Self__ asynchrous functions with C++20 coroutines +using __USE_AWAITABLE__ and __CO_SPAWN__. + +[import ../../../example/cpp20_coroutines.cpp] +[cpp20_coroutines_examples] +[endsect] + +[section:futures Async functions with futures] +This example demonstrates how to use __Self__ asynchrous functions with __USE_FUTURE__ +completion token. +[import ../../../example/futures.cpp] +[futures_examples] +[endsect] + +[endsect] diff --git a/doc/qbk/examples/Network_connection.qbk b/doc/qbk/examples/Network_connection.qbk new file mode 100644 index 0000000..54344da --- /dev/null +++ b/doc/qbk/examples/Network_connection.qbk @@ -0,0 +1,43 @@ +[/ + Copyright (c) 2023 Mireo + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:network_connection Establishing a network connection with different protocols ] +The __MQTT__ protocol requires the underlying transport protocol that ensures +orderly, lossless transmission of byte streams between the Client and Server in both directions. + +The following examples demonstrate how to establish a network connection using suitable +transport protocols such as TCP/IP, TLS/SSL, and WebSocket. + +[import ../../../example/network_connection.cpp] + +[h3 TCP/IP connection] +To create a TCP/IP connection with a Broker, initialize __Self__ with __TCP_SOCKET__ as the __StreamType__. +[tcp] + +[h3 TLS/SSL connection] +To establish a secure and encrypted connection using the TLS/SSL protocol, supply a context object that meets the __TlsContext__ requirements. +Additionally, initialize __Self__ with an underlying stream that implements TLS/SSL protocol as the __StreamType__. + +This example will demonstrate how to set up an SSL connection using __SSL_CONTEXT__ and __SSL_STREAM__. +To use SSL support in __Asio__, __OPENSSL__ is required. +[tls] + +[h3 WebSocket connection] +The WebSocket connection can be established over an unencrypted TCP/IP connection or an encrypted TLS/SSL connection. + +The following example will showcase setting up WebSocket over TCP/IP and WebSocket over TLS/SLL connection using +__WEBSOCKET_STREAM__. + +[h4 WebSocket over TCP/IP] +[websocket_tcp] + +[h4 WebSocket over TLS/SSL] +[websocket_tls] + +Once the __Self__ has been initialized with a suitable __StreamType__, it is prepared for configuration and utilization. + +[endsect] diff --git a/doc/qbk/reference/concepts/StreamType.qbk b/doc/qbk/reference/concepts/StreamType.qbk index 0388152..c03da36 100644 --- a/doc/qbk/reference/concepts/StreamType.qbk +++ b/doc/qbk/reference/concepts/StreamType.qbk @@ -13,11 +13,8 @@ Additionally, it should follow Asio's layered stream model by having a `lowest_layer_type` member type, and a `lowest_layer` member function, returing a `lowest_layer_type&`. -The `lowest_layer_type` should inherit from [asioreflink basic_stream_socket basic_stream_socket]. +The `lowest_layer_type` should inherit from __TCP_SOCKET__. -The types [asioreflink basic_stream_socket basic_stream_socket], -[asioreflink ssl__stream ssl::stream], and -[beastreflink boost__beast__websocket__stream websocket::stream] -meet these requirements. +The types __TCP_SOCKET__, __SSL_STREAM__ and __WEBSOCKET_STREAM__ meet these requirements. [endsect] diff --git a/doc/reference.dox b/doc/reference.dox index 4f090f8..5d26e4f 100644 --- a/doc/reference.dox +++ b/doc/reference.dox @@ -72,7 +72,6 @@ WARN_LOGFILE = INPUT = ../include/async_mqtt5/error.hpp \ ../include/async_mqtt5/types.hpp \ ../include/async_mqtt5/mqtt_client.hpp \ - ../include/async_mqtt5/detail/any_authenticator.hpp FILE_PATTERNS = RECURSIVE = NO EXCLUDE = diff --git a/example/cpp20_coroutines.cpp b/example/cpp20_coroutines.cpp index 24ca02a..72fc398 100644 --- a/example/cpp20_coroutines.cpp +++ b/example/cpp20_coroutines.cpp @@ -20,7 +20,7 @@ using client_type = async_mqtt5::mqtt_client; * When an asynchronous function is called, the coroutine is suspended. * After the asynchronous operation finishes, the coroutine resumes from the point it was suspended. * - * In this example, each asynchronous function is invoked with boost::asio::use_awaitable completion token. + * In this example, each asynchronous function is invoked with ``__USE_AWAITABLE__`` completion token. * When using this completion token, co_await will throw exceptions instead of returning an error code. * If you do not wish to throw exceptions, refer to the following use_nothrow_awaitable and nothrow_coroutine() example. */ @@ -100,7 +100,7 @@ asio::awaitable coroutine(client_type& client) { } /** - * A modified completion token. Using this completion token instead of asio::use_awaitable + * A modified completion token. Using this completion token instead of ``__USE_AWAITABLE__`` * will prevent co_await from throwing exceptions. Instead, co_await will return the error code * along with other values specified in the handler signature. */ diff --git a/example/futures.cpp b/example/futures.cpp index 0e0a8b2..c60e351 100644 --- a/example/futures.cpp +++ b/example/futures.cpp @@ -70,7 +70,7 @@ void run_with_future(client_type& client) { std::cout << "Subscribe Reason Code: " << sub_rcs[0].message() << std::endl; // Note: the get() call on async_receive future could block indefinitely if the mqtt_client - // failed to subscribe or there are no Application Messages to be received from the subcribed Topic! + // failed to subscribe or there are no Application Messages to be received from the subscribed Topic! if (!sub_rcs[0]) { using rec_fut_type = std::tuple; std::future rec_fut = client.async_receive(asio::use_future); diff --git a/example/network_connection.cpp b/example/network_connection.cpp new file mode 100644 index 0000000..09928d4 --- /dev/null +++ b/example/network_connection.cpp @@ -0,0 +1,164 @@ +//[network_connectiong + + +//[tcp +#include + +#include + +#include + +void tcp_setup() { + boost::asio::io_context ioc; + + // Use ``__TCP_SOCKET__`` as the underlying stream. + async_mqtt5::mqtt_client client(ioc, ""); +} + +//] + + +//[tls +#include +#include + +#include + +#include + + +// External customization point. +namespace async_mqtt5 { + +// This client uses this funcction to indicate which hostname it is +// attempting to connect to at the start of the handshaking process. +template +void assign_tls_sni( + const authority_path& ap, + boost::asio::ssl::context& ctx, + boost::asio::ssl::stream& stream +) { + SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); +} + +} // end namespace async_mqtt5 + +// The certificate file in PEM format. +constexpr char certificate[] = +"-----BEGIN CERTIFICATE-----\n" +"...........................\n" +"-----END CERTIFICATE-----\n" +; + +void ssl_setup() { + boost::asio::io_context ioc; + + // Context satisfying ``__TlsContext__`` requirements 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); + + async_mqtt5::error_code ec; + + // Add the trusted certificate authority for performing verification. + context.add_certificate_authority(boost::asio::buffer(certificate), ec); + + // Set peer verification mode used by the context. + // This will verify that the server's certificate is valid and signed by a trusted certificate authority. + context.set_verify_mode(boost::asio::ssl::verify_peer, ec); + + // Use ``__SSL_STREAM__`` as the ``__StreamType__`` with ``__SSL_CONTEXT__`` as the ``__TlsContext__``. + async_mqtt5::mqtt_client< + boost::asio::ssl::stream, + boost::asio::ssl::context + > client(ioc, "", std::move(context)); +} +//] + +//[websocket_tcp +#include + +#include + +#include + +#include + +void websocket_tcp_setup() { + boost::asio::io_context ioc; + + // Use ``[beastreflink boost__beast__websocket__stream websocket::stream<__TCP_SOCKET__>]`` as the underlying stream. + async_mqtt5::mqtt_client< + boost::beast::websocket::stream + > client(ioc, ""); +} + +//] + +//[websocket_tls +#include +#include + +#include + +#include + +#include + +constexpr char ca[] = +"-----BEGIN CERTIFICATE-----\n" +"...........................\n" +"-----END CERTIFICATE-----\n" +; + +namespace boost::beast::websocket { + +// ``[beastreflink boost__beast__websocket__async_teardown boost::beast::websocket::async_teardown]`` is a free function +// designed to initiate the asynchronous teardown of a connection. +// The specific behaviour of this function is based on the NextLayer type (Socket type) used to create the ``__WEBSOCKET_STREAM__``. +// ``__Beast__`` library includes an implementation of this function for ``__TCP_SOCKET__``. +// However, the callers are responsible for providing a suitable overload of this function for any other type, +// such as ``__SSL_STREAM__`` as shown in this example. +template +void async_teardown( + boost::beast::role_type role, + asio::ssl::stream& stream, + TeardownHandler&& handler +) { + return stream.async_shutdown(std::forward(handler)); +} + +} // end namespace boost::beast::websocket + +namespace async_mqtt5 { + +template +void assign_tls_sni( + const authority_path& ap, + asio::ssl::context& ctx, + asio::ssl::stream& stream +) { + ssl_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); +} + +} // end namespace async_mqtt5 + +void websocket_tls_setup() { + boost::asio::io_context ioc; + + boost::asio::ssl::context context(boost::asio::ssl::context::tls_client); + + async_mqtt5::error_code ec; + context.add_certificate_authority(boost::asio::buffer(ca), ec); + context.set_verify_mode(boost::asio::ssl::verify_peer, ec); + + async_mqtt5::mqtt_client< + boost::beast::websocket::stream>, + boost::asio::ssl::context + > client(ioc, "", std::move(context)); +} + +//] + + +//] diff --git a/include/async_mqtt5/mqtt_client.hpp b/include/async_mqtt5/mqtt_client.hpp index dc576b3..89f7a08 100644 --- a/include/async_mqtt5/mqtt_client.hpp +++ b/include/async_mqtt5/mqtt_client.hpp @@ -85,13 +85,18 @@ public: mqtt_client(context.get_executor(), cnf, std::move(tls_context)) {} - /// Move-construct an mqtt_client from another. - /** Moved-from client can only be destructed. */ + /** + * \brief Move-construct an mqtt_client from another. + * + * \details Moved-from client can only be destructed + */ mqtt_client(mqtt_client&& other) noexcept = default; - /// Move-assign an mqtt_client from another. - /** Cancels this client first. - * Moved-from client can only be destructed. + + /** + * \brief Move assignment operator. + * + * \details Cancels this client first. Moved-from client can only be destructed. */ mqtt_client& operator=(mqtt_client&& other) noexcept { cancel();