forked from boostorg/mqtt5
Async.MQTT5 -> Boost.MQTT5
Summary: related to T15996 folder structure include/async_mqtt5 -> include/boost/mqtt5 namespace async_mqtt5 -> namespace boost::mqtt5 all tabs replaced with 4 spaces (because tabs are banned) boost-like order of includes TODO: fix all docs Reviewers: ivica Reviewed By: ivica Subscribers: iljazovic, miljen Differential Revision: https://repo.mireo.local/D33152
This commit is contained in:
1 parent
1225cc778a
commit
afc270f10e
136 files changed
+17834
-17859
No files matched your search
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_HPP
|
||||
#define ASYNC_MQTT5_HPP
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/logger.hpp>
|
||||
#include <async_mqtt5/logger_traits.hpp>
|
||||
#include <async_mqtt5/mqtt_client.hpp>
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#endif // !ASYNC_MQTT5_HPP
|
||||
@@ -1,143 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ANY_AUTHENTICATOR
|
||||
#define ASYNC_MQTT5_ANY_AUTHENTICATOR
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <boost/type_traits/is_detected.hpp>
|
||||
#include <boost/type_traits/is_detected_convertible.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
using auth_handler_type = asio::any_completion_handler<
|
||||
void (error_code, std::string)
|
||||
>;
|
||||
|
||||
template <typename T, typename ...Ts>
|
||||
using async_auth_sig = decltype(
|
||||
std::declval<T>().async_auth(std::declval<Ts>()...)
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
using method_sig = decltype(
|
||||
std::declval<T>().method()
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_authenticator =
|
||||
boost::is_detected<
|
||||
async_auth_sig, T,
|
||||
auth_step_e, std::string, auth_handler_type
|
||||
>::value &&
|
||||
boost::is_detected_convertible_v<std::string_view, method_sig, T>;
|
||||
|
||||
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 <
|
||||
typename Authenticator,
|
||||
typename = std::enable_if_t<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)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class any_authenticator {
|
||||
std::string _method;
|
||||
std::shared_ptr<detail::auth_fun_base> _auth_fun;
|
||||
|
||||
public:
|
||||
any_authenticator() = default;
|
||||
|
||||
template <
|
||||
typename Authenticator,
|
||||
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
|
||||
>
|
||||
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 initiation = [](
|
||||
auto handler, any_authenticator& self,
|
||||
auth_step_e step, std::string data
|
||||
) {
|
||||
self._auth_fun->async_auth(
|
||||
step, std::move(data), std::move(handler)
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), step, std::move(data)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
|
||||
#endif // !ASYNC_MQTT5_ANY_AUTHENTICATOR
|
||||
@@ -1,218 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ASYNC_MUTEX_HPP
|
||||
#define ASYNC_MQTT5_ASYNC_MUTEX_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/execution.hpp>
|
||||
#include <boost/asio/require.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
class async_mutex {
|
||||
public:
|
||||
using executor_type = asio::any_io_executor;
|
||||
private:
|
||||
using queued_op_t = asio::any_completion_handler<
|
||||
void (error_code)
|
||||
>;
|
||||
using queue_t = std::deque<queued_op_t>;
|
||||
|
||||
// Handler with assigned tracking executor.
|
||||
// Objects of this type are type-erased by any_completion_handler
|
||||
// and stored in the waiting queue.
|
||||
template <typename Handler, typename Executor>
|
||||
class tracked_op {
|
||||
tracking_type<Handler, Executor> _executor;
|
||||
Handler _handler;
|
||||
public:
|
||||
tracked_op(Handler&& h, const Executor& ex) :
|
||||
_executor(tracking_executor(h, ex)), _handler(std::move(h))
|
||||
{}
|
||||
|
||||
tracked_op(tracked_op&&) = default;
|
||||
tracked_op(const tracked_op&) = delete;
|
||||
|
||||
tracked_op& operator=(tracked_op&&) = default;
|
||||
tracked_op& operator=(const tracked_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = tracking_type<Handler, Executor>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
void operator()(error_code ec) {
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
// Per-operation cancellation helper.
|
||||
// It is safe to emit the cancellation signal from any thread
|
||||
// provided there are no other concurrent calls to the async_mutex.
|
||||
// The helper stores queue iterator to operation since the iterator
|
||||
// would not be invalidated by other queue operations.
|
||||
class cancel_waiting_op {
|
||||
queue_t::iterator _ihandler;
|
||||
public:
|
||||
explicit cancel_waiting_op(queue_t::iterator ih) : _ihandler(ih) {}
|
||||
|
||||
void operator()(asio::cancellation_type_t type) {
|
||||
if (type == asio::cancellation_type_t::none)
|
||||
return;
|
||||
if (*_ihandler) {
|
||||
auto h = std::move(*_ihandler);
|
||||
auto ex = asio::get_associated_executor(h);
|
||||
asio::require(ex, asio::execution::blocking.possibly)
|
||||
.execute([h = std::move(h)]() mutable {
|
||||
std::move(h)(asio::error::operation_aborted);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool _locked { false };
|
||||
queue_t _waiting;
|
||||
executor_type _ex;
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
explicit async_mutex(Executor&& ex) : _ex(std::forward<Executor>(ex)) {}
|
||||
|
||||
async_mutex(const async_mutex&) = delete;
|
||||
async_mutex& operator=(const async_mutex&) = delete;
|
||||
|
||||
~async_mutex() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept {
|
||||
return _ex;
|
||||
}
|
||||
|
||||
bool is_locked() const noexcept {
|
||||
return _locked;
|
||||
}
|
||||
|
||||
// Schedules mutex for lock operation and return immediately.
|
||||
// Calls given completion handler when mutex is locked.
|
||||
// It's the responsibility of the completion handler to unlock the mutex.
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) lock(CompletionToken&& token) noexcept {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiation = [] (auto handler, async_mutex& self) {
|
||||
self.execute_or_queue(std::move(handler));
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this)
|
||||
);
|
||||
}
|
||||
|
||||
// Unlocks the mutex. The mutex must be in locked state.
|
||||
// Next queued operation, if any, will be executed in a manner
|
||||
// equivalent to asio::post.
|
||||
void unlock() {
|
||||
while (!_waiting.empty()) {
|
||||
auto op = std::move(_waiting.front());
|
||||
_waiting.pop_front();
|
||||
if (!op) continue;
|
||||
op.get_cancellation_slot().clear();
|
||||
execute_op(std::move(op));
|
||||
return;
|
||||
}
|
||||
_locked = false;
|
||||
}
|
||||
|
||||
// Cancels all outstanding operations waiting on the mutex.
|
||||
void cancel() {
|
||||
while (!_waiting.empty()) {
|
||||
auto op = std::move(_waiting.front());
|
||||
_waiting.pop_front();
|
||||
if (!op) continue;
|
||||
op.get_cancellation_slot().clear();
|
||||
asio::require(_ex, asio::execution::blocking.never)
|
||||
.execute([ex = _ex, op = std::move(op)]() mutable {
|
||||
auto opex = asio::get_associated_executor(op, ex);
|
||||
opex.execute(
|
||||
[op = std::move(op)]() mutable {
|
||||
op(asio::error::operation_aborted);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Schedule operation to `opex` executor using `_ex` executor.
|
||||
// The operation is equivalent to asio::post(_ex, op) but
|
||||
// for some reason this form of execution is much faster.
|
||||
void execute_op(queued_op_t op) {
|
||||
asio::require(_ex, asio::execution::blocking.never)
|
||||
.execute([ex = _ex, op = std::move(op)]() mutable {
|
||||
auto opex = asio::get_associated_executor(op, ex);
|
||||
opex.execute(
|
||||
[op = std::move(op)]() mutable {
|
||||
op(error_code {});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Executes operation immediately if mutex is not locked
|
||||
// or queues it for later execution otherwise. In both cases
|
||||
// the operation will be executed in a manner equivalent
|
||||
// to asio::post to avoid recursion.
|
||||
template <typename Handler>
|
||||
void execute_or_queue(Handler&& handler) noexcept {
|
||||
tracked_op h { std::move(handler), _ex };
|
||||
if (_locked) {
|
||||
_waiting.emplace_back(std::move(h));
|
||||
auto slot = _waiting.back().get_cancellation_slot();
|
||||
if (slot.is_connected())
|
||||
slot.template emplace<cancel_waiting_op>(
|
||||
_waiting.end() - 1
|
||||
);
|
||||
}
|
||||
else {
|
||||
_locked = true;
|
||||
execute_op(queued_op_t { std::move(h) });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASYNC_MUTEX_HPP
|
||||
@@ -1,98 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
#define ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_immediate_executor.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
template <typename Handler, typename Executor>
|
||||
class cancellable_handler {
|
||||
Executor _executor;
|
||||
Handler _handler;
|
||||
tracking_type<Handler, Executor> _handler_ex;
|
||||
asio::cancellation_state _cancellation_state;
|
||||
|
||||
public:
|
||||
cancellable_handler(Handler&& handler, const Executor& ex) :
|
||||
_executor(ex),
|
||||
_handler(std::move(handler)),
|
||||
_handler_ex(tracking_executor(_handler, ex)),
|
||||
_cancellation_state(
|
||||
asio::get_associated_cancellation_slot(_handler),
|
||||
asio::enable_total_cancellation {},
|
||||
asio::enable_terminal_cancellation {}
|
||||
)
|
||||
{}
|
||||
|
||||
cancellable_handler(cancellable_handler&&) = default;
|
||||
cancellable_handler(const cancellable_handler&) = delete;
|
||||
|
||||
cancellable_handler& operator=(cancellable_handler&&) = default;
|
||||
cancellable_handler& operator=(const cancellable_handler&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return _cancellation_state.slot();
|
||||
}
|
||||
|
||||
using executor_type = tracking_type<Handler, Executor>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _handler_ex;
|
||||
}
|
||||
|
||||
using immediate_executor_type =
|
||||
asio::associated_immediate_executor_t<Handler, Executor>;
|
||||
immediate_executor_type get_immediate_executor() const noexcept {
|
||||
// get_associated_immediate_executor will require asio::execution::blocking.never
|
||||
// on the default executor.
|
||||
return asio::get_associated_immediate_executor(_handler, _executor);
|
||||
}
|
||||
|
||||
asio::cancellation_type_t cancelled() const {
|
||||
return _cancellation_state.cancelled();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void complete(Args&&... args) {
|
||||
asio::get_associated_cancellation_slot(_handler).clear();
|
||||
asio::dispatch(
|
||||
_handler_ex,
|
||||
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void complete_immediate(Args&&... args) {
|
||||
asio::get_associated_cancellation_slot(_handler).clear();
|
||||
auto ex = get_immediate_executor();
|
||||
asio::dispatch(
|
||||
ex,
|
||||
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
@@ -1,103 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_CHANNEL_TRAITS_HPP
|
||||
#define ASYNC_MQTT5_CHANNEL_TRAITS_HPP
|
||||
|
||||
#include <deque>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
template <typename Element>
|
||||
class bounded_deque {
|
||||
std::deque<Element> _buffer;
|
||||
static constexpr size_t MAX_SIZE = 65535;
|
||||
|
||||
public:
|
||||
bounded_deque() = default;
|
||||
bounded_deque(size_t n) : _buffer(n) {}
|
||||
|
||||
size_t size() const {
|
||||
return _buffer.size();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void push_back(E&& e) {
|
||||
if (_buffer.size() == MAX_SIZE)
|
||||
_buffer.pop_front();
|
||||
_buffer.push_back(std::forward<E>(e));
|
||||
}
|
||||
|
||||
void pop_front() {
|
||||
_buffer.pop_front();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_buffer.clear();
|
||||
}
|
||||
|
||||
const auto& front() const noexcept {
|
||||
return _buffer.front();
|
||||
}
|
||||
|
||||
auto& front() noexcept {
|
||||
return _buffer.front();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Signatures>
|
||||
struct channel_traits {
|
||||
template <typename... NewSignatures>
|
||||
struct rebind {
|
||||
using other = channel_traits<NewSignatures...>;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct channel_traits<R(error_code, Args...)> {
|
||||
static_assert(sizeof...(Args) > 0);
|
||||
|
||||
template <typename... NewSignatures>
|
||||
struct rebind {
|
||||
using other = channel_traits<NewSignatures...>;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container {
|
||||
using type = bounded_deque<Element>;
|
||||
};
|
||||
|
||||
using receive_cancelled_signature = R(error_code, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f) {
|
||||
std::forward<F>(f)(
|
||||
asio::error::operation_aborted,
|
||||
typename std::decay_t<Args>()...
|
||||
);
|
||||
}
|
||||
|
||||
using receive_closed_signature = R(error_code, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f) {
|
||||
std::forward<F>(f)(
|
||||
asio::error::operation_aborted,
|
||||
typename std::decay_t<Args>()...
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CHANNEL_TRAITS_HPP
|
||||
@@ -1,193 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_CONTROL_PACKET_HPP
|
||||
#define ASYNC_MQTT5_CONTROL_PACKET_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/smart_ptr/allocate_unique.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
/* max varint number (268'435'455) + fixed header size (1 + 4) */
|
||||
static constexpr int32_t default_max_send_size = 268'435'460;
|
||||
|
||||
enum class control_code_e : std::uint8_t {
|
||||
no_packet = 0b00000000, // 0
|
||||
|
||||
connect = 0b00010000, // 1
|
||||
connack = 0b00100000, // 2
|
||||
publish = 0b00110000, // 3
|
||||
puback = 0b01000000, // 4
|
||||
pubrec = 0b01010000, // 5
|
||||
pubrel = 0b01100000, // 6
|
||||
pubcomp = 0b01110000, // 7
|
||||
subscribe = 0b10000000, // 8
|
||||
suback = 0b10010000, // 9
|
||||
unsubscribe = 0b10100000, // 10
|
||||
unsuback = 0b10110000, // 11
|
||||
pingreq = 0b11000000, // 12
|
||||
pingresp = 0b11010000, // 13
|
||||
disconnect = 0b11100000, // 14
|
||||
auth = 0b11110000, // 15
|
||||
};
|
||||
|
||||
constexpr struct with_pid_ {} with_pid {};
|
||||
constexpr struct no_pid_ {} no_pid {};
|
||||
|
||||
template <typename Allocator>
|
||||
class control_packet {
|
||||
uint16_t _packet_id;
|
||||
|
||||
using alloc_type = Allocator;
|
||||
using deleter = boost::alloc_deleter<std::string, alloc_type>;
|
||||
std::unique_ptr<std::string, deleter> _packet;
|
||||
|
||||
control_packet(
|
||||
const Allocator& a,
|
||||
uint16_t packet_id, std::string packet
|
||||
) :
|
||||
_packet_id(packet_id),
|
||||
_packet(boost::allocate_unique<std::string>(a, std::move(packet)))
|
||||
{}
|
||||
|
||||
public:
|
||||
control_packet(control_packet&&) noexcept = default;
|
||||
control_packet(const control_packet&) = delete;
|
||||
|
||||
control_packet& operator=(control_packet&&) noexcept = default;
|
||||
control_packet& operator=(const control_packet&) = delete;
|
||||
|
||||
template <
|
||||
typename EncodeFun,
|
||||
typename ...Args
|
||||
>
|
||||
static control_packet of(
|
||||
with_pid_, const Allocator& alloc,
|
||||
EncodeFun&& encode, uint16_t packet_id, Args&&... args
|
||||
) {
|
||||
return control_packet {
|
||||
alloc, packet_id, encode(packet_id, std::forward<Args>(args)...)
|
||||
};
|
||||
}
|
||||
|
||||
template <
|
||||
typename EncodeFun,
|
||||
typename ...Args
|
||||
>
|
||||
static control_packet of(
|
||||
no_pid_, const Allocator& alloc,
|
||||
EncodeFun&& encode, Args&&... args
|
||||
) {
|
||||
return control_packet {
|
||||
alloc, uint16_t(0), encode(std::forward<Args>(args)...)
|
||||
};
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _packet->size();
|
||||
}
|
||||
|
||||
control_code_e control_code() const {
|
||||
return control_code_e(uint8_t(*(_packet->data())) & 0b11110000);
|
||||
}
|
||||
|
||||
uint16_t packet_id() const {
|
||||
return _packet_id;
|
||||
}
|
||||
|
||||
qos_e qos() const {
|
||||
assert(control_code() == control_code_e::publish);
|
||||
auto byte = (uint8_t(*(_packet->data())) & 0b00000110) >> 1;
|
||||
return qos_e(byte);
|
||||
}
|
||||
|
||||
control_packet& set_dup() {
|
||||
assert(control_code() == control_code_e::publish);
|
||||
auto& byte = *(_packet->data());
|
||||
byte |= 0b00001000;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string_view wire_data() const {
|
||||
return *_packet;
|
||||
}
|
||||
};
|
||||
|
||||
class packet_id_allocator {
|
||||
struct interval {
|
||||
uint16_t start, end;
|
||||
|
||||
interval(uint16_t start, uint16_t end) :
|
||||
start(start), end(end)
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<interval> _free_ids;
|
||||
static constexpr uint16_t MAX_PACKET_ID = 65535;
|
||||
|
||||
public:
|
||||
packet_id_allocator() {
|
||||
_free_ids.emplace_back(MAX_PACKET_ID, uint16_t(0));
|
||||
}
|
||||
|
||||
packet_id_allocator(packet_id_allocator&&) noexcept = default;
|
||||
packet_id_allocator(const packet_id_allocator&) = delete;
|
||||
|
||||
packet_id_allocator& operator=(packet_id_allocator&&) noexcept = default;
|
||||
packet_id_allocator& operator=(const packet_id_allocator&) = delete;
|
||||
|
||||
uint16_t allocate() {
|
||||
if (_free_ids.empty()) return 0;
|
||||
auto& last = _free_ids.back();
|
||||
if (last.start == ++last.end) {
|
||||
auto ret = last.end;
|
||||
_free_ids.pop_back();
|
||||
return ret;
|
||||
}
|
||||
return last.end;
|
||||
}
|
||||
|
||||
void free(uint16_t pid) {
|
||||
auto it = std::upper_bound(
|
||||
_free_ids.begin(), _free_ids.end(), pid,
|
||||
[](const uint16_t x, const interval& i) { return x > i.start; }
|
||||
);
|
||||
uint16_t* end_p = nullptr;
|
||||
if (it != _free_ids.begin()) {
|
||||
auto pit = std::prev(it);
|
||||
if (pit->end == pid)
|
||||
end_p = &pit->end;
|
||||
}
|
||||
if (it != _free_ids.end() && pid - 1 == it->start) {
|
||||
if (!end_p)
|
||||
it->start = pid;
|
||||
else {
|
||||
*end_p = it->end;
|
||||
_free_ids.erase(it);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!end_p)
|
||||
_free_ids.insert(it, interval(pid, pid - 1));
|
||||
else
|
||||
*end_p = pid - 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CONTROL_PACKET_HPP
|
||||
@@ -1,117 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_INTERNAL_TYPES_HPP
|
||||
#define ASYNC_MQTT5_INTERNAL_TYPES_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <async_mqtt5/detail/any_authenticator.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
using byte_citer = std::string::const_iterator;
|
||||
|
||||
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using duration = time_stamp::duration;
|
||||
|
||||
struct credentials {
|
||||
std::string client_id;
|
||||
std::optional<std::string> username;
|
||||
std::optional<std::string> password;
|
||||
|
||||
credentials() = default;
|
||||
credentials(
|
||||
std::string client_id,
|
||||
std::string username, std::string password
|
||||
) :
|
||||
client_id(std::move(client_id))
|
||||
{
|
||||
if (!username.empty())
|
||||
this->username = std::move(username);
|
||||
if (!password.empty())
|
||||
this->password = std::move(password);
|
||||
}
|
||||
};
|
||||
|
||||
class session_state {
|
||||
uint8_t _flags = 0b00;
|
||||
|
||||
static constexpr uint8_t session_present_flag = 0b01;
|
||||
static constexpr uint8_t subscriptions_present_flag = 0b10;
|
||||
public:
|
||||
void session_present(bool present) {
|
||||
return update_flag(present, session_present_flag);
|
||||
}
|
||||
|
||||
bool session_present() const {
|
||||
return _flags & session_present_flag;
|
||||
}
|
||||
|
||||
void subscriptions_present(bool present) {
|
||||
return update_flag(present, subscriptions_present_flag);
|
||||
}
|
||||
|
||||
bool subscriptions_present() const {
|
||||
return _flags & subscriptions_present_flag;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_flag(bool set, uint8_t flag) {
|
||||
if (set)
|
||||
_flags |= flag;
|
||||
else
|
||||
_flags &= ~flag;
|
||||
}
|
||||
};
|
||||
|
||||
struct mqtt_ctx {
|
||||
credentials creds;
|
||||
std::optional<will> will_msg;
|
||||
uint16_t keep_alive = 60;
|
||||
connect_props co_props;
|
||||
connack_props ca_props;
|
||||
session_state state;
|
||||
any_authenticator authenticator;
|
||||
|
||||
mqtt_ctx() = default;
|
||||
|
||||
mqtt_ctx(const mqtt_ctx& other) :
|
||||
creds(other.creds), will_msg(other.will_msg),
|
||||
keep_alive(other.keep_alive), co_props(other.co_props),
|
||||
ca_props {}, state {},
|
||||
authenticator(other.authenticator)
|
||||
{}
|
||||
};
|
||||
|
||||
struct disconnect_ctx {
|
||||
disconnect_rc_e reason_code = disconnect_rc_e::normal_disconnection;
|
||||
disconnect_props props = {};
|
||||
bool terminal = false;
|
||||
};
|
||||
|
||||
using serial_num_t = uint32_t;
|
||||
constexpr serial_num_t no_serial = 0;
|
||||
|
||||
namespace send_flag {
|
||||
|
||||
constexpr unsigned none = 0b000;
|
||||
constexpr unsigned throttled = 0b001;
|
||||
constexpr unsigned prioritized = 0b010;
|
||||
constexpr unsigned terminal = 0b100;
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_INTERNAL_TYPES_HPP
|
||||
@@ -1,76 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_LOG_INVOKE_HPP
|
||||
#define ASYNC_MQTT5_LOG_INVOKE_HPP
|
||||
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/logger_traits.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
|
||||
template <typename LoggerType>
|
||||
class log_invoke {
|
||||
LoggerType _logger;
|
||||
public:
|
||||
explicit log_invoke(LoggerType logger = {}) :
|
||||
_logger(std::move(logger))
|
||||
{}
|
||||
|
||||
void at_resolve(
|
||||
error_code ec, std::string_view host, std::string_view port,
|
||||
const asio::ip::tcp::resolver::results_type& eps
|
||||
) {
|
||||
if constexpr (has_at_resolve<LoggerType>)
|
||||
_logger.at_resolve(ec, host, port, eps);
|
||||
}
|
||||
|
||||
void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if constexpr (has_at_tcp_connect<LoggerType>)
|
||||
_logger.at_tcp_connect(ec, ep);
|
||||
}
|
||||
|
||||
void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if constexpr (has_at_tls_handshake<LoggerType>)
|
||||
_logger.at_tls_handshake(ec, ep);
|
||||
}
|
||||
|
||||
void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if constexpr (has_at_ws_handshake<LoggerType>)
|
||||
_logger.at_ws_handshake(ec, ep);
|
||||
}
|
||||
|
||||
void at_connack(
|
||||
reason_code rc,
|
||||
bool session_present, const connack_props& ca_props
|
||||
) {
|
||||
if constexpr (has_at_connack<LoggerType>)
|
||||
_logger.at_connack(rc, session_present, ca_props);
|
||||
}
|
||||
|
||||
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
|
||||
if constexpr (has_at_disconnect<LoggerType>)
|
||||
_logger.at_disconnect(rc, dc_props);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
|
||||
#endif // !ASYNC_MQTT5_LOG_INVOKE_HPP
|
||||
@@ -1,119 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_TOPIC_VALIDATION_HPP
|
||||
#define ASYNC_MQTT5_TOPIC_VALIDATION_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#include <async_mqtt5/detail/utf8_mqtt.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
static constexpr int32_t min_subscription_identifier = 1;
|
||||
static constexpr int32_t max_subscription_identifier = 268'435'455;
|
||||
|
||||
static constexpr std::string_view shared_sub_prefix = "$share/";
|
||||
|
||||
inline bool is_utf8_no_wildcard(validation_result result) {
|
||||
return result == validation_result::valid;
|
||||
}
|
||||
|
||||
inline bool is_not_empty(size_t sz) {
|
||||
return sz != 0;
|
||||
}
|
||||
|
||||
inline bool is_valid_topic_size(size_t sz) {
|
||||
return is_not_empty(sz) && is_valid_string_size(sz);
|
||||
}
|
||||
|
||||
inline validation_result validate_topic_name(std::string_view str) {
|
||||
return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard);
|
||||
}
|
||||
|
||||
inline validation_result validate_topic_alias_name(std::string_view str) {
|
||||
return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard);
|
||||
}
|
||||
|
||||
inline validation_result validate_shared_topic_name(std::string_view str) {
|
||||
return validate_impl(str, is_not_empty, is_utf8_no_wildcard);
|
||||
}
|
||||
|
||||
inline validation_result validate_topic_filter(std::string_view str) {
|
||||
if (!is_valid_topic_size(str.size()))
|
||||
return validation_result::invalid;
|
||||
|
||||
constexpr int multi_lvl_wildcard = '#';
|
||||
constexpr int single_lvl_wildcard = '+';
|
||||
|
||||
// must be the last character preceded by '/' or stand alone
|
||||
// #, .../#
|
||||
if (str.back() == multi_lvl_wildcard) {
|
||||
str.remove_suffix(1);
|
||||
|
||||
if (!str.empty() && str.back() != '/')
|
||||
return validation_result::invalid;
|
||||
}
|
||||
|
||||
int last_c = -1;
|
||||
validation_result result;
|
||||
while (!str.empty()) {
|
||||
int c = pop_front_unichar(str);
|
||||
|
||||
// can be used at any level, but must occupy an entire level
|
||||
// +, +/..., .../+/..., .../+
|
||||
bool is_valid_single_lvl = (c == single_lvl_wildcard) &&
|
||||
(str.empty() || str.front() == '/') &&
|
||||
(last_c == -1 || last_c == '/');
|
||||
|
||||
result = validate_mqtt_utf8_char(c);
|
||||
if (
|
||||
result == validation_result::valid ||
|
||||
is_valid_single_lvl
|
||||
) {
|
||||
last_c = c;
|
||||
continue;
|
||||
}
|
||||
|
||||
return validation_result::invalid;
|
||||
}
|
||||
|
||||
return validation_result::valid;
|
||||
}
|
||||
|
||||
inline validation_result validate_shared_topic_filter(
|
||||
std::string_view str, bool wildcard_allowed = true
|
||||
) {
|
||||
if (!is_valid_topic_size(str.size()))
|
||||
return validation_result::invalid;
|
||||
|
||||
if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0)
|
||||
return validation_result::invalid;
|
||||
|
||||
str.remove_prefix(shared_sub_prefix.size());
|
||||
|
||||
size_t share_name_end = str.find_first_of('/');
|
||||
if (share_name_end == std::string::npos)
|
||||
return validation_result::invalid;
|
||||
|
||||
validation_result result;
|
||||
result = validate_shared_topic_name(str.substr(0, share_name_end));
|
||||
|
||||
if (result != validation_result::valid)
|
||||
return validation_result::invalid;
|
||||
|
||||
auto topic_filter = str.substr(share_name_end + 1);
|
||||
return wildcard_allowed ?
|
||||
validate_topic_filter(topic_filter) :
|
||||
validate_topic_name(topic_filter)
|
||||
;
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif //ASYNC_MQTT5_TOPIC_VALIDATION_HPP
|
||||
@@ -1,116 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_UTF8_MQTT_HPP
|
||||
#define ASYNC_MQTT5_UTF8_MQTT_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
enum class validation_result : uint8_t {
|
||||
valid = 0,
|
||||
has_wildcard_character,
|
||||
invalid
|
||||
};
|
||||
|
||||
inline int pop_front_unichar(std::string_view& s) {
|
||||
// assuming that s.length() is > 0
|
||||
|
||||
int n = s[0] & 0xF0;
|
||||
int ch = -1;
|
||||
|
||||
if ((n & 0x80) == 0) {
|
||||
ch = s[0];
|
||||
s.remove_prefix(1);
|
||||
}
|
||||
else if ((n == 0xC0 || n == 0xD0) && s.size() > 1) {
|
||||
ch = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F);
|
||||
s.remove_prefix(2);
|
||||
}
|
||||
else if ((n == 0xE0) && s.size() > 2) {
|
||||
ch = ((s[0] & 0x1F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F);
|
||||
s.remove_prefix(3);
|
||||
}
|
||||
else if ((n == 0xF0) && s.size() > 3) {
|
||||
ch = ((s[0] & 0x1F) << 18) | ((s[1] & 0x3F) << 12) |
|
||||
((s[2] & 0x3F) << 6) | (s[3] & 0x3F);
|
||||
s.remove_prefix(4);
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
inline validation_result validate_mqtt_utf8_char(int c) {
|
||||
constexpr int fe_flag = 0xFE;
|
||||
constexpr int ff_flag = 0xFF;
|
||||
|
||||
constexpr int multi_lvl_wildcard = '#';
|
||||
constexpr int single_lvl_wildcard = '+';
|
||||
|
||||
if (c == multi_lvl_wildcard || c == single_lvl_wildcard)
|
||||
return validation_result::has_wildcard_character;
|
||||
|
||||
if (c > 0x001F && // U+0000...U+001F control characters
|
||||
(c < 0x007F || c > 0x009F) && // U+007F...0+009F control characters
|
||||
(c < 0xD800 || c > 0xDFFF) && // U+D800...U+DFFF surrogates
|
||||
(c < 0xFDD0 || c > 0xFDEF) && // U+FDD0...U+FDEF non-characters
|
||||
(c & fe_flag) != fe_flag && // non-characters
|
||||
(c & ff_flag) != ff_flag
|
||||
)
|
||||
return validation_result::valid;
|
||||
|
||||
return validation_result::invalid;
|
||||
}
|
||||
|
||||
inline bool is_valid_string_size(size_t sz) {
|
||||
constexpr size_t max_sz = 65535;
|
||||
return sz <= max_sz;
|
||||
}
|
||||
|
||||
inline bool is_utf8(validation_result result) {
|
||||
return result == validation_result::valid ||
|
||||
result == validation_result::has_wildcard_character;
|
||||
}
|
||||
|
||||
template <typename ValidSizeCondition, typename ValidCondition>
|
||||
validation_result validate_impl(
|
||||
std::string_view str,
|
||||
ValidSizeCondition&& size_condition, ValidCondition&& condition
|
||||
) {
|
||||
if (!size_condition(str.size()))
|
||||
return validation_result::invalid;
|
||||
|
||||
validation_result result;
|
||||
while (!str.empty()) {
|
||||
int c = pop_front_unichar(str);
|
||||
|
||||
result = validate_mqtt_utf8_char(c);
|
||||
if (!condition(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
return validation_result::valid;
|
||||
}
|
||||
|
||||
inline validation_result validate_mqtt_utf8(std::string_view str) {
|
||||
return validate_impl(str, is_valid_string_size, is_utf8);
|
||||
}
|
||||
|
||||
inline bool is_valid_string_pair(
|
||||
const std::pair<std::string, std::string>& str_pair
|
||||
) {
|
||||
return validate_mqtt_utf8(str_pair.first) == validation_result::valid &&
|
||||
validate_mqtt_utf8(str_pair.second) == validation_result::valid;
|
||||
}
|
||||
|
||||
} // namespace async_mqtt5::detail
|
||||
|
||||
#endif //ASYNC_MQTT5_UTF8_MQTT_HPP
|
||||
@@ -1,168 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ERROR_HPP
|
||||
#define ASYNC_MQTT5_ERROR_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
/**
|
||||
* \brief A representation of Disconnect Reason Code.
|
||||
*
|
||||
* \details Represents all Reason Codes that the Client can send to the Server
|
||||
* in the \__DISCONNECT\__ packet as the reason for the disconnection.
|
||||
*/
|
||||
enum class disconnect_rc_e : uint8_t {
|
||||
/** Close the connection normally. Do not send the Will Message. */
|
||||
normal_disconnection = 0x00,
|
||||
|
||||
/** The Client wishes to disconnect but requires that
|
||||
the Server also publishes its Will Message. */
|
||||
disconnect_with_will_message = 0x04
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
enum class disconnect_rc_e : uint8_t {
|
||||
normal_disconnection = 0x00,
|
||||
disconnect_with_will_message = 0x04,
|
||||
|
||||
unspecified_error = 0x80,
|
||||
malformed_packet = 0x81,
|
||||
protocol_error = 0x82,
|
||||
implementation_specific_error = 0x83,
|
||||
topic_name_invalid = 0x90,
|
||||
receive_maximum_exceeded = 0x93,
|
||||
topic_alias_invalid = 0x94,
|
||||
packet_too_large = 0x95,
|
||||
message_rate_too_high = 0x96,
|
||||
quota_exceeded = 0x97,
|
||||
administrative_action = 0x98,
|
||||
payload_format_invalid = 0x99
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace client {
|
||||
/**
|
||||
* \brief Defines error codes related to MQTT client.
|
||||
*
|
||||
* \details Encapsulates errors that occur on the client side.
|
||||
*/
|
||||
enum class error : int {
|
||||
/** The packet is malformed */
|
||||
malformed_packet = 100,
|
||||
|
||||
/** The packet has exceeded the Maximum Packet Size the Server is willing to accept */
|
||||
packet_too_large,
|
||||
|
||||
/** The Client's session does not exist or it has expired */
|
||||
session_expired,
|
||||
|
||||
/** There are no more available Packet Identifiers to use */
|
||||
pid_overrun,
|
||||
|
||||
/** The Topic is invalid and does not conform to the specification */
|
||||
invalid_topic,
|
||||
|
||||
// publish
|
||||
/** The Server does not support the specified \ref qos_e */
|
||||
qos_not_supported,
|
||||
|
||||
/** The Server does not support retained messages */
|
||||
retain_not_available,
|
||||
|
||||
/** The Client attempted to send a Topic Alias that is greater than Topic Alias Maximum */
|
||||
topic_alias_maximum_reached,
|
||||
|
||||
// subscribe
|
||||
/** The Server does not support Wildcard Subscriptions */
|
||||
wildcard_subscription_not_available,
|
||||
|
||||
/** The Server does not support this Subscription Identifier */
|
||||
subscription_identifier_not_available,
|
||||
|
||||
/** The Server does not support Shared Subscriptions */
|
||||
shared_subscription_not_available
|
||||
};
|
||||
|
||||
|
||||
inline std::string client_error_to_string(error err) {
|
||||
switch (err) {
|
||||
case error::malformed_packet:
|
||||
return "The packet is malformed";
|
||||
case error::packet_too_large:
|
||||
return "The packet has exceeded the Maximum Packet Size "
|
||||
"the Server is willing to accept";
|
||||
case error::session_expired:
|
||||
return "The Client's session does not exist or it has expired";
|
||||
case error::pid_overrun:
|
||||
return "There are no more available Packet Identifiers to use";
|
||||
case error::invalid_topic:
|
||||
return "The Topic is invalid and "
|
||||
"does not conform to the specification";
|
||||
case error::qos_not_supported:
|
||||
return "The Server does not support the specified QoS";
|
||||
case error::retain_not_available:
|
||||
return "The Server does not support retained messages";
|
||||
case error::topic_alias_maximum_reached:
|
||||
return "The Client attempted to send a Topic Alias "
|
||||
"that is greater than Topic Alias Maximum";
|
||||
case error::wildcard_subscription_not_available:
|
||||
return "The Server does not support Wildcard Subscriptions";
|
||||
case error::subscription_identifier_not_available:
|
||||
return "The Server does not support this Subscription Identifier";
|
||||
case error::shared_subscription_not_available:
|
||||
return "The Server does not support Shared Subscriptions";
|
||||
default:
|
||||
return "Unknown client error";
|
||||
}
|
||||
}
|
||||
|
||||
struct client_ec_category : public boost::system::error_category {
|
||||
const char* name() const noexcept override { return "mqtt_client_error"; }
|
||||
std::string message(int ev) const noexcept override {
|
||||
return client_error_to_string(static_cast<error>(ev));
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns the error category associated with \ref client::error.
|
||||
inline const client_ec_category& get_error_code_category() {
|
||||
static client_ec_category cat;
|
||||
return cat;
|
||||
}
|
||||
|
||||
/// Creates an \ref error_code from a \ref client::error.
|
||||
inline boost::system::error_code make_error_code(error r) {
|
||||
return { static_cast<int>(r), get_error_code_category() };
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const error& err) {
|
||||
os << get_error_code_category().name() << ":" << static_cast<int>(err);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // end namespace client
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
namespace boost::system {
|
||||
|
||||
template <>
|
||||
struct is_error_code_enum <async_mqtt5::client::error> : std::true_type {};
|
||||
|
||||
} // end namespace boost::system
|
||||
|
||||
|
||||
#endif // !ASYNC_MQTT5_ERROR_HPP
|
||||
@@ -1,243 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ASSEMBLE_OP_HPP
|
||||
#define ASYNC_MQTT5_ASSEMBLE_OP_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class data_span : private std::pair<byte_citer, byte_citer> {
|
||||
using base = std::pair<byte_citer, byte_citer>;
|
||||
public:
|
||||
using base::base;
|
||||
|
||||
auto first() const {
|
||||
return base::first;
|
||||
}
|
||||
auto last() const {
|
||||
return base::second;
|
||||
}
|
||||
void expand_suffix(size_t num_chars) {
|
||||
base::second += num_chars;
|
||||
}
|
||||
void remove_prefix(size_t num_chars) {
|
||||
base::first += num_chars;
|
||||
}
|
||||
size_t size() const {
|
||||
return std::distance(base::first, base::second);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class assemble_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_read {};
|
||||
|
||||
static constexpr uint32_t max_recv_size = 65'536;
|
||||
|
||||
client_service& _svc;
|
||||
handler_type _handler;
|
||||
|
||||
std::string& _read_buff;
|
||||
data_span& _data_span;
|
||||
|
||||
public:
|
||||
assemble_op(
|
||||
client_service& svc, handler_type&& handler,
|
||||
std::string& read_buff, data_span& active_span
|
||||
) :
|
||||
_svc(svc),
|
||||
_handler(std::move(handler)),
|
||||
_read_buff(read_buff), _data_span(active_span)
|
||||
{}
|
||||
|
||||
assemble_op(assemble_op&&) noexcept = default;
|
||||
assemble_op(const assemble_op&) = delete;
|
||||
|
||||
assemble_op& operator=(assemble_op&&) noexcept = default;
|
||||
assemble_op& operator=(const assemble_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename CompletionCondition>
|
||||
void perform(CompletionCondition cc) {
|
||||
_read_buff.erase(
|
||||
_read_buff.cbegin(), _data_span.first()
|
||||
);
|
||||
_read_buff.resize(
|
||||
_svc.connect_property(prop::maximum_packet_size).value_or(max_recv_size)
|
||||
);
|
||||
_data_span = {
|
||||
_read_buff.cbegin(),
|
||||
_read_buff.cbegin() + _data_span.size()
|
||||
};
|
||||
|
||||
if (cc(error_code {}, 0) == 0 && _data_span.size()) {
|
||||
return asio::post(
|
||||
_svc.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_read {}, error_code {},
|
||||
0, std::move(cc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Must be evaluated before this is moved
|
||||
auto store_begin = _read_buff.data() + _data_span.size();
|
||||
auto store_size = std::distance(_data_span.last(), _read_buff.cend());
|
||||
|
||||
_svc._stream.async_read_some(
|
||||
asio::buffer(store_begin, store_size), compute_read_timeout(),
|
||||
asio::prepend(
|
||||
asio::append(std::move(*this), std::move(cc)),
|
||||
on_read {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionCondition>
|
||||
void operator()(
|
||||
on_read, error_code ec, size_t bytes_read,
|
||||
CompletionCondition cc
|
||||
) {
|
||||
if (ec == asio::error::try_again) {
|
||||
_svc.update_session_state();
|
||||
_svc._async_sender.resend();
|
||||
_data_span = { _read_buff.cend(), _read_buff.cend() };
|
||||
return perform(std::move(cc));
|
||||
}
|
||||
|
||||
if (ec)
|
||||
return complete(ec, 0, {}, {});
|
||||
|
||||
_data_span.expand_suffix(bytes_read);
|
||||
assert(_data_span.size());
|
||||
|
||||
auto control_byte = uint8_t(*_data_span.first());
|
||||
|
||||
if ((control_byte & 0b11110000) == 0)
|
||||
// close the connection, cancel
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
|
||||
auto first = _data_span.first() + 1;
|
||||
auto varlen = decoders::type_parse(
|
||||
first, _data_span.last(), decoders::basic::varint_
|
||||
);
|
||||
|
||||
if (!varlen) {
|
||||
if (_data_span.size() < 5)
|
||||
return perform(asio::transfer_at_least(1));
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
}
|
||||
|
||||
auto recv_size = _svc.connect_property(prop::maximum_packet_size)
|
||||
.value_or(max_recv_size);
|
||||
if (static_cast<uint32_t>(*varlen) > recv_size - std::distance(_data_span.first(), first))
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
|
||||
if (std::distance(first, _data_span.last()) < *varlen)
|
||||
return perform(asio::transfer_at_least(1));
|
||||
|
||||
_data_span.remove_prefix(
|
||||
std::distance(_data_span.first(), first) + *varlen
|
||||
);
|
||||
|
||||
dispatch(control_byte, first, first + *varlen);
|
||||
}
|
||||
|
||||
private:
|
||||
duration compute_read_timeout() const {
|
||||
auto negotiated_ka = _svc.negotiated_keep_alive();
|
||||
return negotiated_ka ?
|
||||
std::chrono::milliseconds(3 * negotiated_ka * 1000 / 2) :
|
||||
duration(std::numeric_limits<duration::rep>::max());
|
||||
}
|
||||
|
||||
static bool valid_header(uint8_t control_byte) {
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
if (code == control_code_e::publish)
|
||||
return true;
|
||||
|
||||
auto res = control_byte & 0b00001111;
|
||||
if (code == control_code_e::pubrel)
|
||||
return res == 0b00000010;
|
||||
return res == 0b00000000;
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
uint8_t control_byte, byte_citer first, byte_citer last
|
||||
) {
|
||||
using namespace decoders;
|
||||
|
||||
if (!valid_header(control_byte))
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
if (code == control_code_e::pingresp)
|
||||
return perform(asio::transfer_at_least(0));
|
||||
|
||||
bool is_reply = code != control_code_e::publish &&
|
||||
code != control_code_e::auth &&
|
||||
code != control_code_e::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(asio::transfer_at_least(0));
|
||||
}
|
||||
|
||||
complete(error_code {}, control_byte, first, last);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec)
|
||||
_data_span = { _read_buff.cend(), _read_buff.cend() };
|
||||
std::move(_handler)(ec, control_code, first, last);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASSEMBLE_OP_HPP
|
||||
@@ -1,303 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ASYNC_SENDER_HPP
|
||||
#define ASYNC_MQTT5_ASYNC_SENDER_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class write_req {
|
||||
static constexpr unsigned SERIAL_BITS = sizeof(serial_num_t) * 8;
|
||||
|
||||
asio::const_buffer _buffer;
|
||||
serial_num_t _serial_num;
|
||||
unsigned _flags;
|
||||
|
||||
using handler_type = asio::any_completion_handler<void (error_code)>;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
write_req(
|
||||
asio::const_buffer buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
handler_type handler
|
||||
) :
|
||||
_buffer(buffer), _serial_num(serial_num), _flags(flags),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
write_req(write_req&&) = default;
|
||||
write_req(const write_req&) = delete;
|
||||
|
||||
write_req& operator=(write_req&&) = default;
|
||||
write_req& operator=(const write_req&) = delete;
|
||||
|
||||
static serial_num_t next_serial_num(serial_num_t last) {
|
||||
return last + 1;
|
||||
}
|
||||
|
||||
asio::const_buffer buffer() const {
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
void complete(error_code ec) {
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
|
||||
void complete_post(const asio::any_io_executor& ex, error_code ec) {
|
||||
asio::post(
|
||||
ex,
|
||||
asio::prepend(std::move(_handler), ec)
|
||||
);
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return !_handler;
|
||||
}
|
||||
|
||||
bool throttled() const {
|
||||
return _flags & send_flag::throttled;
|
||||
}
|
||||
|
||||
bool terminal() const {
|
||||
return _flags & send_flag::terminal;
|
||||
}
|
||||
|
||||
bool operator<(const write_req& other) const {
|
||||
if (prioritized() != other.prioritized())
|
||||
return prioritized();
|
||||
|
||||
auto s1 = _serial_num;
|
||||
auto s2 = other._serial_num;
|
||||
|
||||
if (s1 < s2)
|
||||
return (s2 - s1) < (1u << (SERIAL_BITS - 1));
|
||||
|
||||
return (s1 - s2) >= (1u << (SERIAL_BITS - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
bool prioritized() const {
|
||||
return _flags & send_flag::prioritized;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ClientService>
|
||||
class async_sender {
|
||||
using self_type = async_sender<ClientService>;
|
||||
|
||||
using client_service = ClientService;
|
||||
|
||||
using queue_allocator_type = asio::recycling_allocator<write_req>;
|
||||
using write_queue_t = std::vector<write_req, queue_allocator_type>;
|
||||
|
||||
ClientService& _svc;
|
||||
write_queue_t _write_queue;
|
||||
bool _write_in_progress { false };
|
||||
|
||||
static constexpr uint16_t MAX_LIMIT = 65535;
|
||||
uint16_t _limit { MAX_LIMIT };
|
||||
uint16_t _quota { MAX_LIMIT };
|
||||
|
||||
serial_num_t _last_serial_num { 0 };
|
||||
|
||||
public:
|
||||
explicit async_sender(ClientService& svc) : _svc(svc) {}
|
||||
|
||||
async_sender(async_sender&&) = default;
|
||||
async_sender(const async_sender&) = delete;
|
||||
|
||||
async_sender& operator=(async_sender&&) = default;
|
||||
async_sender& operator=(const async_sender&) = delete;
|
||||
|
||||
using allocator_type = queue_allocator_type;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc.get_executor();
|
||||
}
|
||||
|
||||
serial_num_t next_serial_num() {
|
||||
return _last_serial_num = write_req::next_serial_num(_last_serial_num);
|
||||
}
|
||||
|
||||
template <typename CompletionToken, typename BufferType>
|
||||
decltype(auto) async_send(
|
||||
const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, self_type& self, const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags
|
||||
) {
|
||||
self._write_queue.emplace_back(
|
||||
asio::buffer(buffer), serial_num, flags, std::move(handler)
|
||||
);
|
||||
self.do_write();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this),
|
||||
buffer, serial_num, flags
|
||||
);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
auto ops = std::move(_write_queue);
|
||||
for (auto& op : ops)
|
||||
op.complete_post(_svc.get_executor(), asio::error::operation_aborted);
|
||||
}
|
||||
|
||||
void resend() {
|
||||
if (_write_in_progress)
|
||||
return;
|
||||
|
||||
// The _write_in_progress flag is set to true to prevent any write
|
||||
// operations executing before the _write_queue is filled with
|
||||
// all the packets that require resending.
|
||||
_write_in_progress = true;
|
||||
|
||||
auto new_limit = _svc._stream_context.connack_property(prop::receive_maximum);
|
||||
_limit = new_limit.value_or(MAX_LIMIT);
|
||||
_quota = _limit;
|
||||
|
||||
auto write_queue = std::move(_write_queue);
|
||||
_svc._replies.resend_unanswered();
|
||||
|
||||
for (auto& op : write_queue)
|
||||
op.complete(asio::error::try_again);
|
||||
|
||||
std::stable_sort(_write_queue.begin(), _write_queue.end());
|
||||
|
||||
_write_in_progress = false;
|
||||
do_write();
|
||||
}
|
||||
|
||||
void operator()(write_queue_t write_queue, error_code ec, size_t) {
|
||||
_write_in_progress = false;
|
||||
|
||||
if (ec == asio::error::try_again) {
|
||||
_svc.update_session_state();
|
||||
_write_queue.insert(
|
||||
_write_queue.begin(),
|
||||
std::make_move_iterator(write_queue.begin()),
|
||||
std::make_move_iterator(write_queue.end())
|
||||
);
|
||||
return resend();
|
||||
}
|
||||
|
||||
if (ec == asio::error::no_recovery)
|
||||
_svc.cancel();
|
||||
|
||||
// errors, if any, are propagated to ops
|
||||
for (auto& op : write_queue)
|
||||
op.complete(ec);
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return;
|
||||
|
||||
do_write();
|
||||
}
|
||||
|
||||
void throttled_op_done() {
|
||||
if (_limit == MAX_LIMIT)
|
||||
return;
|
||||
|
||||
++_quota;
|
||||
do_write();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_write() {
|
||||
if (_write_in_progress || _write_queue.empty())
|
||||
return;
|
||||
|
||||
_write_in_progress = true;
|
||||
|
||||
write_queue_t write_queue;
|
||||
|
||||
auto terminal_req = std::find_if(
|
||||
_write_queue.begin(), _write_queue.end(),
|
||||
[](const auto& op) { return op.terminal(); }
|
||||
);
|
||||
|
||||
if (terminal_req != _write_queue.end()) {
|
||||
write_queue.push_back(std::move(*terminal_req));
|
||||
_write_queue.erase(terminal_req);
|
||||
}
|
||||
else if (_limit == MAX_LIMIT) {
|
||||
write_queue = std::move(_write_queue);
|
||||
}
|
||||
else {
|
||||
for (write_req& req : _write_queue)
|
||||
if (!req.throttled())
|
||||
write_queue.push_back(std::move(req));
|
||||
else if (_quota > 0) {
|
||||
--_quota;
|
||||
write_queue.push_back(std::move(req));
|
||||
}
|
||||
|
||||
if (write_queue.empty()) {
|
||||
_write_in_progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::remove_if(
|
||||
_write_queue.begin(), _write_queue.end(),
|
||||
[](const write_req& req) { return req.empty(); }
|
||||
);
|
||||
_write_queue.erase(it, _write_queue.end());
|
||||
}
|
||||
|
||||
std::vector<asio::const_buffer> buffers;
|
||||
buffers.reserve(write_queue.size());
|
||||
for (const auto& op : write_queue)
|
||||
buffers.push_back(op.buffer());
|
||||
|
||||
_svc._replies.clear_fast_replies();
|
||||
|
||||
_svc._stream.async_write(
|
||||
buffers,
|
||||
asio::prepend(std::ref(*this), std::move(write_queue))
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASYNC_SENDER_HPP
|
||||
@@ -1,236 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
#define ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/async_mutex.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/endpoints.hpp>
|
||||
#include <async_mqtt5/impl/read_op.hpp>
|
||||
#include <async_mqtt5/impl/reconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/write_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename StreamContext = std::monostate,
|
||||
typename LoggerType = noop_logger
|
||||
>
|
||||
class autoconnect_stream {
|
||||
public:
|
||||
using self_type = autoconnect_stream<StreamType, StreamContext, LoggerType>;
|
||||
using stream_type = StreamType;
|
||||
using stream_context_type = StreamContext;
|
||||
using logger_type = LoggerType;
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
private:
|
||||
using stream_ptr = std::shared_ptr<stream_type>;
|
||||
|
||||
executor_type _stream_executor;
|
||||
async_mutex _conn_mtx;
|
||||
asio::steady_timer _read_timer, _connect_timer;
|
||||
endpoints<logger_type> _endpoints;
|
||||
|
||||
stream_ptr _stream_ptr;
|
||||
stream_context_type& _stream_context;
|
||||
|
||||
log_invoke<logger_type>& _log;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class read_op;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class write_op;
|
||||
|
||||
template <typename Owner>
|
||||
friend class reconnect_op;
|
||||
|
||||
public:
|
||||
autoconnect_stream(
|
||||
const executor_type& ex, stream_context_type& context,
|
||||
log_invoke<logger_type>& log
|
||||
) :
|
||||
_stream_executor(ex),
|
||||
_conn_mtx(_stream_executor),
|
||||
_read_timer(_stream_executor), _connect_timer(_stream_executor),
|
||||
_endpoints(_stream_executor, _connect_timer, log),
|
||||
_stream_context(context),
|
||||
_log(log)
|
||||
{
|
||||
replace_next_layer(construct_next_layer());
|
||||
}
|
||||
|
||||
autoconnect_stream(const autoconnect_stream&) = delete;
|
||||
autoconnect_stream& operator=(const autoconnect_stream&) = delete;
|
||||
|
||||
using next_layer_type = stream_type;
|
||||
next_layer_type& next_layer() {
|
||||
return *_stream_ptr;
|
||||
}
|
||||
|
||||
const next_layer_type& next_layer() const {
|
||||
return *_stream_ptr;
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream_executor;
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
_endpoints.brokers(std::move(hosts), default_port);
|
||||
}
|
||||
|
||||
void clone_endpoints(const autoconnect_stream& other) {
|
||||
_endpoints.clone_servers(other._endpoints);
|
||||
}
|
||||
|
||||
bool is_open() const noexcept {
|
||||
return lowest_layer(*_stream_ptr).is_open();
|
||||
}
|
||||
|
||||
void open() {
|
||||
open_lowest_layer(_stream_ptr, asio::ip::tcp::v4());
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).cancel(ec);
|
||||
_conn_mtx.cancel();
|
||||
_connect_timer.cancel();
|
||||
}
|
||||
|
||||
void close() {
|
||||
error_code ec;
|
||||
shutdown(asio::ip::tcp::socket::shutdown_both);
|
||||
lowest_layer(*_stream_ptr).close(ec);
|
||||
}
|
||||
|
||||
void shutdown(asio::ip::tcp::socket::shutdown_type what) {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).shutdown(what, ec);
|
||||
}
|
||||
|
||||
bool was_connected() const {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).remote_endpoint(ec);
|
||||
return ec == boost::system::errc::success;
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_read_some(
|
||||
const BufferType& buffer, duration wait_for, CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, size_t);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, self_type& self,
|
||||
const BufferType& buffer, duration wait_for
|
||||
) {
|
||||
read_op { self, std::move(handler) }.perform(buffer, wait_for);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), buffer, wait_for
|
||||
);
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_write(
|
||||
const BufferType& buffer, CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, size_t);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, self_type& self, const BufferType& buffer
|
||||
) {
|
||||
write_op { self, std::move(handler) }.perform(buffer);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), buffer
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
log_invoke<logger_type>& log() {
|
||||
return _log;
|
||||
}
|
||||
|
||||
static void open_lowest_layer(const stream_ptr& sptr, asio::ip::tcp protocol) {
|
||||
error_code ec;
|
||||
auto& layer = lowest_layer(*sptr);
|
||||
layer.open(protocol, ec);
|
||||
layer.set_option(asio::socket_base::reuse_address(true), ec);
|
||||
layer.set_option(asio::ip::tcp::no_delay(true), ec);
|
||||
}
|
||||
|
||||
stream_ptr construct_next_layer() const {
|
||||
stream_ptr sptr;
|
||||
if constexpr (has_tls_context<StreamContext>)
|
||||
sptr = std::make_shared<stream_type>(
|
||||
_stream_executor, _stream_context.tls_context()
|
||||
);
|
||||
else
|
||||
sptr = std::make_shared<stream_type>(_stream_executor);
|
||||
|
||||
return sptr;
|
||||
}
|
||||
|
||||
stream_ptr construct_and_open_next_layer(asio::ip::tcp protocol) const {
|
||||
auto sptr = construct_next_layer();
|
||||
open_lowest_layer(sptr, protocol);
|
||||
return sptr;
|
||||
}
|
||||
|
||||
void replace_next_layer(stream_ptr sptr) {
|
||||
// close() will cancel all outstanding async operations on
|
||||
// _stream_ptr; cancelling posts operation_aborted to handlers
|
||||
// but handlers will be executed after std::exchange below;
|
||||
// handlers should therefore treat (operation_aborted && is_open())
|
||||
// equivalent to try_again.
|
||||
|
||||
if (_stream_ptr)
|
||||
close();
|
||||
std::exchange(_stream_ptr, std::move(sptr));
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_reconnect(stream_ptr s, CompletionToken&& token) {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiation = [](auto handler, self_type& self, stream_ptr s) {
|
||||
reconnect_op { self, std::move(handler) }.perform(std::move(s));
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), std::move(s)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
@@ -1,524 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_CLIENT_SERVICE_HPP
|
||||
#define ASYNC_MQTT5_CLIENT_SERVICE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/experimental/basic_channel.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/channel_traits.hpp>
|
||||
#include <async_mqtt5/detail/log_invoke.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/assemble_op.hpp>
|
||||
#include <async_mqtt5/impl/async_sender.hpp>
|
||||
#include <async_mqtt5/impl/autoconnect_stream.hpp>
|
||||
#include <async_mqtt5/impl/replies.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename StreamType, typename TlsContext,
|
||||
typename Enable = void
|
||||
>
|
||||
class stream_context;
|
||||
|
||||
template <
|
||||
typename StreamType, typename TlsContext
|
||||
>
|
||||
class stream_context<
|
||||
StreamType, TlsContext,
|
||||
std::enable_if_t<has_tls_layer<StreamType>>
|
||||
> {
|
||||
using tls_context_type = TlsContext;
|
||||
|
||||
mqtt_ctx _mqtt_context;
|
||||
std::shared_ptr<tls_context_type> _tls_context_ptr;
|
||||
|
||||
public:
|
||||
explicit stream_context(TlsContext tls_context) :
|
||||
_tls_context_ptr(std::make_shared<tls_context_type>(std::move(tls_context)))
|
||||
{}
|
||||
|
||||
stream_context(const stream_context& other) :
|
||||
_mqtt_context(other._mqtt_context), _tls_context_ptr(other._tls_context_ptr)
|
||||
{}
|
||||
|
||||
auto& mqtt_context() {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
const auto& mqtt_context() const {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
auto& tls_context() {
|
||||
return *_tls_context_ptr;
|
||||
}
|
||||
|
||||
auto& session_state() {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
const auto& session_state() const {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
_mqtt_context.will_msg = std::move(will);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.ca_props[prop];
|
||||
}
|
||||
|
||||
const auto& connack_properties() const {
|
||||
return _mqtt_context.ca_props;
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
void connect_properties(connect_props props) {
|
||||
_mqtt_context.co_props = std::move(props);
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_mqtt_context.creds = {
|
||||
std::move(client_id),
|
||||
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>
|
||||
class stream_context<
|
||||
StreamType, std::monostate,
|
||||
std::enable_if_t<!has_tls_layer<StreamType>>
|
||||
> {
|
||||
mqtt_ctx _mqtt_context;
|
||||
public:
|
||||
explicit stream_context(std::monostate) {}
|
||||
|
||||
stream_context(const stream_context& other) :
|
||||
_mqtt_context(other._mqtt_context)
|
||||
{}
|
||||
|
||||
auto& mqtt_context() {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
const auto& mqtt_context() const {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
auto& session_state() {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
const auto& session_state() const {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
_mqtt_context.will_msg = std::move(will);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.ca_props[prop];
|
||||
}
|
||||
|
||||
const auto& connack_properties() const {
|
||||
return _mqtt_context.ca_props;
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
void connect_properties(connect_props props) {
|
||||
_mqtt_context.co_props = std::move(props);
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_mqtt_context.creds = {
|
||||
std::move(client_id),
|
||||
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,
|
||||
typename TlsContext = std::monostate,
|
||||
typename LoggerType = noop_logger
|
||||
>
|
||||
class client_service {
|
||||
using self_type = client_service<StreamType, TlsContext, LoggerType>;
|
||||
using stream_context_type = stream_context<StreamType, TlsContext>;
|
||||
using stream_type = autoconnect_stream<
|
||||
StreamType, stream_context_type, LoggerType
|
||||
>;
|
||||
public:
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
private:
|
||||
using tls_context_type = TlsContext;
|
||||
using logger_type = LoggerType;
|
||||
using receive_channel = asio::experimental::basic_channel<
|
||||
executor_type,
|
||||
channel_traits<>,
|
||||
void (error_code, std::string, std::string, publish_props)
|
||||
>;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class run_op;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class async_sender;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class assemble_op;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class ping_op;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class sentry_op;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class re_auth_op;
|
||||
|
||||
executor_type _executor;
|
||||
|
||||
log_invoke<logger_type> _log;
|
||||
|
||||
stream_context_type _stream_context;
|
||||
stream_type _stream;
|
||||
|
||||
packet_id_allocator _pid_allocator;
|
||||
replies _replies;
|
||||
async_sender<client_service> _async_sender;
|
||||
|
||||
std::string _read_buff;
|
||||
data_span _active_span;
|
||||
|
||||
receive_channel _rec_channel;
|
||||
|
||||
asio::steady_timer _ping_timer;
|
||||
asio::steady_timer _sentry_timer;
|
||||
|
||||
client_service(const client_service& other) :
|
||||
_executor(other._executor),
|
||||
_log(other._log),
|
||||
_stream_context(other._stream_context),
|
||||
_stream(_executor, _stream_context, _log),
|
||||
_replies(_executor),
|
||||
_async_sender(*this),
|
||||
_active_span(_read_buff.cend(), _read_buff.cend()),
|
||||
_rec_channel(_executor, std::numeric_limits<size_t>::max()),
|
||||
_ping_timer(_executor),
|
||||
_sentry_timer(_executor)
|
||||
{
|
||||
_stream.clone_endpoints(other._stream);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
explicit client_service(
|
||||
const executor_type& ex,
|
||||
tls_context_type tls_context = {}, logger_type logger = {}
|
||||
) :
|
||||
_executor(ex),
|
||||
_log(std::move(logger)),
|
||||
_stream_context(std::move(tls_context)),
|
||||
_stream(ex, _stream_context, _log),
|
||||
_replies(ex),
|
||||
_async_sender(*this),
|
||||
_active_span(_read_buff.cend(), _read_buff.cend()),
|
||||
_rec_channel(ex, std::numeric_limits<size_t>::max()),
|
||||
_ping_timer(ex),
|
||||
_sentry_timer(ex)
|
||||
{}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
auto dup() const {
|
||||
return std::shared_ptr<client_service>(new client_service(*this));
|
||||
}
|
||||
|
||||
template <
|
||||
typename Ctx = TlsContext,
|
||||
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, bool> = true
|
||||
>
|
||||
decltype(auto) tls_context() {
|
||||
return _stream_context.tls_context();
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
if (!is_open())
|
||||
_stream_context.will(std::move(will));
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
if (!is_open())
|
||||
_stream_context.credentials(
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
);
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
if (!is_open())
|
||||
_stream.brokers(std::move(hosts), default_port);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Authenticator,
|
||||
std::enable_if_t<is_authenticator<Authenticator>, bool> = true
|
||||
>
|
||||
void authenticator(Authenticator&& authenticator) {
|
||||
if (!is_open())
|
||||
_stream_context.authenticator(
|
||||
std::forward<Authenticator>(authenticator)
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t negotiated_keep_alive() const {
|
||||
return connack_property(prop::server_keep_alive)
|
||||
.value_or(_stream_context.mqtt_context().keep_alive);
|
||||
}
|
||||
|
||||
void keep_alive(uint16_t seconds) {
|
||||
if (!is_open())
|
||||
_stream_context.mqtt_context().keep_alive = seconds;
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _stream_context.connect_property(prop);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
void connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop,
|
||||
prop::value_type_t<p> value
|
||||
){
|
||||
if (!is_open())
|
||||
_stream_context.connect_property(prop) = value;
|
||||
}
|
||||
|
||||
void connect_properties(connect_props props) {
|
||||
if (!is_open())
|
||||
_stream_context.connect_properties(std::move(props));
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _stream_context.connack_property(prop);
|
||||
}
|
||||
|
||||
const auto& connack_properties() const {
|
||||
return _stream_context.connack_properties();
|
||||
}
|
||||
|
||||
void open_stream() {
|
||||
_stream.open();
|
||||
}
|
||||
|
||||
bool is_open() const {
|
||||
return _stream.is_open();
|
||||
}
|
||||
|
||||
void close_stream() {
|
||||
_stream.close();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (!_stream.is_open()) return;
|
||||
|
||||
_ping_timer.cancel();
|
||||
_sentry_timer.cancel();
|
||||
|
||||
_rec_channel.close();
|
||||
_replies.cancel_unanswered();
|
||||
_async_sender.cancel();
|
||||
_stream.cancel();
|
||||
_stream.close();
|
||||
}
|
||||
|
||||
log_invoke<LoggerType>& log() {
|
||||
return _log;
|
||||
}
|
||||
|
||||
uint16_t allocate_pid() {
|
||||
return _pid_allocator.allocate();
|
||||
}
|
||||
|
||||
void free_pid(uint16_t pid, bool was_throttled = false) {
|
||||
_pid_allocator.free(pid);
|
||||
if (was_throttled)
|
||||
_async_sender.throttled_op_done();
|
||||
}
|
||||
|
||||
serial_num_t next_serial_num() {
|
||||
return _async_sender.next_serial_num();
|
||||
}
|
||||
|
||||
bool subscriptions_present() const {
|
||||
return _stream_context.session_state().subscriptions_present();
|
||||
}
|
||||
|
||||
void subscriptions_present(bool present) {
|
||||
_stream_context.session_state().subscriptions_present(present);
|
||||
}
|
||||
|
||||
void update_session_state() {
|
||||
auto& session_state = _stream_context.session_state();
|
||||
|
||||
if (!session_state.session_present()) {
|
||||
_replies.clear_pending_pubrels();
|
||||
session_state.session_present(true);
|
||||
|
||||
if (session_state.subscriptions_present()) {
|
||||
channel_store_error(client::error::session_expired);
|
||||
session_state.subscriptions_present(false);
|
||||
}
|
||||
}
|
||||
|
||||
_ping_timer.cancel();
|
||||
}
|
||||
|
||||
bool channel_store(decoders::publish_message message) {
|
||||
auto& [topic, packet_id, flags, props, payload] = message;
|
||||
return _rec_channel.try_send(
|
||||
error_code {}, std::move(topic),
|
||||
std::move(payload), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
bool channel_store_error(error_code ec) {
|
||||
return _rec_channel.try_send(
|
||||
ec, std::string {}, std::string {}, publish_props {}
|
||||
);
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_send(
|
||||
const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return _async_sender.async_send(
|
||||
buffer, serial_num, flags, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_assemble(CompletionToken&& token) {
|
||||
using Signature = void (error_code, uint8_t, byte_citer, byte_citer);
|
||||
|
||||
auto initiation = [] (
|
||||
auto handler, self_type& self,
|
||||
std::string& read_buff, data_span& active_span
|
||||
) {
|
||||
assemble_op {
|
||||
self, std::move(handler), read_buff, active_span
|
||||
}.perform(asio::transfer_at_least(0));
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature> (
|
||||
initiation, token, std::ref(*this),
|
||||
std::ref(_read_buff), std::ref(_active_span)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_wait_reply(
|
||||
control_code_e code, uint16_t packet_id, CompletionToken&& token
|
||||
) {
|
||||
return _replies.async_wait_reply(
|
||||
code, packet_id, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_channel_receive(CompletionToken&& token) {
|
||||
return _rec_channel.async_receive(std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CLIENT_SERVICE_HPP
|
||||
@@ -1,461 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_BASE_DECODERS_HPP
|
||||
#define ASYNC_MQTT5_BASE_DECODERS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/spirit/home/x3/binary/binary.hpp>
|
||||
#include <boost/fusion/adapted/std_tuple.hpp>
|
||||
#include <boost/fusion/container/deque.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/detail/traits.hpp>
|
||||
|
||||
namespace async_mqtt5::decoders {
|
||||
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
template <typename T>
|
||||
struct convert { using type = T; };
|
||||
|
||||
template <typename ...Args>
|
||||
struct convert<boost::fusion::deque<Args...>> {
|
||||
using type = std::tuple<typename convert<Args>::type...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct convert<boost::optional<T>> {
|
||||
using type = std::optional<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct convert<std::vector<T>> {
|
||||
using type = std::vector<typename convert<T>::type>;
|
||||
};
|
||||
|
||||
template <typename T, typename Parser>
|
||||
constexpr auto as(Parser&& p) {
|
||||
return x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
|
||||
template <typename It, typename Parser>
|
||||
auto type_parse(It& first, const It last, const Parser& p) {
|
||||
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Parser&>()));
|
||||
using attr_type = typename x3::traits::attribute_of<Parser, ctx_type>::type;
|
||||
|
||||
using rv_type = typename convert<attr_type>::type;
|
||||
|
||||
std::optional<rv_type> rv;
|
||||
rv_type value {};
|
||||
if (x3::phrase_parse(first, last, as<rv_type>(p), x3::eps(false), value))
|
||||
rv = std::move(value);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
template <typename AttributeType, typename It, typename Parser>
|
||||
auto type_parse(It& first, const It last, const Parser& p) {
|
||||
std::optional<AttributeType> rv;
|
||||
AttributeType value {};
|
||||
if (x3::phrase_parse(first, last, as<AttributeType>(p), x3::eps(false), value))
|
||||
rv = std::move(value);
|
||||
return rv;
|
||||
}
|
||||
|
||||
namespace basic {
|
||||
|
||||
template <typename T>
|
||||
constexpr auto to(T& arg) {
|
||||
return [&](auto& ctx) {
|
||||
using ctx_type = decltype(ctx);
|
||||
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
|
||||
if constexpr (detail::is_boost_iterator<attr_type>)
|
||||
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
|
||||
else
|
||||
arg = x3::_attr(ctx);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename LenParser, typename Subject, typename Enable = void>
|
||||
class scope_limit {};
|
||||
|
||||
template <typename LenParser, typename Subject>
|
||||
class scope_limit<
|
||||
LenParser, Subject,
|
||||
std::enable_if_t<x3::traits::is_parser<LenParser>::value>
|
||||
> :
|
||||
public x3::unary_parser<Subject, scope_limit<LenParser, Subject>>
|
||||
{
|
||||
using base_type = x3::unary_parser<Subject, scope_limit<LenParser, Subject>>;
|
||||
LenParser _lp;
|
||||
public:
|
||||
using ctx_type =
|
||||
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using attribute_type =
|
||||
typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
static bool const has_attribute = true;
|
||||
|
||||
scope_limit(const LenParser& lp, const Subject& subject) :
|
||||
base_type(subject), _lp(lp)
|
||||
{}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
typename x3::traits::attribute_of<LenParser, Ctx>::type len;
|
||||
if (!_lp.parse(iter, last, ctx, rctx, len))
|
||||
return false;
|
||||
if (iter + len > last)
|
||||
return false;
|
||||
|
||||
if (!base_type::subject.parse(iter, iter + len, ctx, rctx, attr))
|
||||
return false;
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Size, typename Subject>
|
||||
class scope_limit<
|
||||
Size, Subject,
|
||||
std::enable_if_t<std::is_arithmetic_v<Size>>
|
||||
> :
|
||||
public x3::unary_parser<Subject, scope_limit<Size, Subject>>
|
||||
{
|
||||
using base_type = x3::unary_parser<Subject, scope_limit<Size, Subject>>;
|
||||
size_t _limit;
|
||||
public:
|
||||
using ctx_type =
|
||||
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using attribute_type =
|
||||
typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
static bool const has_attribute = true;
|
||||
|
||||
scope_limit(Size limit, const Subject& subject) :
|
||||
base_type(subject), _limit(limit)
|
||||
{}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
if (iter + _limit > last)
|
||||
return false;
|
||||
|
||||
if (!base_type::subject.parse(iter, iter + _limit, ctx, rctx, attr))
|
||||
return false;
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename LenParser, typename Enable = void>
|
||||
struct scope_limit_gen {
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return scope_limit<LenParser, Subject> { _lp, x3::as_parser(p) };
|
||||
}
|
||||
LenParser _lp;
|
||||
};
|
||||
|
||||
template <typename Size>
|
||||
struct scope_limit_gen<
|
||||
Size,
|
||||
std::enable_if_t<std::is_arithmetic_v<Size>>
|
||||
> {
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return scope_limit<Size, Subject> { limit, x3::as_parser(p) };
|
||||
}
|
||||
Size limit;
|
||||
};
|
||||
|
||||
template <
|
||||
typename Parser,
|
||||
std::enable_if_t<x3::traits::is_parser<Parser>::value, bool> = true
|
||||
>
|
||||
scope_limit_gen<Parser> scope_limit_(const Parser& p) {
|
||||
return { p };
|
||||
}
|
||||
|
||||
template <
|
||||
typename Size,
|
||||
std::enable_if_t<std::is_arithmetic_v<Size>, bool> = true
|
||||
>
|
||||
scope_limit_gen<Size> scope_limit_(Size limit) {
|
||||
return { limit };
|
||||
}
|
||||
|
||||
struct verbatim_parser : x3::parser<verbatim_parser> {
|
||||
using attribute_type = std::string;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx&, RCtx&, Attr& attr) const {
|
||||
attr = std::string { first, last };
|
||||
first = last;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto verbatim_ = verbatim_parser{};
|
||||
|
||||
struct varint_parser : x3::parser<varint_parser> {
|
||||
using attribute_type = int32_t;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx&, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
if (iter == last)
|
||||
return false;
|
||||
|
||||
int32_t result = 0; unsigned bit_shift = 0;
|
||||
|
||||
for (; iter != last && bit_shift < sizeof(int32_t) * 7; ++iter) {
|
||||
auto val = *iter;
|
||||
if (val & 0b1000'0000u) {
|
||||
result |= (val & 0b0111'1111u) << bit_shift;
|
||||
bit_shift += 7;
|
||||
}
|
||||
else {
|
||||
result |= (static_cast<int32_t>(val) << bit_shift);
|
||||
bit_shift = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bit_shift)
|
||||
return false;
|
||||
|
||||
attr = result;
|
||||
first = ++iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr varint_parser varint_{};
|
||||
|
||||
struct len_prefix_parser : x3::parser<len_prefix_parser> {
|
||||
using attribute_type = std::string;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
typename x3::traits::attribute_of<decltype(x3::big_word), Ctx>::type len;
|
||||
if (x3::big_word.parse(iter, last, ctx, rctx, len)) {
|
||||
if (std::distance(iter, last) < len)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
attr = std::string(iter, iter + len);
|
||||
first = iter + len;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr len_prefix_parser utf8_ {};
|
||||
constexpr len_prefix_parser binary_ {};
|
||||
|
||||
/*
|
||||
Boost Spirit incorrectly deduces atribute type for a parser of the form
|
||||
(eps(a) | parser1) >> (eps(b) | parser)
|
||||
and we had to create if_ parser to remedy the issue
|
||||
*/
|
||||
|
||||
template <typename Subject>
|
||||
class conditional_parser :
|
||||
public x3::unary_parser<Subject, conditional_parser<Subject>> {
|
||||
|
||||
using base_type = x3::unary_parser<Subject, conditional_parser<Subject>>;
|
||||
bool _condition;
|
||||
public:
|
||||
using ctx_type =
|
||||
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using subject_attr_type =
|
||||
typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
using attribute_type = boost::optional<subject_attr_type>;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
conditional_parser(const Subject& s, bool condition) :
|
||||
base_type(s), _condition(condition) {}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
if (!_condition)
|
||||
return true;
|
||||
|
||||
It iter = first;
|
||||
subject_attr_type sattr {};
|
||||
if (!base_type::subject.parse(iter, last, ctx, rctx, sattr))
|
||||
return false;
|
||||
|
||||
attr.emplace(std::move(sattr));
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct conditional_gen {
|
||||
bool _condition;
|
||||
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return conditional_parser<Subject> { p, _condition };
|
||||
}
|
||||
};
|
||||
|
||||
inline conditional_gen if_(bool condition) {
|
||||
return { condition };
|
||||
}
|
||||
|
||||
} // end namespace basic
|
||||
|
||||
|
||||
namespace prop {
|
||||
|
||||
namespace basic = async_mqtt5::decoders::basic;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Prop>
|
||||
bool parse_to_prop(
|
||||
It& iter, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Prop& prop
|
||||
) {
|
||||
using namespace async_mqtt5::detail;
|
||||
using prop_type = std::remove_reference_t<decltype(prop)>;
|
||||
|
||||
bool rv = false;
|
||||
|
||||
if constexpr (std::is_same_v<prop_type, uint8_t>)
|
||||
rv = x3::byte_.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, uint16_t>)
|
||||
rv = x3::big_word.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, int32_t>)
|
||||
rv = basic::varint_.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, uint32_t>)
|
||||
rv = x3::big_dword.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, std::string>)
|
||||
rv = basic::utf8_.parse(iter, last, ctx, rctx, prop);
|
||||
|
||||
else if constexpr (is_optional<prop_type>) {
|
||||
typename prop_type::value_type val;
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, val);
|
||||
if (rv) prop.emplace(std::move(val));
|
||||
}
|
||||
|
||||
else if constexpr (is_pair<prop_type>) {
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, prop.first);
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, prop.second);
|
||||
}
|
||||
|
||||
else if constexpr (is_vector<prop_type> || is_small_vector<prop_type>) {
|
||||
typename std::remove_reference_t<prop_type>::value_type value;
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, value);
|
||||
if (rv) prop.push_back(std::move(value));
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <typename Props>
|
||||
class prop_parser : public x3::parser<prop_parser<Props>> {
|
||||
public:
|
||||
using attribute_type = Props;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
if (iter == last)
|
||||
return true;
|
||||
|
||||
int32_t props_length;
|
||||
if (!basic::varint_.parse(iter, last, ctx, rctx, props_length))
|
||||
return false;
|
||||
|
||||
const It scoped_last = iter + props_length;
|
||||
// attr = Props{};
|
||||
|
||||
while (iter < scoped_last) {
|
||||
uint8_t prop_id = *iter++;
|
||||
bool rv = true;
|
||||
It saved = iter;
|
||||
|
||||
attr.apply_on(
|
||||
prop_id,
|
||||
[&rv, &iter, scoped_last, &ctx, &rctx](auto& prop) {
|
||||
rv = detail::parse_to_prop(
|
||||
iter, scoped_last, ctx, rctx, prop
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// either rv = false or property with prop_id was not found
|
||||
if (!rv || iter == saved)
|
||||
return false;
|
||||
}
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Props>
|
||||
constexpr auto props_ = prop_parser<Props> {};
|
||||
|
||||
} // end namespace prop
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::decoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_BASE_DECODERS_HPP
|
||||
@@ -1,513 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_BASE_ENCODERS_HPP
|
||||
#define ASYNC_MQTT5_BASE_ENCODERS_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/core/identity.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/type_traits/is_detected_exact.hpp>
|
||||
#include <boost/type_traits/remove_cv_ref.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/detail/traits.hpp>
|
||||
|
||||
namespace async_mqtt5::encoders {
|
||||
|
||||
namespace basic {
|
||||
|
||||
using varint_t = int*;
|
||||
|
||||
inline void to_variable_bytes(std::string& s, int32_t val) {
|
||||
if (val > 0xfffffff) return;
|
||||
while (val > 127) {
|
||||
s.push_back(char((val & 0b01111111) | 0b10000000));
|
||||
val >>= 7;
|
||||
}
|
||||
s.push_back(val & 0b01111111);
|
||||
}
|
||||
|
||||
inline size_t variable_length(int32_t val) {
|
||||
if (val > 0xfffffff) return 0;
|
||||
size_t rv = 1;
|
||||
for (; val > 127; ++rv) val >>= 7;
|
||||
return rv;
|
||||
}
|
||||
|
||||
struct encoder {};
|
||||
|
||||
template <size_t bits, typename repr = uint8_t>
|
||||
class flag_def : public encoder {
|
||||
template <size_t num_bits>
|
||||
using least_type = std::conditional_t<
|
||||
num_bits <= 8, uint8_t,
|
||||
std::conditional_t<
|
||||
num_bits <= 16, uint16_t,
|
||||
std::conditional_t<
|
||||
num_bits <= 32, uint32_t,
|
||||
std::conditional_t<num_bits <= 64, uint64_t, void>
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
template <size_t oth_bits, typename oth_repr>
|
||||
friend class flag_def;
|
||||
|
||||
repr _val { 0 };
|
||||
|
||||
public:
|
||||
flag_def(repr val) : _val(val) {}
|
||||
flag_def() = default;
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename projection = boost::identity,
|
||||
std::enable_if_t<detail::is_optional<T>, bool> = true
|
||||
>
|
||||
auto operator()(T&& value, projection proj = {}) const {
|
||||
if constexpr (std::is_same_v<projection, boost::identity>) {
|
||||
repr val = value.has_value();
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
else {
|
||||
repr val = value.has_value() ?
|
||||
static_cast<repr>(std::invoke(proj, *value)) : 0;
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename projection = boost::identity,
|
||||
std::enable_if_t<!detail::is_optional<T>, bool> = true
|
||||
>
|
||||
auto operator()(T&& value, projection proj = {}) const {
|
||||
auto val = static_cast<repr>(std::invoke(proj, value));
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
|
||||
size_t byte_size() const { return sizeof(repr); }
|
||||
|
||||
template <size_t rhs_bits, typename rhs_repr>
|
||||
auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
|
||||
using res_repr = least_type<bits + rhs_bits>;
|
||||
auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
|
||||
return flag_def<bits + rhs_bits, res_repr> { val };
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
using namespace boost::endian;
|
||||
size_t sz = s.size(); s.resize(sz + sizeof(repr));
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<repr, sizeof(repr), order::big>(p, _val);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t bits, typename repr = uint8_t>
|
||||
constexpr auto flag = flag_def<bits, repr>{};
|
||||
|
||||
|
||||
template <typename T, typename Repr>
|
||||
class int_val : public encoder {
|
||||
T _val;
|
||||
public:
|
||||
int_val(T val) : _val(val) {}
|
||||
|
||||
size_t byte_size() const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (_val) return val_length(*_val);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return val_length(_val);
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (_val) return encode_val(s, *_val);
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return encode_val(s, _val);
|
||||
}
|
||||
private:
|
||||
template <typename U>
|
||||
static size_t val_length(U&& val) {
|
||||
if constexpr (std::is_same_v<Repr, varint_t>)
|
||||
return variable_length(int32_t(val));
|
||||
else
|
||||
return sizeof(Repr);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static std::string& encode_val(std::string& s, U&& val) {
|
||||
using namespace boost::endian;
|
||||
if constexpr (std::is_same_v<Repr, varint_t>) {
|
||||
to_variable_bytes(s, int32_t(val));
|
||||
return s;
|
||||
}
|
||||
else {
|
||||
size_t sz = s.size(); s.resize(sz + sizeof(Repr));
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<Repr, sizeof(Repr), order::big>(p, val);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Repr>
|
||||
class int_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& val) const {
|
||||
return int_val<T, Repr> { std::forward<T>(val) };
|
||||
}
|
||||
|
||||
template <typename T, typename projection>
|
||||
auto operator()(T&& val, projection proj) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
using rv_type = std::invoke_result_t<
|
||||
projection, typename boost::remove_cv_ref_t<T>::value_type
|
||||
>;
|
||||
if (val.has_value())
|
||||
return (*this)(std::invoke(proj, *val));
|
||||
return int_val<rv_type, Repr> { rv_type {} };
|
||||
}
|
||||
else {
|
||||
using rv_type = std::invoke_result_t<projection, T>;
|
||||
return int_val<rv_type, Repr> { std::invoke(proj, val) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto byte_ = int_def<uint8_t> {};
|
||||
constexpr auto int16_ = int_def<uint16_t> {};
|
||||
constexpr auto int32_ = int_def<uint32_t> {};
|
||||
constexpr auto varlen_ = int_def<varint_t> {};
|
||||
|
||||
|
||||
template <typename T>
|
||||
class array_val : public encoder {
|
||||
T _val;
|
||||
bool _with_length;
|
||||
public:
|
||||
array_val(T val, bool with_length) :
|
||||
_val(val), _with_length(with_length)
|
||||
{
|
||||
static_assert(
|
||||
std::is_reference_v<T> || std::is_same_v<T, std::string_view>
|
||||
);
|
||||
}
|
||||
|
||||
size_t byte_size() const {
|
||||
if constexpr (detail::is_optional<T>)
|
||||
return _val ? _with_length * 2 + val_length(*_val) : 0;
|
||||
else
|
||||
return _with_length * 2 + val_length(_val);
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (_val) return encode_val(s, *_val);
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return encode_val(s, _val);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename V>
|
||||
using has_size = decltype(std::declval<V&>().size());
|
||||
|
||||
template <typename U>
|
||||
static size_t val_length(U&& val) {
|
||||
if constexpr (std::is_same_v<boost::remove_cv_ref_t<U>, const char*>)
|
||||
return std::strlen(val);
|
||||
|
||||
if constexpr (boost::is_detected_exact_v<size_t, has_size, U>)
|
||||
return val.size();
|
||||
else // fallback to type const char (&)[N] (substract 1 for trailing 0)
|
||||
return sizeof(val) - 1;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
std::string& encode_val(std::string& s, U&& u) const {
|
||||
using namespace boost::endian;
|
||||
auto byte_len = val_length(std::forward<U>(u));
|
||||
if (byte_len == 0 && !_with_length) return s;
|
||||
if (_with_length) {
|
||||
size_t sz = s.size(); s.resize(sz + 2);
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<int16_t, sizeof(int16_t), order::big>(
|
||||
p, int16_t(byte_len)
|
||||
);
|
||||
}
|
||||
s.append(std::begin(u), std::begin(u) + byte_len);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool with_length = true>
|
||||
class array_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& val) const {
|
||||
return array_val<T> { std::forward<T>(val), with_length };
|
||||
}
|
||||
|
||||
template <typename T, typename projection>
|
||||
auto operator()(T&& val, projection proj) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
using rv_type = std::invoke_result_t<
|
||||
projection, typename boost::remove_cv_ref_t<T>::value_type
|
||||
>;
|
||||
if (val.has_value())
|
||||
return (*this)(std::invoke(proj, *val));
|
||||
return array_val<rv_type> { rv_type {}, false };
|
||||
}
|
||||
else {
|
||||
const auto& av = std::invoke(proj, val);
|
||||
return array_val<T> { av, true };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using utf8_def = array_def<true>;
|
||||
|
||||
constexpr auto utf8_ = utf8_def {};
|
||||
constexpr auto binary_ = array_def<true> {}; // for now
|
||||
constexpr auto verbatim_ = array_def<false> {};
|
||||
|
||||
|
||||
template <typename T, typename U>
|
||||
class composed_val : public encoder {
|
||||
T _lhs; U _rhs;
|
||||
public:
|
||||
composed_val(T lhs, U rhs) :
|
||||
_lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs))
|
||||
{}
|
||||
|
||||
size_t byte_size() const {
|
||||
return _lhs.byte_size() + _rhs.byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
_lhs.encode(s);
|
||||
return _rhs.encode(s);
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename T, typename U,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<encoder, std::decay_t<T>> &&
|
||||
std::is_base_of_v<encoder, std::decay_t<U>>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
inline auto operator&(T&& t, U&& u) {
|
||||
return composed_val(std::forward<T>(t), std::forward<U>(u));
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
std::enable_if_t<std::is_base_of_v<encoder, std::decay_t<T>>, bool> = true
|
||||
>
|
||||
std::string& operator<<(std::string& s, T&& t) {
|
||||
return t.encode(s);
|
||||
}
|
||||
|
||||
} // end namespace basic
|
||||
|
||||
namespace prop {
|
||||
|
||||
namespace pp = async_mqtt5::prop;
|
||||
|
||||
template <typename T>
|
||||
auto encoder_for_prop_value(const T& val) {
|
||||
if constexpr (std::is_same_v<T, uint8_t>)
|
||||
return basic::int_def<uint8_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, uint16_t>)
|
||||
return basic::int_def<uint16_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, int32_t>)
|
||||
return basic::int_def<basic::varint_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, uint32_t>)
|
||||
return basic::int_def<uint32_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
return basic::utf8_def{}(val);
|
||||
else if constexpr (detail::is_pair<T>)
|
||||
return encoder_for_prop_value(val.first) &
|
||||
encoder_for_prop_value(val.second);
|
||||
}
|
||||
|
||||
template <typename T, pp::property_type p, typename Enable = void>
|
||||
class prop_val;
|
||||
|
||||
template <
|
||||
typename T, pp::property_type p
|
||||
>
|
||||
class prop_val<
|
||||
T, p,
|
||||
std::enable_if_t<!detail::is_vector<T> && detail::is_optional<T>>
|
||||
> : public basic::encoder {
|
||||
// allows T to be reference type to std::optional
|
||||
static inline boost::remove_cv_ref_t<T> nulltype;
|
||||
T _val;
|
||||
public:
|
||||
prop_val(T val) : _val(val) {
|
||||
static_assert(std::is_reference_v<T>);
|
||||
}
|
||||
prop_val() : _val(nulltype) {}
|
||||
|
||||
size_t byte_size() const {
|
||||
if (!_val) return 0;
|
||||
return 1 + encoder_for_prop_value(*_val).byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if (!_val)
|
||||
return s;
|
||||
s.push_back(p);
|
||||
return encoder_for_prop_value(*_val).encode(s);
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename T, pp::property_type p
|
||||
>
|
||||
class prop_val<
|
||||
T, p,
|
||||
std::enable_if_t<detail::is_vector<T> || detail::is_small_vector<T>>
|
||||
> : public basic::encoder {
|
||||
// allows T to be reference type to std::vector
|
||||
static inline boost::remove_cv_ref_t<T> nulltype;
|
||||
T _val;
|
||||
public:
|
||||
prop_val(T val) : _val(val) {
|
||||
static_assert(std::is_reference_v<T>);
|
||||
}
|
||||
|
||||
prop_val() : _val(nulltype) { }
|
||||
|
||||
size_t byte_size() const {
|
||||
if (_val.empty()) return 0;
|
||||
|
||||
size_t total_size = 0;
|
||||
for (const auto& elem : _val)
|
||||
total_size += 1 + encoder_for_prop_value(elem).byte_size();
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if (_val.empty())
|
||||
return s;
|
||||
|
||||
for (const auto& elem: _val) {
|
||||
s.push_back(p);
|
||||
encoder_for_prop_value(elem).encode(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Props>
|
||||
class props_val : public basic::encoder {
|
||||
static inline std::decay_t<Props> nulltype;
|
||||
|
||||
template <pp::property_type P, typename T>
|
||||
static auto to_prop_val(const T& val) {
|
||||
return prop_val<const T&, P>(val);
|
||||
}
|
||||
|
||||
template <pp::property_type ...Ps>
|
||||
static auto to_prop_vals(const pp::properties<Ps...>& props) {
|
||||
return std::make_tuple(
|
||||
to_prop_val<Ps>(
|
||||
props[std::integral_constant<pp::property_type, Ps> {}]
|
||||
)...
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
auto apply_each(Func&& func) const {
|
||||
return std::apply([&func](const auto&... props) {
|
||||
return (std::invoke(func, props), ...);
|
||||
}, _prop_vals);
|
||||
}
|
||||
|
||||
decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
|
||||
bool _may_omit;
|
||||
|
||||
public:
|
||||
props_val(Props val, bool may_omit) :
|
||||
_prop_vals(to_prop_vals(val)), _may_omit(may_omit)
|
||||
{
|
||||
static_assert(std::is_reference_v<Props>);
|
||||
}
|
||||
props_val(bool may_omit) :
|
||||
_prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit)
|
||||
{}
|
||||
|
||||
size_t byte_size() const {
|
||||
size_t psize = props_size();
|
||||
if (_may_omit && psize == 0) return 0;
|
||||
return psize + basic::varlen_(psize).byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
size_t psize = props_size();
|
||||
if (_may_omit && psize == 0) return s;
|
||||
basic::varlen_(psize).encode(s);
|
||||
apply_each([&s](const auto& pv) { return pv.encode(s); });
|
||||
return s;
|
||||
}
|
||||
private:
|
||||
size_t props_size() const {
|
||||
size_t retval = 0;
|
||||
apply_each([&retval](const auto& pv) {
|
||||
return retval += pv.byte_size();
|
||||
});
|
||||
return retval;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool may_omit>
|
||||
class props_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& prop_container) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (prop_container.has_value())
|
||||
return (*this)(*prop_container);
|
||||
return props_val<
|
||||
const typename boost::remove_cv_ref_t<T>::value_type&
|
||||
>(true);
|
||||
}
|
||||
else {
|
||||
return props_val<T> { prop_container, may_omit };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto props_ = props_def<false> {};
|
||||
constexpr auto props_may_omit_ = props_def<true> {};
|
||||
|
||||
} // end namespace prop
|
||||
|
||||
} // end namespace async_mqtt5::encoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_BASE_ENCODERS_HPP
|
||||
@@ -1,307 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_MESSAGE_DECODERS_HPP
|
||||
#define ASYNC_MQTT5_MESSAGE_DECODERS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/impl/codecs/base_decoders.hpp>
|
||||
|
||||
namespace async_mqtt5::decoders {
|
||||
|
||||
using byte_citer = detail::byte_citer;
|
||||
|
||||
using fixed_header = std::tuple<
|
||||
uint8_t, // control byte
|
||||
uint32_t // remaining_length
|
||||
>;
|
||||
|
||||
inline std::optional<fixed_header> decode_fixed_header(
|
||||
byte_citer& it, const byte_citer last
|
||||
) {
|
||||
auto fixed_header_ = x3::byte_ >> basic::varint_;
|
||||
return type_parse(it, last, fixed_header_);
|
||||
}
|
||||
|
||||
using packet_id = uint16_t;
|
||||
|
||||
inline std::optional<packet_id> decode_packet_id(
|
||||
byte_citer& it
|
||||
) {
|
||||
auto packet_id_ = x3::big_word;
|
||||
return type_parse(it, it + sizeof(uint16_t), packet_id_);
|
||||
}
|
||||
|
||||
using connect_message = std::tuple<
|
||||
std::string, // client_id,
|
||||
std::optional<std::string>, // user_name,
|
||||
std::optional<std::string>, // password,
|
||||
uint16_t, // keep_alive,
|
||||
bool, // clean_start,
|
||||
connect_props, // props,
|
||||
std::optional<will> // will
|
||||
>;
|
||||
|
||||
inline std::optional<connect_message> decode_connect(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto var_header_ =
|
||||
basic::utf8_ >> // MQTT
|
||||
x3::byte_ >> // (num 5)
|
||||
x3::byte_ >> // conn_flags_
|
||||
x3::big_word >> // keep_alive
|
||||
prop::props_<connect_props>;
|
||||
|
||||
const byte_citer end = it + remain_length;
|
||||
auto vh = type_parse(it, end, var_header_);
|
||||
if (!vh)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh;
|
||||
|
||||
if (mqtt_str != "MQTT" || version != 5)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
bool has_will = (flags & 0b00000100);
|
||||
bool has_uname = (flags & 0b10000000);
|
||||
bool has_pwd = (flags & 0b01000000);
|
||||
|
||||
auto payload_ =
|
||||
basic::utf8_ >> // client_id
|
||||
basic::if_(has_will)[prop::props_<will_props>] >>
|
||||
basic::if_(has_will)[basic::utf8_] >> // will topic
|
||||
basic::if_(has_will)[basic::binary_] >> // will message
|
||||
basic::if_(has_uname)[basic::utf8_] >> // username
|
||||
basic::if_(has_pwd)[basic::utf8_]; // password
|
||||
|
||||
auto pload = type_parse(it, end, payload_);
|
||||
if (!pload)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
std::optional<will> w;
|
||||
|
||||
if (has_will)
|
||||
w.emplace(
|
||||
std::move(*std::get<2>(*pload)), // will_topic
|
||||
std::move(*std::get<3>(*pload)), // will_message
|
||||
qos_e((flags & 0b00011000) >> 3),
|
||||
retain_e((flags & 0b00100000) >> 5),
|
||||
std::move(*std::get<1>(*pload)) // will props
|
||||
);
|
||||
|
||||
connect_message retval = {
|
||||
std::move(std::get<0>(*pload)), // client_id
|
||||
std::move(std::get<4>(*pload)), // user_name
|
||||
std::move(std::get<5>(*pload)), // password
|
||||
keep_alive,
|
||||
flags & 0b00000010, // clean_start
|
||||
std::move(cprops), // connect_props
|
||||
std::move(w) // will
|
||||
};
|
||||
|
||||
return std::optional<connect_message> { std::move(retval) };
|
||||
}
|
||||
|
||||
using connack_message = std::tuple<
|
||||
uint8_t, // session_present
|
||||
uint8_t, // connect reason code
|
||||
connack_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<connack_message> decode_connack(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto connack_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> x3::byte_ >> prop::props_<connack_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, connack_);
|
||||
}
|
||||
|
||||
using publish_message = std::tuple<
|
||||
std::string, // topic
|
||||
std::optional<uint16_t>, // packet_id
|
||||
uint8_t, // dup_e, qos_e, retain_e
|
||||
publish_props, // publish props
|
||||
std::string // payload
|
||||
>;
|
||||
|
||||
inline std::optional<publish_message> decode_publish(
|
||||
uint8_t control_byte, uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
uint8_t flags = control_byte & 0b1111;
|
||||
auto qos = qos_e((flags >> 1) & 0b11);
|
||||
|
||||
auto publish_ = basic::scope_limit_(remain_length)[
|
||||
basic::utf8_ >> basic::if_(qos != qos_e::at_most_once)[x3::big_word] >>
|
||||
x3::attr(flags) >> prop::props_<publish_props> >> basic::verbatim_
|
||||
];
|
||||
return type_parse(it, it + remain_length, publish_);
|
||||
}
|
||||
|
||||
using puback_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
puback_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<puback_message> decode_puback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return puback_message {};
|
||||
auto puback_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<puback_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, puback_);
|
||||
}
|
||||
|
||||
using pubrec_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubrec_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubrec_message> decode_pubrec(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return pubrec_message {};
|
||||
auto pubrec_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubrec_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubrec_);
|
||||
}
|
||||
|
||||
using pubrel_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubrel_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubrel_message> decode_pubrel(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return pubrel_message {};
|
||||
auto pubrel_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubrel_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubrel_);
|
||||
}
|
||||
|
||||
using pubcomp_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubcomp_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubcomp_message> decode_pubcomp(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return pubcomp_message {};
|
||||
auto pubcomp_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubcomp_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubcomp_);
|
||||
}
|
||||
|
||||
using subscribe_message = std::tuple<
|
||||
subscribe_props,
|
||||
std::vector<std::tuple<std::string, uint8_t>> // topic filter with opts
|
||||
>;
|
||||
|
||||
inline std::optional<subscribe_message> decode_subscribe(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto subscribe_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<subscribe_props> >> +(basic::utf8_ >> x3::byte_)
|
||||
];
|
||||
return type_parse(it, it + remain_length, subscribe_);
|
||||
}
|
||||
|
||||
using suback_message = std::tuple<
|
||||
suback_props,
|
||||
std::vector<uint8_t> // reason_codes
|
||||
>;
|
||||
|
||||
inline std::optional<suback_message> decode_suback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto suback_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<suback_props> >> +x3::byte_
|
||||
];
|
||||
return type_parse(it, it + remain_length, suback_);
|
||||
}
|
||||
|
||||
using unsubscribe_message = std::tuple<
|
||||
unsubscribe_props,
|
||||
std::vector<std::string> // topics
|
||||
>;
|
||||
|
||||
inline std::optional<unsubscribe_message> decode_unsubscribe(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto unsubscribe_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<unsubscribe_props> >> +basic::utf8_
|
||||
];
|
||||
return type_parse(it, it + remain_length, unsubscribe_);
|
||||
}
|
||||
|
||||
using unsuback_message = std::tuple<
|
||||
unsuback_props,
|
||||
std::vector<uint8_t> // reason_codes
|
||||
>;
|
||||
|
||||
inline std::optional<unsuback_message> decode_unsuback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto unsuback_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<unsuback_props> >> +x3::byte_
|
||||
];
|
||||
return type_parse(it, it + remain_length, unsuback_);
|
||||
}
|
||||
|
||||
using disconnect_message = std::tuple<
|
||||
uint8_t, // reason_code
|
||||
disconnect_props
|
||||
>;
|
||||
|
||||
inline std::optional<disconnect_message> decode_disconnect(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return disconnect_message {};
|
||||
auto disconnect_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<disconnect_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, disconnect_);
|
||||
}
|
||||
|
||||
using auth_message = std::tuple<
|
||||
uint8_t, // reason_code
|
||||
auth_props
|
||||
>;
|
||||
|
||||
inline std::optional<auth_message> decode_auth(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return auth_message {};
|
||||
auto auth_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<auth_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, auth_);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::decoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_MESSAGE_DECODERS_HPP
|
||||
@@ -1,431 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
#define ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/impl/codecs/base_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::encoders {
|
||||
|
||||
template <typename encoder>
|
||||
std::string encode(const encoder& e) {
|
||||
std::string s;
|
||||
s.reserve(e.byte_size());
|
||||
s << e;
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_connect(
|
||||
std::string_view client_id,
|
||||
std::optional<std::string_view> user_name,
|
||||
std::optional<std::string_view> password,
|
||||
uint16_t keep_alive, bool clean_start,
|
||||
const connect_props& props,
|
||||
const std::optional<will>& w
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0001) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto conn_flags_ =
|
||||
basic::flag<1>(user_name) |
|
||||
basic::flag<1>(password) |
|
||||
basic::flag<1>(w, &will::retain) |
|
||||
basic::flag<2>(w, &will::qos) |
|
||||
basic::flag<1>(w) |
|
||||
basic::flag<1>(clean_start) |
|
||||
basic::flag<1>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::utf8_("MQTT") &
|
||||
basic::byte_(uint8_t(5)) &
|
||||
conn_flags_ &
|
||||
basic::int16_(keep_alive) &
|
||||
prop::props_(props);
|
||||
|
||||
auto payload_ =
|
||||
basic::utf8_(client_id) &
|
||||
prop::props_(w) &
|
||||
basic::utf8_(w, &will::topic) &
|
||||
basic::binary_(w, &will::message) &
|
||||
basic::utf8_(user_name) &
|
||||
basic::utf8_(password);
|
||||
|
||||
auto message_body_ = var_header_ & payload_;
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(message_body_.byte_size());
|
||||
|
||||
auto connect_message_ = fixed_header_ & message_body_;
|
||||
|
||||
return encode(connect_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_connack(
|
||||
bool session_present,
|
||||
uint8_t reason_code,
|
||||
const connack_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0010) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::flag<1>(session_present) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto connack_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(connack_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_publish(
|
||||
uint16_t packet_id,
|
||||
std::string_view topic_name,
|
||||
std::string_view payload,
|
||||
qos_e qos, retain_e retain, dup_e dup,
|
||||
const publish_props& props
|
||||
) {
|
||||
|
||||
std::optional<uint16_t> used_packet_id;
|
||||
if (qos != qos_e::at_most_once) used_packet_id.emplace(packet_id);
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0011) |
|
||||
basic::flag<1>(dup) |
|
||||
basic::flag<2>(qos) |
|
||||
basic::flag<1>(retain);
|
||||
|
||||
auto var_header_ =
|
||||
basic::utf8_(topic_name) &
|
||||
basic::int16_(used_packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_body_ = var_header_ & basic::verbatim_(payload);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(message_body_.byte_size());
|
||||
|
||||
auto publish_message_ = fixed_header_ & message_body_;
|
||||
|
||||
return encode(publish_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_puback(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const puback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0100) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto puback_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(puback_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubrec(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubrec_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0101) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubrec_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubrec_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubrel(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubrel_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0110) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubrel_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubrel_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubcomp(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubcomp_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0111) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubcomp_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubcomp_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_subscribe(
|
||||
uint16_t packet_id,
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1000) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
size_t payload_size = 0;
|
||||
for (const auto& [topic_filter, _]: topics)
|
||||
payload_size += basic::utf8_(topic_filter).byte_size() + 1;
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + payload_size) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + payload_size);
|
||||
|
||||
for (const auto& [topic_filter, sub_opts]: topics) {
|
||||
auto opts_ =
|
||||
basic::flag<2>(sub_opts.retain_handling) |
|
||||
basic::flag<1>(sub_opts.retain_as_published) |
|
||||
basic::flag<1>(sub_opts.no_local) |
|
||||
basic::flag<2>(sub_opts.max_qos);
|
||||
auto filter_ = basic::utf8_(topic_filter) & opts_;
|
||||
s << filter_;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_suback(
|
||||
uint16_t packet_id,
|
||||
const std::vector<uint8_t>& reason_codes,
|
||||
const suback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1001) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + reason_codes.size());
|
||||
|
||||
for (auto reason_code: reason_codes)
|
||||
s << basic::byte_(reason_code);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_unsubscribe(
|
||||
uint16_t packet_id,
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1010) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
size_t payload_size = 0;
|
||||
for (const auto& topic: topics)
|
||||
payload_size += basic::utf8_(topic).byte_size();
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + payload_size) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + payload_size);
|
||||
|
||||
for (const auto& topic: topics)
|
||||
s << basic::utf8_(topic);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_unsuback(
|
||||
uint16_t packet_id,
|
||||
const std::vector<uint8_t>& reason_codes,
|
||||
const unsuback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1011) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + reason_codes.size());
|
||||
|
||||
for (auto reason_code: reason_codes)
|
||||
s << basic::byte_(reason_code);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_pingreq() {
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1100) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto remaining_len_ =
|
||||
basic::byte_(uint8_t(0));
|
||||
|
||||
auto ping_req_ = packet_type_ & remaining_len_;
|
||||
|
||||
return encode(ping_req_);
|
||||
}
|
||||
|
||||
inline std::string encode_pingresp() {
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1101) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto remaining_len_ =
|
||||
basic::byte_(uint8_t(0));
|
||||
|
||||
auto ping_resp_ = packet_type_ & remaining_len_;
|
||||
|
||||
return encode(ping_resp_);
|
||||
}
|
||||
|
||||
inline std::string encode_disconnect(
|
||||
uint8_t reason_code,
|
||||
const disconnect_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1110) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto disconnect_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(disconnect_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_auth(
|
||||
uint8_t reason_code,
|
||||
const auth_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1111) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto auth_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(auth_message_);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::encoders
|
||||
|
||||
#endif // !ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
@@ -1,436 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_CONNECT_OP_HPP
|
||||
#define ASYNC_MQTT5_CONNECT_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
template <typename Stream, typename LoggerType>
|
||||
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_packet {};
|
||||
struct on_init_auth_data {};
|
||||
struct on_auth_data {};
|
||||
struct on_send_auth {};
|
||||
struct on_complete_auth {};
|
||||
|
||||
Stream& _stream;
|
||||
mqtt_ctx& _ctx;
|
||||
log_invoke<LoggerType>& _log;
|
||||
|
||||
using handler_type = asio::any_completion_handler<void (error_code)>;
|
||||
handler_type _handler;
|
||||
|
||||
std::unique_ptr<std::string> _buffer_ptr;
|
||||
asio::cancellation_state _cancellation_state;
|
||||
|
||||
using endpoint = asio::ip::tcp::endpoint;
|
||||
|
||||
public:
|
||||
template <typename Handler>
|
||||
connect_op(
|
||||
Stream& stream, mqtt_ctx& ctx,
|
||||
log_invoke<LoggerType>& log,
|
||||
Handler&& handler
|
||||
) :
|
||||
_stream(stream), _ctx(ctx), _log(log),
|
||||
_handler(std::forward<Handler>(handler)),
|
||||
_cancellation_state(
|
||||
asio::get_associated_cancellation_slot(_handler),
|
||||
asio::enable_total_cancellation {},
|
||||
asio::enable_total_cancellation {}
|
||||
)
|
||||
{}
|
||||
|
||||
connect_op(connect_op&&) = default;
|
||||
connect_op(const connect_op&) = delete;
|
||||
|
||||
connect_op& operator=(connect_op&&) = default;
|
||||
connect_op& operator=(const connect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return _cancellation_state.slot();
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
void perform(const endpoint& ep, authority_path ap) {
|
||||
lowest_layer(_stream).async_connect(
|
||||
ep,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_connect {}),
|
||||
ep, std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_connect, error_code ec, endpoint ep, authority_path ap
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
_log.at_tcp_connect(ec, ep);
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
do_tls_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void do_tls_handshake(endpoint ep, authority_path ap) {
|
||||
if constexpr (has_tls_handshake<Stream>) {
|
||||
_stream.async_handshake(
|
||||
tls_handshake_type<Stream>::client,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_tls_handshake {}),
|
||||
std::move(ep), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
else if constexpr (
|
||||
has_tls_handshake<next_layer_type<Stream>>
|
||||
) {
|
||||
_stream.next_layer().async_handshake(
|
||||
tls_handshake_type<next_layer_type<Stream>>::client,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_tls_handshake {}),
|
||||
std::move(ep), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
do_ws_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_tls_handshake, error_code ec, endpoint ep, authority_path ap
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
_log.at_tls_handshake(ec, ep);
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
do_ws_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void do_ws_handshake(endpoint ep, authority_path ap) {
|
||||
if constexpr (has_ws_handshake<Stream>)
|
||||
// If you get a compilation error here,
|
||||
// it might be because of a missing <async_mqtt5/websocket.hpp> include
|
||||
ws_handshake_traits<Stream>::async_handshake(
|
||||
_stream, std::move(ap),
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_ws_handshake {}), ep
|
||||
)
|
||||
);
|
||||
else
|
||||
(*this)(on_ws_handshake {}, error_code {}, ep);
|
||||
}
|
||||
|
||||
void operator()(on_ws_handshake, error_code ec, endpoint ep) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if constexpr (has_ws_handshake<Stream>)
|
||||
_log.at_ws_handshake(ec, ep);
|
||||
|
||||
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 (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
_ctx.co_props[prop::authentication_data] = std::move(data);
|
||||
send_connect();
|
||||
}
|
||||
|
||||
void send_connect() {
|
||||
auto packet = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_connect,
|
||||
_ctx.creds.client_id,
|
||||
_ctx.creds.username, _ctx.creds.password,
|
||||
_ctx.keep_alive, false, _ctx.co_props, _ctx.will_msg
|
||||
);
|
||||
|
||||
auto wire_data = packet.wire_data();
|
||||
|
||||
detail::async_write(
|
||||
_stream, asio::buffer(wire_data),
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_send_connect {}),
|
||||
std::move(packet)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_send_connect, error_code ec, size_t) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
_buffer_ptr = std::make_unique<std::string>(min_packet_sz, char(0));
|
||||
|
||||
auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz);
|
||||
asio::async_read(
|
||||
_stream, buff, asio::transfer_all(),
|
||||
asio::prepend(std::move(*this), on_fixed_header {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_fixed_header, error_code ec, size_t num_read
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
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;
|
||||
auto varlen = decoders::type_parse(
|
||||
varlen_ptr, _buffer_ptr->cend(), decoders::basic::varint_
|
||||
);
|
||||
|
||||
if (!varlen)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
auto varlen_sz = std::distance(_buffer_ptr->cbegin() + 1, varlen_ptr);
|
||||
auto remain_len = *varlen -
|
||||
std::distance(varlen_ptr, _buffer_ptr->cbegin() + num_read);
|
||||
|
||||
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;
|
||||
auto last = first + *varlen;
|
||||
|
||||
asio::async_read(
|
||||
_stream, buff, asio::transfer_all(),
|
||||
asio::prepend(
|
||||
asio::append(std::move(*this), code, first, last),
|
||||
on_read_packet {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_read_packet, error_code ec, size_t, control_code_e code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
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 = static_cast<uint32_t>(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;
|
||||
_ctx.state.session_present(session_present);
|
||||
|
||||
// Unexpected result handling:
|
||||
// - If we don't have a Session State, and we get session_present = true,
|
||||
// we must close the network connection (and restart with a clean start)
|
||||
// - If we have a Session State, and we get session_present = false,
|
||||
// we must discard our Session State
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::connack>(reason_code);
|
||||
if (!rc.has_value()) // reason code not allowed in CONNACK
|
||||
return complete(client::error::malformed_packet);
|
||||
|
||||
_log.at_connack(*rc, session_present, ca_props);
|
||||
if (*rc)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
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 = static_cast<uint32_t>(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 (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
auto wire_data = packet.wire_data();
|
||||
|
||||
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 (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz);
|
||||
asio::async_read(
|
||||
_stream, buff, asio::transfer_all(),
|
||||
asio::prepend(std::move(*this), on_fixed_header {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_complete_auth, error_code ec, std::string) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_cancelled() const {
|
||||
return _cancellation_state.cancelled() != asio::cancellation_type::none;
|
||||
}
|
||||
|
||||
void complete(error_code ec) {
|
||||
_cancellation_state.slot().clear();
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_CONNECT_OP_HPP
|
||||
@@ -1,317 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_DISCONNECT_OP_HPP
|
||||
#define ASYNC_MQTT5_DISCONNECT_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/topic_validation.hpp>
|
||||
#include <async_mqtt5/detail/utf8_mqtt.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename ClientService,
|
||||
typename DisconnectContext
|
||||
>
|
||||
class disconnect_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_disconnect {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
DisconnectContext _context;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
asio::any_completion_handler<void (error_code)>,
|
||||
typename ClientService::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
template <typename Handler>
|
||||
disconnect_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
DisconnectContext&& context, Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)), _context(std::move(context)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
disconnect_op(disconnect_op&&) = default;
|
||||
disconnect_op(const disconnect_op&) = delete;
|
||||
|
||||
disconnect_op& operator=(disconnect_op&&) = default;
|
||||
disconnect_op& operator=(const disconnect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
error_code ec = validate_disconnect(_context.props);
|
||||
if (ec)
|
||||
return complete_immediate(ec);
|
||||
|
||||
auto disconnect = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_disconnect,
|
||||
static_cast<uint8_t>(_context.reason_code), _context.props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (disconnect.size() > max_packet_size)
|
||||
// drop properties
|
||||
return send_disconnect(control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_disconnect,
|
||||
static_cast<uint8_t>(_context.reason_code), disconnect_props {}
|
||||
));
|
||||
|
||||
send_disconnect(std::move(disconnect));
|
||||
}
|
||||
|
||||
void send_disconnect(control_packet<allocator_type> disconnect) {
|
||||
auto wire_data = disconnect.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::terminal,
|
||||
asio::prepend(
|
||||
std::move(*this),
|
||||
on_disconnect {}, std::move(disconnect)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_disconnect,
|
||||
control_packet<allocator_type> disconnect, error_code ec
|
||||
) {
|
||||
// The connection must be closed even
|
||||
// if we failed to send the DISCONNECT packet
|
||||
// with Reason Code of 0x80 or greater.
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec == asio::error::try_again) {
|
||||
if (_context.terminal)
|
||||
return send_disconnect(std::move(disconnect));
|
||||
return complete(error_code {});
|
||||
}
|
||||
|
||||
if (_context.terminal) {
|
||||
_svc_ptr->cancel();
|
||||
return complete(error_code {});
|
||||
}
|
||||
|
||||
_svc_ptr->close_stream();
|
||||
_svc_ptr->open_stream();
|
||||
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
static error_code validate_disconnect(const disconnect_props& props) {
|
||||
const auto& reason_string = props[prop::reason_string];
|
||||
if (
|
||||
reason_string &&
|
||||
validate_mqtt_utf8(*reason_string) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
void complete(error_code ec) {
|
||||
_handler.complete(ec);
|
||||
}
|
||||
|
||||
void complete_immediate(error_code ec) {
|
||||
_handler.complete_immediate(ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class terminal_disconnect_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
static constexpr uint8_t seconds = 5;
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
std::unique_ptr<asio::steady_timer> _timer;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
terminal_disconnect_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_timer(new asio::steady_timer(_svc_ptr->get_executor())),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
terminal_disconnect_op(terminal_disconnect_op&&) = default;
|
||||
terminal_disconnect_op(const terminal_disconnect_op&) = delete;
|
||||
|
||||
terminal_disconnect_op& operator=(terminal_disconnect_op&&) = default;
|
||||
terminal_disconnect_op& operator=(const terminal_disconnect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename DisconnectContext>
|
||||
void perform(DisconnectContext&& context) {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
auto init_disconnect = [](
|
||||
auto handler, disconnect_ctx ctx,
|
||||
std::shared_ptr<ClientService> svc_ptr
|
||||
) {
|
||||
disconnect_op {
|
||||
std::move(svc_ptr), std::move(ctx), std::move(handler)
|
||||
}.perform();
|
||||
};
|
||||
|
||||
_timer->expires_after(std::chrono::seconds(seconds));
|
||||
|
||||
auto timed_disconnect = asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void (error_code)>(
|
||||
init_disconnect, asio::deferred,
|
||||
std::forward<DisconnectContext>(context), _svc_ptr
|
||||
),
|
||||
_timer->async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_disconnect.async_wait(
|
||||
asioex::wait_for_one(), std::move(*this)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
std::array<std::size_t, 2> /* ord */,
|
||||
error_code disconnect_ec, error_code /* timer_ec */
|
||||
) {
|
||||
std::move(_handler)(disconnect_ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, bool terminal>
|
||||
class initiate_async_disconnect {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_disconnect(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
disconnect_rc_e rc, const disconnect_props& props
|
||||
) {
|
||||
auto ctx = disconnect_ctx { rc, props, terminal };
|
||||
if constexpr (terminal)
|
||||
terminal_disconnect_op { _svc_ptr, std::move(handler) }
|
||||
.perform(std::move(ctx));
|
||||
else
|
||||
disconnect_op { _svc_ptr, std::move(ctx), std::move(handler) }
|
||||
.perform();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, typename CompletionToken>
|
||||
decltype(auto) async_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
std::shared_ptr<ClientService> svc_ptr,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiate_async_disconnect<ClientService, false>(std::move(svc_ptr)), token,
|
||||
reason_code, props
|
||||
);
|
||||
}
|
||||
|
||||
template <typename ClientService, typename CompletionToken>
|
||||
decltype(auto) async_terminal_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
std::shared_ptr<ClientService> svc_ptr,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiate_async_disconnect<ClientService, true>(std::move(svc_ptr)), token,
|
||||
reason_code, props
|
||||
);
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_DISCONNECT_HPP
|
||||
@@ -1,242 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ENDPOINTS_HPP
|
||||
#define ASYNC_MQTT5_ENDPOINTS_HPP
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class resolve_op {
|
||||
struct on_resolve {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
resolve_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
resolve_op(resolve_op&&) = default;
|
||||
resolve_op(const resolve_op&) = delete;
|
||||
|
||||
resolve_op& operator=(resolve_op&&) = default;
|
||||
resolve_op& operator=(const resolve_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
void perform() {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
if (_owner._servers.empty())
|
||||
return complete_post(asio::error::host_not_found, {}, {});
|
||||
|
||||
_owner._current_host++;
|
||||
|
||||
if (_owner._current_host + 1 > static_cast<int>(_owner._servers.size())) {
|
||||
_owner._current_host = -1;
|
||||
return complete_post(asio::error::try_again, {}, {});
|
||||
}
|
||||
|
||||
authority_path ap = _owner._servers[_owner._current_host];
|
||||
|
||||
_owner._connect_timer.expires_after(std::chrono::seconds(5));
|
||||
|
||||
auto timed_resolve = asioex::make_parallel_group(
|
||||
_owner._resolver.async_resolve(ap.host, ap.port, asio::deferred),
|
||||
_owner._connect_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_resolve.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_resolve {}),
|
||||
std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_resolve, std::array<std::size_t, 2> ord,
|
||||
error_code resolve_ec, epoints epts,
|
||||
error_code timer_ec, authority_path ap
|
||||
) {
|
||||
if (
|
||||
(ord[0] == 0 && resolve_ec == asio::error::operation_aborted) ||
|
||||
(ord[0] == 1 && timer_ec == asio::error::operation_aborted)
|
||||
)
|
||||
return complete(asio::error::operation_aborted, {}, {});
|
||||
|
||||
resolve_ec = timer_ec ? resolve_ec : asio::error::timed_out;
|
||||
_owner._log.at_resolve(resolve_ec, ap.host, ap.port, epts);
|
||||
if (!resolve_ec)
|
||||
return complete(error_code {}, std::move(epts), std::move(ap));
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, epoints eps, authority_path ap) {
|
||||
std::move(_handler)(ec, std::move(eps), std::move(ap));
|
||||
}
|
||||
|
||||
void complete_post(error_code ec, epoints eps, authority_path ap) {
|
||||
asio::post(
|
||||
_owner.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(_handler), ec,
|
||||
std::move(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename LoggerType>
|
||||
class endpoints {
|
||||
using logger_type = LoggerType;
|
||||
|
||||
asio::ip::tcp::resolver _resolver;
|
||||
asio::steady_timer& _connect_timer;
|
||||
|
||||
std::vector<authority_path> _servers;
|
||||
|
||||
int _current_host { -1 };
|
||||
|
||||
log_invoke<logger_type>& _log;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class resolve_op;
|
||||
|
||||
template <typename T>
|
||||
static constexpr auto to_(T& arg) {
|
||||
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
|
||||
}
|
||||
|
||||
template <typename T, typename Parser>
|
||||
static constexpr auto as_(Parser&& p){
|
||||
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
endpoints(
|
||||
Executor ex, asio::steady_timer& timer,
|
||||
log_invoke<logger_type>& log
|
||||
) :
|
||||
_resolver(std::move(ex)), _connect_timer(timer),
|
||||
_log(log)
|
||||
{}
|
||||
|
||||
endpoints(const endpoints&) = delete;
|
||||
endpoints& operator=(const endpoints&) = delete;
|
||||
|
||||
void clone_servers(const endpoints& other) {
|
||||
_servers = other._servers;
|
||||
}
|
||||
|
||||
using executor_type = asio::ip::tcp::resolver::executor_type;
|
||||
// NOTE: asio::ip::basic_resolver returns executor by value
|
||||
executor_type get_executor() noexcept {
|
||||
return _resolver.get_executor();
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_next_endpoint(CompletionToken&& token) {
|
||||
using Signature = void (error_code, epoints, authority_path);
|
||||
|
||||
auto initiation = [](auto handler, endpoints& self) {
|
||||
resolve_op { self, std::move(handler) }.perform();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this)
|
||||
);
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
_servers.clear();
|
||||
|
||||
std::string host, port, path;
|
||||
|
||||
// loosely based on RFC 3986
|
||||
auto unreserved_ = x3::char_("-a-zA-Z_0-9._~");
|
||||
auto digit_ = x3::char_("0-9");
|
||||
auto separator_ = x3::char_(',');
|
||||
|
||||
auto host_ = as_<std::string>(+unreserved_)[to_(host)];
|
||||
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
|
||||
auto path_ = as_<std::string>(x3::char_('/') >> *unreserved_)[to_(path)];
|
||||
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
|
||||
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
|
||||
|
||||
for (auto b = hosts.begin(); b != hosts.end(); ) {
|
||||
host.clear(); port.clear(); path.clear();
|
||||
if (phrase_parse(b, hosts.end(), uri_, x3::eps(false))) {
|
||||
_servers.push_back({
|
||||
std::move(host),
|
||||
port.empty()
|
||||
? std::to_string(default_port)
|
||||
: std::move(port),
|
||||
std::move(path)
|
||||
});
|
||||
}
|
||||
else b = hosts.end();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_ENDPOINTS_HPP
|
||||
@@ -1,110 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_PING_OP_HPP
|
||||
#define ASYNC_MQTT5_PING_OP_HPP
|
||||
|
||||
#include <limits>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class ping_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_timer {};
|
||||
struct on_pingreq {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
ping_op(std::shared_ptr<client_service> svc_ptr, Handler&& handler) :
|
||||
_svc_ptr(std::move(svc_ptr)), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
ping_op(ping_op&&) noexcept = default;
|
||||
ping_op(const ping_op&) = delete;
|
||||
|
||||
ping_op& operator=(ping_op&&) noexcept = default;
|
||||
ping_op& operator=(const ping_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->_ping_timer.expires_after(compute_wait_time());
|
||||
_svc_ptr->_ping_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_timer, error_code ec) {
|
||||
if (!_svc_ptr->is_open())
|
||||
return complete();
|
||||
else if (ec == asio::error::operation_aborted)
|
||||
return perform();
|
||||
|
||||
auto pingreq = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(), encoders::encode_pingreq
|
||||
);
|
||||
|
||||
auto wire_data = pingreq.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_pingreq {}),
|
||||
std::move(pingreq)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_pingreq, error_code ec) {
|
||||
if (!ec || ec == asio::error::try_again)
|
||||
return perform();
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
private:
|
||||
duration compute_wait_time() const {
|
||||
auto negotiated_ka = _svc_ptr->negotiated_keep_alive();
|
||||
return negotiated_ka ?
|
||||
std::chrono::seconds(negotiated_ka) :
|
||||
duration(std::numeric_limits<duration::rep>::max());
|
||||
}
|
||||
|
||||
void complete() {
|
||||
return std::move(_handler)();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_PING_OP_HPP
|
||||
@@ -1,219 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_PUBLISH_REC_OP_HPP
|
||||
#define ASYNC_MQTT5_PUBLISH_REC_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class publish_rec_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_puback {};
|
||||
struct on_pubrec {};
|
||||
struct on_pubrel {};
|
||||
struct on_pubcomp {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
decoders::publish_message _message;
|
||||
|
||||
public:
|
||||
explicit publish_rec_op(std::shared_ptr<client_service> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
publish_rec_op(publish_rec_op&&) noexcept = default;
|
||||
publish_rec_op(const publish_rec_op&) = delete;
|
||||
|
||||
publish_rec_op& operator=(publish_rec_op&&) noexcept = default;
|
||||
publish_rec_op& operator=(const publish_rec_op&) = delete;
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(decoders::publish_message message) {
|
||||
auto flags = std::get<2>(message);
|
||||
auto qos_bits = (flags >> 1) & 0b11;
|
||||
if (qos_bits == 0b11)
|
||||
return on_malformed_packet(
|
||||
"Malformed PUBLISH received: QoS bits set to 0b11"
|
||||
);
|
||||
|
||||
auto qos = qos_e(qos_bits);
|
||||
_message = std::move(message);
|
||||
|
||||
if (qos == qos_e::at_most_once)
|
||||
return complete();
|
||||
|
||||
auto packet_id = std::get<1>(_message);
|
||||
|
||||
if (qos == qos_e::at_least_once) {
|
||||
auto puback = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_puback, *packet_id,
|
||||
uint8_t(0), puback_props {}
|
||||
);
|
||||
return send_puback(std::move(puback));
|
||||
}
|
||||
|
||||
// qos == qos_e::exactly_once
|
||||
auto pubrec = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubrec, *packet_id,
|
||||
uint8_t(0), pubrec_props {}
|
||||
);
|
||||
|
||||
return send_pubrec(std::move(pubrec));
|
||||
}
|
||||
|
||||
void send_puback(control_packet<allocator_type> puback) {
|
||||
auto wire_data = puback.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_puback {}),
|
||||
std::move(puback)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_puback, error_code ec) {
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
void send_pubrec(control_packet<allocator_type> pubrec) {
|
||||
auto wire_data = pubrec.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(std::move(*this), on_pubrec {}, std::move(pubrec))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrec, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
wait_pubrel(packet.packet_id());
|
||||
}
|
||||
|
||||
void wait_pubrel(uint16_t packet_id) {
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubrel, packet_id,
|
||||
asio::prepend(std::move(*this), on_pubrel {}, packet_id)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrel, uint16_t packet_id,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return wait_pubrel(packet_id);
|
||||
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
auto pubrel = decoders::decode_pubrel(static_cast<uint32_t>(std::distance(first, last)), first);
|
||||
if (!pubrel.has_value()) {
|
||||
on_malformed_packet("Malformed PUBREL received: cannot decode");
|
||||
return wait_pubrel(packet_id);
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubrel;
|
||||
auto rc = to_reason_code<reason_codes::category::pubrel>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBREL received: invalid Reason Code");
|
||||
return wait_pubrel(packet_id);
|
||||
}
|
||||
|
||||
auto pubcomp = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubcomp, packet_id,
|
||||
uint8_t(0), pubcomp_props{}
|
||||
);
|
||||
send_pubcomp(std::move(pubcomp));
|
||||
}
|
||||
|
||||
void send_pubcomp(control_packet<allocator_type> pubcomp) {
|
||||
auto wire_data = pubcomp.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubcomp))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubcomp, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return wait_pubrel(packet.packet_id());
|
||||
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
private:
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props,
|
||||
_svc_ptr, asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete() {
|
||||
/* auto rv = */_svc_ptr->channel_store(std::move(_message));
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_PUBLISH_REC_OP_HPP
|
||||
@@ -1,508 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
#define ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/topic_validation.hpp>
|
||||
#include <async_mqtt5/detail/utf8_mqtt.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using on_publish_signature = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
void (error_code),
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
void (error_code, reason_code, puback_props),
|
||||
void (error_code, reason_code, pubcomp_props)
|
||||
>
|
||||
>;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using on_publish_props_type = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
void,
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
puback_props,
|
||||
pubcomp_props
|
||||
>
|
||||
>;
|
||||
|
||||
template <typename ClientService, typename Handler, qos_e qos_type>
|
||||
class publish_send_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_publish {};
|
||||
struct on_puback {};
|
||||
struct on_pubrec {};
|
||||
struct on_pubrel {};
|
||||
struct on_pubcomp {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
serial_num_t _serial_num;
|
||||
|
||||
public:
|
||||
publish_send_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
publish_send_op(publish_send_op&&) = default;
|
||||
publish_send_op(const publish_send_op&) = delete;
|
||||
|
||||
publish_send_op& operator=(publish_send_op&&) = default;
|
||||
publish_send_op& operator=(const publish_send_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) {
|
||||
uint16_t packet_id = 0;
|
||||
if constexpr (qos_type != qos_e::at_most_once) {
|
||||
packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_immediate(client::error::pid_overrun, packet_id);
|
||||
}
|
||||
|
||||
auto ec = validate_publish(topic, payload, retain, props);
|
||||
if (ec)
|
||||
return complete_immediate(ec, packet_id);
|
||||
|
||||
_serial_num = _svc_ptr->next_serial_num();
|
||||
|
||||
auto publish = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_publish, packet_id,
|
||||
std::move(topic), std::move(payload),
|
||||
qos_type, retain, dup_e::no, props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (publish.size() > max_packet_size)
|
||||
return complete_immediate(client::error::packet_too_large, packet_id);
|
||||
|
||||
send_publish(std::move(publish));
|
||||
}
|
||||
|
||||
void send_publish(control_packet<allocator_type> publish) {
|
||||
auto wire_data = publish.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
_serial_num,
|
||||
send_flag::throttled * (qos_type != qos_e::at_most_once),
|
||||
asio::prepend(std::move(*this), on_publish {}, std::move(publish))
|
||||
);
|
||||
}
|
||||
|
||||
void resend_publish(control_packet<allocator_type> publish) {
|
||||
if (_handler.cancelled() != asio::cancellation_type_t::none)
|
||||
return complete(
|
||||
asio::error::operation_aborted, publish.packet_id()
|
||||
);
|
||||
send_publish(std::move(publish));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_publish, control_packet<allocator_type> publish,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return resend_publish(std::move(publish));
|
||||
|
||||
if constexpr (qos_type == qos_e::at_most_once)
|
||||
return complete(ec);
|
||||
|
||||
else {
|
||||
auto packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
if constexpr (qos_type == qos_e::at_least_once)
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::puback, packet_id,
|
||||
asio::prepend(
|
||||
std::move(*this), on_puback {}, std::move(publish)
|
||||
)
|
||||
);
|
||||
|
||||
else if constexpr (qos_type == qos_e::exactly_once)
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubrec, packet_id,
|
||||
asio::prepend(
|
||||
std::move(*this), on_pubrec {}, std::move(publish)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::at_least_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_puback, control_packet<allocator_type> publish,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
|
||||
uint16_t packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto puback = decoders::decode_puback(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!puback.has_value()) {
|
||||
on_malformed_packet("Malformed PUBACK: cannot decode");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *puback;
|
||||
auto rc = to_reason_code<reason_codes::category::puback>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBACK: invalid Reason Code");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
complete(ec, packet_id, *rc, std::move(props));
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::exactly_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_pubrec, control_packet<allocator_type> publish,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
|
||||
uint16_t packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto pubrec = decoders::decode_pubrec(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!pubrec.has_value()) {
|
||||
on_malformed_packet("Malformed PUBREC: cannot decode");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubrec;
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::pubrec>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBREC: invalid Reason Code");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
if (*rc)
|
||||
return complete(ec, packet_id, *rc);
|
||||
|
||||
auto pubrel = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubrel, packet_id,
|
||||
0, pubrel_props {}
|
||||
);
|
||||
|
||||
send_pubrel(std::move(pubrel), false);
|
||||
}
|
||||
|
||||
void send_pubrel(control_packet<allocator_type> pubrel, bool throttled) {
|
||||
auto wire_data = pubrel.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
_serial_num,
|
||||
(send_flag::throttled * throttled) | send_flag::prioritized,
|
||||
asio::prepend(std::move(*this), on_pubrel {}, std::move(pubrel))
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::exactly_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_pubrel, control_packet<allocator_type> pubrel, error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
|
||||
uint16_t packet_id = pubrel.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubcomp, packet_id,
|
||||
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubrel))
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::exactly_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_pubcomp, control_packet<allocator_type> pubrel,
|
||||
error_code ec,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
|
||||
uint16_t packet_id = pubrel.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto pubcomp = decoders::decode_pubcomp(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!pubcomp.has_value()) {
|
||||
on_malformed_packet("Malformed PUBCOMP: cannot decode");
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubcomp;
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::pubcomp>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBCOMP: invalid Reason Code");
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
}
|
||||
|
||||
return complete(ec, pubrel.packet_id(), *rc);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
error_code validate_publish(
|
||||
const std::string& topic, const std::string& payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) const {
|
||||
constexpr uint8_t default_retain_available = 1;
|
||||
constexpr uint8_t default_maximum_qos = 2;
|
||||
constexpr uint8_t default_payload_format_ind = 0;
|
||||
|
||||
auto topic_name_valid = props[prop::topic_alias].has_value() ?
|
||||
validate_topic_alias_name(topic) == validation_result::valid :
|
||||
validate_topic_name(topic) == validation_result::valid
|
||||
;
|
||||
|
||||
if (!topic_name_valid)
|
||||
return client::error::invalid_topic;
|
||||
|
||||
auto max_qos = _svc_ptr->connack_property(prop::maximum_qos)
|
||||
.value_or(default_maximum_qos);
|
||||
auto retain_available = _svc_ptr->connack_property(prop::retain_available)
|
||||
.value_or(default_retain_available);
|
||||
|
||||
if (uint8_t(qos_type) > max_qos)
|
||||
return client::error::qos_not_supported;
|
||||
|
||||
if (retain_available == 0 && retain == retain_e::yes)
|
||||
return client::error::retain_not_available;
|
||||
|
||||
auto payload_format_ind = props[prop::payload_format_indicator]
|
||||
.value_or(default_payload_format_ind);
|
||||
if (
|
||||
payload_format_ind == 1 &&
|
||||
validate_mqtt_utf8(payload) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
return validate_props(props);
|
||||
}
|
||||
|
||||
error_code validate_props(const publish_props& props) const {
|
||||
constexpr uint16_t default_topic_alias_max = 0;
|
||||
|
||||
const auto& topic_alias = props[prop::topic_alias];
|
||||
if (topic_alias) {
|
||||
auto topic_alias_max = _svc_ptr->connack_property(prop::topic_alias_maximum)
|
||||
.value_or(default_topic_alias_max);
|
||||
|
||||
if (topic_alias_max == 0 || *topic_alias > topic_alias_max)
|
||||
return client::error::topic_alias_maximum_reached;
|
||||
if (*topic_alias == 0 )
|
||||
return client::error::malformed_packet;
|
||||
}
|
||||
|
||||
const auto& response_topic = props[prop::response_topic];
|
||||
if (
|
||||
response_topic &&
|
||||
validate_topic_name(*response_topic) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
|
||||
if (!props[prop::subscription_identifier].empty())
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& content_type = props[prop::content_type];
|
||||
if (
|
||||
content_type &&
|
||||
validate_mqtt_utf8(*content_type) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::at_most_once, bool> = true
|
||||
>
|
||||
void complete(error_code ec, uint16_t = 0) {
|
||||
_handler.complete(ec);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::at_most_once, bool> = true
|
||||
>
|
||||
void complete_immediate(error_code ec, uint16_t) {
|
||||
_handler.complete_immediate(ec);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Props = on_publish_props_type<qos_type>,
|
||||
std::enable_if_t<
|
||||
std::is_same_v<Props, puback_props> ||
|
||||
std::is_same_v<Props, pubcomp_props>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
reason_code rc = reason_codes::empty, Props&& props = Props {}
|
||||
) {
|
||||
_svc_ptr->free_pid(packet_id, true);
|
||||
_handler.complete(ec, rc, std::forward<Props>(props));
|
||||
}
|
||||
|
||||
template <
|
||||
typename Props = on_publish_props_type<qos_type>,
|
||||
std::enable_if_t<
|
||||
std::is_same_v<Props, puback_props> ||
|
||||
std::is_same_v<Props, pubcomp_props>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
void complete_immediate(error_code ec, uint16_t packet_id) {
|
||||
if (packet_id != 0)
|
||||
_svc_ptr->free_pid(packet_id, false);
|
||||
_handler.complete_immediate(ec, reason_codes::empty, Props {});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, qos_e qos_type>
|
||||
class initiate_async_publish {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_publish(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) {
|
||||
detail::publish_send_op<ClientService, Handler, qos_type> {
|
||||
_svc_ptr, std::move(handler)
|
||||
}.perform(
|
||||
std::move(topic), std::move(payload), retain, props
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
@@ -1,152 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_RE_AUTH_OP_hpp
|
||||
#define ASYNC_MQTT5_RE_AUTH_OP_hpp
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/any_authenticator.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class re_auth_op {
|
||||
using client_service = ClientService;
|
||||
struct on_auth_data {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
any_authenticator& _auth;
|
||||
|
||||
public:
|
||||
explicit re_auth_op(std::shared_ptr<client_service> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_auth(_svc_ptr->_stream_context.mqtt_context().authenticator)
|
||||
{}
|
||||
|
||||
re_auth_op(re_auth_op&&) noexcept = default;
|
||||
re_auth_op(const re_auth_op&) = delete;
|
||||
|
||||
re_auth_op& operator=(re_auth_op&&) noexcept = default;
|
||||
re_auth_op& operator=(const re_auth_op&) = delete;
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
if (_auth.method().empty())
|
||||
return;
|
||||
|
||||
auto auth_step = auth_step_e::client_initial;
|
||||
return _auth.async_auth(
|
||||
auth_step, "",
|
||||
asio::prepend(std::move(*this), on_auth_data {}, auth_step)
|
||||
);
|
||||
}
|
||||
|
||||
void perform(decoders::auth_message auth_message) {
|
||||
if (_auth.method().empty())
|
||||
return on_auth_fail(
|
||||
"Unexpected AUTH received",
|
||||
disconnect_rc_e::protocol_error
|
||||
);
|
||||
|
||||
const auto& [rc, props] = auth_message;
|
||||
auto auth_rc = to_reason_code<reason_codes::category::auth>(rc);
|
||||
if (!auth_rc.has_value())
|
||||
return on_auth_fail(
|
||||
"Malformed AUTH received: bad reason code",
|
||||
disconnect_rc_e::malformed_packet
|
||||
);
|
||||
|
||||
const auto& server_auth_method = props[prop::authentication_method];
|
||||
if (!server_auth_method || *server_auth_method != _auth.method())
|
||||
return on_auth_fail(
|
||||
"Malformed AUTH received: wrong authentication method",
|
||||
disconnect_rc_e::protocol_error
|
||||
);
|
||||
|
||||
auto auth_step = auth_rc == reason_codes::success ?
|
||||
auth_step_e::server_final : auth_step_e::server_challenge;
|
||||
auto data = props[prop::authentication_data].value_or("");
|
||||
|
||||
return _auth.async_auth(
|
||||
auth_step, std::move(data),
|
||||
asio::prepend(std::move(*this), on_auth_data {}, auth_step)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_auth_data, auth_step_e auth_step, error_code ec, std::string data
|
||||
) {
|
||||
if (ec)
|
||||
return on_auth_fail(
|
||||
"Re-authentication: authentication fail",
|
||||
disconnect_rc_e::unspecified_error
|
||||
);
|
||||
|
||||
if (auth_step == auth_step_e::server_final)
|
||||
return;
|
||||
|
||||
auth_props props;
|
||||
props[prop::authentication_method] = _auth.method();
|
||||
props[prop::authentication_data] = std::move(data);
|
||||
auto rc = auth_step == auth_step_e::client_initial ?
|
||||
reason_codes::reauthenticate : reason_codes::continue_authentication;
|
||||
|
||||
auto packet = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_auth,
|
||||
rc.value(), props
|
||||
);
|
||||
|
||||
auto wire_data = packet.wire_data();
|
||||
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(asio::detached, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
void on_auth_fail(std::string message, disconnect_rc_e reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = std::move(message);
|
||||
|
||||
async_disconnect(reason, props, _svc_ptr, asio::detached);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_RE_AUTH_OP_HPP
|
||||
@@ -1,174 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_READ_MESSAGE_OP_HPP
|
||||
#define ASYNC_MQTT5_READ_MESSAGE_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/publish_rec_op.hpp>
|
||||
#include <async_mqtt5/impl/re_auth_op.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class read_message_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_message {};
|
||||
struct on_disconnect {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
read_message_op(std::shared_ptr<client_service> svc_ptr, Handler&& handler)
|
||||
: _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
read_message_op(read_message_op&&) noexcept = default;
|
||||
read_message_op(const read_message_op&) = delete;
|
||||
|
||||
read_message_op& operator=(read_message_op&&) noexcept = default;
|
||||
read_message_op& operator=(const read_message_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->async_assemble(
|
||||
asio::prepend(std::move(*this), on_message {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_message, error_code ec,
|
||||
uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == client::error::malformed_packet)
|
||||
return on_malformed_packet(
|
||||
"Malformed Packet received from the Server"
|
||||
);
|
||||
|
||||
if (ec == asio::error::no_recovery)
|
||||
_svc_ptr->cancel();
|
||||
|
||||
if (ec)
|
||||
return complete();
|
||||
|
||||
dispatch(control_code, first, last);
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
if (ec)
|
||||
return complete();
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void dispatch(
|
||||
uint8_t control_byte,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
switch (code) {
|
||||
case control_code_e::publish: {
|
||||
auto msg = decoders::decode_publish(
|
||||
control_byte, static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!msg.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed PUBLISH received: cannot decode"
|
||||
);
|
||||
|
||||
publish_rec_op { _svc_ptr }.perform(std::move(*msg));
|
||||
}
|
||||
break;
|
||||
case control_code_e::disconnect: {
|
||||
auto rv = decoders::decode_disconnect(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!rv.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed DISCONNECT received: cannot decode"
|
||||
);
|
||||
|
||||
const auto& [rc, props] = *rv;
|
||||
_svc_ptr->log().at_disconnect(
|
||||
to_reason_code<reason_codes::category::disconnect>(rc)
|
||||
.value_or(reason_codes::unspecified_error),
|
||||
props
|
||||
);
|
||||
_svc_ptr->close_stream();
|
||||
_svc_ptr->open_stream();
|
||||
}
|
||||
break;
|
||||
case control_code_e::auth: {
|
||||
auto rv = decoders::decode_auth(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!rv.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed AUTH received: cannot decode"
|
||||
);
|
||||
|
||||
re_auth_op { _svc_ptr }.perform(std::move(*rv));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
auto svc_ptr = _svc_ptr; // copy before this is moved
|
||||
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, svc_ptr,
|
||||
asio::prepend(std::move(*this), on_disconnect {})
|
||||
);
|
||||
}
|
||||
|
||||
void complete() {
|
||||
return std::move(_handler)();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_READ_MESSAGE_OP_HPP
|
||||
@@ -1,140 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_READ_OP_HPP
|
||||
#define ASYNC_MQTT5_READ_OP_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class read_op {
|
||||
struct on_read {};
|
||||
struct on_reconnect {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
read_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
read_op(read_op&&) = default;
|
||||
read_op(const read_op&) = delete;
|
||||
|
||||
read_op& operator=(read_op&&) = default;
|
||||
read_op& operator=(const read_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(
|
||||
const BufferType& buffer, duration wait_for
|
||||
) {
|
||||
auto stream_ptr = _owner._stream_ptr;
|
||||
|
||||
if (_owner.was_connected()) {
|
||||
_owner._read_timer.expires_after(wait_for);
|
||||
|
||||
auto timed_read = asioex::make_parallel_group(
|
||||
stream_ptr->async_read_some(buffer, asio::deferred),
|
||||
_owner._read_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_read.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::prepend(std::move(*this), on_read {}, stream_ptr)
|
||||
);
|
||||
}
|
||||
else
|
||||
asio::post(
|
||||
_owner.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_read {}, stream_ptr,
|
||||
std::array<size_t, 2> { 0, 1 },
|
||||
asio::error::not_connected, 0, error_code {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_read, typename Owner::stream_ptr stream_ptr,
|
||||
std::array<std::size_t, 2> ord, error_code read_ec, size_t bytes_read,
|
||||
error_code
|
||||
) {
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted, bytes_read);
|
||||
|
||||
error_code ec = ord[0] == 1 ? asio::error::timed_out : read_ec;
|
||||
bytes_read = ord[0] == 0 ? bytes_read : 0;
|
||||
|
||||
if (!ec)
|
||||
return complete(ec, bytes_read);
|
||||
|
||||
// websocket returns operation_aborted if disconnected
|
||||
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
|
||||
return _owner.async_reconnect(
|
||||
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
|
||||
);
|
||||
|
||||
return complete(asio::error::no_recovery, bytes_read);
|
||||
}
|
||||
|
||||
void operator()(on_reconnect, error_code ec) {
|
||||
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
|
||||
ec = asio::error::try_again;
|
||||
|
||||
return complete(ec, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, size_t bytes_read) {
|
||||
std::move(_handler)(ec, bytes_read);
|
||||
}
|
||||
|
||||
static bool should_reconnect(error_code ec) {
|
||||
using namespace asio::error;
|
||||
// note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved)
|
||||
return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */
|
||||
ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */
|
||||
ec == connection_aborted || ec == not_connected ||
|
||||
ec == timed_out || ec == connection_reset ||
|
||||
ec == broken_pipe || ec == asio::error::eof;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_READ_OP_HPP
|
||||
@@ -1,251 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_RECONNECT_OP_HPP
|
||||
#define ASYNC_MQTT5_RECONNECT_OP_HPP
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <boost/random/linear_congruential.hpp>
|
||||
#include <boost/random/uniform_smallint.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
#include <async_mqtt5/impl/connect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
class exponential_backoff {
|
||||
int _curr_exp { 0 };
|
||||
|
||||
static constexpr int _base_mulptilier = 1000;
|
||||
static constexpr int _max_exp = 4;
|
||||
|
||||
// sizeof(_generator) = 8
|
||||
boost::random::rand48 _generator { uint32_t(std::time(0)) };
|
||||
boost::random::uniform_smallint<> _distribution { -500, 500 };
|
||||
public:
|
||||
exponential_backoff() = default;
|
||||
|
||||
duration generate() {
|
||||
int exponent = _curr_exp < _max_exp ? _curr_exp++ : _max_exp;
|
||||
int base = 1 << exponent;
|
||||
return std::chrono::milliseconds(
|
||||
base * _base_mulptilier + _distribution(_generator) /* noise */
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename Owner>
|
||||
class reconnect_op {
|
||||
struct on_locked {};
|
||||
struct on_next_endpoint {};
|
||||
struct on_connect {};
|
||||
struct on_backoff {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = asio::any_completion_handler<void (error_code)>;
|
||||
handler_type _handler;
|
||||
|
||||
std::unique_ptr<std::string> _buffer_ptr;
|
||||
|
||||
exponential_backoff _generator;
|
||||
|
||||
using endpoint = asio::ip::tcp::endpoint;
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
public:
|
||||
template <typename Handler>
|
||||
reconnect_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
reconnect_op(reconnect_op&&) = default;
|
||||
reconnect_op(const reconnect_op&) = delete;
|
||||
|
||||
reconnect_op& operator=(reconnect_op&&) = default;
|
||||
reconnect_op& operator=(const reconnect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
void perform(typename Owner::stream_ptr s) {
|
||||
_owner._conn_mtx.lock(
|
||||
asio::prepend(std::move(*this), on_locked {}, s)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_locked, typename Owner::stream_ptr s, error_code ec) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
// cancelled without acquiring the lock (by calling client.cancel())
|
||||
return std::move(_handler)(ec);
|
||||
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (s != _owner._stream_ptr)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
do_reconnect();
|
||||
}
|
||||
|
||||
void do_reconnect() {
|
||||
_owner._endpoints.async_next_endpoint(
|
||||
asio::prepend(std::move(*this), on_next_endpoint {})
|
||||
);
|
||||
}
|
||||
|
||||
void backoff_and_reconnect() {
|
||||
_owner._connect_timer.expires_after(_generator.generate());
|
||||
_owner._connect_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_backoff {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_backoff, error_code ec) {
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
do_reconnect();
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_next_endpoint, error_code ec,
|
||||
epoints eps, authority_path ap
|
||||
) {
|
||||
// the three error codes below are the only possible codes
|
||||
// that may be returned from async_next_endpont
|
||||
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec == asio::error::try_again)
|
||||
return backoff_and_reconnect();
|
||||
|
||||
if (ec == asio::error::host_not_found)
|
||||
return complete(asio::error::no_recovery);
|
||||
|
||||
connect(eps.cbegin(), std::move(ap));
|
||||
}
|
||||
|
||||
void connect(epoints::const_iterator eps, authority_path ap) {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
const auto& ep = eps->endpoint();
|
||||
auto sptr = _owner.construct_and_open_next_layer(ep.protocol());
|
||||
|
||||
if constexpr (has_tls_context<typename Owner::stream_context_type>)
|
||||
setup_tls_sni(
|
||||
ap, _owner._stream_context.tls_context(), *sptr
|
||||
);
|
||||
|
||||
// wait max 5 seconds for the connect (handshake) op to finish
|
||||
_owner._connect_timer.expires_after(std::chrono::seconds(5));
|
||||
|
||||
auto init_connect = [](
|
||||
auto handler, typename Owner::stream_type& stream,
|
||||
mqtt_ctx& context, log_invoke<typename Owner::logger_type>& log,
|
||||
endpoint ep, authority_path ap
|
||||
) {
|
||||
connect_op { stream, context, log, std::move(handler) }
|
||||
.perform(ep, std::move(ap));
|
||||
};
|
||||
|
||||
auto timed_connect = asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void(error_code)>(
|
||||
init_connect, asio::deferred, std::ref(*sptr),
|
||||
std::ref(_owner._stream_context.mqtt_context()),
|
||||
std::ref(_owner.log()),
|
||||
ep, ap
|
||||
),
|
||||
_owner._connect_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_connect.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_connect {},
|
||||
std::move(sptr), std::move(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_connect,
|
||||
typename Owner::stream_ptr sptr, epoints::const_iterator eps, authority_path ap,
|
||||
std::array<std::size_t, 2> ord,
|
||||
error_code connect_ec, error_code timer_ec
|
||||
) {
|
||||
// connect_ec may be any of:
|
||||
// 1) async_connect error codes
|
||||
// 2) async_handshake (TLS) error codes
|
||||
// 3) async_handshake (WebSocket) error codes
|
||||
// 4) async_write error codes
|
||||
// 5) async_read error codes
|
||||
// 5) client::error::malformed_packet
|
||||
if (
|
||||
(ord[0] == 0 && connect_ec == asio::error::operation_aborted) ||
|
||||
(ord[0] == 1 && timer_ec == asio::error::operation_aborted) ||
|
||||
!_owner.is_open()
|
||||
)
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
// retry for operation timed out and any other error_code or client::error::malformed_packet
|
||||
if (ord[0] == 1 || connect_ec) {
|
||||
// if the hostname resolved into more endpoints, try the next one
|
||||
if (++eps != epoints::const_iterator())
|
||||
return connect(std::move(eps), std::move(ap));
|
||||
// try next server
|
||||
return do_reconnect();
|
||||
}
|
||||
|
||||
_owner.replace_next_layer(std::move(sptr));
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec) {
|
||||
_owner._conn_mtx.unlock();
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_RECONNECT_OP_HPP
|
||||
@@ -1,246 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_REPLIES_HPP
|
||||
#define ASYNC_MQTT5_REPLIES_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class replies {
|
||||
public:
|
||||
using executor_type = asio::any_io_executor;
|
||||
private:
|
||||
using Signature = void (error_code, byte_citer, byte_citer);
|
||||
|
||||
static constexpr auto max_reply_time = std::chrono::seconds(20);
|
||||
|
||||
class reply_handler {
|
||||
asio::any_completion_handler<Signature> _handler;
|
||||
control_code_e _code;
|
||||
uint16_t _packet_id;
|
||||
std::chrono::time_point<std::chrono::system_clock> _ts;
|
||||
public:
|
||||
template <typename H>
|
||||
reply_handler(control_code_e code, uint16_t pid, H&& handler) :
|
||||
_handler(std::forward<H>(handler)), _code(code), _packet_id(pid),
|
||||
_ts(std::chrono::system_clock::now())
|
||||
{}
|
||||
|
||||
reply_handler(reply_handler&&) = default;
|
||||
reply_handler(const reply_handler&) = delete;
|
||||
|
||||
reply_handler& operator=(reply_handler&&) = default;
|
||||
reply_handler& operator=(const reply_handler&) = delete;
|
||||
|
||||
void complete(
|
||||
error_code ec,
|
||||
byte_citer first = byte_citer {}, byte_citer last = byte_citer {}
|
||||
) {
|
||||
std::move(_handler)(ec, first, last);
|
||||
}
|
||||
|
||||
void complete_post(const executor_type& ex, error_code ec) {
|
||||
asio::post(
|
||||
ex,
|
||||
asio::prepend(
|
||||
std::move(_handler), ec, byte_citer {}, byte_citer {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t packet_id() const noexcept {
|
||||
return _packet_id;
|
||||
}
|
||||
|
||||
control_code_e code() const noexcept {
|
||||
return _code;
|
||||
}
|
||||
|
||||
auto time() const noexcept {
|
||||
return _ts;
|
||||
}
|
||||
};
|
||||
|
||||
executor_type _ex;
|
||||
|
||||
using handlers = std::vector<reply_handler>;
|
||||
handlers _handlers;
|
||||
|
||||
struct fast_reply {
|
||||
control_code_e code;
|
||||
uint16_t packet_id;
|
||||
std::unique_ptr<std::string> packet;
|
||||
};
|
||||
using fast_replies = std::vector<fast_reply>;
|
||||
fast_replies _fast_replies;
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
explicit replies(Executor ex) : _ex(std::move(ex)) {}
|
||||
|
||||
replies(replies&&) = default;
|
||||
replies(const replies&) = delete;
|
||||
|
||||
replies& operator=(replies&&) = default;
|
||||
replies& operator=(const replies&) = delete;
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_wait_reply(
|
||||
control_code_e code, uint16_t packet_id, CompletionToken&& token
|
||||
) {
|
||||
auto dup_handler_ptr = find_handler(code, packet_id);
|
||||
if (dup_handler_ptr != _handlers.end()) {
|
||||
dup_handler_ptr->complete_post(_ex, asio::error::operation_aborted);
|
||||
_handlers.erase(dup_handler_ptr);
|
||||
}
|
||||
|
||||
auto freply = find_fast_reply(code, packet_id);
|
||||
|
||||
if (freply == _fast_replies.end()) {
|
||||
auto initiation = [](
|
||||
auto handler, replies& self,
|
||||
control_code_e code, uint16_t packet_id
|
||||
) {
|
||||
self._handlers.emplace_back(
|
||||
code, packet_id, std::move(handler)
|
||||
);
|
||||
};
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), code, packet_id
|
||||
);
|
||||
}
|
||||
|
||||
auto fdata = std::move(*freply);
|
||||
_fast_replies.erase(freply);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, std::unique_ptr<std::string> packet,
|
||||
const executor_type& ex
|
||||
) {
|
||||
byte_citer first = packet->cbegin();
|
||||
byte_citer last = packet->cend();
|
||||
|
||||
asio::post(
|
||||
ex,
|
||||
asio::consign(
|
||||
asio::prepend(
|
||||
std::move(handler), error_code {}, first, last
|
||||
),
|
||||
std::move(packet)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::move(fdata.packet), _ex
|
||||
);
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
error_code ec, control_code_e code, uint16_t packet_id,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
auto handler_ptr = find_handler(code, packet_id);
|
||||
|
||||
if (handler_ptr == _handlers.end()) {
|
||||
_fast_replies.push_back({
|
||||
code, packet_id,
|
||||
std::make_unique<std::string>(first, last)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = std::move(*handler_ptr);
|
||||
_handlers.erase(handler_ptr);
|
||||
handler.complete(ec, first, last);
|
||||
}
|
||||
|
||||
void resend_unanswered() {
|
||||
auto ua = std::move(_handlers);
|
||||
for (auto& h : ua)
|
||||
h.complete(asio::error::try_again);
|
||||
}
|
||||
|
||||
void cancel_unanswered() {
|
||||
auto ua = std::move(_handlers);
|
||||
for (auto& h : ua)
|
||||
h.complete_post(_ex, asio::error::operation_aborted);
|
||||
}
|
||||
|
||||
bool any_expired() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
return std::any_of(
|
||||
_handlers.begin(), _handlers.end(),
|
||||
[now](const auto& h) {
|
||||
return now - h.time() > max_reply_time;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void clear_fast_replies() {
|
||||
_fast_replies.clear();
|
||||
}
|
||||
|
||||
void clear_pending_pubrels() {
|
||||
for (auto it = _handlers.begin(); it != _handlers.end();) {
|
||||
if (it->code() == control_code_e::pubrel) {
|
||||
it->complete(asio::error::operation_aborted);
|
||||
it = _handlers.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
handlers::iterator find_handler(control_code_e code, uint16_t packet_id) {
|
||||
return std::find_if(
|
||||
_handlers.begin(), _handlers.end(),
|
||||
[code, packet_id](const auto& h) {
|
||||
return h.code() == code && h.packet_id() == packet_id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fast_replies::iterator find_fast_reply(
|
||||
control_code_e code, uint16_t packet_id
|
||||
) {
|
||||
return std::find_if(
|
||||
_fast_replies.begin(), _fast_replies.end(),
|
||||
[code, packet_id](const auto& f) {
|
||||
return f.code == code && f.packet_id == packet_id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_REPLIES_HPP
|
||||
@@ -1,142 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_RUN_OP_HPP
|
||||
#define ASYNC_MQTT5_RUN_OP_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/read_message_op.hpp>
|
||||
#include <async_mqtt5/impl/ping_op.hpp>
|
||||
#include <async_mqtt5/impl/sentry_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class run_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
run_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
run_op(run_op&&) = default;
|
||||
run_op(const run_op&) = delete;
|
||||
|
||||
run_op& operator=(run_op&&) = default;
|
||||
run_op& operator=(const run_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
_svc_ptr->_stream.open();
|
||||
_svc_ptr->_rec_channel.reset();
|
||||
|
||||
auto init_read_message_op = [](
|
||||
auto handler, std::shared_ptr<client_service> svc_ptr
|
||||
) {
|
||||
return read_message_op { std::move(svc_ptr), std::move(handler) }
|
||||
.perform();
|
||||
};
|
||||
|
||||
auto init_ping_op = [](
|
||||
auto handler, std::shared_ptr<client_service> svc_ptr
|
||||
) {
|
||||
return ping_op { std::move(svc_ptr), std::move(handler) }
|
||||
.perform();
|
||||
};
|
||||
|
||||
auto init_senty_op = [](
|
||||
auto handler, std::shared_ptr<client_service> svc_ptr
|
||||
) {
|
||||
return sentry_op { std::move(svc_ptr), std::move(handler) }
|
||||
.perform();
|
||||
};
|
||||
|
||||
asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void ()>(
|
||||
init_read_message_op, asio::deferred, _svc_ptr
|
||||
),
|
||||
asio::async_initiate<const asio::deferred_t, void ()>(
|
||||
init_ping_op, asio::deferred, _svc_ptr
|
||||
),
|
||||
asio::async_initiate<const asio::deferred_t, void ()>(
|
||||
init_senty_op, asio::deferred, _svc_ptr
|
||||
)
|
||||
).async_wait(asioex::wait_for_all(), std::move(*this));
|
||||
}
|
||||
|
||||
void operator()(std::array<std::size_t, 3> /* ord */) {
|
||||
_handler.complete(asio::error::operation_aborted);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class initiate_async_run {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_run(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(Handler&& handler) {
|
||||
run_op<ClientService, Handler> {
|
||||
_svc_ptr, std::move(handler)
|
||||
}.perform();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_RUN_OP_HPP
|
||||
@@ -1,100 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_SENTRY_OP_HPP
|
||||
#define ASYNC_MQTT5_SENTRY_OP_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class sentry_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_timer {};
|
||||
struct on_disconnect {};
|
||||
|
||||
static constexpr auto check_interval = std::chrono::seconds(3);
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
sentry_op(std::shared_ptr<client_service> svc_ptr, Handler&& handler) :
|
||||
_svc_ptr(std::move(svc_ptr)), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
sentry_op(sentry_op&&) noexcept = default;
|
||||
sentry_op(const sentry_op&) = delete;
|
||||
|
||||
sentry_op& operator=(sentry_op&&) noexcept = default;
|
||||
sentry_op& operator=(const sentry_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->_sentry_timer.expires_after(check_interval);
|
||||
_svc_ptr->_sentry_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_timer, error_code) {
|
||||
if (!_svc_ptr->is_open())
|
||||
return complete();
|
||||
|
||||
if (_svc_ptr->_replies.any_expired()) {
|
||||
auto props = disconnect_props {};
|
||||
// TODO add what packet was expected?
|
||||
props[prop::reason_string] = "No reply received within 20 seconds";
|
||||
auto svc_ptr = _svc_ptr;
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::unspecified_error, props, svc_ptr,
|
||||
asio::prepend(std::move(*this), on_disconnect {})
|
||||
);
|
||||
}
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
if (ec)
|
||||
return complete();
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void complete() {
|
||||
return std::move(_handler)();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_SENTRY_OP_HPP
|
||||
@@ -1,336 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_SUBSCRIBE_OP_HPP
|
||||
#define ASYNC_MQTT5_SUBSCRIBE_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/topic_validation.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class subscribe_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_subscribe {};
|
||||
struct on_suback {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
size_t _num_topics { 0 };
|
||||
|
||||
public:
|
||||
subscribe_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
subscribe_op(subscribe_op&&) = default;
|
||||
subscribe_op(const subscribe_op&) = delete;
|
||||
|
||||
subscribe_op& operator=(subscribe_op&&) = default;
|
||||
subscribe_op& operator=(const subscribe_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props
|
||||
) {
|
||||
_num_topics = topics.size();
|
||||
|
||||
uint16_t packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_immediate(client::error::pid_overrun, packet_id);
|
||||
|
||||
if (_num_topics == 0)
|
||||
return complete_immediate(client::error::invalid_topic, packet_id);
|
||||
|
||||
auto ec = validate_subscribe(topics, props);
|
||||
if (ec)
|
||||
return complete_immediate(ec, packet_id);
|
||||
|
||||
auto subscribe = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_subscribe, packet_id,
|
||||
topics, props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (subscribe.size() > max_packet_size)
|
||||
return complete_immediate(client::error::packet_too_large, packet_id);
|
||||
|
||||
send_subscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void send_subscribe(control_packet<allocator_type> subscribe) {
|
||||
auto wire_data = subscribe.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(
|
||||
std::move(*this), on_subscribe {}, std::move(subscribe)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void resend_subscribe(control_packet<allocator_type> subscribe) {
|
||||
if (_handler.cancelled() != asio::cancellation_type_t::none)
|
||||
return complete(
|
||||
asio::error::operation_aborted, subscribe.packet_id()
|
||||
);
|
||||
send_subscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_subscribe, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return resend_subscribe(std::move(packet));
|
||||
|
||||
auto packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::suback, packet_id,
|
||||
asio::prepend(std::move(*this), on_suback {}, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_suback, control_packet<allocator_type> packet,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_subscribe(std::move(packet));
|
||||
|
||||
uint16_t packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto suback = decoders::decode_suback(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!suback.has_value()) {
|
||||
on_malformed_packet("Malformed SUBACK: cannot decode");
|
||||
return resend_subscribe(std::move(packet));
|
||||
}
|
||||
|
||||
auto& [props, rcs] = *suback;
|
||||
auto reason_codes = to_reason_codes(std::move(rcs));
|
||||
if (reason_codes.size() != _num_topics) {
|
||||
on_malformed_packet(
|
||||
"Malformed SUBACK: does not contain a "
|
||||
"valid Reason Code for every Topic Filter"
|
||||
);
|
||||
return resend_subscribe(std::move(packet));
|
||||
}
|
||||
|
||||
complete(
|
||||
ec, packet_id, std::move(reason_codes), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
error_code validate_subscribe(
|
||||
const std::vector<subscribe_topic>& topics, const subscribe_props& props
|
||||
) const {
|
||||
error_code ec;
|
||||
for (const auto& topic: topics) {
|
||||
ec = validate_topic(topic);
|
||||
if (ec)
|
||||
return ec;
|
||||
}
|
||||
|
||||
ec = validate_props(props);
|
||||
return ec;
|
||||
}
|
||||
|
||||
error_code validate_topic(const subscribe_topic& topic) const {
|
||||
auto wildcard_available = _svc_ptr->connack_property(
|
||||
prop::wildcard_subscription_available
|
||||
).value_or(1);
|
||||
auto shared_available = _svc_ptr->connack_property(
|
||||
prop::shared_subscription_available
|
||||
).value_or(1);
|
||||
|
||||
std::string_view topic_filter = topic.topic_filter;
|
||||
|
||||
validation_result result = validation_result::valid;
|
||||
if (
|
||||
topic_filter.compare(0, shared_sub_prefix.size(), shared_sub_prefix) == 0
|
||||
) {
|
||||
if (!shared_available)
|
||||
return client::error::shared_subscription_not_available;
|
||||
|
||||
result = validate_shared_topic_filter(topic_filter, wildcard_available);
|
||||
} else
|
||||
result = wildcard_available ?
|
||||
validate_topic_filter(topic_filter) :
|
||||
validate_topic_name(topic_filter);
|
||||
|
||||
if (result == validation_result::invalid)
|
||||
return client::error::invalid_topic;
|
||||
if (!wildcard_available && result != validation_result::valid)
|
||||
return client::error::wildcard_subscription_not_available;
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
error_code validate_props(const subscribe_props& props) const {
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& sub_id = props[prop::subscription_identifier];
|
||||
if (!sub_id.has_value())
|
||||
return error_code {};
|
||||
|
||||
auto sub_id_available = _svc_ptr->connack_property(
|
||||
prop::subscription_identifier_available
|
||||
).value_or(1);
|
||||
|
||||
if (!sub_id_available)
|
||||
return client::error::subscription_identifier_not_available;
|
||||
|
||||
return (min_subscription_identifier <= *sub_id &&
|
||||
*sub_id <= max_subscription_identifier) ?
|
||||
error_code {} :
|
||||
client::error::malformed_packet;
|
||||
}
|
||||
|
||||
static std::vector<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
|
||||
std::vector<reason_code> ret;
|
||||
for (uint8_t code : codes) {
|
||||
auto rc = to_reason_code<reason_codes::category::suback>(code);
|
||||
if (rc)
|
||||
ret.push_back(*rc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete_immediate(error_code ec, uint16_t packet_id) {
|
||||
if (packet_id != 0)
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete_immediate(
|
||||
ec, std::vector<reason_code>(_num_topics, reason_codes::empty),
|
||||
suback_props {}
|
||||
);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
std::vector<reason_code> reason_codes = {}, suback_props props = {}
|
||||
) {
|
||||
if (reason_codes.empty() && _num_topics)
|
||||
reason_codes = std::vector<reason_code>(_num_topics, reason_codes::empty);
|
||||
|
||||
if (!_svc_ptr->subscriptions_present()) {
|
||||
bool has_success_rc = std::any_of(
|
||||
reason_codes.cbegin(), reason_codes.cend(),
|
||||
[](const reason_code& rc) { return !rc; }
|
||||
);
|
||||
if (has_success_rc)
|
||||
_svc_ptr->subscriptions_present(true);
|
||||
}
|
||||
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete(ec, std::move(reason_codes), std::move(props));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class initiate_async_subscribe {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_subscribe(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
const std::vector<subscribe_topic>& topics, const subscribe_props& props
|
||||
) {
|
||||
detail::subscribe_op { _svc_ptr, std::move(handler) }
|
||||
.perform(topics, props);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_SUBSCRIBE_OP_HPP
|
||||
@@ -1,277 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
#define ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <async_mqtt5/detail/control_packet.hpp>
|
||||
#include <async_mqtt5/detail/internal_types.hpp>
|
||||
#include <async_mqtt5/detail/topic_validation.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/disconnect_op.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class unsubscribe_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_unsubscribe {};
|
||||
struct on_unsuback {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
size_t _num_topics { 0 };
|
||||
|
||||
public:
|
||||
unsubscribe_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribe_op(unsubscribe_op&&) = default;
|
||||
unsubscribe_op(const unsubscribe_op&) = delete;
|
||||
|
||||
unsubscribe_op& operator=(unsubscribe_op&&) = default;
|
||||
unsubscribe_op& operator=(const unsubscribe_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
_num_topics = topics.size();
|
||||
|
||||
uint16_t packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_immediate(client::error::pid_overrun, packet_id);
|
||||
|
||||
if (_num_topics == 0)
|
||||
return complete_immediate(client::error::invalid_topic, packet_id);
|
||||
|
||||
auto ec = validate_unsubscribe(topics, props);
|
||||
if (ec)
|
||||
return complete_immediate(ec, packet_id);
|
||||
|
||||
auto unsubscribe = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_unsubscribe, packet_id,
|
||||
topics, props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (unsubscribe.size() > max_packet_size)
|
||||
return complete_immediate(client::error::packet_too_large, packet_id);
|
||||
|
||||
send_unsubscribe(std::move(unsubscribe));
|
||||
}
|
||||
|
||||
void send_unsubscribe(control_packet<allocator_type> unsubscribe) {
|
||||
auto wire_data = unsubscribe.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(
|
||||
std::move(*this), on_unsubscribe {}, std::move(unsubscribe)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void resend_unsubscribe(control_packet<allocator_type> subscribe) {
|
||||
if (_handler.cancelled() != asio::cancellation_type_t::none)
|
||||
return complete(
|
||||
asio::error::operation_aborted, subscribe.packet_id()
|
||||
);
|
||||
send_unsubscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_unsubscribe, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
|
||||
auto packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::unsuback, packet_id,
|
||||
asio::prepend(std::move(*this), on_unsuback {}, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_unsuback, control_packet<allocator_type> packet,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
|
||||
uint16_t packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto unsuback = decoders::decode_unsuback(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!unsuback.has_value()) {
|
||||
on_malformed_packet("Malformed UNSUBACK: cannot decode");
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
}
|
||||
|
||||
auto& [props, rcs] = *unsuback;
|
||||
auto reason_codes = to_reason_codes(std::move(rcs));
|
||||
if (reason_codes.size() != _num_topics) {
|
||||
on_malformed_packet(
|
||||
"Malformed UNSUBACK: does not contain a "
|
||||
"valid Reason Code for every Topic Filter"
|
||||
);
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
}
|
||||
|
||||
complete(
|
||||
ec, packet_id, std::move(reason_codes), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static error_code validate_unsubscribe(
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
for (const auto& topic : topics)
|
||||
if (validate_topic_filter(topic) != validation_result::valid)
|
||||
return client::error::invalid_topic;
|
||||
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
static std::vector<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
|
||||
std::vector<reason_code> ret;
|
||||
for (uint8_t code : codes) {
|
||||
auto rc = to_reason_code<reason_codes::category::unsuback>(code);
|
||||
if (rc)
|
||||
ret.push_back(*rc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_malformed_packet(
|
||||
const std::string& reason
|
||||
) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete_immediate(error_code ec, uint16_t packet_id) {
|
||||
if (packet_id != 0)
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete_immediate(
|
||||
ec, std::vector<reason_code>(_num_topics, reason_codes::empty),
|
||||
unsuback_props {}
|
||||
);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
std::vector<reason_code> reason_codes = {}, unsuback_props props = {}
|
||||
) {
|
||||
if (reason_codes.empty() && _num_topics)
|
||||
reason_codes = std::vector<reason_code>(_num_topics, reason_codes::empty);
|
||||
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete(ec, std::move(reason_codes), std::move(props));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class initiate_async_unsubscribe {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_unsubscribe(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
const std::vector<std::string>& topics, const unsubscribe_props& props
|
||||
) {
|
||||
detail::unsubscribe_op { _svc_ptr, std::move(handler) }
|
||||
.perform(topics, props);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
@@ -1,118 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_WRITE_OP_HPP
|
||||
#define ASYNC_MQTT5_WRITE_OP_HPP
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/async_traits.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class write_op {
|
||||
struct on_write {};
|
||||
struct on_reconnect {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
write_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
write_op(write_op&&) = default;
|
||||
write_op(const write_op&) = delete;
|
||||
|
||||
write_op& operator=(write_op&&) = default;
|
||||
write_op& operator=(const write_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(BufferType& buffer) {
|
||||
auto stream_ptr = _owner._stream_ptr;
|
||||
if (_owner.was_connected())
|
||||
// note: write operation should not be time-limited
|
||||
detail::async_write(
|
||||
*stream_ptr, buffer,
|
||||
asio::prepend(std::move(*this), on_write {}, stream_ptr)
|
||||
);
|
||||
else
|
||||
asio::post(
|
||||
_owner.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_write {},
|
||||
stream_ptr, asio::error::not_connected, 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_write, typename Owner::stream_ptr stream_ptr,
|
||||
error_code ec, size_t bytes_written
|
||||
) {
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted, 0);
|
||||
|
||||
if (!ec)
|
||||
return complete(ec, bytes_written);
|
||||
|
||||
// websocket returns operation_aborted if disconnected
|
||||
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
|
||||
return _owner.async_reconnect(
|
||||
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
|
||||
);
|
||||
|
||||
return complete(asio::error::no_recovery, 0);
|
||||
}
|
||||
|
||||
void operator()(on_reconnect, error_code ec) {
|
||||
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
|
||||
ec = asio::error::try_again;
|
||||
|
||||
return complete(ec, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, size_t bytes_written) {
|
||||
std::move(_handler)(ec, bytes_written);
|
||||
}
|
||||
|
||||
static bool should_reconnect(error_code ec) {
|
||||
using namespace asio::error;
|
||||
// note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved)
|
||||
return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */
|
||||
ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */
|
||||
ec == connection_aborted || ec == not_connected ||
|
||||
ec == timed_out || ec == connection_reset ||
|
||||
ec == broken_pipe || ec == asio::error::eof;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_WRITE_OP_HPP
|
||||
@@ -1,257 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_LOGGER_HPP
|
||||
#define ASYNC_MQTT5_LOGGER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/type_traits/remove_cv_ref.hpp>
|
||||
|
||||
#include <async_mqtt5/logger_traits.hpp>
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <async_mqtt5/detail/traits.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
/**
|
||||
* \brief Represents the severity level of log messages.
|
||||
*/
|
||||
enum class log_level : uint8_t {
|
||||
/** Error messages that indicate serious issues. **/
|
||||
error = 1,
|
||||
|
||||
/** Warnings that indicate potential problems or non-critical issues. **/
|
||||
warning,
|
||||
|
||||
/** Informational messages that highlight normal application behaviour and events. **/
|
||||
info,
|
||||
|
||||
/** Detailed messages useful for diagnosing issues. **/
|
||||
debug
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A logger class that can used by the \ref mqtt_client to output
|
||||
* the results of operations to stderr.
|
||||
*
|
||||
* \details All functions are invoked directly within the \ref mqtt_client using
|
||||
* its default executor. If the \ref mqtt_client is initialized with an explicit or
|
||||
* implicit strand, none of the functions will be invoked concurrently.
|
||||
*
|
||||
* \par Thread safety
|
||||
* ['Distinct objects]: unsafe. \n
|
||||
* ['Shared objects]: unsafe. \n
|
||||
* This class is [*not thread-safe].
|
||||
*/
|
||||
class logger {
|
||||
constexpr static auto prefix = "[Async.MQTT5]";
|
||||
|
||||
log_level _level;
|
||||
public:
|
||||
|
||||
/**
|
||||
* \brief Constructs a logger that filters log messages based on the specified log level.
|
||||
*
|
||||
* \param level Messages with a log level higher than the given log level will be suppressed.
|
||||
*/
|
||||
logger(log_level level = log_level::warning) : _level(level) {}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the resolve operation.
|
||||
*
|
||||
* \param ec Error code returned by the resolve operation.
|
||||
* \param host Hostname used in the resolve operation.
|
||||
* \param port Port used in the resolve operation.
|
||||
* \param eps Endpoints returned by the resolve operation.
|
||||
*/
|
||||
void at_resolve(
|
||||
error_code ec, std::string_view host, std::string_view port,
|
||||
const asio::ip::tcp::resolver::results_type& eps
|
||||
) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "resolve: "
|
||||
<< host << ":" << port;
|
||||
std::clog << " - " << ec.message();
|
||||
|
||||
if (_level == log_level::debug) {
|
||||
std::clog << " [";
|
||||
for (auto it = eps.begin(); it != eps.end();) {
|
||||
std::clog << it->endpoint().address().to_string();
|
||||
if (++it != eps.end())
|
||||
std::clog << ",";
|
||||
}
|
||||
std::clog << "]";
|
||||
}
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the TCP connect operation.
|
||||
*
|
||||
* \param ec Error code returned by the TCP connect operation.
|
||||
* \param ep The TCP endpoint used to establish the TCP connection.
|
||||
*/
|
||||
void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "TCP connect: "
|
||||
<< ep.address().to_string() << ":" << ep.port()
|
||||
<< " - " << ec.message()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the TLS handshake operation.
|
||||
*
|
||||
* \param ec Error code returned by the TLS handshake operation.
|
||||
* \param ep The TCP endpoint used to establish the TLS handshake.
|
||||
*/
|
||||
void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "TLS handshake: "
|
||||
<< ep.address().to_string() << ":" << ep.port()
|
||||
<< " - " << ec.message()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the WebSocket handshake operation.
|
||||
*
|
||||
* \param ec Error code returned by the WebSocket handshake operation.
|
||||
* \param ep The TCP endpoint used to establish the WebSocket handshake.
|
||||
*/
|
||||
void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "WebSocket handshake: "
|
||||
<< ep.address().to_string() << ":" << ep.port()
|
||||
<< " - " << ec.message()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the contents of the \__CONNACK\__ packet sent by the Broker.
|
||||
*
|
||||
* \param rc Reason Code in the received \__CONNACK\__ packet indicating
|
||||
* the result of the MQTT handshake.
|
||||
* \param session_present A flag indicating whether the Broker already has a session associated
|
||||
* with this connection.
|
||||
* \param ca_props \__CONNACK_PROPS\__ received in the \__CONNACK\__ packet.
|
||||
*/
|
||||
void at_connack(
|
||||
reason_code rc,
|
||||
bool session_present, const connack_props& ca_props
|
||||
) {
|
||||
if (!rc && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog << "connack: " << rc.message() << ".";
|
||||
if (_level == log_level::debug) {
|
||||
std::clog << " session_present:" << session_present << " ";
|
||||
output_props(ca_props);
|
||||
}
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the contents of the \__DISCONNECT\__ packet sent by the Broker.
|
||||
*
|
||||
* \param rc Reason Code in the received \__DISCONNECT\__ packet indicating
|
||||
* the reason behind the disconnection.
|
||||
* \param dc_props \__DISCONNECT_PROPS\__ received in the \__DISCONNECT\__ packet.
|
||||
*/
|
||||
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
|
||||
output_prefix();
|
||||
std::clog << "disconnect: " << rc.message() << ".";
|
||||
if (_level == log_level::debug)
|
||||
output_props(dc_props);
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
void output_prefix() {
|
||||
std::clog << prefix << " ";
|
||||
}
|
||||
|
||||
template <typename Props>
|
||||
void output_props(const Props& props) {
|
||||
props.visit(
|
||||
[](const auto& prop, const auto& val) -> bool {
|
||||
if constexpr (detail::is_optional<decltype(val)>) {
|
||||
if (val.has_value()) {
|
||||
std::clog << property_name(prop) << ":";
|
||||
using value_type = boost::remove_cv_ref_t<decltype(*val)>;
|
||||
if constexpr (std::is_same_v<value_type, uint8_t>)
|
||||
std::clog << std::to_string(*val) << " ";
|
||||
else
|
||||
std::clog << *val << " ";
|
||||
}
|
||||
} else { // is vector
|
||||
if (val.empty())
|
||||
return true;
|
||||
|
||||
std::clog << property_name(prop) << ":";
|
||||
std::clog << "[";
|
||||
for (auto i = 0; i < val.size(); i++) {
|
||||
if constexpr (detail::is_pair<decltype(val[i])>)
|
||||
std::clog << "(" << val[i].first << "," << val[i].second << ")";
|
||||
else
|
||||
std::clog << std::to_string(val[i]);
|
||||
if (i + 1 < val.size())
|
||||
std::clog << ", ";
|
||||
}
|
||||
std::clog << "]";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
static std::string_view property_name(std::integral_constant<prop::property_type, p>) {
|
||||
return prop::name_v<p>;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Verify that the logger class satisfies the LoggerType concept
|
||||
static_assert(has_at_resolve<logger>);
|
||||
static_assert(has_at_tcp_connect<logger>);
|
||||
static_assert(has_at_tls_handshake<logger>);
|
||||
static_assert(has_at_ws_handshake<logger>);
|
||||
static_assert(has_at_connack<logger>);
|
||||
static_assert(has_at_disconnect<logger>);
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
|
||||
#endif // !ASYNC_MQTT5_LOGGER_HPP
|
||||
@@ -1,981 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_MQTT_CLIENT_HPP
|
||||
#define ASYNC_MQTT5_MQTT_CLIENT_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant> // std::monostate
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/error.hpp>
|
||||
#include <async_mqtt5/logger_traits.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
#include <async_mqtt5/detail/log_invoke.hpp>
|
||||
#include <async_mqtt5/detail/rebind_executor.hpp>
|
||||
|
||||
#include <async_mqtt5/impl/client_service.hpp>
|
||||
#include <async_mqtt5/impl/run_op.hpp>
|
||||
#include <async_mqtt5/impl/publish_send_op.hpp>
|
||||
#include <async_mqtt5/impl/re_auth_op.hpp>
|
||||
#include <async_mqtt5/impl/subscribe_op.hpp>
|
||||
#include <async_mqtt5/impl/unsubscribe_op.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
/**
|
||||
* \brief \__MQTT\__ client used to connect and communicate with a Broker.
|
||||
*
|
||||
* \tparam \__StreamType\__ Type of the underlying transport protocol used to transfer
|
||||
* the stream of bytes between the Client and the Broker. The transport must be
|
||||
* ordered and lossless.
|
||||
* \tparam \__TlsContext\__ Type of the context object used in TLS/SSL connections.
|
||||
* \tparam \__LoggerType\__ Type of object used to log events within the Client.
|
||||
*
|
||||
* \par Thread safety
|
||||
* ['Distinct objects]: safe. \n
|
||||
* ['Shared objects]: unsafe. \n
|
||||
* This class is [*not thread-safe].
|
||||
* The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.
|
||||
*/
|
||||
template <
|
||||
typename StreamType,
|
||||
typename TlsContext = std::monostate,
|
||||
typename LoggerType = noop_logger
|
||||
>
|
||||
class mqtt_client {
|
||||
public:
|
||||
/// The executor type associated with the client.
|
||||
using executor_type = typename StreamType::executor_type;
|
||||
|
||||
/// Rebinds the client type to another executor.
|
||||
template <typename Executor>
|
||||
struct rebind_executor {
|
||||
/// The client type when rebound to the specified executor.
|
||||
using other = mqtt_client<
|
||||
typename detail::rebind_executor<StreamType, Executor>::other,
|
||||
TlsContext
|
||||
>;
|
||||
};
|
||||
|
||||
private:
|
||||
using stream_type = StreamType;
|
||||
using tls_context_type = TlsContext;
|
||||
using logger_type = LoggerType;
|
||||
|
||||
using client_service_type = detail::client_service<
|
||||
stream_type, tls_context_type, logger_type
|
||||
>;
|
||||
using impl_type = std::shared_ptr<client_service_type>;
|
||||
impl_type _impl;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs a Client with given parameters.
|
||||
*
|
||||
* \param ex An executor that will be associated with the Client.
|
||||
* \param tls_context A context object used in TLS/SSL connection.
|
||||
* \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client.
|
||||
*/
|
||||
explicit mqtt_client(
|
||||
const executor_type& ex,
|
||||
tls_context_type tls_context = {}, logger_type logger = {}
|
||||
) :
|
||||
_impl(std::make_shared<client_service_type>(
|
||||
ex, std::move(tls_context), std::move(logger)
|
||||
))
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Constructs a Client with given parameters.
|
||||
*
|
||||
* \tparam \__ExecutionContext\__ Type of a concrete execution context.
|
||||
* \param context Execution context whose executor will be associated with the Client.
|
||||
* \param tls_context A context object used in TLS/SSL connection.
|
||||
* \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client.
|
||||
*
|
||||
* \par Precondition
|
||||
* \code
|
||||
* std::is_convertible_v<ExecutionContext&, asio::execution_context&>
|
||||
* \endcode
|
||||
*/
|
||||
template <
|
||||
typename ExecutionContext,
|
||||
std::enable_if_t<
|
||||
std::is_convertible_v<ExecutionContext&, asio::execution_context&>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
explicit mqtt_client(
|
||||
ExecutionContext& context,
|
||||
tls_context_type tls_context = {}, logger_type logger = {}
|
||||
) :
|
||||
mqtt_client(
|
||||
context.get_executor(),
|
||||
std::move(tls_context), std::move(logger)
|
||||
)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Move-construct an mqtt_client from another.
|
||||
*
|
||||
* \details Moved-from client can only be destructed
|
||||
*/
|
||||
mqtt_client(mqtt_client&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* \brief Move assignment operator.
|
||||
*
|
||||
* \details Cancels this client first. Moved-from client can only be destructed.
|
||||
*/
|
||||
mqtt_client& operator=(mqtt_client&& other) noexcept {
|
||||
_impl->cancel();
|
||||
_impl = std::move(other._impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destructor.
|
||||
*
|
||||
* \details Automatically calls \ref mqtt_client::cancel.
|
||||
*/
|
||||
~mqtt_client() {
|
||||
if (_impl)
|
||||
_impl->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the executor associated with the object.
|
||||
*/
|
||||
executor_type get_executor() const noexcept {
|
||||
return _impl->get_executor();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the context object used in TLS/SSL connection.
|
||||
*
|
||||
* \note This function may only be invoked
|
||||
* when the template parameter \__TlsContext\__ was configured
|
||||
* with non-default type during the creation of a \ref mqtt_client.
|
||||
*
|
||||
* \par Precondition
|
||||
* \code
|
||||
* !std::is_same_v<TlsContext, std::monostate>
|
||||
* \endcode
|
||||
*/
|
||||
template <
|
||||
typename Ctx = TlsContext,
|
||||
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, bool> = true
|
||||
>
|
||||
decltype(auto) tls_context() {
|
||||
return _impl->tls_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Start the Client.
|
||||
*
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (__ERROR_CODE__)
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete with
|
||||
* `boost::asio::error::operation_aborted` when the client is cancelled by calling
|
||||
* \ref mqtt_client::async_disconnect, \ref mqtt_client::cancel, destruction or
|
||||
* if a non-recoverable error happens during a connection attempt (e.g. access denied).
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::asio::error::operation_aborted`\n
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_run(CompletionToken&& token = {}) {
|
||||
using Signature = void (error_code);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_run(_impl), token
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Cancel all asynchronous operations. This function has terminal effects.
|
||||
*
|
||||
* \details All outstanding operations will complete
|
||||
* with `boost::asio::error::operation_aborted`.
|
||||
*
|
||||
* \attention This function has terminal effects and will close the Client.
|
||||
* The Client cannot be used before calling \ref mqtt_client::async_run again.
|
||||
*/
|
||||
void cancel() {
|
||||
auto impl = _impl;
|
||||
_impl = impl->dup();
|
||||
impl->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign a \ref will Message.
|
||||
*
|
||||
* \details The \ref will Message that the Broker should publish
|
||||
* after the Network Connection is closed and it is not
|
||||
* closed normally.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*/
|
||||
mqtt_client& will(will will) {
|
||||
_impl->will(std::move(will));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign credentials that will be used to connect to a Broker.
|
||||
*
|
||||
* \details Credentials consist of a unique Client Identifier and, optionally,
|
||||
* a User Name and Password.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*/
|
||||
mqtt_client& credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_impl->credentials(
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign a list of Brokers that the Client will attempt to connect to.
|
||||
*
|
||||
* \details The Client will cycle through the list of hosts,
|
||||
* attempting to establish a connection with each
|
||||
* until it successfully establishes a connection.
|
||||
*
|
||||
* \param hosts List of Broker addresses and ports.
|
||||
* Address and ports are separated with a colon `:` while
|
||||
* pairs of addresses and ports are separated with a comma `,`.
|
||||
* \param default_port The default port to connect to in case the port is not
|
||||
* explicitly specified in the `hosts` list.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*
|
||||
* \par Example
|
||||
* Some valid `hosts` string:
|
||||
*
|
||||
* \code
|
||||
* std::string valid_hosts_1 = "broker1:1883, broker2, broker3:1883";
|
||||
* std::string valid_hosts_2 = "broker1";
|
||||
* \endcode
|
||||
*
|
||||
*/
|
||||
mqtt_client& brokers(std::string hosts, uint16_t default_port = 1883) {
|
||||
_impl->brokers(std::move(hosts), default_port);
|
||||
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 re_authenticate.
|
||||
*
|
||||
* \param authenticator Object that will be stored (move-constructed or by reference)
|
||||
* and used for authentication. It needs to satisfy \__is_authenticator\__ concept.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename Authenticator,
|
||||
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
|
||||
>
|
||||
mqtt_client& authenticator(Authenticator&& authenticator) {
|
||||
_impl->authenticator(std::forward<Authenticator>(authenticator));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign the maximum time interval that is permitted to elapse between
|
||||
* two transmissions from the Client.
|
||||
*
|
||||
* \details A non-zero value initiates a process of sending a \__PINGREQ\__
|
||||
* packet every `seconds`. If this function is not invoked, the Client assumes
|
||||
* a \__KEEP_ALIVE\__ interval of 60 seconds.
|
||||
*
|
||||
* \param seconds Time interval in seconds.
|
||||
*
|
||||
* \note If the Server sends a \__SERVER_KEEP_ALIVE\__,
|
||||
* the Client will send a \__PINGREQ\__ packet every \__SERVER_KEEP_ALIVE\__ seconds.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*
|
||||
*/
|
||||
mqtt_client& keep_alive(uint16_t seconds) {
|
||||
_impl->keep_alive(seconds);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign \__CONNECT_PROPS\__ that will be sent in a \__CONNECT\__ packet.
|
||||
* \param props \__CONNECT_PROPS\__ sent in a \__CONNECT\__ packet.
|
||||
* \see See \__CONNECT_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
mqtt_client& connect_properties(connect_props props) {
|
||||
_impl->connect_properties(std::move(props));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign a property that will be sent in a \__CONNECT\__ packet.
|
||||
* \param prop The \__CONNECT_PROPS\__ property to set.
|
||||
* \param value Value that will be assigned to the property.
|
||||
*
|
||||
* \par Example
|
||||
* \code
|
||||
* client.connect_property(prop::session_expiry_interval, 40); // ok
|
||||
* client.connect_property(prop::reason_string, "reason"); // does not compile, not a CONNECT prop!
|
||||
* \endcode
|
||||
*
|
||||
* \see See \__CONNECT_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
template <prop::property_type p>
|
||||
mqtt_client& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop,
|
||||
prop::value_type_t<p> value
|
||||
) {
|
||||
_impl->connect_property(prop, std::move(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Initiates [mqttlink 3901257 Re-authentication]
|
||||
* using the authenticator given in the \ref authenticator method.
|
||||
*
|
||||
* \note If \ref authenticator was not called, this method does nothing.
|
||||
*/
|
||||
void re_authenticate() {
|
||||
detail::re_auth_op { _impl }.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Retrieves the value of a specific property from the last \__CONNACK\__ packet received.
|
||||
*
|
||||
* \details The return type varies according to the property requested.
|
||||
* For all properties, the return type will be `std::optional` of their respective value type.
|
||||
* For `async_mqtt5::prop::user_property`, the return type is
|
||||
* `std::vector<std::pair<std::string, std::string>>`.
|
||||
*
|
||||
* \param prop The \__CONNACK_PROPS\__ property value to retrieve.
|
||||
*
|
||||
* \par Example
|
||||
* \code
|
||||
* std::optional<std::string> auth_method = client.connack_property(async_mqtt5::prop::authentication_method); // ok
|
||||
* std::optional<std::string> c_type = client.connack_property(async_mqtt5::prop::content_type); // does not compile, not a CONNACK prop!
|
||||
* \endcode
|
||||
*
|
||||
* \see See \__CONNACK_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _impl->connack_property(prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Retrieves the \__CONNACK_PROPS\__ from the last \__CONNACK\__ packet received.
|
||||
*
|
||||
* \see See \__CONNACK_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
const connack_props& connack_properties() const {
|
||||
return _impl->connack_properties();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send a \__PUBLISH\__ packet to Broker to transport an
|
||||
* Application Message.
|
||||
*
|
||||
* \tparam qos_type The \ref qos_e level of assurance for delivery.
|
||||
* \param topic Identification of the information channel to which
|
||||
* Payload data is published.
|
||||
* \param payload The Application Message that is being published.
|
||||
* \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 completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation depends on the \ref qos_e specified:\n
|
||||
*
|
||||
* `qos` == `qos_e::at_most_once`:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__ // Result of operation
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* `qos` == `qos_e::at_least_once`:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* __REASON_CODE__, // Reason Code received from Broker.
|
||||
* __PUBACK_PROPS__ // Properties received in the PUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* `qos` == `qos_e::exactly_once`:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* __REASON_CODE__, // Reason Code received from Broker.
|
||||
* __PUBCOMP_PROPS__ // Properties received in the PUBCOMP packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* Depending on the \ref qos_e specified, the asynchronous operation will complete
|
||||
* when one of the following conditions is true:\n
|
||||
* - If `qos` == `qos_e::at_most_once` and the Client
|
||||
* has successfully written the packet to the transport. \n
|
||||
* - If `qos` == `qos_e::at_least_once` and the packet has
|
||||
* been sent and acknowledged through the reception of a \__PUBACK\__ packet.
|
||||
* - If `qos` == `qos_e::exactly_once` and the packet has
|
||||
* been sent and fully acknowledged through the reception of a \__PUBCOMP\__ packet.
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link async_mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link async_mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link async_mqtt5::client::error::qos_not_supported \endlink
|
||||
* - \link async_mqtt5::client::error::retain_not_available \endlink
|
||||
* - \link async_mqtt5::client::error::topic_alias_maximum_reached \endlink
|
||||
* - \link async_mqtt5::client::error::invalid_topic \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__PUBLISH\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <qos_e qos_type,
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_publish(
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
using Signature = detail::on_publish_signature<qos_type>;
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_publish<client_service_type, qos_type>(_impl),
|
||||
token,
|
||||
std::move(topic), std::move(payload), retain, props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription
|
||||
* to one or more Topics of interest.
|
||||
*
|
||||
* \details After the subscription has been established, the Broker will send
|
||||
* PUBLISH packets to the Client to forward Application Messages that were published
|
||||
* to Topics that the Client subscribed to. The Application Messages can be received
|
||||
* with \ref mqtt_client::async_receive function.
|
||||
*
|
||||
* \param topics A list of \ref subscribe_topic of interest.
|
||||
* \param props An instance of \__SUBSCRIBE_PROPS\__.
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating
|
||||
* // the subscription result for each Topic
|
||||
* // in the SUBSCRIBE packet.
|
||||
* __SUBACK_PROPS__, // Properties received in the SUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent a \__SUBSCRIBE\__ packet
|
||||
* and has received a \__SUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link async_mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link async_mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link async_mqtt5::client::error::invalid_topic \endlink
|
||||
* - \link async_mqtt5::client::error::wildcard_subscription_not_available \endlink
|
||||
* - \link async_mqtt5::client::error::subscription_identifier_not_available \endlink
|
||||
* - \link async_mqtt5::client::error::shared_subscription_not_available \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_subscribe(
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
using Signature = void (
|
||||
error_code, std::vector<reason_code>, suback_props
|
||||
);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_subscribe(_impl), token,
|
||||
topics, props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription
|
||||
* to one Topic of interest.
|
||||
*
|
||||
* \details After the subscription has been established, the Broker will send
|
||||
* \__PUBLISH\__ packets to the Client to forward Application Messages that were published
|
||||
* to Topics that the Client subscribed to. The Application Messages can be received
|
||||
* with \ref mqtt_client::async_receive function.
|
||||
*
|
||||
* \param 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 completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes containing the
|
||||
* // single subscription result for the Topic
|
||||
* // in the SUBSCRIBE packet.
|
||||
* __SUBACK_PROPS__, // Properties received in the SUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent a \__SUBSCRIBE\__ packet
|
||||
* and has received a \__SUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link async_mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link async_mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link async_mqtt5::client::error::invalid_topic \endlink
|
||||
* - \link async_mqtt5::client::error::wildcard_subscription_not_available \endlink
|
||||
* - \link async_mqtt5::client::error::subscription_identifier_not_available \endlink
|
||||
* - \link async_mqtt5::client::error::shared_subscription_not_available \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_subscribe(
|
||||
const subscribe_topic& topic, const subscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
return async_subscribe(
|
||||
std::vector<subscribe_topic> { topic }, props,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe from one
|
||||
* or more Topics.
|
||||
*
|
||||
* \note The Client may still receive residual Application Messages
|
||||
* through the \ref mqtt_client::async_receive function
|
||||
* from Topics the Client just unsubscribed to.
|
||||
*
|
||||
* \param topics List of Topics to unsubscribe from.
|
||||
* \param props An instance of \__UNSUBSCRIBE_PROPS\__.
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating
|
||||
* // the result of unsubscribe operation
|
||||
* // for each Topic in the UNSUBSCRIBE packet.
|
||||
* __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent an \__UNSUBSCRIBE\__ packet
|
||||
* and has received an \__UNSUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link async_mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link async_mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link async_mqtt5::client::error::invalid_topic \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_unsubscribe(
|
||||
const std::vector<std::string>& topics, const unsubscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
using Signature = void (
|
||||
error_code, std::vector<reason_code>, unsuback_props
|
||||
);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_unsubscribe(_impl), token,
|
||||
topics, props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe
|
||||
* from one Topic.
|
||||
*
|
||||
* \note The Client may still receive residual Application Messages
|
||||
* through the \ref mqtt_client::async_receive function
|
||||
* from Topics the Client just unsubscribed to.
|
||||
*
|
||||
* \param 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 completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes containing
|
||||
* // the result of unsubscribe operation
|
||||
* // for the Topic in the UNSUBSCRIBE packet.
|
||||
* __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent an \__UNSUBSCRIBE\__ packet
|
||||
* and has received an \__UNSUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link async_mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link async_mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link async_mqtt5::client::error::invalid_topic \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_unsubscribe(
|
||||
const std::string& topic, const unsubscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
return async_unsubscribe(
|
||||
std::vector<std::string> { topic }, props,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Asynchronously receive an Application Message.
|
||||
*
|
||||
* \details The Client will receive and complete deliveries for all the
|
||||
* \__PUBLISH\__ packets received from the Broker throughout its lifetime.
|
||||
* The Client will store them internally in the order they were delivered.
|
||||
* Calling this function will attempt to receive an Application Message
|
||||
* from internal storage.
|
||||
*
|
||||
* \note It is only recommended to call this function if you have established
|
||||
* a successful subscription to a Topic using the \ref async_subscribe function.
|
||||
*
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__POST\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::string, // Topic, the origin of the Application Message.
|
||||
* std::string, // Payload, the content of the Application Message.
|
||||
* __PUBLISH_PROPS__, // Properties received in the PUBLISH packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has a pending Application Message in its internal storage
|
||||
* ready to be received.
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success`\n
|
||||
* - `boost::asio::error::operation_aborted`\n
|
||||
* - \link async_mqtt5::client::error::session_expired \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` \n
|
||||
* - `cancellation_type::partial` \n
|
||||
* - `cancellation_type::total` \n
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_receive(CompletionToken&& token = {}) {
|
||||
return _impl->async_channel_receive(std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Disconnect the Client by sending a \__DISCONNECT\__ packet
|
||||
* with a specified Reason Code. This function has terminal effects.
|
||||
*
|
||||
* \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker
|
||||
* with a Reason Code describing the reason for disconnection.
|
||||
* If the \__DISCONNECT\__ packet is successfully transmitted,
|
||||
* or if `5 seconds` elapsed without a successful send, the Client will terminate the connection.
|
||||
*
|
||||
* \attention This function has terminal effects and will close the Client.
|
||||
* See \ref mqtt_client::cancel.
|
||||
*
|
||||
* \param reason_code Reason Code to notify
|
||||
* the Broker of the reason for the 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 completes.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__ // Result of operation.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has sent a \__DISCONNECT\__ packet.\n
|
||||
* - 5 seconds have elapsed without a successful send.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success`\n
|
||||
* - `boost::asio::error::operation_aborted`[footnote
|
||||
This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server.
|
||||
Regardless, the connection to the Server is terminated, and the Client is cancelled.
|
||||
]\n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
auto impl = _impl;
|
||||
_impl = impl->dup();
|
||||
return detail::async_terminal_disconnect(
|
||||
detail::disconnect_rc_e(static_cast<uint8_t>(reason_code)),
|
||||
props, impl, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Disconnect the Client by sending a \__DISCONNECT\__ packet
|
||||
* with a Reason Code of reason_codes.normal_disconnection.
|
||||
* This function has terminal effects.
|
||||
*
|
||||
* \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker
|
||||
* with a Reason Code describing the reason for disconnection.
|
||||
* If the \__DISCONNECT\__ packet is successfully transmitted,
|
||||
* or if `5 seconds` elapsed without a successful send, the Client will terminate the connection.
|
||||
*
|
||||
* \attention This function has terminal effects and will close the Client.
|
||||
* 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 completes.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__ // Result of operation.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has attempted to send a \__DISCONNECT\__ packet, regardless of whether
|
||||
* the sending was successful or not.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success`\n
|
||||
* - `boost::asio::error::operation_aborted`[footnote
|
||||
This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server.
|
||||
Regardless, the connection to the Server is terminated, and the Client is cancelled.
|
||||
]\n
|
||||
* - \link async_mqtt5::client::error::malformed_packet \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_disconnect(CompletionToken&& token = {}) {
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::normal_disconnection,
|
||||
disconnect_props {}, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_MQTT_CLIENT_HPP
|
||||
@@ -1,253 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_PROPERTY_TYPES_HPP
|
||||
#define ASYNC_MQTT5_PROPERTY_TYPES_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
namespace async_mqtt5::prop {
|
||||
|
||||
enum property_type : uint8_t {
|
||||
payload_format_indicator_t = 0x01,
|
||||
message_expiry_interval_t = 0x02,
|
||||
content_type_t = 0x03,
|
||||
response_topic_t = 0x08,
|
||||
correlation_data_t = 0x09,
|
||||
subscription_identifier_t = 0x0b,
|
||||
session_expiry_interval_t = 0x11,
|
||||
assigned_client_identifier_t = 0x12,
|
||||
server_keep_alive_t = 0x13,
|
||||
authentication_method_t = 0x15,
|
||||
authentication_data_t = 0x16,
|
||||
request_problem_information_t = 0x17,
|
||||
will_delay_interval_t = 0x18,
|
||||
request_response_information_t = 0x19,
|
||||
response_information_t = 0x1a,
|
||||
server_reference_t = 0x1c,
|
||||
reason_string_t = 0x1f,
|
||||
receive_maximum_t = 0x21,
|
||||
topic_alias_maximum_t = 0x22,
|
||||
topic_alias_t = 0x23,
|
||||
maximum_qos_t = 0x24,
|
||||
retain_available_t = 0x25,
|
||||
user_property_t = 0x26,
|
||||
maximum_packet_size_t = 0x27,
|
||||
wildcard_subscription_available_t = 0x28,
|
||||
subscription_identifier_available_t = 0x29,
|
||||
shared_subscription_available_t = 0x2a
|
||||
};
|
||||
|
||||
class alignas(8) subscription_identifiers :
|
||||
public boost::container::small_vector<int32_t, 1>
|
||||
{
|
||||
using base_type = boost::container::small_vector<int32_t, 1>;
|
||||
|
||||
public:
|
||||
using base_type::base_type;
|
||||
subscription_identifiers(int32_t val) : base_type { val } {}
|
||||
|
||||
bool has_value() const noexcept {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
int32_t& operator*() noexcept {
|
||||
return front();
|
||||
}
|
||||
|
||||
int32_t operator*() const noexcept {
|
||||
return front();
|
||||
}
|
||||
|
||||
void emplace(int32_t val = 0) {
|
||||
*this = val;
|
||||
}
|
||||
|
||||
int32_t value() const {
|
||||
return front();
|
||||
}
|
||||
|
||||
int32_t value_or(int32_t default_val) const noexcept {
|
||||
return empty() ? default_val : front();
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
template <property_type p>
|
||||
struct property_traits;
|
||||
|
||||
using user_property_value_t = std::vector<std::pair<std::string, std::string>>;
|
||||
|
||||
#define DEF_PROPERTY_TRAIT(Pname, Ptype) \
|
||||
template <> \
|
||||
struct property_traits<Pname##_t> { \
|
||||
static constexpr std::string_view name = #Pname; \
|
||||
using type = Ptype; \
|
||||
}; \
|
||||
constexpr std::integral_constant<property_type, Pname##_t> Pname {};
|
||||
|
||||
DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(content_type, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(response_topic, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(correlation_data, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(subscription_identifier, subscription_identifiers);
|
||||
DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(server_keep_alive, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(authentication_method, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(authentication_data, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(request_problem_information, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(will_delay_interval, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(request_response_information, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(response_information, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(server_reference, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(reason_string, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(receive_maximum, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(topic_alias, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(maximum_qos, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(retain_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(user_property, user_property_value_t);
|
||||
DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional<uint8_t>);
|
||||
|
||||
#undef DEF_PROPERTY_TRAIT
|
||||
|
||||
template <property_type p>
|
||||
using value_type_t = typename property_traits<p>::type;
|
||||
|
||||
template <property_type p>
|
||||
constexpr std::string_view name_v = property_traits<p>::name;
|
||||
|
||||
template <property_type ...Ps>
|
||||
class properties {
|
||||
|
||||
template <property_type p>
|
||||
struct property {
|
||||
using key = std::integral_constant<property_type, p>;
|
||||
constexpr static std::string_view name = name_v<p>;
|
||||
value_type_t<p> value;
|
||||
};
|
||||
std::tuple<property<Ps>...> _props;
|
||||
|
||||
public:
|
||||
|
||||
template <property_type v>
|
||||
constexpr auto& operator[](std::integral_constant<property_type, v>)
|
||||
noexcept {
|
||||
return std::get<property<v>>(_props).value;
|
||||
}
|
||||
|
||||
template <property_type v>
|
||||
constexpr const auto& operator[](std::integral_constant<property_type, v>)
|
||||
const noexcept {
|
||||
return std::get<property<v>>(_props).value;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
using is_apply_on = std::conjunction<
|
||||
std::is_invocable<Func, value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <typename Func>
|
||||
using is_nothrow_apply_on = std::conjunction<
|
||||
std::is_nothrow_invocable<Func, value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <
|
||||
typename Func,
|
||||
std::enable_if_t<is_apply_on<Func>::value, bool> = true
|
||||
>
|
||||
constexpr bool apply_on(uint8_t property_id, Func&& func)
|
||||
noexcept (is_nothrow_apply_on<Func>::value) {
|
||||
return std::apply(
|
||||
[&func, property_id](auto&... ptype) {
|
||||
auto pc = [&func, property_id](auto& px) {
|
||||
using ptype = std::remove_reference_t<decltype(px)>;
|
||||
constexpr typename ptype::key prop;
|
||||
if (prop.value == property_id)
|
||||
std::invoke(func, px.value);
|
||||
return prop.value != property_id;
|
||||
};
|
||||
return (pc(ptype) && ...);
|
||||
},
|
||||
_props
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
using is_visitor = std::conjunction<
|
||||
std::is_invocable_r<bool, Func, decltype(Ps), value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <typename Func>
|
||||
using is_nothrow_visitor = std::conjunction<
|
||||
std::is_nothrow_invocable<Func, decltype(Ps), value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <
|
||||
typename Func,
|
||||
std::enable_if_t<is_visitor<Func>::value, bool> = true
|
||||
>
|
||||
constexpr bool visit(Func&& func)
|
||||
const noexcept (is_nothrow_visitor<Func>::value) {
|
||||
return std::apply(
|
||||
[&func](const auto&... props) {
|
||||
auto pc = [&func](const auto& px) {
|
||||
using ptype = std::remove_reference_t<decltype(px)>;
|
||||
constexpr typename ptype::key prop;
|
||||
return std::invoke(func, prop, px.value);
|
||||
};
|
||||
return (pc(props) &&...);
|
||||
},
|
||||
_props
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Func,
|
||||
std::enable_if_t<is_visitor<Func>::value, bool> = true
|
||||
>
|
||||
constexpr bool visit(Func&& func)
|
||||
noexcept (is_nothrow_visitor<Func>::value) {
|
||||
return std::apply(
|
||||
[&func](auto&... props) {
|
||||
auto pc = [&func](auto& px) {
|
||||
using ptype = std::remove_reference_t<decltype(px)>;
|
||||
constexpr typename ptype::key prop;
|
||||
return std::invoke(func, prop, px.value);
|
||||
};
|
||||
return (pc(props) && ...);
|
||||
},
|
||||
_props
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5::prop
|
||||
|
||||
#endif // !ASYNC_MQTT5_PROPERTY_TYPES_HPP
|
||||
@@ -1,495 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_REASON_CODES_HPP
|
||||
#define ASYNC_MQTT5_REASON_CODES_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
/// \cond internal
|
||||
namespace reason_codes {
|
||||
|
||||
enum class category : uint8_t {
|
||||
none,
|
||||
connack, puback, pubrec,
|
||||
pubrel, pubcomp, suback,
|
||||
unsuback, auth, disconnect
|
||||
};
|
||||
|
||||
} // end namespace reason_codes
|
||||
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* \brief A class holding Reason Code values originating from Control Packets.
|
||||
*
|
||||
* \details A Reason Code is a one byte unsigned value that indicates the result of an operation.
|
||||
* Reason Codes less than 0x80 indicate successful completion of an operation.
|
||||
* The normal Reason Code for success is 0.
|
||||
* Reason Code values of 0x80 or greater indicate failure.
|
||||
* The \__CONNACK\__, \__PUBACK\__, \__PUBREC\__, \__PUBREL\__, \__PUBCOMP\__, \__DISCONNECT\__
|
||||
* and \__AUTH\__ Control Packets have a single Reason Code as part of the Variable Header.
|
||||
* The \__SUBACK\__ and \__UNSUBACK\__ packets contain a list of one or more Reason Codes in the Payload.
|
||||
*
|
||||
* \see See \__REASON_CODES\__ for a complete list of all possible instances of this class.
|
||||
*/
|
||||
class reason_code {
|
||||
uint8_t _code;
|
||||
reason_codes::category _category { reason_codes::category::none };
|
||||
public:
|
||||
/// \cond INTERNAL
|
||||
constexpr reason_code() : _code(0xff) {}
|
||||
|
||||
constexpr reason_code(uint8_t code, reason_codes::category cat) :
|
||||
_code(code), _category(cat)
|
||||
{}
|
||||
|
||||
constexpr explicit reason_code(uint8_t code) : _code(code) {}
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* \brief Indication if the object holds a Reason Code indicating an error.
|
||||
*
|
||||
* \details Any Reason Code holding a value equal to or greater than 0x80.
|
||||
*/
|
||||
explicit operator bool() const noexcept {
|
||||
return _code >= 0x80;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the byte value of the Reason Code.
|
||||
*/
|
||||
constexpr uint8_t value() const noexcept {
|
||||
return _code;
|
||||
}
|
||||
|
||||
/// Insertion operator.
|
||||
friend std::ostream& operator<<(std::ostream& os, const reason_code& rc) {
|
||||
os << rc.message();
|
||||
return os;
|
||||
}
|
||||
|
||||
/// Operator less than.
|
||||
friend bool operator<(const reason_code& lhs, const reason_code& rhs) {
|
||||
return lhs._code < rhs._code;
|
||||
}
|
||||
|
||||
/// Equality operator.
|
||||
friend bool operator==(const reason_code& lhs, const reason_code& rhs) {
|
||||
return lhs._code == rhs._code && lhs._category == rhs._category;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a message describing the meaning behind the Reason Code.
|
||||
*/
|
||||
std::string message() const {
|
||||
switch (_code) {
|
||||
case 0x00:
|
||||
if (_category == reason_codes::category::suback)
|
||||
return "The subscription is accepted with maximum QoS sent at 0";
|
||||
if (_category == reason_codes::category::disconnect)
|
||||
return "Close the connection normally. Do not send the Will Message";
|
||||
return "The operation completed successfully";
|
||||
case 0x01:
|
||||
return "The subscription is accepted with maximum QoS sent at 1";
|
||||
case 0x02:
|
||||
return "The subscription is accepted with maximum QoS sent at 2";
|
||||
case 0x04:
|
||||
return "The Client wishes to disconnect but requires"
|
||||
"that the Server also publishes its Will Message";
|
||||
case 0x10:
|
||||
return "The message is accepted but there are no subscribers";
|
||||
case 0x11:
|
||||
return "No matching Topic Filter is being used by the Client.";
|
||||
case 0x18:
|
||||
return "Continue the authentication with another step";
|
||||
case 0x19:
|
||||
return "Initiate a re-authentication";
|
||||
case 0x80:
|
||||
return "The Server does not wish to reveal the reason for the"
|
||||
"failure or none of the other Reason Codes apply";
|
||||
case 0x81:
|
||||
return "Data within the packet could not be correctly parsed";
|
||||
case 0x82:
|
||||
return "Data in the packet does not conform to this specification";
|
||||
case 0x83:
|
||||
return "The packet is valid but not accepted by this Server";
|
||||
case 0x84:
|
||||
return "The Server does not support the requested "
|
||||
"version of the MQTT protocol";
|
||||
case 0x85:
|
||||
return "The Client ID is valid but not allowed by this Server";
|
||||
case 0x86:
|
||||
return "The Server does not accept the User Name or Password provided";
|
||||
case 0x87:
|
||||
return "The request is not authorized";
|
||||
case 0x88:
|
||||
return "The MQTT Server is not available";
|
||||
case 0x89:
|
||||
return "The MQTT Server is busy, try again later";
|
||||
case 0x8a:
|
||||
return "The Client has been banned by administrative action";
|
||||
case 0x8b:
|
||||
return "The Server is shutting down";
|
||||
case 0x8c:
|
||||
return "The authentication method is not supported or "
|
||||
"does not match the method currently in use";
|
||||
case 0x8d:
|
||||
return "No packet has been received for 1.5 times the Keepalive time";
|
||||
case 0x8e:
|
||||
return "Another Connection using the same ClientID has connected "
|
||||
"causing this Connection to be closed";
|
||||
case 0x8f:
|
||||
return "The Topic Filer is not malformed, but it is not accepted";
|
||||
case 0x90:
|
||||
return "The Topic Name is not malformed, but it is not accepted";
|
||||
case 0x91:
|
||||
return "The Packet Identifier is already in use";
|
||||
case 0x92:
|
||||
return "The Packet Identifier is not known";
|
||||
case 0x93:
|
||||
return "The Client or Server has received more than the Receive "
|
||||
"Maximum publication for which it has not sent PUBACK or PUBCOMP";
|
||||
case 0x94:
|
||||
return "The Client or Server received a PUBLISH packet containing "
|
||||
"a Topic Alias greater than the Maximum Topic Alias";
|
||||
case 0x95:
|
||||
return "The packet exceeded the maximum permissible size";
|
||||
case 0x96:
|
||||
return "The received data rate is too high";
|
||||
case 0x97:
|
||||
return "An implementation or administrative imposed limit has been exceeded";
|
||||
case 0x98:
|
||||
return "The Connection is closed due to an administrative action";
|
||||
case 0x99:
|
||||
return "The Payload does not match the specified Payload Format Indicator";
|
||||
case 0x9a:
|
||||
return "The Server does not support retained messages";
|
||||
case 0x9b:
|
||||
return "The Server does not support the QoS the Client specified or "
|
||||
"it is greater than the Maximum QoS specified";
|
||||
case 0x9c:
|
||||
return "The Client should temporarily use another server";
|
||||
case 0x9d:
|
||||
return "The Client should permanently use another server";
|
||||
case 0x9e:
|
||||
return "The Server does not support Shared Subscriptions for this Client";
|
||||
case 0x9f:
|
||||
return "The connection rate limit has been exceeded";
|
||||
case 0xa0:
|
||||
return "The maximum connection time authorized for this "
|
||||
"connection has been exceeded";
|
||||
case 0xa1:
|
||||
return "The Server does not support Subscription Identifiers";
|
||||
case 0xa2:
|
||||
return "The Server does not support Wildcard Subscriptions";
|
||||
case 0xff:
|
||||
return "No reason code";
|
||||
default:
|
||||
return "Invalid reason code";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace reason_codes {
|
||||
|
||||
/** No Reason Code. A \ref client::error occurred.*/
|
||||
constexpr reason_code empty {};
|
||||
|
||||
/** The operation completed successfully. */
|
||||
constexpr reason_code success { 0x00 };
|
||||
|
||||
/** Close the connection normally. Do not send the Will Message. */
|
||||
constexpr reason_code normal_disconnection { 0x00, category::disconnect };
|
||||
|
||||
/** The subscription is accepted with maximum QoS sent at 0. */
|
||||
constexpr reason_code granted_qos_0 { 0x00, category::suback };
|
||||
|
||||
/** The subscription is accepted with maximum QoS sent at 1. */
|
||||
constexpr reason_code granted_qos_1 { 0x01 };
|
||||
|
||||
/** The subscription is accepted with maximum QoS sent at 2 */
|
||||
constexpr reason_code granted_qos_2 { 0x02 };
|
||||
|
||||
/** The Client wishes to disconnect but requires that
|
||||
the Server also publishes its Will Message. */
|
||||
constexpr reason_code disconnect_with_will_message { 0x04 };
|
||||
|
||||
/** The message is accepted but there are no subscribers. */
|
||||
constexpr reason_code no_matching_subscribers { 0x10 };
|
||||
|
||||
/** No matching Topic Filter is being used by the Client. */
|
||||
constexpr reason_code no_subscription_existed { 0x11 };
|
||||
|
||||
/** Continue the authentication with another step. */
|
||||
constexpr reason_code continue_authentication { 0x18 };
|
||||
|
||||
/** Initiate a re-authentication. */
|
||||
constexpr reason_code reauthenticate { 0x19 };
|
||||
|
||||
/** The Server does not wish to reveal the reason for the
|
||||
failure or none of the other Reason Codes apply. */
|
||||
constexpr reason_code unspecified_error { 0x80 };
|
||||
|
||||
/** Data within the packet could not be correctly parsed. */
|
||||
constexpr reason_code malformed_packet { 0x81 };
|
||||
|
||||
/** Data in the packet does not conform to this specification. */
|
||||
constexpr reason_code protocol_error { 0x82 };
|
||||
|
||||
/** The packet is valid but not accepted by this Server. */
|
||||
constexpr reason_code implementation_specific_error { 0x83 };
|
||||
|
||||
/** The Server does not support the requested version of the MQTT protocol. */
|
||||
constexpr reason_code unsupported_protocol_version { 0x84 };
|
||||
|
||||
/** The Client ID is valid but not allowed by this Server. */
|
||||
constexpr reason_code client_identifier_not_valid { 0x85 };
|
||||
|
||||
/** The Server does not accept the User Name or Password provided. */
|
||||
constexpr reason_code bad_username_or_password { 0x86 };
|
||||
|
||||
/** The request is not authorized. */
|
||||
constexpr reason_code not_authorized { 0x87 };
|
||||
|
||||
/** The MQTT Server is not available. */
|
||||
constexpr reason_code server_unavailable { 0x88 };
|
||||
|
||||
/** The MQTT Server is busy, try again later. */
|
||||
constexpr reason_code server_busy { 0x89 };
|
||||
|
||||
/** The Client has been banned by administrative action. */
|
||||
constexpr reason_code banned { 0x8a };
|
||||
|
||||
/** The Server is shutting down. */
|
||||
constexpr reason_code server_shutting_down { 0x8b };
|
||||
|
||||
/** The authentication method is not supported or
|
||||
does not match the method currently in use. */
|
||||
constexpr reason_code bad_authentication_method { 0x8c };
|
||||
|
||||
/** No packet has been received for 1.5 times the Keepalive time. */
|
||||
constexpr reason_code keep_alive_timeout { 0x8d };
|
||||
|
||||
/** Another Connection using the same ClientID has connected
|
||||
causing this Connection to be closed. */
|
||||
constexpr reason_code session_taken_over { 0x8e };
|
||||
|
||||
/** The Topic Filter is not malformed, but it is not accepted. */
|
||||
constexpr reason_code topic_filter_invalid { 0x8f };
|
||||
|
||||
/** The Topic Name is not malformed, but it is not accepted. */
|
||||
constexpr reason_code topic_name_invalid { 0x90 };
|
||||
|
||||
/** The Packet Identifier is already in use. */
|
||||
constexpr reason_code packet_identifier_in_use { 0x91 };
|
||||
|
||||
/** The Packet Identifier is not known. */
|
||||
constexpr reason_code packet_identifier_not_found { 0x92 };
|
||||
|
||||
/** The Client or Server has received more than the Receive
|
||||
Maximum publication for which it has not sent PUBACK or PUBCOMP. */
|
||||
constexpr reason_code receive_maximum_exceeded { 0x93 };
|
||||
|
||||
/** The Client or Server received a PUBLISH packet containing
|
||||
a Topic Alias greater than the Maximum Topic Alias. */
|
||||
constexpr reason_code topic_alias_invalid { 0x94 };
|
||||
|
||||
/** The packet exceeded the maximum permissible size. */
|
||||
constexpr reason_code packet_too_large { 0x95 };
|
||||
|
||||
/** The received data rate is too high. */
|
||||
constexpr reason_code message_rate_too_high { 0x96 };
|
||||
|
||||
/** An implementation or administrative imposed limit has been exceeded. */
|
||||
constexpr reason_code quota_exceeded { 0x97 };
|
||||
|
||||
/** The Connection is closed due to an administrative action. */
|
||||
constexpr reason_code administrative_action { 0x98 };
|
||||
|
||||
/** The Payload does not match the specified Payload Format Indicator. */
|
||||
constexpr reason_code payload_format_invalid { 0x99 };
|
||||
|
||||
/** The Server does not support retained messages. */
|
||||
constexpr reason_code retain_not_supported { 0x9a };
|
||||
|
||||
/** The Server does not support the QoS the Client specified or
|
||||
it is greater than the Maximum QoS specified. */
|
||||
constexpr reason_code qos_not_supported { 0x9b };
|
||||
|
||||
/** The Client should temporarily use another server. */
|
||||
constexpr reason_code use_another_server { 0x9c };
|
||||
|
||||
/** The Client should permanently use another server. */
|
||||
constexpr reason_code server_moved { 0x9d };
|
||||
|
||||
/** The Server does not support Shared Subscriptions for this Client. */
|
||||
constexpr reason_code shared_subscriptions_not_supported { 0x9e };
|
||||
|
||||
/** The connection rate limit has been exceeded. */
|
||||
constexpr reason_code connection_rate_exceeded { 0x9f };
|
||||
|
||||
/** The maximum connection time authorized for this
|
||||
connection has been exceeded. */
|
||||
constexpr reason_code maximum_connect_time { 0xa0 };
|
||||
|
||||
/** The Server does not support Subscription Identifiers. */
|
||||
constexpr reason_code subscription_ids_not_supported { 0xa1 };
|
||||
|
||||
/** The Server does not support Wildcard Subscriptions. */
|
||||
constexpr reason_code wildcard_subscriptions_not_supported { 0xa2 };
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::connack, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, unspecified_error, malformed_packet,
|
||||
protocol_error, implementation_specific_error,
|
||||
unsupported_protocol_version, client_identifier_not_valid,
|
||||
bad_username_or_password, not_authorized,
|
||||
server_unavailable, server_busy, banned,
|
||||
bad_authentication_method, topic_name_invalid,
|
||||
packet_too_large, quota_exceeded,
|
||||
payload_format_invalid, retain_not_supported,
|
||||
qos_not_supported, use_another_server,
|
||||
server_moved, connection_rate_exceeded
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::auth, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, continue_authentication, reauthenticate
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<
|
||||
cat == category::puback || cat == category::pubrec, bool
|
||||
> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, no_matching_subscribers, unspecified_error,
|
||||
implementation_specific_error, not_authorized,
|
||||
topic_name_invalid, packet_identifier_in_use,
|
||||
quota_exceeded, payload_format_invalid
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<
|
||||
cat == category::pubrel || cat == category::pubcomp, bool
|
||||
> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, packet_identifier_not_found
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::suback, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
granted_qos_0, granted_qos_1, granted_qos_2,
|
||||
unspecified_error, implementation_specific_error,
|
||||
not_authorized, topic_filter_invalid,
|
||||
packet_identifier_in_use, quota_exceeded,
|
||||
shared_subscriptions_not_supported,
|
||||
subscription_ids_not_supported,
|
||||
wildcard_subscriptions_not_supported
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::unsuback, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, no_subscription_existed,
|
||||
unspecified_error, implementation_specific_error,
|
||||
not_authorized, topic_filter_invalid,
|
||||
packet_identifier_in_use
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::disconnect, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
normal_disconnection, unspecified_error,
|
||||
malformed_packet, protocol_error,
|
||||
implementation_specific_error, not_authorized,
|
||||
server_busy, server_shutting_down,
|
||||
keep_alive_timeout, session_taken_over,
|
||||
topic_filter_invalid, topic_name_invalid,
|
||||
receive_maximum_exceeded, topic_alias_invalid,
|
||||
packet_too_large, message_rate_too_high,
|
||||
quota_exceeded, administrative_action,
|
||||
payload_format_invalid, retain_not_supported,
|
||||
qos_not_supported, use_another_server,
|
||||
server_moved, shared_subscriptions_not_supported,
|
||||
connection_rate_exceeded, maximum_connect_time,
|
||||
subscription_ids_not_supported,
|
||||
wildcard_subscriptions_not_supported
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace detail
|
||||
} // end namespace reason_codes
|
||||
|
||||
|
||||
template <reason_codes::category cat>
|
||||
std::optional<reason_code> to_reason_code(uint8_t code) {
|
||||
auto [ptr, len] = reason_codes::detail::valid_codes<cat>();
|
||||
auto it = std::lower_bound(ptr, ptr + len, reason_code(code));
|
||||
|
||||
if (it->value() == code)
|
||||
return *it;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_REASON_CODES_HPP
|
||||
@@ -1,368 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_TYPES_HPP
|
||||
#define ASYNC_MQTT5_TYPES_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
/** An alias for `boost::system::error_code`. */
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
/**
|
||||
* \brief A data structure used to store information related to an authority
|
||||
* such as the hostname, port, and path.
|
||||
*/
|
||||
struct authority_path {
|
||||
/** The hostname of the authority as a domain name or an IP address. */
|
||||
std::string host;
|
||||
|
||||
/** The port number used for communication. */
|
||||
std::string port;
|
||||
|
||||
/** Specifies the endpoint path relevant to WebSocket connections. */
|
||||
std::string path;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the Quality of Service (\__QOS__\)
|
||||
* property of the \__PUBLISH\__ packets.
|
||||
*
|
||||
* \details Determines how the \__PUBLISH\__ packets are delivered
|
||||
* from the sender to the receiver.
|
||||
*/
|
||||
enum class qos_e : std::uint8_t {
|
||||
/** The message arrives at the receiver either once or not at all. */
|
||||
at_most_once = 0b00,
|
||||
|
||||
/** Ensures the message arrives at the receiver at least once. */
|
||||
at_least_once = 0b01,
|
||||
|
||||
/** All messages arrive at the receiver exactly once without
|
||||
loss or duplication of the messages. */
|
||||
exactly_once = 0b10
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the \__RETAIN\__ flag in the \__PUBLISH\__ packets.
|
||||
*
|
||||
* \details This flag informs the Server about whether or not it should
|
||||
* store the current message.
|
||||
*/
|
||||
enum class retain_e : std::uint8_t {
|
||||
/** The Server will replace any existing retained message for this Topic
|
||||
with this message. */
|
||||
yes = 0b1,
|
||||
|
||||
/** The Server will not store this message and will not remove or replace
|
||||
any existing retained message. */
|
||||
no = 0b0
|
||||
};
|
||||
|
||||
|
||||
enum class dup_e : std::uint8_t {
|
||||
yes = 0b1, no = 0b0
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the stage of \__ENHANCED_AUTH\__ process.
|
||||
*/
|
||||
enum class auth_step_e {
|
||||
/** The Client needs to send initial authentication data. */
|
||||
client_initial,
|
||||
|
||||
/** Server responded with reason_codes.continue_authentication and possibly
|
||||
* authentication data, the Client needs to send further authentication data.
|
||||
*/
|
||||
server_challenge,
|
||||
|
||||
/** Server responded with reason_codes.success and final
|
||||
* authentication data, which the Client validates.
|
||||
*/
|
||||
server_final
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Representation of the No Local Subscribe Option.
|
||||
*
|
||||
* \details A Subscribe Option indicating whether or not Application Messages
|
||||
* will be forwarded to a connection with a ClientID equal to the ClientID of the
|
||||
* publishing connection.
|
||||
*/
|
||||
enum class no_local_e : std::uint8_t {
|
||||
/** Application Messages can be forwarded to a connection with equal ClientID. */
|
||||
no = 0b0,
|
||||
|
||||
/** Application Messages MUST NOT be forwarded to a connection with equal ClientID. */
|
||||
yes = 0b1
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Representation of the Retain As Published Subscribe Option.
|
||||
*
|
||||
* \details A Subscribe Option indicating whether or not Application Messages forwarded
|
||||
* using this subscription keep the \__RETAIN\__ flag they were published with.
|
||||
*/
|
||||
enum class retain_as_published_e : std::uint8_t {
|
||||
/** Application Messages have the \__RETAIN\__ flag set to 0. */
|
||||
dont = 0b0,
|
||||
|
||||
/** Application Messages keep the \__RETAIN\__ flag they were published with. */
|
||||
retain = 0b1
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Representation of the Retain Handling Subscribe Option.
|
||||
*
|
||||
* \details A Subscribe Option specifying whether retained messages are sent
|
||||
* when the subscription is established.
|
||||
*/
|
||||
enum class retain_handling_e : std::uint8_t {
|
||||
/** Send retained messages at the time of subscribe. */
|
||||
send = 0b00,
|
||||
|
||||
/** Send retained message only if the subscription does not currently exist. */
|
||||
new_subscription_only = 0b01,
|
||||
|
||||
/** Do not send retained messages at the time of subscribe. */
|
||||
not_send = 0b10
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the \__SUBSCRIBE_OPTIONS\__ associated with each Subscription.
|
||||
*/
|
||||
struct subscribe_options {
|
||||
/// Maximum \__QOS\__ level at which the Server can send Application Messages to the Client.
|
||||
qos_e max_qos = qos_e::exactly_once;
|
||||
|
||||
/// Option determining if Application Messages will be forwarded to a connection with an equal ClientID.
|
||||
no_local_e no_local = no_local_e::yes;
|
||||
|
||||
/// Option determining if Application Message will keep their \__RETAIN\__ flag.
|
||||
retain_as_published_e retain_as_published = retain_as_published_e::retain;
|
||||
|
||||
/// Option determining if retained messages are sent when the subscription is established.
|
||||
retain_handling_e retain_handling = retain_handling_e::new_subscription_only;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A representation of a Topic Subscription consisting of a Topic Filter and
|
||||
* Subscribe Options.
|
||||
*/
|
||||
struct subscribe_topic {
|
||||
/// An UTF-8 Encoded String indicating the Topics to which the Client wants to subscribe.
|
||||
std::string topic_filter;
|
||||
|
||||
/// The \ref subscribe_options associated with the subscription.
|
||||
subscribe_options sub_opts;
|
||||
};
|
||||
|
||||
/// \cond
|
||||
|
||||
class connect_props : public prop::properties<
|
||||
prop::session_expiry_interval_t,
|
||||
prop::receive_maximum_t,
|
||||
prop::maximum_packet_size_t,
|
||||
prop::topic_alias_maximum_t,
|
||||
prop::request_response_information_t,
|
||||
prop::request_problem_information_t,
|
||||
prop::user_property_t,
|
||||
prop::authentication_method_t,
|
||||
prop::authentication_data_t
|
||||
> {};
|
||||
|
||||
class connack_props : public prop::properties<
|
||||
prop::session_expiry_interval_t,
|
||||
prop::receive_maximum_t,
|
||||
prop::maximum_qos_t,
|
||||
prop::retain_available_t,
|
||||
prop::maximum_packet_size_t,
|
||||
prop::assigned_client_identifier_t,
|
||||
prop::topic_alias_maximum_t,
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t,
|
||||
prop::wildcard_subscription_available_t,
|
||||
prop::subscription_identifier_available_t,
|
||||
prop::shared_subscription_available_t,
|
||||
prop::server_keep_alive_t,
|
||||
prop::response_information_t,
|
||||
prop::server_reference_t,
|
||||
prop::authentication_method_t,
|
||||
prop::authentication_data_t
|
||||
> {};
|
||||
|
||||
class publish_props : public prop::properties<
|
||||
prop::payload_format_indicator_t,
|
||||
prop::message_expiry_interval_t,
|
||||
prop::content_type_t,
|
||||
prop::response_topic_t,
|
||||
prop::correlation_data_t,
|
||||
prop::subscription_identifier_t,
|
||||
prop::topic_alias_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
// puback, pubcomp
|
||||
class puback_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class pubcomp_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class pubrec_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class pubrel_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
|
||||
class subscribe_props : public prop::properties<
|
||||
prop::subscription_identifier_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class suback_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class unsubscribe_props : public prop::properties<
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class unsuback_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class disconnect_props : public prop::properties<
|
||||
prop::session_expiry_interval_t,
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t,
|
||||
prop::server_reference_t
|
||||
> {};
|
||||
|
||||
class auth_props : public prop::properties<
|
||||
prop::authentication_method_t,
|
||||
prop::authentication_data_t,
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
|
||||
class will_props : public prop::properties<
|
||||
prop::will_delay_interval_t,
|
||||
prop::payload_format_indicator_t,
|
||||
prop::message_expiry_interval_t,
|
||||
prop::content_type_t,
|
||||
prop::response_topic_t,
|
||||
prop::correlation_data_t,
|
||||
prop::user_property_t
|
||||
>{};
|
||||
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* \brief Represents the Will Message.
|
||||
*
|
||||
* \details A Will Message is an Application Message that
|
||||
* the Broker should publish after the Network Connection is closed
|
||||
* in cases where the Network Connection is not closed normally.
|
||||
*/
|
||||
class will : public will_props {
|
||||
std::string _topic;
|
||||
std::string _message;
|
||||
qos_e _qos; retain_e _retain;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs an empty Will Message.
|
||||
*
|
||||
* \attention Do not use!
|
||||
* An empty Will Message results in an empty Topic Name which is not valid.
|
||||
* Internal uses only.
|
||||
*/
|
||||
will() = default;
|
||||
|
||||
/**
|
||||
* \brief Construct a Will Message.
|
||||
*
|
||||
* \param topic Topic, identification of the information channel to which
|
||||
* the Will Message will be published.
|
||||
* \param message The message that will be published.
|
||||
* \param qos The \ref qos_e level used when publishing the Will Message.
|
||||
* \param retain The \ref retain_e flag specifying if the Will Message
|
||||
* is to be retained when it is published.
|
||||
*/
|
||||
will(
|
||||
std::string topic, std::string message,
|
||||
qos_e qos = qos_e::at_most_once, retain_e retain = retain_e::no
|
||||
) :
|
||||
_topic(std::move(topic)), _message(std::move(message)),
|
||||
_qos(qos), _retain(retain)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Construct a Will Message.
|
||||
*
|
||||
* \param topic Topic name, identification of the information channel to which
|
||||
* the Will Message will be published.
|
||||
* \param message The message that will be published.
|
||||
* \param qos The \ref qos_e level used when publishing the Will Message.
|
||||
* \param retain The \ref retain_e flag specifying if the Will Message
|
||||
* is to be retained when it is published.
|
||||
* \param props Will properties.
|
||||
*/
|
||||
will(
|
||||
std::string topic, std::string message,
|
||||
qos_e qos, retain_e retain, will_props props
|
||||
) :
|
||||
will_props(std::move(props)),
|
||||
_topic(std::move(topic)), _message(std::move(message)),
|
||||
_qos(qos), _retain(retain)
|
||||
{}
|
||||
|
||||
/// Get the Topic Name.
|
||||
std::string_view topic() const {
|
||||
return _topic;
|
||||
}
|
||||
|
||||
/// Get the Application Message.
|
||||
std::string_view message() const {
|
||||
return _message;
|
||||
}
|
||||
|
||||
/// Get the \ref qos_e.
|
||||
constexpr qos_e qos() const {
|
||||
return _qos;
|
||||
}
|
||||
|
||||
/// Get the \ref retain_e.
|
||||
constexpr retain_e retain() const {
|
||||
return _retain;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_TYPES_HPP
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_WEBSOCKET_HPP
|
||||
#define ASYNC_MQTT5_WEBSOCKET_HPP
|
||||
|
||||
#include <boost/beast/http/field.hpp>
|
||||
#include <boost/beast/websocket/rfc6455.hpp>
|
||||
#include <boost/beast/websocket/stream.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
|
||||
// Trait definition for Beast
|
||||
template <typename Stream>
|
||||
struct ws_handshake_traits<boost::beast::websocket::stream<Stream>> {
|
||||
|
||||
template <typename CompletionToken>
|
||||
static decltype(auto) async_handshake(
|
||||
boost::beast::websocket::stream<Stream>& stream,
|
||||
authority_path ap, CompletionToken&& token
|
||||
) {
|
||||
using namespace boost::beast;
|
||||
|
||||
// Set suggested timeout settings for the websocket
|
||||
stream.set_option(
|
||||
websocket::stream_base::timeout::suggested(role_type::client)
|
||||
);
|
||||
|
||||
stream.binary(true);
|
||||
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
stream.set_option(websocket::stream_base::decorator(
|
||||
[](websocket::request_type& req) {
|
||||
req.set(http::field::sec_websocket_protocol, "mqtt");
|
||||
req.set(http::field::user_agent, "boost.mqtt");
|
||||
})
|
||||
);
|
||||
|
||||
stream.async_handshake(
|
||||
ap.host + ':' + ap.port, ap.path,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_WEBSOCKET_HPP
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_HPP
|
||||
#define BOOST_MQTT5_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/logger.hpp>
|
||||
#include <boost/mqtt5/logger_traits.hpp>
|
||||
#include <boost/mqtt5/mqtt_client.hpp>
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#endif // !BOOST_MQTT5_HPP
|
||||
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_ANY_AUTHENTICATOR
|
||||
#define BOOST_MQTT5_ANY_AUTHENTICATOR
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/type_traits/is_detected.hpp>
|
||||
#include <boost/type_traits/is_detected_convertible.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
using auth_handler_type = asio::any_completion_handler<
|
||||
void (error_code, std::string)
|
||||
>;
|
||||
|
||||
template <typename T, typename ...Ts>
|
||||
using async_auth_sig = decltype(
|
||||
std::declval<T>().async_auth(std::declval<Ts>()...)
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
using method_sig = decltype(
|
||||
std::declval<T>().method()
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_authenticator =
|
||||
boost::is_detected<
|
||||
async_auth_sig, T,
|
||||
auth_step_e, std::string, auth_handler_type
|
||||
>::value &&
|
||||
boost::is_detected_convertible_v<std::string_view, method_sig, T>;
|
||||
|
||||
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 <
|
||||
typename Authenticator,
|
||||
typename = std::enable_if_t<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)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class any_authenticator {
|
||||
std::string _method;
|
||||
std::shared_ptr<detail::auth_fun_base> _auth_fun;
|
||||
|
||||
public:
|
||||
any_authenticator() = default;
|
||||
|
||||
template <
|
||||
typename Authenticator,
|
||||
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
|
||||
>
|
||||
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 initiation = [](
|
||||
auto handler, any_authenticator& self,
|
||||
auth_step_e step, std::string data
|
||||
) {
|
||||
self._auth_fun->async_auth(
|
||||
step, std::move(data), std::move(handler)
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), step, std::move(data)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
|
||||
#endif // !BOOST_MQTT5_ANY_AUTHENTICATOR
|
||||
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_ASYNC_MUTEX_HPP
|
||||
#define BOOST_MQTT5_ASYNC_MUTEX_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/async_traits.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/execution.hpp>
|
||||
#include <boost/asio/require.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
class async_mutex {
|
||||
public:
|
||||
using executor_type = asio::any_io_executor;
|
||||
private:
|
||||
using queued_op_t = asio::any_completion_handler<
|
||||
void (error_code)
|
||||
>;
|
||||
using queue_t = std::deque<queued_op_t>;
|
||||
|
||||
// Handler with assigned tracking executor.
|
||||
// Objects of this type are type-erased by any_completion_handler
|
||||
// and stored in the waiting queue.
|
||||
template <typename Handler, typename Executor>
|
||||
class tracked_op {
|
||||
tracking_type<Handler, Executor> _executor;
|
||||
Handler _handler;
|
||||
public:
|
||||
tracked_op(Handler&& h, const Executor& ex) :
|
||||
_executor(tracking_executor(h, ex)), _handler(std::move(h))
|
||||
{}
|
||||
|
||||
tracked_op(tracked_op&&) = default;
|
||||
tracked_op(const tracked_op&) = delete;
|
||||
|
||||
tracked_op& operator=(tracked_op&&) = default;
|
||||
tracked_op& operator=(const tracked_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = tracking_type<Handler, Executor>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
void operator()(error_code ec) {
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
// Per-operation cancellation helper.
|
||||
// It is safe to emit the cancellation signal from any thread
|
||||
// provided there are no other concurrent calls to the async_mutex.
|
||||
// The helper stores queue iterator to operation since the iterator
|
||||
// would not be invalidated by other queue operations.
|
||||
class cancel_waiting_op {
|
||||
queue_t::iterator _ihandler;
|
||||
public:
|
||||
explicit cancel_waiting_op(queue_t::iterator ih) : _ihandler(ih) {}
|
||||
|
||||
void operator()(asio::cancellation_type_t type) {
|
||||
if (type == asio::cancellation_type_t::none)
|
||||
return;
|
||||
if (*_ihandler) {
|
||||
auto h = std::move(*_ihandler);
|
||||
auto ex = asio::get_associated_executor(h);
|
||||
asio::require(ex, asio::execution::blocking.possibly)
|
||||
.execute([h = std::move(h)]() mutable {
|
||||
std::move(h)(asio::error::operation_aborted);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool _locked { false };
|
||||
queue_t _waiting;
|
||||
executor_type _ex;
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
explicit async_mutex(Executor&& ex) : _ex(std::forward<Executor>(ex)) {}
|
||||
|
||||
async_mutex(const async_mutex&) = delete;
|
||||
async_mutex& operator=(const async_mutex&) = delete;
|
||||
|
||||
~async_mutex() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept {
|
||||
return _ex;
|
||||
}
|
||||
|
||||
bool is_locked() const noexcept {
|
||||
return _locked;
|
||||
}
|
||||
|
||||
// Schedules mutex for lock operation and return immediately.
|
||||
// Calls given completion handler when mutex is locked.
|
||||
// It's the responsibility of the completion handler to unlock the mutex.
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) lock(CompletionToken&& token) noexcept {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiation = [] (auto handler, async_mutex& self) {
|
||||
self.execute_or_queue(std::move(handler));
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this)
|
||||
);
|
||||
}
|
||||
|
||||
// Unlocks the mutex. The mutex must be in locked state.
|
||||
// Next queued operation, if any, will be executed in a manner
|
||||
// equivalent to asio::post.
|
||||
void unlock() {
|
||||
while (!_waiting.empty()) {
|
||||
auto op = std::move(_waiting.front());
|
||||
_waiting.pop_front();
|
||||
if (!op) continue;
|
||||
op.get_cancellation_slot().clear();
|
||||
execute_op(std::move(op));
|
||||
return;
|
||||
}
|
||||
_locked = false;
|
||||
}
|
||||
|
||||
// Cancels all outstanding operations waiting on the mutex.
|
||||
void cancel() {
|
||||
while (!_waiting.empty()) {
|
||||
auto op = std::move(_waiting.front());
|
||||
_waiting.pop_front();
|
||||
if (!op) continue;
|
||||
op.get_cancellation_slot().clear();
|
||||
asio::require(_ex, asio::execution::blocking.never)
|
||||
.execute([ex = _ex, op = std::move(op)]() mutable {
|
||||
auto opex = asio::get_associated_executor(op, ex);
|
||||
opex.execute(
|
||||
[op = std::move(op)]() mutable {
|
||||
op(asio::error::operation_aborted);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Schedule operation to `opex` executor using `_ex` executor.
|
||||
// The operation is equivalent to asio::post(_ex, op) but
|
||||
// for some reason this form of execution is much faster.
|
||||
void execute_op(queued_op_t op) {
|
||||
asio::require(_ex, asio::execution::blocking.never)
|
||||
.execute([ex = _ex, op = std::move(op)]() mutable {
|
||||
auto opex = asio::get_associated_executor(op, ex);
|
||||
opex.execute(
|
||||
[op = std::move(op)]() mutable {
|
||||
op(error_code {});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Executes operation immediately if mutex is not locked
|
||||
// or queues it for later execution otherwise. In both cases
|
||||
// the operation will be executed in a manner equivalent
|
||||
// to asio::post to avoid recursion.
|
||||
template <typename Handler>
|
||||
void execute_or_queue(Handler&& handler) noexcept {
|
||||
tracked_op h { std::move(handler), _ex };
|
||||
if (_locked) {
|
||||
_waiting.emplace_back(std::move(h));
|
||||
auto slot = _waiting.back().get_cancellation_slot();
|
||||
if (slot.is_connected())
|
||||
slot.template emplace<cancel_waiting_op>(
|
||||
_waiting.end() - 1
|
||||
);
|
||||
}
|
||||
else {
|
||||
_locked = true;
|
||||
execute_op(queued_op_t { std::move(h) });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_ASYNC_MUTEX_HPP
|
||||
+63
-64
@@ -5,23 +5,22 @@
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_ASYNC_TRAITS_HPP
|
||||
#define ASYNC_MQTT5_ASYNC_TRAITS_HPP
|
||||
#ifndef BOOST_MQTT5_ASYNC_TRAITS_HPP
|
||||
#define BOOST_MQTT5_ASYNC_TRAITS_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/execution.hpp>
|
||||
#include <boost/asio/prefer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
#include <boost/type_traits/detected_or.hpp>
|
||||
#include <boost/type_traits/is_detected.hpp>
|
||||
#include <boost/type_traits/remove_cv_ref.hpp>
|
||||
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
@@ -44,19 +43,19 @@ namespace detail {
|
||||
|
||||
template <typename Handler, typename DfltExecutor>
|
||||
using tracking_type = std::decay_t<
|
||||
typename asio::prefer_result<
|
||||
asio::associated_executor_t<Handler, DfltExecutor>,
|
||||
asio::execution::outstanding_work_t::tracked_t
|
||||
>::type
|
||||
typename asio::prefer_result<
|
||||
asio::associated_executor_t<Handler, DfltExecutor>,
|
||||
asio::execution::outstanding_work_t::tracked_t
|
||||
>::type
|
||||
>;
|
||||
|
||||
template <typename Handler, typename DfltExecutor>
|
||||
tracking_type<Handler, DfltExecutor>
|
||||
tracking_executor(const Handler& handler, const DfltExecutor& ex) {
|
||||
return asio::prefer(
|
||||
asio::get_associated_executor(handler, ex),
|
||||
asio::execution::outstanding_work.tracked
|
||||
);
|
||||
return asio::prefer(
|
||||
asio::get_associated_executor(handler, ex),
|
||||
asio::execution::outstanding_work.tracked
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,27 +71,27 @@ using tls_handshake_type_of = boost::detected_or_t<void, tls_handshake_t, T>;
|
||||
|
||||
template <typename T, typename ...Ts>
|
||||
using async_tls_handshake_sig = decltype(
|
||||
std::declval<T&>().async_handshake(std::declval<Ts>()...)
|
||||
std::declval<T&>().async_handshake(std::declval<Ts>()...)
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
constexpr bool has_tls_handshake = boost::is_detected<
|
||||
async_tls_handshake_sig, T, tls_handshake_type_of<T>,
|
||||
decltype(handshake_handler_t)
|
||||
async_tls_handshake_sig, T, tls_handshake_type_of<T>,
|
||||
decltype(handshake_handler_t)
|
||||
>::value;
|
||||
|
||||
// websocket handshake
|
||||
|
||||
template <typename T, typename ...Ts>
|
||||
using async_ws_handshake_sig = decltype(
|
||||
std::declval<T&>().async_handshake(std::declval<Ts>()...)
|
||||
std::declval<T&>().async_handshake(std::declval<Ts>()...)
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
constexpr bool has_ws_handshake = boost::is_detected<
|
||||
async_ws_handshake_sig, T,
|
||||
std::string_view, std::string_view,
|
||||
decltype(handshake_handler_t)
|
||||
async_ws_handshake_sig, T,
|
||||
std::string_view, std::string_view,
|
||||
decltype(handshake_handler_t)
|
||||
>::value;
|
||||
|
||||
// next layer
|
||||
@@ -102,57 +101,57 @@ using next_layer_sig = decltype(std::declval<T&>().next_layer());
|
||||
|
||||
template <typename T>
|
||||
constexpr bool has_next_layer = boost::is_detected<
|
||||
next_layer_sig, boost::remove_cv_ref_t<T>
|
||||
next_layer_sig, boost::remove_cv_ref_t<T>
|
||||
>::value;
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct next_layer_type_impl {
|
||||
using type = T;
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct next_layer_type_impl<T, std::enable_if_t<has_next_layer<T>>> {
|
||||
using type = typename T::next_layer_type;
|
||||
using type = typename T::next_layer_type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using next_layer_type = typename next_layer_type_impl<
|
||||
boost::remove_cv_ref_t<T>
|
||||
boost::remove_cv_ref_t<T>
|
||||
>::type;
|
||||
|
||||
template <typename T>
|
||||
next_layer_type<T>& next_layer(T&& a) {
|
||||
if constexpr (has_next_layer<T>)
|
||||
return a.next_layer();
|
||||
else
|
||||
return std::forward<T>(a);
|
||||
if constexpr (has_next_layer<T>)
|
||||
return a.next_layer();
|
||||
else
|
||||
return std::forward<T>(a);
|
||||
}
|
||||
|
||||
// lowest layer
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct lowest_layer_type_impl {
|
||||
using type = T;
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct lowest_layer_type_impl<T, std::enable_if_t<has_next_layer<T>>> {
|
||||
using type = typename lowest_layer_type_impl<
|
||||
next_layer_type<T>
|
||||
>::type;
|
||||
using type = typename lowest_layer_type_impl<
|
||||
next_layer_type<T>
|
||||
>::type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using lowest_layer_type = typename lowest_layer_type_impl<
|
||||
boost::remove_cv_ref_t<T>
|
||||
boost::remove_cv_ref_t<T>
|
||||
>::type;
|
||||
|
||||
template <typename T>
|
||||
lowest_layer_type<T>& lowest_layer(T&& a) {
|
||||
if constexpr (has_next_layer<T>)
|
||||
return lowest_layer(a.next_layer());
|
||||
else
|
||||
return std::forward<T>(a);
|
||||
if constexpr (has_next_layer<T>)
|
||||
return lowest_layer(a.next_layer());
|
||||
else
|
||||
return std::forward<T>(a);
|
||||
}
|
||||
|
||||
// tls layer
|
||||
@@ -162,78 +161,78 @@ struct has_tls_layer_impl : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_tls_layer_impl<
|
||||
T, std::enable_if_t<has_tls_handshake<T>>
|
||||
T, std::enable_if_t<has_tls_handshake<T>>
|
||||
> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_tls_layer_impl<
|
||||
T, std::enable_if_t<!has_tls_handshake<T> && has_next_layer<T>>
|
||||
T, std::enable_if_t<!has_tls_handshake<T> && has_next_layer<T>>
|
||||
> : has_tls_layer_impl<
|
||||
boost::remove_cv_ref_t<decltype(std::declval<T&>().next_layer())>
|
||||
boost::remove_cv_ref_t<decltype(std::declval<T&>().next_layer())>
|
||||
> {};
|
||||
|
||||
template <typename T>
|
||||
constexpr bool has_tls_layer = has_tls_layer_impl<
|
||||
boost::remove_cv_ref_t<T>
|
||||
boost::remove_cv_ref_t<T>
|
||||
>::value;
|
||||
|
||||
// tls context
|
||||
|
||||
template <typename T>
|
||||
using tls_context_sig = decltype(
|
||||
std::declval<T&>().tls_context()
|
||||
std::declval<T&>().tls_context()
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
constexpr bool has_tls_context = boost::is_detected<
|
||||
tls_context_sig, T
|
||||
tls_context_sig, T
|
||||
>::value;
|
||||
|
||||
// setup_tls_sni
|
||||
|
||||
template <typename TlsContext, typename Stream>
|
||||
void setup_tls_sni(const authority_path& ap, TlsContext& ctx, Stream& s) {
|
||||
if constexpr (has_tls_handshake<Stream>)
|
||||
assign_tls_sni(ap, ctx, s);
|
||||
else if constexpr (has_next_layer<Stream>)
|
||||
setup_tls_sni(ap, ctx, next_layer(s));
|
||||
if constexpr (has_tls_handshake<Stream>)
|
||||
assign_tls_sni(ap, ctx, s);
|
||||
else if constexpr (has_next_layer<Stream>)
|
||||
setup_tls_sni(ap, ctx, next_layer(s));
|
||||
}
|
||||
|
||||
// async_write
|
||||
|
||||
template <typename T, typename ...Ts>
|
||||
using async_write_sig = decltype(
|
||||
std::declval<T&>().async_write(std::declval<Ts>()...)
|
||||
std::declval<T&>().async_write(std::declval<Ts>()...)
|
||||
);
|
||||
|
||||
constexpr auto write_handler_t = [](error_code, size_t) {};
|
||||
|
||||
template <typename T, typename B>
|
||||
constexpr bool has_async_write = boost::is_detected<
|
||||
async_write_sig, T, B, decltype(write_handler_t)
|
||||
async_write_sig, T, B, decltype(write_handler_t)
|
||||
>::value;
|
||||
|
||||
template <
|
||||
typename Stream,
|
||||
typename ConstBufferSequence,
|
||||
typename CompletionToken
|
||||
typename Stream,
|
||||
typename ConstBufferSequence,
|
||||
typename CompletionToken
|
||||
>
|
||||
decltype(auto) async_write(
|
||||
Stream& stream, const ConstBufferSequence& buff, CompletionToken&& token
|
||||
Stream& stream, const ConstBufferSequence& buff, CompletionToken&& token
|
||||
) {
|
||||
if constexpr (has_async_write<Stream, ConstBufferSequence>)
|
||||
return stream.async_write(
|
||||
buff, std::forward<CompletionToken>(token)
|
||||
);
|
||||
else
|
||||
return asio::async_write(
|
||||
stream, buff, std::forward<CompletionToken>(token)
|
||||
);
|
||||
if constexpr (has_async_write<Stream, ConstBufferSequence>)
|
||||
return stream.async_write(
|
||||
buff, std::forward<CompletionToken>(token)
|
||||
);
|
||||
else
|
||||
return asio::async_write(
|
||||
stream, buff, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
#endif // !ASYNC_MQTT5_ASYNC_TRAITS_HPP
|
||||
#endif // !BOOST_MQTT5_ASYNC_TRAITS_HPP
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
#define BOOST_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/async_traits.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/associated_immediate_executor.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
template <typename Handler, typename Executor>
|
||||
class cancellable_handler {
|
||||
Executor _executor;
|
||||
Handler _handler;
|
||||
tracking_type<Handler, Executor> _handler_ex;
|
||||
asio::cancellation_state _cancellation_state;
|
||||
|
||||
public:
|
||||
cancellable_handler(Handler&& handler, const Executor& ex) :
|
||||
_executor(ex),
|
||||
_handler(std::move(handler)),
|
||||
_handler_ex(tracking_executor(_handler, ex)),
|
||||
_cancellation_state(
|
||||
asio::get_associated_cancellation_slot(_handler),
|
||||
asio::enable_total_cancellation {},
|
||||
asio::enable_terminal_cancellation {}
|
||||
)
|
||||
{}
|
||||
|
||||
cancellable_handler(cancellable_handler&&) = default;
|
||||
cancellable_handler(const cancellable_handler&) = delete;
|
||||
|
||||
cancellable_handler& operator=(cancellable_handler&&) = default;
|
||||
cancellable_handler& operator=(const cancellable_handler&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<Handler>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::associated_cancellation_slot_t<Handler>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return _cancellation_state.slot();
|
||||
}
|
||||
|
||||
using executor_type = tracking_type<Handler, Executor>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _handler_ex;
|
||||
}
|
||||
|
||||
using immediate_executor_type =
|
||||
asio::associated_immediate_executor_t<Handler, Executor>;
|
||||
immediate_executor_type get_immediate_executor() const noexcept {
|
||||
// get_associated_immediate_executor will require asio::execution::blocking.never
|
||||
// on the default executor.
|
||||
return asio::get_associated_immediate_executor(_handler, _executor);
|
||||
}
|
||||
|
||||
asio::cancellation_type_t cancelled() const {
|
||||
return _cancellation_state.cancelled();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void complete(Args&&... args) {
|
||||
asio::get_associated_cancellation_slot(_handler).clear();
|
||||
asio::dispatch(
|
||||
_handler_ex,
|
||||
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void complete_immediate(Args&&... args) {
|
||||
asio::get_associated_cancellation_slot(_handler).clear();
|
||||
auto ex = get_immediate_executor();
|
||||
asio::dispatch(
|
||||
ex,
|
||||
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_CANCELLABLE_HANDLER_HPP
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_CHANNEL_TRAITS_HPP
|
||||
#define BOOST_MQTT5_CHANNEL_TRAITS_HPP
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
template <typename Element>
|
||||
class bounded_deque {
|
||||
std::deque<Element> _buffer;
|
||||
static constexpr size_t MAX_SIZE = 65535;
|
||||
|
||||
public:
|
||||
bounded_deque() = default;
|
||||
bounded_deque(size_t n) : _buffer(n) {}
|
||||
|
||||
size_t size() const {
|
||||
return _buffer.size();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void push_back(E&& e) {
|
||||
if (_buffer.size() == MAX_SIZE)
|
||||
_buffer.pop_front();
|
||||
_buffer.push_back(std::forward<E>(e));
|
||||
}
|
||||
|
||||
void pop_front() {
|
||||
_buffer.pop_front();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_buffer.clear();
|
||||
}
|
||||
|
||||
const auto& front() const noexcept {
|
||||
return _buffer.front();
|
||||
}
|
||||
|
||||
auto& front() noexcept {
|
||||
return _buffer.front();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Signatures>
|
||||
struct channel_traits {
|
||||
template <typename... NewSignatures>
|
||||
struct rebind {
|
||||
using other = channel_traits<NewSignatures...>;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct channel_traits<R(error_code, Args...)> {
|
||||
static_assert(sizeof...(Args) > 0);
|
||||
|
||||
template <typename... NewSignatures>
|
||||
struct rebind {
|
||||
using other = channel_traits<NewSignatures...>;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container {
|
||||
using type = bounded_deque<Element>;
|
||||
};
|
||||
|
||||
using receive_cancelled_signature = R(error_code, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f) {
|
||||
std::forward<F>(f)(
|
||||
asio::error::operation_aborted,
|
||||
typename std::decay_t<Args>()...
|
||||
);
|
||||
}
|
||||
|
||||
using receive_closed_signature = R(error_code, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f) {
|
||||
std::forward<F>(f)(
|
||||
asio::error::operation_aborted,
|
||||
typename std::decay_t<Args>()...
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_CHANNEL_TRAITS_HPP
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_CONTROL_PACKET_HPP
|
||||
#define BOOST_MQTT5_CONTROL_PACKET_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/smart_ptr/allocate_unique.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
/* max varint number (268'435'455) + fixed header size (1 + 4) */
|
||||
static constexpr int32_t default_max_send_size = 268'435'460;
|
||||
|
||||
enum class control_code_e : std::uint8_t {
|
||||
no_packet = 0b00000000, // 0
|
||||
|
||||
connect = 0b00010000, // 1
|
||||
connack = 0b00100000, // 2
|
||||
publish = 0b00110000, // 3
|
||||
puback = 0b01000000, // 4
|
||||
pubrec = 0b01010000, // 5
|
||||
pubrel = 0b01100000, // 6
|
||||
pubcomp = 0b01110000, // 7
|
||||
subscribe = 0b10000000, // 8
|
||||
suback = 0b10010000, // 9
|
||||
unsubscribe = 0b10100000, // 10
|
||||
unsuback = 0b10110000, // 11
|
||||
pingreq = 0b11000000, // 12
|
||||
pingresp = 0b11010000, // 13
|
||||
disconnect = 0b11100000, // 14
|
||||
auth = 0b11110000, // 15
|
||||
};
|
||||
|
||||
constexpr struct with_pid_ {} with_pid {};
|
||||
constexpr struct no_pid_ {} no_pid {};
|
||||
|
||||
template <typename Allocator>
|
||||
class control_packet {
|
||||
uint16_t _packet_id;
|
||||
|
||||
using alloc_type = Allocator;
|
||||
using deleter = boost::alloc_deleter<std::string, alloc_type>;
|
||||
std::unique_ptr<std::string, deleter> _packet;
|
||||
|
||||
control_packet(
|
||||
const Allocator& a,
|
||||
uint16_t packet_id, std::string packet
|
||||
) :
|
||||
_packet_id(packet_id),
|
||||
_packet(boost::allocate_unique<std::string>(a, std::move(packet)))
|
||||
{}
|
||||
|
||||
public:
|
||||
control_packet(control_packet&&) noexcept = default;
|
||||
control_packet(const control_packet&) = delete;
|
||||
|
||||
control_packet& operator=(control_packet&&) noexcept = default;
|
||||
control_packet& operator=(const control_packet&) = delete;
|
||||
|
||||
template <
|
||||
typename EncodeFun,
|
||||
typename ...Args
|
||||
>
|
||||
static control_packet of(
|
||||
with_pid_, const Allocator& alloc,
|
||||
EncodeFun&& encode, uint16_t packet_id, Args&&... args
|
||||
) {
|
||||
return control_packet {
|
||||
alloc, packet_id, encode(packet_id, std::forward<Args>(args)...)
|
||||
};
|
||||
}
|
||||
|
||||
template <
|
||||
typename EncodeFun,
|
||||
typename ...Args
|
||||
>
|
||||
static control_packet of(
|
||||
no_pid_, const Allocator& alloc,
|
||||
EncodeFun&& encode, Args&&... args
|
||||
) {
|
||||
return control_packet {
|
||||
alloc, uint16_t(0), encode(std::forward<Args>(args)...)
|
||||
};
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _packet->size();
|
||||
}
|
||||
|
||||
control_code_e control_code() const {
|
||||
return control_code_e(uint8_t(*(_packet->data())) & 0b11110000);
|
||||
}
|
||||
|
||||
uint16_t packet_id() const {
|
||||
return _packet_id;
|
||||
}
|
||||
|
||||
qos_e qos() const {
|
||||
assert(control_code() == control_code_e::publish);
|
||||
auto byte = (uint8_t(*(_packet->data())) & 0b00000110) >> 1;
|
||||
return qos_e(byte);
|
||||
}
|
||||
|
||||
control_packet& set_dup() {
|
||||
assert(control_code() == control_code_e::publish);
|
||||
auto& byte = *(_packet->data());
|
||||
byte |= 0b00001000;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string_view wire_data() const {
|
||||
return *_packet;
|
||||
}
|
||||
};
|
||||
|
||||
class packet_id_allocator {
|
||||
struct interval {
|
||||
uint16_t start, end;
|
||||
|
||||
interval(uint16_t start, uint16_t end) :
|
||||
start(start), end(end)
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<interval> _free_ids;
|
||||
static constexpr uint16_t MAX_PACKET_ID = 65535;
|
||||
|
||||
public:
|
||||
packet_id_allocator() {
|
||||
_free_ids.emplace_back(MAX_PACKET_ID, uint16_t(0));
|
||||
}
|
||||
|
||||
packet_id_allocator(packet_id_allocator&&) noexcept = default;
|
||||
packet_id_allocator(const packet_id_allocator&) = delete;
|
||||
|
||||
packet_id_allocator& operator=(packet_id_allocator&&) noexcept = default;
|
||||
packet_id_allocator& operator=(const packet_id_allocator&) = delete;
|
||||
|
||||
uint16_t allocate() {
|
||||
if (_free_ids.empty()) return 0;
|
||||
auto& last = _free_ids.back();
|
||||
if (last.start == ++last.end) {
|
||||
auto ret = last.end;
|
||||
_free_ids.pop_back();
|
||||
return ret;
|
||||
}
|
||||
return last.end;
|
||||
}
|
||||
|
||||
void free(uint16_t pid) {
|
||||
auto it = std::upper_bound(
|
||||
_free_ids.begin(), _free_ids.end(), pid,
|
||||
[](const uint16_t x, const interval& i) { return x > i.start; }
|
||||
);
|
||||
uint16_t* end_p = nullptr;
|
||||
if (it != _free_ids.begin()) {
|
||||
auto pit = std::prev(it);
|
||||
if (pit->end == pid)
|
||||
end_p = &pit->end;
|
||||
}
|
||||
if (it != _free_ids.end() && pid - 1 == it->start) {
|
||||
if (!end_p)
|
||||
it->start = pid;
|
||||
else {
|
||||
*end_p = it->end;
|
||||
_free_ids.erase(it);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!end_p)
|
||||
_free_ids.insert(it, interval(pid, pid - 1));
|
||||
else
|
||||
*end_p = pid - 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_CONTROL_PACKET_HPP
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_INTERNAL_TYPES_HPP
|
||||
#define BOOST_MQTT5_INTERNAL_TYPES_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/any_authenticator.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
using byte_citer = std::string::const_iterator;
|
||||
|
||||
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using duration = time_stamp::duration;
|
||||
|
||||
struct credentials {
|
||||
std::string client_id;
|
||||
std::optional<std::string> username;
|
||||
std::optional<std::string> password;
|
||||
|
||||
credentials() = default;
|
||||
credentials(
|
||||
std::string client_id,
|
||||
std::string username, std::string password
|
||||
) :
|
||||
client_id(std::move(client_id))
|
||||
{
|
||||
if (!username.empty())
|
||||
this->username = std::move(username);
|
||||
if (!password.empty())
|
||||
this->password = std::move(password);
|
||||
}
|
||||
};
|
||||
|
||||
class session_state {
|
||||
uint8_t _flags = 0b00;
|
||||
|
||||
static constexpr uint8_t session_present_flag = 0b01;
|
||||
static constexpr uint8_t subscriptions_present_flag = 0b10;
|
||||
public:
|
||||
void session_present(bool present) {
|
||||
return update_flag(present, session_present_flag);
|
||||
}
|
||||
|
||||
bool session_present() const {
|
||||
return _flags & session_present_flag;
|
||||
}
|
||||
|
||||
void subscriptions_present(bool present) {
|
||||
return update_flag(present, subscriptions_present_flag);
|
||||
}
|
||||
|
||||
bool subscriptions_present() const {
|
||||
return _flags & subscriptions_present_flag;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_flag(bool set, uint8_t flag) {
|
||||
if (set)
|
||||
_flags |= flag;
|
||||
else
|
||||
_flags &= ~flag;
|
||||
}
|
||||
};
|
||||
|
||||
struct mqtt_ctx {
|
||||
credentials creds;
|
||||
std::optional<will> will_msg;
|
||||
uint16_t keep_alive = 60;
|
||||
connect_props co_props;
|
||||
connack_props ca_props;
|
||||
session_state state;
|
||||
any_authenticator authenticator;
|
||||
|
||||
mqtt_ctx() = default;
|
||||
|
||||
mqtt_ctx(const mqtt_ctx& other) :
|
||||
creds(other.creds), will_msg(other.will_msg),
|
||||
keep_alive(other.keep_alive), co_props(other.co_props),
|
||||
ca_props {}, state {},
|
||||
authenticator(other.authenticator)
|
||||
{}
|
||||
};
|
||||
|
||||
struct disconnect_ctx {
|
||||
disconnect_rc_e reason_code = disconnect_rc_e::normal_disconnection;
|
||||
disconnect_props props = {};
|
||||
bool terminal = false;
|
||||
};
|
||||
|
||||
using serial_num_t = uint32_t;
|
||||
constexpr serial_num_t no_serial = 0;
|
||||
|
||||
namespace send_flag {
|
||||
|
||||
constexpr unsigned none = 0b000;
|
||||
constexpr unsigned throttled = 0b001;
|
||||
constexpr unsigned prioritized = 0b010;
|
||||
constexpr unsigned terminal = 0b100;
|
||||
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_INTERNAL_TYPES_HPP
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_LOG_INVOKE_HPP
|
||||
#define BOOST_MQTT5_LOG_INVOKE_HPP
|
||||
|
||||
#include <boost/mqtt5/logger_traits.hpp>
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
|
||||
template <typename LoggerType>
|
||||
class log_invoke {
|
||||
LoggerType _logger;
|
||||
public:
|
||||
explicit log_invoke(LoggerType logger = {}) :
|
||||
_logger(std::move(logger))
|
||||
{}
|
||||
|
||||
void at_resolve(
|
||||
error_code ec, std::string_view host, std::string_view port,
|
||||
const asio::ip::tcp::resolver::results_type& eps
|
||||
) {
|
||||
if constexpr (has_at_resolve<LoggerType>)
|
||||
_logger.at_resolve(ec, host, port, eps);
|
||||
}
|
||||
|
||||
void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if constexpr (has_at_tcp_connect<LoggerType>)
|
||||
_logger.at_tcp_connect(ec, ep);
|
||||
}
|
||||
|
||||
void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if constexpr (has_at_tls_handshake<LoggerType>)
|
||||
_logger.at_tls_handshake(ec, ep);
|
||||
}
|
||||
|
||||
void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if constexpr (has_at_ws_handshake<LoggerType>)
|
||||
_logger.at_ws_handshake(ec, ep);
|
||||
}
|
||||
|
||||
void at_connack(
|
||||
reason_code rc,
|
||||
bool session_present, const connack_props& ca_props
|
||||
) {
|
||||
if constexpr (has_at_connack<LoggerType>)
|
||||
_logger.at_connack(rc, session_present, ca_props);
|
||||
}
|
||||
|
||||
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
|
||||
if constexpr (has_at_disconnect<LoggerType>)
|
||||
_logger.at_disconnect(rc, dc_props);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
|
||||
#endif // !BOOST_MQTT5_LOG_INVOKE_HPP
|
||||
+15
-15
@@ -5,8 +5,8 @@
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_REBIND_EXECUTOR_HPP
|
||||
#define ASYNC_MQTT5_REBIND_EXECUTOR_HPP
|
||||
#ifndef BOOST_MQTT5_REBIND_EXECUTOR_HPP
|
||||
#define BOOST_MQTT5_REBIND_EXECUTOR_HPP
|
||||
|
||||
namespace boost::asio::ssl {
|
||||
|
||||
@@ -25,34 +25,34 @@ class stream;
|
||||
|
||||
}// end namespace boost::beast::websocket
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename Stream, typename Executor>
|
||||
struct rebind_executor {
|
||||
using other = typename Stream::template rebind_executor<Executor>::other;
|
||||
using other = typename Stream::template rebind_executor<Executor>::other;
|
||||
};
|
||||
|
||||
// asio::ssl::stream does not define a rebind_executor member type
|
||||
template <typename Stream, typename Executor>
|
||||
struct rebind_executor<asio::ssl::stream<Stream>, Executor> {
|
||||
using other = typename asio::ssl::stream<
|
||||
typename rebind_executor<Stream, Executor>::other
|
||||
>;
|
||||
using other = typename asio::ssl::stream<
|
||||
typename rebind_executor<Stream, Executor>::other
|
||||
>;
|
||||
};
|
||||
|
||||
template <typename Stream, bool deflate_supported, typename Executor>
|
||||
struct rebind_executor<
|
||||
boost::beast::websocket::stream<asio::ssl::stream<Stream>, deflate_supported>,
|
||||
Executor
|
||||
boost::beast::websocket::stream<asio::ssl::stream<Stream>, deflate_supported>,
|
||||
Executor
|
||||
> {
|
||||
using other = typename boost::beast::websocket::stream<
|
||||
asio::ssl::stream<typename rebind_executor<Stream, Executor>::other>,
|
||||
deflate_supported
|
||||
>;
|
||||
using other = typename boost::beast::websocket::stream<
|
||||
asio::ssl::stream<typename rebind_executor<Stream, Executor>::other>,
|
||||
deflate_supported
|
||||
>;
|
||||
};
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_REBIND_EXECUTOR_HPP
|
||||
#endif // !BOOST_MQTT5_REBIND_EXECUTOR_HPP
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_TOPIC_VALIDATION_HPP
|
||||
#define BOOST_MQTT5_TOPIC_VALIDATION_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/utf8_mqtt.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
static constexpr int32_t min_subscription_identifier = 1;
|
||||
static constexpr int32_t max_subscription_identifier = 268'435'455;
|
||||
|
||||
static constexpr std::string_view shared_sub_prefix = "$share/";
|
||||
|
||||
inline bool is_utf8_no_wildcard(validation_result result) {
|
||||
return result == validation_result::valid;
|
||||
}
|
||||
|
||||
inline bool is_not_empty(size_t sz) {
|
||||
return sz != 0;
|
||||
}
|
||||
|
||||
inline bool is_valid_topic_size(size_t sz) {
|
||||
return is_not_empty(sz) && is_valid_string_size(sz);
|
||||
}
|
||||
|
||||
inline validation_result validate_topic_name(std::string_view str) {
|
||||
return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard);
|
||||
}
|
||||
|
||||
inline validation_result validate_topic_alias_name(std::string_view str) {
|
||||
return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard);
|
||||
}
|
||||
|
||||
inline validation_result validate_shared_topic_name(std::string_view str) {
|
||||
return validate_impl(str, is_not_empty, is_utf8_no_wildcard);
|
||||
}
|
||||
|
||||
inline validation_result validate_topic_filter(std::string_view str) {
|
||||
if (!is_valid_topic_size(str.size()))
|
||||
return validation_result::invalid;
|
||||
|
||||
constexpr int multi_lvl_wildcard = '#';
|
||||
constexpr int single_lvl_wildcard = '+';
|
||||
|
||||
// must be the last character preceded by '/' or stand alone
|
||||
// #, .../#
|
||||
if (str.back() == multi_lvl_wildcard) {
|
||||
str.remove_suffix(1);
|
||||
|
||||
if (!str.empty() && str.back() != '/')
|
||||
return validation_result::invalid;
|
||||
}
|
||||
|
||||
int last_c = -1;
|
||||
validation_result result;
|
||||
while (!str.empty()) {
|
||||
int c = pop_front_unichar(str);
|
||||
|
||||
// can be used at any level, but must occupy an entire level
|
||||
// +, +/..., .../+/..., .../+
|
||||
bool is_valid_single_lvl = (c == single_lvl_wildcard) &&
|
||||
(str.empty() || str.front() == '/') &&
|
||||
(last_c == -1 || last_c == '/');
|
||||
|
||||
result = validate_mqtt_utf8_char(c);
|
||||
if (
|
||||
result == validation_result::valid ||
|
||||
is_valid_single_lvl
|
||||
) {
|
||||
last_c = c;
|
||||
continue;
|
||||
}
|
||||
|
||||
return validation_result::invalid;
|
||||
}
|
||||
|
||||
return validation_result::valid;
|
||||
}
|
||||
|
||||
inline validation_result validate_shared_topic_filter(
|
||||
std::string_view str, bool wildcard_allowed = true
|
||||
) {
|
||||
if (!is_valid_topic_size(str.size()))
|
||||
return validation_result::invalid;
|
||||
|
||||
if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0)
|
||||
return validation_result::invalid;
|
||||
|
||||
str.remove_prefix(shared_sub_prefix.size());
|
||||
|
||||
size_t share_name_end = str.find_first_of('/');
|
||||
if (share_name_end == std::string::npos)
|
||||
return validation_result::invalid;
|
||||
|
||||
validation_result result;
|
||||
result = validate_shared_topic_name(str.substr(0, share_name_end));
|
||||
|
||||
if (result != validation_result::valid)
|
||||
return validation_result::invalid;
|
||||
|
||||
auto topic_filter = str.substr(share_name_end + 1);
|
||||
return wildcard_allowed ?
|
||||
validate_topic_filter(topic_filter) :
|
||||
validate_topic_name(topic_filter)
|
||||
;
|
||||
}
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif //BOOST_MQTT5_TOPIC_VALIDATION_HPP
|
||||
@@ -5,18 +5,18 @@
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_TRAITS_HPP
|
||||
#define ASYNC_MQTT5_TRAITS_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#ifndef BOOST_MQTT5_TRAITS_HPP
|
||||
#define BOOST_MQTT5_TRAITS_HPP
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/range/iterator_range_core.hpp>
|
||||
#include <boost/type_traits/remove_cv_ref.hpp>
|
||||
|
||||
namespace async_mqtt5::detail {
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
template <typename>
|
||||
constexpr bool is_optional_impl = false;
|
||||
@@ -35,29 +35,29 @@ constexpr bool is_specialization<T<Args...>, T> = true;
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_vector = is_specialization<
|
||||
boost::remove_cv_ref_t<T>, std::vector
|
||||
boost::remove_cv_ref_t<T>, std::vector
|
||||
>;
|
||||
|
||||
template <typename... Args>
|
||||
constexpr std::true_type is_small_vector_impl(
|
||||
boost::container::small_vector_base<Args...> const &
|
||||
boost::container::small_vector_base<Args...> const &
|
||||
);
|
||||
constexpr std::false_type is_small_vector_impl( ... );
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_small_vector =
|
||||
decltype(is_small_vector_impl(std::declval<T>()))::value;
|
||||
decltype(is_small_vector_impl(std::declval<T>()))::value;
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_pair = is_specialization<
|
||||
boost::remove_cv_ref_t<T>, std::pair
|
||||
boost::remove_cv_ref_t<T>, std::pair
|
||||
>;
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_boost_iterator = is_specialization<
|
||||
boost::remove_cv_ref_t<T>, boost::iterator_range
|
||||
boost::remove_cv_ref_t<T>, boost::iterator_range
|
||||
>;
|
||||
|
||||
} // end namespace async_mqtt5::detail
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !ASYNC_MQTT5_TRAITS_HPP
|
||||
#endif // !BOOST_MQTT5_TRAITS_HPP
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_UTF8_MQTT_HPP
|
||||
#define BOOST_MQTT5_UTF8_MQTT_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
enum class validation_result : uint8_t {
|
||||
valid = 0,
|
||||
has_wildcard_character,
|
||||
invalid
|
||||
};
|
||||
|
||||
inline int pop_front_unichar(std::string_view& s) {
|
||||
// assuming that s.length() is > 0
|
||||
|
||||
int n = s[0] & 0xF0;
|
||||
int ch = -1;
|
||||
|
||||
if ((n & 0x80) == 0) {
|
||||
ch = s[0];
|
||||
s.remove_prefix(1);
|
||||
}
|
||||
else if ((n == 0xC0 || n == 0xD0) && s.size() > 1) {
|
||||
ch = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F);
|
||||
s.remove_prefix(2);
|
||||
}
|
||||
else if ((n == 0xE0) && s.size() > 2) {
|
||||
ch = ((s[0] & 0x1F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F);
|
||||
s.remove_prefix(3);
|
||||
}
|
||||
else if ((n == 0xF0) && s.size() > 3) {
|
||||
ch = ((s[0] & 0x1F) << 18) | ((s[1] & 0x3F) << 12) |
|
||||
((s[2] & 0x3F) << 6) | (s[3] & 0x3F);
|
||||
s.remove_prefix(4);
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
inline validation_result validate_mqtt_utf8_char(int c) {
|
||||
constexpr int fe_flag = 0xFE;
|
||||
constexpr int ff_flag = 0xFF;
|
||||
|
||||
constexpr int multi_lvl_wildcard = '#';
|
||||
constexpr int single_lvl_wildcard = '+';
|
||||
|
||||
if (c == multi_lvl_wildcard || c == single_lvl_wildcard)
|
||||
return validation_result::has_wildcard_character;
|
||||
|
||||
if (c > 0x001F && // U+0000...U+001F control characters
|
||||
(c < 0x007F || c > 0x009F) && // U+007F...0+009F control characters
|
||||
(c < 0xD800 || c > 0xDFFF) && // U+D800...U+DFFF surrogates
|
||||
(c < 0xFDD0 || c > 0xFDEF) && // U+FDD0...U+FDEF non-characters
|
||||
(c & fe_flag) != fe_flag && // non-characters
|
||||
(c & ff_flag) != ff_flag
|
||||
)
|
||||
return validation_result::valid;
|
||||
|
||||
return validation_result::invalid;
|
||||
}
|
||||
|
||||
inline bool is_valid_string_size(size_t sz) {
|
||||
constexpr size_t max_sz = 65535;
|
||||
return sz <= max_sz;
|
||||
}
|
||||
|
||||
inline bool is_utf8(validation_result result) {
|
||||
return result == validation_result::valid ||
|
||||
result == validation_result::has_wildcard_character;
|
||||
}
|
||||
|
||||
template <typename ValidSizeCondition, typename ValidCondition>
|
||||
validation_result validate_impl(
|
||||
std::string_view str,
|
||||
ValidSizeCondition&& size_condition, ValidCondition&& condition
|
||||
) {
|
||||
if (!size_condition(str.size()))
|
||||
return validation_result::invalid;
|
||||
|
||||
validation_result result;
|
||||
while (!str.empty()) {
|
||||
int c = pop_front_unichar(str);
|
||||
|
||||
result = validate_mqtt_utf8_char(c);
|
||||
if (!condition(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
return validation_result::valid;
|
||||
}
|
||||
|
||||
inline validation_result validate_mqtt_utf8(std::string_view str) {
|
||||
return validate_impl(str, is_valid_string_size, is_utf8);
|
||||
}
|
||||
|
||||
inline bool is_valid_string_pair(
|
||||
const std::pair<std::string, std::string>& str_pair
|
||||
) {
|
||||
return validate_mqtt_utf8(str_pair.first) == validation_result::valid &&
|
||||
validate_mqtt_utf8(str_pair.second) == validation_result::valid;
|
||||
}
|
||||
|
||||
} // namespace boost::mqtt5::detail
|
||||
|
||||
#endif //BOOST_MQTT5_UTF8_MQTT_HPP
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_ERROR_HPP
|
||||
#define BOOST_MQTT5_ERROR_HPP
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
/**
|
||||
* \brief A representation of Disconnect Reason Code.
|
||||
*
|
||||
* \details Represents all Reason Codes that the Client can send to the Server
|
||||
* in the \__DISCONNECT\__ packet as the reason for the disconnection.
|
||||
*/
|
||||
enum class disconnect_rc_e : uint8_t {
|
||||
/** Close the connection normally. Do not send the Will Message. */
|
||||
normal_disconnection = 0x00,
|
||||
|
||||
/** The Client wishes to disconnect but requires that
|
||||
the Server also publishes its Will Message. */
|
||||
disconnect_with_will_message = 0x04
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
enum class disconnect_rc_e : uint8_t {
|
||||
normal_disconnection = 0x00,
|
||||
disconnect_with_will_message = 0x04,
|
||||
|
||||
unspecified_error = 0x80,
|
||||
malformed_packet = 0x81,
|
||||
protocol_error = 0x82,
|
||||
implementation_specific_error = 0x83,
|
||||
topic_name_invalid = 0x90,
|
||||
receive_maximum_exceeded = 0x93,
|
||||
topic_alias_invalid = 0x94,
|
||||
packet_too_large = 0x95,
|
||||
message_rate_too_high = 0x96,
|
||||
quota_exceeded = 0x97,
|
||||
administrative_action = 0x98,
|
||||
payload_format_invalid = 0x99
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace client {
|
||||
/**
|
||||
* \brief Defines error codes related to MQTT client.
|
||||
*
|
||||
* \details Encapsulates errors that occur on the client side.
|
||||
*/
|
||||
enum class error : int {
|
||||
/** The packet is malformed */
|
||||
malformed_packet = 100,
|
||||
|
||||
/** The packet has exceeded the Maximum Packet Size the Server is willing to accept */
|
||||
packet_too_large,
|
||||
|
||||
/** The Client's session does not exist or it has expired */
|
||||
session_expired,
|
||||
|
||||
/** There are no more available Packet Identifiers to use */
|
||||
pid_overrun,
|
||||
|
||||
/** The Topic is invalid and does not conform to the specification */
|
||||
invalid_topic,
|
||||
|
||||
// publish
|
||||
/** The Server does not support the specified \ref qos_e */
|
||||
qos_not_supported,
|
||||
|
||||
/** The Server does not support retained messages */
|
||||
retain_not_available,
|
||||
|
||||
/** The Client attempted to send a Topic Alias that is greater than Topic Alias Maximum */
|
||||
topic_alias_maximum_reached,
|
||||
|
||||
// subscribe
|
||||
/** The Server does not support Wildcard Subscriptions */
|
||||
wildcard_subscription_not_available,
|
||||
|
||||
/** The Server does not support this Subscription Identifier */
|
||||
subscription_identifier_not_available,
|
||||
|
||||
/** The Server does not support Shared Subscriptions */
|
||||
shared_subscription_not_available
|
||||
};
|
||||
|
||||
|
||||
inline std::string client_error_to_string(error err) {
|
||||
switch (err) {
|
||||
case error::malformed_packet:
|
||||
return "The packet is malformed";
|
||||
case error::packet_too_large:
|
||||
return "The packet has exceeded the Maximum Packet Size "
|
||||
"the Server is willing to accept";
|
||||
case error::session_expired:
|
||||
return "The Client's session does not exist or it has expired";
|
||||
case error::pid_overrun:
|
||||
return "There are no more available Packet Identifiers to use";
|
||||
case error::invalid_topic:
|
||||
return "The Topic is invalid and "
|
||||
"does not conform to the specification";
|
||||
case error::qos_not_supported:
|
||||
return "The Server does not support the specified QoS";
|
||||
case error::retain_not_available:
|
||||
return "The Server does not support retained messages";
|
||||
case error::topic_alias_maximum_reached:
|
||||
return "The Client attempted to send a Topic Alias "
|
||||
"that is greater than Topic Alias Maximum";
|
||||
case error::wildcard_subscription_not_available:
|
||||
return "The Server does not support Wildcard Subscriptions";
|
||||
case error::subscription_identifier_not_available:
|
||||
return "The Server does not support this Subscription Identifier";
|
||||
case error::shared_subscription_not_available:
|
||||
return "The Server does not support Shared Subscriptions";
|
||||
default:
|
||||
return "Unknown client error";
|
||||
}
|
||||
}
|
||||
|
||||
struct client_ec_category : public boost::system::error_category {
|
||||
const char* name() const noexcept override { return "mqtt_client_error"; }
|
||||
std::string message(int ev) const noexcept override {
|
||||
return client_error_to_string(static_cast<error>(ev));
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns the error category associated with \ref client::error.
|
||||
inline const client_ec_category& get_error_code_category() {
|
||||
static client_ec_category cat;
|
||||
return cat;
|
||||
}
|
||||
|
||||
/// Creates an \ref error_code from a \ref client::error.
|
||||
inline boost::system::error_code make_error_code(error r) {
|
||||
return { static_cast<int>(r), get_error_code_category() };
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const error& err) {
|
||||
os << get_error_code_category().name() << ":" << static_cast<int>(err);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // end namespace client
|
||||
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
namespace boost::system {
|
||||
|
||||
template <>
|
||||
struct is_error_code_enum <boost::mqtt5::client::error> : std::true_type {};
|
||||
|
||||
} // end namespace boost::system
|
||||
|
||||
|
||||
#endif // !BOOST_MQTT5_ERROR_HPP
|
||||
@@ -0,0 +1,243 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_ASSEMBLE_OP_HPP
|
||||
#define BOOST_MQTT5_ASSEMBLE_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class data_span : private std::pair<byte_citer, byte_citer> {
|
||||
using base = std::pair<byte_citer, byte_citer>;
|
||||
public:
|
||||
using base::base;
|
||||
|
||||
auto first() const {
|
||||
return base::first;
|
||||
}
|
||||
auto last() const {
|
||||
return base::second;
|
||||
}
|
||||
void expand_suffix(size_t num_chars) {
|
||||
base::second += num_chars;
|
||||
}
|
||||
void remove_prefix(size_t num_chars) {
|
||||
base::first += num_chars;
|
||||
}
|
||||
size_t size() const {
|
||||
return std::distance(base::first, base::second);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class assemble_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_read {};
|
||||
|
||||
static constexpr uint32_t max_recv_size = 65'536;
|
||||
|
||||
client_service& _svc;
|
||||
handler_type _handler;
|
||||
|
||||
std::string& _read_buff;
|
||||
data_span& _data_span;
|
||||
|
||||
public:
|
||||
assemble_op(
|
||||
client_service& svc, handler_type&& handler,
|
||||
std::string& read_buff, data_span& active_span
|
||||
) :
|
||||
_svc(svc),
|
||||
_handler(std::move(handler)),
|
||||
_read_buff(read_buff), _data_span(active_span)
|
||||
{}
|
||||
|
||||
assemble_op(assemble_op&&) noexcept = default;
|
||||
assemble_op(const assemble_op&) = delete;
|
||||
|
||||
assemble_op& operator=(assemble_op&&) noexcept = default;
|
||||
assemble_op& operator=(const assemble_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename CompletionCondition>
|
||||
void perform(CompletionCondition cc) {
|
||||
_read_buff.erase(
|
||||
_read_buff.cbegin(), _data_span.first()
|
||||
);
|
||||
_read_buff.resize(
|
||||
_svc.connect_property(prop::maximum_packet_size).value_or(max_recv_size)
|
||||
);
|
||||
_data_span = {
|
||||
_read_buff.cbegin(),
|
||||
_read_buff.cbegin() + _data_span.size()
|
||||
};
|
||||
|
||||
if (cc(error_code {}, 0) == 0 && _data_span.size()) {
|
||||
return asio::post(
|
||||
_svc.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_read {}, error_code {},
|
||||
0, std::move(cc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Must be evaluated before this is moved
|
||||
auto store_begin = _read_buff.data() + _data_span.size();
|
||||
auto store_size = std::distance(_data_span.last(), _read_buff.cend());
|
||||
|
||||
_svc._stream.async_read_some(
|
||||
asio::buffer(store_begin, store_size), compute_read_timeout(),
|
||||
asio::prepend(
|
||||
asio::append(std::move(*this), std::move(cc)),
|
||||
on_read {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionCondition>
|
||||
void operator()(
|
||||
on_read, error_code ec, size_t bytes_read,
|
||||
CompletionCondition cc
|
||||
) {
|
||||
if (ec == asio::error::try_again) {
|
||||
_svc.update_session_state();
|
||||
_svc._async_sender.resend();
|
||||
_data_span = { _read_buff.cend(), _read_buff.cend() };
|
||||
return perform(std::move(cc));
|
||||
}
|
||||
|
||||
if (ec)
|
||||
return complete(ec, 0, {}, {});
|
||||
|
||||
_data_span.expand_suffix(bytes_read);
|
||||
assert(_data_span.size());
|
||||
|
||||
auto control_byte = uint8_t(*_data_span.first());
|
||||
|
||||
if ((control_byte & 0b11110000) == 0)
|
||||
// close the connection, cancel
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
|
||||
auto first = _data_span.first() + 1;
|
||||
auto varlen = decoders::type_parse(
|
||||
first, _data_span.last(), decoders::basic::varint_
|
||||
);
|
||||
|
||||
if (!varlen) {
|
||||
if (_data_span.size() < 5)
|
||||
return perform(asio::transfer_at_least(1));
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
}
|
||||
|
||||
auto recv_size = _svc.connect_property(prop::maximum_packet_size)
|
||||
.value_or(max_recv_size);
|
||||
if (static_cast<uint32_t>(*varlen) > recv_size - std::distance(_data_span.first(), first))
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
|
||||
if (std::distance(first, _data_span.last()) < *varlen)
|
||||
return perform(asio::transfer_at_least(1));
|
||||
|
||||
_data_span.remove_prefix(
|
||||
std::distance(_data_span.first(), first) + *varlen
|
||||
);
|
||||
|
||||
dispatch(control_byte, first, first + *varlen);
|
||||
}
|
||||
|
||||
private:
|
||||
duration compute_read_timeout() const {
|
||||
auto negotiated_ka = _svc.negotiated_keep_alive();
|
||||
return negotiated_ka ?
|
||||
std::chrono::milliseconds(3 * negotiated_ka * 1000 / 2) :
|
||||
duration(std::numeric_limits<duration::rep>::max());
|
||||
}
|
||||
|
||||
static bool valid_header(uint8_t control_byte) {
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
if (code == control_code_e::publish)
|
||||
return true;
|
||||
|
||||
auto res = control_byte & 0b00001111;
|
||||
if (code == control_code_e::pubrel)
|
||||
return res == 0b00000010;
|
||||
return res == 0b00000000;
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
uint8_t control_byte, byte_citer first, byte_citer last
|
||||
) {
|
||||
using namespace decoders;
|
||||
|
||||
if (!valid_header(control_byte))
|
||||
return complete(client::error::malformed_packet, 0, {}, {});
|
||||
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
if (code == control_code_e::pingresp)
|
||||
return perform(asio::transfer_at_least(0));
|
||||
|
||||
bool is_reply = code != control_code_e::publish &&
|
||||
code != control_code_e::auth &&
|
||||
code != control_code_e::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(asio::transfer_at_least(0));
|
||||
}
|
||||
|
||||
complete(error_code {}, control_byte, first, last);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec)
|
||||
_data_span = { _read_buff.cend(), _read_buff.cend() };
|
||||
std::move(_handler)(ec, control_code, first, last);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_ASSEMBLE_OP_HPP
|
||||
@@ -0,0 +1,303 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_ASYNC_SENDER_HPP
|
||||
#define BOOST_MQTT5_ASYNC_SENDER_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/bind_allocator.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class write_req {
|
||||
static constexpr unsigned SERIAL_BITS = sizeof(serial_num_t) * 8;
|
||||
|
||||
asio::const_buffer _buffer;
|
||||
serial_num_t _serial_num;
|
||||
unsigned _flags;
|
||||
|
||||
using handler_type = asio::any_completion_handler<void (error_code)>;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
write_req(
|
||||
asio::const_buffer buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
handler_type handler
|
||||
) :
|
||||
_buffer(buffer), _serial_num(serial_num), _flags(flags),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
write_req(write_req&&) = default;
|
||||
write_req(const write_req&) = delete;
|
||||
|
||||
write_req& operator=(write_req&&) = default;
|
||||
write_req& operator=(const write_req&) = delete;
|
||||
|
||||
static serial_num_t next_serial_num(serial_num_t last) {
|
||||
return last + 1;
|
||||
}
|
||||
|
||||
asio::const_buffer buffer() const {
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
void complete(error_code ec) {
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
|
||||
void complete_post(const asio::any_io_executor& ex, error_code ec) {
|
||||
asio::post(
|
||||
ex,
|
||||
asio::prepend(std::move(_handler), ec)
|
||||
);
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return !_handler;
|
||||
}
|
||||
|
||||
bool throttled() const {
|
||||
return _flags & send_flag::throttled;
|
||||
}
|
||||
|
||||
bool terminal() const {
|
||||
return _flags & send_flag::terminal;
|
||||
}
|
||||
|
||||
bool operator<(const write_req& other) const {
|
||||
if (prioritized() != other.prioritized())
|
||||
return prioritized();
|
||||
|
||||
auto s1 = _serial_num;
|
||||
auto s2 = other._serial_num;
|
||||
|
||||
if (s1 < s2)
|
||||
return (s2 - s1) < (1u << (SERIAL_BITS - 1));
|
||||
|
||||
return (s1 - s2) >= (1u << (SERIAL_BITS - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
bool prioritized() const {
|
||||
return _flags & send_flag::prioritized;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename ClientService>
|
||||
class async_sender {
|
||||
using self_type = async_sender<ClientService>;
|
||||
|
||||
using client_service = ClientService;
|
||||
|
||||
using queue_allocator_type = asio::recycling_allocator<write_req>;
|
||||
using write_queue_t = std::vector<write_req, queue_allocator_type>;
|
||||
|
||||
ClientService& _svc;
|
||||
write_queue_t _write_queue;
|
||||
bool _write_in_progress { false };
|
||||
|
||||
static constexpr uint16_t MAX_LIMIT = 65535;
|
||||
uint16_t _limit { MAX_LIMIT };
|
||||
uint16_t _quota { MAX_LIMIT };
|
||||
|
||||
serial_num_t _last_serial_num { 0 };
|
||||
|
||||
public:
|
||||
explicit async_sender(ClientService& svc) : _svc(svc) {}
|
||||
|
||||
async_sender(async_sender&&) = default;
|
||||
async_sender(const async_sender&) = delete;
|
||||
|
||||
async_sender& operator=(async_sender&&) = default;
|
||||
async_sender& operator=(const async_sender&) = delete;
|
||||
|
||||
using allocator_type = queue_allocator_type;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc.get_executor();
|
||||
}
|
||||
|
||||
serial_num_t next_serial_num() {
|
||||
return _last_serial_num = write_req::next_serial_num(_last_serial_num);
|
||||
}
|
||||
|
||||
template <typename CompletionToken, typename BufferType>
|
||||
decltype(auto) async_send(
|
||||
const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, self_type& self, const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags
|
||||
) {
|
||||
self._write_queue.emplace_back(
|
||||
asio::buffer(buffer), serial_num, flags, std::move(handler)
|
||||
);
|
||||
self.do_write();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this),
|
||||
buffer, serial_num, flags
|
||||
);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
auto ops = std::move(_write_queue);
|
||||
for (auto& op : ops)
|
||||
op.complete_post(_svc.get_executor(), asio::error::operation_aborted);
|
||||
}
|
||||
|
||||
void resend() {
|
||||
if (_write_in_progress)
|
||||
return;
|
||||
|
||||
// The _write_in_progress flag is set to true to prevent any write
|
||||
// operations executing before the _write_queue is filled with
|
||||
// all the packets that require resending.
|
||||
_write_in_progress = true;
|
||||
|
||||
auto new_limit = _svc._stream_context.connack_property(prop::receive_maximum);
|
||||
_limit = new_limit.value_or(MAX_LIMIT);
|
||||
_quota = _limit;
|
||||
|
||||
auto write_queue = std::move(_write_queue);
|
||||
_svc._replies.resend_unanswered();
|
||||
|
||||
for (auto& op : write_queue)
|
||||
op.complete(asio::error::try_again);
|
||||
|
||||
std::stable_sort(_write_queue.begin(), _write_queue.end());
|
||||
|
||||
_write_in_progress = false;
|
||||
do_write();
|
||||
}
|
||||
|
||||
void operator()(write_queue_t write_queue, error_code ec, size_t) {
|
||||
_write_in_progress = false;
|
||||
|
||||
if (ec == asio::error::try_again) {
|
||||
_svc.update_session_state();
|
||||
_write_queue.insert(
|
||||
_write_queue.begin(),
|
||||
std::make_move_iterator(write_queue.begin()),
|
||||
std::make_move_iterator(write_queue.end())
|
||||
);
|
||||
return resend();
|
||||
}
|
||||
|
||||
if (ec == asio::error::no_recovery)
|
||||
_svc.cancel();
|
||||
|
||||
// errors, if any, are propagated to ops
|
||||
for (auto& op : write_queue)
|
||||
op.complete(ec);
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return;
|
||||
|
||||
do_write();
|
||||
}
|
||||
|
||||
void throttled_op_done() {
|
||||
if (_limit == MAX_LIMIT)
|
||||
return;
|
||||
|
||||
++_quota;
|
||||
do_write();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_write() {
|
||||
if (_write_in_progress || _write_queue.empty())
|
||||
return;
|
||||
|
||||
_write_in_progress = true;
|
||||
|
||||
write_queue_t write_queue;
|
||||
|
||||
auto terminal_req = std::find_if(
|
||||
_write_queue.begin(), _write_queue.end(),
|
||||
[](const auto& op) { return op.terminal(); }
|
||||
);
|
||||
|
||||
if (terminal_req != _write_queue.end()) {
|
||||
write_queue.push_back(std::move(*terminal_req));
|
||||
_write_queue.erase(terminal_req);
|
||||
}
|
||||
else if (_limit == MAX_LIMIT) {
|
||||
write_queue = std::move(_write_queue);
|
||||
}
|
||||
else {
|
||||
for (write_req& req : _write_queue)
|
||||
if (!req.throttled())
|
||||
write_queue.push_back(std::move(req));
|
||||
else if (_quota > 0) {
|
||||
--_quota;
|
||||
write_queue.push_back(std::move(req));
|
||||
}
|
||||
|
||||
if (write_queue.empty()) {
|
||||
_write_in_progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::remove_if(
|
||||
_write_queue.begin(), _write_queue.end(),
|
||||
[](const write_req& req) { return req.empty(); }
|
||||
);
|
||||
_write_queue.erase(it, _write_queue.end());
|
||||
}
|
||||
|
||||
std::vector<asio::const_buffer> buffers;
|
||||
buffers.reserve(write_queue.size());
|
||||
for (const auto& op : write_queue)
|
||||
buffers.push_back(op.buffer());
|
||||
|
||||
_svc._replies.clear_fast_replies();
|
||||
|
||||
_svc._stream.async_write(
|
||||
buffers,
|
||||
asio::prepend(std::ref(*this), std::move(write_queue))
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_ASYNC_SENDER_HPP
|
||||
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
#define BOOST_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/async_mutex.hpp>
|
||||
#include <boost/mqtt5/detail/async_traits.hpp>
|
||||
#include <boost/mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/endpoints.hpp>
|
||||
#include <boost/mqtt5/impl/read_op.hpp>
|
||||
#include <boost/mqtt5/impl/reconnect_op.hpp>
|
||||
#include <boost/mqtt5/impl/write_op.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
template <
|
||||
typename StreamType,
|
||||
typename StreamContext = std::monostate,
|
||||
typename LoggerType = noop_logger
|
||||
>
|
||||
class autoconnect_stream {
|
||||
public:
|
||||
using self_type = autoconnect_stream<StreamType, StreamContext, LoggerType>;
|
||||
using stream_type = StreamType;
|
||||
using stream_context_type = StreamContext;
|
||||
using logger_type = LoggerType;
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
private:
|
||||
using stream_ptr = std::shared_ptr<stream_type>;
|
||||
|
||||
executor_type _stream_executor;
|
||||
async_mutex _conn_mtx;
|
||||
asio::steady_timer _read_timer, _connect_timer;
|
||||
endpoints<logger_type> _endpoints;
|
||||
|
||||
stream_ptr _stream_ptr;
|
||||
stream_context_type& _stream_context;
|
||||
|
||||
log_invoke<logger_type>& _log;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class read_op;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class write_op;
|
||||
|
||||
template <typename Owner>
|
||||
friend class reconnect_op;
|
||||
|
||||
public:
|
||||
autoconnect_stream(
|
||||
const executor_type& ex, stream_context_type& context,
|
||||
log_invoke<logger_type>& log
|
||||
) :
|
||||
_stream_executor(ex),
|
||||
_conn_mtx(_stream_executor),
|
||||
_read_timer(_stream_executor), _connect_timer(_stream_executor),
|
||||
_endpoints(_stream_executor, _connect_timer, log),
|
||||
_stream_context(context),
|
||||
_log(log)
|
||||
{
|
||||
replace_next_layer(construct_next_layer());
|
||||
}
|
||||
|
||||
autoconnect_stream(const autoconnect_stream&) = delete;
|
||||
autoconnect_stream& operator=(const autoconnect_stream&) = delete;
|
||||
|
||||
using next_layer_type = stream_type;
|
||||
next_layer_type& next_layer() {
|
||||
return *_stream_ptr;
|
||||
}
|
||||
|
||||
const next_layer_type& next_layer() const {
|
||||
return *_stream_ptr;
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _stream_executor;
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
_endpoints.brokers(std::move(hosts), default_port);
|
||||
}
|
||||
|
||||
void clone_endpoints(const autoconnect_stream& other) {
|
||||
_endpoints.clone_servers(other._endpoints);
|
||||
}
|
||||
|
||||
bool is_open() const noexcept {
|
||||
return lowest_layer(*_stream_ptr).is_open();
|
||||
}
|
||||
|
||||
void open() {
|
||||
open_lowest_layer(_stream_ptr, asio::ip::tcp::v4());
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).cancel(ec);
|
||||
_conn_mtx.cancel();
|
||||
_connect_timer.cancel();
|
||||
}
|
||||
|
||||
void close() {
|
||||
error_code ec;
|
||||
shutdown(asio::ip::tcp::socket::shutdown_both);
|
||||
lowest_layer(*_stream_ptr).close(ec);
|
||||
}
|
||||
|
||||
void shutdown(asio::ip::tcp::socket::shutdown_type what) {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).shutdown(what, ec);
|
||||
}
|
||||
|
||||
bool was_connected() const {
|
||||
error_code ec;
|
||||
lowest_layer(*_stream_ptr).remote_endpoint(ec);
|
||||
return ec == boost::system::errc::success;
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_read_some(
|
||||
const BufferType& buffer, duration wait_for, CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, size_t);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, self_type& self,
|
||||
const BufferType& buffer, duration wait_for
|
||||
) {
|
||||
read_op { self, std::move(handler) }.perform(buffer, wait_for);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), buffer, wait_for
|
||||
);
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_write(
|
||||
const BufferType& buffer, CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code, size_t);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, self_type& self, const BufferType& buffer
|
||||
) {
|
||||
write_op { self, std::move(handler) }.perform(buffer);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), buffer
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
log_invoke<logger_type>& log() {
|
||||
return _log;
|
||||
}
|
||||
|
||||
static void open_lowest_layer(const stream_ptr& sptr, asio::ip::tcp protocol) {
|
||||
error_code ec;
|
||||
auto& layer = lowest_layer(*sptr);
|
||||
layer.open(protocol, ec);
|
||||
layer.set_option(asio::socket_base::reuse_address(true), ec);
|
||||
layer.set_option(asio::ip::tcp::no_delay(true), ec);
|
||||
}
|
||||
|
||||
stream_ptr construct_next_layer() const {
|
||||
stream_ptr sptr;
|
||||
if constexpr (has_tls_context<StreamContext>)
|
||||
sptr = std::make_shared<stream_type>(
|
||||
_stream_executor, _stream_context.tls_context()
|
||||
);
|
||||
else
|
||||
sptr = std::make_shared<stream_type>(_stream_executor);
|
||||
|
||||
return sptr;
|
||||
}
|
||||
|
||||
stream_ptr construct_and_open_next_layer(asio::ip::tcp protocol) const {
|
||||
auto sptr = construct_next_layer();
|
||||
open_lowest_layer(sptr, protocol);
|
||||
return sptr;
|
||||
}
|
||||
|
||||
void replace_next_layer(stream_ptr sptr) {
|
||||
// close() will cancel all outstanding async operations on
|
||||
// _stream_ptr; cancelling posts operation_aborted to handlers
|
||||
// but handlers will be executed after std::exchange below;
|
||||
// handlers should therefore treat (operation_aborted && is_open())
|
||||
// equivalent to try_again.
|
||||
|
||||
if (_stream_ptr)
|
||||
close();
|
||||
std::exchange(_stream_ptr, std::move(sptr));
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_reconnect(stream_ptr s, CompletionToken&& token) {
|
||||
using Signature = void (error_code);
|
||||
|
||||
auto initiation = [](auto handler, self_type& self, stream_ptr s) {
|
||||
reconnect_op { self, std::move(handler) }.perform(std::move(s));
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), std::move(s)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_AUTOCONNECT_STREAM_HPP
|
||||
@@ -0,0 +1,524 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_CLIENT_SERVICE_HPP
|
||||
#define BOOST_MQTT5_CLIENT_SERVICE_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/channel_traits.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
#include <boost/mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/assemble_op.hpp>
|
||||
#include <boost/mqtt5/impl/async_sender.hpp>
|
||||
#include <boost/mqtt5/impl/autoconnect_stream.hpp>
|
||||
#include <boost/mqtt5/impl/replies.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/experimental/basic_channel.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename StreamType, typename TlsContext,
|
||||
typename Enable = void
|
||||
>
|
||||
class stream_context;
|
||||
|
||||
template <
|
||||
typename StreamType, typename TlsContext
|
||||
>
|
||||
class stream_context<
|
||||
StreamType, TlsContext,
|
||||
std::enable_if_t<has_tls_layer<StreamType>>
|
||||
> {
|
||||
using tls_context_type = TlsContext;
|
||||
|
||||
mqtt_ctx _mqtt_context;
|
||||
std::shared_ptr<tls_context_type> _tls_context_ptr;
|
||||
|
||||
public:
|
||||
explicit stream_context(TlsContext tls_context) :
|
||||
_tls_context_ptr(std::make_shared<tls_context_type>(std::move(tls_context)))
|
||||
{}
|
||||
|
||||
stream_context(const stream_context& other) :
|
||||
_mqtt_context(other._mqtt_context), _tls_context_ptr(other._tls_context_ptr)
|
||||
{}
|
||||
|
||||
auto& mqtt_context() {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
const auto& mqtt_context() const {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
auto& tls_context() {
|
||||
return *_tls_context_ptr;
|
||||
}
|
||||
|
||||
auto& session_state() {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
const auto& session_state() const {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
_mqtt_context.will_msg = std::move(will);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.ca_props[prop];
|
||||
}
|
||||
|
||||
const auto& connack_properties() const {
|
||||
return _mqtt_context.ca_props;
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
void connect_properties(connect_props props) {
|
||||
_mqtt_context.co_props = std::move(props);
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_mqtt_context.creds = {
|
||||
std::move(client_id),
|
||||
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>
|
||||
class stream_context<
|
||||
StreamType, std::monostate,
|
||||
std::enable_if_t<!has_tls_layer<StreamType>>
|
||||
> {
|
||||
mqtt_ctx _mqtt_context;
|
||||
public:
|
||||
explicit stream_context(std::monostate) {}
|
||||
|
||||
stream_context(const stream_context& other) :
|
||||
_mqtt_context(other._mqtt_context)
|
||||
{}
|
||||
|
||||
auto& mqtt_context() {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
const auto& mqtt_context() const {
|
||||
return _mqtt_context;
|
||||
}
|
||||
|
||||
auto& session_state() {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
const auto& session_state() const {
|
||||
return _mqtt_context.state;
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
_mqtt_context.will_msg = std::move(will);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.ca_props[prop];
|
||||
}
|
||||
|
||||
const auto& connack_properties() const {
|
||||
return _mqtt_context.ca_props;
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) {
|
||||
return _mqtt_context.co_props[prop];
|
||||
}
|
||||
|
||||
void connect_properties(connect_props props) {
|
||||
_mqtt_context.co_props = std::move(props);
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_mqtt_context.creds = {
|
||||
std::move(client_id),
|
||||
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,
|
||||
typename TlsContext = std::monostate,
|
||||
typename LoggerType = noop_logger
|
||||
>
|
||||
class client_service {
|
||||
using self_type = client_service<StreamType, TlsContext, LoggerType>;
|
||||
using stream_context_type = stream_context<StreamType, TlsContext>;
|
||||
using stream_type = autoconnect_stream<
|
||||
StreamType, stream_context_type, LoggerType
|
||||
>;
|
||||
public:
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
private:
|
||||
using tls_context_type = TlsContext;
|
||||
using logger_type = LoggerType;
|
||||
using receive_channel = asio::experimental::basic_channel<
|
||||
executor_type,
|
||||
channel_traits<>,
|
||||
void (error_code, std::string, std::string, publish_props)
|
||||
>;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class run_op;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class async_sender;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class assemble_op;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class ping_op;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
friend class sentry_op;
|
||||
|
||||
template <typename ClientService>
|
||||
friend class re_auth_op;
|
||||
|
||||
executor_type _executor;
|
||||
|
||||
log_invoke<logger_type> _log;
|
||||
|
||||
stream_context_type _stream_context;
|
||||
stream_type _stream;
|
||||
|
||||
packet_id_allocator _pid_allocator;
|
||||
replies _replies;
|
||||
async_sender<client_service> _async_sender;
|
||||
|
||||
std::string _read_buff;
|
||||
data_span _active_span;
|
||||
|
||||
receive_channel _rec_channel;
|
||||
|
||||
asio::steady_timer _ping_timer;
|
||||
asio::steady_timer _sentry_timer;
|
||||
|
||||
client_service(const client_service& other) :
|
||||
_executor(other._executor),
|
||||
_log(other._log),
|
||||
_stream_context(other._stream_context),
|
||||
_stream(_executor, _stream_context, _log),
|
||||
_replies(_executor),
|
||||
_async_sender(*this),
|
||||
_active_span(_read_buff.cend(), _read_buff.cend()),
|
||||
_rec_channel(_executor, std::numeric_limits<size_t>::max()),
|
||||
_ping_timer(_executor),
|
||||
_sentry_timer(_executor)
|
||||
{
|
||||
_stream.clone_endpoints(other._stream);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
explicit client_service(
|
||||
const executor_type& ex,
|
||||
tls_context_type tls_context = {}, logger_type logger = {}
|
||||
) :
|
||||
_executor(ex),
|
||||
_log(std::move(logger)),
|
||||
_stream_context(std::move(tls_context)),
|
||||
_stream(ex, _stream_context, _log),
|
||||
_replies(ex),
|
||||
_async_sender(*this),
|
||||
_active_span(_read_buff.cend(), _read_buff.cend()),
|
||||
_rec_channel(ex, std::numeric_limits<size_t>::max()),
|
||||
_ping_timer(ex),
|
||||
_sentry_timer(ex)
|
||||
{}
|
||||
|
||||
executor_type get_executor() const noexcept {
|
||||
return _executor;
|
||||
}
|
||||
|
||||
auto dup() const {
|
||||
return std::shared_ptr<client_service>(new client_service(*this));
|
||||
}
|
||||
|
||||
template <
|
||||
typename Ctx = TlsContext,
|
||||
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, bool> = true
|
||||
>
|
||||
decltype(auto) tls_context() {
|
||||
return _stream_context.tls_context();
|
||||
}
|
||||
|
||||
void will(will will) {
|
||||
if (!is_open())
|
||||
_stream_context.will(std::move(will));
|
||||
}
|
||||
|
||||
void credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
if (!is_open())
|
||||
_stream_context.credentials(
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
);
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
if (!is_open())
|
||||
_stream.brokers(std::move(hosts), default_port);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Authenticator,
|
||||
std::enable_if_t<is_authenticator<Authenticator>, bool> = true
|
||||
>
|
||||
void authenticator(Authenticator&& authenticator) {
|
||||
if (!is_open())
|
||||
_stream_context.authenticator(
|
||||
std::forward<Authenticator>(authenticator)
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t negotiated_keep_alive() const {
|
||||
return connack_property(prop::server_keep_alive)
|
||||
.value_or(_stream_context.mqtt_context().keep_alive);
|
||||
}
|
||||
|
||||
void keep_alive(uint16_t seconds) {
|
||||
if (!is_open())
|
||||
_stream_context.mqtt_context().keep_alive = seconds;
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _stream_context.connect_property(prop);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
void connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop,
|
||||
prop::value_type_t<p> value
|
||||
){
|
||||
if (!is_open())
|
||||
_stream_context.connect_property(prop) = value;
|
||||
}
|
||||
|
||||
void connect_properties(connect_props props) {
|
||||
if (!is_open())
|
||||
_stream_context.connect_properties(std::move(props));
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _stream_context.connack_property(prop);
|
||||
}
|
||||
|
||||
const auto& connack_properties() const {
|
||||
return _stream_context.connack_properties();
|
||||
}
|
||||
|
||||
void open_stream() {
|
||||
_stream.open();
|
||||
}
|
||||
|
||||
bool is_open() const {
|
||||
return _stream.is_open();
|
||||
}
|
||||
|
||||
void close_stream() {
|
||||
_stream.close();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (!_stream.is_open()) return;
|
||||
|
||||
_ping_timer.cancel();
|
||||
_sentry_timer.cancel();
|
||||
|
||||
_rec_channel.close();
|
||||
_replies.cancel_unanswered();
|
||||
_async_sender.cancel();
|
||||
_stream.cancel();
|
||||
_stream.close();
|
||||
}
|
||||
|
||||
log_invoke<LoggerType>& log() {
|
||||
return _log;
|
||||
}
|
||||
|
||||
uint16_t allocate_pid() {
|
||||
return _pid_allocator.allocate();
|
||||
}
|
||||
|
||||
void free_pid(uint16_t pid, bool was_throttled = false) {
|
||||
_pid_allocator.free(pid);
|
||||
if (was_throttled)
|
||||
_async_sender.throttled_op_done();
|
||||
}
|
||||
|
||||
serial_num_t next_serial_num() {
|
||||
return _async_sender.next_serial_num();
|
||||
}
|
||||
|
||||
bool subscriptions_present() const {
|
||||
return _stream_context.session_state().subscriptions_present();
|
||||
}
|
||||
|
||||
void subscriptions_present(bool present) {
|
||||
_stream_context.session_state().subscriptions_present(present);
|
||||
}
|
||||
|
||||
void update_session_state() {
|
||||
auto& session_state = _stream_context.session_state();
|
||||
|
||||
if (!session_state.session_present()) {
|
||||
_replies.clear_pending_pubrels();
|
||||
session_state.session_present(true);
|
||||
|
||||
if (session_state.subscriptions_present()) {
|
||||
channel_store_error(client::error::session_expired);
|
||||
session_state.subscriptions_present(false);
|
||||
}
|
||||
}
|
||||
|
||||
_ping_timer.cancel();
|
||||
}
|
||||
|
||||
bool channel_store(decoders::publish_message message) {
|
||||
auto& [topic, packet_id, flags, props, payload] = message;
|
||||
return _rec_channel.try_send(
|
||||
error_code {}, std::move(topic),
|
||||
std::move(payload), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
bool channel_store_error(error_code ec) {
|
||||
return _rec_channel.try_send(
|
||||
ec, std::string {}, std::string {}, publish_props {}
|
||||
);
|
||||
}
|
||||
|
||||
template <typename BufferType, typename CompletionToken>
|
||||
decltype(auto) async_send(
|
||||
const BufferType& buffer,
|
||||
serial_num_t serial_num, unsigned flags,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
return _async_sender.async_send(
|
||||
buffer, serial_num, flags, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_assemble(CompletionToken&& token) {
|
||||
using Signature = void (error_code, uint8_t, byte_citer, byte_citer);
|
||||
|
||||
auto initiation = [] (
|
||||
auto handler, self_type& self,
|
||||
std::string& read_buff, data_span& active_span
|
||||
) {
|
||||
assemble_op {
|
||||
self, std::move(handler), read_buff, active_span
|
||||
}.perform(asio::transfer_at_least(0));
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature> (
|
||||
initiation, token, std::ref(*this),
|
||||
std::ref(_read_buff), std::ref(_active_span)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_wait_reply(
|
||||
control_code_e code, uint16_t packet_id, CompletionToken&& token
|
||||
) {
|
||||
return _replies.async_wait_reply(
|
||||
code, packet_id, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_channel_receive(CompletionToken&& token) {
|
||||
return _rec_channel.async_receive(std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_CLIENT_SERVICE_HPP
|
||||
@@ -0,0 +1,462 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_BASE_DECODERS_HPP
|
||||
#define BOOST_MQTT5_BASE_DECODERS_HPP
|
||||
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/traits.hpp>
|
||||
|
||||
#include <boost/fusion/adapted/std_tuple.hpp>
|
||||
#include <boost/fusion/container/deque.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/spirit/home/x3/binary/binary.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5::decoders {
|
||||
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
template <typename T>
|
||||
struct convert { using type = T; };
|
||||
|
||||
template <typename ...Args>
|
||||
struct convert<boost::fusion::deque<Args...>> {
|
||||
using type = std::tuple<typename convert<Args>::type...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct convert<boost::optional<T>> {
|
||||
using type = std::optional<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct convert<std::vector<T>> {
|
||||
using type = std::vector<typename convert<T>::type>;
|
||||
};
|
||||
|
||||
template <typename T, typename Parser>
|
||||
constexpr auto as(Parser&& p) {
|
||||
return x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
|
||||
template <typename It, typename Parser>
|
||||
auto type_parse(It& first, const It last, const Parser& p) {
|
||||
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Parser&>()));
|
||||
using attr_type = typename x3::traits::attribute_of<Parser, ctx_type>::type;
|
||||
|
||||
using rv_type = typename convert<attr_type>::type;
|
||||
|
||||
std::optional<rv_type> rv;
|
||||
rv_type value {};
|
||||
if (x3::phrase_parse(first, last, as<rv_type>(p), x3::eps(false), value))
|
||||
rv = std::move(value);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
template <typename AttributeType, typename It, typename Parser>
|
||||
auto type_parse(It& first, const It last, const Parser& p) {
|
||||
std::optional<AttributeType> rv;
|
||||
AttributeType value {};
|
||||
if (x3::phrase_parse(first, last, as<AttributeType>(p), x3::eps(false), value))
|
||||
rv = std::move(value);
|
||||
return rv;
|
||||
}
|
||||
|
||||
namespace basic {
|
||||
|
||||
template <typename T>
|
||||
constexpr auto to(T& arg) {
|
||||
return [&](auto& ctx) {
|
||||
using ctx_type = decltype(ctx);
|
||||
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
|
||||
if constexpr (detail::is_boost_iterator<attr_type>)
|
||||
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
|
||||
else
|
||||
arg = x3::_attr(ctx);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename LenParser, typename Subject, typename Enable = void>
|
||||
class scope_limit {};
|
||||
|
||||
template <typename LenParser, typename Subject>
|
||||
class scope_limit<
|
||||
LenParser, Subject,
|
||||
std::enable_if_t<x3::traits::is_parser<LenParser>::value>
|
||||
> :
|
||||
public x3::unary_parser<Subject, scope_limit<LenParser, Subject>>
|
||||
{
|
||||
using base_type = x3::unary_parser<Subject, scope_limit<LenParser, Subject>>;
|
||||
LenParser _lp;
|
||||
public:
|
||||
using ctx_type =
|
||||
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using attribute_type =
|
||||
typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
static bool const has_attribute = true;
|
||||
|
||||
scope_limit(const LenParser& lp, const Subject& subject) :
|
||||
base_type(subject), _lp(lp)
|
||||
{}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
typename x3::traits::attribute_of<LenParser, Ctx>::type len;
|
||||
if (!_lp.parse(iter, last, ctx, rctx, len))
|
||||
return false;
|
||||
if (iter + len > last)
|
||||
return false;
|
||||
|
||||
if (!base_type::subject.parse(iter, iter + len, ctx, rctx, attr))
|
||||
return false;
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Size, typename Subject>
|
||||
class scope_limit<
|
||||
Size, Subject,
|
||||
std::enable_if_t<std::is_arithmetic_v<Size>>
|
||||
> :
|
||||
public x3::unary_parser<Subject, scope_limit<Size, Subject>>
|
||||
{
|
||||
using base_type = x3::unary_parser<Subject, scope_limit<Size, Subject>>;
|
||||
size_t _limit;
|
||||
public:
|
||||
using ctx_type =
|
||||
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using attribute_type =
|
||||
typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
static bool const has_attribute = true;
|
||||
|
||||
scope_limit(Size limit, const Subject& subject) :
|
||||
base_type(subject), _limit(limit)
|
||||
{}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
if (iter + _limit > last)
|
||||
return false;
|
||||
|
||||
if (!base_type::subject.parse(iter, iter + _limit, ctx, rctx, attr))
|
||||
return false;
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename LenParser, typename Enable = void>
|
||||
struct scope_limit_gen {
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return scope_limit<LenParser, Subject> { _lp, x3::as_parser(p) };
|
||||
}
|
||||
LenParser _lp;
|
||||
};
|
||||
|
||||
template <typename Size>
|
||||
struct scope_limit_gen<
|
||||
Size,
|
||||
std::enable_if_t<std::is_arithmetic_v<Size>>
|
||||
> {
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return scope_limit<Size, Subject> { limit, x3::as_parser(p) };
|
||||
}
|
||||
Size limit;
|
||||
};
|
||||
|
||||
template <
|
||||
typename Parser,
|
||||
std::enable_if_t<x3::traits::is_parser<Parser>::value, bool> = true
|
||||
>
|
||||
scope_limit_gen<Parser> scope_limit_(const Parser& p) {
|
||||
return { p };
|
||||
}
|
||||
|
||||
template <
|
||||
typename Size,
|
||||
std::enable_if_t<std::is_arithmetic_v<Size>, bool> = true
|
||||
>
|
||||
scope_limit_gen<Size> scope_limit_(Size limit) {
|
||||
return { limit };
|
||||
}
|
||||
|
||||
struct verbatim_parser : x3::parser<verbatim_parser> {
|
||||
using attribute_type = std::string;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(It& first, const It last, const Ctx&, RCtx&, Attr& attr) const {
|
||||
attr = std::string { first, last };
|
||||
first = last;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto verbatim_ = verbatim_parser{};
|
||||
|
||||
struct varint_parser : x3::parser<varint_parser> {
|
||||
using attribute_type = int32_t;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx&, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
if (iter == last)
|
||||
return false;
|
||||
|
||||
int32_t result = 0; unsigned bit_shift = 0;
|
||||
|
||||
for (; iter != last && bit_shift < sizeof(int32_t) * 7; ++iter) {
|
||||
auto val = *iter;
|
||||
if (val & 0b1000'0000u) {
|
||||
result |= (val & 0b0111'1111u) << bit_shift;
|
||||
bit_shift += 7;
|
||||
}
|
||||
else {
|
||||
result |= (static_cast<int32_t>(val) << bit_shift);
|
||||
bit_shift = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bit_shift)
|
||||
return false;
|
||||
|
||||
attr = result;
|
||||
first = ++iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr varint_parser varint_{};
|
||||
|
||||
struct len_prefix_parser : x3::parser<len_prefix_parser> {
|
||||
using attribute_type = std::string;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
typename x3::traits::attribute_of<decltype(x3::big_word), Ctx>::type len;
|
||||
if (x3::big_word.parse(iter, last, ctx, rctx, len)) {
|
||||
if (std::distance(iter, last) < len)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
attr = std::string(iter, iter + len);
|
||||
first = iter + len;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr len_prefix_parser utf8_ {};
|
||||
constexpr len_prefix_parser binary_ {};
|
||||
|
||||
/*
|
||||
Boost Spirit incorrectly deduces atribute type for a parser of the form
|
||||
(eps(a) | parser1) >> (eps(b) | parser)
|
||||
and we had to create if_ parser to remedy the issue
|
||||
*/
|
||||
|
||||
template <typename Subject>
|
||||
class conditional_parser :
|
||||
public x3::unary_parser<Subject, conditional_parser<Subject>> {
|
||||
|
||||
using base_type = x3::unary_parser<Subject, conditional_parser<Subject>>;
|
||||
bool _condition;
|
||||
public:
|
||||
using ctx_type =
|
||||
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
|
||||
using subject_attr_type =
|
||||
typename x3::traits::attribute_of<Subject, ctx_type>::type;
|
||||
|
||||
using attribute_type = boost::optional<subject_attr_type>;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
conditional_parser(const Subject& s, bool condition) :
|
||||
base_type(s), _condition(condition) {}
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
if (!_condition)
|
||||
return true;
|
||||
|
||||
It iter = first;
|
||||
subject_attr_type sattr {};
|
||||
if (!base_type::subject.parse(iter, last, ctx, rctx, sattr))
|
||||
return false;
|
||||
|
||||
attr.emplace(std::move(sattr));
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct conditional_gen {
|
||||
bool _condition;
|
||||
|
||||
template <typename Subject>
|
||||
auto operator[](const Subject& p) const {
|
||||
return conditional_parser<Subject> { p, _condition };
|
||||
}
|
||||
};
|
||||
|
||||
inline conditional_gen if_(bool condition) {
|
||||
return { condition };
|
||||
}
|
||||
|
||||
} // end namespace basic
|
||||
|
||||
|
||||
namespace prop {
|
||||
|
||||
namespace basic = boost::mqtt5::decoders::basic;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Prop>
|
||||
bool parse_to_prop(
|
||||
It& iter, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Prop& prop
|
||||
) {
|
||||
using namespace boost::mqtt5::detail;
|
||||
using prop_type = std::remove_reference_t<decltype(prop)>;
|
||||
|
||||
bool rv = false;
|
||||
|
||||
if constexpr (std::is_same_v<prop_type, uint8_t>)
|
||||
rv = x3::byte_.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, uint16_t>)
|
||||
rv = x3::big_word.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, int32_t>)
|
||||
rv = basic::varint_.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, uint32_t>)
|
||||
rv = x3::big_dword.parse(iter, last, ctx, rctx, prop);
|
||||
else if constexpr (std::is_same_v<prop_type, std::string>)
|
||||
rv = basic::utf8_.parse(iter, last, ctx, rctx, prop);
|
||||
|
||||
else if constexpr (is_optional<prop_type>) {
|
||||
typename prop_type::value_type val;
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, val);
|
||||
if (rv) prop.emplace(std::move(val));
|
||||
}
|
||||
|
||||
else if constexpr (is_pair<prop_type>) {
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, prop.first);
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, prop.second);
|
||||
}
|
||||
|
||||
else if constexpr (is_vector<prop_type> || is_small_vector<prop_type>) {
|
||||
typename std::remove_reference_t<prop_type>::value_type value;
|
||||
rv = parse_to_prop(iter, last, ctx, rctx, value);
|
||||
if (rv) prop.push_back(std::move(value));
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <typename Props>
|
||||
class prop_parser : public x3::parser<prop_parser<Props>> {
|
||||
public:
|
||||
using attribute_type = Props;
|
||||
static bool const has_attribute = true;
|
||||
|
||||
template <typename It, typename Ctx, typename RCtx, typename Attr>
|
||||
bool parse(
|
||||
It& first, const It last,
|
||||
const Ctx& ctx, RCtx& rctx, Attr& attr
|
||||
) const {
|
||||
|
||||
It iter = first;
|
||||
x3::skip_over(iter, last, ctx);
|
||||
|
||||
if (iter == last)
|
||||
return true;
|
||||
|
||||
int32_t props_length;
|
||||
if (!basic::varint_.parse(iter, last, ctx, rctx, props_length))
|
||||
return false;
|
||||
|
||||
const It scoped_last = iter + props_length;
|
||||
// attr = Props{};
|
||||
|
||||
while (iter < scoped_last) {
|
||||
uint8_t prop_id = *iter++;
|
||||
bool rv = true;
|
||||
It saved = iter;
|
||||
|
||||
attr.apply_on(
|
||||
prop_id,
|
||||
[&rv, &iter, scoped_last, &ctx, &rctx](auto& prop) {
|
||||
rv = detail::parse_to_prop(
|
||||
iter, scoped_last, ctx, rctx, prop
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// either rv = false or property with prop_id was not found
|
||||
if (!rv || iter == saved)
|
||||
return false;
|
||||
}
|
||||
|
||||
first = iter;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Props>
|
||||
constexpr auto props_ = prop_parser<Props> {};
|
||||
|
||||
} // end namespace prop
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::decoders
|
||||
|
||||
#endif // !BOOST_MQTT5_BASE_DECODERS_HPP
|
||||
@@ -0,0 +1,514 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_BASE_ENCODERS_HPP
|
||||
#define BOOST_MQTT5_BASE_ENCODERS_HPP
|
||||
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/traits.hpp>
|
||||
|
||||
#include <boost/core/identity.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/type_traits/is_detected_exact.hpp>
|
||||
#include <boost/type_traits/remove_cv_ref.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5::encoders {
|
||||
|
||||
namespace basic {
|
||||
|
||||
using varint_t = int*;
|
||||
|
||||
inline void to_variable_bytes(std::string& s, int32_t val) {
|
||||
if (val > 0xfffffff) return;
|
||||
while (val > 127) {
|
||||
s.push_back(char((val & 0b01111111) | 0b10000000));
|
||||
val >>= 7;
|
||||
}
|
||||
s.push_back(val & 0b01111111);
|
||||
}
|
||||
|
||||
inline size_t variable_length(int32_t val) {
|
||||
if (val > 0xfffffff) return 0;
|
||||
size_t rv = 1;
|
||||
for (; val > 127; ++rv) val >>= 7;
|
||||
return rv;
|
||||
}
|
||||
|
||||
struct encoder {};
|
||||
|
||||
template <size_t bits, typename repr = uint8_t>
|
||||
class flag_def : public encoder {
|
||||
template <size_t num_bits>
|
||||
using least_type = std::conditional_t<
|
||||
num_bits <= 8, uint8_t,
|
||||
std::conditional_t<
|
||||
num_bits <= 16, uint16_t,
|
||||
std::conditional_t<
|
||||
num_bits <= 32, uint32_t,
|
||||
std::conditional_t<num_bits <= 64, uint64_t, void>
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
template <size_t oth_bits, typename oth_repr>
|
||||
friend class flag_def;
|
||||
|
||||
repr _val { 0 };
|
||||
|
||||
public:
|
||||
flag_def(repr val) : _val(val) {}
|
||||
flag_def() = default;
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename projection = boost::identity,
|
||||
std::enable_if_t<detail::is_optional<T>, bool> = true
|
||||
>
|
||||
auto operator()(T&& value, projection proj = {}) const {
|
||||
if constexpr (std::is_same_v<projection, boost::identity>) {
|
||||
repr val = value.has_value();
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
else {
|
||||
repr val = value.has_value() ?
|
||||
static_cast<repr>(std::invoke(proj, *value)) : 0;
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
typename projection = boost::identity,
|
||||
std::enable_if_t<!detail::is_optional<T>, bool> = true
|
||||
>
|
||||
auto operator()(T&& value, projection proj = {}) const {
|
||||
auto val = static_cast<repr>(std::invoke(proj, value));
|
||||
return flag_def<bits, repr> { val };
|
||||
}
|
||||
|
||||
size_t byte_size() const { return sizeof(repr); }
|
||||
|
||||
template <size_t rhs_bits, typename rhs_repr>
|
||||
auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
|
||||
using res_repr = least_type<bits + rhs_bits>;
|
||||
auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
|
||||
return flag_def<bits + rhs_bits, res_repr> { val };
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
using namespace boost::endian;
|
||||
size_t sz = s.size(); s.resize(sz + sizeof(repr));
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<repr, sizeof(repr), order::big>(p, _val);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t bits, typename repr = uint8_t>
|
||||
constexpr auto flag = flag_def<bits, repr>{};
|
||||
|
||||
|
||||
template <typename T, typename Repr>
|
||||
class int_val : public encoder {
|
||||
T _val;
|
||||
public:
|
||||
int_val(T val) : _val(val) {}
|
||||
|
||||
size_t byte_size() const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (_val) return val_length(*_val);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return val_length(_val);
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (_val) return encode_val(s, *_val);
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return encode_val(s, _val);
|
||||
}
|
||||
private:
|
||||
template <typename U>
|
||||
static size_t val_length(U&& val) {
|
||||
if constexpr (std::is_same_v<Repr, varint_t>)
|
||||
return variable_length(int32_t(val));
|
||||
else
|
||||
return sizeof(Repr);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static std::string& encode_val(std::string& s, U&& val) {
|
||||
using namespace boost::endian;
|
||||
if constexpr (std::is_same_v<Repr, varint_t>) {
|
||||
to_variable_bytes(s, int32_t(val));
|
||||
return s;
|
||||
}
|
||||
else {
|
||||
size_t sz = s.size(); s.resize(sz + sizeof(Repr));
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<Repr, sizeof(Repr), order::big>(p, val);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Repr>
|
||||
class int_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& val) const {
|
||||
return int_val<T, Repr> { std::forward<T>(val) };
|
||||
}
|
||||
|
||||
template <typename T, typename projection>
|
||||
auto operator()(T&& val, projection proj) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
using rv_type = std::invoke_result_t<
|
||||
projection, typename boost::remove_cv_ref_t<T>::value_type
|
||||
>;
|
||||
if (val.has_value())
|
||||
return (*this)(std::invoke(proj, *val));
|
||||
return int_val<rv_type, Repr> { rv_type {} };
|
||||
}
|
||||
else {
|
||||
using rv_type = std::invoke_result_t<projection, T>;
|
||||
return int_val<rv_type, Repr> { std::invoke(proj, val) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto byte_ = int_def<uint8_t> {};
|
||||
constexpr auto int16_ = int_def<uint16_t> {};
|
||||
constexpr auto int32_ = int_def<uint32_t> {};
|
||||
constexpr auto varlen_ = int_def<varint_t> {};
|
||||
|
||||
|
||||
template <typename T>
|
||||
class array_val : public encoder {
|
||||
T _val;
|
||||
bool _with_length;
|
||||
public:
|
||||
array_val(T val, bool with_length) :
|
||||
_val(val), _with_length(with_length)
|
||||
{
|
||||
static_assert(
|
||||
std::is_reference_v<T> || std::is_same_v<T, std::string_view>
|
||||
);
|
||||
}
|
||||
|
||||
size_t byte_size() const {
|
||||
if constexpr (detail::is_optional<T>)
|
||||
return _val ? _with_length * 2 + val_length(*_val) : 0;
|
||||
else
|
||||
return _with_length * 2 + val_length(_val);
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (_val) return encode_val(s, *_val);
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return encode_val(s, _val);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename V>
|
||||
using has_size = decltype(std::declval<V&>().size());
|
||||
|
||||
template <typename U>
|
||||
static size_t val_length(U&& val) {
|
||||
if constexpr (std::is_same_v<boost::remove_cv_ref_t<U>, const char*>)
|
||||
return std::strlen(val);
|
||||
|
||||
if constexpr (boost::is_detected_exact_v<size_t, has_size, U>)
|
||||
return val.size();
|
||||
else // fallback to type const char (&)[N] (substract 1 for trailing 0)
|
||||
return sizeof(val) - 1;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
std::string& encode_val(std::string& s, U&& u) const {
|
||||
using namespace boost::endian;
|
||||
auto byte_len = val_length(std::forward<U>(u));
|
||||
if (byte_len == 0 && !_with_length) return s;
|
||||
if (_with_length) {
|
||||
size_t sz = s.size(); s.resize(sz + 2);
|
||||
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
|
||||
endian_store<int16_t, sizeof(int16_t), order::big>(
|
||||
p, int16_t(byte_len)
|
||||
);
|
||||
}
|
||||
s.append(std::begin(u), std::begin(u) + byte_len);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool with_length = true>
|
||||
class array_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& val) const {
|
||||
return array_val<T> { std::forward<T>(val), with_length };
|
||||
}
|
||||
|
||||
template <typename T, typename projection>
|
||||
auto operator()(T&& val, projection proj) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
using rv_type = std::invoke_result_t<
|
||||
projection, typename boost::remove_cv_ref_t<T>::value_type
|
||||
>;
|
||||
if (val.has_value())
|
||||
return (*this)(std::invoke(proj, *val));
|
||||
return array_val<rv_type> { rv_type {}, false };
|
||||
}
|
||||
else {
|
||||
const auto& av = std::invoke(proj, val);
|
||||
return array_val<T> { av, true };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using utf8_def = array_def<true>;
|
||||
|
||||
constexpr auto utf8_ = utf8_def {};
|
||||
constexpr auto binary_ = array_def<true> {}; // for now
|
||||
constexpr auto verbatim_ = array_def<false> {};
|
||||
|
||||
|
||||
template <typename T, typename U>
|
||||
class composed_val : public encoder {
|
||||
T _lhs; U _rhs;
|
||||
public:
|
||||
composed_val(T lhs, U rhs) :
|
||||
_lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs))
|
||||
{}
|
||||
|
||||
size_t byte_size() const {
|
||||
return _lhs.byte_size() + _rhs.byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
_lhs.encode(s);
|
||||
return _rhs.encode(s);
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename T, typename U,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<encoder, std::decay_t<T>> &&
|
||||
std::is_base_of_v<encoder, std::decay_t<U>>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
inline auto operator&(T&& t, U&& u) {
|
||||
return composed_val(std::forward<T>(t), std::forward<U>(u));
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
std::enable_if_t<std::is_base_of_v<encoder, std::decay_t<T>>, bool> = true
|
||||
>
|
||||
std::string& operator<<(std::string& s, T&& t) {
|
||||
return t.encode(s);
|
||||
}
|
||||
|
||||
} // end namespace basic
|
||||
|
||||
namespace prop {
|
||||
|
||||
namespace pp = boost::mqtt5::prop;
|
||||
|
||||
template <typename T>
|
||||
auto encoder_for_prop_value(const T& val) {
|
||||
if constexpr (std::is_same_v<T, uint8_t>)
|
||||
return basic::int_def<uint8_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, uint16_t>)
|
||||
return basic::int_def<uint16_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, int32_t>)
|
||||
return basic::int_def<basic::varint_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, uint32_t>)
|
||||
return basic::int_def<uint32_t>{}(val);
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
return basic::utf8_def{}(val);
|
||||
else if constexpr (detail::is_pair<T>)
|
||||
return encoder_for_prop_value(val.first) &
|
||||
encoder_for_prop_value(val.second);
|
||||
}
|
||||
|
||||
template <typename T, pp::property_type p, typename Enable = void>
|
||||
class prop_val;
|
||||
|
||||
template <
|
||||
typename T, pp::property_type p
|
||||
>
|
||||
class prop_val<
|
||||
T, p,
|
||||
std::enable_if_t<!detail::is_vector<T> && detail::is_optional<T>>
|
||||
> : public basic::encoder {
|
||||
// allows T to be reference type to std::optional
|
||||
static inline boost::remove_cv_ref_t<T> nulltype;
|
||||
T _val;
|
||||
public:
|
||||
prop_val(T val) : _val(val) {
|
||||
static_assert(std::is_reference_v<T>);
|
||||
}
|
||||
prop_val() : _val(nulltype) {}
|
||||
|
||||
size_t byte_size() const {
|
||||
if (!_val) return 0;
|
||||
return 1 + encoder_for_prop_value(*_val).byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if (!_val)
|
||||
return s;
|
||||
s.push_back(p);
|
||||
return encoder_for_prop_value(*_val).encode(s);
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename T, pp::property_type p
|
||||
>
|
||||
class prop_val<
|
||||
T, p,
|
||||
std::enable_if_t<detail::is_vector<T> || detail::is_small_vector<T>>
|
||||
> : public basic::encoder {
|
||||
// allows T to be reference type to std::vector
|
||||
static inline boost::remove_cv_ref_t<T> nulltype;
|
||||
T _val;
|
||||
public:
|
||||
prop_val(T val) : _val(val) {
|
||||
static_assert(std::is_reference_v<T>);
|
||||
}
|
||||
|
||||
prop_val() : _val(nulltype) { }
|
||||
|
||||
size_t byte_size() const {
|
||||
if (_val.empty()) return 0;
|
||||
|
||||
size_t total_size = 0;
|
||||
for (const auto& elem : _val)
|
||||
total_size += 1 + encoder_for_prop_value(elem).byte_size();
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
if (_val.empty())
|
||||
return s;
|
||||
|
||||
for (const auto& elem: _val) {
|
||||
s.push_back(p);
|
||||
encoder_for_prop_value(elem).encode(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Props>
|
||||
class props_val : public basic::encoder {
|
||||
static inline std::decay_t<Props> nulltype;
|
||||
|
||||
template <pp::property_type P, typename T>
|
||||
static auto to_prop_val(const T& val) {
|
||||
return prop_val<const T&, P>(val);
|
||||
}
|
||||
|
||||
template <pp::property_type ...Ps>
|
||||
static auto to_prop_vals(const pp::properties<Ps...>& props) {
|
||||
return std::make_tuple(
|
||||
to_prop_val<Ps>(
|
||||
props[std::integral_constant<pp::property_type, Ps> {}]
|
||||
)...
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
auto apply_each(Func&& func) const {
|
||||
return std::apply([&func](const auto&... props) {
|
||||
return (std::invoke(func, props), ...);
|
||||
}, _prop_vals);
|
||||
}
|
||||
|
||||
decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
|
||||
bool _may_omit;
|
||||
|
||||
public:
|
||||
props_val(Props val, bool may_omit) :
|
||||
_prop_vals(to_prop_vals(val)), _may_omit(may_omit)
|
||||
{
|
||||
static_assert(std::is_reference_v<Props>);
|
||||
}
|
||||
props_val(bool may_omit) :
|
||||
_prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit)
|
||||
{}
|
||||
|
||||
size_t byte_size() const {
|
||||
size_t psize = props_size();
|
||||
if (_may_omit && psize == 0) return 0;
|
||||
return psize + basic::varlen_(psize).byte_size();
|
||||
}
|
||||
|
||||
std::string& encode(std::string& s) const {
|
||||
size_t psize = props_size();
|
||||
if (_may_omit && psize == 0) return s;
|
||||
basic::varlen_(psize).encode(s);
|
||||
apply_each([&s](const auto& pv) { return pv.encode(s); });
|
||||
return s;
|
||||
}
|
||||
private:
|
||||
size_t props_size() const {
|
||||
size_t retval = 0;
|
||||
apply_each([&retval](const auto& pv) {
|
||||
return retval += pv.byte_size();
|
||||
});
|
||||
return retval;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool may_omit>
|
||||
class props_def {
|
||||
public:
|
||||
template <typename T>
|
||||
auto operator()(T&& prop_container) const {
|
||||
if constexpr (detail::is_optional<T>) {
|
||||
if (prop_container.has_value())
|
||||
return (*this)(*prop_container);
|
||||
return props_val<
|
||||
const typename boost::remove_cv_ref_t<T>::value_type&
|
||||
>(true);
|
||||
}
|
||||
else {
|
||||
return props_val<T> { prop_container, may_omit };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto props_ = props_def<false> {};
|
||||
constexpr auto props_may_omit_ = props_def<true> {};
|
||||
|
||||
} // end namespace prop
|
||||
|
||||
} // end namespace boost::mqtt5::encoders
|
||||
|
||||
#endif // !BOOST_MQTT5_BASE_ENCODERS_HPP
|
||||
@@ -0,0 +1,309 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_MESSAGE_DECODERS_HPP
|
||||
#define BOOST_MQTT5_MESSAGE_DECODERS_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/base_decoders.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::decoders {
|
||||
|
||||
using byte_citer = detail::byte_citer;
|
||||
|
||||
using fixed_header = std::tuple<
|
||||
uint8_t, // control byte
|
||||
uint32_t // remaining_length
|
||||
>;
|
||||
|
||||
inline std::optional<fixed_header> decode_fixed_header(
|
||||
byte_citer& it, const byte_citer last
|
||||
) {
|
||||
auto fixed_header_ = x3::byte_ >> basic::varint_;
|
||||
return type_parse(it, last, fixed_header_);
|
||||
}
|
||||
|
||||
using packet_id = uint16_t;
|
||||
|
||||
inline std::optional<packet_id> decode_packet_id(
|
||||
byte_citer& it
|
||||
) {
|
||||
auto packet_id_ = x3::big_word;
|
||||
return type_parse(it, it + sizeof(uint16_t), packet_id_);
|
||||
}
|
||||
|
||||
using connect_message = std::tuple<
|
||||
std::string, // client_id,
|
||||
std::optional<std::string>, // user_name,
|
||||
std::optional<std::string>, // password,
|
||||
uint16_t, // keep_alive,
|
||||
bool, // clean_start,
|
||||
connect_props, // props,
|
||||
std::optional<will> // will
|
||||
>;
|
||||
|
||||
inline std::optional<connect_message> decode_connect(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto var_header_ =
|
||||
basic::utf8_ >> // MQTT
|
||||
x3::byte_ >> // (num 5)
|
||||
x3::byte_ >> // conn_flags_
|
||||
x3::big_word >> // keep_alive
|
||||
prop::props_<connect_props>;
|
||||
|
||||
const byte_citer end = it + remain_length;
|
||||
auto vh = type_parse(it, end, var_header_);
|
||||
if (!vh)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh;
|
||||
|
||||
if (mqtt_str != "MQTT" || version != 5)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
bool has_will = (flags & 0b00000100);
|
||||
bool has_uname = (flags & 0b10000000);
|
||||
bool has_pwd = (flags & 0b01000000);
|
||||
|
||||
auto payload_ =
|
||||
basic::utf8_ >> // client_id
|
||||
basic::if_(has_will)[prop::props_<will_props>] >>
|
||||
basic::if_(has_will)[basic::utf8_] >> // will topic
|
||||
basic::if_(has_will)[basic::binary_] >> // will message
|
||||
basic::if_(has_uname)[basic::utf8_] >> // username
|
||||
basic::if_(has_pwd)[basic::utf8_]; // password
|
||||
|
||||
auto pload = type_parse(it, end, payload_);
|
||||
if (!pload)
|
||||
return std::optional<connect_message>{};
|
||||
|
||||
std::optional<will> w;
|
||||
|
||||
if (has_will)
|
||||
w.emplace(
|
||||
std::move(*std::get<2>(*pload)), // will_topic
|
||||
std::move(*std::get<3>(*pload)), // will_message
|
||||
qos_e((flags & 0b00011000) >> 3),
|
||||
retain_e((flags & 0b00100000) >> 5),
|
||||
std::move(*std::get<1>(*pload)) // will props
|
||||
);
|
||||
|
||||
connect_message retval = {
|
||||
std::move(std::get<0>(*pload)), // client_id
|
||||
std::move(std::get<4>(*pload)), // user_name
|
||||
std::move(std::get<5>(*pload)), // password
|
||||
keep_alive,
|
||||
flags & 0b00000010, // clean_start
|
||||
std::move(cprops), // connect_props
|
||||
std::move(w) // will
|
||||
};
|
||||
|
||||
return std::optional<connect_message> { std::move(retval) };
|
||||
}
|
||||
|
||||
using connack_message = std::tuple<
|
||||
uint8_t, // session_present
|
||||
uint8_t, // connect reason code
|
||||
connack_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<connack_message> decode_connack(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto connack_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> x3::byte_ >> prop::props_<connack_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, connack_);
|
||||
}
|
||||
|
||||
using publish_message = std::tuple<
|
||||
std::string, // topic
|
||||
std::optional<uint16_t>, // packet_id
|
||||
uint8_t, // dup_e, qos_e, retain_e
|
||||
publish_props, // publish props
|
||||
std::string // payload
|
||||
>;
|
||||
|
||||
inline std::optional<publish_message> decode_publish(
|
||||
uint8_t control_byte, uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
uint8_t flags = control_byte & 0b1111;
|
||||
auto qos = qos_e((flags >> 1) & 0b11);
|
||||
|
||||
auto publish_ = basic::scope_limit_(remain_length)[
|
||||
basic::utf8_ >> basic::if_(qos != qos_e::at_most_once)[x3::big_word] >>
|
||||
x3::attr(flags) >> prop::props_<publish_props> >> basic::verbatim_
|
||||
];
|
||||
return type_parse(it, it + remain_length, publish_);
|
||||
}
|
||||
|
||||
using puback_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
puback_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<puback_message> decode_puback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return puback_message {};
|
||||
auto puback_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<puback_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, puback_);
|
||||
}
|
||||
|
||||
using pubrec_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubrec_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubrec_message> decode_pubrec(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return pubrec_message {};
|
||||
auto pubrec_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubrec_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubrec_);
|
||||
}
|
||||
|
||||
using pubrel_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubrel_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubrel_message> decode_pubrel(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return pubrel_message {};
|
||||
auto pubrel_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubrel_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubrel_);
|
||||
}
|
||||
|
||||
using pubcomp_message = std::tuple<
|
||||
uint8_t, // puback reason code
|
||||
pubcomp_props // props
|
||||
>;
|
||||
|
||||
inline std::optional<pubcomp_message> decode_pubcomp(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return pubcomp_message {};
|
||||
auto pubcomp_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<pubcomp_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, pubcomp_);
|
||||
}
|
||||
|
||||
using subscribe_message = std::tuple<
|
||||
subscribe_props,
|
||||
std::vector<std::tuple<std::string, uint8_t>> // topic filter with opts
|
||||
>;
|
||||
|
||||
inline std::optional<subscribe_message> decode_subscribe(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto subscribe_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<subscribe_props> >> +(basic::utf8_ >> x3::byte_)
|
||||
];
|
||||
return type_parse(it, it + remain_length, subscribe_);
|
||||
}
|
||||
|
||||
using suback_message = std::tuple<
|
||||
suback_props,
|
||||
std::vector<uint8_t> // reason_codes
|
||||
>;
|
||||
|
||||
inline std::optional<suback_message> decode_suback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto suback_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<suback_props> >> +x3::byte_
|
||||
];
|
||||
return type_parse(it, it + remain_length, suback_);
|
||||
}
|
||||
|
||||
using unsubscribe_message = std::tuple<
|
||||
unsubscribe_props,
|
||||
std::vector<std::string> // topics
|
||||
>;
|
||||
|
||||
inline std::optional<unsubscribe_message> decode_unsubscribe(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto unsubscribe_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<unsubscribe_props> >> +basic::utf8_
|
||||
];
|
||||
return type_parse(it, it + remain_length, unsubscribe_);
|
||||
}
|
||||
|
||||
using unsuback_message = std::tuple<
|
||||
unsuback_props,
|
||||
std::vector<uint8_t> // reason_codes
|
||||
>;
|
||||
|
||||
inline std::optional<unsuback_message> decode_unsuback(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
auto unsuback_ = basic::scope_limit_(remain_length)[
|
||||
prop::props_<unsuback_props> >> +x3::byte_
|
||||
];
|
||||
return type_parse(it, it + remain_length, unsuback_);
|
||||
}
|
||||
|
||||
using disconnect_message = std::tuple<
|
||||
uint8_t, // reason_code
|
||||
disconnect_props
|
||||
>;
|
||||
|
||||
inline std::optional<disconnect_message> decode_disconnect(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return disconnect_message {};
|
||||
auto disconnect_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<disconnect_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, disconnect_);
|
||||
}
|
||||
|
||||
using auth_message = std::tuple<
|
||||
uint8_t, // reason_code
|
||||
auth_props
|
||||
>;
|
||||
|
||||
inline std::optional<auth_message> decode_auth(
|
||||
uint32_t remain_length, byte_citer& it
|
||||
) {
|
||||
if (remain_length == 0)
|
||||
return auth_message {};
|
||||
auto auth_ = basic::scope_limit_(remain_length)[
|
||||
x3::byte_ >> prop::props_<auth_props>
|
||||
];
|
||||
return type_parse(it, it + remain_length, auth_);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::decoders
|
||||
|
||||
#endif // !BOOST_MQTT5_MESSAGE_DECODERS_HPP
|
||||
@@ -0,0 +1,432 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
#define BOOST_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/base_encoders.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::encoders {
|
||||
|
||||
template <typename encoder>
|
||||
std::string encode(const encoder& e) {
|
||||
std::string s;
|
||||
s.reserve(e.byte_size());
|
||||
s << e;
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_connect(
|
||||
std::string_view client_id,
|
||||
std::optional<std::string_view> user_name,
|
||||
std::optional<std::string_view> password,
|
||||
uint16_t keep_alive, bool clean_start,
|
||||
const connect_props& props,
|
||||
const std::optional<will>& w
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0001) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto conn_flags_ =
|
||||
basic::flag<1>(user_name) |
|
||||
basic::flag<1>(password) |
|
||||
basic::flag<1>(w, &will::retain) |
|
||||
basic::flag<2>(w, &will::qos) |
|
||||
basic::flag<1>(w) |
|
||||
basic::flag<1>(clean_start) |
|
||||
basic::flag<1>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::utf8_("MQTT") &
|
||||
basic::byte_(uint8_t(5)) &
|
||||
conn_flags_ &
|
||||
basic::int16_(keep_alive) &
|
||||
prop::props_(props);
|
||||
|
||||
auto payload_ =
|
||||
basic::utf8_(client_id) &
|
||||
prop::props_(w) &
|
||||
basic::utf8_(w, &will::topic) &
|
||||
basic::binary_(w, &will::message) &
|
||||
basic::utf8_(user_name) &
|
||||
basic::utf8_(password);
|
||||
|
||||
auto message_body_ = var_header_ & payload_;
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(message_body_.byte_size());
|
||||
|
||||
auto connect_message_ = fixed_header_ & message_body_;
|
||||
|
||||
return encode(connect_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_connack(
|
||||
bool session_present,
|
||||
uint8_t reason_code,
|
||||
const connack_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0010) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::flag<1>(session_present) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto connack_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(connack_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_publish(
|
||||
uint16_t packet_id,
|
||||
std::string_view topic_name,
|
||||
std::string_view payload,
|
||||
qos_e qos, retain_e retain, dup_e dup,
|
||||
const publish_props& props
|
||||
) {
|
||||
|
||||
std::optional<uint16_t> used_packet_id;
|
||||
if (qos != qos_e::at_most_once) used_packet_id.emplace(packet_id);
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0011) |
|
||||
basic::flag<1>(dup) |
|
||||
basic::flag<2>(qos) |
|
||||
basic::flag<1>(retain);
|
||||
|
||||
auto var_header_ =
|
||||
basic::utf8_(topic_name) &
|
||||
basic::int16_(used_packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_body_ = var_header_ & basic::verbatim_(payload);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(message_body_.byte_size());
|
||||
|
||||
auto publish_message_ = fixed_header_ & message_body_;
|
||||
|
||||
return encode(publish_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_puback(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const puback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0100) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto puback_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(puback_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubrec(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubrec_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0101) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubrec_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubrec_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubrel(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubrel_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0110) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubrel_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubrel_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_pubcomp(
|
||||
uint16_t packet_id,
|
||||
uint8_t reason_code,
|
||||
const pubcomp_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b0111) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_may_omit_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto pubcomp_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(pubcomp_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_subscribe(
|
||||
uint16_t packet_id,
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1000) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
size_t payload_size = 0;
|
||||
for (const auto& [topic_filter, _]: topics)
|
||||
payload_size += basic::utf8_(topic_filter).byte_size() + 1;
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + payload_size) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + payload_size);
|
||||
|
||||
for (const auto& [topic_filter, sub_opts]: topics) {
|
||||
auto opts_ =
|
||||
basic::flag<2>(sub_opts.retain_handling) |
|
||||
basic::flag<1>(sub_opts.retain_as_published) |
|
||||
basic::flag<1>(sub_opts.no_local) |
|
||||
basic::flag<2>(sub_opts.max_qos);
|
||||
auto filter_ = basic::utf8_(topic_filter) & opts_;
|
||||
s << filter_;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_suback(
|
||||
uint16_t packet_id,
|
||||
const std::vector<uint8_t>& reason_codes,
|
||||
const suback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1001) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + reason_codes.size());
|
||||
|
||||
for (auto reason_code: reason_codes)
|
||||
s << basic::byte_(reason_code);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_unsubscribe(
|
||||
uint16_t packet_id,
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1010) |
|
||||
basic::flag<4>(0b0010);
|
||||
|
||||
size_t payload_size = 0;
|
||||
for (const auto& topic: topics)
|
||||
payload_size += basic::utf8_(topic).byte_size();
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + payload_size) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + payload_size);
|
||||
|
||||
for (const auto& topic: topics)
|
||||
s << basic::utf8_(topic);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_unsuback(
|
||||
uint16_t packet_id,
|
||||
const std::vector<uint8_t>& reason_codes,
|
||||
const unsuback_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1011) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::int16_(packet_id) &
|
||||
prop::props_(props);
|
||||
|
||||
auto message_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
|
||||
var_header_;
|
||||
|
||||
auto s = encode(message_);
|
||||
s.reserve(s.size() + reason_codes.size());
|
||||
|
||||
for (auto reason_code: reason_codes)
|
||||
s << basic::byte_(reason_code);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string encode_pingreq() {
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1100) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto remaining_len_ =
|
||||
basic::byte_(uint8_t(0));
|
||||
|
||||
auto ping_req_ = packet_type_ & remaining_len_;
|
||||
|
||||
return encode(ping_req_);
|
||||
}
|
||||
|
||||
inline std::string encode_pingresp() {
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1101) |
|
||||
basic::flag<4>(0);
|
||||
|
||||
auto remaining_len_ =
|
||||
basic::byte_(uint8_t(0));
|
||||
|
||||
auto ping_resp_ = packet_type_ & remaining_len_;
|
||||
|
||||
return encode(ping_resp_);
|
||||
}
|
||||
|
||||
inline std::string encode_disconnect(
|
||||
uint8_t reason_code,
|
||||
const disconnect_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1110) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto disconnect_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(disconnect_message_);
|
||||
}
|
||||
|
||||
inline std::string encode_auth(
|
||||
uint8_t reason_code,
|
||||
const auth_props& props
|
||||
) {
|
||||
|
||||
auto packet_type_ =
|
||||
basic::flag<4>(0b1111) |
|
||||
basic::flag<4>(0b0000);
|
||||
|
||||
auto var_header_ =
|
||||
basic::byte_(reason_code) &
|
||||
prop::props_(props);
|
||||
|
||||
auto fixed_header_ =
|
||||
packet_type_ &
|
||||
basic::varlen_(var_header_.byte_size());
|
||||
|
||||
auto auth_message_ = fixed_header_ & var_header_;
|
||||
|
||||
return encode(auth_message_);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::encoders
|
||||
|
||||
#endif // !BOOST_MQTT5_MESSAGE_ENCODERS_HPP
|
||||
@@ -0,0 +1,436 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_CONNECT_OP_HPP
|
||||
#define BOOST_MQTT5_CONNECT_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/async_traits.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
#include <boost/mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_state.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
template <typename Stream, typename LoggerType>
|
||||
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_packet {};
|
||||
struct on_init_auth_data {};
|
||||
struct on_auth_data {};
|
||||
struct on_send_auth {};
|
||||
struct on_complete_auth {};
|
||||
|
||||
Stream& _stream;
|
||||
mqtt_ctx& _ctx;
|
||||
log_invoke<LoggerType>& _log;
|
||||
|
||||
using handler_type = asio::any_completion_handler<void (error_code)>;
|
||||
handler_type _handler;
|
||||
|
||||
std::unique_ptr<std::string> _buffer_ptr;
|
||||
asio::cancellation_state _cancellation_state;
|
||||
|
||||
using endpoint = asio::ip::tcp::endpoint;
|
||||
|
||||
public:
|
||||
template <typename Handler>
|
||||
connect_op(
|
||||
Stream& stream, mqtt_ctx& ctx,
|
||||
log_invoke<LoggerType>& log,
|
||||
Handler&& handler
|
||||
) :
|
||||
_stream(stream), _ctx(ctx), _log(log),
|
||||
_handler(std::forward<Handler>(handler)),
|
||||
_cancellation_state(
|
||||
asio::get_associated_cancellation_slot(_handler),
|
||||
asio::enable_total_cancellation {},
|
||||
asio::enable_total_cancellation {}
|
||||
)
|
||||
{}
|
||||
|
||||
connect_op(connect_op&&) = default;
|
||||
connect_op(const connect_op&) = delete;
|
||||
|
||||
connect_op& operator=(connect_op&&) = default;
|
||||
connect_op& operator=(const connect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return _cancellation_state.slot();
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
void perform(const endpoint& ep, authority_path ap) {
|
||||
lowest_layer(_stream).async_connect(
|
||||
ep,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_connect {}),
|
||||
ep, std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_connect, error_code ec, endpoint ep, authority_path ap
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
_log.at_tcp_connect(ec, ep);
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
do_tls_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void do_tls_handshake(endpoint ep, authority_path ap) {
|
||||
if constexpr (has_tls_handshake<Stream>) {
|
||||
_stream.async_handshake(
|
||||
tls_handshake_type<Stream>::client,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_tls_handshake {}),
|
||||
std::move(ep), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
else if constexpr (
|
||||
has_tls_handshake<next_layer_type<Stream>>
|
||||
) {
|
||||
_stream.next_layer().async_handshake(
|
||||
tls_handshake_type<next_layer_type<Stream>>::client,
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_tls_handshake {}),
|
||||
std::move(ep), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
do_ws_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_tls_handshake, error_code ec, endpoint ep, authority_path ap
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
_log.at_tls_handshake(ec, ep);
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
do_ws_handshake(std::move(ep), std::move(ap));
|
||||
}
|
||||
|
||||
void do_ws_handshake(endpoint ep, authority_path ap) {
|
||||
if constexpr (has_ws_handshake<Stream>)
|
||||
// If you get a compilation error here,
|
||||
// it might be because of a missing <boost/mqtt5/websocket.hpp> include
|
||||
ws_handshake_traits<Stream>::async_handshake(
|
||||
_stream, std::move(ap),
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_ws_handshake {}), ep
|
||||
)
|
||||
);
|
||||
else
|
||||
(*this)(on_ws_handshake {}, error_code {}, ep);
|
||||
}
|
||||
|
||||
void operator()(on_ws_handshake, error_code ec, endpoint ep) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if constexpr (has_ws_handshake<Stream>)
|
||||
_log.at_ws_handshake(ec, ep);
|
||||
|
||||
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 (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
_ctx.co_props[prop::authentication_data] = std::move(data);
|
||||
send_connect();
|
||||
}
|
||||
|
||||
void send_connect() {
|
||||
auto packet = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_connect,
|
||||
_ctx.creds.client_id,
|
||||
_ctx.creds.username, _ctx.creds.password,
|
||||
_ctx.keep_alive, false, _ctx.co_props, _ctx.will_msg
|
||||
);
|
||||
|
||||
auto wire_data = packet.wire_data();
|
||||
|
||||
detail::async_write(
|
||||
_stream, asio::buffer(wire_data),
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_send_connect {}),
|
||||
std::move(packet)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_send_connect, error_code ec, size_t) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
_buffer_ptr = std::make_unique<std::string>(min_packet_sz, char(0));
|
||||
|
||||
auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz);
|
||||
asio::async_read(
|
||||
_stream, buff, asio::transfer_all(),
|
||||
asio::prepend(std::move(*this), on_fixed_header {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_fixed_header, error_code ec, size_t num_read
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
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;
|
||||
auto varlen = decoders::type_parse(
|
||||
varlen_ptr, _buffer_ptr->cend(), decoders::basic::varint_
|
||||
);
|
||||
|
||||
if (!varlen)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
auto varlen_sz = std::distance(_buffer_ptr->cbegin() + 1, varlen_ptr);
|
||||
auto remain_len = *varlen -
|
||||
std::distance(varlen_ptr, _buffer_ptr->cbegin() + num_read);
|
||||
|
||||
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;
|
||||
auto last = first + *varlen;
|
||||
|
||||
asio::async_read(
|
||||
_stream, buff, asio::transfer_all(),
|
||||
asio::prepend(
|
||||
asio::append(std::move(*this), code, first, last),
|
||||
on_read_packet {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_read_packet, error_code ec, size_t, control_code_e code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
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 = static_cast<uint32_t>(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;
|
||||
_ctx.state.session_present(session_present);
|
||||
|
||||
// Unexpected result handling:
|
||||
// - If we don't have a Session State, and we get session_present = true,
|
||||
// we must close the network connection (and restart with a clean start)
|
||||
// - If we have a Session State, and we get session_present = false,
|
||||
// we must discard our Session State
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::connack>(reason_code);
|
||||
if (!rc.has_value()) // reason code not allowed in CONNACK
|
||||
return complete(client::error::malformed_packet);
|
||||
|
||||
_log.at_connack(*rc, session_present, ca_props);
|
||||
if (*rc)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
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 = static_cast<uint32_t>(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 (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
auto wire_data = packet.wire_data();
|
||||
|
||||
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 (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(ec);
|
||||
|
||||
auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz);
|
||||
asio::async_read(
|
||||
_stream, buff, asio::transfer_all(),
|
||||
asio::prepend(std::move(*this), on_fixed_header {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_complete_auth, error_code ec, std::string) {
|
||||
if (is_cancelled())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_cancelled() const {
|
||||
return _cancellation_state.cancelled() != asio::cancellation_type::none;
|
||||
}
|
||||
|
||||
void complete(error_code ec) {
|
||||
_cancellation_state.slot().clear();
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_CONNECT_OP_HPP
|
||||
@@ -0,0 +1,317 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_DISCONNECT_OP_HPP
|
||||
#define BOOST_MQTT5_DISCONNECT_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
#include <boost/mqtt5/detail/topic_validation.hpp>
|
||||
#include <boost/mqtt5/detail/utf8_mqtt.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <
|
||||
typename ClientService,
|
||||
typename DisconnectContext
|
||||
>
|
||||
class disconnect_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_disconnect {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
DisconnectContext _context;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
asio::any_completion_handler<void (error_code)>,
|
||||
typename ClientService::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
template <typename Handler>
|
||||
disconnect_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
DisconnectContext&& context, Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)), _context(std::move(context)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
disconnect_op(disconnect_op&&) = default;
|
||||
disconnect_op(const disconnect_op&) = delete;
|
||||
|
||||
disconnect_op& operator=(disconnect_op&&) = default;
|
||||
disconnect_op& operator=(const disconnect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
error_code ec = validate_disconnect(_context.props);
|
||||
if (ec)
|
||||
return complete_immediate(ec);
|
||||
|
||||
auto disconnect = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_disconnect,
|
||||
static_cast<uint8_t>(_context.reason_code), _context.props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (disconnect.size() > max_packet_size)
|
||||
// drop properties
|
||||
return send_disconnect(control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_disconnect,
|
||||
static_cast<uint8_t>(_context.reason_code), disconnect_props {}
|
||||
));
|
||||
|
||||
send_disconnect(std::move(disconnect));
|
||||
}
|
||||
|
||||
void send_disconnect(control_packet<allocator_type> disconnect) {
|
||||
auto wire_data = disconnect.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::terminal,
|
||||
asio::prepend(
|
||||
std::move(*this),
|
||||
on_disconnect {}, std::move(disconnect)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_disconnect,
|
||||
control_packet<allocator_type> disconnect, error_code ec
|
||||
) {
|
||||
// The connection must be closed even
|
||||
// if we failed to send the DISCONNECT packet
|
||||
// with Reason Code of 0x80 or greater.
|
||||
|
||||
if (
|
||||
ec == asio::error::operation_aborted ||
|
||||
ec == asio::error::no_recovery
|
||||
)
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec == asio::error::try_again) {
|
||||
if (_context.terminal)
|
||||
return send_disconnect(std::move(disconnect));
|
||||
return complete(error_code {});
|
||||
}
|
||||
|
||||
if (_context.terminal) {
|
||||
_svc_ptr->cancel();
|
||||
return complete(error_code {});
|
||||
}
|
||||
|
||||
_svc_ptr->close_stream();
|
||||
_svc_ptr->open_stream();
|
||||
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
static error_code validate_disconnect(const disconnect_props& props) {
|
||||
const auto& reason_string = props[prop::reason_string];
|
||||
if (
|
||||
reason_string &&
|
||||
validate_mqtt_utf8(*reason_string) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
void complete(error_code ec) {
|
||||
_handler.complete(ec);
|
||||
}
|
||||
|
||||
void complete_immediate(error_code ec) {
|
||||
_handler.complete_immediate(ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class terminal_disconnect_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
static constexpr uint8_t seconds = 5;
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
std::unique_ptr<asio::steady_timer> _timer;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
terminal_disconnect_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_timer(new asio::steady_timer(_svc_ptr->get_executor())),
|
||||
_handler(std::move(handler))
|
||||
{}
|
||||
|
||||
terminal_disconnect_op(terminal_disconnect_op&&) = default;
|
||||
terminal_disconnect_op(const terminal_disconnect_op&) = delete;
|
||||
|
||||
terminal_disconnect_op& operator=(terminal_disconnect_op&&) = default;
|
||||
terminal_disconnect_op& operator=(const terminal_disconnect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type = asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename DisconnectContext>
|
||||
void perform(DisconnectContext&& context) {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
auto init_disconnect = [](
|
||||
auto handler, disconnect_ctx ctx,
|
||||
std::shared_ptr<ClientService> svc_ptr
|
||||
) {
|
||||
disconnect_op {
|
||||
std::move(svc_ptr), std::move(ctx), std::move(handler)
|
||||
}.perform();
|
||||
};
|
||||
|
||||
_timer->expires_after(std::chrono::seconds(seconds));
|
||||
|
||||
auto timed_disconnect = asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void (error_code)>(
|
||||
init_disconnect, asio::deferred,
|
||||
std::forward<DisconnectContext>(context), _svc_ptr
|
||||
),
|
||||
_timer->async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_disconnect.async_wait(
|
||||
asioex::wait_for_one(), std::move(*this)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
std::array<std::size_t, 2> /* ord */,
|
||||
error_code disconnect_ec, error_code /* timer_ec */
|
||||
) {
|
||||
std::move(_handler)(disconnect_ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, bool terminal>
|
||||
class initiate_async_disconnect {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_disconnect(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
disconnect_rc_e rc, const disconnect_props& props
|
||||
) {
|
||||
auto ctx = disconnect_ctx { rc, props, terminal };
|
||||
if constexpr (terminal)
|
||||
terminal_disconnect_op { _svc_ptr, std::move(handler) }
|
||||
.perform(std::move(ctx));
|
||||
else
|
||||
disconnect_op { _svc_ptr, std::move(ctx), std::move(handler) }
|
||||
.perform();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, typename CompletionToken>
|
||||
decltype(auto) async_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
std::shared_ptr<ClientService> svc_ptr,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiate_async_disconnect<ClientService, false>(std::move(svc_ptr)), token,
|
||||
reason_code, props
|
||||
);
|
||||
}
|
||||
|
||||
template <typename ClientService, typename CompletionToken>
|
||||
decltype(auto) async_terminal_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
std::shared_ptr<ClientService> svc_ptr,
|
||||
CompletionToken&& token
|
||||
) {
|
||||
using Signature = void (error_code);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiate_async_disconnect<ClientService, true>(std::move(svc_ptr)), token,
|
||||
reason_code, props
|
||||
);
|
||||
}
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_DISCONNECT_HPP
|
||||
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_ENDPOINTS_HPP
|
||||
#define BOOST_MQTT5_ENDPOINTS_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/log_invoke.hpp>
|
||||
|
||||
#include <boost/asio/append.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class resolve_op {
|
||||
struct on_resolve {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
resolve_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
resolve_op(resolve_op&&) = default;
|
||||
resolve_op(const resolve_op&) = delete;
|
||||
|
||||
resolve_op& operator=(resolve_op&&) = default;
|
||||
resolve_op& operator=(const resolve_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
void perform() {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
if (_owner._servers.empty())
|
||||
return complete_post(asio::error::host_not_found, {}, {});
|
||||
|
||||
_owner._current_host++;
|
||||
|
||||
if (_owner._current_host + 1 > static_cast<int>(_owner._servers.size())) {
|
||||
_owner._current_host = -1;
|
||||
return complete_post(asio::error::try_again, {}, {});
|
||||
}
|
||||
|
||||
authority_path ap = _owner._servers[_owner._current_host];
|
||||
|
||||
_owner._connect_timer.expires_after(std::chrono::seconds(5));
|
||||
|
||||
auto timed_resolve = asioex::make_parallel_group(
|
||||
_owner._resolver.async_resolve(ap.host, ap.port, asio::deferred),
|
||||
_owner._connect_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_resolve.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::append(
|
||||
asio::prepend(std::move(*this), on_resolve {}),
|
||||
std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_resolve, std::array<std::size_t, 2> ord,
|
||||
error_code resolve_ec, epoints epts,
|
||||
error_code timer_ec, authority_path ap
|
||||
) {
|
||||
if (
|
||||
(ord[0] == 0 && resolve_ec == asio::error::operation_aborted) ||
|
||||
(ord[0] == 1 && timer_ec == asio::error::operation_aborted)
|
||||
)
|
||||
return complete(asio::error::operation_aborted, {}, {});
|
||||
|
||||
resolve_ec = timer_ec ? resolve_ec : asio::error::timed_out;
|
||||
_owner._log.at_resolve(resolve_ec, ap.host, ap.port, epts);
|
||||
if (!resolve_ec)
|
||||
return complete(error_code {}, std::move(epts), std::move(ap));
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, epoints eps, authority_path ap) {
|
||||
std::move(_handler)(ec, std::move(eps), std::move(ap));
|
||||
}
|
||||
|
||||
void complete_post(error_code ec, epoints eps, authority_path ap) {
|
||||
asio::post(
|
||||
_owner.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(_handler), ec,
|
||||
std::move(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename LoggerType>
|
||||
class endpoints {
|
||||
using logger_type = LoggerType;
|
||||
|
||||
asio::ip::tcp::resolver _resolver;
|
||||
asio::steady_timer& _connect_timer;
|
||||
|
||||
std::vector<authority_path> _servers;
|
||||
|
||||
int _current_host { -1 };
|
||||
|
||||
log_invoke<logger_type>& _log;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
friend class resolve_op;
|
||||
|
||||
template <typename T>
|
||||
static constexpr auto to_(T& arg) {
|
||||
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
|
||||
}
|
||||
|
||||
template <typename T, typename Parser>
|
||||
static constexpr auto as_(Parser&& p){
|
||||
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
endpoints(
|
||||
Executor ex, asio::steady_timer& timer,
|
||||
log_invoke<logger_type>& log
|
||||
) :
|
||||
_resolver(std::move(ex)), _connect_timer(timer),
|
||||
_log(log)
|
||||
{}
|
||||
|
||||
endpoints(const endpoints&) = delete;
|
||||
endpoints& operator=(const endpoints&) = delete;
|
||||
|
||||
void clone_servers(const endpoints& other) {
|
||||
_servers = other._servers;
|
||||
}
|
||||
|
||||
using executor_type = asio::ip::tcp::resolver::executor_type;
|
||||
// NOTE: asio::ip::basic_resolver returns executor by value
|
||||
executor_type get_executor() noexcept {
|
||||
return _resolver.get_executor();
|
||||
}
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_next_endpoint(CompletionToken&& token) {
|
||||
using Signature = void (error_code, epoints, authority_path);
|
||||
|
||||
auto initiation = [](auto handler, endpoints& self) {
|
||||
resolve_op { self, std::move(handler) }.perform();
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this)
|
||||
);
|
||||
}
|
||||
|
||||
void brokers(std::string hosts, uint16_t default_port) {
|
||||
namespace x3 = boost::spirit::x3;
|
||||
|
||||
_servers.clear();
|
||||
|
||||
std::string host, port, path;
|
||||
|
||||
// loosely based on RFC 3986
|
||||
auto unreserved_ = x3::char_("-a-zA-Z_0-9._~");
|
||||
auto digit_ = x3::char_("0-9");
|
||||
auto separator_ = x3::char_(',');
|
||||
|
||||
auto host_ = as_<std::string>(+unreserved_)[to_(host)];
|
||||
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
|
||||
auto path_ = as_<std::string>(x3::char_('/') >> *unreserved_)[to_(path)];
|
||||
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
|
||||
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
|
||||
|
||||
for (auto b = hosts.begin(); b != hosts.end(); ) {
|
||||
host.clear(); port.clear(); path.clear();
|
||||
if (phrase_parse(b, hosts.end(), uri_, x3::eps(false))) {
|
||||
_servers.push_back({
|
||||
std::move(host),
|
||||
port.empty()
|
||||
? std::to_string(default_port)
|
||||
: std::move(port),
|
||||
std::move(path)
|
||||
});
|
||||
}
|
||||
else b = hosts.end();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_ENDPOINTS_HPP
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_PING_OP_HPP
|
||||
#define BOOST_MQTT5_PING_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class ping_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_timer {};
|
||||
struct on_pingreq {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
ping_op(std::shared_ptr<client_service> svc_ptr, Handler&& handler) :
|
||||
_svc_ptr(std::move(svc_ptr)), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
ping_op(ping_op&&) noexcept = default;
|
||||
ping_op(const ping_op&) = delete;
|
||||
|
||||
ping_op& operator=(ping_op&&) noexcept = default;
|
||||
ping_op& operator=(const ping_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->_ping_timer.expires_after(compute_wait_time());
|
||||
_svc_ptr->_ping_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_timer, error_code ec) {
|
||||
if (!_svc_ptr->is_open())
|
||||
return complete();
|
||||
else if (ec == asio::error::operation_aborted)
|
||||
return perform();
|
||||
|
||||
auto pingreq = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(), encoders::encode_pingreq
|
||||
);
|
||||
|
||||
auto wire_data = pingreq.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_pingreq {}),
|
||||
std::move(pingreq)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_pingreq, error_code ec) {
|
||||
if (!ec || ec == asio::error::try_again)
|
||||
return perform();
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
private:
|
||||
duration compute_wait_time() const {
|
||||
auto negotiated_ka = _svc_ptr->negotiated_keep_alive();
|
||||
return negotiated_ka ?
|
||||
std::chrono::seconds(negotiated_ka) :
|
||||
duration(std::numeric_limits<duration::rep>::max());
|
||||
}
|
||||
|
||||
void complete() {
|
||||
return std::move(_handler)();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_PING_OP_HPP
|
||||
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_PUBLISH_REC_OP_HPP
|
||||
#define BOOST_MQTT5_PUBLISH_REC_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class publish_rec_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_puback {};
|
||||
struct on_pubrec {};
|
||||
struct on_pubrel {};
|
||||
struct on_pubcomp {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
decoders::publish_message _message;
|
||||
|
||||
public:
|
||||
explicit publish_rec_op(std::shared_ptr<client_service> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
publish_rec_op(publish_rec_op&&) noexcept = default;
|
||||
publish_rec_op(const publish_rec_op&) = delete;
|
||||
|
||||
publish_rec_op& operator=(publish_rec_op&&) noexcept = default;
|
||||
publish_rec_op& operator=(const publish_rec_op&) = delete;
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(decoders::publish_message message) {
|
||||
auto flags = std::get<2>(message);
|
||||
auto qos_bits = (flags >> 1) & 0b11;
|
||||
if (qos_bits == 0b11)
|
||||
return on_malformed_packet(
|
||||
"Malformed PUBLISH received: QoS bits set to 0b11"
|
||||
);
|
||||
|
||||
auto qos = qos_e(qos_bits);
|
||||
_message = std::move(message);
|
||||
|
||||
if (qos == qos_e::at_most_once)
|
||||
return complete();
|
||||
|
||||
auto packet_id = std::get<1>(_message);
|
||||
|
||||
if (qos == qos_e::at_least_once) {
|
||||
auto puback = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_puback, *packet_id,
|
||||
uint8_t(0), puback_props {}
|
||||
);
|
||||
return send_puback(std::move(puback));
|
||||
}
|
||||
|
||||
// qos == qos_e::exactly_once
|
||||
auto pubrec = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubrec, *packet_id,
|
||||
uint8_t(0), pubrec_props {}
|
||||
);
|
||||
|
||||
return send_pubrec(std::move(pubrec));
|
||||
}
|
||||
|
||||
void send_puback(control_packet<allocator_type> puback) {
|
||||
auto wire_data = puback.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(
|
||||
asio::prepend(std::move(*this), on_puback {}),
|
||||
std::move(puback)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_puback, error_code ec) {
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
void send_pubrec(control_packet<allocator_type> pubrec) {
|
||||
auto wire_data = pubrec.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(std::move(*this), on_pubrec {}, std::move(pubrec))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrec, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
wait_pubrel(packet.packet_id());
|
||||
}
|
||||
|
||||
void wait_pubrel(uint16_t packet_id) {
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubrel, packet_id,
|
||||
asio::prepend(std::move(*this), on_pubrel {}, packet_id)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubrel, uint16_t packet_id,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return wait_pubrel(packet_id);
|
||||
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
auto pubrel = decoders::decode_pubrel(static_cast<uint32_t>(std::distance(first, last)), first);
|
||||
if (!pubrel.has_value()) {
|
||||
on_malformed_packet("Malformed PUBREL received: cannot decode");
|
||||
return wait_pubrel(packet_id);
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubrel;
|
||||
auto rc = to_reason_code<reason_codes::category::pubrel>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBREL received: invalid Reason Code");
|
||||
return wait_pubrel(packet_id);
|
||||
}
|
||||
|
||||
auto pubcomp = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubcomp, packet_id,
|
||||
uint8_t(0), pubcomp_props{}
|
||||
);
|
||||
send_pubcomp(std::move(pubcomp));
|
||||
}
|
||||
|
||||
void send_pubcomp(control_packet<allocator_type> pubcomp) {
|
||||
auto wire_data = pubcomp.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubcomp))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_pubcomp, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return wait_pubrel(packet.packet_id());
|
||||
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
private:
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props,
|
||||
_svc_ptr, asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete() {
|
||||
/* auto rv = */_svc_ptr->channel_store(std::move(_message));
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_PUBLISH_REC_OP_HPP
|
||||
@@ -0,0 +1,508 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
#define BOOST_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
#include <boost/mqtt5/detail/topic_validation.hpp>
|
||||
#include <boost/mqtt5/detail/utf8_mqtt.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using on_publish_signature = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
void (error_code),
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
void (error_code, reason_code, puback_props),
|
||||
void (error_code, reason_code, pubcomp_props)
|
||||
>
|
||||
>;
|
||||
|
||||
template <qos_e qos_type>
|
||||
using on_publish_props_type = std::conditional_t<
|
||||
qos_type == qos_e::at_most_once,
|
||||
void,
|
||||
std::conditional_t<
|
||||
qos_type == qos_e::at_least_once,
|
||||
puback_props,
|
||||
pubcomp_props
|
||||
>
|
||||
>;
|
||||
|
||||
template <typename ClientService, typename Handler, qos_e qos_type>
|
||||
class publish_send_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_publish {};
|
||||
struct on_puback {};
|
||||
struct on_pubrec {};
|
||||
struct on_pubrel {};
|
||||
struct on_pubcomp {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
serial_num_t _serial_num;
|
||||
|
||||
public:
|
||||
publish_send_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
publish_send_op(publish_send_op&&) = default;
|
||||
publish_send_op(const publish_send_op&) = delete;
|
||||
|
||||
publish_send_op& operator=(publish_send_op&&) = default;
|
||||
publish_send_op& operator=(const publish_send_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) {
|
||||
uint16_t packet_id = 0;
|
||||
if constexpr (qos_type != qos_e::at_most_once) {
|
||||
packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_immediate(client::error::pid_overrun, packet_id);
|
||||
}
|
||||
|
||||
auto ec = validate_publish(topic, payload, retain, props);
|
||||
if (ec)
|
||||
return complete_immediate(ec, packet_id);
|
||||
|
||||
_serial_num = _svc_ptr->next_serial_num();
|
||||
|
||||
auto publish = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_publish, packet_id,
|
||||
std::move(topic), std::move(payload),
|
||||
qos_type, retain, dup_e::no, props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (publish.size() > max_packet_size)
|
||||
return complete_immediate(client::error::packet_too_large, packet_id);
|
||||
|
||||
send_publish(std::move(publish));
|
||||
}
|
||||
|
||||
void send_publish(control_packet<allocator_type> publish) {
|
||||
auto wire_data = publish.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
_serial_num,
|
||||
send_flag::throttled * (qos_type != qos_e::at_most_once),
|
||||
asio::prepend(std::move(*this), on_publish {}, std::move(publish))
|
||||
);
|
||||
}
|
||||
|
||||
void resend_publish(control_packet<allocator_type> publish) {
|
||||
if (_handler.cancelled() != asio::cancellation_type_t::none)
|
||||
return complete(
|
||||
asio::error::operation_aborted, publish.packet_id()
|
||||
);
|
||||
send_publish(std::move(publish));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_publish, control_packet<allocator_type> publish,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return resend_publish(std::move(publish));
|
||||
|
||||
if constexpr (qos_type == qos_e::at_most_once)
|
||||
return complete(ec);
|
||||
|
||||
else {
|
||||
auto packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
if constexpr (qos_type == qos_e::at_least_once)
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::puback, packet_id,
|
||||
asio::prepend(
|
||||
std::move(*this), on_puback {}, std::move(publish)
|
||||
)
|
||||
);
|
||||
|
||||
else if constexpr (qos_type == qos_e::exactly_once)
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubrec, packet_id,
|
||||
asio::prepend(
|
||||
std::move(*this), on_pubrec {}, std::move(publish)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::at_least_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_puback, control_packet<allocator_type> publish,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
|
||||
uint16_t packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto puback = decoders::decode_puback(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!puback.has_value()) {
|
||||
on_malformed_packet("Malformed PUBACK: cannot decode");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *puback;
|
||||
auto rc = to_reason_code<reason_codes::category::puback>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBACK: invalid Reason Code");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
complete(ec, packet_id, *rc, std::move(props));
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::exactly_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_pubrec, control_packet<allocator_type> publish,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
|
||||
uint16_t packet_id = publish.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto pubrec = decoders::decode_pubrec(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!pubrec.has_value()) {
|
||||
on_malformed_packet("Malformed PUBREC: cannot decode");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubrec;
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::pubrec>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBREC: invalid Reason Code");
|
||||
return resend_publish(std::move(publish.set_dup()));
|
||||
}
|
||||
|
||||
if (*rc)
|
||||
return complete(ec, packet_id, *rc);
|
||||
|
||||
auto pubrel = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_pubrel, packet_id,
|
||||
0, pubrel_props {}
|
||||
);
|
||||
|
||||
send_pubrel(std::move(pubrel), false);
|
||||
}
|
||||
|
||||
void send_pubrel(control_packet<allocator_type> pubrel, bool throttled) {
|
||||
auto wire_data = pubrel.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
_serial_num,
|
||||
(send_flag::throttled * throttled) | send_flag::prioritized,
|
||||
asio::prepend(std::move(*this), on_pubrel {}, std::move(pubrel))
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::exactly_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_pubrel, control_packet<allocator_type> pubrel, error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
|
||||
uint16_t packet_id = pubrel.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::pubcomp, packet_id,
|
||||
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubrel))
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::exactly_once, bool> = true
|
||||
>
|
||||
void operator()(
|
||||
on_pubcomp, control_packet<allocator_type> pubrel,
|
||||
error_code ec,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
|
||||
uint16_t packet_id = pubrel.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto pubcomp = decoders::decode_pubcomp(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!pubcomp.has_value()) {
|
||||
on_malformed_packet("Malformed PUBCOMP: cannot decode");
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
}
|
||||
|
||||
auto& [reason_code, props] = *pubcomp;
|
||||
|
||||
auto rc = to_reason_code<reason_codes::category::pubcomp>(reason_code);
|
||||
if (!rc) {
|
||||
on_malformed_packet("Malformed PUBCOMP: invalid Reason Code");
|
||||
return send_pubrel(std::move(pubrel), true);
|
||||
}
|
||||
|
||||
return complete(ec, pubrel.packet_id(), *rc);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
error_code validate_publish(
|
||||
const std::string& topic, const std::string& payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) const {
|
||||
constexpr uint8_t default_retain_available = 1;
|
||||
constexpr uint8_t default_maximum_qos = 2;
|
||||
constexpr uint8_t default_payload_format_ind = 0;
|
||||
|
||||
auto topic_name_valid = props[prop::topic_alias].has_value() ?
|
||||
validate_topic_alias_name(topic) == validation_result::valid :
|
||||
validate_topic_name(topic) == validation_result::valid
|
||||
;
|
||||
|
||||
if (!topic_name_valid)
|
||||
return client::error::invalid_topic;
|
||||
|
||||
auto max_qos = _svc_ptr->connack_property(prop::maximum_qos)
|
||||
.value_or(default_maximum_qos);
|
||||
auto retain_available = _svc_ptr->connack_property(prop::retain_available)
|
||||
.value_or(default_retain_available);
|
||||
|
||||
if (uint8_t(qos_type) > max_qos)
|
||||
return client::error::qos_not_supported;
|
||||
|
||||
if (retain_available == 0 && retain == retain_e::yes)
|
||||
return client::error::retain_not_available;
|
||||
|
||||
auto payload_format_ind = props[prop::payload_format_indicator]
|
||||
.value_or(default_payload_format_ind);
|
||||
if (
|
||||
payload_format_ind == 1 &&
|
||||
validate_mqtt_utf8(payload) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
return validate_props(props);
|
||||
}
|
||||
|
||||
error_code validate_props(const publish_props& props) const {
|
||||
constexpr uint16_t default_topic_alias_max = 0;
|
||||
|
||||
const auto& topic_alias = props[prop::topic_alias];
|
||||
if (topic_alias) {
|
||||
auto topic_alias_max = _svc_ptr->connack_property(prop::topic_alias_maximum)
|
||||
.value_or(default_topic_alias_max);
|
||||
|
||||
if (topic_alias_max == 0 || *topic_alias > topic_alias_max)
|
||||
return client::error::topic_alias_maximum_reached;
|
||||
if (*topic_alias == 0 )
|
||||
return client::error::malformed_packet;
|
||||
}
|
||||
|
||||
const auto& response_topic = props[prop::response_topic];
|
||||
if (
|
||||
response_topic &&
|
||||
validate_topic_name(*response_topic) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
|
||||
if (!props[prop::subscription_identifier].empty())
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& content_type = props[prop::content_type];
|
||||
if (
|
||||
content_type &&
|
||||
validate_mqtt_utf8(*content_type) != validation_result::valid
|
||||
)
|
||||
return client::error::malformed_packet;
|
||||
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::at_most_once, bool> = true
|
||||
>
|
||||
void complete(error_code ec, uint16_t = 0) {
|
||||
_handler.complete(ec);
|
||||
}
|
||||
|
||||
template <
|
||||
qos_e q = qos_type,
|
||||
std::enable_if_t<q == qos_e::at_most_once, bool> = true
|
||||
>
|
||||
void complete_immediate(error_code ec, uint16_t) {
|
||||
_handler.complete_immediate(ec);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Props = on_publish_props_type<qos_type>,
|
||||
std::enable_if_t<
|
||||
std::is_same_v<Props, puback_props> ||
|
||||
std::is_same_v<Props, pubcomp_props>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
reason_code rc = reason_codes::empty, Props&& props = Props {}
|
||||
) {
|
||||
_svc_ptr->free_pid(packet_id, true);
|
||||
_handler.complete(ec, rc, std::forward<Props>(props));
|
||||
}
|
||||
|
||||
template <
|
||||
typename Props = on_publish_props_type<qos_type>,
|
||||
std::enable_if_t<
|
||||
std::is_same_v<Props, puback_props> ||
|
||||
std::is_same_v<Props, pubcomp_props>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
void complete_immediate(error_code ec, uint16_t packet_id) {
|
||||
if (packet_id != 0)
|
||||
_svc_ptr->free_pid(packet_id, false);
|
||||
_handler.complete_immediate(ec, reason_codes::empty, Props {});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService, qos_e qos_type>
|
||||
class initiate_async_publish {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_publish(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props
|
||||
) {
|
||||
detail::publish_send_op<ClientService, Handler, qos_type> {
|
||||
_svc_ptr, std::move(handler)
|
||||
}.perform(
|
||||
std::move(topic), std::move(payload), retain, props
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_PUBLISH_SEND_OP_HPP
|
||||
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_RE_AUTH_OP_hpp
|
||||
#define BOOST_MQTT5_RE_AUTH_OP_hpp
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/any_authenticator.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService>
|
||||
class re_auth_op {
|
||||
using client_service = ClientService;
|
||||
struct on_auth_data {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
any_authenticator& _auth;
|
||||
|
||||
public:
|
||||
explicit re_auth_op(std::shared_ptr<client_service> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_auth(_svc_ptr->_stream_context.mqtt_context().authenticator)
|
||||
{}
|
||||
|
||||
re_auth_op(re_auth_op&&) noexcept = default;
|
||||
re_auth_op(const re_auth_op&) = delete;
|
||||
|
||||
re_auth_op& operator=(re_auth_op&&) noexcept = default;
|
||||
re_auth_op& operator=(const re_auth_op&) = delete;
|
||||
|
||||
using allocator_type = asio::recycling_allocator<void>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return allocator_type {};
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
if (_auth.method().empty())
|
||||
return;
|
||||
|
||||
auto auth_step = auth_step_e::client_initial;
|
||||
return _auth.async_auth(
|
||||
auth_step, "",
|
||||
asio::prepend(std::move(*this), on_auth_data {}, auth_step)
|
||||
);
|
||||
}
|
||||
|
||||
void perform(decoders::auth_message auth_message) {
|
||||
if (_auth.method().empty())
|
||||
return on_auth_fail(
|
||||
"Unexpected AUTH received",
|
||||
disconnect_rc_e::protocol_error
|
||||
);
|
||||
|
||||
const auto& [rc, props] = auth_message;
|
||||
auto auth_rc = to_reason_code<reason_codes::category::auth>(rc);
|
||||
if (!auth_rc.has_value())
|
||||
return on_auth_fail(
|
||||
"Malformed AUTH received: bad reason code",
|
||||
disconnect_rc_e::malformed_packet
|
||||
);
|
||||
|
||||
const auto& server_auth_method = props[prop::authentication_method];
|
||||
if (!server_auth_method || *server_auth_method != _auth.method())
|
||||
return on_auth_fail(
|
||||
"Malformed AUTH received: wrong authentication method",
|
||||
disconnect_rc_e::protocol_error
|
||||
);
|
||||
|
||||
auto auth_step = auth_rc == reason_codes::success ?
|
||||
auth_step_e::server_final : auth_step_e::server_challenge;
|
||||
auto data = props[prop::authentication_data].value_or("");
|
||||
|
||||
return _auth.async_auth(
|
||||
auth_step, std::move(data),
|
||||
asio::prepend(std::move(*this), on_auth_data {}, auth_step)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_auth_data, auth_step_e auth_step, error_code ec, std::string data
|
||||
) {
|
||||
if (ec)
|
||||
return on_auth_fail(
|
||||
"Re-authentication: authentication fail",
|
||||
disconnect_rc_e::unspecified_error
|
||||
);
|
||||
|
||||
if (auth_step == auth_step_e::server_final)
|
||||
return;
|
||||
|
||||
auth_props props;
|
||||
props[prop::authentication_method] = _auth.method();
|
||||
props[prop::authentication_data] = std::move(data);
|
||||
auto rc = auth_step == auth_step_e::client_initial ?
|
||||
reason_codes::reauthenticate : reason_codes::continue_authentication;
|
||||
|
||||
auto packet = control_packet<allocator_type>::of(
|
||||
no_pid, get_allocator(),
|
||||
encoders::encode_auth,
|
||||
rc.value(), props
|
||||
);
|
||||
|
||||
auto wire_data = packet.wire_data();
|
||||
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::consign(asio::detached, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
void on_auth_fail(std::string message, disconnect_rc_e reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = std::move(message);
|
||||
|
||||
async_disconnect(reason, props, _svc_ptr, asio::detached);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_RE_AUTH_OP_HPP
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_READ_MESSAGE_OP_HPP
|
||||
#define BOOST_MQTT5_READ_MESSAGE_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
#include <boost/mqtt5/impl/publish_rec_op.hpp>
|
||||
#include <boost/mqtt5/impl/re_auth_op.hpp>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/recycling_allocator.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class read_message_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_message {};
|
||||
struct on_disconnect {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
read_message_op(std::shared_ptr<client_service> svc_ptr, Handler&& handler)
|
||||
: _svc_ptr(std::move(svc_ptr)), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
read_message_op(read_message_op&&) noexcept = default;
|
||||
read_message_op(const read_message_op&) = delete;
|
||||
|
||||
read_message_op& operator=(read_message_op&&) noexcept = default;
|
||||
read_message_op& operator=(const read_message_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->async_assemble(
|
||||
asio::prepend(std::move(*this), on_message {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_message, error_code ec,
|
||||
uint8_t control_code,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == client::error::malformed_packet)
|
||||
return on_malformed_packet(
|
||||
"Malformed Packet received from the Server"
|
||||
);
|
||||
|
||||
if (ec == asio::error::no_recovery)
|
||||
_svc_ptr->cancel();
|
||||
|
||||
if (ec)
|
||||
return complete();
|
||||
|
||||
dispatch(control_code, first, last);
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
if (ec)
|
||||
return complete();
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void dispatch(
|
||||
uint8_t control_byte,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
auto code = control_code_e(control_byte & 0b11110000);
|
||||
|
||||
switch (code) {
|
||||
case control_code_e::publish: {
|
||||
auto msg = decoders::decode_publish(
|
||||
control_byte, static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!msg.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed PUBLISH received: cannot decode"
|
||||
);
|
||||
|
||||
publish_rec_op { _svc_ptr }.perform(std::move(*msg));
|
||||
}
|
||||
break;
|
||||
case control_code_e::disconnect: {
|
||||
auto rv = decoders::decode_disconnect(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!rv.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed DISCONNECT received: cannot decode"
|
||||
);
|
||||
|
||||
const auto& [rc, props] = *rv;
|
||||
_svc_ptr->log().at_disconnect(
|
||||
to_reason_code<reason_codes::category::disconnect>(rc)
|
||||
.value_or(reason_codes::unspecified_error),
|
||||
props
|
||||
);
|
||||
_svc_ptr->close_stream();
|
||||
_svc_ptr->open_stream();
|
||||
}
|
||||
break;
|
||||
case control_code_e::auth: {
|
||||
auto rv = decoders::decode_auth(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!rv.has_value())
|
||||
return on_malformed_packet(
|
||||
"Malformed AUTH received: cannot decode"
|
||||
);
|
||||
|
||||
re_auth_op { _svc_ptr }.perform(std::move(*rv));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
auto svc_ptr = _svc_ptr; // copy before this is moved
|
||||
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, svc_ptr,
|
||||
asio::prepend(std::move(*this), on_disconnect {})
|
||||
);
|
||||
}
|
||||
|
||||
void complete() {
|
||||
return std::move(_handler)();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_READ_MESSAGE_OP_HPP
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_READ_OP_HPP
|
||||
#define BOOST_MQTT5_READ_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class read_op {
|
||||
struct on_read {};
|
||||
struct on_reconnect {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
read_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
read_op(read_op&&) = default;
|
||||
read_op(const read_op&) = delete;
|
||||
|
||||
read_op& operator=(read_op&&) = default;
|
||||
read_op& operator=(const read_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(
|
||||
const BufferType& buffer, duration wait_for
|
||||
) {
|
||||
auto stream_ptr = _owner._stream_ptr;
|
||||
|
||||
if (_owner.was_connected()) {
|
||||
_owner._read_timer.expires_after(wait_for);
|
||||
|
||||
auto timed_read = asioex::make_parallel_group(
|
||||
stream_ptr->async_read_some(buffer, asio::deferred),
|
||||
_owner._read_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_read.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::prepend(std::move(*this), on_read {}, stream_ptr)
|
||||
);
|
||||
}
|
||||
else
|
||||
asio::post(
|
||||
_owner.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_read {}, stream_ptr,
|
||||
std::array<size_t, 2> { 0, 1 },
|
||||
asio::error::not_connected, 0, error_code {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_read, typename Owner::stream_ptr stream_ptr,
|
||||
std::array<std::size_t, 2> ord, error_code read_ec, size_t bytes_read,
|
||||
error_code
|
||||
) {
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted, bytes_read);
|
||||
|
||||
error_code ec = ord[0] == 1 ? asio::error::timed_out : read_ec;
|
||||
bytes_read = ord[0] == 0 ? bytes_read : 0;
|
||||
|
||||
if (!ec)
|
||||
return complete(ec, bytes_read);
|
||||
|
||||
// websocket returns operation_aborted if disconnected
|
||||
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
|
||||
return _owner.async_reconnect(
|
||||
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
|
||||
);
|
||||
|
||||
return complete(asio::error::no_recovery, bytes_read);
|
||||
}
|
||||
|
||||
void operator()(on_reconnect, error_code ec) {
|
||||
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
|
||||
ec = asio::error::try_again;
|
||||
|
||||
return complete(ec, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, size_t bytes_read) {
|
||||
std::move(_handler)(ec, bytes_read);
|
||||
}
|
||||
|
||||
static bool should_reconnect(error_code ec) {
|
||||
using namespace asio::error;
|
||||
// note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved)
|
||||
return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */
|
||||
ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */
|
||||
ec == connection_aborted || ec == not_connected ||
|
||||
ec == timed_out || ec == connection_reset ||
|
||||
ec == broken_pipe || ec == asio::error::eof;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_READ_OP_HPP
|
||||
@@ -0,0 +1,252 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_RECONNECT_OP_HPP
|
||||
#define BOOST_MQTT5_RECONNECT_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/async_traits.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/connect_op.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/random/linear_congruential.hpp>
|
||||
#include <boost/random/uniform_smallint.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
class exponential_backoff {
|
||||
int _curr_exp { 0 };
|
||||
|
||||
static constexpr int _base_mulptilier = 1000;
|
||||
static constexpr int _max_exp = 4;
|
||||
|
||||
// sizeof(_generator) = 8
|
||||
boost::random::rand48 _generator { uint32_t(std::time(0)) };
|
||||
boost::random::uniform_smallint<> _distribution { -500, 500 };
|
||||
public:
|
||||
exponential_backoff() = default;
|
||||
|
||||
duration generate() {
|
||||
int exponent = _curr_exp < _max_exp ? _curr_exp++ : _max_exp;
|
||||
int base = 1 << exponent;
|
||||
return std::chrono::milliseconds(
|
||||
base * _base_mulptilier + _distribution(_generator) /* noise */
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename Owner>
|
||||
class reconnect_op {
|
||||
struct on_locked {};
|
||||
struct on_next_endpoint {};
|
||||
struct on_connect {};
|
||||
struct on_backoff {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = asio::any_completion_handler<void (error_code)>;
|
||||
handler_type _handler;
|
||||
|
||||
std::unique_ptr<std::string> _buffer_ptr;
|
||||
|
||||
exponential_backoff _generator;
|
||||
|
||||
using endpoint = asio::ip::tcp::endpoint;
|
||||
using epoints = asio::ip::tcp::resolver::results_type;
|
||||
|
||||
public:
|
||||
template <typename Handler>
|
||||
reconnect_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
reconnect_op(reconnect_op&&) = default;
|
||||
reconnect_op(const reconnect_op&) = delete;
|
||||
|
||||
reconnect_op& operator=(reconnect_op&&) = default;
|
||||
reconnect_op& operator=(const reconnect_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using cancellation_slot_type =
|
||||
asio::associated_cancellation_slot_t<handler_type>;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept {
|
||||
return asio::get_associated_cancellation_slot(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
void perform(typename Owner::stream_ptr s) {
|
||||
_owner._conn_mtx.lock(
|
||||
asio::prepend(std::move(*this), on_locked {}, s)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_locked, typename Owner::stream_ptr s, error_code ec) {
|
||||
if (ec == asio::error::operation_aborted)
|
||||
// cancelled without acquiring the lock (by calling client.cancel())
|
||||
return std::move(_handler)(ec);
|
||||
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (s != _owner._stream_ptr)
|
||||
return complete(asio::error::try_again);
|
||||
|
||||
do_reconnect();
|
||||
}
|
||||
|
||||
void do_reconnect() {
|
||||
_owner._endpoints.async_next_endpoint(
|
||||
asio::prepend(std::move(*this), on_next_endpoint {})
|
||||
);
|
||||
}
|
||||
|
||||
void backoff_and_reconnect() {
|
||||
_owner._connect_timer.expires_after(_generator.generate());
|
||||
_owner._connect_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_backoff {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_backoff, error_code ec) {
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
do_reconnect();
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_next_endpoint, error_code ec,
|
||||
epoints eps, authority_path ap
|
||||
) {
|
||||
// the three error codes below are the only possible codes
|
||||
// that may be returned from async_next_endpont
|
||||
|
||||
if (ec == asio::error::operation_aborted || !_owner.is_open())
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
if (ec == asio::error::try_again)
|
||||
return backoff_and_reconnect();
|
||||
|
||||
if (ec == asio::error::host_not_found)
|
||||
return complete(asio::error::no_recovery);
|
||||
|
||||
connect(eps.cbegin(), std::move(ap));
|
||||
}
|
||||
|
||||
void connect(epoints::const_iterator eps, authority_path ap) {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
const auto& ep = eps->endpoint();
|
||||
auto sptr = _owner.construct_and_open_next_layer(ep.protocol());
|
||||
|
||||
if constexpr (has_tls_context<typename Owner::stream_context_type>)
|
||||
setup_tls_sni(
|
||||
ap, _owner._stream_context.tls_context(), *sptr
|
||||
);
|
||||
|
||||
// wait max 5 seconds for the connect (handshake) op to finish
|
||||
_owner._connect_timer.expires_after(std::chrono::seconds(5));
|
||||
|
||||
auto init_connect = [](
|
||||
auto handler, typename Owner::stream_type& stream,
|
||||
mqtt_ctx& context, log_invoke<typename Owner::logger_type>& log,
|
||||
endpoint ep, authority_path ap
|
||||
) {
|
||||
connect_op { stream, context, log, std::move(handler) }
|
||||
.perform(ep, std::move(ap));
|
||||
};
|
||||
|
||||
auto timed_connect = asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void(error_code)>(
|
||||
init_connect, asio::deferred, std::ref(*sptr),
|
||||
std::ref(_owner._stream_context.mqtt_context()),
|
||||
std::ref(_owner.log()),
|
||||
ep, ap
|
||||
),
|
||||
_owner._connect_timer.async_wait(asio::deferred)
|
||||
);
|
||||
|
||||
timed_connect.async_wait(
|
||||
asioex::wait_for_one(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_connect {},
|
||||
std::move(sptr), std::move(eps), std::move(ap)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_connect,
|
||||
typename Owner::stream_ptr sptr, epoints::const_iterator eps, authority_path ap,
|
||||
std::array<std::size_t, 2> ord,
|
||||
error_code connect_ec, error_code timer_ec
|
||||
) {
|
||||
// connect_ec may be any of:
|
||||
// 1) async_connect error codes
|
||||
// 2) async_handshake (TLS) error codes
|
||||
// 3) async_handshake (WebSocket) error codes
|
||||
// 4) async_write error codes
|
||||
// 5) async_read error codes
|
||||
// 5) client::error::malformed_packet
|
||||
if (
|
||||
(ord[0] == 0 && connect_ec == asio::error::operation_aborted) ||
|
||||
(ord[0] == 1 && timer_ec == asio::error::operation_aborted) ||
|
||||
!_owner.is_open()
|
||||
)
|
||||
return complete(asio::error::operation_aborted);
|
||||
|
||||
// retry for operation timed out and any other error_code or client::error::malformed_packet
|
||||
if (ord[0] == 1 || connect_ec) {
|
||||
// if the hostname resolved into more endpoints, try the next one
|
||||
if (++eps != epoints::const_iterator())
|
||||
return connect(std::move(eps), std::move(ap));
|
||||
// try next server
|
||||
return do_reconnect();
|
||||
}
|
||||
|
||||
_owner.replace_next_layer(std::move(sptr));
|
||||
complete(error_code {});
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec) {
|
||||
_owner._conn_mtx.unlock();
|
||||
std::move(_handler)(ec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_RECONNECT_OP_HPP
|
||||
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_REPLIES_HPP
|
||||
#define BOOST_MQTT5_REPLIES_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class replies {
|
||||
public:
|
||||
using executor_type = asio::any_io_executor;
|
||||
private:
|
||||
using Signature = void (error_code, byte_citer, byte_citer);
|
||||
|
||||
static constexpr auto max_reply_time = std::chrono::seconds(20);
|
||||
|
||||
class reply_handler {
|
||||
asio::any_completion_handler<Signature> _handler;
|
||||
control_code_e _code;
|
||||
uint16_t _packet_id;
|
||||
std::chrono::time_point<std::chrono::system_clock> _ts;
|
||||
public:
|
||||
template <typename H>
|
||||
reply_handler(control_code_e code, uint16_t pid, H&& handler) :
|
||||
_handler(std::forward<H>(handler)), _code(code), _packet_id(pid),
|
||||
_ts(std::chrono::system_clock::now())
|
||||
{}
|
||||
|
||||
reply_handler(reply_handler&&) = default;
|
||||
reply_handler(const reply_handler&) = delete;
|
||||
|
||||
reply_handler& operator=(reply_handler&&) = default;
|
||||
reply_handler& operator=(const reply_handler&) = delete;
|
||||
|
||||
void complete(
|
||||
error_code ec,
|
||||
byte_citer first = byte_citer {}, byte_citer last = byte_citer {}
|
||||
) {
|
||||
std::move(_handler)(ec, first, last);
|
||||
}
|
||||
|
||||
void complete_post(const executor_type& ex, error_code ec) {
|
||||
asio::post(
|
||||
ex,
|
||||
asio::prepend(
|
||||
std::move(_handler), ec, byte_citer {}, byte_citer {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t packet_id() const noexcept {
|
||||
return _packet_id;
|
||||
}
|
||||
|
||||
control_code_e code() const noexcept {
|
||||
return _code;
|
||||
}
|
||||
|
||||
auto time() const noexcept {
|
||||
return _ts;
|
||||
}
|
||||
};
|
||||
|
||||
executor_type _ex;
|
||||
|
||||
using handlers = std::vector<reply_handler>;
|
||||
handlers _handlers;
|
||||
|
||||
struct fast_reply {
|
||||
control_code_e code;
|
||||
uint16_t packet_id;
|
||||
std::unique_ptr<std::string> packet;
|
||||
};
|
||||
using fast_replies = std::vector<fast_reply>;
|
||||
fast_replies _fast_replies;
|
||||
|
||||
public:
|
||||
template <typename Executor>
|
||||
explicit replies(Executor ex) : _ex(std::move(ex)) {}
|
||||
|
||||
replies(replies&&) = default;
|
||||
replies(const replies&) = delete;
|
||||
|
||||
replies& operator=(replies&&) = default;
|
||||
replies& operator=(const replies&) = delete;
|
||||
|
||||
template <typename CompletionToken>
|
||||
decltype(auto) async_wait_reply(
|
||||
control_code_e code, uint16_t packet_id, CompletionToken&& token
|
||||
) {
|
||||
auto dup_handler_ptr = find_handler(code, packet_id);
|
||||
if (dup_handler_ptr != _handlers.end()) {
|
||||
dup_handler_ptr->complete_post(_ex, asio::error::operation_aborted);
|
||||
_handlers.erase(dup_handler_ptr);
|
||||
}
|
||||
|
||||
auto freply = find_fast_reply(code, packet_id);
|
||||
|
||||
if (freply == _fast_replies.end()) {
|
||||
auto initiation = [](
|
||||
auto handler, replies& self,
|
||||
control_code_e code, uint16_t packet_id
|
||||
) {
|
||||
self._handlers.emplace_back(
|
||||
code, packet_id, std::move(handler)
|
||||
);
|
||||
};
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::ref(*this), code, packet_id
|
||||
);
|
||||
}
|
||||
|
||||
auto fdata = std::move(*freply);
|
||||
_fast_replies.erase(freply);
|
||||
|
||||
auto initiation = [](
|
||||
auto handler, std::unique_ptr<std::string> packet,
|
||||
const executor_type& ex
|
||||
) {
|
||||
byte_citer first = packet->cbegin();
|
||||
byte_citer last = packet->cend();
|
||||
|
||||
asio::post(
|
||||
ex,
|
||||
asio::consign(
|
||||
asio::prepend(
|
||||
std::move(handler), error_code {}, first, last
|
||||
),
|
||||
std::move(packet)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
initiation, token, std::move(fdata.packet), _ex
|
||||
);
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
error_code ec, control_code_e code, uint16_t packet_id,
|
||||
byte_citer first, byte_citer last
|
||||
) {
|
||||
auto handler_ptr = find_handler(code, packet_id);
|
||||
|
||||
if (handler_ptr == _handlers.end()) {
|
||||
_fast_replies.push_back({
|
||||
code, packet_id,
|
||||
std::make_unique<std::string>(first, last)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = std::move(*handler_ptr);
|
||||
_handlers.erase(handler_ptr);
|
||||
handler.complete(ec, first, last);
|
||||
}
|
||||
|
||||
void resend_unanswered() {
|
||||
auto ua = std::move(_handlers);
|
||||
for (auto& h : ua)
|
||||
h.complete(asio::error::try_again);
|
||||
}
|
||||
|
||||
void cancel_unanswered() {
|
||||
auto ua = std::move(_handlers);
|
||||
for (auto& h : ua)
|
||||
h.complete_post(_ex, asio::error::operation_aborted);
|
||||
}
|
||||
|
||||
bool any_expired() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
return std::any_of(
|
||||
_handlers.begin(), _handlers.end(),
|
||||
[now](const auto& h) {
|
||||
return now - h.time() > max_reply_time;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void clear_fast_replies() {
|
||||
_fast_replies.clear();
|
||||
}
|
||||
|
||||
void clear_pending_pubrels() {
|
||||
for (auto it = _handlers.begin(); it != _handlers.end();) {
|
||||
if (it->code() == control_code_e::pubrel) {
|
||||
it->complete(asio::error::operation_aborted);
|
||||
it = _handlers.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
handlers::iterator find_handler(control_code_e code, uint16_t packet_id) {
|
||||
return std::find_if(
|
||||
_handlers.begin(), _handlers.end(),
|
||||
[code, packet_id](const auto& h) {
|
||||
return h.code() == code && h.packet_id() == packet_id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fast_replies::iterator find_fast_reply(
|
||||
control_code_e code, uint16_t packet_id
|
||||
) {
|
||||
return std::find_if(
|
||||
_fast_replies.begin(), _fast_replies.end(),
|
||||
[code, packet_id](const auto& f) {
|
||||
return f.code == code && f.packet_id == packet_id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_REPLIES_HPP
|
||||
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_RUN_OP_HPP
|
||||
#define BOOST_MQTT5_RUN_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/ping_op.hpp>
|
||||
#include <boost/mqtt5/impl/read_message_op.hpp>
|
||||
#include <boost/mqtt5/impl/sentry_op.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class run_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
run_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
run_op(run_op&&) = default;
|
||||
run_op(const run_op&) = delete;
|
||||
|
||||
run_op& operator=(run_op&&) = default;
|
||||
run_op& operator=(const run_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
namespace asioex = boost::asio::experimental;
|
||||
|
||||
_svc_ptr->_stream.open();
|
||||
_svc_ptr->_rec_channel.reset();
|
||||
|
||||
auto init_read_message_op = [](
|
||||
auto handler, std::shared_ptr<client_service> svc_ptr
|
||||
) {
|
||||
return read_message_op { std::move(svc_ptr), std::move(handler) }
|
||||
.perform();
|
||||
};
|
||||
|
||||
auto init_ping_op = [](
|
||||
auto handler, std::shared_ptr<client_service> svc_ptr
|
||||
) {
|
||||
return ping_op { std::move(svc_ptr), std::move(handler) }
|
||||
.perform();
|
||||
};
|
||||
|
||||
auto init_senty_op = [](
|
||||
auto handler, std::shared_ptr<client_service> svc_ptr
|
||||
) {
|
||||
return sentry_op { std::move(svc_ptr), std::move(handler) }
|
||||
.perform();
|
||||
};
|
||||
|
||||
asioex::make_parallel_group(
|
||||
asio::async_initiate<const asio::deferred_t, void ()>(
|
||||
init_read_message_op, asio::deferred, _svc_ptr
|
||||
),
|
||||
asio::async_initiate<const asio::deferred_t, void ()>(
|
||||
init_ping_op, asio::deferred, _svc_ptr
|
||||
),
|
||||
asio::async_initiate<const asio::deferred_t, void ()>(
|
||||
init_senty_op, asio::deferred, _svc_ptr
|
||||
)
|
||||
).async_wait(asioex::wait_for_all(), std::move(*this));
|
||||
}
|
||||
|
||||
void operator()(std::array<std::size_t, 3> /* ord */) {
|
||||
_handler.complete(asio::error::operation_aborted);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class initiate_async_run {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_run(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(Handler&& handler) {
|
||||
run_op<ClientService, Handler> {
|
||||
_svc_ptr, std::move(handler)
|
||||
}.perform();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_RUN_OP_HPP
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_SENTRY_OP_HPP
|
||||
#define BOOST_MQTT5_SENTRY_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class sentry_op {
|
||||
using client_service = ClientService;
|
||||
using handler_type = Handler;
|
||||
|
||||
struct on_timer {};
|
||||
struct on_disconnect {};
|
||||
|
||||
static constexpr auto check_interval = std::chrono::seconds(3);
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
sentry_op(std::shared_ptr<client_service> svc_ptr, Handler&& handler) :
|
||||
_svc_ptr(std::move(svc_ptr)), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
sentry_op(sentry_op&&) noexcept = default;
|
||||
sentry_op(const sentry_op&) = delete;
|
||||
|
||||
sentry_op& operator=(sentry_op&&) noexcept = default;
|
||||
sentry_op& operator=(const sentry_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform() {
|
||||
_svc_ptr->_sentry_timer.expires_after(check_interval);
|
||||
_svc_ptr->_sentry_timer.async_wait(
|
||||
asio::prepend(std::move(*this), on_timer {})
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(on_timer, error_code) {
|
||||
if (!_svc_ptr->is_open())
|
||||
return complete();
|
||||
|
||||
if (_svc_ptr->_replies.any_expired()) {
|
||||
auto props = disconnect_props {};
|
||||
// TODO add what packet was expected?
|
||||
props[prop::reason_string] = "No reply received within 20 seconds";
|
||||
auto svc_ptr = _svc_ptr;
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::unspecified_error, props, svc_ptr,
|
||||
asio::prepend(std::move(*this), on_disconnect {})
|
||||
);
|
||||
}
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
void operator()(on_disconnect, error_code ec) {
|
||||
if (ec)
|
||||
return complete();
|
||||
|
||||
perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void complete() {
|
||||
return std::move(_handler)();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_SENTRY_OP_HPP
|
||||
@@ -0,0 +1,335 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_SUBSCRIBE_OP_HPP
|
||||
#define BOOST_MQTT5_SUBSCRIBE_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
#include <boost/mqtt5/detail/topic_validation.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class subscribe_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_subscribe {};
|
||||
struct on_suback {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
size_t _num_topics { 0 };
|
||||
|
||||
public:
|
||||
subscribe_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
subscribe_op(subscribe_op&&) = default;
|
||||
subscribe_op(const subscribe_op&) = delete;
|
||||
|
||||
subscribe_op& operator=(subscribe_op&&) = default;
|
||||
subscribe_op& operator=(const subscribe_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props
|
||||
) {
|
||||
_num_topics = topics.size();
|
||||
|
||||
uint16_t packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_immediate(client::error::pid_overrun, packet_id);
|
||||
|
||||
if (_num_topics == 0)
|
||||
return complete_immediate(client::error::invalid_topic, packet_id);
|
||||
|
||||
auto ec = validate_subscribe(topics, props);
|
||||
if (ec)
|
||||
return complete_immediate(ec, packet_id);
|
||||
|
||||
auto subscribe = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_subscribe, packet_id,
|
||||
topics, props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (subscribe.size() > max_packet_size)
|
||||
return complete_immediate(client::error::packet_too_large, packet_id);
|
||||
|
||||
send_subscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void send_subscribe(control_packet<allocator_type> subscribe) {
|
||||
auto wire_data = subscribe.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(
|
||||
std::move(*this), on_subscribe {}, std::move(subscribe)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void resend_subscribe(control_packet<allocator_type> subscribe) {
|
||||
if (_handler.cancelled() != asio::cancellation_type_t::none)
|
||||
return complete(
|
||||
asio::error::operation_aborted, subscribe.packet_id()
|
||||
);
|
||||
send_subscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_subscribe, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return resend_subscribe(std::move(packet));
|
||||
|
||||
auto packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::suback, packet_id,
|
||||
asio::prepend(std::move(*this), on_suback {}, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_suback, control_packet<allocator_type> packet,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_subscribe(std::move(packet));
|
||||
|
||||
uint16_t packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto suback = decoders::decode_suback(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!suback.has_value()) {
|
||||
on_malformed_packet("Malformed SUBACK: cannot decode");
|
||||
return resend_subscribe(std::move(packet));
|
||||
}
|
||||
|
||||
auto& [props, rcs] = *suback;
|
||||
auto reason_codes = to_reason_codes(std::move(rcs));
|
||||
if (reason_codes.size() != _num_topics) {
|
||||
on_malformed_packet(
|
||||
"Malformed SUBACK: does not contain a "
|
||||
"valid Reason Code for every Topic Filter"
|
||||
);
|
||||
return resend_subscribe(std::move(packet));
|
||||
}
|
||||
|
||||
complete(
|
||||
ec, packet_id, std::move(reason_codes), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
error_code validate_subscribe(
|
||||
const std::vector<subscribe_topic>& topics, const subscribe_props& props
|
||||
) const {
|
||||
error_code ec;
|
||||
for (const auto& topic: topics) {
|
||||
ec = validate_topic(topic);
|
||||
if (ec)
|
||||
return ec;
|
||||
}
|
||||
|
||||
ec = validate_props(props);
|
||||
return ec;
|
||||
}
|
||||
|
||||
error_code validate_topic(const subscribe_topic& topic) const {
|
||||
auto wildcard_available = _svc_ptr->connack_property(
|
||||
prop::wildcard_subscription_available
|
||||
).value_or(1);
|
||||
auto shared_available = _svc_ptr->connack_property(
|
||||
prop::shared_subscription_available
|
||||
).value_or(1);
|
||||
|
||||
std::string_view topic_filter = topic.topic_filter;
|
||||
|
||||
validation_result result = validation_result::valid;
|
||||
if (
|
||||
topic_filter.compare(0, shared_sub_prefix.size(), shared_sub_prefix) == 0
|
||||
) {
|
||||
if (!shared_available)
|
||||
return client::error::shared_subscription_not_available;
|
||||
|
||||
result = validate_shared_topic_filter(topic_filter, wildcard_available);
|
||||
} else
|
||||
result = wildcard_available ?
|
||||
validate_topic_filter(topic_filter) :
|
||||
validate_topic_name(topic_filter);
|
||||
|
||||
if (result == validation_result::invalid)
|
||||
return client::error::invalid_topic;
|
||||
if (!wildcard_available && result != validation_result::valid)
|
||||
return client::error::wildcard_subscription_not_available;
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
error_code validate_props(const subscribe_props& props) const {
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
|
||||
const auto& sub_id = props[prop::subscription_identifier];
|
||||
if (!sub_id.has_value())
|
||||
return error_code {};
|
||||
|
||||
auto sub_id_available = _svc_ptr->connack_property(
|
||||
prop::subscription_identifier_available
|
||||
).value_or(1);
|
||||
|
||||
if (!sub_id_available)
|
||||
return client::error::subscription_identifier_not_available;
|
||||
|
||||
return (min_subscription_identifier <= *sub_id &&
|
||||
*sub_id <= max_subscription_identifier) ?
|
||||
error_code {} :
|
||||
client::error::malformed_packet;
|
||||
}
|
||||
|
||||
static std::vector<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
|
||||
std::vector<reason_code> ret;
|
||||
for (uint8_t code : codes) {
|
||||
auto rc = to_reason_code<reason_codes::category::suback>(code);
|
||||
if (rc)
|
||||
ret.push_back(*rc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_malformed_packet(const std::string& reason) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete_immediate(error_code ec, uint16_t packet_id) {
|
||||
if (packet_id != 0)
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete_immediate(
|
||||
ec, std::vector<reason_code>(_num_topics, reason_codes::empty),
|
||||
suback_props {}
|
||||
);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
std::vector<reason_code> reason_codes = {}, suback_props props = {}
|
||||
) {
|
||||
if (reason_codes.empty() && _num_topics)
|
||||
reason_codes = std::vector<reason_code>(_num_topics, reason_codes::empty);
|
||||
|
||||
if (!_svc_ptr->subscriptions_present()) {
|
||||
bool has_success_rc = std::any_of(
|
||||
reason_codes.cbegin(), reason_codes.cend(),
|
||||
[](const reason_code& rc) { return !rc; }
|
||||
);
|
||||
if (has_success_rc)
|
||||
_svc_ptr->subscriptions_present(true);
|
||||
}
|
||||
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete(ec, std::move(reason_codes), std::move(props));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class initiate_async_subscribe {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_subscribe(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
const std::vector<subscribe_topic>& topics, const subscribe_props& props
|
||||
) {
|
||||
detail::subscribe_op { _svc_ptr, std::move(handler) }
|
||||
.perform(topics, props);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_SUBSCRIBE_OP_HPP
|
||||
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
#define BOOST_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/cancellable_handler.hpp>
|
||||
#include <boost/mqtt5/detail/control_packet.hpp>
|
||||
#include <boost/mqtt5/detail/internal_types.hpp>
|
||||
#include <boost/mqtt5/detail/topic_validation.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/codecs/message_decoders.hpp>
|
||||
#include <boost/mqtt5/impl/codecs/message_encoders.hpp>
|
||||
#include <boost/mqtt5/impl/disconnect_op.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_cancellation_slot.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
template <typename ClientService, typename Handler>
|
||||
class unsubscribe_op {
|
||||
using client_service = ClientService;
|
||||
|
||||
struct on_unsubscribe {};
|
||||
struct on_unsuback {};
|
||||
|
||||
std::shared_ptr<client_service> _svc_ptr;
|
||||
|
||||
using handler_type = cancellable_handler<
|
||||
Handler,
|
||||
typename client_service::executor_type
|
||||
>;
|
||||
handler_type _handler;
|
||||
|
||||
size_t _num_topics { 0 };
|
||||
|
||||
public:
|
||||
unsubscribe_op(
|
||||
std::shared_ptr<client_service> svc_ptr,
|
||||
Handler&& handler
|
||||
) :
|
||||
_svc_ptr(std::move(svc_ptr)),
|
||||
_handler(std::move(handler), _svc_ptr->get_executor())
|
||||
{
|
||||
auto slot = asio::get_associated_cancellation_slot(_handler);
|
||||
if (slot.is_connected())
|
||||
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
|
||||
svc.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribe_op(unsubscribe_op&&) = default;
|
||||
unsubscribe_op(const unsubscribe_op&) = delete;
|
||||
|
||||
unsubscribe_op& operator=(unsubscribe_op&&) = default;
|
||||
unsubscribe_op& operator=(const unsubscribe_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = typename client_service::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
void perform(
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
_num_topics = topics.size();
|
||||
|
||||
uint16_t packet_id = _svc_ptr->allocate_pid();
|
||||
if (packet_id == 0)
|
||||
return complete_immediate(client::error::pid_overrun, packet_id);
|
||||
|
||||
if (_num_topics == 0)
|
||||
return complete_immediate(client::error::invalid_topic, packet_id);
|
||||
|
||||
auto ec = validate_unsubscribe(topics, props);
|
||||
if (ec)
|
||||
return complete_immediate(ec, packet_id);
|
||||
|
||||
auto unsubscribe = control_packet<allocator_type>::of(
|
||||
with_pid, get_allocator(),
|
||||
encoders::encode_unsubscribe, packet_id,
|
||||
topics, props
|
||||
);
|
||||
|
||||
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
|
||||
.value_or(default_max_send_size);
|
||||
if (unsubscribe.size() > max_packet_size)
|
||||
return complete_immediate(client::error::packet_too_large, packet_id);
|
||||
|
||||
send_unsubscribe(std::move(unsubscribe));
|
||||
}
|
||||
|
||||
void send_unsubscribe(control_packet<allocator_type> unsubscribe) {
|
||||
auto wire_data = unsubscribe.wire_data();
|
||||
_svc_ptr->async_send(
|
||||
wire_data,
|
||||
no_serial, send_flag::none,
|
||||
asio::prepend(
|
||||
std::move(*this), on_unsubscribe {}, std::move(unsubscribe)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void resend_unsubscribe(control_packet<allocator_type> subscribe) {
|
||||
if (_handler.cancelled() != asio::cancellation_type_t::none)
|
||||
return complete(
|
||||
asio::error::operation_aborted, subscribe.packet_id()
|
||||
);
|
||||
send_unsubscribe(std::move(subscribe));
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_unsubscribe, control_packet<allocator_type> packet,
|
||||
error_code ec
|
||||
) {
|
||||
if (ec == asio::error::try_again)
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
|
||||
auto packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
_svc_ptr->async_wait_reply(
|
||||
control_code_e::unsuback, packet_id,
|
||||
asio::prepend(std::move(*this), on_unsuback {}, std::move(packet))
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_unsuback, control_packet<allocator_type> packet,
|
||||
error_code ec, byte_citer first, byte_citer last
|
||||
) {
|
||||
if (ec == asio::error::try_again) // "resend unanswered"
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
|
||||
uint16_t packet_id = packet.packet_id();
|
||||
|
||||
if (ec)
|
||||
return complete(ec, packet_id);
|
||||
|
||||
auto unsuback = decoders::decode_unsuback(
|
||||
static_cast<uint32_t>(std::distance(first, last)), first
|
||||
);
|
||||
if (!unsuback.has_value()) {
|
||||
on_malformed_packet("Malformed UNSUBACK: cannot decode");
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
}
|
||||
|
||||
auto& [props, rcs] = *unsuback;
|
||||
auto reason_codes = to_reason_codes(std::move(rcs));
|
||||
if (reason_codes.size() != _num_topics) {
|
||||
on_malformed_packet(
|
||||
"Malformed UNSUBACK: does not contain a "
|
||||
"valid Reason Code for every Topic Filter"
|
||||
);
|
||||
return resend_unsubscribe(std::move(packet));
|
||||
}
|
||||
|
||||
complete(
|
||||
ec, packet_id, std::move(reason_codes), std::move(props)
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static error_code validate_unsubscribe(
|
||||
const std::vector<std::string>& topics,
|
||||
const unsubscribe_props& props
|
||||
) {
|
||||
for (const auto& topic : topics)
|
||||
if (validate_topic_filter(topic) != validation_result::valid)
|
||||
return client::error::invalid_topic;
|
||||
|
||||
const auto& user_properties = props[prop::user_property];
|
||||
for (const auto& user_property: user_properties)
|
||||
if (!is_valid_string_pair(user_property))
|
||||
return client::error::malformed_packet;
|
||||
return error_code {};
|
||||
}
|
||||
|
||||
static std::vector<reason_code> to_reason_codes(std::vector<uint8_t> codes) {
|
||||
std::vector<reason_code> ret;
|
||||
for (uint8_t code : codes) {
|
||||
auto rc = to_reason_code<reason_codes::category::unsuback>(code);
|
||||
if (rc)
|
||||
ret.push_back(*rc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void on_malformed_packet(
|
||||
const std::string& reason
|
||||
) {
|
||||
auto props = disconnect_props {};
|
||||
props[prop::reason_string] = reason;
|
||||
async_disconnect(
|
||||
disconnect_rc_e::malformed_packet, props, _svc_ptr,
|
||||
asio::detached
|
||||
);
|
||||
}
|
||||
|
||||
void complete_immediate(error_code ec, uint16_t packet_id) {
|
||||
if (packet_id != 0)
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete_immediate(
|
||||
ec, std::vector<reason_code>(_num_topics, reason_codes::empty),
|
||||
unsuback_props {}
|
||||
);
|
||||
}
|
||||
|
||||
void complete(
|
||||
error_code ec, uint16_t packet_id,
|
||||
std::vector<reason_code> reason_codes = {}, unsuback_props props = {}
|
||||
) {
|
||||
if (reason_codes.empty() && _num_topics)
|
||||
reason_codes = std::vector<reason_code>(_num_topics, reason_codes::empty);
|
||||
|
||||
_svc_ptr->free_pid(packet_id);
|
||||
_handler.complete(ec, std::move(reason_codes), std::move(props));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClientService>
|
||||
class initiate_async_unsubscribe {
|
||||
std::shared_ptr<ClientService> _svc_ptr;
|
||||
public:
|
||||
explicit initiate_async_unsubscribe(std::shared_ptr<ClientService> svc_ptr) :
|
||||
_svc_ptr(std::move(svc_ptr))
|
||||
{}
|
||||
|
||||
using executor_type = typename ClientService::executor_type;
|
||||
executor_type get_executor() const noexcept {
|
||||
return _svc_ptr->get_executor();
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
const std::vector<std::string>& topics, const unsubscribe_props& props
|
||||
) {
|
||||
detail::unsubscribe_op { _svc_ptr, std::move(handler) }
|
||||
.perform(topics, props);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_UNSUBSCRIBE_OP_HPP
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_WRITE_OP_HPP
|
||||
#define BOOST_MQTT5_WRITE_OP_HPP
|
||||
|
||||
#include <boost/mqtt5/detail/async_traits.hpp>
|
||||
|
||||
#include <boost/asio/associated_allocator.hpp>
|
||||
#include <boost/asio/associated_executor.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/prepend.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace boost::mqtt5::detail {
|
||||
|
||||
template <typename Owner, typename Handler>
|
||||
class write_op {
|
||||
struct on_write {};
|
||||
struct on_reconnect {};
|
||||
|
||||
Owner& _owner;
|
||||
|
||||
using handler_type = Handler;
|
||||
handler_type _handler;
|
||||
|
||||
public:
|
||||
write_op(Owner& owner, Handler&& handler) :
|
||||
_owner(owner), _handler(std::move(handler))
|
||||
{}
|
||||
|
||||
write_op(write_op&&) = default;
|
||||
write_op(const write_op&) = delete;
|
||||
|
||||
write_op& operator=(write_op&&) = default;
|
||||
write_op& operator=(const write_op&) = delete;
|
||||
|
||||
using allocator_type = asio::associated_allocator_t<handler_type>;
|
||||
allocator_type get_allocator() const noexcept {
|
||||
return asio::get_associated_allocator(_handler);
|
||||
}
|
||||
|
||||
using executor_type = asio::associated_executor_t<handler_type>;
|
||||
executor_type get_executor() const noexcept {
|
||||
return asio::get_associated_executor(_handler);
|
||||
}
|
||||
|
||||
template <typename BufferType>
|
||||
void perform(BufferType& buffer) {
|
||||
auto stream_ptr = _owner._stream_ptr;
|
||||
if (_owner.was_connected())
|
||||
// note: write operation should not be time-limited
|
||||
detail::async_write(
|
||||
*stream_ptr, buffer,
|
||||
asio::prepend(std::move(*this), on_write {}, stream_ptr)
|
||||
);
|
||||
else
|
||||
asio::post(
|
||||
_owner.get_executor(),
|
||||
asio::prepend(
|
||||
std::move(*this), on_write {},
|
||||
stream_ptr, asio::error::not_connected, 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void operator()(
|
||||
on_write, typename Owner::stream_ptr stream_ptr,
|
||||
error_code ec, size_t bytes_written
|
||||
) {
|
||||
if (!_owner.is_open())
|
||||
return complete(asio::error::operation_aborted, 0);
|
||||
|
||||
if (!ec)
|
||||
return complete(ec, bytes_written);
|
||||
|
||||
// websocket returns operation_aborted if disconnected
|
||||
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
|
||||
return _owner.async_reconnect(
|
||||
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
|
||||
);
|
||||
|
||||
return complete(asio::error::no_recovery, 0);
|
||||
}
|
||||
|
||||
void operator()(on_reconnect, error_code ec) {
|
||||
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
|
||||
ec = asio::error::try_again;
|
||||
|
||||
return complete(ec, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void complete(error_code ec, size_t bytes_written) {
|
||||
std::move(_handler)(ec, bytes_written);
|
||||
}
|
||||
|
||||
static bool should_reconnect(error_code ec) {
|
||||
using namespace asio::error;
|
||||
// note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved)
|
||||
return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */
|
||||
ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */
|
||||
ec == connection_aborted || ec == not_connected ||
|
||||
ec == timed_out || ec == connection_reset ||
|
||||
ec == broken_pipe || ec == asio::error::eof;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5::detail
|
||||
|
||||
#endif // !BOOST_MQTT5_WRITE_OP_HPP
|
||||
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_LOGGER_HPP
|
||||
#define BOOST_MQTT5_LOGGER_HPP
|
||||
|
||||
#include <boost/mqtt5/logger_traits.hpp>
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/traits.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/type_traits/remove_cv_ref.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
/**
|
||||
* \brief Represents the severity level of log messages.
|
||||
*/
|
||||
enum class log_level : uint8_t {
|
||||
/** Error messages that indicate serious issues. **/
|
||||
error = 1,
|
||||
|
||||
/** Warnings that indicate potential problems or non-critical issues. **/
|
||||
warning,
|
||||
|
||||
/** Informational messages that highlight normal application behaviour and events. **/
|
||||
info,
|
||||
|
||||
/** Detailed messages useful for diagnosing issues. **/
|
||||
debug
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A logger class that can used by the \ref mqtt_client to output
|
||||
* the results of operations to stderr.
|
||||
*
|
||||
* \details All functions are invoked directly within the \ref mqtt_client using
|
||||
* its default executor. If the \ref mqtt_client is initialized with an explicit or
|
||||
* implicit strand, none of the functions will be invoked concurrently.
|
||||
*
|
||||
* \par Thread safety
|
||||
* ['Distinct objects]: unsafe. \n
|
||||
* ['Shared objects]: unsafe. \n
|
||||
* This class is [*not thread-safe].
|
||||
*/
|
||||
class logger {
|
||||
constexpr static auto prefix = "[Boost.MQTT5]";
|
||||
|
||||
log_level _level;
|
||||
public:
|
||||
|
||||
/**
|
||||
* \brief Constructs a logger that filters log messages based on the specified log level.
|
||||
*
|
||||
* \param level Messages with a log level higher than the given log level will be suppressed.
|
||||
*/
|
||||
logger(log_level level = log_level::warning) : _level(level) {}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the resolve operation.
|
||||
*
|
||||
* \param ec Error code returned by the resolve operation.
|
||||
* \param host Hostname used in the resolve operation.
|
||||
* \param port Port used in the resolve operation.
|
||||
* \param eps Endpoints returned by the resolve operation.
|
||||
*/
|
||||
void at_resolve(
|
||||
error_code ec, std::string_view host, std::string_view port,
|
||||
const asio::ip::tcp::resolver::results_type& eps
|
||||
) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "resolve: "
|
||||
<< host << ":" << port;
|
||||
std::clog << " - " << ec.message();
|
||||
|
||||
if (_level == log_level::debug) {
|
||||
std::clog << " [";
|
||||
for (auto it = eps.begin(); it != eps.end();) {
|
||||
std::clog << it->endpoint().address().to_string();
|
||||
if (++it != eps.end())
|
||||
std::clog << ",";
|
||||
}
|
||||
std::clog << "]";
|
||||
}
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the TCP connect operation.
|
||||
*
|
||||
* \param ec Error code returned by the TCP connect operation.
|
||||
* \param ep The TCP endpoint used to establish the TCP connection.
|
||||
*/
|
||||
void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "TCP connect: "
|
||||
<< ep.address().to_string() << ":" << ep.port()
|
||||
<< " - " << ec.message()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the TLS handshake operation.
|
||||
*
|
||||
* \param ec Error code returned by the TLS handshake operation.
|
||||
* \param ep The TCP endpoint used to establish the TLS handshake.
|
||||
*/
|
||||
void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "TLS handshake: "
|
||||
<< ep.address().to_string() << ":" << ep.port()
|
||||
<< " - " << ec.message()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the results of the WebSocket handshake operation.
|
||||
*
|
||||
* \param ec Error code returned by the WebSocket handshake operation.
|
||||
* \param ep The TCP endpoint used to establish the WebSocket handshake.
|
||||
*/
|
||||
void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep) {
|
||||
if (!ec && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog
|
||||
<< "WebSocket handshake: "
|
||||
<< ep.address().to_string() << ":" << ep.port()
|
||||
<< " - " << ec.message()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the contents of the \__CONNACK\__ packet sent by the Broker.
|
||||
*
|
||||
* \param rc Reason Code in the received \__CONNACK\__ packet indicating
|
||||
* the result of the MQTT handshake.
|
||||
* \param session_present A flag indicating whether the Broker already has a session associated
|
||||
* with this connection.
|
||||
* \param ca_props \__CONNACK_PROPS\__ received in the \__CONNACK\__ packet.
|
||||
*/
|
||||
void at_connack(
|
||||
reason_code rc,
|
||||
bool session_present, const connack_props& ca_props
|
||||
) {
|
||||
if (!rc && _level < log_level::info)
|
||||
return;
|
||||
|
||||
output_prefix();
|
||||
std::clog << "connack: " << rc.message() << ".";
|
||||
if (_level == log_level::debug) {
|
||||
std::clog << " session_present:" << session_present << " ";
|
||||
output_props(ca_props);
|
||||
}
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Outputs the contents of the \__DISCONNECT\__ packet sent by the Broker.
|
||||
*
|
||||
* \param rc Reason Code in the received \__DISCONNECT\__ packet indicating
|
||||
* the reason behind the disconnection.
|
||||
* \param dc_props \__DISCONNECT_PROPS\__ received in the \__DISCONNECT\__ packet.
|
||||
*/
|
||||
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
|
||||
output_prefix();
|
||||
std::clog << "disconnect: " << rc.message() << ".";
|
||||
if (_level == log_level::debug)
|
||||
output_props(dc_props);
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
void output_prefix() {
|
||||
std::clog << prefix << " ";
|
||||
}
|
||||
|
||||
template <typename Props>
|
||||
void output_props(const Props& props) {
|
||||
props.visit(
|
||||
[](const auto& prop, const auto& val) -> bool {
|
||||
if constexpr (detail::is_optional<decltype(val)>) {
|
||||
if (val.has_value()) {
|
||||
std::clog << property_name(prop) << ":";
|
||||
using value_type = boost::remove_cv_ref_t<decltype(*val)>;
|
||||
if constexpr (std::is_same_v<value_type, uint8_t>)
|
||||
std::clog << std::to_string(*val) << " ";
|
||||
else
|
||||
std::clog << *val << " ";
|
||||
}
|
||||
} else { // is vector
|
||||
if (val.empty())
|
||||
return true;
|
||||
|
||||
std::clog << property_name(prop) << ":";
|
||||
std::clog << "[";
|
||||
for (auto i = 0; i < val.size(); i++) {
|
||||
if constexpr (detail::is_pair<decltype(val[i])>)
|
||||
std::clog << "(" << val[i].first << "," << val[i].second << ")";
|
||||
else
|
||||
std::clog << std::to_string(val[i]);
|
||||
if (i + 1 < val.size())
|
||||
std::clog << ", ";
|
||||
}
|
||||
std::clog << "]";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
template <prop::property_type p>
|
||||
static std::string_view property_name(std::integral_constant<prop::property_type, p>) {
|
||||
return prop::name_v<p>;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Verify that the logger class satisfies the LoggerType concept
|
||||
static_assert(has_at_resolve<logger>);
|
||||
static_assert(has_at_tcp_connect<logger>);
|
||||
static_assert(has_at_tls_handshake<logger>);
|
||||
static_assert(has_at_ws_handshake<logger>);
|
||||
static_assert(has_at_connack<logger>);
|
||||
static_assert(has_at_disconnect<logger>);
|
||||
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
|
||||
#endif // !BOOST_MQTT5_LOGGER_HPP
|
||||
@@ -5,22 +5,22 @@
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASYNC_MQTT5_LOGGER_TRAITS_HPP
|
||||
#define ASYNC_MQTT5_LOGGER_TRAITS_HPP
|
||||
#ifndef BOOST_MQTT5_LOGGER_TRAITS_HPP
|
||||
#define BOOST_MQTT5_LOGGER_TRAITS_HPP
|
||||
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
#include <boost/mqtt5/reason_codes.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/type_traits/is_detected.hpp>
|
||||
|
||||
#include <async_mqtt5/reason_codes.hpp>
|
||||
#include <async_mqtt5/property_types.hpp>
|
||||
#include <async_mqtt5/types.hpp>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace async_mqtt5 {
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
@@ -32,11 +32,11 @@ class noop_logger {};
|
||||
|
||||
template <typename T>
|
||||
using at_resolve_sig = decltype(
|
||||
std::declval<T&>().at_resolve(
|
||||
std::declval<error_code>(),
|
||||
std::declval<std::string_view>(), std::declval<std::string_view>(),
|
||||
std::declval<const asio::ip::tcp::resolver::results_type&>()
|
||||
)
|
||||
std::declval<T&>().at_resolve(
|
||||
std::declval<error_code>(),
|
||||
std::declval<std::string_view>(), std::declval<std::string_view>(),
|
||||
std::declval<const asio::ip::tcp::resolver::results_type&>()
|
||||
)
|
||||
);
|
||||
template <typename T>
|
||||
constexpr bool has_at_resolve = boost::is_detected<at_resolve_sig, T>::value;
|
||||
@@ -45,9 +45,9 @@ constexpr bool has_at_resolve = boost::is_detected<at_resolve_sig, T>::value;
|
||||
|
||||
template <typename T>
|
||||
using at_tcp_connect_sig = decltype(
|
||||
std::declval<T&>().at_tcp_connect(
|
||||
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
|
||||
)
|
||||
std::declval<T&>().at_tcp_connect(
|
||||
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
|
||||
)
|
||||
);
|
||||
template <typename T>
|
||||
constexpr bool has_at_tcp_connect = boost::is_detected<at_tcp_connect_sig, T>::value;
|
||||
@@ -56,9 +56,9 @@ constexpr bool has_at_tcp_connect = boost::is_detected<at_tcp_connect_sig, T>::v
|
||||
|
||||
template <typename T>
|
||||
using at_tls_handshake_sig = decltype(
|
||||
std::declval<T&>().at_tls_handshake(
|
||||
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
|
||||
)
|
||||
std::declval<T&>().at_tls_handshake(
|
||||
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
|
||||
)
|
||||
);
|
||||
template <typename T>
|
||||
constexpr bool has_at_tls_handshake = boost::is_detected<at_tls_handshake_sig, T>::value;
|
||||
@@ -67,9 +67,9 @@ constexpr bool has_at_tls_handshake = boost::is_detected<at_tls_handshake_sig, T
|
||||
|
||||
template <typename T>
|
||||
using at_ws_handshake_sig = decltype(
|
||||
std::declval<T&>().at_ws_handshake(
|
||||
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
|
||||
)
|
||||
std::declval<T&>().at_ws_handshake(
|
||||
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
|
||||
)
|
||||
);
|
||||
template <typename T>
|
||||
constexpr bool has_at_ws_handshake = boost::is_detected<at_ws_handshake_sig, T>::value;
|
||||
@@ -78,10 +78,10 @@ constexpr bool has_at_ws_handshake = boost::is_detected<at_ws_handshake_sig, T>:
|
||||
|
||||
template <typename T>
|
||||
using at_connack_sig = decltype(
|
||||
std::declval<T&>().at_connack(
|
||||
std::declval<reason_code>(),
|
||||
std::declval<bool>(), std::declval<const connack_props&>()
|
||||
)
|
||||
std::declval<T&>().at_connack(
|
||||
std::declval<reason_code>(),
|
||||
std::declval<bool>(), std::declval<const connack_props&>()
|
||||
)
|
||||
);
|
||||
template <typename T>
|
||||
constexpr bool has_at_connack = boost::is_detected<at_connack_sig, T>::value;
|
||||
@@ -90,14 +90,14 @@ constexpr bool has_at_connack = boost::is_detected<at_connack_sig, T>::value;
|
||||
|
||||
template <typename T>
|
||||
using at_disconnect_sig = decltype(
|
||||
std::declval<T&>().at_disconnect(
|
||||
std::declval<reason_code>(), std::declval<const disconnect_props&>()
|
||||
)
|
||||
std::declval<T&>().at_disconnect(
|
||||
std::declval<reason_code>(), std::declval<const disconnect_props&>()
|
||||
)
|
||||
);
|
||||
template <typename T>
|
||||
constexpr bool has_at_disconnect = boost::is_detected<at_disconnect_sig, T>::value;
|
||||
|
||||
} // end namespace async_mqtt5
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
|
||||
#endif // !ASYNC_MQTT5_LOGGER_TRAITS_HPP
|
||||
#endif // !BOOST_MQTT5_LOGGER_TRAITS_HPP
|
||||
@@ -0,0 +1,981 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_MQTT_CLIENT_HPP
|
||||
#define BOOST_MQTT5_MQTT_CLIENT_HPP
|
||||
|
||||
#include <boost/mqtt5/error.hpp>
|
||||
#include <boost/mqtt5/logger_traits.hpp>
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/mqtt5/detail/log_invoke.hpp>
|
||||
#include <boost/mqtt5/detail/rebind_executor.hpp>
|
||||
|
||||
#include <boost/mqtt5/impl/client_service.hpp>
|
||||
#include <boost/mqtt5/impl/publish_send_op.hpp>
|
||||
#include <boost/mqtt5/impl/re_auth_op.hpp>
|
||||
#include <boost/mqtt5/impl/run_op.hpp>
|
||||
#include <boost/mqtt5/impl/subscribe_op.hpp>
|
||||
#include <boost/mqtt5/impl/unsubscribe_op.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant> // std::monostate
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
/**
|
||||
* \brief \__MQTT\__ client used to connect and communicate with a Broker.
|
||||
*
|
||||
* \tparam \__StreamType\__ Type of the underlying transport protocol used to transfer
|
||||
* the stream of bytes between the Client and the Broker. The transport must be
|
||||
* ordered and lossless.
|
||||
* \tparam \__TlsContext\__ Type of the context object used in TLS/SSL connections.
|
||||
* \tparam \__LoggerType\__ Type of object used to log events within the Client.
|
||||
*
|
||||
* \par Thread safety
|
||||
* ['Distinct objects]: safe. \n
|
||||
* ['Shared objects]: unsafe. \n
|
||||
* This class is [*not thread-safe].
|
||||
* The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.
|
||||
*/
|
||||
template <
|
||||
typename StreamType,
|
||||
typename TlsContext = std::monostate,
|
||||
typename LoggerType = noop_logger
|
||||
>
|
||||
class mqtt_client {
|
||||
public:
|
||||
/// The executor type associated with the client.
|
||||
using executor_type = typename StreamType::executor_type;
|
||||
|
||||
/// Rebinds the client type to another executor.
|
||||
template <typename Executor>
|
||||
struct rebind_executor {
|
||||
/// The client type when rebound to the specified executor.
|
||||
using other = mqtt_client<
|
||||
typename detail::rebind_executor<StreamType, Executor>::other,
|
||||
TlsContext
|
||||
>;
|
||||
};
|
||||
|
||||
private:
|
||||
using stream_type = StreamType;
|
||||
using tls_context_type = TlsContext;
|
||||
using logger_type = LoggerType;
|
||||
|
||||
using client_service_type = detail::client_service<
|
||||
stream_type, tls_context_type, logger_type
|
||||
>;
|
||||
using impl_type = std::shared_ptr<client_service_type>;
|
||||
impl_type _impl;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs a Client with given parameters.
|
||||
*
|
||||
* \param ex An executor that will be associated with the Client.
|
||||
* \param tls_context A context object used in TLS/SSL connection.
|
||||
* \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client.
|
||||
*/
|
||||
explicit mqtt_client(
|
||||
const executor_type& ex,
|
||||
tls_context_type tls_context = {}, logger_type logger = {}
|
||||
) :
|
||||
_impl(std::make_shared<client_service_type>(
|
||||
ex, std::move(tls_context), std::move(logger)
|
||||
))
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Constructs a Client with given parameters.
|
||||
*
|
||||
* \tparam \__ExecutionContext\__ Type of a concrete execution context.
|
||||
* \param context Execution context whose executor will be associated with the Client.
|
||||
* \param tls_context A context object used in TLS/SSL connection.
|
||||
* \param logger An object satisfying the \__LoggerType\__ concept used to log events within the Client.
|
||||
*
|
||||
* \par Precondition
|
||||
* \code
|
||||
* std::is_convertible_v<ExecutionContext&, asio::execution_context&>
|
||||
* \endcode
|
||||
*/
|
||||
template <
|
||||
typename ExecutionContext,
|
||||
std::enable_if_t<
|
||||
std::is_convertible_v<ExecutionContext&, asio::execution_context&>,
|
||||
bool
|
||||
> = true
|
||||
>
|
||||
explicit mqtt_client(
|
||||
ExecutionContext& context,
|
||||
tls_context_type tls_context = {}, logger_type logger = {}
|
||||
) :
|
||||
mqtt_client(
|
||||
context.get_executor(),
|
||||
std::move(tls_context), std::move(logger)
|
||||
)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Move-construct an mqtt_client from another.
|
||||
*
|
||||
* \details Moved-from client can only be destructed
|
||||
*/
|
||||
mqtt_client(mqtt_client&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* \brief Move assignment operator.
|
||||
*
|
||||
* \details Cancels this client first. Moved-from client can only be destructed.
|
||||
*/
|
||||
mqtt_client& operator=(mqtt_client&& other) noexcept {
|
||||
_impl->cancel();
|
||||
_impl = std::move(other._impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destructor.
|
||||
*
|
||||
* \details Automatically calls \ref mqtt_client::cancel.
|
||||
*/
|
||||
~mqtt_client() {
|
||||
if (_impl)
|
||||
_impl->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the executor associated with the object.
|
||||
*/
|
||||
executor_type get_executor() const noexcept {
|
||||
return _impl->get_executor();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the context object used in TLS/SSL connection.
|
||||
*
|
||||
* \note This function may only be invoked
|
||||
* when the template parameter \__TlsContext\__ was configured
|
||||
* with non-default type during the creation of a \ref mqtt_client.
|
||||
*
|
||||
* \par Precondition
|
||||
* \code
|
||||
* !std::is_same_v<TlsContext, std::monostate>
|
||||
* \endcode
|
||||
*/
|
||||
template <
|
||||
typename Ctx = TlsContext,
|
||||
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, bool> = true
|
||||
>
|
||||
decltype(auto) tls_context() {
|
||||
return _impl->tls_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Start the Client.
|
||||
*
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (__ERROR_CODE__)
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete with
|
||||
* `boost::asio::error::operation_aborted` when the client is cancelled by calling
|
||||
* \ref mqtt_client::async_disconnect, \ref mqtt_client::cancel, destruction or
|
||||
* if a non-recoverable error happens during a connection attempt (e.g. access denied).
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::asio::error::operation_aborted`\n
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_run(CompletionToken&& token = {}) {
|
||||
using Signature = void (error_code);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_run(_impl), token
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Cancel all asynchronous operations. This function has terminal effects.
|
||||
*
|
||||
* \details All outstanding operations will complete
|
||||
* with `boost::asio::error::operation_aborted`.
|
||||
*
|
||||
* \attention This function has terminal effects and will close the Client.
|
||||
* The Client cannot be used before calling \ref mqtt_client::async_run again.
|
||||
*/
|
||||
void cancel() {
|
||||
auto impl = _impl;
|
||||
_impl = impl->dup();
|
||||
impl->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign a \ref will Message.
|
||||
*
|
||||
* \details The \ref will Message that the Broker should publish
|
||||
* after the Network Connection is closed and it is not
|
||||
* closed normally.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*/
|
||||
mqtt_client& will(will will) {
|
||||
_impl->will(std::move(will));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign credentials that will be used to connect to a Broker.
|
||||
*
|
||||
* \details Credentials consist of a unique Client Identifier and, optionally,
|
||||
* a User Name and Password.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*/
|
||||
mqtt_client& credentials(
|
||||
std::string client_id,
|
||||
std::string username = "", std::string password = ""
|
||||
) {
|
||||
_impl->credentials(
|
||||
std::move(client_id),
|
||||
std::move(username), std::move(password)
|
||||
);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign a list of Brokers that the Client will attempt to connect to.
|
||||
*
|
||||
* \details The Client will cycle through the list of hosts,
|
||||
* attempting to establish a connection with each
|
||||
* until it successfully establishes a connection.
|
||||
*
|
||||
* \param hosts List of Broker addresses and ports.
|
||||
* Address and ports are separated with a colon `:` while
|
||||
* pairs of addresses and ports are separated with a comma `,`.
|
||||
* \param default_port The default port to connect to in case the port is not
|
||||
* explicitly specified in the `hosts` list.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*
|
||||
* \par Example
|
||||
* Some valid `hosts` string:
|
||||
*
|
||||
* \code
|
||||
* std::string valid_hosts_1 = "broker1:1883, broker2, broker3:1883";
|
||||
* std::string valid_hosts_2 = "broker1";
|
||||
* \endcode
|
||||
*
|
||||
*/
|
||||
mqtt_client& brokers(std::string hosts, uint16_t default_port = 1883) {
|
||||
_impl->brokers(std::move(hosts), default_port);
|
||||
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 re_authenticate.
|
||||
*
|
||||
* \param authenticator Object that will be stored (move-constructed or by reference)
|
||||
* and used for authentication. It needs to satisfy \__is_authenticator\__ concept.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename Authenticator,
|
||||
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
|
||||
>
|
||||
mqtt_client& authenticator(Authenticator&& authenticator) {
|
||||
_impl->authenticator(std::forward<Authenticator>(authenticator));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign the maximum time interval that is permitted to elapse between
|
||||
* two transmissions from the Client.
|
||||
*
|
||||
* \details A non-zero value initiates a process of sending a \__PINGREQ\__
|
||||
* packet every `seconds`. If this function is not invoked, the Client assumes
|
||||
* a \__KEEP_ALIVE\__ interval of 60 seconds.
|
||||
*
|
||||
* \param seconds Time interval in seconds.
|
||||
*
|
||||
* \note If the Server sends a \__SERVER_KEEP_ALIVE\__,
|
||||
* the Client will send a \__PINGREQ\__ packet every \__SERVER_KEEP_ALIVE\__ seconds.
|
||||
*
|
||||
* \attention This function takes action when the client is in a non-operational state,
|
||||
* meaning the \ref async_run function has not been invoked.
|
||||
* Furthermore, you can use this function after the \ref cancel function has been called,
|
||||
* before the \ref async_run function is invoked again.
|
||||
*
|
||||
*/
|
||||
mqtt_client& keep_alive(uint16_t seconds) {
|
||||
_impl->keep_alive(seconds);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign \__CONNECT_PROPS\__ that will be sent in a \__CONNECT\__ packet.
|
||||
* \param props \__CONNECT_PROPS\__ sent in a \__CONNECT\__ packet.
|
||||
* \see See \__CONNECT_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
mqtt_client& connect_properties(connect_props props) {
|
||||
_impl->connect_properties(std::move(props));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Assign a property that will be sent in a \__CONNECT\__ packet.
|
||||
* \param prop The \__CONNECT_PROPS\__ property to set.
|
||||
* \param value Value that will be assigned to the property.
|
||||
*
|
||||
* \par Example
|
||||
* \code
|
||||
* client.connect_property(prop::session_expiry_interval, 40); // ok
|
||||
* client.connect_property(prop::reason_string, "reason"); // does not compile, not a CONNECT prop!
|
||||
* \endcode
|
||||
*
|
||||
* \see See \__CONNECT_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
template <prop::property_type p>
|
||||
mqtt_client& connect_property(
|
||||
std::integral_constant<prop::property_type, p> prop,
|
||||
prop::value_type_t<p> value
|
||||
) {
|
||||
_impl->connect_property(prop, std::move(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Initiates [mqttlink 3901257 Re-authentication]
|
||||
* using the authenticator given in the \ref authenticator method.
|
||||
*
|
||||
* \note If \ref authenticator was not called, this method does nothing.
|
||||
*/
|
||||
void re_authenticate() {
|
||||
detail::re_auth_op { _impl }.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Retrieves the value of a specific property from the last \__CONNACK\__ packet received.
|
||||
*
|
||||
* \details The return type varies according to the property requested.
|
||||
* For all properties, the return type will be `std::optional` of their respective value type.
|
||||
* For `boost::mqtt5::prop::user_property`, the return type is
|
||||
* `std::vector<std::pair<std::string, std::string>>`.
|
||||
*
|
||||
* \param prop The \__CONNACK_PROPS\__ property value to retrieve.
|
||||
*
|
||||
* \par Example
|
||||
* \code
|
||||
* std::optional<std::string> auth_method = client.connack_property(boost::mqtt5::prop::authentication_method); // ok
|
||||
* std::optional<std::string> c_type = client.connack_property(boost::mqtt5::prop::content_type); // does not compile, not a CONNACK prop!
|
||||
* \endcode
|
||||
*
|
||||
* \see See \__CONNACK_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
template <prop::property_type p>
|
||||
const auto& connack_property(
|
||||
std::integral_constant<prop::property_type, p> prop
|
||||
) const {
|
||||
return _impl->connack_property(prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Retrieves the \__CONNACK_PROPS\__ from the last \__CONNACK\__ packet received.
|
||||
*
|
||||
* \see See \__CONNACK_PROPS\__ for all eligible properties.
|
||||
*/
|
||||
const connack_props& connack_properties() const {
|
||||
return _impl->connack_properties();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send a \__PUBLISH\__ packet to Broker to transport an
|
||||
* Application Message.
|
||||
*
|
||||
* \tparam qos_type The \ref qos_e level of assurance for delivery.
|
||||
* \param topic Identification of the information channel to which
|
||||
* Payload data is published.
|
||||
* \param payload The Application Message that is being published.
|
||||
* \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 completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation depends on the \ref qos_e specified:\n
|
||||
*
|
||||
* `qos` == `qos_e::at_most_once`:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__ // Result of operation
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* `qos` == `qos_e::at_least_once`:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* __REASON_CODE__, // Reason Code received from Broker.
|
||||
* __PUBACK_PROPS__ // Properties received in the PUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* `qos` == `qos_e::exactly_once`:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* __REASON_CODE__, // Reason Code received from Broker.
|
||||
* __PUBCOMP_PROPS__ // Properties received in the PUBCOMP packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* Depending on the \ref qos_e specified, the asynchronous operation will complete
|
||||
* when one of the following conditions is true:\n
|
||||
* - If `qos` == `qos_e::at_most_once` and the Client
|
||||
* has successfully written the packet to the transport. \n
|
||||
* - If `qos` == `qos_e::at_least_once` and the packet has
|
||||
* been sent and acknowledged through the reception of a \__PUBACK\__ packet.
|
||||
* - If `qos` == `qos_e::exactly_once` and the packet has
|
||||
* been sent and fully acknowledged through the reception of a \__PUBCOMP\__ packet.
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link boost::mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link boost::mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link boost::mqtt5::client::error::qos_not_supported \endlink
|
||||
* - \link boost::mqtt5::client::error::retain_not_available \endlink
|
||||
* - \link boost::mqtt5::client::error::topic_alias_maximum_reached \endlink
|
||||
* - \link boost::mqtt5::client::error::invalid_topic \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__PUBLISH\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <qos_e qos_type,
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_publish(
|
||||
std::string topic, std::string payload,
|
||||
retain_e retain, const publish_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
using Signature = detail::on_publish_signature<qos_type>;
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_publish<client_service_type, qos_type>(_impl),
|
||||
token,
|
||||
std::move(topic), std::move(payload), retain, props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription
|
||||
* to one or more Topics of interest.
|
||||
*
|
||||
* \details After the subscription has been established, the Broker will send
|
||||
* PUBLISH packets to the Client to forward Application Messages that were published
|
||||
* to Topics that the Client subscribed to. The Application Messages can be received
|
||||
* with \ref mqtt_client::async_receive function.
|
||||
*
|
||||
* \param topics A list of \ref subscribe_topic of interest.
|
||||
* \param props An instance of \__SUBSCRIBE_PROPS\__.
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating
|
||||
* // the subscription result for each Topic
|
||||
* // in the SUBSCRIBE packet.
|
||||
* __SUBACK_PROPS__, // Properties received in the SUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent a \__SUBSCRIBE\__ packet
|
||||
* and has received a \__SUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link boost::mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link boost::mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link boost::mqtt5::client::error::invalid_topic \endlink
|
||||
* - \link boost::mqtt5::client::error::wildcard_subscription_not_available \endlink
|
||||
* - \link boost::mqtt5::client::error::subscription_identifier_not_available \endlink
|
||||
* - \link boost::mqtt5::client::error::shared_subscription_not_available \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_subscribe(
|
||||
const std::vector<subscribe_topic>& topics,
|
||||
const subscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
using Signature = void (
|
||||
error_code, std::vector<reason_code>, suback_props
|
||||
);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_subscribe(_impl), token,
|
||||
topics, props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send a \__SUBSCRIBE\__ packet to Broker to create a subscription
|
||||
* to one Topic of interest.
|
||||
*
|
||||
* \details After the subscription has been established, the Broker will send
|
||||
* \__PUBLISH\__ packets to the Client to forward Application Messages that were published
|
||||
* to Topics that the Client subscribed to. The Application Messages can be received
|
||||
* with \ref mqtt_client::async_receive function.
|
||||
*
|
||||
* \param 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 completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes containing the
|
||||
* // single subscription result for the Topic
|
||||
* // in the SUBSCRIBE packet.
|
||||
* __SUBACK_PROPS__, // Properties received in the SUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent a \__SUBSCRIBE\__ packet
|
||||
* and has received a \__SUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link boost::mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link boost::mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link boost::mqtt5::client::error::invalid_topic \endlink
|
||||
* - \link boost::mqtt5::client::error::wildcard_subscription_not_available \endlink
|
||||
* - \link boost::mqtt5::client::error::subscription_identifier_not_available \endlink
|
||||
* - \link boost::mqtt5::client::error::shared_subscription_not_available \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__SUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_subscribe(
|
||||
const subscribe_topic& topic, const subscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
return async_subscribe(
|
||||
std::vector<subscribe_topic> { topic }, props,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe from one
|
||||
* or more Topics.
|
||||
*
|
||||
* \note The Client may still receive residual Application Messages
|
||||
* through the \ref mqtt_client::async_receive function
|
||||
* from Topics the Client just unsubscribed to.
|
||||
*
|
||||
* \param topics List of Topics to unsubscribe from.
|
||||
* \param props An instance of \__UNSUBSCRIBE_PROPS\__.
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes indicating
|
||||
* // the result of unsubscribe operation
|
||||
* // for each Topic in the UNSUBSCRIBE packet.
|
||||
* __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent an \__UNSUBSCRIBE\__ packet
|
||||
* and has received an \__UNSUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link boost::mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link boost::mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link boost::mqtt5::client::error::invalid_topic \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_unsubscribe(
|
||||
const std::vector<std::string>& topics, const unsubscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
using Signature = void (
|
||||
error_code, std::vector<reason_code>, unsuback_props
|
||||
);
|
||||
return asio::async_initiate<CompletionToken, Signature>(
|
||||
detail::initiate_async_unsubscribe(_impl), token,
|
||||
topics, props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Send an \__UNSUBSCRIBE\__ packet to Broker to unsubscribe
|
||||
* from one Topic.
|
||||
*
|
||||
* \note The Client may still receive residual Application Messages
|
||||
* through the \ref mqtt_client::async_receive function
|
||||
* from Topics the Client just unsubscribed to.
|
||||
*
|
||||
* \param 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 completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__ASYNC_IMMEDIATE\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::vector<__REASON_CODE__>, // Vector of Reason Codes containing
|
||||
* // the result of unsubscribe operation
|
||||
* // for the Topic in the UNSUBSCRIBE packet.
|
||||
* __UNSUBACK_PROPS__, // Properties received in the UNSUBACK packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has successfully sent an \__UNSUBSCRIBE\__ packet
|
||||
* and has received an \__UNSUBACK\__ response from the Broker.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success` \n
|
||||
* - `boost::asio::error::no_recovery` \n
|
||||
* - `boost::asio::error::operation_aborted` \n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
* - \link boost::mqtt5::client::error::packet_too_large \endlink
|
||||
* - \link boost::mqtt5::client::error::pid_overrun \endlink
|
||||
* - \link boost::mqtt5::client::error::invalid_topic \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
* - `cancellation_type::partial` & `cancellation_type::total` - prevents potential resending of the \__UNSUBSCRIBE\__ packet \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_unsubscribe(
|
||||
const std::string& topic, const unsubscribe_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
return async_unsubscribe(
|
||||
std::vector<std::string> { topic }, props,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Asynchronously receive an Application Message.
|
||||
*
|
||||
* \details The Client will receive and complete deliveries for all the
|
||||
* \__PUBLISH\__ packets received from the Broker throughout its lifetime.
|
||||
* The Client will store them internally in the order they were delivered.
|
||||
* Calling this function will attempt to receive an Application Message
|
||||
* from internal storage.
|
||||
*
|
||||
* \note It is only recommended to call this function if you have established
|
||||
* a successful subscription to a Topic using the \ref async_subscribe function.
|
||||
*
|
||||
* \param token Completion token that will be used to produce a
|
||||
* completion handler. The handler will be invoked when the operation completes.
|
||||
* On immediate completion, invocation of the handler will be performed in a manner
|
||||
* equivalent to using \__POST\__.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__, // Result of operation.
|
||||
* std::string, // Topic, the origin of the Application Message.
|
||||
* std::string, // Payload, the content of the Application Message.
|
||||
* __PUBLISH_PROPS__, // Properties received in the PUBLISH packet.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has a pending Application Message in its internal storage
|
||||
* ready to be received.
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success`\n
|
||||
* - `boost::asio::error::operation_aborted`\n
|
||||
* - \link boost::mqtt5::client::error::session_expired \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` \n
|
||||
* - `cancellation_type::partial` \n
|
||||
* - `cancellation_type::total` \n
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_receive(CompletionToken&& token = {}) {
|
||||
return _impl->async_channel_receive(std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Disconnect the Client by sending a \__DISCONNECT\__ packet
|
||||
* with a specified Reason Code. This function has terminal effects.
|
||||
*
|
||||
* \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker
|
||||
* with a Reason Code describing the reason for disconnection.
|
||||
* If the \__DISCONNECT\__ packet is successfully transmitted,
|
||||
* or if `5 seconds` elapsed without a successful send, the Client will terminate the connection.
|
||||
*
|
||||
* \attention This function has terminal effects and will close the Client.
|
||||
* See \ref mqtt_client::cancel.
|
||||
*
|
||||
* \param reason_code Reason Code to notify
|
||||
* the Broker of the reason for the 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 completes.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__ // Result of operation.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has sent a \__DISCONNECT\__ packet.\n
|
||||
* - 5 seconds have elapsed without a successful send.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success`\n
|
||||
* - `boost::asio::error::operation_aborted`[footnote
|
||||
This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server.
|
||||
Regardless, the connection to the Server is terminated, and the Client is cancelled.
|
||||
]\n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_disconnect(
|
||||
disconnect_rc_e reason_code, const disconnect_props& props,
|
||||
CompletionToken&& token = {}
|
||||
) {
|
||||
auto impl = _impl;
|
||||
_impl = impl->dup();
|
||||
return detail::async_terminal_disconnect(
|
||||
detail::disconnect_rc_e(static_cast<uint8_t>(reason_code)),
|
||||
props, impl, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Disconnect the Client by sending a \__DISCONNECT\__ packet
|
||||
* with a Reason Code of reason_codes.normal_disconnection.
|
||||
* This function has terminal effects.
|
||||
*
|
||||
* \details The Client will attempt to send a \__DISCONNECT\__ packet to the Broker
|
||||
* with a Reason Code describing the reason for disconnection.
|
||||
* If the \__DISCONNECT\__ packet is successfully transmitted,
|
||||
* or if `5 seconds` elapsed without a successful send, the Client will terminate the connection.
|
||||
*
|
||||
* \attention This function has terminal effects and will close the Client.
|
||||
* 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 completes.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation:
|
||||
* \code
|
||||
* void (
|
||||
* __ERROR_CODE__ // Result of operation.
|
||||
* )
|
||||
* \endcode
|
||||
*
|
||||
* \par Completion condition
|
||||
* The asynchronous operation will complete when one of the following conditions is true:\n
|
||||
* - The Client has attempted to send a \__DISCONNECT\__ packet, regardless of whether
|
||||
* the sending was successful or not.\n
|
||||
* - An error occurred. This is indicated by an associated \__ERROR_CODE\__ in the handler.\n
|
||||
*
|
||||
* \par Error codes
|
||||
* The list of all possible error codes that this operation can finish with:\n
|
||||
* - `boost::system::errc::errc_t::success`\n
|
||||
* - `boost::asio::error::operation_aborted`[footnote
|
||||
This error code can appear if the Client fails to send the \__DISCONNECT\__ packet to the Server.
|
||||
Regardless, the connection to the Server is terminated, and the Client is cancelled.
|
||||
]\n
|
||||
* - \link boost::mqtt5::client::error::malformed_packet \endlink
|
||||
*
|
||||
* Refer to the section on \__ERROR_HANDLING\__ to find the underlying causes for each error code.
|
||||
*
|
||||
* \par Per-Operation Cancellation
|
||||
* This asynchronous operation supports cancellation for the following \__CANCELLATION_TYPE\__ values:\n
|
||||
* - `cancellation_type::terminal` - invokes \ref mqtt_client::cancel \n
|
||||
*/
|
||||
template <
|
||||
typename CompletionToken =
|
||||
typename asio::default_completion_token<executor_type>::type
|
||||
>
|
||||
decltype(auto) async_disconnect(CompletionToken&& token = {}) {
|
||||
return async_disconnect(
|
||||
disconnect_rc_e::normal_disconnection,
|
||||
disconnect_props {}, std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
#endif // !BOOST_MQTT5_MQTT_CLIENT_HPP
|
||||
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_PROPERTY_TYPES_HPP
|
||||
#define BOOST_MQTT5_PROPERTY_TYPES_HPP
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace boost::mqtt5::prop {
|
||||
|
||||
enum property_type : uint8_t {
|
||||
payload_format_indicator_t = 0x01,
|
||||
message_expiry_interval_t = 0x02,
|
||||
content_type_t = 0x03,
|
||||
response_topic_t = 0x08,
|
||||
correlation_data_t = 0x09,
|
||||
subscription_identifier_t = 0x0b,
|
||||
session_expiry_interval_t = 0x11,
|
||||
assigned_client_identifier_t = 0x12,
|
||||
server_keep_alive_t = 0x13,
|
||||
authentication_method_t = 0x15,
|
||||
authentication_data_t = 0x16,
|
||||
request_problem_information_t = 0x17,
|
||||
will_delay_interval_t = 0x18,
|
||||
request_response_information_t = 0x19,
|
||||
response_information_t = 0x1a,
|
||||
server_reference_t = 0x1c,
|
||||
reason_string_t = 0x1f,
|
||||
receive_maximum_t = 0x21,
|
||||
topic_alias_maximum_t = 0x22,
|
||||
topic_alias_t = 0x23,
|
||||
maximum_qos_t = 0x24,
|
||||
retain_available_t = 0x25,
|
||||
user_property_t = 0x26,
|
||||
maximum_packet_size_t = 0x27,
|
||||
wildcard_subscription_available_t = 0x28,
|
||||
subscription_identifier_available_t = 0x29,
|
||||
shared_subscription_available_t = 0x2a
|
||||
};
|
||||
|
||||
class alignas(8) subscription_identifiers :
|
||||
public boost::container::small_vector<int32_t, 1>
|
||||
{
|
||||
using base_type = boost::container::small_vector<int32_t, 1>;
|
||||
|
||||
public:
|
||||
using base_type::base_type;
|
||||
subscription_identifiers(int32_t val) : base_type { val } {}
|
||||
|
||||
bool has_value() const noexcept {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
int32_t& operator*() noexcept {
|
||||
return front();
|
||||
}
|
||||
|
||||
int32_t operator*() const noexcept {
|
||||
return front();
|
||||
}
|
||||
|
||||
void emplace(int32_t val = 0) {
|
||||
*this = val;
|
||||
}
|
||||
|
||||
int32_t value() const {
|
||||
return front();
|
||||
}
|
||||
|
||||
int32_t value_or(int32_t default_val) const noexcept {
|
||||
return empty() ? default_val : front();
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
template <property_type p>
|
||||
struct property_traits;
|
||||
|
||||
using user_property_value_t = std::vector<std::pair<std::string, std::string>>;
|
||||
|
||||
#define DEF_PROPERTY_TRAIT(Pname, Ptype) \
|
||||
template <> \
|
||||
struct property_traits<Pname##_t> { \
|
||||
static constexpr std::string_view name = #Pname; \
|
||||
using type = Ptype; \
|
||||
}; \
|
||||
constexpr std::integral_constant<property_type, Pname##_t> Pname {};
|
||||
|
||||
DEF_PROPERTY_TRAIT(payload_format_indicator, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(message_expiry_interval, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(content_type, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(response_topic, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(correlation_data, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(subscription_identifier, subscription_identifiers);
|
||||
DEF_PROPERTY_TRAIT(session_expiry_interval, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(assigned_client_identifier, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(server_keep_alive, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(authentication_method, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(authentication_data, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(request_problem_information, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(will_delay_interval, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(request_response_information, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(response_information, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(server_reference, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(reason_string, std::optional<std::string>);
|
||||
DEF_PROPERTY_TRAIT(receive_maximum, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(topic_alias_maximum, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(topic_alias, std::optional<uint16_t>);
|
||||
DEF_PROPERTY_TRAIT(maximum_qos, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(retain_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(user_property, user_property_value_t);
|
||||
DEF_PROPERTY_TRAIT(maximum_packet_size, std::optional<uint32_t>);
|
||||
DEF_PROPERTY_TRAIT(wildcard_subscription_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(subscription_identifier_available, std::optional<uint8_t>);
|
||||
DEF_PROPERTY_TRAIT(shared_subscription_available, std::optional<uint8_t>);
|
||||
|
||||
#undef DEF_PROPERTY_TRAIT
|
||||
|
||||
template <property_type p>
|
||||
using value_type_t = typename property_traits<p>::type;
|
||||
|
||||
template <property_type p>
|
||||
constexpr std::string_view name_v = property_traits<p>::name;
|
||||
|
||||
template <property_type ...Ps>
|
||||
class properties {
|
||||
|
||||
template <property_type p>
|
||||
struct property {
|
||||
using key = std::integral_constant<property_type, p>;
|
||||
constexpr static std::string_view name = name_v<p>;
|
||||
value_type_t<p> value;
|
||||
};
|
||||
std::tuple<property<Ps>...> _props;
|
||||
|
||||
public:
|
||||
|
||||
template <property_type v>
|
||||
constexpr auto& operator[](std::integral_constant<property_type, v>)
|
||||
noexcept {
|
||||
return std::get<property<v>>(_props).value;
|
||||
}
|
||||
|
||||
template <property_type v>
|
||||
constexpr const auto& operator[](std::integral_constant<property_type, v>)
|
||||
const noexcept {
|
||||
return std::get<property<v>>(_props).value;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
using is_apply_on = std::conjunction<
|
||||
std::is_invocable<Func, value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <typename Func>
|
||||
using is_nothrow_apply_on = std::conjunction<
|
||||
std::is_nothrow_invocable<Func, value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <
|
||||
typename Func,
|
||||
std::enable_if_t<is_apply_on<Func>::value, bool> = true
|
||||
>
|
||||
constexpr bool apply_on(uint8_t property_id, Func&& func)
|
||||
noexcept (is_nothrow_apply_on<Func>::value) {
|
||||
return std::apply(
|
||||
[&func, property_id](auto&... ptype) {
|
||||
auto pc = [&func, property_id](auto& px) {
|
||||
using ptype = std::remove_reference_t<decltype(px)>;
|
||||
constexpr typename ptype::key prop;
|
||||
if (prop.value == property_id)
|
||||
std::invoke(func, px.value);
|
||||
return prop.value != property_id;
|
||||
};
|
||||
return (pc(ptype) && ...);
|
||||
},
|
||||
_props
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
using is_visitor = std::conjunction<
|
||||
std::is_invocable_r<bool, Func, decltype(Ps), value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <typename Func>
|
||||
using is_nothrow_visitor = std::conjunction<
|
||||
std::is_nothrow_invocable<Func, decltype(Ps), value_type_t<Ps>&>...
|
||||
>;
|
||||
|
||||
template <
|
||||
typename Func,
|
||||
std::enable_if_t<is_visitor<Func>::value, bool> = true
|
||||
>
|
||||
constexpr bool visit(Func&& func)
|
||||
const noexcept (is_nothrow_visitor<Func>::value) {
|
||||
return std::apply(
|
||||
[&func](const auto&... props) {
|
||||
auto pc = [&func](const auto& px) {
|
||||
using ptype = std::remove_reference_t<decltype(px)>;
|
||||
constexpr typename ptype::key prop;
|
||||
return std::invoke(func, prop, px.value);
|
||||
};
|
||||
return (pc(props) &&...);
|
||||
},
|
||||
_props
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Func,
|
||||
std::enable_if_t<is_visitor<Func>::value, bool> = true
|
||||
>
|
||||
constexpr bool visit(Func&& func)
|
||||
noexcept (is_nothrow_visitor<Func>::value) {
|
||||
return std::apply(
|
||||
[&func](auto&... props) {
|
||||
auto pc = [&func](auto& px) {
|
||||
using ptype = std::remove_reference_t<decltype(px)>;
|
||||
constexpr typename ptype::key prop;
|
||||
return std::invoke(func, prop, px.value);
|
||||
};
|
||||
return (pc(props) && ...);
|
||||
},
|
||||
_props
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5::prop
|
||||
|
||||
#endif // !BOOST_MQTT5_PROPERTY_TYPES_HPP
|
||||
@@ -0,0 +1,495 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_REASON_CODES_HPP
|
||||
#define BOOST_MQTT5_REASON_CODES_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
/// \cond internal
|
||||
namespace reason_codes {
|
||||
|
||||
enum class category : uint8_t {
|
||||
none,
|
||||
connack, puback, pubrec,
|
||||
pubrel, pubcomp, suback,
|
||||
unsuback, auth, disconnect
|
||||
};
|
||||
|
||||
} // end namespace reason_codes
|
||||
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* \brief A class holding Reason Code values originating from Control Packets.
|
||||
*
|
||||
* \details A Reason Code is a one byte unsigned value that indicates the result of an operation.
|
||||
* Reason Codes less than 0x80 indicate successful completion of an operation.
|
||||
* The normal Reason Code for success is 0.
|
||||
* Reason Code values of 0x80 or greater indicate failure.
|
||||
* The \__CONNACK\__, \__PUBACK\__, \__PUBREC\__, \__PUBREL\__, \__PUBCOMP\__, \__DISCONNECT\__
|
||||
* and \__AUTH\__ Control Packets have a single Reason Code as part of the Variable Header.
|
||||
* The \__SUBACK\__ and \__UNSUBACK\__ packets contain a list of one or more Reason Codes in the Payload.
|
||||
*
|
||||
* \see See \__REASON_CODES\__ for a complete list of all possible instances of this class.
|
||||
*/
|
||||
class reason_code {
|
||||
uint8_t _code;
|
||||
reason_codes::category _category { reason_codes::category::none };
|
||||
public:
|
||||
/// \cond INTERNAL
|
||||
constexpr reason_code() : _code(0xff) {}
|
||||
|
||||
constexpr reason_code(uint8_t code, reason_codes::category cat) :
|
||||
_code(code), _category(cat)
|
||||
{}
|
||||
|
||||
constexpr explicit reason_code(uint8_t code) : _code(code) {}
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* \brief Indication if the object holds a Reason Code indicating an error.
|
||||
*
|
||||
* \details Any Reason Code holding a value equal to or greater than 0x80.
|
||||
*/
|
||||
explicit operator bool() const noexcept {
|
||||
return _code >= 0x80;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the byte value of the Reason Code.
|
||||
*/
|
||||
constexpr uint8_t value() const noexcept {
|
||||
return _code;
|
||||
}
|
||||
|
||||
/// Insertion operator.
|
||||
friend std::ostream& operator<<(std::ostream& os, const reason_code& rc) {
|
||||
os << rc.message();
|
||||
return os;
|
||||
}
|
||||
|
||||
/// Operator less than.
|
||||
friend bool operator<(const reason_code& lhs, const reason_code& rhs) {
|
||||
return lhs._code < rhs._code;
|
||||
}
|
||||
|
||||
/// Equality operator.
|
||||
friend bool operator==(const reason_code& lhs, const reason_code& rhs) {
|
||||
return lhs._code == rhs._code && lhs._category == rhs._category;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a message describing the meaning behind the Reason Code.
|
||||
*/
|
||||
std::string message() const {
|
||||
switch (_code) {
|
||||
case 0x00:
|
||||
if (_category == reason_codes::category::suback)
|
||||
return "The subscription is accepted with maximum QoS sent at 0";
|
||||
if (_category == reason_codes::category::disconnect)
|
||||
return "Close the connection normally. Do not send the Will Message";
|
||||
return "The operation completed successfully";
|
||||
case 0x01:
|
||||
return "The subscription is accepted with maximum QoS sent at 1";
|
||||
case 0x02:
|
||||
return "The subscription is accepted with maximum QoS sent at 2";
|
||||
case 0x04:
|
||||
return "The Client wishes to disconnect but requires"
|
||||
"that the Server also publishes its Will Message";
|
||||
case 0x10:
|
||||
return "The message is accepted but there are no subscribers";
|
||||
case 0x11:
|
||||
return "No matching Topic Filter is being used by the Client.";
|
||||
case 0x18:
|
||||
return "Continue the authentication with another step";
|
||||
case 0x19:
|
||||
return "Initiate a re-authentication";
|
||||
case 0x80:
|
||||
return "The Server does not wish to reveal the reason for the"
|
||||
"failure or none of the other Reason Codes apply";
|
||||
case 0x81:
|
||||
return "Data within the packet could not be correctly parsed";
|
||||
case 0x82:
|
||||
return "Data in the packet does not conform to this specification";
|
||||
case 0x83:
|
||||
return "The packet is valid but not accepted by this Server";
|
||||
case 0x84:
|
||||
return "The Server does not support the requested "
|
||||
"version of the MQTT protocol";
|
||||
case 0x85:
|
||||
return "The Client ID is valid but not allowed by this Server";
|
||||
case 0x86:
|
||||
return "The Server does not accept the User Name or Password provided";
|
||||
case 0x87:
|
||||
return "The request is not authorized";
|
||||
case 0x88:
|
||||
return "The MQTT Server is not available";
|
||||
case 0x89:
|
||||
return "The MQTT Server is busy, try again later";
|
||||
case 0x8a:
|
||||
return "The Client has been banned by administrative action";
|
||||
case 0x8b:
|
||||
return "The Server is shutting down";
|
||||
case 0x8c:
|
||||
return "The authentication method is not supported or "
|
||||
"does not match the method currently in use";
|
||||
case 0x8d:
|
||||
return "No packet has been received for 1.5 times the Keepalive time";
|
||||
case 0x8e:
|
||||
return "Another Connection using the same ClientID has connected "
|
||||
"causing this Connection to be closed";
|
||||
case 0x8f:
|
||||
return "The Topic Filer is not malformed, but it is not accepted";
|
||||
case 0x90:
|
||||
return "The Topic Name is not malformed, but it is not accepted";
|
||||
case 0x91:
|
||||
return "The Packet Identifier is already in use";
|
||||
case 0x92:
|
||||
return "The Packet Identifier is not known";
|
||||
case 0x93:
|
||||
return "The Client or Server has received more than the Receive "
|
||||
"Maximum publication for which it has not sent PUBACK or PUBCOMP";
|
||||
case 0x94:
|
||||
return "The Client or Server received a PUBLISH packet containing "
|
||||
"a Topic Alias greater than the Maximum Topic Alias";
|
||||
case 0x95:
|
||||
return "The packet exceeded the maximum permissible size";
|
||||
case 0x96:
|
||||
return "The received data rate is too high";
|
||||
case 0x97:
|
||||
return "An implementation or administrative imposed limit has been exceeded";
|
||||
case 0x98:
|
||||
return "The Connection is closed due to an administrative action";
|
||||
case 0x99:
|
||||
return "The Payload does not match the specified Payload Format Indicator";
|
||||
case 0x9a:
|
||||
return "The Server does not support retained messages";
|
||||
case 0x9b:
|
||||
return "The Server does not support the QoS the Client specified or "
|
||||
"it is greater than the Maximum QoS specified";
|
||||
case 0x9c:
|
||||
return "The Client should temporarily use another server";
|
||||
case 0x9d:
|
||||
return "The Client should permanently use another server";
|
||||
case 0x9e:
|
||||
return "The Server does not support Shared Subscriptions for this Client";
|
||||
case 0x9f:
|
||||
return "The connection rate limit has been exceeded";
|
||||
case 0xa0:
|
||||
return "The maximum connection time authorized for this "
|
||||
"connection has been exceeded";
|
||||
case 0xa1:
|
||||
return "The Server does not support Subscription Identifiers";
|
||||
case 0xa2:
|
||||
return "The Server does not support Wildcard Subscriptions";
|
||||
case 0xff:
|
||||
return "No reason code";
|
||||
default:
|
||||
return "Invalid reason code";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace reason_codes {
|
||||
|
||||
/** No Reason Code. A \ref client::error occurred.*/
|
||||
constexpr reason_code empty {};
|
||||
|
||||
/** The operation completed successfully. */
|
||||
constexpr reason_code success { 0x00 };
|
||||
|
||||
/** Close the connection normally. Do not send the Will Message. */
|
||||
constexpr reason_code normal_disconnection { 0x00, category::disconnect };
|
||||
|
||||
/** The subscription is accepted with maximum QoS sent at 0. */
|
||||
constexpr reason_code granted_qos_0 { 0x00, category::suback };
|
||||
|
||||
/** The subscription is accepted with maximum QoS sent at 1. */
|
||||
constexpr reason_code granted_qos_1 { 0x01 };
|
||||
|
||||
/** The subscription is accepted with maximum QoS sent at 2 */
|
||||
constexpr reason_code granted_qos_2 { 0x02 };
|
||||
|
||||
/** The Client wishes to disconnect but requires that
|
||||
the Server also publishes its Will Message. */
|
||||
constexpr reason_code disconnect_with_will_message { 0x04 };
|
||||
|
||||
/** The message is accepted but there are no subscribers. */
|
||||
constexpr reason_code no_matching_subscribers { 0x10 };
|
||||
|
||||
/** No matching Topic Filter is being used by the Client. */
|
||||
constexpr reason_code no_subscription_existed { 0x11 };
|
||||
|
||||
/** Continue the authentication with another step. */
|
||||
constexpr reason_code continue_authentication { 0x18 };
|
||||
|
||||
/** Initiate a re-authentication. */
|
||||
constexpr reason_code reauthenticate { 0x19 };
|
||||
|
||||
/** The Server does not wish to reveal the reason for the
|
||||
failure or none of the other Reason Codes apply. */
|
||||
constexpr reason_code unspecified_error { 0x80 };
|
||||
|
||||
/** Data within the packet could not be correctly parsed. */
|
||||
constexpr reason_code malformed_packet { 0x81 };
|
||||
|
||||
/** Data in the packet does not conform to this specification. */
|
||||
constexpr reason_code protocol_error { 0x82 };
|
||||
|
||||
/** The packet is valid but not accepted by this Server. */
|
||||
constexpr reason_code implementation_specific_error { 0x83 };
|
||||
|
||||
/** The Server does not support the requested version of the MQTT protocol. */
|
||||
constexpr reason_code unsupported_protocol_version { 0x84 };
|
||||
|
||||
/** The Client ID is valid but not allowed by this Server. */
|
||||
constexpr reason_code client_identifier_not_valid { 0x85 };
|
||||
|
||||
/** The Server does not accept the User Name or Password provided. */
|
||||
constexpr reason_code bad_username_or_password { 0x86 };
|
||||
|
||||
/** The request is not authorized. */
|
||||
constexpr reason_code not_authorized { 0x87 };
|
||||
|
||||
/** The MQTT Server is not available. */
|
||||
constexpr reason_code server_unavailable { 0x88 };
|
||||
|
||||
/** The MQTT Server is busy, try again later. */
|
||||
constexpr reason_code server_busy { 0x89 };
|
||||
|
||||
/** The Client has been banned by administrative action. */
|
||||
constexpr reason_code banned { 0x8a };
|
||||
|
||||
/** The Server is shutting down. */
|
||||
constexpr reason_code server_shutting_down { 0x8b };
|
||||
|
||||
/** The authentication method is not supported or
|
||||
does not match the method currently in use. */
|
||||
constexpr reason_code bad_authentication_method { 0x8c };
|
||||
|
||||
/** No packet has been received for 1.5 times the Keepalive time. */
|
||||
constexpr reason_code keep_alive_timeout { 0x8d };
|
||||
|
||||
/** Another Connection using the same ClientID has connected
|
||||
causing this Connection to be closed. */
|
||||
constexpr reason_code session_taken_over { 0x8e };
|
||||
|
||||
/** The Topic Filter is not malformed, but it is not accepted. */
|
||||
constexpr reason_code topic_filter_invalid { 0x8f };
|
||||
|
||||
/** The Topic Name is not malformed, but it is not accepted. */
|
||||
constexpr reason_code topic_name_invalid { 0x90 };
|
||||
|
||||
/** The Packet Identifier is already in use. */
|
||||
constexpr reason_code packet_identifier_in_use { 0x91 };
|
||||
|
||||
/** The Packet Identifier is not known. */
|
||||
constexpr reason_code packet_identifier_not_found { 0x92 };
|
||||
|
||||
/** The Client or Server has received more than the Receive
|
||||
Maximum publication for which it has not sent PUBACK or PUBCOMP. */
|
||||
constexpr reason_code receive_maximum_exceeded { 0x93 };
|
||||
|
||||
/** The Client or Server received a PUBLISH packet containing
|
||||
a Topic Alias greater than the Maximum Topic Alias. */
|
||||
constexpr reason_code topic_alias_invalid { 0x94 };
|
||||
|
||||
/** The packet exceeded the maximum permissible size. */
|
||||
constexpr reason_code packet_too_large { 0x95 };
|
||||
|
||||
/** The received data rate is too high. */
|
||||
constexpr reason_code message_rate_too_high { 0x96 };
|
||||
|
||||
/** An implementation or administrative imposed limit has been exceeded. */
|
||||
constexpr reason_code quota_exceeded { 0x97 };
|
||||
|
||||
/** The Connection is closed due to an administrative action. */
|
||||
constexpr reason_code administrative_action { 0x98 };
|
||||
|
||||
/** The Payload does not match the specified Payload Format Indicator. */
|
||||
constexpr reason_code payload_format_invalid { 0x99 };
|
||||
|
||||
/** The Server does not support retained messages. */
|
||||
constexpr reason_code retain_not_supported { 0x9a };
|
||||
|
||||
/** The Server does not support the QoS the Client specified or
|
||||
it is greater than the Maximum QoS specified. */
|
||||
constexpr reason_code qos_not_supported { 0x9b };
|
||||
|
||||
/** The Client should temporarily use another server. */
|
||||
constexpr reason_code use_another_server { 0x9c };
|
||||
|
||||
/** The Client should permanently use another server. */
|
||||
constexpr reason_code server_moved { 0x9d };
|
||||
|
||||
/** The Server does not support Shared Subscriptions for this Client. */
|
||||
constexpr reason_code shared_subscriptions_not_supported { 0x9e };
|
||||
|
||||
/** The connection rate limit has been exceeded. */
|
||||
constexpr reason_code connection_rate_exceeded { 0x9f };
|
||||
|
||||
/** The maximum connection time authorized for this
|
||||
connection has been exceeded. */
|
||||
constexpr reason_code maximum_connect_time { 0xa0 };
|
||||
|
||||
/** The Server does not support Subscription Identifiers. */
|
||||
constexpr reason_code subscription_ids_not_supported { 0xa1 };
|
||||
|
||||
/** The Server does not support Wildcard Subscriptions. */
|
||||
constexpr reason_code wildcard_subscriptions_not_supported { 0xa2 };
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::connack, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, unspecified_error, malformed_packet,
|
||||
protocol_error, implementation_specific_error,
|
||||
unsupported_protocol_version, client_identifier_not_valid,
|
||||
bad_username_or_password, not_authorized,
|
||||
server_unavailable, server_busy, banned,
|
||||
bad_authentication_method, topic_name_invalid,
|
||||
packet_too_large, quota_exceeded,
|
||||
payload_format_invalid, retain_not_supported,
|
||||
qos_not_supported, use_another_server,
|
||||
server_moved, connection_rate_exceeded
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::auth, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, continue_authentication, reauthenticate
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<
|
||||
cat == category::puback || cat == category::pubrec, bool
|
||||
> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, no_matching_subscribers, unspecified_error,
|
||||
implementation_specific_error, not_authorized,
|
||||
topic_name_invalid, packet_identifier_in_use,
|
||||
quota_exceeded, payload_format_invalid
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<
|
||||
cat == category::pubrel || cat == category::pubcomp, bool
|
||||
> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, packet_identifier_not_found
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::suback, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
granted_qos_0, granted_qos_1, granted_qos_2,
|
||||
unspecified_error, implementation_specific_error,
|
||||
not_authorized, topic_filter_invalid,
|
||||
packet_identifier_in_use, quota_exceeded,
|
||||
shared_subscriptions_not_supported,
|
||||
subscription_ids_not_supported,
|
||||
wildcard_subscriptions_not_supported
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::unsuback, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
success, no_subscription_existed,
|
||||
unspecified_error, implementation_specific_error,
|
||||
not_authorized, topic_filter_invalid,
|
||||
packet_identifier_in_use
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
template <
|
||||
category cat,
|
||||
std::enable_if_t<cat == category::disconnect, bool> = true
|
||||
>
|
||||
std::pair<reason_code*, size_t> valid_codes() {
|
||||
static reason_code valid_codes[] = {
|
||||
normal_disconnection, unspecified_error,
|
||||
malformed_packet, protocol_error,
|
||||
implementation_specific_error, not_authorized,
|
||||
server_busy, server_shutting_down,
|
||||
keep_alive_timeout, session_taken_over,
|
||||
topic_filter_invalid, topic_name_invalid,
|
||||
receive_maximum_exceeded, topic_alias_invalid,
|
||||
packet_too_large, message_rate_too_high,
|
||||
quota_exceeded, administrative_action,
|
||||
payload_format_invalid, retain_not_supported,
|
||||
qos_not_supported, use_another_server,
|
||||
server_moved, shared_subscriptions_not_supported,
|
||||
connection_rate_exceeded, maximum_connect_time,
|
||||
subscription_ids_not_supported,
|
||||
wildcard_subscriptions_not_supported
|
||||
};
|
||||
static size_t len = sizeof(valid_codes) / sizeof(reason_code);
|
||||
return std::make_pair(valid_codes, len);
|
||||
}
|
||||
|
||||
|
||||
} // end namespace detail
|
||||
} // end namespace reason_codes
|
||||
|
||||
|
||||
template <reason_codes::category cat>
|
||||
std::optional<reason_code> to_reason_code(uint8_t code) {
|
||||
auto [ptr, len] = reason_codes::detail::valid_codes<cat>();
|
||||
auto it = std::lower_bound(ptr, ptr + len, reason_code(code));
|
||||
|
||||
if (it->value() == code)
|
||||
return *it;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
#endif // !BOOST_MQTT5_REASON_CODES_HPP
|
||||
@@ -0,0 +1,368 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_TYPES_HPP
|
||||
#define BOOST_MQTT5_TYPES_HPP
|
||||
|
||||
#include <boost/mqtt5/property_types.hpp>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
/** An alias for `boost::system::error_code`. */
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
/**
|
||||
* \brief A data structure used to store information related to an authority
|
||||
* such as the hostname, port, and path.
|
||||
*/
|
||||
struct authority_path {
|
||||
/** The hostname of the authority as a domain name or an IP address. */
|
||||
std::string host;
|
||||
|
||||
/** The port number used for communication. */
|
||||
std::string port;
|
||||
|
||||
/** Specifies the endpoint path relevant to WebSocket connections. */
|
||||
std::string path;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the Quality of Service (\__QOS__\)
|
||||
* property of the \__PUBLISH\__ packets.
|
||||
*
|
||||
* \details Determines how the \__PUBLISH\__ packets are delivered
|
||||
* from the sender to the receiver.
|
||||
*/
|
||||
enum class qos_e : std::uint8_t {
|
||||
/** The message arrives at the receiver either once or not at all. */
|
||||
at_most_once = 0b00,
|
||||
|
||||
/** Ensures the message arrives at the receiver at least once. */
|
||||
at_least_once = 0b01,
|
||||
|
||||
/** All messages arrive at the receiver exactly once without
|
||||
loss or duplication of the messages. */
|
||||
exactly_once = 0b10
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the \__RETAIN\__ flag in the \__PUBLISH\__ packets.
|
||||
*
|
||||
* \details This flag informs the Server about whether or not it should
|
||||
* store the current message.
|
||||
*/
|
||||
enum class retain_e : std::uint8_t {
|
||||
/** The Server will replace any existing retained message for this Topic
|
||||
with this message. */
|
||||
yes = 0b1,
|
||||
|
||||
/** The Server will not store this message and will not remove or replace
|
||||
any existing retained message. */
|
||||
no = 0b0
|
||||
};
|
||||
|
||||
|
||||
enum class dup_e : std::uint8_t {
|
||||
yes = 0b1, no = 0b0
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the stage of \__ENHANCED_AUTH\__ process.
|
||||
*/
|
||||
enum class auth_step_e {
|
||||
/** The Client needs to send initial authentication data. */
|
||||
client_initial,
|
||||
|
||||
/** Server responded with reason_codes.continue_authentication and possibly
|
||||
* authentication data, the Client needs to send further authentication data.
|
||||
*/
|
||||
server_challenge,
|
||||
|
||||
/** Server responded with reason_codes.success and final
|
||||
* authentication data, which the Client validates.
|
||||
*/
|
||||
server_final
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Representation of the No Local Subscribe Option.
|
||||
*
|
||||
* \details A Subscribe Option indicating whether or not Application Messages
|
||||
* will be forwarded to a connection with a ClientID equal to the ClientID of the
|
||||
* publishing connection.
|
||||
*/
|
||||
enum class no_local_e : std::uint8_t {
|
||||
/** Application Messages can be forwarded to a connection with equal ClientID. */
|
||||
no = 0b0,
|
||||
|
||||
/** Application Messages MUST NOT be forwarded to a connection with equal ClientID. */
|
||||
yes = 0b1
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Representation of the Retain As Published Subscribe Option.
|
||||
*
|
||||
* \details A Subscribe Option indicating whether or not Application Messages forwarded
|
||||
* using this subscription keep the \__RETAIN\__ flag they were published with.
|
||||
*/
|
||||
enum class retain_as_published_e : std::uint8_t {
|
||||
/** Application Messages have the \__RETAIN\__ flag set to 0. */
|
||||
dont = 0b0,
|
||||
|
||||
/** Application Messages keep the \__RETAIN\__ flag they were published with. */
|
||||
retain = 0b1
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Representation of the Retain Handling Subscribe Option.
|
||||
*
|
||||
* \details A Subscribe Option specifying whether retained messages are sent
|
||||
* when the subscription is established.
|
||||
*/
|
||||
enum class retain_handling_e : std::uint8_t {
|
||||
/** Send retained messages at the time of subscribe. */
|
||||
send = 0b00,
|
||||
|
||||
/** Send retained message only if the subscription does not currently exist. */
|
||||
new_subscription_only = 0b01,
|
||||
|
||||
/** Do not send retained messages at the time of subscribe. */
|
||||
not_send = 0b10
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents the \__SUBSCRIBE_OPTIONS\__ associated with each Subscription.
|
||||
*/
|
||||
struct subscribe_options {
|
||||
/// Maximum \__QOS\__ level at which the Server can send Application Messages to the Client.
|
||||
qos_e max_qos = qos_e::exactly_once;
|
||||
|
||||
/// Option determining if Application Messages will be forwarded to a connection with an equal ClientID.
|
||||
no_local_e no_local = no_local_e::yes;
|
||||
|
||||
/// Option determining if Application Message will keep their \__RETAIN\__ flag.
|
||||
retain_as_published_e retain_as_published = retain_as_published_e::retain;
|
||||
|
||||
/// Option determining if retained messages are sent when the subscription is established.
|
||||
retain_handling_e retain_handling = retain_handling_e::new_subscription_only;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A representation of a Topic Subscription consisting of a Topic Filter and
|
||||
* Subscribe Options.
|
||||
*/
|
||||
struct subscribe_topic {
|
||||
/// An UTF-8 Encoded String indicating the Topics to which the Client wants to subscribe.
|
||||
std::string topic_filter;
|
||||
|
||||
/// The \ref subscribe_options associated with the subscription.
|
||||
subscribe_options sub_opts;
|
||||
};
|
||||
|
||||
/// \cond
|
||||
|
||||
class connect_props : public prop::properties<
|
||||
prop::session_expiry_interval_t,
|
||||
prop::receive_maximum_t,
|
||||
prop::maximum_packet_size_t,
|
||||
prop::topic_alias_maximum_t,
|
||||
prop::request_response_information_t,
|
||||
prop::request_problem_information_t,
|
||||
prop::user_property_t,
|
||||
prop::authentication_method_t,
|
||||
prop::authentication_data_t
|
||||
> {};
|
||||
|
||||
class connack_props : public prop::properties<
|
||||
prop::session_expiry_interval_t,
|
||||
prop::receive_maximum_t,
|
||||
prop::maximum_qos_t,
|
||||
prop::retain_available_t,
|
||||
prop::maximum_packet_size_t,
|
||||
prop::assigned_client_identifier_t,
|
||||
prop::topic_alias_maximum_t,
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t,
|
||||
prop::wildcard_subscription_available_t,
|
||||
prop::subscription_identifier_available_t,
|
||||
prop::shared_subscription_available_t,
|
||||
prop::server_keep_alive_t,
|
||||
prop::response_information_t,
|
||||
prop::server_reference_t,
|
||||
prop::authentication_method_t,
|
||||
prop::authentication_data_t
|
||||
> {};
|
||||
|
||||
class publish_props : public prop::properties<
|
||||
prop::payload_format_indicator_t,
|
||||
prop::message_expiry_interval_t,
|
||||
prop::content_type_t,
|
||||
prop::response_topic_t,
|
||||
prop::correlation_data_t,
|
||||
prop::subscription_identifier_t,
|
||||
prop::topic_alias_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
// puback, pubcomp
|
||||
class puback_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class pubcomp_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class pubrec_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class pubrel_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
|
||||
class subscribe_props : public prop::properties<
|
||||
prop::subscription_identifier_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class suback_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class unsubscribe_props : public prop::properties<
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class unsuback_props : public prop::properties<
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
class disconnect_props : public prop::properties<
|
||||
prop::session_expiry_interval_t,
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t,
|
||||
prop::server_reference_t
|
||||
> {};
|
||||
|
||||
class auth_props : public prop::properties<
|
||||
prop::authentication_method_t,
|
||||
prop::authentication_data_t,
|
||||
prop::reason_string_t,
|
||||
prop::user_property_t
|
||||
> {};
|
||||
|
||||
|
||||
class will_props : public prop::properties<
|
||||
prop::will_delay_interval_t,
|
||||
prop::payload_format_indicator_t,
|
||||
prop::message_expiry_interval_t,
|
||||
prop::content_type_t,
|
||||
prop::response_topic_t,
|
||||
prop::correlation_data_t,
|
||||
prop::user_property_t
|
||||
>{};
|
||||
|
||||
/// \endcond
|
||||
|
||||
/**
|
||||
* \brief Represents the Will Message.
|
||||
*
|
||||
* \details A Will Message is an Application Message that
|
||||
* the Broker should publish after the Network Connection is closed
|
||||
* in cases where the Network Connection is not closed normally.
|
||||
*/
|
||||
class will : public will_props {
|
||||
std::string _topic;
|
||||
std::string _message;
|
||||
qos_e _qos; retain_e _retain;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs an empty Will Message.
|
||||
*
|
||||
* \attention Do not use!
|
||||
* An empty Will Message results in an empty Topic Name which is not valid.
|
||||
* Internal uses only.
|
||||
*/
|
||||
will() = default;
|
||||
|
||||
/**
|
||||
* \brief Construct a Will Message.
|
||||
*
|
||||
* \param topic Topic, identification of the information channel to which
|
||||
* the Will Message will be published.
|
||||
* \param message The message that will be published.
|
||||
* \param qos The \ref qos_e level used when publishing the Will Message.
|
||||
* \param retain The \ref retain_e flag specifying if the Will Message
|
||||
* is to be retained when it is published.
|
||||
*/
|
||||
will(
|
||||
std::string topic, std::string message,
|
||||
qos_e qos = qos_e::at_most_once, retain_e retain = retain_e::no
|
||||
) :
|
||||
_topic(std::move(topic)), _message(std::move(message)),
|
||||
_qos(qos), _retain(retain)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Construct a Will Message.
|
||||
*
|
||||
* \param topic Topic name, identification of the information channel to which
|
||||
* the Will Message will be published.
|
||||
* \param message The message that will be published.
|
||||
* \param qos The \ref qos_e level used when publishing the Will Message.
|
||||
* \param retain The \ref retain_e flag specifying if the Will Message
|
||||
* is to be retained when it is published.
|
||||
* \param props Will properties.
|
||||
*/
|
||||
will(
|
||||
std::string topic, std::string message,
|
||||
qos_e qos, retain_e retain, will_props props
|
||||
) :
|
||||
will_props(std::move(props)),
|
||||
_topic(std::move(topic)), _message(std::move(message)),
|
||||
_qos(qos), _retain(retain)
|
||||
{}
|
||||
|
||||
/// Get the Topic Name.
|
||||
std::string_view topic() const {
|
||||
return _topic;
|
||||
}
|
||||
|
||||
/// Get the Application Message.
|
||||
std::string_view message() const {
|
||||
return _message;
|
||||
}
|
||||
|
||||
/// Get the \ref qos_e.
|
||||
constexpr qos_e qos() const {
|
||||
return _qos;
|
||||
}
|
||||
|
||||
/// Get the \ref retain_e.
|
||||
constexpr retain_e retain() const {
|
||||
return _retain;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
#endif // !BOOST_MQTT5_TYPES_HPP
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MQTT5_WEBSOCKET_HPP
|
||||
#define BOOST_MQTT5_WEBSOCKET_HPP
|
||||
|
||||
#include <boost/mqtt5/types.hpp>
|
||||
|
||||
#include <boost/beast/http/field.hpp>
|
||||
#include <boost/beast/websocket/rfc6455.hpp>
|
||||
#include <boost/beast/websocket/stream.hpp>
|
||||
|
||||
namespace boost::mqtt5 {
|
||||
|
||||
// Trait definition for Beast
|
||||
template <typename Stream>
|
||||
struct ws_handshake_traits<boost::beast::websocket::stream<Stream>> {
|
||||
|
||||
template <typename CompletionToken>
|
||||
static decltype(auto) async_handshake(
|
||||
boost::beast::websocket::stream<Stream>& stream,
|
||||
authority_path ap, CompletionToken&& token
|
||||
) {
|
||||
using namespace boost::beast;
|
||||
|
||||
// Set suggested timeout settings for the websocket
|
||||
stream.set_option(
|
||||
websocket::stream_base::timeout::suggested(role_type::client)
|
||||
);
|
||||
|
||||
stream.binary(true);
|
||||
|
||||
// Set a decorator to change the User-Agent of the handshake
|
||||
stream.set_option(websocket::stream_base::decorator(
|
||||
[](websocket::request_type& req) {
|
||||
req.set(http::field::sec_websocket_protocol, "mqtt");
|
||||
req.set(http::field::user_agent, "boost.mqtt");
|
||||
})
|
||||
);
|
||||
|
||||
stream.async_handshake(
|
||||
ap.host + ':' + ap.port, ap.path,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace boost::mqtt5
|
||||
|
||||
#endif // !BOOST_MQTT5_WEBSOCKET_HPP
|
||||
Reference in new issue
Block a user