[mqtt-client] add support for enhanced authentication

Summary:
- Relates to T12899
- TODO: support re-authentication

Reviewers: ivica

Reviewed By: ivica

Subscribers: korina

Maniphest Tasks: T12899

Differential Revision: https://repo.mireo.local/D26327
This commit is contained in:
Bruno Iljazovic
2023-11-03 08:38:28 +01:00
parent 57349c587b
commit 7e60e7a919
17 changed files with 392 additions and 64 deletions

View File

@ -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
;

View File

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

View File

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

View File

@ -27,6 +27,7 @@
<member><link linkend="async_mqtt5.ref.ExecutionContext">ExecutionContext</link></member>
<member><link linkend="async_mqtt5.ref.StreamType">StreamType</link></member>
<member><link linkend="async_mqtt5.ref.TlsContext">TlsContext</link></member>
<member><link linkend="async_mqtt5.ref.is_authenticator">is_authenticator</link></member>
</simplelist>
</entry>
<entry valign="top">
@ -36,6 +37,7 @@
</simplelist>
<bridgehead renderas="sect3">Enumerations</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.auth_step_e">auth_step_e</link></member>
<member><link linkend="async_mqtt5.ref.client.error">client_error</link></member>
<member><link linkend="async_mqtt5.ref.disconnect_rc_e">disconnect_rc_e</link></member>
<member><link linkend="async_mqtt5.ref.qos_e">qos_e</link></member>

View File

@ -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 =

View File

@ -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 @@
<xsl:when test="contains($qualified-name, 'ExecutionContext')">ExecutionContext</xsl:when>
<xsl:when test="contains($qualified-name, 'StreamType')">StreamType</xsl:when>
<xsl:when test="contains($qualified-name, 'TlsContext')">TlsContext</xsl:when>
<xsl:when test="contains($qualified-name, 'is_authenticator')">is_authenticator</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
@ -1531,7 +1533,8 @@
</xsl:when>
<!-- unfortunately, there is no better way to differentiate between template types and non-documented types -->
<xsl:when test="contains(type, 'CompletionToken') or contains(type, 'ExecutionContext')
or contains(type, 'TlsContext') or contains(type, 'StreamType')">
or contains(type, 'TlsContext') or contains(type, 'StreamType')
or contains(type, 'is_authenticator')">
<xsl:call-template name="mqtt-template">
<xsl:with-param name="qualified-name" select="$type"/>
</xsl:call-template>

View File

@ -0,0 +1,108 @@
#ifndef ASYNC_MQTT5_ANY_AUTHENTICATOR
#define ASYNC_MQTT5_ANY_AUTHENTICATOR
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/async_result.hpp>
#include <async_mqtt5/types.hpp>
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 <typename T>
concept is_authenticator = requires (T a) {
{
a.async_auth(auth_step_e {}, std::string {}, auth_handler_type {})
} -> std::same_as<void>;
{ a.method() } -> std::same_as<std::string_view>;
};
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 <is_authenticator Authenticator>
class auth_fun : public auth_fun_base {
Authenticator _authenticator;
public:
auth_fun(Authenticator authenticator) :
auth_fun_base(&async_auth),
_authenticator(std::forward<Authenticator>(authenticator))
{}
static void async_auth(
auth_step_e step, std::string data, auth_handler_type auth_handler,
auth_fun_base* base_ptr
) {
auto auth_fun_ptr = static_cast<auth_fun*>(base_ptr);
auth_fun_ptr->_authenticator.async_auth(
step, std::move(data), std::move(auth_handler)
);
}
};
} // end namespace detail
class any_authenticator {
std::string _method;
std::unique_ptr<detail::auth_fun_base> _auth_fun;
public:
any_authenticator() = default;
template <detail::is_authenticator Authenticator>
any_authenticator(Authenticator&& a) :
_method(a.method()),
_auth_fun(
new detail::auth_fun<Authenticator>(
std::forward<Authenticator>(a)
)
)
{}
std::string_view method() const {
return _method;
}
template <typename CompletionToken>
decltype(auto) async_auth(
auth_step_e step, std::string data,
CompletionToken&& token
) {
using Signature = void(error_code, std::string);
auto 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<CompletionToken, Signature>(
initiate, token, step, std::move(data)
);
}
};
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_ANY_AUTHENTICATOR

View File

@ -4,6 +4,8 @@
#include <optional>
#include <string>
#include <async_mqtt5/detail/any_authenticator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/types.hpp>
@ -38,6 +40,7 @@ struct mqtt_context {
std::optional<will> will;
connect_props co_props;
connack_props ca_props;
any_authenticator authenticator;
};
struct disconnect_context {

View File

@ -486,6 +486,16 @@ requires (cat == connack) {
return std::make_pair(valid_codes, len);
}
template <category cat>
inline std::pair<reason_code*, size_t> 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 <category cat>
inline std::pair<reason_code*, size_t> valid_codes()
requires (cat == puback || cat == pubrec) {

View File

@ -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
)
);

View File

@ -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; }

View File

@ -57,6 +57,13 @@ public:
std::move(username), std::move(password)
};
}
template <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <typename StreamType>
@ -88,6 +95,13 @@ public:
std::move(username), std::move(password)
};
}
template <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <
@ -177,6 +191,13 @@ public:
_stream.brokers(std::move(hosts), default_port);
}
template <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_stream_context.authenticator(
std::forward<Authenticator>(authenticator)
);
}
template <typename Prop>
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<CompletionToken, signature> (
return asio::async_initiate<CompletionToken, Signature> (
std::move(initiation), token, wait_for
);
}

View File

@ -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<std::string>(min_connack_sz, 0);
_buffer_ptr = std::make_unique<std::string>(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_codes::category::auth>(reason_code);
if (
!rc.has_value() ||
auth_props[prop::authentication_method]
!= _ctx.co_props[prop::authentication_method]
)
return complete(client::error::malformed_packet);
_ctx.authenticator.async_auth(
auth_step_e::server_challenge,
auth_props[prop::authentication_data].value_or(""),
asio::prepend(std::move(*this), on_auth_data {})
);
}
void operator()(on_auth_data, error_code ec, std::string data) {
if (ec)
return complete(asio::error::try_again);
auth_props props;
props[prop::authentication_method] =
_ctx.co_props[prop::authentication_method];
props[prop::authentication_data] = std::move(data);
auto packet = control_packet<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_auth,
reason_codes::continue_authentication.value(), props
);
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:

View File

@ -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;

View File

@ -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();
}
};

View File

@ -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<client_service_type>(
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 <detail::is_authenticator Authenticator>
mqtt_client& authenticator(Authenticator&& authenticator) {
_svc_ptr->authenticator(std::forward<Authenticator>(authenticator));
return *this;
}
/**
* \brief Initiates re-authentication.
* TODO
*/
template <typename CompletionToken>
decltype(auto) async_authenticate(CompletionToken&& token) {
using Signature = void(error_code);
auto initiate = [] (
auto handler
) {
// TODO re-authentication
};
return asio::async_initiate<CompletionToken, Signature>(
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\__.
*

View File

@ -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.