mirror of
https://github.com/boostorg/mqtt5.git
synced 2025-07-31 04:57:33 +02:00
[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:
@ -34,6 +34,7 @@ make xml/index.xml
|
|||||||
../include/async_mqtt5/error.hpp
|
../include/async_mqtt5/error.hpp
|
||||||
../include/async_mqtt5/types.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
|
||||||
:
|
:
|
||||||
@call-doxygen
|
@call-doxygen
|
||||||
;
|
;
|
||||||
|
@ -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]]]
|
[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 __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 __ExecutionContext__ [reflink ExecutionContext]]
|
||||||
[def __StreamType__ [reflink2 StreamType StreamType]]
|
[def __StreamType__ [reflink StreamType]]
|
||||||
[def __TlsContext__ [reflink2 TlsContext TlsContext]]
|
[def __TlsContext__ [reflink TlsContext]]
|
||||||
|
[def __is_authenticator__ [reflink is_authenticator]]
|
||||||
|
|
||||||
[def __Boost__ [@https://www.boost.org/ Boost]]
|
[def __Boost__ [@https://www.boost.org/ Boost]]
|
||||||
[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]]
|
[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]]
|
||||||
@ -55,6 +56,7 @@
|
|||||||
[def __QOS__ [mqttlink 3901234 `QoS`]]
|
[def __QOS__ [mqttlink 3901234 `QoS`]]
|
||||||
[def __RETAIN__ [mqttlink 3901104 `RETAIN`]]
|
[def __RETAIN__ [mqttlink 3901104 `RETAIN`]]
|
||||||
[def __SUBSCRIBE_OPTIONS__ [mqttlink 3901169 `Subscribe Options`]]
|
[def __SUBSCRIBE_OPTIONS__ [mqttlink 3901169 `Subscribe Options`]]
|
||||||
|
[def __ENHANCED_AUTH__ [mqttlink 3901256 `Enhanced Authentication`]]
|
||||||
|
|
||||||
[def __CONNECT__ [mqttlink 3901033 `CONNECT`]]
|
[def __CONNECT__ [mqttlink 3901033 `CONNECT`]]
|
||||||
[def __CONNACK__ [mqttlink 3901074 `CONNACK`]]
|
[def __CONNACK__ [mqttlink 3901074 `CONNACK`]]
|
||||||
|
25
doc/qbk/reference/concepts/is_authenticator.qbk
Normal file
25
doc/qbk/reference/concepts/is_authenticator.qbk
Normal 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]
|
@ -27,6 +27,7 @@
|
|||||||
<member><link linkend="async_mqtt5.ref.ExecutionContext">ExecutionContext</link></member>
|
<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.StreamType">StreamType</link></member>
|
||||||
<member><link linkend="async_mqtt5.ref.TlsContext">TlsContext</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>
|
</simplelist>
|
||||||
</entry>
|
</entry>
|
||||||
<entry valign="top">
|
<entry valign="top">
|
||||||
@ -36,6 +37,7 @@
|
|||||||
</simplelist>
|
</simplelist>
|
||||||
<bridgehead renderas="sect3">Enumerations</bridgehead>
|
<bridgehead renderas="sect3">Enumerations</bridgehead>
|
||||||
<simplelist type="vert" columns="1">
|
<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.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.disconnect_rc_e">disconnect_rc_e</link></member>
|
||||||
<member><link linkend="async_mqtt5.ref.qos_e">qos_e</link></member>
|
<member><link linkend="async_mqtt5.ref.qos_e">qos_e</link></member>
|
||||||
|
@ -71,7 +71,8 @@ WARN_LOGFILE =
|
|||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
INPUT = ../include/async_mqtt5/error.hpp \
|
INPUT = ../include/async_mqtt5/error.hpp \
|
||||||
../include/async_mqtt5/types.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 =
|
FILE_PATTERNS =
|
||||||
RECURSIVE = NO
|
RECURSIVE = NO
|
||||||
EXCLUDE =
|
EXCLUDE =
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
[include concepts/ExecutionContext.qbk]
|
[include concepts/ExecutionContext.qbk]
|
||||||
[include concepts/StreamType.qbk]
|
[include concepts/StreamType.qbk]
|
||||||
[include concepts/TlsContext.qbk]
|
[include concepts/TlsContext.qbk]
|
||||||
|
[include concepts/is_authenticator.qbk]
|
||||||
[include reason_codes/Reason_codes.qbk]
|
[include reason_codes/Reason_codes.qbk]
|
||||||
[include properties/will_props.qbk]
|
[include properties/will_props.qbk]
|
||||||
[include properties/connect_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, 'ExecutionContext')">ExecutionContext</xsl:when>
|
||||||
<xsl:when test="contains($qualified-name, 'StreamType')">StreamType</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, 'TlsContext')">TlsContext</xsl:when>
|
||||||
|
<xsl:when test="contains($qualified-name, 'is_authenticator')">is_authenticator</xsl:when>
|
||||||
<xsl:otherwise></xsl:otherwise>
|
<xsl:otherwise></xsl:otherwise>
|
||||||
</xsl:choose>
|
</xsl:choose>
|
||||||
</xsl:variable>
|
</xsl:variable>
|
||||||
@ -1531,7 +1533,8 @@
|
|||||||
</xsl:when>
|
</xsl:when>
|
||||||
<!-- unfortunately, there is no better way to differentiate between template types and non-documented types -->
|
<!-- 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')
|
<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:call-template name="mqtt-template">
|
||||||
<xsl:with-param name="qualified-name" select="$type"/>
|
<xsl:with-param name="qualified-name" select="$type"/>
|
||||||
</xsl:call-template>
|
</xsl:call-template>
|
||||||
|
108
include/async_mqtt5/detail/any_authenticator.hpp
Normal file
108
include/async_mqtt5/detail/any_authenticator.hpp
Normal 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
|
@ -4,6 +4,8 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <async_mqtt5/detail/any_authenticator.hpp>
|
||||||
|
|
||||||
#include <async_mqtt5/error.hpp>
|
#include <async_mqtt5/error.hpp>
|
||||||
#include <async_mqtt5/types.hpp>
|
#include <async_mqtt5/types.hpp>
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ struct mqtt_context {
|
|||||||
std::optional<will> will;
|
std::optional<will> will;
|
||||||
connect_props co_props;
|
connect_props co_props;
|
||||||
connack_props ca_props;
|
connack_props ca_props;
|
||||||
|
any_authenticator authenticator;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct disconnect_context {
|
struct disconnect_context {
|
||||||
|
@ -486,6 +486,16 @@ requires (cat == connack) {
|
|||||||
return std::make_pair(valid_codes, len);
|
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>
|
template <category cat>
|
||||||
inline std::pair<reason_code*, size_t> valid_codes()
|
inline std::pair<reason_code*, size_t> valid_codes()
|
||||||
requires (cat == puback || cat == pubrec) {
|
requires (cat == puback || cat == pubrec) {
|
||||||
|
@ -95,6 +95,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (cc(error_code {}, 0) == 0 && _data_span.size()) {
|
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(
|
return asio::post(
|
||||||
asio::prepend(
|
asio::prepend(
|
||||||
std::move(*this), on_read {}, error_code {},
|
std::move(*this), on_read {}, error_code {},
|
||||||
@ -128,16 +131,16 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ec)
|
if (ec)
|
||||||
return complete(ec, 0, 0, {}, {});
|
return complete(ec, 0, {}, {});
|
||||||
|
|
||||||
_data_span.expand_suffix(bytes_read);
|
_data_span.expand_suffix(bytes_read);
|
||||||
assert(_data_span.size());
|
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
|
// 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 first = _data_span.first() + 1;
|
||||||
auto varlen = decoders::type_parse(
|
auto varlen = decoders::type_parse(
|
||||||
@ -147,12 +150,12 @@ public:
|
|||||||
if (!varlen) {
|
if (!varlen) {
|
||||||
if (_data_span.size() < 5)
|
if (_data_span.size() < 5)
|
||||||
return perform(wait_for, asio::transfer_at_least(1));
|
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
|
// TODO: respect max packet size which could be dinamically set by the broker
|
||||||
if (*varlen > max_packet_size - std::distance(_data_span.first(), first))
|
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)
|
if (std::distance(first, _data_span.last()) < *varlen)
|
||||||
return perform(wait_for, asio::transfer_at_least(1));
|
return perform(wait_for, asio::transfer_at_least(1));
|
||||||
@ -161,7 +164,7 @@ public:
|
|||||||
std::distance(_data_span.first(), first) + *varlen
|
std::distance(_data_span.first(), first) + *varlen
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(wait_for, control_code, first, first + *varlen);
|
dispatch(wait_for, control_byte, first, first + *varlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -179,51 +182,39 @@ private:
|
|||||||
return res == 0b00000000;
|
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(
|
void dispatch(
|
||||||
duration wait_for,
|
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 namespace decoders;
|
||||||
using enum control_code_e;
|
using enum control_code_e;
|
||||||
|
|
||||||
if (!valid_header(control_code))
|
if (!valid_header(control_byte))
|
||||||
return complete(client::error::malformed_packet, 0, 0, {}, {});
|
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)
|
if (code == pingresp)
|
||||||
return perform(wait_for, asio::transfer_at_least(0));
|
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;
|
bool is_reply = code != publish && code != auth && code != disconnect;
|
||||||
if (is_reply) {
|
if (is_reply) {
|
||||||
|
auto packet_id = decoders::decode_packet_id(first).value();
|
||||||
_svc._replies.dispatch(error_code {}, code, packet_id, first, last);
|
_svc._replies.dispatch(error_code {}, code, packet_id, first, last);
|
||||||
return perform(wait_for, asio::transfer_at_least(0));
|
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(
|
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
|
byte_citer first, byte_citer last
|
||||||
) {
|
) {
|
||||||
asio::dispatch(
|
asio::dispatch(
|
||||||
get_executor(),
|
get_executor(),
|
||||||
asio::prepend(
|
asio::prepend(
|
||||||
std::move(_handler), ec, packet_id, control_code,
|
std::move(_handler), ec, control_code,
|
||||||
first, last
|
first, last
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,7 @@ public:
|
|||||||
_handler(std::move(handler)) {}
|
_handler(std::move(handler)) {}
|
||||||
|
|
||||||
static serial_num_t next_serial_num(serial_num_t last) {
|
static serial_num_t next_serial_num(serial_num_t last) {
|
||||||
return ++last;
|
return last + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::const_buffer buffer() const { return _buffer; }
|
asio::const_buffer buffer() const { return _buffer; }
|
||||||
|
@ -57,6 +57,13 @@ public:
|
|||||||
std::move(username), std::move(password)
|
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>
|
template <typename StreamType>
|
||||||
@ -88,6 +95,13 @@ public:
|
|||||||
std::move(username), std::move(password)
|
std::move(username), std::move(password)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Authenticator>
|
||||||
|
void authenticator(Authenticator&& authenticator) {
|
||||||
|
_mqtt_context.authenticator = any_authenticator(
|
||||||
|
std::forward<Authenticator>(authenticator)
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
@ -177,6 +191,13 @@ public:
|
|||||||
_stream.brokers(std::move(hosts), default_port);
|
_stream.brokers(std::move(hosts), default_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Authenticator>
|
||||||
|
void authenticator(Authenticator&& authenticator) {
|
||||||
|
_stream_context.authenticator(
|
||||||
|
std::forward<Authenticator>(authenticator)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Prop>
|
template <typename Prop>
|
||||||
auto connack_prop(Prop p) {
|
auto connack_prop(Prop p) {
|
||||||
return _stream_context.connack_prop(p);
|
return _stream_context.connack_prop(p);
|
||||||
@ -243,10 +264,10 @@ public:
|
|||||||
}.perform(wait_for, asio::transfer_at_least(0));
|
}.perform(wait_for, asio::transfer_at_least(0));
|
||||||
};
|
};
|
||||||
|
|
||||||
using signature = void (
|
using Signature = void (
|
||||||
error_code, uint16_t, uint8_t, byte_citer, byte_citer
|
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
|
std::move(initiation), token, wait_for
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,18 @@ template <
|
|||||||
typename Stream, typename Handler
|
typename Stream, typename Handler
|
||||||
>
|
>
|
||||||
class connect_op {
|
class connect_op {
|
||||||
|
static constexpr size_t min_packet_sz = 5;
|
||||||
|
|
||||||
struct on_connect {};
|
struct on_connect {};
|
||||||
struct on_tls_handshake {};
|
struct on_tls_handshake {};
|
||||||
struct on_ws_handshake {};
|
struct on_ws_handshake {};
|
||||||
struct on_send_connect {};
|
struct on_send_connect {};
|
||||||
struct on_fixed_header {};
|
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;
|
Stream& _stream;
|
||||||
mqtt_context& _ctx;
|
mqtt_context& _ctx;
|
||||||
@ -152,13 +158,30 @@ public:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
send_connect();
|
(*this)(on_ws_handshake {}, error_code {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(on_ws_handshake, error_code ec) {
|
void operator()(on_ws_handshake, error_code ec) {
|
||||||
if (ec)
|
if (ec)
|
||||||
return complete(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();
|
send_connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,10 +209,9 @@ public:
|
|||||||
if (ec)
|
if (ec)
|
||||||
return complete(ec);
|
return complete(ec);
|
||||||
|
|
||||||
constexpr size_t min_connack_sz = 5;
|
_buffer_ptr = std::make_unique<std::string>(min_packet_sz, 0);
|
||||||
_buffer_ptr = std::make_unique<std::string>(min_connack_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(
|
asio::async_read(
|
||||||
_stream, buff,
|
_stream, buff,
|
||||||
asio::prepend(std::move(*this), on_fixed_header {})
|
asio::prepend(std::move(*this), on_fixed_header {})
|
||||||
@ -202,8 +224,9 @@ public:
|
|||||||
if (ec)
|
if (ec)
|
||||||
return complete(ec);
|
return complete(ec);
|
||||||
|
|
||||||
auto control_byte = (*_buffer_ptr)[0];
|
auto code = control_code_e((*_buffer_ptr)[0] & 0b11110000);
|
||||||
if (control_byte != 0b00100000)
|
|
||||||
|
if (code != control_code_e::auth && code != control_code_e::connack)
|
||||||
return complete(asio::error::try_again);
|
return complete(asio::error::try_again);
|
||||||
|
|
||||||
auto varlen_ptr = _buffer_ptr->cbegin() + 1;
|
auto varlen_ptr = _buffer_ptr->cbegin() + 1;
|
||||||
@ -217,7 +240,8 @@ public:
|
|||||||
auto remain_len = *varlen -
|
auto remain_len = *varlen -
|
||||||
std::distance(varlen_ptr, _buffer_ptr->cbegin() + num_read);
|
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 buff = asio::buffer(_buffer_ptr->data() + num_read, remain_len);
|
||||||
auto first = _buffer_ptr->cbegin() + varlen_sz + 1;
|
auto first = _buffer_ptr->cbegin() + varlen_sz + 1;
|
||||||
@ -227,21 +251,33 @@ public:
|
|||||||
_stream, buff,
|
_stream, buff,
|
||||||
asio::prepend(
|
asio::prepend(
|
||||||
asio::append(
|
asio::append(
|
||||||
std::move(*this), uint8_t(control_byte), first, last
|
std::move(*this), code, first, last
|
||||||
), on_read_connack {}
|
), on_read_packet {}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(
|
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
|
byte_citer first, byte_citer last
|
||||||
) {
|
) {
|
||||||
if (ec)
|
if (ec)
|
||||||
return complete(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 packet_length = std::distance(first, last);
|
||||||
auto rv = decoders::decode_connack(packet_length, first);
|
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;
|
const auto& [session_present, reason_code, ca_props] = *rv;
|
||||||
|
|
||||||
_ctx.ca_props = ca_props;
|
_ctx.ca_props = ca_props;
|
||||||
@ -257,7 +293,84 @@ public:
|
|||||||
if (!rc.has_value()) // reason code not allowed in CONNACK
|
if (!rc.has_value()) // reason code not allowed in CONNACK
|
||||||
return complete(client::error::malformed_packet);
|
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:
|
private:
|
||||||
|
@ -55,7 +55,7 @@ public:
|
|||||||
|
|
||||||
void operator()(
|
void operator()(
|
||||||
on_message, error_code ec,
|
on_message, error_code ec,
|
||||||
uint16_t packet_id, uint8_t control_code,
|
uint8_t control_code,
|
||||||
byte_citer first, byte_citer last
|
byte_citer first, byte_citer last
|
||||||
) {
|
) {
|
||||||
if (ec == client::error::malformed_packet)
|
if (ec == client::error::malformed_packet)
|
||||||
@ -69,19 +69,17 @@ public:
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dispatch(ec, packet_id, control_code, first, last);
|
dispatch(control_code, first, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(on_disconnect, error_code ec) {
|
void operator()(on_disconnect, error_code ec) {
|
||||||
if (!ec || ec == asio::error::try_again)
|
if (!ec)
|
||||||
perform();
|
perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// TODO: ec & packet_id are not used here
|
|
||||||
void dispatch(
|
void dispatch(
|
||||||
error_code ec, uint16_t packet_id, uint8_t control_byte,
|
uint8_t control_byte,
|
||||||
byte_citer first, byte_citer last
|
byte_citer first, byte_citer last
|
||||||
) {
|
) {
|
||||||
using enum control_code_e;
|
using enum control_code_e;
|
||||||
|
@ -83,7 +83,7 @@ public:
|
|||||||
void operator()(on_disconnect, error_code ec) {
|
void operator()(on_disconnect, error_code ec) {
|
||||||
get_cancellation_slot().clear();
|
get_cancellation_slot().clear();
|
||||||
|
|
||||||
if (!ec || ec == asio::error::try_again)
|
if (!ec)
|
||||||
perform();
|
perform();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -55,7 +55,7 @@ public:
|
|||||||
explicit mqtt_client(
|
explicit mqtt_client(
|
||||||
const executor_type& ex,
|
const executor_type& ex,
|
||||||
const std::string& cnf,
|
const std::string& cnf,
|
||||||
tls_context_type tls_context = {}
|
TlsContext tls_context = {}
|
||||||
) :
|
) :
|
||||||
_svc_ptr(std::make_shared<client_service_type>(
|
_svc_ptr(std::make_shared<client_service_type>(
|
||||||
ex, cnf, std::move(tls_context)
|
ex, cnf, std::move(tls_context)
|
||||||
@ -216,6 +216,39 @@ public:
|
|||||||
return *this;
|
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
|
* \brief Send a \__PUBLISH\__ packet to Broker to transport an
|
||||||
@ -228,7 +261,7 @@ public:
|
|||||||
* \param retain The \ref retain_e flag.
|
* \param retain The \ref retain_e flag.
|
||||||
* \param props An instance of \__PUBLISH_PROPS\__.
|
* \param props An instance of \__PUBLISH_PROPS\__.
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -313,7 +346,7 @@ public:
|
|||||||
* \param topics A list of \ref subscribe_topic of interest.
|
* \param topics A list of \ref subscribe_topic of interest.
|
||||||
* \param props An instance of \__SUBSCRIBE_PROPS\__.
|
* \param props An instance of \__SUBSCRIBE_PROPS\__.
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -373,7 +406,7 @@ public:
|
|||||||
* \param topic A \ref subscribe_topic of interest.
|
* \param topic A \ref subscribe_topic of interest.
|
||||||
* \param props An instance of \__SUBSCRIBE_PROPS\__.
|
* \param props An instance of \__SUBSCRIBE_PROPS\__.
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -421,7 +454,7 @@ public:
|
|||||||
* \param topics List of Topics to unsubscribe from.
|
* \param topics List of Topics to unsubscribe from.
|
||||||
* \param props An instance of \__UNSUBSCRIBE_PROPS\__.
|
* \param props An instance of \__UNSUBSCRIBE_PROPS\__.
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -480,7 +513,7 @@ public:
|
|||||||
* \param topic Topic to unsubscribe from.
|
* \param topic Topic to unsubscribe from.
|
||||||
* \param props An instance of \__UNSUBSCRIBE_PROPS\__.
|
* \param props An instance of \__UNSUBSCRIBE_PROPS\__.
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -531,7 +564,7 @@ public:
|
|||||||
* or there is a pending Application Message.
|
* or there is a pending Application Message.
|
||||||
*
|
*
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -574,7 +607,7 @@ public:
|
|||||||
* the Broker of the reason for disconnection.
|
* the Broker of the reason for disconnection.
|
||||||
* \param props An instance of \__DISCONNECT_PROPS\__.
|
* \param props An instance of \__DISCONNECT_PROPS\__.
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
@ -616,7 +649,7 @@ public:
|
|||||||
* See \ref mqtt_client::cancel.
|
* See \ref mqtt_client::cancel.
|
||||||
*
|
*
|
||||||
* \param token Completion token that will be used to produce a
|
* \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
|
* On immediate completion, invocation of the handler will be performed in a manner
|
||||||
* equivalent to using \__POST\__.
|
* equivalent to using \__POST\__.
|
||||||
*
|
*
|
||||||
|
@ -61,6 +61,23 @@ enum class dup_e : std::uint8_t {
|
|||||||
yes = 0b1, no = 0b0
|
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.
|
* \brief Represents the \__SUBSCRIBE_OPTIONS\__ associated with each Subscription.
|
||||||
|
Reference in New Issue
Block a user