diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35ce599..735fe30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,6 +101,14 @@ jobs: cmake -S . -B build -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}" cmake --install build + - name: Build examples + run: | + cmake -S example -B example\\build -A ${{ matrix.architecture }} ^ + -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" ^ + -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" ^ + -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}" + cmake --build example\\build -j 4 + - name: Build tests run: | cmake -S test -B test\\build -A ${{ matrix.architecture }} ^ @@ -278,6 +286,14 @@ jobs: -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" sudo cmake --install build + - name: Build examples + run: | + cmake -S example -B example/build \ + -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" \ + -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ + -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" + cmake --build example/build -j 4 + - name: Build tests run: | cmake -S test -B test/build \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 91b38de..a8f8474 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(async-mqtt5 VERSION 1.0.1 LANGUAGES CXX) +project(async-mqtt5 VERSION 1.0.2 LANGUAGES CXX) include(cmake/project-is-top-level.cmake) include(cmake/variables.cmake) diff --git a/README.md b/README.md index 1607b92..4bafbcc 100644 --- a/README.md +++ b/README.md @@ -89,15 +89,16 @@ The following example illustrates a scenario of configuring a Client and publish #include #include -#include +#include +#include int main() { boost::asio::io_context ioc; async_mqtt5::mqtt_client c(ioc); - c.credentials("", "", "") - .brokers("", 1883) + c.brokers("", 1883) + .credentials("", "", "") .async_run(boost::asio::detached); c.async_publish( @@ -114,27 +115,6 @@ int main() { ``` To see more examples, visit [Examples](https://github.com/mireo/async-mqtt5/tree/master/example). -Building with CMake ---------- -You can use the `CMakeLists.txt` provided in our repository to compile and run any of the [examples](https://github.com/mireo/async-mqtt5/tree/master/example) or your own source files. -The following commands demonstrate compiling and running the previous code using CMake on Linux. -The source file is located at [example/hello_world_over_tcp.cpp](https://github.com/mireo/async-mqtt5/blob/master/example/hello_world_over_tcp.cpp). - -```bash - # navigate to the root folder of Async.MQTT5 - - # compile the example - cmake -S . -B {build-folder} -DBUILD_EXAMPLES=ON -DCMAKE_EXE_LINKER_FLAGS="-pthread" - cmake --build {build-folder} - - # run the example - ./{build-folder}/example/example -``` - -You can edit the [example/CMakeLists.txt](https://github.com/mireo/async-mqtt5/blob/master/example/CMakeLists.txt) file to compile the source file of your choice. -By default, it will compile [example/hello_world_over_tcp.cpp](https://github.com/mireo/async-mqtt5/blob/master/example/hello_world_over_tcp.cpp). - - Contributing --------- When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. diff --git a/doc/qbk/01_intro.qbk b/doc/qbk/01_intro.qbk index 9a5a25d..a6d5c75 100644 --- a/doc/qbk/01_intro.qbk +++ b/doc/qbk/01_intro.qbk @@ -208,23 +208,6 @@ The following example illustrates a scenario of configuring a Client and publish To see more examples, visit [link async_mqtt5.examples Examples]. -[heading Building with CMake] -You can use the `CMakeLists.txt` provided in our repository to compile and run any of the [link async_mqtt5.examples examples] or your own source files. -The following commands demonstrate compiling and running the previous code using CMake on Linux. -The source file is located at [ghreflink example/hello_world_over_tcp.cpp example/hello_world_over_tcp.cpp]. - - # navigate to the root folder of Async.MQTT5 - - # compile the example - cmake -S . -B {build-folder} -DBUILD_EXAMPLES=ON -DCMAKE_EXE_LINKER_FLAGS="-pthread" - cmake --build {build-folder} - - # run the example - ./{build-folder}/example/example - -You can edit the [ghreflink example/CMakeLists.txt example/CMakeLists.txt] file to compile the source file of your choice. -By default, it will compile [ghreflink example/hello_world_over_tcp.cpp example/hello_world_over_tcp.cpp]. - [heading Acknowledgements] We thank [@https://github.com/chriskohlhoff Christopher Kohlhoff] for his outstanding __Asio__ library, which inspired the design of all interfaces and implementation strategies. diff --git a/doc/qbk/02_getting_started.qbk b/doc/qbk/02_getting_started.qbk index 529113c..0391bad 100644 --- a/doc/qbk/02_getting_started.qbk +++ b/doc/qbk/02_getting_started.qbk @@ -30,7 +30,12 @@ which might be required for establishing a secure connection. In this example, we choose TCP/IP as the underlying protocol to initialize the __Client__. -[init_tcp_client] +[!c++] + // Initialize the execution context required to run I/O operations. + boost::asio::io_context ioc; + + // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream. + async_mqtt5::mqtt_client client(ioc); [endsect] [/transport_protocol] @@ -48,9 +53,9 @@ Listing Brokers from different clusters may lead to inconsistencies between MQTT * *Assign a custom user-implemented authenticator:* The custom authentication will be used for __ENHANCED_AUTH__ ([refmem mqtt_client authenticator]). * *Defining CONNECT Packet Properties:* Specify properties that will be included in the __CONNECT__ packet sent during connection initiation (see [refmem mqtt_client connect_property] and [refmem mqtt_client connect_properties]). -We will connect to the __HIVEMQ__'s public Broker at `broker.hivemq.com`, which listens for MQTT connections on the default TCP/IP port 1883. -Additionally, we will set the Client Identifier to `async_mqtt5_tester` using the [refmem mqtt_client credentials] function. -This is not strictly mandatory, as some Brokers allow anonymous connections. +Firstly, we will specify the Broker we want to connect to using the [refmem mqtt_client brokers] function. +This can be __HIVEMQ__'s public Broker available at `broker.hivemq.com`:1883. +Additionally, we can set the Client Identifier using the [refmem mqtt_client credentials] function. This is not mandatory, as some Brokers allow anonymous connections. After configuring the `mqtt_client`, we call the [refmem mqtt_client async_run] function. This starts the process of establishing a connection with the Broker. @@ -120,12 +125,7 @@ The __Self__ library provides a built-in [ghreflink include/async_mqtt5/logger.h This logger outputs detailed information about each step in the connection process, including DNS resolution, TCP connection, TLS handshake, WebSocket handshake, and MQTT handshake. To enable this functionality, construct the __Client__ with an instance of this logger class: -[!c++] - // Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate. - async_mqtt5::mqtt_client client( - ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::debug) - ); - +[init_tcp_client_with_logger] [endsect] [/debugging] diff --git a/doc/qbk/examples/Examples.qbk b/doc/qbk/examples/Examples.qbk index 28bf0d1..e5852d6 100644 --- a/doc/qbk/examples/Examples.qbk +++ b/doc/qbk/examples/Examples.qbk @@ -28,7 +28,6 @@ This example illustrates the process of setting up the Client to connect to the [section:publisher The publisher] This example shows how to use __Client__ as a publisher that publishes sensor readings every `5` seconds. -The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ as the completion token. [publisher] [endsect] @@ -36,7 +35,6 @@ The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ [section:receiver The receiver] This example shows how to use __Client__ as a receiver. The __Client__ subscribes and indefinitely receives Application Messages from the Broker. -The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ as the completion token. [receiver] [endsect] diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 3281d00..16993ce 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,10 +8,20 @@ if(PROJECT_IS_TOP_LEVEL) find_package(async-mqtt5 REQUIRED) endif() -# set(EXAMPLE .cpp) -set(EXAMPLE hello_world_over_tcp.cpp) +function(add_example name) + add_executable("${name}" ${ARGN}) + target_link_libraries("${name}" PRIVATE Async::MQTT5) + string(FIND "${example_name}" "tls" found_tls) + if(found_tls GREATER -1) + target_link_libraries("${name}" PRIVATE OpenSSL::SSL) + endif() +endfunction() -find_package(OpenSSL REQUIRED) # if you require SSL connection -add_executable(example ${EXAMPLE}) -target_compile_features(example PRIVATE cxx_std_17) # or cxx_std_20 -target_link_libraries(example PRIVATE Async::MQTT5 OpenSSL::SSL) +file(GLOB examples "*.cpp") + +foreach(file_path ${examples}) + get_filename_component(example_name "${file_path}" NAME_WE) + add_example("${example_name}" "${file_path}") +endforeach() + +find_package(OpenSSL REQUIRED) diff --git a/example/hello_world_in_coro_multithreaded_env.cpp b/example/hello_world_in_coro_multithreaded_env.cpp index cfec032..d656ac3 100644 --- a/example/hello_world_in_coro_multithreaded_env.cpp +++ b/example/hello_world_in_coro_multithreaded_env.cpp @@ -5,31 +5,47 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -//[hello_world_in_coro_multithreaded_env -#include -#include - #include #ifdef BOOST_ASIO_HAS_CO_AWAIT +//[hello_world_in_coro_multithreaded_env +#include +#include +#include + #include #include #include -#include +#include #include +#include #include -#include +#include +#include +#include -using client_type = async_mqtt5::mqtt_client; +struct config { + 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 +>; + +// client_type without logging +//using client_type = async_mqtt5::mqtt_client; // Modified completion token that will prevent co_await from throwing exceptions. -constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable); +constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); boost::asio::awaitable publish_hello_world( - client_type& client, - const boost::asio::strand& strand + const config& cfg, client_type& client, + const boost::asio::strand& strand ) { // Confirmation that the coroutine running in the strand. assert(strand.running_in_this_thread()); @@ -37,55 +53,63 @@ boost::asio::awaitable publish_hello_world( // 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("", 1883) - .async_run(boost::asio::detached); + 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( - "", "Hello world!", async_mqtt5::retain_e::no, + "async-mqtt5/test" /* topic */, "Hello world!" /* payload*/, async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, use_nothrow_awaitable); co_await client.async_disconnect(use_nothrow_awaitable); co_return; } -int main() { - // Create a multithreaded environment where 4 threads - // will be calling ioc.run(). +int main(int argc, char** argv) { + config cfg; - // Number of threads that will call io_context::run(). - int thread_num = 4; - boost::asio::io_context ioc(4); + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } - // Create the remaining threads (aside of this one). - std::vector threads; - threads.reserve(thread_num - 1); + // 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(ioc.get_executor()); + 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); + client_type client(strand, {} /* tls_context */, async_mqtt5::logger(async_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(client, strand), boost::asio::detached); + co_spawn( + strand, + publish_hello_world(cfg, client, strand), + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); - // Call ioc.run() in the other threads. - for (int i = 0; i < thread_num - 1; ++i) - threads.emplace_back([&ioc] { ioc.run(); }); - - // Call ioc.run() on this thread. - ioc.run(); - - for (auto& t : threads) - if (t.joinable()) t.join(); + tp.join(); return 0; } -#endif - //] + +#else + +#include + +int main() { + std::cout << "This example requires C++20 standard to compile and run" << std::endl; +} + +#endif diff --git a/example/hello_world_in_multithreaded_env.cpp b/example/hello_world_in_multithreaded_env.cpp index 1101bee..e648145 100644 --- a/example/hello_world_in_multithreaded_env.cpp +++ b/example/hello_world_in_multithreaded_env.cpp @@ -7,89 +7,98 @@ //[hello_world_in_multithreaded_env #include -#include +#include #include +#include +#include #include -#include -#include +#include +#include #include #include -#include +#include +#include +#include +#include -int main() { - // Create a multithreaded environment where 4 threads - // will be calling ioc.run(). +struct config { + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; +}; - // Number of threads that will call io_context::run(). - int thread_num = 4; - boost::asio::io_context ioc(4); +int main(int argc, char** argv) { + config cfg; - // Create the remaining threads (aside of this one). - std::vector threads; - threads.reserve(thread_num - 1); + 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 io_context's executor. + // 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 io_context. - boost::asio::strand strand = boost::asio::make_strand(ioc.get_executor()); + // 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 client(strand); + 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)); // Configure the client. - client.brokers("", 1883); + 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::post( - strand, - [&client, &strand] { - // 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); - } + 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::post( - strand, - [&client, &strand] { - assert(strand.running_in_this_thread()); + boost::asio::dispatch( + boost::asio::bind_executor( + strand, + [&client, &strand, &cfg] { + assert(strand.running_in_this_thread()); - client.async_publish( - "", "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. - // However, you must not bind it to any other executor! - [&client, &strand]( - async_mqtt5::error_code ec, async_mqtt5::reason_code rc, - async_mqtt5::puback_props props - ) { - assert(strand.running_in_this_thread()); + client.async_publish( + "async-mqtt5/test", "Hello World!", 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()); - 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. This will cause ioc.run() to return. - client.cancel(); - } - ); - } + // Stop the Client. + client.cancel(); + } + ); + } + ) ); - // Call ioc.run() on the other threads. - for (int i = 0; i < thread_num - 1; ++i) - threads.emplace_back([&ioc] { ioc.run(); }); - - // Call ioc.run() on this thread. - ioc.run(); - - for (auto& t : threads) - if (t.joinable()) t.join(); + tp.join(); return 0; } diff --git a/example/hello_world_over_tcp.cpp b/example/hello_world_over_tcp.cpp index 0c3808d..8107391 100644 --- a/example/hello_world_over_tcp.cpp +++ b/example/hello_world_over_tcp.cpp @@ -7,27 +7,48 @@ //[hello_world_over_tcp #include +#include #include #include #include -#include +#include +#include +#include + +struct config { + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; // 1883 is the default TCP MQTT port. + std::string client_id = "async_mqtt5_tester"; +}; + +int main(int argc, char** argv) { + config cfg; + + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } -int main() { - //[init_tcp_client - // Initialize the execution context required to run I/O operations. boost::asio::io_context ioc; - // Construct the Client with ``__TCP_SOCKET__`` as the underlying stream. - async_mqtt5::mqtt_client client(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)); //] + // If you want to use the Client without logging, initialise it with the following line instead. + //async_mqtt5::mqtt_client client(ioc); + //[configure_tcp_client - // 1883 is the default TCP MQTT port. - client.brokers("broker.hivemq.com", 1883) - .credentials("async_mqtt5_tester") - .async_run(boost::asio::detached); + 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 diff --git a/example/hello_world_over_tls.cpp b/example/hello_world_over_tls.cpp index b97e810..3884bb4 100644 --- a/example/hello_world_over_tls.cpp +++ b/example/hello_world_over_tls.cpp @@ -7,21 +7,30 @@ //[hello_world_over_tls #include +#include #include #include #include #include -#include +#include +#include +#include + +struct config { + std::string brokers = "broker.hivemq.com"; + uint16_t port = 8883; // 8883 is the default TLS MQTT port. + std::string client_id = "async_mqtt5_tester"; +}; // External customization point. namespace async_mqtt5 { +// Specify that the TLS handshake will be performed as a client. template struct tls_handshake_type> { static constexpr auto client = boost::asio::ssl::stream_base::client; - static constexpr auto server = boost::asio::ssl::stream_base::server; }; // This client uses this function to indicate which hostname it is @@ -37,49 +46,49 @@ void assign_tls_sni( } // end namespace async_mqtt5 -// The certificate file in the PEM format. -constexpr char ca_cert[] = -"-----BEGIN CERTIFICATE-----\n" -"...........................\n" -"-----END CERTIFICATE-----\n" -; +int main(int argc, char** argv) { + config cfg; + + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } -int main() { boost::asio::io_context ioc; - // Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use. + // 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); - async_mqtt5::error_code ec; - - // Add the trusted certificate authority for performing verification. - context.add_certificate_authority(boost::asio::buffer(ca_cert), ec); - if (ec) - std::cout << "Failed to add certificate authority!" << std::endl; - ec.clear(); - - // 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); - if (ec) - std::cout << "Failed to set peer verification mode!" << std::endl; - ec.clear(); + // 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. + // with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type + // and logging enabled. async_mqtt5::mqtt_client< boost::asio::ssl::stream, - boost::asio::ssl::context - > client(ioc, std::move(context)); + boost::asio::ssl::context, + async_mqtt5::logger + > client(ioc, std::move(context), async_mqtt5::logger(async_mqtt5::log_level::info)); - // 8883 is the default TLS MQTT port. - client.brokers("", 8883) - .async_run(boost::asio::detached); + + // If you want to use the Client without logging, initialise it with the following line instead. + //async_mqtt5::mqtt_client< + // boost::asio::ssl::stream, + // boost::asio::ssl::context + //> client(ioc, std::move(context)); + + 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( - "", "Hello world!", + "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; diff --git a/example/hello_world_over_websocket_tcp.cpp b/example/hello_world_over_websocket_tcp.cpp index 221a1bf..007e566 100644 --- a/example/hello_world_over_websocket_tcp.cpp +++ b/example/hello_world_over_websocket_tcp.cpp @@ -7,6 +7,7 @@ //[hello_world_over_websocket_tcp #include +#include #include #include @@ -14,24 +15,47 @@ #include -#include +#include +#include +#include + #include // WebSocket traits -int main() { +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"; +}; + +int main(int argc, char** argv) { + config cfg; + + 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; - // Construct the Client with WebSocket/TCP as the underlying stream. + // 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 - > client(ioc); + boost::beast::websocket::stream, + std::monostate, + async_mqtt5::logger + > client(ioc, {}, async_mqtt5::logger(async_mqtt5::log_level::info)); - // 8083 is the default Webscoket/TCP MQTT port. - client.brokers("", 8083) // Path example: localhost/mqtt - .async_run(boost::asio::detached); + // If you want to use the Client without logging, initialise it with the following line instead. + //async_mqtt5::mqtt_client> client(ioc); + + client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to. + .credentials(cfg.client_id) // Set the Client Identifier. (optional) + .async_run(boost::asio::detached); // Start the Client. client.async_publish( - "", "Hello world!", - async_mqtt5::retain_e::no, async_mqtt5::publish_props{}, + "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); diff --git a/example/hello_world_over_websocket_tls.cpp b/example/hello_world_over_websocket_tls.cpp index d7e858a..b562b45 100644 --- a/example/hello_world_over_websocket_tls.cpp +++ b/example/hello_world_over_websocket_tls.cpp @@ -7,6 +7,7 @@ //[hello_world_over_websocket_tls #include +#include #include #include @@ -14,18 +15,27 @@ #include #include -#include // async_teardown specialization for websocket ssl stream +#include // async_teardown specialization for WebSocket SSL stream + +#include +#include +#include -#include #include // WebSocket traits +struct config { + std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt + uint16_t port = 8884; // 8884 is the default Websocket/TLS MQTT port. + std::string client_id = "async_mqtt5_tester"; +}; + // External customization point. namespace async_mqtt5 { +// Specify that the TLS handshake will be performed as a client. template struct tls_handshake_type> { static constexpr auto client = boost::asio::ssl::stream_base::client; - static constexpr auto server = boost::asio::ssl::stream_base::server; }; // This client uses this function to indicate which hostname it is @@ -41,49 +51,47 @@ void assign_tls_sni( } // end namespace async_mqtt5 -// The certificate file in the PEM format. -constexpr char ca_cert[] = -"-----BEGIN CERTIFICATE-----\n" -"...........................\n" -"-----END CERTIFICATE-----\n" -; +int main(int argc, char** argv) { + config cfg; + + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } -int main() { boost::asio::io_context ioc; - // Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use. + // 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); - async_mqtt5::error_code ec; - - // Add the trusted certificate authority for performing verification. - context.add_certificate_authority(boost::asio::buffer(ca_cert), ec); - if (ec) - std::cout << "Failed to add certificate authority!" << std::endl; - ec.clear(); - - // 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); - if (ec) - std::cout << "Failed to set peer verification mode!" << std::endl; - ec.clear(); + // 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. + // with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type and enabled logging. async_mqtt5::mqtt_client< boost::beast::websocket::stream>, - boost::asio::ssl::context - > client(ioc, std::move(context)); + boost::asio::ssl::context, + async_mqtt5::logger + > client(ioc, std::move(context), async_mqtt5::logger(async_mqtt5::log_level::info)); - // 8884 is the default Websocket/TLS MQTT port. - client.brokers("", 8884) // Path example: localhost/mqtt - .async_run(boost::asio::detached); + // If you want to use the Client without logging, initialise it with the following line instead. + //async_mqtt5::mqtt_client< + // boost::beast::websocket::stream>, + // boost::asio::ssl::context + //> client(ioc, std::move(context)); + + 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( - "", "Hello world!", + "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; diff --git a/example/multiflight_client.cpp b/example/multiflight_client.cpp index 86bee22..8c710e4 100644 --- a/example/multiflight_client.cpp +++ b/example/multiflight_client.cpp @@ -7,27 +7,50 @@ //[multiflight_client #include +#include #include #include #include #include -#include +#include +#include +#include +#include + +struct config { + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; +}; + +int main(int argc, char** argv) { + config cfg; + + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } -int main() { boost::asio::io_context ioc; - async_mqtt5::mqtt_client client(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)); - client.brokers("", 1883) - .async_run(boost::asio::detached); + 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( - "", "Hello world!", + "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; diff --git a/example/publisher.cpp b/example/publisher.cpp index 1461bf1..2091b12 100644 --- a/example/publisher.cpp +++ b/example/publisher.cpp @@ -5,6 +5,9 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#ifdef BOOST_ASIO_HAS_CO_AWAIT + //[publisher #include #include @@ -13,22 +16,35 @@ #include #include +#include #include #include #include #include -#include #include -#include +#include +#include +#include +#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +struct config { + 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::use_awaitable); +constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); -using client_type = async_mqtt5::mqtt_client; +// client_type with logging enabled +using client_type = async_mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +>; + +// client_type without logging +//using client_type = async_mqtt5::mqtt_client; int next_sensor_reading() { srand(static_cast(std::time(0))); @@ -36,12 +52,13 @@ int next_sensor_reading() { } boost::asio::awaitable publish_sensor_readings( - 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("", 1883) // Broker that we want to connect to. 1883 is the default TCP port. - .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. for (;;) { // Get the next sensor reading. @@ -49,7 +66,7 @@ boost::asio::awaitable publish_sensor_readings( // Publish the sensor reading with QoS 1. auto&& [ec, rc, props] = co_await client.async_publish( - "", reading, + "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: @@ -78,12 +95,20 @@ boost::asio::awaitable publish_sensor_readings( co_return; } -int main() { +int main(int argc, char** argv) { + config cfg; + + 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 the Client to connect to the Broker over TCP. - client_type client(ioc); + client_type client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); // Initialise the timer. boost::asio::steady_timer timer(ioc); @@ -98,12 +123,27 @@ int main() { }); // Spawn the coroutine. - co_spawn(ioc.get_executor(), publish_sensor_readings(client, timer), boost::asio::detached); + 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(); } -#endif - //] + +#else + +#include + +int main() { + std::cout << "This example requires C++20 standard to compile and run" << std::endl; +} + +#endif diff --git a/example/receiver.cpp b/example/receiver.cpp index a8f70ce..fa7757a 100644 --- a/example/receiver.cpp +++ b/example/receiver.cpp @@ -5,31 +5,48 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#ifdef BOOST_ASIO_HAS_CO_AWAIT + //[receiver #include +#include #include #include +#include #include #include #include -#include #include -#include +#include +#include +#include +#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +struct config { + 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::use_awaitable); +constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); -using client_type = async_mqtt5::mqtt_client; +// client_type with logging enabled +using client_type = async_mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +>; + +// client_type without logging +//using client_type = async_mqtt5::mqtt_client; boost::asio::awaitable subscribe(client_type& client) { // Configure the request to subscribe to a Topic. async_mqtt5::subscribe_topic sub_topic = async_mqtt5::subscribe_topic{ - "", + "test" /* topic */, async_mqtt5::subscribe_options { async_mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2. async_mqtt5::no_local_e::no, // Forward message from Clients with same ID. @@ -55,10 +72,13 @@ boost::asio::awaitable subscribe(client_type& client) { co_return !ec && !sub_codes[0]; // True if the subscription was successfully established. } -boost::asio::awaitable subscribe_and_receive(client_type& client) { +boost::asio::awaitable subscribe_and_receive( + 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("", 1883) // Broker that we want to connect to. 1883 is the default TCP port. + 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, @@ -90,12 +110,20 @@ boost::asio::awaitable subscribe_and_receive(client_type& client) { co_return; } -int main() { +int main(int argc, char** argv) { + config cfg; + + 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 the Client to connect to the Broker over TCP. - client_type client(ioc); + client_type client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::info)); // Set up signals to stop the program on demand. boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); @@ -106,12 +134,27 @@ int main() { }); // Spawn the coroutine. - co_spawn(ioc, subscribe_and_receive(client), boost::asio::detached); + co_spawn( + ioc, + subscribe_and_receive(cfg, client), + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); // Start the execution. ioc.run(); } -#endif - //] + +#else + +#include + +int main() { + std::cout << "This example requires C++20 standard to compile and run" << std::endl; +} + +#endif diff --git a/example/timeout_with_awaitable_operators.cpp b/example/timeout_with_awaitable_operators.cpp index abf5009..1ef6044 100644 --- a/example/timeout_with_awaitable_operators.cpp +++ b/example/timeout_with_awaitable_operators.cpp @@ -5,13 +5,18 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include +#ifdef BOOST_ASIO_HAS_CO_AWAIT + //[timeout_with_awaitable_operators #include #include +#include #include #include #include +#include #include #include #include @@ -19,42 +24,72 @@ #include #include -#include +#include +#include +#include +#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +struct config { + 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::use_awaitable); +constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred); -using client_type = async_mqtt5::mqtt_client; +// client_type with logging enabled +using client_type = async_mqtt5::mqtt_client< + boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, async_mqtt5::logger +>; -boost::asio::awaitable send_over_mqtt(client_type& client, const std::string& message) { - client.brokers("", 1883) - .async_run(boost::asio::detached); +// client_type without logging +//using client_type = async_mqtt5::mqtt_client; + +boost::asio::awaitable send_over_mqtt( + 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. auto&& [pec, prc, puback_props] = co_await client.async_publish( - "", message, + "async-mqtt5/test", "Hello World!", async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, use_nothrow_awaitable ); co_await client.async_disconnect(use_nothrow_awaitable); + co_return; } -int main() { +int main(int argc, char** argv) { + config cfg; + + 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; - co_spawn(ioc, [&ioc]() -> boost::asio::awaitable { - // Construct the Client. - async_mqtt5::mqtt_client client(ioc); + co_spawn( + ioc, + [&ioc, &cfg]() -> boost::asio::awaitable { + // Initialise the Client to connect to the Broker over TCP. + client_type client(ioc); + + // You can also initialise the Client and its logger with a specific log_level (default log_level::info). + //client_type client(ioc, {} /* tls_context */, async_mqtt5::logger(async_mqtt5::log_level::debug)); // 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(client, "Hello world!") || - timer.async_wait(use_nothrow_awaitable) + send_over_mqtt(cfg, client) || + timer.async_wait(boost::asio::as_tuple(boost::asio::use_awaitable)) ); // The timer expired first. The client is cancelled. @@ -64,11 +99,24 @@ int main() { else std::cout << "Send over MQTT completed!" << std::endl; - }, boost::asio::detached); + }, + [](std::exception_ptr e) { + if (e) + std::rethrow_exception(e); + } + ); ioc.run(); } -#endif - //] + +#else + +#include + +int main() { + std::cout << "This example requires C++20 standard to compile and run" << std::endl; +} + +#endif diff --git a/example/timeout_with_parallel_group.cpp b/example/timeout_with_parallel_group.cpp index 191ffac..32a076b 100644 --- a/example/timeout_with_parallel_group.cpp +++ b/example/timeout_with_parallel_group.cpp @@ -19,23 +19,47 @@ #include #include -#include +#include +#include +#include +#include + +struct config { + std::string brokers = "broker.hivemq.com"; + uint16_t port = 1883; + std::string client_id = "async_mqtt5_tester"; +}; + +int main(int argc, char** argv) { + config cfg; + + if (argc == 4) { + cfg.brokers = argv[1]; + cfg.port = uint16_t(std::stoi(argv[2])); + cfg.client_id = argv[3]; + } -int main() { boost::asio::io_context ioc; - // Construct the Client. - async_mqtt5::mqtt_client client(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)); + + // If you want to use the Client without logging, initialise it with the following line instead. + //async_mqtt5::mqtt_client client(ioc); // Construct the timer. boost::asio::steady_timer timer(ioc, std::chrono::seconds(5)); - client.brokers("", 1883) - .async_run(boost::asio::detached); + 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( - { "" }, async_mqtt5::subscribe_props {}, + { "test" /* Topic */}, async_mqtt5::subscribe_props {}, [](async_mqtt5::error_code ec, std::vector rcs, async_mqtt5::suback_props) { std::cout << "[subscribe ec]: " << ec.message() << std::endl; std::cout << "[subscribe rc]: " << rcs[0].message() << std::endl; diff --git a/include/async_mqtt5/logger.hpp b/include/async_mqtt5/logger.hpp index 2744eb0..3aeb18e 100644 --- a/include/async_mqtt5/logger.hpp +++ b/include/async_mqtt5/logger.hpp @@ -98,8 +98,9 @@ public: if (++it != eps.end()) std::clog << ","; } - std::clog << "]" << std::endl; + std::clog << "]"; } + std::clog << std::endl; } /** @@ -114,7 +115,7 @@ public: output_prefix(); std::clog - << "connect: " + << "TCP connect: " << ep.address().to_string() << ":" << ep.port() << " - " << ec.message() << std::endl; diff --git a/test/unit/logger.cpp b/test/unit/logger.cpp index 4f548a0..ce2d071 100644 --- a/test/unit/logger.cpp +++ b/test/unit/logger.cpp @@ -116,7 +116,7 @@ BOOST_AUTO_TEST_CASE(successful_connect_debug) { std::string log = output.rdbuf()->str(); BOOST_TEST_MESSAGE(log); BOOST_TEST_WARN(contains(log, "resolve")); - BOOST_TEST_WARN(contains(log, "connect")); + BOOST_TEST_WARN(contains(log, "TCP connect")); BOOST_TEST_WARN(contains(log, "TLS handshake")); BOOST_TEST_WARN(contains(log, "WebSocket handshake")); BOOST_TEST_WARN(contains(log, "connack"));