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:
Korina Šimičević committed 2025-01-14 08:57:44 +01:00
1 parent 1225cc778a
commit afc270f10e
136 files changed
+17834 -17859

No files matched your search

-19
View File
@@ -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
-218
View File
@@ -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
-76
View File
@@ -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
-116
View File
@@ -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
-168
View File
@@ -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
-243
View File
@@ -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
-303
View File
@@ -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
-524
View File
@@ -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
-436
View File
@@ -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
-317
View File
@@ -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
-242
View File
@@ -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
-110
View File
@@ -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
-219
View File
@@ -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
-152
View File
@@ -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
-140
View File
@@ -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
-251
View File
@@ -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
-246
View File
@@ -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
-142
View File
@@ -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
-100
View File
@@ -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
-336
View File
@@ -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
-277
View File
@@ -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
-118
View File
@@ -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
-257
View File
@@ -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
-981
View File
@@ -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
-253
View File
@@ -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
-495
View File
@@ -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
-368
View File
@@ -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
-54
View File
@@ -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
+19
View File
@@ -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
+218
View File
@@ -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
@@ -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
+76
View File
@@ -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
@@ -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
+116
View File
@@ -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
+168
View File
@@ -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
+243
View File
@@ -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
+303
View File
@@ -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
+524
View File
@@ -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
+436
View File
@@ -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
+317
View File
@@ -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
+242
View File
@@ -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
+110
View File
@@ -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
+219
View File
@@ -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
+152
View File
@@ -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
+139
View File
@@ -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
+252
View File
@@ -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
+247
View File
@@ -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
+142
View File
@@ -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
+100
View File
@@ -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
+335
View File
@@ -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
+277
View File
@@ -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
+118
View File
@@ -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
+258
View File
@@ -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
+981
View File
@@ -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
+253
View File
@@ -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
+495
View File
@@ -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
+368
View File
@@ -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
+54
View File
@@ -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