mirror of
https://github.com/boostorg/mqtt5.git
synced 2025-07-30 12:37:36 +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/types.hpp
|
||||
../include/async_mqtt5/mqtt_client.hpp
|
||||
../include/async_mqtt5/detail/any_authenticator.hpp
|
||||
:
|
||||
@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]]]
|
||||
|
||||
[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`]]
|
||||
|
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.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>
|
||||
|
@ -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 =
|
||||
|
@ -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>
|
||||
|
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 <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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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\__.
|
||||
*
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user