diff --git a/doc/Jamfile b/doc/Jamfile index 042a45d..1337b4a 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -34,6 +34,7 @@ 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 ; diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk index b6d0391..f1e1125 100644 --- a/doc/qbk/00_main.qbk +++ b/doc/qbk/00_main.qbk @@ -30,9 +30,10 @@ [template mqttlink[id text][@https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc[id] [text]]] [def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers CompletionToken]] -[def __ExecutionContext__ [reflink2 ExecutionContext ExecutionContext]] -[def __StreamType__ [reflink2 StreamType StreamType]] -[def __TlsContext__ [reflink2 TlsContext TlsContext]] +[def __ExecutionContext__ [reflink ExecutionContext]] +[def __StreamType__ [reflink StreamType]] +[def __TlsContext__ [reflink TlsContext]] +[def __is_authenticator__ [reflink is_authenticator]] [def __Boost__ [@https://www.boost.org/ Boost]] [def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]] @@ -55,6 +56,7 @@ [def __QOS__ [mqttlink 3901234 `QoS`]] [def __RETAIN__ [mqttlink 3901104 `RETAIN`]] [def __SUBSCRIBE_OPTIONS__ [mqttlink 3901169 `Subscribe Options`]] +[def __ENHANCED_AUTH__ [mqttlink 3901256 `Enhanced Authentication`]] [def __CONNECT__ [mqttlink 3901033 `CONNECT`]] [def __CONNACK__ [mqttlink 3901074 `CONNACK`]] diff --git a/doc/qbk/reference/concepts/is_authenticator.qbk b/doc/qbk/reference/concepts/is_authenticator.qbk new file mode 100644 index 0000000..7b1e652 --- /dev/null +++ b/doc/qbk/reference/concepts/is_authenticator.qbk @@ -0,0 +1,25 @@ +[/ + 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:is_authenticator is_authenticator concept] + +`is_authenticator` represents authenticator object that needs to have following functions: + + void async_auth( + ``[reflink2 auth_step_e async_mqtt5::auth_step_e]`` auth_step, // authentication stage + std::string server_data, // server authentication data + ``[asioreflink any_completion_handler any_completion_handler]``< + void( + __ERROR_CODE__ ec, // non-trivial error code aborts authentication + std::string auth_data // client authentication data + ) + > + ); + std::string_view method(); // returns authentication method + + +[endsect] diff --git a/doc/qbk/reference/quickref.xml b/doc/qbk/reference/quickref.xml index e1c18c2..2836112 100644 --- a/doc/qbk/reference/quickref.xml +++ b/doc/qbk/reference/quickref.xml @@ -27,6 +27,7 @@ ExecutionContext StreamType TlsContext + is_authenticator @@ -36,6 +37,7 @@ Enumerations + auth_step_e client_error disconnect_rc_e qos_e diff --git a/doc/reference.dox b/doc/reference.dox index a524653..4f090f8 100644 --- a/doc/reference.dox +++ b/doc/reference.dox @@ -71,7 +71,8 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- INPUT = ../include/async_mqtt5/error.hpp \ ../include/async_mqtt5/types.hpp \ - ../include/async_mqtt5/mqtt_client.hpp + ../include/async_mqtt5/mqtt_client.hpp \ + ../include/async_mqtt5/detail/any_authenticator.hpp FILE_PATTERNS = RECURSIVE = NO EXCLUDE = diff --git a/doc/reference.xsl b/doc/reference.xsl index 3ea5940..dad8434 100644 --- a/doc/reference.xsl +++ b/doc/reference.xsl @@ -40,6 +40,7 @@ [include concepts/ExecutionContext.qbk] [include concepts/StreamType.qbk] [include concepts/TlsContext.qbk] +[include concepts/is_authenticator.qbk] [include reason_codes/Reason_codes.qbk] [include properties/will_props.qbk] [include properties/connect_props.qbk] @@ -1481,6 +1482,7 @@ ExecutionContext StreamType TlsContext + is_authenticator @@ -1531,7 +1533,8 @@ + or contains(type, 'TlsContext') or contains(type, 'StreamType') + or contains(type, 'is_authenticator')"> diff --git a/include/async_mqtt5/detail/any_authenticator.hpp b/include/async_mqtt5/detail/any_authenticator.hpp new file mode 100644 index 0000000..0774d4e --- /dev/null +++ b/include/async_mqtt5/detail/any_authenticator.hpp @@ -0,0 +1,108 @@ +#ifndef ASYNC_MQTT5_ANY_AUTHENTICATOR +#define ASYNC_MQTT5_ANY_AUTHENTICATOR + +#include +#include + +#include + +namespace async_mqtt5 { + +namespace asio = boost::asio; +using error_code = boost::system::error_code; + +namespace detail { + +using auth_handler_type = asio::any_completion_handler< + void(error_code ec, std::string auth_data) +>; + +template +concept is_authenticator = requires (T a) { + { + a.async_auth(auth_step_e {}, std::string {}, auth_handler_type {}) + } -> std::same_as; + { a.method() } -> std::same_as; +}; + +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 +class auth_fun : public auth_fun_base { + Authenticator _authenticator; + +public: + auth_fun(Authenticator authenticator) : + auth_fun_base(&async_auth), + _authenticator(std::forward(authenticator)) + {} + + static void async_auth( + auth_step_e step, std::string data, auth_handler_type auth_handler, + auth_fun_base* base_ptr + ) { + auto auth_fun_ptr = static_cast(base_ptr); + auth_fun_ptr->_authenticator.async_auth( + step, std::move(data), std::move(auth_handler) + ); + } +}; + +} // end namespace detail + +class any_authenticator { + std::string _method; + std::unique_ptr _auth_fun; + +public: + any_authenticator() = default; + + template + any_authenticator(Authenticator&& a) : + _method(a.method()), + _auth_fun( + new detail::auth_fun( + std::forward(a) + ) + ) + {} + + std::string_view method() const { + return _method; + } + + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using Signature = void(error_code, std::string); + + auto initiate = [this](auto handler, auth_step_e step, std::string data) { + _auth_fun->async_auth(step, std::move(data), std::move(handler)); + }; + + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } +}; + +} // end namespace async_mqtt5 + +#endif // !ASYNC_MQTT5_ANY_AUTHENTICATOR diff --git a/include/async_mqtt5/detail/internal_types.hpp b/include/async_mqtt5/detail/internal_types.hpp index 9355601..5406a00 100644 --- a/include/async_mqtt5/detail/internal_types.hpp +++ b/include/async_mqtt5/detail/internal_types.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -38,6 +40,7 @@ struct mqtt_context { std::optional will; connect_props co_props; connack_props ca_props; + any_authenticator authenticator; }; struct disconnect_context { diff --git a/include/async_mqtt5/error.hpp b/include/async_mqtt5/error.hpp index bd6b9a8..306bb88 100644 --- a/include/async_mqtt5/error.hpp +++ b/include/async_mqtt5/error.hpp @@ -486,6 +486,16 @@ requires (cat == connack) { return std::make_pair(valid_codes, len); } +template +inline std::pair valid_codes() +requires (cat == auth) { + static reason_code valid_codes[] = { + success, continue_authentication + }; + static size_t len = sizeof(valid_codes) / sizeof(reason_code); + return std::make_pair(valid_codes, len); +} + template inline std::pair valid_codes() requires (cat == puback || cat == pubrec) { diff --git a/include/async_mqtt5/impl/assemble_op.hpp b/include/async_mqtt5/impl/assemble_op.hpp index 310a0fb..4f4e892 100644 --- a/include/async_mqtt5/impl/assemble_op.hpp +++ b/include/async_mqtt5/impl/assemble_op.hpp @@ -95,6 +95,9 @@ public: }; if (cc(error_code {}, 0) == 0 && _data_span.size()) { + /* TODO clear read buffer on reconnect + * OR use dispatch instead of post here + */ return asio::post( asio::prepend( std::move(*this), on_read {}, error_code {}, @@ -128,16 +131,16 @@ public: } if (ec) - return complete(ec, 0, 0, {}, {}); + return complete(ec, 0, {}, {}); _data_span.expand_suffix(bytes_read); assert(_data_span.size()); - auto control_code = uint8_t(*_data_span.first()); + auto control_byte = uint8_t(*_data_span.first()); - if ((control_code & 0b11110000) == 0) + if ((control_byte & 0b11110000) == 0) // close the connection, cancel - return complete(client::error::malformed_packet, 0, 0, {}, {}); + return complete(client::error::malformed_packet, 0, {}, {}); auto first = _data_span.first() + 1; auto varlen = decoders::type_parse( @@ -147,12 +150,12 @@ public: if (!varlen) { if (_data_span.size() < 5) return perform(wait_for, asio::transfer_at_least(1)); - return complete(client::error::malformed_packet, 0, 0, {}, {}); + return complete(client::error::malformed_packet, 0, {}, {}); } // TODO: respect max packet size which could be dinamically set by the broker if (*varlen > max_packet_size - std::distance(_data_span.first(), first)) - return complete(client::error::malformed_packet, 0, 0, {}, {}); + return complete(client::error::malformed_packet, 0, {}, {}); if (std::distance(first, _data_span.last()) < *varlen) return perform(wait_for, asio::transfer_at_least(1)); @@ -161,7 +164,7 @@ public: std::distance(_data_span.first(), first) + *varlen ); - dispatch(wait_for, control_code, first, first + *varlen); + dispatch(wait_for, control_byte, first, first + *varlen); } private: @@ -179,51 +182,39 @@ private: return res == 0b00000000; } - static bool contains_packet_id(control_code_e code) { - using enum control_code_e; - - return code == puback || code == pubrec - || code == pubrel || code == pubcomp - || code == subscribe || code == suback - || code == unsubscribe || code == unsuback; - } - void dispatch( duration wait_for, - uint8_t control_code, byte_citer first, byte_citer last + uint8_t control_byte, byte_citer first, byte_citer last ) { using namespace decoders; using enum control_code_e; - if (!valid_header(control_code)) - return complete(client::error::malformed_packet, 0, 0, {}, {}); + if (!valid_header(control_byte)) + return complete(client::error::malformed_packet, 0, {}, {}); - auto code = control_code_e(control_code & 0b11110000); + auto code = control_code_e(control_byte & 0b11110000); if (code == pingresp) return perform(wait_for, asio::transfer_at_least(0)); - uint16_t packet_id = 0; - if (contains_packet_id(code)) - packet_id = decoders::decode_packet_id(first).value(); - bool is_reply = code != publish && code != auth && code != 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(wait_for, asio::transfer_at_least(0)); } - complete(error_code {}, packet_id, control_code, first, last); + complete(error_code {}, control_byte, first, last); } void complete( - error_code ec, uint16_t packet_id, uint8_t control_code, + error_code ec, uint8_t control_code, byte_citer first, byte_citer last ) { asio::dispatch( get_executor(), asio::prepend( - std::move(_handler), ec, packet_id, control_code, + std::move(_handler), ec, control_code, first, last ) ); diff --git a/include/async_mqtt5/impl/async_sender.hpp b/include/async_mqtt5/impl/async_sender.hpp index 2939ace..1a5360c 100644 --- a/include/async_mqtt5/impl/async_sender.hpp +++ b/include/async_mqtt5/impl/async_sender.hpp @@ -30,7 +30,7 @@ public: _handler(std::move(handler)) {} static serial_num_t next_serial_num(serial_num_t last) { - return ++last; + return last + 1; } asio::const_buffer buffer() const { return _buffer; } diff --git a/include/async_mqtt5/impl/client_service.hpp b/include/async_mqtt5/impl/client_service.hpp index 0345d5e..6ed9949 100644 --- a/include/async_mqtt5/impl/client_service.hpp +++ b/include/async_mqtt5/impl/client_service.hpp @@ -57,6 +57,13 @@ public: std::move(username), std::move(password) }; } + + template + void authenticator(Authenticator&& authenticator) { + _mqtt_context.authenticator = any_authenticator( + std::forward(authenticator) + ); + } }; template @@ -88,6 +95,13 @@ public: std::move(username), std::move(password) }; } + + template + void authenticator(Authenticator&& authenticator) { + _mqtt_context.authenticator = any_authenticator( + std::forward(authenticator) + ); + } }; template < @@ -177,6 +191,13 @@ public: _stream.brokers(std::move(hosts), default_port); } + template + void authenticator(Authenticator&& authenticator) { + _stream_context.authenticator( + std::forward(authenticator) + ); + } + template auto connack_prop(Prop p) { return _stream_context.connack_prop(p); @@ -243,10 +264,10 @@ public: }.perform(wait_for, asio::transfer_at_least(0)); }; - using signature = void ( - error_code, uint16_t, uint8_t, byte_citer, byte_citer + using Signature = void ( + error_code, uint8_t, byte_citer, byte_citer ); - return asio::async_initiate ( + return asio::async_initiate ( std::move(initiation), token, wait_for ); } diff --git a/include/async_mqtt5/impl/connect_op.hpp b/include/async_mqtt5/impl/connect_op.hpp index 692b26f..c379d2b 100644 --- a/include/async_mqtt5/impl/connect_op.hpp +++ b/include/async_mqtt5/impl/connect_op.hpp @@ -28,12 +28,18 @@ template < typename Stream, typename Handler > 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_connack {}; + struct on_read_packet {}; + struct on_init_auth_data {}; + struct on_auth_data {}; + struct on_send_auth {}; + struct on_complete_auth {}; Stream& _stream; mqtt_context& _ctx; @@ -152,13 +158,30 @@ public: ); } else - send_connect(); + (*this)(on_ws_handshake {}, error_code {}); } void operator()(on_ws_handshake, error_code ec) { 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 (ec) + return complete(asio::error::try_again); + + _ctx.co_props[prop::authentication_data] = std::move(data); send_connect(); } @@ -186,10 +209,9 @@ public: if (ec) return complete(ec); - constexpr size_t min_connack_sz = 5; - _buffer_ptr = std::make_unique(min_connack_sz, 0); + _buffer_ptr = std::make_unique(min_packet_sz, 0); - auto buff = asio::buffer(_buffer_ptr->data(), min_connack_sz); + auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz); asio::async_read( _stream, buff, asio::prepend(std::move(*this), on_fixed_header {}) @@ -202,8 +224,9 @@ public: if (ec) return complete(ec); - auto control_byte = (*_buffer_ptr)[0]; - if (control_byte != 0b00100000) + 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; @@ -217,7 +240,8 @@ public: auto remain_len = *varlen - std::distance(varlen_ptr, _buffer_ptr->cbegin() + num_read); - _buffer_ptr->resize(_buffer_ptr->size() + remain_len); + 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; @@ -227,21 +251,33 @@ public: _stream, buff, asio::prepend( asio::append( - std::move(*this), uint8_t(control_byte), first, last - ), on_read_connack {} + std::move(*this), code, first, last + ), on_read_packet {} ) ); } void operator()( - on_read_connack, error_code ec, size_t, uint8_t control_code, + on_read_packet, error_code ec, size_t, control_code_e code, byte_citer first, byte_citer last ) { 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 = 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; @@ -257,7 +293,84 @@ public: if (!rc.has_value()) // reason code not allowed in CONNACK return complete(client::error::malformed_packet); - complete(to_asio_error(*rc)); + auto ec = to_asio_error(*rc); + if (ec) + return complete(ec); + + 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 = std::distance(first, last); + auto rv = decoders::decode_auth(packet_length, first); + if (!rv.has_value()) + return complete(client::error::malformed_packet); + const auto& [reason_code, auth_props] = *rv; + + auto rc = to_reason_code(reason_code); + if ( + !rc.has_value() || + auth_props[prop::authentication_method] + != _ctx.co_props[prop::authentication_method] + ) + return complete(client::error::malformed_packet); + + _ctx.authenticator.async_auth( + auth_step_e::server_challenge, + auth_props[prop::authentication_data].value_or(""), + asio::prepend(std::move(*this), on_auth_data {}) + ); + } + + void operator()(on_auth_data, error_code ec, std::string data) { + if (ec) + return complete(asio::error::try_again); + + auth_props props; + props[prop::authentication_method] = + _ctx.co_props[prop::authentication_method]; + props[prop::authentication_data] = std::move(data); + + auto packet = control_packet::of( + no_pid, get_allocator(), + encoders::encode_auth, + reason_codes::continue_authentication.value(), props + ); + + const auto& wire_data = packet.wire_data(); + + async_mqtt5::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 (ec) + return complete(ec); + + auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz); + asio::async_read( + _stream, buff, + asio::prepend(std::move(*this), on_fixed_header {}) + ); + } + + void operator()(on_complete_auth, error_code ec, std::string) { + if (ec) + return complete(asio::error::try_again); + + complete(error_code {}); } private: diff --git a/include/async_mqtt5/impl/read_message_op.hpp b/include/async_mqtt5/impl/read_message_op.hpp index fddb544..3fccb2d 100644 --- a/include/async_mqtt5/impl/read_message_op.hpp +++ b/include/async_mqtt5/impl/read_message_op.hpp @@ -55,7 +55,7 @@ public: void operator()( on_message, error_code ec, - uint16_t packet_id, uint8_t control_code, + uint8_t control_code, byte_citer first, byte_citer last ) { if (ec == client::error::malformed_packet) @@ -69,19 +69,17 @@ public: ) return; - dispatch(ec, packet_id, control_code, first, last); + dispatch(control_code, first, last); } void operator()(on_disconnect, error_code ec) { - if (!ec || ec == asio::error::try_again) + if (!ec) perform(); } private: - - // TODO: ec & packet_id are not used here void dispatch( - error_code ec, uint16_t packet_id, uint8_t control_byte, + uint8_t control_byte, byte_citer first, byte_citer last ) { using enum control_code_e; diff --git a/include/async_mqtt5/impl/sentry_op.hpp b/include/async_mqtt5/impl/sentry_op.hpp index 9881d2f..94a307f 100644 --- a/include/async_mqtt5/impl/sentry_op.hpp +++ b/include/async_mqtt5/impl/sentry_op.hpp @@ -83,7 +83,7 @@ public: void operator()(on_disconnect, error_code ec) { get_cancellation_slot().clear(); - if (!ec || ec == asio::error::try_again) + if (!ec) perform(); } }; diff --git a/include/async_mqtt5/mqtt_client.hpp b/include/async_mqtt5/mqtt_client.hpp index 1c926b4..dc576b3 100644 --- a/include/async_mqtt5/mqtt_client.hpp +++ b/include/async_mqtt5/mqtt_client.hpp @@ -55,7 +55,7 @@ public: explicit mqtt_client( const executor_type& ex, const std::string& cnf, - tls_context_type tls_context = {} + TlsContext tls_context = {} ) : _svc_ptr(std::make_shared( ex, cnf, std::move(tls_context) @@ -216,6 +216,39 @@ public: 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 async_authenticate. + * + * \param authenticator Object that will be stored (move-constructed or by reference) + * and used for authentication. It needs to satisfy \__is_authenticator\__ concept. + * + */ + template + mqtt_client& authenticator(Authenticator&& authenticator) { + _svc_ptr->authenticator(std::forward(authenticator)); + return *this; + } + + /** + * \brief Initiates re-authentication. + * TODO + */ + template + decltype(auto) async_authenticate(CompletionToken&& token) { + using Signature = void(error_code); + + auto initiate = [] ( + auto handler + ) { + // TODO re-authentication + }; + + return asio::async_initiate( + std::move(initiate), token + ); + } /** * \brief Send a \__PUBLISH\__ packet to Broker to transport an @@ -228,7 +261,7 @@ public: * \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 is completed. + * 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\__. * @@ -313,7 +346,7 @@ public: * \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 is completed. + * 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\__. * @@ -373,7 +406,7 @@ public: * \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 is completed. + * 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\__. * @@ -421,7 +454,7 @@ public: * \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 is completed. + * 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\__. * @@ -480,7 +513,7 @@ public: * \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 is completed. + * 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\__. * @@ -531,7 +564,7 @@ public: * or there is a pending Application Message. * * \param token Completion token that will be used to produce a - * completion handler. The handler will be invoked when the operation is completed. + * 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\__. * @@ -574,7 +607,7 @@ public: * the Broker of the reason for 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 is completed. + * 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\__. * @@ -616,7 +649,7 @@ public: * 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 is completed. + * 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\__. * diff --git a/include/async_mqtt5/types.hpp b/include/async_mqtt5/types.hpp index 79c1c82..2584a17 100644 --- a/include/async_mqtt5/types.hpp +++ b/include/async_mqtt5/types.hpp @@ -61,6 +61,23 @@ enum class dup_e : std::uint8_t { yes = 0b1, no = 0b0 }; +/** + * \brief Represents the stage of \__ENHANCED_AUTH\__ process. + */ +enum class auth_step_e { + /** Client needs to send initial authentication data. */ + client_initial, + + /** Server responded with reason_codes.continue_authentication and possibly + * authentication data, client needs to send further authentication data. + */ + server_challenge, + + /** Server responded with reason_codes.success and final + * authentication data, which client validates. + */ + server_final +}; /** * \brief Represents the \__SUBSCRIBE_OPTIONS\__ associated with each Subscription.