/* * Copyright (c) 2017-2023 zhllxt * * author : zhllxt * email : 37792738@qq.com * * chinese : http://blog.mcxiaoke.com/mqtt/protocol/MQTT-3.1.1-CN.html * english : http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef __ASIO2_MQTT_PROTOCOL_V4_HPP__ #define __ASIO2_MQTT_PROTOCOL_V4_HPP__ #if defined(_MSC_VER) && (_MSC_VER >= 1200) #pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include namespace asio2::mqtt::v4 { static constexpr std::uint8_t version_number = asio2::detail::to_underlying(mqtt::version::v4); enum class connect_reason_code : std::uint8_t { /* 0 0x00 Connection Accepted : */ success = 0, /* 1 0x01 Connection Refused : */ unacceptable_protocol_version = 1, /* 2 0x02 Connection Refused : */ identifier_rejected = 2, /* 3 0x03 Connection Refused : */ server_unavailable = 3, /* 4 0x04 Connection Refused : */ bad_user_name_or_password = 4, /* 5 0x05 Connection Refused : */ not_authorized = 5, /* 6 - 255 Reserved for future use */ }; /** * CONNECT - Client requests a connection to a Server * * After a Network Connection is established by a Client to a Server, the first packet sent from the * Client to the Server MUST be a CONNECT packet [MQTT-3.1.0-1]. * * A Client can only send the CONNECT Packet once over a Network Connection. The Server MUST process * a second CONNECT Packet sent from a Client as a protocol violation and disconnect the Client * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028 */ class connect : public fixed_header { public: connect() : fixed_header(control_packet_type::connect) { update_remain_length(); } connect(utf8_string::value_type clientid) : fixed_header(control_packet_type::connect) { client_id(std::move(clientid)); update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline connect& serialize(Container& buffer) { fixed_header::serialize(buffer); protocol_name_ .serialize(buffer); protocol_version_ .serialize(buffer); connect_flags_.byte .serialize(buffer); keep_alive_ .serialize(buffer); client_id_ .serialize(buffer); if (will_topic_ ) will_topic_ ->serialize(buffer); if (will_payload_) will_payload_->serialize(buffer); if (username_ ) username_ ->serialize(buffer); if (password_ ) password_ ->serialize(buffer); return (*this); } inline connect& deserialize(std::string_view& data) { fixed_header::deserialize(data); protocol_name_ .deserialize(data); protocol_version_ .deserialize(data); connect_flags_.byte .deserialize(data); keep_alive_ .deserialize(data); client_id_ .deserialize(data); if (has_will()) { utf8_string will_topic{}; will_topic.deserialize(data); will_topic_ = std::move(will_topic); binary_data will_payload{}; will_payload.deserialize(data); will_payload_ = std::move(will_payload); } if (has_username()) { utf8_string username{}; username.deserialize(data); username_ = std::move(username); } if (has_password()) { binary_data password{}; password.deserialize(data); password_ = std::move(password); } update_remain_length(); return (*this); } inline std::uint8_t protocol_version() const { return (protocol_version_ ); } inline bool clean_start () const { return (connect_flags_.bits.clean_session); } inline bool clean_session () const { return (connect_flags_.bits.clean_session); } inline bool has_will () const { return (connect_flags_.bits.will_flag ); } inline qos_type will_qos () const { return static_cast(connect_flags_.bits.will_qos ); } inline bool will_retain () const { return (connect_flags_.bits.will_retain ); } inline bool has_password () const { return (connect_flags_.bits.password_flag); } inline bool has_username () const { return (connect_flags_.bits.username_flag); } inline connect& clean_start (bool v) { connect_flags_.bits.clean_session = v; return (*this); } inline connect& clean_session (bool v) { connect_flags_.bits.clean_session = v; return (*this); } inline two_byte_integer::value_type keep_alive () const { return keep_alive_ ; } inline utf8_string::view_type client_id () const { return client_id_ .data_view(); } inline utf8_string::view_type will_topic () const { return will_topic_ ? will_topic_ ->data_view() : ""; } inline binary_data::view_type will_payload () const { return will_payload_ ? will_payload_->data_view() : ""; } inline utf8_string::view_type username () const { return username_ ? username_ ->data_view() : ""; } inline binary_data::view_type password () const { return password_ ? password_ ->data_view() : ""; } inline connect& keep_alive(two_byte_integer::value_type v) { keep_alive_ = std::move(v); return (*this); } template inline connect& client_id(String&& v) { client_id_ = std::forward(v); update_remain_length(); return (*this); } template inline connect& username(String&& v) { username_ = std::forward(v); connect_flags_.bits.username_flag = true; update_remain_length(); return (*this); } inline connect& password(binary_data::value_type v) { password_ = std::move(v); connect_flags_.bits.password_flag = true; update_remain_length(); return (*this); } template inline connect& will_attributes(String1&& topic, String2&& payload, QosOrInt qos = qos_type::at_most_once, bool retain = false) { will_topic_ = std::forward(topic); will_payload_ = std::forward(payload); connect_flags_.bits.will_flag = true; connect_flags_.bits.will_qos = static_cast(qos); connect_flags_.bits.will_retain = retain; update_remain_length(); return (*this); } inline bool will_topic_has_value() const noexcept { return will_topic_.has_value(); } inline bool username_has_value () const noexcept { return username_ .has_value(); } inline bool password_has_value () const noexcept { return password_ .has_value(); } inline connect& update_remain_length() { remain_length_ = static_cast(0 + protocol_name_ .required_size() + protocol_version_ .required_size() + connect_flags_.byte .required_size() + keep_alive_ .required_size() + client_id_ .required_size() + (will_topic_ ? will_topic_ ->required_size() : 0) + (will_payload_ ? will_payload_->required_size() : 0) + (username_ ? username_ ->required_size() : 0) + (password_ ? password_ ->required_size() : 0) ); return (*this); } protected: // The Protocol Name is a UTF-8 Encoded String that represents the protocol name "MQTT". // The string, its offset and length will not be changed by future versions of the MQTT specification. utf8_string protocol_name_{ "MQTT" }; // The 8 bit unsigned value that represents the revision level of the protocol used by the Client. // The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04). one_byte_integer protocol_version_{ 0x04 }; union { one_byte_integer byte{ 0 }; // all connect flags #if ASIO2_ENDIAN_BIG_BYTE struct { bool username_flag : 1; // User Name Flag bool password_flag : 1; // Password Flag bool will_retain : 1; // will retain setting std::uint8_t will_qos : 2; // will QoS value bool will_flag : 1; // will flag bool clean_session : 1; // Clean Session flag std::uint8_t reserved : 1; // unused } bits; #else struct { std::uint8_t reserved : 1; // unused bool clean_session : 1; // Clean Session flag bool will_flag : 1; // will flag std::uint8_t will_qos : 2; // will QoS value bool will_retain : 1; // will retain setting bool password_flag : 1; // Password Flag bool username_flag : 1; // User Name Flag } bits; #endif } connect_flags_{}; // connect flags byte // The Keep Alive is a time interval measured in seconds. Expressed as a 16-bit word // Default to 60 seconds two_byte_integer keep_alive_ { 60 }; // The Client Identifier (ClientID) identifies the Client to the Server. // Each Client connecting to the Server has a unique ClientID. utf8_string client_id_ {}; // If the Will Flag is set to 1, the Will Topic is the next field in the Payload. std::optional will_topic_ {}; // If the Will Flag is set to 1 the Will Payload is the next field in the Payload. std::optional will_payload_{}; // If the User Name Flag is set to 1, the User Name is the next field in the Payload. std::optional username_ {}; // If the Password Flag is set to 1, the Password is the next field in the Payload. std::optional password_ {}; }; /** * CONNACK - Acknowledge connection request * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033 */ class connack : public fixed_header { public: connack() : fixed_header(control_packet_type::connack) { update_remain_length(); } explicit connack(bool session_present, std::uint8_t reason_code) : fixed_header(control_packet_type::connack) , reason_code_(reason_code) { connack_flags_.bits.session_present = session_present; update_remain_length(); } explicit connack(bool session_present, connect_reason_code reason_code) : fixed_header(control_packet_type::connack) , reason_code_(asio2::detail::to_underlying(reason_code)) { connack_flags_.bits.session_present = session_present; update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline connack& serialize(Container& buffer) { fixed_header::serialize(buffer); connack_flags_.byte .serialize(buffer); reason_code_ .serialize(buffer); return (*this); } inline connack& deserialize(std::string_view& data) { fixed_header::deserialize(data); connack_flags_.byte .deserialize(data); reason_code_ .deserialize(data); update_remain_length(); return (*this); } inline bool session_present() const { return connack_flags_.bits.session_present; } inline connect_reason_code reason_code () const { return static_cast(reason_code_.value()); } inline connack & session_present(bool v) { connack_flags_.bits.session_present = v; return (*this); } inline connack & reason_code (std::uint8_t v) { reason_code_ = v; return (*this); } inline connack & reason_code (connect_reason_code v) { reason_code_ = asio2::detail::to_underlying(v); return (*this); } inline connack& update_remain_length() { remain_length_ = static_cast(0 + connack_flags_.byte .required_size() + reason_code_ .required_size() ); return (*this); } protected: union { one_byte_integer byte{ 0 }; // all connack flags #if ASIO2_ENDIAN_BIG_BYTE struct { std::uint8_t reserved : 7; bool session_present : 1; // session found on the server? } bits; #else struct { bool session_present : 1; // session found on the server? std::uint8_t reserved : 7; } bits; #endif } connack_flags_{}; // connack flags // Byte 2 in the Variable Header is the Connect Reason Code. one_byte_integer reason_code_{ 0 }; }; /** * PUBLISH - Publish message * * A PUBLISH packet is sent from a Client to a Server or from a Server to a Client to transport an Application Message. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037 */ class publish : public fixed_header { public: publish() : fixed_header(control_packet_type::publish) { update_remain_length(); } template, int> = 0> explicit publish(String1&& topic_name, String2&& payload, QosOrInt qos, bool dup = false, bool retain = false) : fixed_header(control_packet_type::publish) , topic_name_ (std::forward(topic_name)) , payload_ (std::forward(payload )) { type_and_flags_.bits.dup = dup; type_and_flags_.bits.qos = static_cast(qos); type_and_flags_.bits.retain = retain; update_remain_length(); } template, int> = 0> explicit publish(std::uint16_t pid, String1&& topic_name, String2&& payload, QosOrInt qos, bool dup = false, bool retain = false) : fixed_header(control_packet_type::publish) , topic_name_ (std::forward(topic_name)) , packet_id_ (pid) , payload_ (std::forward(payload )) { type_and_flags_.bits.dup = dup; type_and_flags_.bits.qos = static_cast(qos); type_and_flags_.bits.retain = retain; ASIO2_ASSERT(type_and_flags_.bits.qos > std::uint8_t(0)); update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline publish& serialize(Container& buffer) { fixed_header::serialize(buffer); // The Packet Identifier field is only present in PUBLISH packets where the QoS level is 1 or 2. // A PUBLISH packet MUST NOT contain a Packet Identifier if its QoS value is set to 0 if ((type_and_flags_.bits.qos == std::uint8_t(0) && packet_id_.has_value()) || (type_and_flags_.bits.qos > std::uint8_t(0) && !packet_id_.has_value())) { ASIO2_ASSERT(false); asio2::set_last_error(mqtt::make_error_code(mqtt::error::malformed_packet)); } topic_name_.serialize(buffer); if (type_and_flags_.bits.qos > std::uint8_t(0) && packet_id_.has_value()) { packet_id_->serialize(buffer); } payload_.serialize(buffer); return (*this); } inline publish& deserialize(std::string_view& data) { fixed_header::deserialize(data); topic_name_.deserialize(data); // A PUBLISH Packet MUST NOT contain a Packet Identifier if its QoS value is set to 0 [MQTT-2.3.1-5]. if (type_and_flags_.bits.qos == 1 || type_and_flags_.bits.qos == 2) { two_byte_integer packet_id{}; packet_id.deserialize(data); packet_id_ = packet_id; } payload_ .deserialize(data); update_remain_length(); return (*this); } inline bool dup () const { return (type_and_flags_.bits.dup ); } inline qos_type qos () const { return static_cast(type_and_flags_.bits.qos ); } inline bool retain() const { return (type_and_flags_.bits.retain); } inline publish & dup (bool v) { type_and_flags_.bits.dup = v; return (*this); } template inline publish & qos (QosOrInt v) { type_and_flags_.bits.qos = static_cast(v); return (*this); } inline publish & retain(bool v) { type_and_flags_.bits.retain = v; return (*this); } inline utf8_string::view_type topic_name() const { return topic_name_.data_view(); } inline two_byte_integer::value_type packet_id () const { return packet_id_ ? packet_id_->value() : 0; } inline application_message::view_type payload () const { return payload_.data_view() ; } inline publish & packet_id (std::uint16_t v) { packet_id_ = v ; update_remain_length(); return (*this); } template inline publish & topic_name(String&& v) { topic_name_ = std::forward(v); update_remain_length(); return (*this); } template inline publish & payload (String&& v) { payload_ = std::forward(v); update_remain_length(); return (*this); } inline bool has_packet_id() const noexcept { return packet_id_.has_value(); } inline publish& update_remain_length() { remain_length_ = static_cast(0 + topic_name_.required_size() + (packet_id_ ? packet_id_->required_size() : 0) + payload_ .required_size() ); return (*this); } protected: // The Topic Name identifies the information channel to which Payload data is published. utf8_string topic_name_{}; // The Packet Identifier field is only present in PUBLISH packets where the QoS level is 1 or 2. // a Two Byte Integer Packet Identifier. std::optional packet_id_ {}; // The Payload contains the Application Message that is being published. // The content and format of the data is application specific. // The length of the Payload can be calculated by subtracting the length of the Variable Header // from the Remaining Length field that is in the Fixed Header. // It is valid for a PUBLISH packet to contain a zero length Payload. application_message payload_ {}; }; /** * PUBACK - Publish acknowledgement * * A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718043 */ class puback : public fixed_header { public: puback() : fixed_header(control_packet_type::puback) { update_remain_length(); } explicit puback(std::uint16_t packet_id) : fixed_header(control_packet_type::puback) , packet_id_ (packet_id) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline puback& serialize(Container& buffer) { fixed_header::serialize(buffer); packet_id_ .serialize(buffer); return (*this); } inline puback& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline puback & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } inline puback& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() ); return (*this); } protected: // This contains the Packet Identifier from the PUBLISH Packet that is being acknowledged. two_byte_integer packet_id_ {}; }; /** * PUBREC - Publish received (QoS 2 publish received, part 1) * * A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. * It is the second packet of the QoS 2 protocol exchange. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718048 */ class pubrec : public fixed_header { public: pubrec() : fixed_header(control_packet_type::pubrec) { update_remain_length(); } explicit pubrec(std::uint16_t packet_id) : fixed_header(control_packet_type::pubrec) , packet_id_ (packet_id) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline pubrec& serialize(Container& buffer) { fixed_header::serialize(buffer); packet_id_ .serialize(buffer); return (*this); } inline pubrec& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline pubrec & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } inline pubrec& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() ); return (*this); } protected: // The variable header contains the Packet Identifier from the PUBLISH Packet that is being acknowledged. two_byte_integer packet_id_ {}; }; /** * PUBREL - Publish release (QoS 2 publish received, part 2) * * A PUBREL Packet is the response to a PUBREC Packet. * It is the third packet of the QoS 2 protocol exchange. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718053 */ class pubrel : public fixed_header { public: pubrel() : fixed_header(control_packet_type::pubrel) { // Bits 3,2,1 and 0 of the Fixed Header in the PUBREL packet are reserved and MUST be // set to 0,0,1 and 0 respectively. // The Server MUST treat any other value as malformed and close the Network Connection [MQTT-3.6.1-1]. type_and_flags_.reserved.bit1 = 1; update_remain_length(); } explicit pubrel(std::uint16_t packet_id) : fixed_header(control_packet_type::pubrel) , packet_id_ (packet_id) { type_and_flags_.reserved.bit1 = 1; update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline pubrel& serialize(Container& buffer) { fixed_header::serialize(buffer); packet_id_ .serialize(buffer); return (*this); } inline pubrel& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline pubrel & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } inline pubrel& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() ); return (*this); } protected: // The variable header contains the same Packet Identifier as the PUBREC Packet that is being acknowledged. two_byte_integer packet_id_ {}; }; /** * PUBCOMP - Publish complete (QoS 2 publish received, part 3) * * The PUBCOMP Packet is the response to a PUBREL Packet. * It is the fourth and final packet of the QoS 2 protocol exchange. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718058 */ class pubcomp : public fixed_header { public: pubcomp() : fixed_header(control_packet_type::pubcomp) { update_remain_length(); } explicit pubcomp(std::uint16_t packet_id) : fixed_header(control_packet_type::pubcomp) , packet_id_ (packet_id) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline pubcomp& serialize(Container& buffer) { fixed_header::serialize(buffer); packet_id_ .serialize(buffer); return (*this); } inline pubcomp& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline pubcomp & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } inline pubcomp& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() ); return (*this); } protected: // The variable header contains the same Packet Identifier as the PUBREL Packet that is being acknowledged. two_byte_integer packet_id_ {}; }; /** * SUBSCRIBE - Subscribe to topics * * The SUBSCRIBE Packet is sent from the Client to the Server to create one or more Subscriptions. * Each Subscription registers a Client's interest in one or more Topics. * The Server sends PUBLISH Packets to the Client in order to forward Application Messages that were * published to Topics that match these Subscriptions. * The SUBSCRIBE Packet also specifies (for each Subscription) the maximum QoS with which the Server * can send Application Messages to the Client. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718063 */ class subscribe : public fixed_header { public: subscribe() : fixed_header(control_packet_type::subscribe) { // Bits 3,2,1 and 0 of the fixed header of the SUBSCRIBE Control Packet are reserved and MUST be // set to 0,0,1 and 0 respectively. The Server MUST treat any other value as malformed and close // the Network Connection [MQTT-3.8.1-1]. type_and_flags_.reserved.bit1 = 1; update_remain_length(); } explicit subscribe(std::uint16_t packet_id) : fixed_header(control_packet_type::subscribe) , packet_id_ (packet_id) { type_and_flags_.reserved.bit1 = 1; update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline subscribe& serialize(Container& buffer) { update_remain_length(); fixed_header::serialize(buffer); packet_id_ .serialize(buffer); subscriptions_.serialize(buffer); return (*this); } inline subscribe& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); subscriptions_.deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline subscriptions_set& subscriptions() { return subscriptions_ ; } inline subscriptions_set const& subscriptions() const { return subscriptions_ ; } inline subscribe & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } template inline subscribe& add_subscriptions(Subscriptions&&... Subscripts) { subscriptions_.add(std::forward(Subscripts)...); update_remain_length(); return (*this); } inline subscribe& erase_subscription(std::string_view topic_filter) { subscriptions_.erase(topic_filter); update_remain_length(); return (*this); } inline subscribe& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() + subscriptions_.required_size() ); return (*this); } protected: // The variable header contains a Packet Identifier. two_byte_integer packet_id_ {}; // The payload of a SUBSCRIBE Packet contains a list of Topic Filters indicating the Topics // to which the Client wants to subscribe. The Topic Filters in a SUBSCRIBE packet payload // MUST be UTF-8 encoded strings as defined in Section 1.5.3 [MQTT-3.8.3-1]. A Server SHOULD // support Topic filters that contain the wildcard characters defined in Section 4.7.1. If // it chooses not to support topic filters that contain wildcard characters it MUST reject // any Subscription request whose filter contains them [MQTT-3.8.3-2]. Each filter is // followed by a byte called the Requested QoS. This gives the maximum QoS level at which // the Server can send Application Messages to the Client. subscriptions_set subscriptions_{}; }; /** * SUBACK - Subscribe acknowledgement * * A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing of a SUBSCRIBE Packet. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718068 */ class suback : public fixed_header { public: suback() : fixed_header(control_packet_type::suback) { update_remain_length(); } explicit suback(std::uint16_t packet_id) : fixed_header(control_packet_type::suback) , packet_id_ (packet_id) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline suback& serialize(Container& buffer) { update_remain_length(); fixed_header::serialize(buffer); packet_id_ .serialize(buffer); reason_codes_ .serialize(buffer); return (*this); } inline suback& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); reason_codes_ .deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline one_byte_integer_set & reason_codes () { return reason_codes_ ; } inline one_byte_integer_set const& reason_codes () const { return reason_codes_ ; } inline suback & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } template inline suback& add_reason_codes(Integers... Ints) { reason_codes_.add(static_cast(Ints)...); update_remain_length(); return (*this); } inline suback& erase_reason_code(std::size_t index) { reason_codes_.erase(index); update_remain_length(); return (*this); } inline suback& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() + reason_codes_ .required_size() ); return (*this); } protected: // The variable header contains the Packet Identifier from the SUBSCRIBE Packet that is being acknowledged. two_byte_integer packet_id_ {}; // The payload contains a list of return codes. Each return code corresponds to a Topic Filter // in the SUBSCRIBE Packet being acknowledged. The order of return codes in the SUBACK Packet // MUST match the order of Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1]. one_byte_integer_set reason_codes_{}; }; /** * UNSUBSCRIBE - Unsubscribe from topics * * An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718072 */ class unsubscribe : public fixed_header { public: unsubscribe() : fixed_header(control_packet_type::unsubscribe) { // Bits 3,2,1 and 0 of the Fixed Header of the UNSUBSCRIBE packet are reserved and MUST // be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as malformed // and close the Network Connection [MQTT-3.10.1-1]. type_and_flags_.reserved.bit1 = 1; update_remain_length(); } template explicit unsubscribe(std::uint16_t packet_id, Strings&&... topic_filters) : fixed_header (control_packet_type::unsubscribe) , packet_id_ (packet_id) , topic_filters_(std::forward(topic_filters)...) { type_and_flags_.reserved.bit1 = 1; update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline unsubscribe& serialize(Container& buffer) { update_remain_length(); fixed_header::serialize(buffer); packet_id_ .serialize(buffer); topic_filters_.serialize(buffer); return (*this); } inline unsubscribe& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); topic_filters_.deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline utf8_string_set & topic_filters() { return topic_filters_ ; } inline utf8_string_set const& topic_filters() const { return topic_filters_ ; } inline unsubscribe & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } template inline unsubscribe& add_topic_filters(Strings&&... Strs) { topic_filters_.add(std::forward(Strs)...); update_remain_length(); return (*this); } inline unsubscribe& erase_topic_filter(std::string_view topic_filter) { topic_filters_.erase(topic_filter); update_remain_length(); return (*this); } inline unsubscribe& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() + topic_filters_.required_size() ); return (*this); } protected: // The variable header contains a Packet Identifier. two_byte_integer packet_id_ {}; // The payload for the UNSUBSCRIBE Packet contains the list of Topic Filters that the Client // wishes to unsubscribe from. The Topic Filters in an UNSUBSCRIBE packet MUST be UTF-8 // encoded strings as defined in Section 1.5.3, packed contiguously [MQTT-3.10.3-1]. // The Payload of an UNSUBSCRIBE packet MUST contain at least one Topic Filter.An UNSUBSCRIBE // packet with no payload is a protocol violation[MQTT - 3.10.3 - 2].See section 4.8 for // information about handling errors. utf8_string_set topic_filters_{}; }; /** * UNSUBACK - Unsubscribe acknowledgement * * The UNSUBACK packet is sent by the Server to the Client to confirm receipt of an UNSUBSCRIBE packet. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718077 */ class unsuback : public fixed_header { public: unsuback() : fixed_header(control_packet_type::unsuback) { update_remain_length(); } explicit unsuback(std::uint16_t packet_id) : fixed_header(control_packet_type::unsuback) , packet_id_ (packet_id) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline unsuback& serialize(Container& buffer) { fixed_header::serialize(buffer); packet_id_ .serialize(buffer); return (*this); } inline unsuback& deserialize(std::string_view& data) { fixed_header::deserialize(data); packet_id_ .deserialize(data); update_remain_length(); return (*this); } inline two_byte_integer::value_type packet_id () const { return packet_id_ .value() ; } inline unsuback & packet_id (std::uint16_t v) { packet_id_ = v; return (*this); } inline unsuback& update_remain_length() { remain_length_ = static_cast(0 + packet_id_ .required_size() ); return (*this); } protected: // The variable header contains the Packet Identifier of the UNSUBSCRIBE Packet that is being acknowledged. two_byte_integer packet_id_ {}; // The UNSUBACK Packet has no payload. }; /** * PINGREQ - PING request * * The PINGREQ packet is sent from a Client to the Server. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718081 */ class pingreq : public fixed_header { public: pingreq() : fixed_header(control_packet_type::pingreq) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline pingreq& serialize(Container& buffer) { fixed_header::serialize(buffer); return (*this); } inline pingreq& deserialize(std::string_view& data) { fixed_header::deserialize(data); update_remain_length(); return (*this); } inline pingreq& update_remain_length() { remain_length_ = static_cast(0 ); return (*this); } protected: // The PINGREQ packet has no Variable Header. // The PINGREQ packet has no Payload. }; /** * PINGRESP - PING response * * A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ packet. * It indicates that the Server is alive. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718086 */ class pingresp : public fixed_header { public: pingresp() : fixed_header(control_packet_type::pingresp) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline pingresp& serialize(Container& buffer) { fixed_header::serialize(buffer); return (*this); } inline pingresp& deserialize(std::string_view& data) { fixed_header::deserialize(data); update_remain_length(); return (*this); } inline pingresp& update_remain_length() { remain_length_ = static_cast(0 ); return (*this); } protected: // The PINGRESP packet has no Variable Header. // The PINGRESP packet has no Payload. }; /** * DISCONNECT - Disconnect notification * * The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. * It indicates that the Client is disconnecting cleanly. * * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718090 */ class disconnect : public fixed_header { public: disconnect() : fixed_header(control_packet_type::disconnect) { update_remain_length(); } inline std::size_t required_size() const { return (fixed_header::required_size() + fixed_header::remain_length()); } /* * The Container is usually a std::string, std::vector, ... */ template inline disconnect& serialize(Container& buffer) { fixed_header::serialize(buffer); return (*this); } inline disconnect& deserialize(std::string_view& data) { fixed_header::deserialize(data); update_remain_length(); return (*this); } inline disconnect& update_remain_length() { remain_length_ = static_cast(0 ); return (*this); } protected: // The DISCONNECT Packet has no variable header. // The DISCONNECT Packet has no payload. }; } namespace asio2::mqtt { template inline constexpr std::string_view to_string(v4::connect_reason_code v) { using namespace std::string_view_literals; switch(v) { case v4::connect_reason_code::success : return "Connection accepted"sv; case v4::connect_reason_code::unacceptable_protocol_version : return "The Server does not support the level of the MQTT protocol requested by the Client"sv; case v4::connect_reason_code::identifier_rejected : return "The Client identifier is correct UTF-8 but not allowed by the Server"sv; case v4::connect_reason_code::server_unavailable : return "The Network Connection has been made but the MQTT service is unavailable"sv; case v4::connect_reason_code::bad_user_name_or_password : return "The data in the user name or password is malformed"sv; case v4::connect_reason_code::not_authorized : return "The Client is not authorized to connect"sv; default: ASIO2_ASSERT(false); break; } return "unknown"sv; } template inline constexpr bool is_v4_message() { using type = asio2::detail::remove_cvref_t; if constexpr ( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v ) { return true; } else { return false; } } } #endif // !__ASIO2_MQTT_PROTOCOL_V4_HPP__