From 237d042001022c5740814ba7460e57067a113b12 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Wed, 25 Mar 2026 18:35:24 +0300 Subject: [PATCH 1/2] Replace const std::vector with boost::span --- include/boost/mqtt5/impl/codecs/message_encoders.hpp | 11 ++++++----- include/boost/mqtt5/impl/subscribe_op.hpp | 8 +++++--- include/boost/mqtt5/impl/unsubscribe_op.hpp | 8 +++++--- include/boost/mqtt5/mqtt_client.hpp | 10 ++++++---- test/unit/sub_unsub.cpp | 8 ++++---- test/unit/subscribe_op.cpp | 2 +- test/unit/unsubscribe_op.cpp | 2 +- 7 files changed, 28 insertions(+), 21 deletions(-) diff --git a/include/boost/mqtt5/impl/codecs/message_encoders.hpp b/include/boost/mqtt5/impl/codecs/message_encoders.hpp index 430308a..4beb516 100644 --- a/include/boost/mqtt5/impl/codecs/message_encoders.hpp +++ b/include/boost/mqtt5/impl/codecs/message_encoders.hpp @@ -12,11 +12,12 @@ #include +#include + #include #include #include #include -#include namespace boost::mqtt5::encoders { @@ -231,7 +232,7 @@ inline std::string encode_pubcomp( inline std::string encode_subscribe( uint16_t packet_id, - const std::vector& topics, + boost::span topics, const subscribe_props& props ) { @@ -270,7 +271,7 @@ inline std::string encode_subscribe( inline std::string encode_suback( uint16_t packet_id, - const std::vector& reason_codes, + boost::span reason_codes, const suback_props& props ) { @@ -298,7 +299,7 @@ inline std::string encode_suback( inline std::string encode_unsubscribe( uint16_t packet_id, - const std::vector& topics, + boost::span topics, const unsubscribe_props& props ) { @@ -330,7 +331,7 @@ inline std::string encode_unsubscribe( inline std::string encode_unsuback( uint16_t packet_id, - const std::vector& reason_codes, + boost::span reason_codes, const unsuback_props& props ) { diff --git a/include/boost/mqtt5/impl/subscribe_op.hpp b/include/boost/mqtt5/impl/subscribe_op.hpp index 8193bf7..015b072 100644 --- a/include/boost/mqtt5/impl/subscribe_op.hpp +++ b/include/boost/mqtt5/impl/subscribe_op.hpp @@ -29,6 +29,8 @@ #include #include +#include + #include #include #include @@ -91,7 +93,7 @@ class subscribe_op { } void perform( - const std::vector& topics, + boost::span topics, const subscribe_props& props ) { _num_topics = topics.size(); @@ -223,7 +225,7 @@ class subscribe_op { } static error_code validate_subscribe( - const std::vector& topics, + boost::span topics, const subscribe_props& props, validation_context& ctx ) { error_code ec; @@ -345,7 +347,7 @@ class initiate_async_subscribe { template void operator()( Handler&& handler, - const std::vector& topics, const subscribe_props& props + span topics, const subscribe_props& props ) { detail::subscribe_op { _svc_ptr, std::move(handler) } .perform(topics, props); diff --git a/include/boost/mqtt5/impl/unsubscribe_op.hpp b/include/boost/mqtt5/impl/unsubscribe_op.hpp index e087059..fe4330d 100644 --- a/include/boost/mqtt5/impl/unsubscribe_op.hpp +++ b/include/boost/mqtt5/impl/unsubscribe_op.hpp @@ -29,6 +29,8 @@ #include #include +#include + #include #include #include @@ -87,7 +89,7 @@ class unsubscribe_op { } void perform( - const std::vector& topics, + boost::span topics, const unsubscribe_props& props ) { _num_topics = topics.size(); @@ -196,7 +198,7 @@ class unsubscribe_op { private: static error_code validate_unsubscribe( - const std::vector& topics, + boost::span topics, const unsubscribe_props& props ) { for (const auto& topic : topics) @@ -268,7 +270,7 @@ class initiate_async_unsubscribe { template void operator()( Handler&& handler, - const std::vector& topics, const unsubscribe_props& props + boost::span topics, const unsubscribe_props& props ) { detail::unsubscribe_op { _svc_ptr, std::move(handler) } .perform(topics, props); diff --git a/include/boost/mqtt5/mqtt_client.hpp b/include/boost/mqtt5/mqtt_client.hpp index df3b133..720b721 100644 --- a/include/boost/mqtt5/mqtt_client.hpp +++ b/include/boost/mqtt5/mqtt_client.hpp @@ -30,6 +30,7 @@ #include #include // std::monostate #include +#include namespace boost::mqtt5 { @@ -581,7 +582,7 @@ class mqtt_client { typename asio::default_completion_token::type > decltype(auto) async_subscribe( - const std::vector& topics, + boost::span topics, const subscribe_props& props, CompletionToken&& token = {} ) { @@ -658,7 +659,7 @@ class mqtt_client { CompletionToken&& token = {} ) { return async_subscribe( - std::vector { topic }, props, + std::array { topic }, props, std::forward(token) ); } @@ -720,7 +721,8 @@ class mqtt_client { typename asio::default_completion_token::type > decltype(auto) async_unsubscribe( - const std::vector& topics, const unsubscribe_props& props, + boost::span topics, + const unsubscribe_props& props, CompletionToken&& token = {} ) { using Signature = void ( @@ -792,7 +794,7 @@ class mqtt_client { CompletionToken&& token = {} ) { return async_unsubscribe( - std::vector { topic }, props, + std::array { topic }, props, std::forward(token) ); } diff --git a/test/unit/sub_unsub.cpp b/test/unit/sub_unsub.cpp index ecaf65c..c54fd1e 100644 --- a/test/unit/sub_unsub.cpp +++ b/test/unit/sub_unsub.cpp @@ -195,7 +195,7 @@ BOOST_FIXTURE_TEST_CASE(receive_malformed_suback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_suback, shared_test_data) { // packets auto malformed_suback = encoders::encode_suback( - 1, { uint8_t(0x04) }, suback_props {} + 1, std::vector{ uint8_t(0x04) }, suback_props {} ); auto disconnect = encoders::encode_disconnect( @@ -229,7 +229,7 @@ BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_suback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(mismatched_num_of_suback_rcs, shared_test_data) { // packets auto malformed_suback = encoders::encode_suback( - 1, { uint8_t(0x00), uint8_t(0x00) }, suback_props {} + 1, std::vector{ uint8_t(0x00), uint8_t(0x00) }, suback_props {} ); auto disconnect = encoders::encode_disconnect( @@ -334,7 +334,7 @@ BOOST_FIXTURE_TEST_CASE(receive_malformed_unsuback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_unsuback, shared_test_data) { // packets auto malformed_unsuback = encoders::encode_unsuback( - 1, { uint8_t(0x04) }, unsuback_props {} + 1, std::vector{ uint8_t(0x04) }, unsuback_props {} ); auto disconnect = encoders::encode_disconnect( @@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_unsuback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(mismatched_num_of_unsuback_rcs, shared_test_data) { // packets auto malformed_unsuback = encoders::encode_unsuback( - 1, { uint8_t(0x00), uint8_t(0x00)}, unsuback_props {} + 1, std::vector{ uint8_t(0x00), uint8_t(0x00)}, unsuback_props {} ); auto disconnect = encoders::encode_disconnect( diff --git a/test/unit/subscribe_op.cpp b/test/unit/subscribe_op.cpp index a8001d5..a1aee7f 100644 --- a/test/unit/subscribe_op.cpp +++ b/test/unit/subscribe_op.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) { client_service_type, decltype(handler) > { svc_ptr, std::move(handler) } .perform( - { { "topic", { qos_e::exactly_once } } }, subscribe_props {} + std::vector{ { "topic", { qos_e::exactly_once } } }, subscribe_props {} ); ioc.poll(); diff --git a/test/unit/unsubscribe_op.cpp b/test/unit/unsubscribe_op.cpp index f9e2c43..2903a39 100644 --- a/test/unit/unsubscribe_op.cpp +++ b/test/unit/unsubscribe_op.cpp @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) { detail::unsubscribe_op< client_service_type, decltype(handler) > { svc_ptr, std::move(handler) } - .perform({ "topic" }, unsubscribe_props {}); + .perform(std::vector{ "topic" }, unsubscribe_props {}); ioc.poll(); BOOST_TEST(handlers_called == expected_handlers_called); From 8dffa6fc13b6723e09b9134f72aa58d8ea25a929 Mon Sep 17 00:00:00 2001 From: vsoulgard Date: Wed, 25 Mar 2026 21:58:35 +0300 Subject: [PATCH 2/2] New changes --- include/boost/mqtt5/detail/traits.hpp | 13 ++ .../mqtt5/impl/codecs/message_encoders.hpp | 17 ++ include/boost/mqtt5/impl/subscribe_op.hpp | 8 + include/boost/mqtt5/impl/unsubscribe_op.hpp | 8 + include/boost/mqtt5/mqtt_client.hpp | 145 +++++++++++++++++- test/unit/sub_unsub.cpp | 8 +- test/unit/subscribe_op.cpp | 2 +- test/unit/unsubscribe_op.cpp | 2 +- 8 files changed, 193 insertions(+), 10 deletions(-) diff --git a/include/boost/mqtt5/detail/traits.hpp b/include/boost/mqtt5/detail/traits.hpp index 3f45a8b..4b2abf7 100644 --- a/include/boost/mqtt5/detail/traits.hpp +++ b/include/boost/mqtt5/detail/traits.hpp @@ -58,6 +58,19 @@ constexpr bool is_boost_iterator = is_specialization< boost::remove_cv_ref_t, boost::iterator_range >; +template +constexpr bool is_container_of_impl = false; + +template +constexpr bool is_container_of_impl> = + std::is_same_v; + +template +constexpr bool is_container_of = is_container_of_impl< + boost::remove_cv_ref_t, U +>; + } // end namespace boost::mqtt5::detail #endif // !BOOST_MQTT5_TRAITS_HPP diff --git a/include/boost/mqtt5/impl/codecs/message_encoders.hpp b/include/boost/mqtt5/impl/codecs/message_encoders.hpp index 4beb516..bfc972f 100644 --- a/include/boost/mqtt5/impl/codecs/message_encoders.hpp +++ b/include/boost/mqtt5/impl/codecs/message_encoders.hpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace boost::mqtt5::encoders { @@ -297,6 +298,14 @@ inline std::string encode_suback( return s; } +inline std::string encode_suback( + uint16_t packet_id, + std::initializer_list reason_codes, + const suback_props& props +) { + return encode_suback(packet_id, std::vector(reason_codes), props); +} + inline std::string encode_unsubscribe( uint16_t packet_id, boost::span topics, @@ -357,6 +366,14 @@ inline std::string encode_unsuback( return s; } +inline std::string encode_unsuback( + uint16_t packet_id, + std::initializer_list reason_codes, + const unsuback_props& props +) { + return encode_unsuback(packet_id, std::vector(reason_codes), props); +} + inline std::string encode_pingreq() { auto packet_type_ = basic::flag<4>(0b1100) | diff --git a/include/boost/mqtt5/impl/subscribe_op.hpp b/include/boost/mqtt5/impl/subscribe_op.hpp index 015b072..69d1037 100644 --- a/include/boost/mqtt5/impl/subscribe_op.hpp +++ b/include/boost/mqtt5/impl/subscribe_op.hpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace boost::mqtt5::detail { @@ -121,6 +122,13 @@ class subscribe_op { send_subscribe(std::move(subscribe)); } + void perform( + std::initializer_list topics, + const subscribe_props& props + ) { + perform(std::vector(topics), props); + } + void send_subscribe(control_packet subscribe) { auto wire_data = subscribe.wire_data(); _svc_ptr->async_send( diff --git a/include/boost/mqtt5/impl/unsubscribe_op.hpp b/include/boost/mqtt5/impl/unsubscribe_op.hpp index fe4330d..0ad9669 100644 --- a/include/boost/mqtt5/impl/unsubscribe_op.hpp +++ b/include/boost/mqtt5/impl/unsubscribe_op.hpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace boost::mqtt5::detail { @@ -117,6 +118,13 @@ class unsubscribe_op { send_unsubscribe(std::move(unsubscribe)); } + void perform( + std::initializer_list topics, + const unsubscribe_props& props + ) { + perform(std::vector(topics), props); + } + void send_unsubscribe(control_packet unsubscribe) { auto wire_data = unsubscribe.wire_data(); _svc_ptr->async_send( diff --git a/include/boost/mqtt5/mqtt_client.hpp b/include/boost/mqtt5/mqtt_client.hpp index 720b721..860783a 100644 --- a/include/boost/mqtt5/mqtt_client.hpp +++ b/include/boost/mqtt5/mqtt_client.hpp @@ -31,6 +31,7 @@ #include // std::monostate #include #include +#include namespace boost::mqtt5 { @@ -578,11 +579,13 @@ class mqtt_client { * */ template < + typename TopicSequence, typename CompletionToken = - typename asio::default_completion_token::type + typename asio::default_completion_token::type, + typename = std::enable_if_t> > decltype(auto) async_subscribe( - boost::span topics, + const TopicSequence& topics, const subscribe_props& props, CompletionToken&& token = {} ) { @@ -595,6 +598,74 @@ class mqtt_client { ); } + /** + * \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 + * - \ref boost::mqtt5::client::error::malformed_packet + * - \ref boost::mqtt5::client::error::packet_too_large + * - \ref boost::mqtt5::client::error::pid_overrun + * - \ref boost::mqtt5::client::error::invalid_topic + * - \ref boost::mqtt5::client::error::wildcard_subscription_not_available + * - \ref boost::mqtt5::client::error::subscription_identifier_not_available + * - \ref boost::mqtt5::client::error::shared_subscription_not_available + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_subscribe( + std::initializer_list topics, + const subscribe_props& props, + CompletionToken&& token = {} + ) { + return async_subscribe(std::vector(topics), props, + std::forward(token)); + } + /** * \brief Send a \__SUBSCRIBE\__ packet to Broker to create a Subscription * to one Topic of interest. @@ -717,11 +788,13 @@ class mqtt_client { * */ template < + typename TopicSequence, typename CompletionToken = - typename asio::default_completion_token::type + typename asio::default_completion_token::type, + typename = std::enable_if_t> > decltype(auto) async_unsubscribe( - boost::span topics, + const TopicSequence& topics, const unsubscribe_props& props, CompletionToken&& token = {} ) { @@ -734,6 +807,70 @@ class mqtt_client { ); } + /** + * \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 + * - \ref boost::mqtt5::client::error::malformed_packet + * - \ref boost::mqtt5::client::error::packet_too_large + * - \ref boost::mqtt5::client::error::pid_overrun + * - \ref boost::mqtt5::client::error::invalid_topic + * + * Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code. + * + * \par Per-Operation Cancellation + * This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n + * - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n + * - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n + * + */ + template < + typename CompletionToken = + typename asio::default_completion_token::type + > + decltype(auto) async_unsubscribe( + std::initializer_list topics, + const unsubscribe_props& props, + CompletionToken&& token = {} + ) { + return async_unsubscribe(std::vector(topics), props, + std::forward(token)); + } + /** * \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe * from one Topic. diff --git a/test/unit/sub_unsub.cpp b/test/unit/sub_unsub.cpp index c54fd1e..ecaf65c 100644 --- a/test/unit/sub_unsub.cpp +++ b/test/unit/sub_unsub.cpp @@ -195,7 +195,7 @@ BOOST_FIXTURE_TEST_CASE(receive_malformed_suback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_suback, shared_test_data) { // packets auto malformed_suback = encoders::encode_suback( - 1, std::vector{ uint8_t(0x04) }, suback_props {} + 1, { uint8_t(0x04) }, suback_props {} ); auto disconnect = encoders::encode_disconnect( @@ -229,7 +229,7 @@ BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_suback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(mismatched_num_of_suback_rcs, shared_test_data) { // packets auto malformed_suback = encoders::encode_suback( - 1, std::vector{ uint8_t(0x00), uint8_t(0x00) }, suback_props {} + 1, { uint8_t(0x00), uint8_t(0x00) }, suback_props {} ); auto disconnect = encoders::encode_disconnect( @@ -334,7 +334,7 @@ BOOST_FIXTURE_TEST_CASE(receive_malformed_unsuback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_unsuback, shared_test_data) { // packets auto malformed_unsuback = encoders::encode_unsuback( - 1, std::vector{ uint8_t(0x04) }, unsuback_props {} + 1, { uint8_t(0x04) }, unsuback_props {} ); auto disconnect = encoders::encode_disconnect( @@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(receive_invalid_rc_in_unsuback, shared_test_data) { BOOST_FIXTURE_TEST_CASE(mismatched_num_of_unsuback_rcs, shared_test_data) { // packets auto malformed_unsuback = encoders::encode_unsuback( - 1, std::vector{ uint8_t(0x00), uint8_t(0x00)}, unsuback_props {} + 1, { uint8_t(0x00), uint8_t(0x00)}, unsuback_props {} ); auto disconnect = encoders::encode_disconnect( diff --git a/test/unit/subscribe_op.cpp b/test/unit/subscribe_op.cpp index a1aee7f..a8001d5 100644 --- a/test/unit/subscribe_op.cpp +++ b/test/unit/subscribe_op.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) { client_service_type, decltype(handler) > { svc_ptr, std::move(handler) } .perform( - std::vector{ { "topic", { qos_e::exactly_once } } }, subscribe_props {} + { { "topic", { qos_e::exactly_once } } }, subscribe_props {} ); ioc.poll(); diff --git a/test/unit/unsubscribe_op.cpp b/test/unit/unsubscribe_op.cpp index 2903a39..f9e2c43 100644 --- a/test/unit/unsubscribe_op.cpp +++ b/test/unit/unsubscribe_op.cpp @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) { detail::unsubscribe_op< client_service_type, decltype(handler) > { svc_ptr, std::move(handler) } - .perform(std::vector{ "topic" }, unsubscribe_props {}); + .perform({ "topic" }, unsubscribe_props {}); ioc.poll(); BOOST_TEST(handlers_called == expected_handlers_called);