#ifndef ASYNC_MQTT5_REPLIES_HPP #define ASYNC_MQTT5_REPLIES_HPP #include #include #include #include #include #include #include namespace async_mqtt5::detail { namespace asio = boost::asio; class replies { using Signature = void (error_code, byte_citer, byte_citer); static constexpr auto max_reply_time = std::chrono::seconds(20); class handler_type : public asio::any_completion_handler { using base = asio::any_completion_handler; control_code_e _code; uint16_t _packet_id; std::chrono::time_point _ts; public: template handler_type(control_code_e code, uint16_t pid, H&& handler) : base(std::forward(handler)), _code(code), _packet_id(pid), _ts(std::chrono::system_clock::now()) {} handler_type(handler_type&& other) noexcept : base(static_cast(other)), _code(other._code), _packet_id(other._packet_id), _ts(other._ts) {} handler_type& operator=(handler_type&& other) noexcept { base::operator=(static_cast(other)); _code = other._code; _packet_id = other._packet_id; _ts = other._ts; return *this; } uint16_t packet_id() const noexcept { return _packet_id; } control_code_e code() const noexcept { return _code; } auto time() const noexcept { return _ts; } }; using handlers = std::vector; handlers _handlers; struct fast_reply { control_code_e code; uint16_t packet_id; std::unique_ptr packet; }; using fast_replies = std::vector; fast_replies _fast_replies; public: template 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()) { std::move(*dup_handler_ptr)( asio::error::operation_aborted, byte_citer {}, byte_citer {} ); _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( initiation, token, std::ref(*this), code, packet_id ); } auto fdata = std::move(*freply); _fast_replies.erase(freply); auto initiation = []( auto handler, std::unique_ptr packet ) { auto ex = asio::get_associated_executor(handler); byte_citer first = packet->cbegin(); byte_citer last = packet->cend(); asio::post( ex, asio::consign( asio::append(std::move(handler), error_code{}, first, last), std::move(packet) ) ); }; return asio::async_initiate( initiation, token, std::move(fdata.packet) ); } 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(first, last) }); return; } auto handler = std::move(*handler_ptr); _handlers.erase(handler_ptr); std::move(handler)(ec, first, last); } void resend_unanswered() { auto ua = std::move(_handlers); for (auto& h : ua) std::move(h)(asio::error::try_again, byte_citer {}, byte_citer {}); } void cancel_unanswered() { auto ua = std::move(_handlers); for (auto& h : ua) std::move(h)( asio::error::operation_aborted, byte_citer {}, byte_citer {} ); } 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) { std::move(*it)( asio::error::operation_aborted, byte_citer {}, byte_citer {} ); 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