Some more parts of MQTT protocol support Compiles, untested.
authorNathael Pajani <nathael.pajani@ed3l.fr>
Fri, 8 Mar 2019 16:32:10 +0000 (17:32 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
include/lib/protocols/mqtt.h
lib/protocols/mqtt.c

index 763e23b..b91abb4 100644 (file)
@@ -99,9 +99,11 @@ struct mqtt_connect_pkt_fixed_payload {
        uint16_t keep_alive_seconds; /* network endian */
 };
 
+#define MQTT_SESSION_RESUME 0x00
+#define MQTT_SESSION_NEW    0x01
 struct mqtt_connect_pkt {
        uint16_t keep_alive_seconds;
-       uint8_t clean_session_flag;
+       uint8_t clean_session_flag; /* Either MQTT_SESSION_RESUME or MQTT_SESSION_NEW */
        char* client_id;
        char* will_topic;
        uint16_t will_msg_size; /* stored in host endianness */
@@ -120,13 +122,14 @@ struct mqtt_connect_pkt {
  *
  * Caller must provide a buffer "buf" big enougth to receive the whole packet and indicate it's size
  * in "buf_size".
- * The return value is the used buffer size (packet size) on success.
- * The return value is negative on error :
+ * Returns the used buffer size (packet size) on success.
+ * Return value is negative on error :
+ *   -EINVAL if either buf or pkt is NULL,
  *   -EINVAL if client_id string provided in mqtt_connect_pkt struct is NULL (it's length may be 0,
  *     but pointer cannot bu NULL).
  *   -E2BIG if buffer is not big enough for packet.
  */
-int mqtt_pack_connect_packet(struct mqtt_connect_pkt* pkt, uint8_t* buf, uint32_t buf_size);
+int mqtt_pack_connect_packet(const struct mqtt_connect_pkt* pkt, uint8_t* buf, uint32_t buf_size);
 
 
 /* Connack return codes returned in a CONNACK packet */
@@ -146,7 +149,7 @@ struct mqtt_connack_response_pkt {
        uint8_t rem_len; /* Remaining length : must be 0x02 */
        uint8_t flags;  /* Connect acknowledge flags */
        uint8_t ret_code; /* Connect return code */
-};
+} __attribute__ ((__packed__));
 
 /* Connect acknowledge flags bit 0 set to 1 only if connect with Clean session flag was set to 0 and
  * the server accepts the connection and has a stored session for this client id */
@@ -156,10 +159,10 @@ struct mqtt_connack_response_pkt {
  * This function may get called to check a supposed connect acknowledge packet.
  * The function checks for conformance to the MQTT protocol and returns 0 if the packet is valid and
  * the server accepted the connection.
- * The function returns -EPROTO in case of invalid connack packet or -EREMOTEIO in case of connection
- * refused by the server (ret_code is then valid in the packet).
+ * The function returns -EINVAL if pkt is NULL, -EPROTO in case of invalid connack packet or
+ *  -EREMOTEIO in case of connection refused by the server (ret_code is then valid in the packet).
  */
-int mqtt_check_connack_response_pkt(struct mqtt_connack_response_pkt* pkt);
+int mqtt_check_connack_response_pkt(const struct mqtt_connack_response_pkt* pkt);
 
 /***************************************************************************** */
 /* publish and puback packets */
@@ -167,7 +170,6 @@ int mqtt_check_connack_response_pkt(struct mqtt_connack_response_pkt* pkt);
 #define MQTT_PUBLISH_DUP  (0x01 << 3)
 #define MQTT_PUBLISH_QoS(x)  (((x) & 0x03) << 1)
 #define MQTT_PUBLISH_RETAIN  (0x01 << 0)
-#define MQTT_PUBREL_FLAG  (0x01 << 1)
 
 /* MQTT publish paquet
  * A publish control packet is sent from a Client to a Server or from Server to a Client to transport
@@ -175,12 +177,34 @@ int mqtt_check_connack_response_pkt(struct mqtt_connack_response_pkt* pkt);
 struct mqtt_publish_pkt {
        char* topic;
        uint16_t packet_id; /* Packet identifier is required for publish if QoS > 0 */
-       uint8_t QoS;
-       uint8_t dup_flags;
+       uint8_t QoS; /* 0, 1 or 2 */
+       uint8_t dup_flag; /* Accept 1 or MQTT_PUBLISH_DUP as flag set */
        uint8_t retain_flag;
+       uint32_t message_size;
        uint8_t* application_message;
 };
 
+/* Create MQTT publish packet
+ * This function must be called in order to create a publish MQTT packet used to publish data on a
+ * topic (send data to the server).
+ * The application message size can be 0, in which case the application_message pointer in the
+ * mqtt_publish_pkt struct may be NULL
+ *
+ * Caller must provide a buffer "buf" big enougth to receive the whole packet and indicate it's size
+ * in "buf_size".
+ * Returns the used buffer size (packet size) on success.
+ * Return value is negative on error :
+ *   -EINVAL if either buf or pkt is NULL,
+ *   -EINVAL if client_id string provided in mqtt_connect_pkt struct is NULL (it's length may be 0,
+ *     but pointer cannot bu NULL).
+ *   -E2BIG if buffer is not big enough for packet.
+ */
+int mqtt_pack_publish_packet(const struct mqtt_publish_pkt* pkt, uint8_t* buf, uint32_t buf_size);
+
+
+
+#define MQTT_PUBREL_FLAG  (0x01 << 1)
+
 /* MQTT publish responses packet, used for puback, pubrec, pubrel and pubcomp */
 /* control paquet type must be either (MQTT_CONTROL_PUBACK << 4) if QoS = 1 (no further response required)
  * or (MQTT_CONTROL_PUBREC << 4) if QoS = 2 and then a publish release must be received or sent
@@ -189,7 +213,26 @@ struct mqtt_publish_response_pkt {
        uint8_t control; /* Packet type */
        uint8_t rem_len; /* Remaining length : must be 0x02 */
        uint16_t acked_pkt_id; /* Id of packet that is being acknowledged, in network endianness */
-};
+} __attribute__ ((__packed__));
+
+/* Build MQTT puback, pubrec, pubrel or pubcomp packet, used in the publish acknowledge one-way or
+ * two-way hand-check mechanism.
+ * The provided buf must be big enough to hold 4 bytes.
+ * Note that buf may safely be cast to or from a mqtt_publish_response_pkt struct pointer.
+ * type is one of MQTT_CONTROL_PUBACK, MQTT_CONTROL_PUBREC, MQTT_CONTROL_PUBREL or MQTT_CONTROL_PUBCOMP.
+ * acked_pkt_id is the ID of the concerned publish packet, in host endianness.
+ * Returns the used buffer size (4 bytes) on success, or -EINVAL in case of a NULL buf pointer.
+ */
+int mqtt_pack_publish_response_pkt(uint8_t* buf, uint16_t acked_pkt_id, uint8_t type);
+
+/* Check MQTT puback, pubrec, pubrel or pubcomp packet
+ * This function may get called to check a supposed publish acknowledge packet in either one-way or
+ * two-way hand-check mechanism.
+ * type is either MQTT_CONTROL_PUBACK, MQTT_CONTROL_PUBREC, MQTT_CONTROL_PUBREL or MQTT_CONTROL_PUBCOMP.
+ * The function checks for conformance to the MQTT protocol and returns 0 if the packet is valid.
+ * The function returns -EPROTO in case of protocol error or -EINVAL in case of a NULL pkt pointer.
+ */
+int mqtt_check_publish_response_pkt(struct mqtt_publish_response_pkt* pkt, uint8_t type);
 
 
 
@@ -224,7 +267,7 @@ struct mqtt_subscribe_response_pkt {
        uint8_t rem_len; /* Remaining length : must be 0x03 in our "single subscription" case */
        uint16_t acked_pkt_id; /* Id of packet that is being acknowledged, in network endianness */
        uint8_t ret_code; /* equals accepted QoS or 0x80 in case of error */
-};
+} __attribute__ ((__packed__));
 
 #define MQTT_SUBSCRIBE_FAILURE   (0x80)
 
@@ -249,7 +292,7 @@ struct mqtt_unsubscribe_response_pkt {
        uint8_t control; /* Packet type */
        uint8_t rem_len; /* Remaining length : must be 0x02 in our "single subscription" case */
        uint16_t acked_pkt_id; /* Id of packet that is being acknowledged, in network endianness */
-};
+} __attribute__ ((__packed__));
 
 
 
@@ -258,7 +301,7 @@ struct mqtt_unsubscribe_response_pkt {
 struct mqtt_ping_pkt {
        uint8_t control; /* Packet type : either (MQTT_CONTROL_PINGREQ << 4) or (MQTT_CONTROL_PINGRESP << 4) */
        uint8_t rem_len; /* Remaining length : must be 0x00 */
-};
+} __attribute__ ((__packed__));
 
 
 /***************************************************************************** */
@@ -269,7 +312,7 @@ struct mqtt_ping_pkt {
 struct mqtt_disconnect_pkt {
        uint8_t control; /* Packet type : (MQTT_CONTROL_DISCONNECT << 4) */
        uint8_t rem_len; /* Remaining length : must be 0x00 */
-};
+} __attribute__ ((__packed__));
 
 
 
index 6adc690..a781b44 100644 (file)
@@ -57,7 +57,7 @@
  * The size is encoded in little endianness (least significant 7 bits on first byte and so on).
  * Refer to section 2.2.3 of mqtt-v3.1.1-os for translation of remaining_length on wire.
  */
-int encode_remaining_length(uint8_t* buf, uint32_t length)
+static int encode_remaining_length(uint8_t* buf, uint32_t length)
 {
        int idx = 0;
 
@@ -73,7 +73,7 @@ int encode_remaining_length(uint8_t* buf, uint32_t length)
        return idx;
 }
 /* Decode remaining length */
-int decode_remaining_length(uint8_t* buf, uint8_t* length)
+static int decode_remaining_length(uint8_t* buf, uint32_t* length)
 {
        int idx = 0;
 
@@ -96,7 +96,7 @@ static int mqtt_pack_str(uint8_t *buf, char* str, uint16_t len)
 }
 
 /* Pack fixed header */
-int mqtt_pack_fixed_header(uint8_t *buf, uint8_t control, uint8_t flags, uint32_t length)
+static int mqtt_pack_fixed_header(uint8_t *buf, uint8_t control, uint8_t flags, uint32_t length)
 {
        *buf = (control << 4) | flags;
        return 1 + encode_remaining_length((buf + 1), length);
@@ -106,16 +106,19 @@ int mqtt_pack_fixed_header(uint8_t *buf, uint8_t control, uint8_t flags, uint32_
 
 /***************************************************************************** */
 /* Create MQTT connect packet
- * This function must be called in order to connectte the connect MQTT packet used to
+ * This function must be called in order to create the connect MQTT packet used to
  * connect to the server.
  */
-int mqtt_pack_connect_packet(struct mqtt_connect_pkt* pkt, uint8_t* buf, uint32_t buf_size)
+int mqtt_pack_connect_packet(const struct mqtt_connect_pkt* pkt, uint8_t* buf, uint32_t buf_size)
 {
        struct mqtt_connect_pkt_fixed_payload fxpl;
        uint32_t remaining_length = 0;
        uint32_t len = 0;
        uint16_t client_id_len = 0, will_topic_len = 0, username_len = 0, password_len = 0;
 
+       if ((pkt == NULL) || (buf == NULL)) {
+               return -EINVAL;
+       }
        /* Fixed payload part */
        memset(&fxpl, 0, sizeof(struct mqtt_connect_pkt_fixed_payload));
        fxpl.proto_name_len = htons(4);
@@ -201,10 +204,12 @@ int mqtt_pack_connect_packet(struct mqtt_connect_pkt* pkt, uint8_t* buf, uint32_
  * This function may get called to check a supposed connect acknowledge packet.
  * The function checks for conformance to the MQTT protocol and returns 0 if the packet is valid,
  * regardless of the retrun code value.
- * The function returns -EPROTO in case of protocol error.
  */
-int mqtt_check_connack_response_pkt(struct mqtt_connack_response_pkt* pkt)
+int mqtt_check_connack_response_pkt(const struct mqtt_connack_response_pkt* pkt)
 {
+       if (pkt == NULL) {
+               return -EINVAL;
+       }
        if (pkt->control != (MQTT_CONTROL_CONNACK << 4)) {
                return -EPROTO;
        }
@@ -226,4 +231,168 @@ int mqtt_check_connack_response_pkt(struct mqtt_connack_response_pkt* pkt)
 
 
 /***************************************************************************** */
+/* Create MQTT publish packet
+ * This function must be called in order to create a publish MQTT packet used to
+ * publish data on a topic (send data to the server).
+ */
+int mqtt_pack_publish_packet(const struct mqtt_publish_pkt* pkt, uint8_t* buf, uint32_t buf_size)
+{
+       uint32_t remaining_length = 0;
+       uint32_t len = 0;
+       uint16_t topic_len = 0;
+       uint8_t publish_flags = 0;
+
+       if ((pkt == NULL) || (buf == NULL)) {
+               return -EINVAL;
+       }
+       /* Check packet consistency */
+       if ((pkt->message_size != 0) && (pkt->application_message == NULL)) {
+               return -ENODATA;
+       }
+       /* Compute message size
+        * Packet ID is 2 bytes long. There is no "message size" field in the publish packet, as the
+        * message size can be computed by substracting the topic string size and packet id size from
+        * the packet "remaining length" field. */
+       remaining_length = 2 + pkt->message_size;
+       /* Topic is mandatory */
+       if (pkt->topic == NULL) {
+               return -EINVAL;
+       }
+       topic_len = strlen(pkt->topic);
+       /* Update packet payload size */
+       remaining_length += topic_len + 2;
+
+       /* Set publish flags */
+       publish_flags = MQTT_PUBLISH_QoS(pkt->QoS);
+       if (pkt->dup_flag) {
+               publish_flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION;
+       }
+       if (pkt->retain_flag) {
+               publish_flags |= MQTT_PUBLISH_RETAIN;
+       }
+
+       /* Build MQTT Publish packet */
+       /* Fixed header */
+       len = mqtt_pack_fixed_header(buf, MQTT_CONTROL_PUBLISH, publish_flags, remaining_length);
+       if (remaining_length + len > buf_size) {
+               return -E2BIG;
+       }
+       /* Topic is mandatory */
+       len += mqtt_pack_str((buf + len), pkt->topic, topic_len);
+       /* Packet ID */
+       *(uint16_t*)(buf + len) = htons(pkt->packet_id);
+       len += 2;
+       /* Add optionnal application message */
+       if (pkt->message_size != 0) {
+               memcpy((buf + len), pkt->application_message, pkt->message_size);
+               len += pkt->message_size;
+       }
+
+       return len;
+}
+
+
+/***************************************************************************** */
+/* Unpack MQTT publish packet
+ * This function must be called in order to transform a received publish MQTT packet to a
+ * mqtt_publish_pkt structure.
+ * The function also checks the validity of the packet.
+ * All returned pointers within the struct will point to parts of the provided buffer, so the buffer
+ * must not be discarded after the call.
+ */
+int mqtt_unpack_publish_packet(struct mqtt_publish_pkt* pkt, uint8_t* buf, uint32_t size)
+{
+       int ret = 0;
+       uint32_t idx = 0, pkt_len = 0;
+       uint16_t tmp = 0, topic_len = 0;
+       if ((pkt == NULL) || (buf == NULL)) {
+               return -EINVAL;
+       }
+       /* Check type */
+       if ((buf[0] & 0xF0) != (MQTT_CONTROL_PUBLISH << 4)) {
+               return -EPROTO;
+       }
+       /* Check packet size */
+       ret = decode_remaining_length((buf + 1), &pkt_len);
+       if ((ret > 4) || ((ret + pkt_len + 1) != size)) {
+               return -EPROTO;
+       }
+       idx = 1 + ret;
+       /* Decode flags */
+       pkt->QoS = (buf[0] >> 1) & 0x03;
+       pkt->dup_flag = (buf[0] & MQTT_PUBLISH_DUP);
+       pkt->retain_flag = (buf[0] & MQTT_PUBLISH_RETAIN);
+       if (pkt->QoS > 2) {
+               return -EPROTO;
+       }
+       /* Decode topic string */
+       tmp = *((uint16_t*)(buf + idx));
+       topic_len = ntohs(tmp);
+       pkt->topic = (char*)(buf + idx + 2);
+       idx += topic_len + 2;
+       if ((idx + 2) > size) {
+               return -EPROTO;
+       }
+       /* Decode packet ID */
+       tmp = *((uint16_t*)(buf + idx));
+       pkt->packet_id = ntohs(tmp);
+       idx += 2;
+       /* Get application message */
+       pkt->message_size = size - idx;
+       if (pkt->message_size != 0) {
+               pkt->application_message = (buf + idx);
+       }
+       return 0;
+}
+
+/***************************************************************************** */
+/* Build MQTT puback, pubrec, pubrel or pubcomp packet, used in the publish acknowledge one-way or
+ * two-way hand-check mechanism.
+ */
+int mqtt_pack_publish_response_pkt(uint8_t* buf, uint16_t acked_pkt_id, uint8_t type)
+{
+       if (buf == NULL) {
+               return -EINVAL;
+       }
+       buf[0] = (type << 4);
+       if (type == MQTT_CONTROL_PUBREL) {
+               buf[0] |= MQTT_PUBREL_FLAG;
+       }
+       buf[1] = 0x02;
+       *(uint16_t*)(buf + 2) = htons(acked_pkt_id);
+
+       return 4;
+}
+
+/***************************************************************************** */
+/* Check MQTT puback, pubrec, pubrel or pubcomp packet
+ * This function may get called to check a supposed publish acknowledge packet in either one-way or
+ * two-way hand-check mechanism.
+ */
+int mqtt_check_publish_response_pkt(struct mqtt_publish_response_pkt* pkt, uint8_t type)
+{
+       uint8_t awaited_control = 0;
+
+       if (pkt == NULL) {
+               return -EINVAL;
+       }
+       /* Check control byte */
+       awaited_control = (type << 4);
+       /* The pubrel packet must also have a "pubrel" flag ... the protocol provides no indication about the
+        * reason behind this */
+       if (type == MQTT_CONTROL_PUBREL) {
+               awaited_control |= MQTT_PUBREL_FLAG;
+       }
+       if (pkt->control != awaited_control) {
+               return -EPROTO;
+       }
+       /* Check size */
+       if (pkt->rem_len != 2) {
+               return -EPROTO;
+       }
+       /* Valid packet */
+       return 0;
+}
+
+