First parts of MQTT protocol support
authorNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 5 Mar 2019 14:28:42 +0000 (15:28 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
include/lib/protocols/mqtt.h [new file with mode: 0644]
lib/protocols/mqtt.c [new file with mode: 0644]

diff --git a/include/lib/protocols/mqtt.h b/include/lib/protocols/mqtt.h
new file mode 100644 (file)
index 0000000..a9a8de9
--- /dev/null
@@ -0,0 +1,313 @@
+/****************************************************************************
+ *   lib/protocols/mqtt/mqtt.h
+ *
+ * Copyright 2019 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+/* 
+ * MQTT client implementation for embeded microcontrollers.
+ *
+ * For protocol defiition, refer to 
+ * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
+ *
+ * This implementation has a limitation on the subscription mechanism : the protocol allows for
+ * multiple subscriptions / unsubscriptions using a single subscribe packet, which is not supported
+ * by this code for code simplicity, memory usage limitation, and packets size limitation.
+ * All other features of the protocol should be fully supported.
+ *
+ * This code is the implementation of the MQTT protocol part of the communication.
+ * The MQTT protocol requires a lossless, ordered transport protocol layer with an address mechanism
+ * which is not part of the code found in mqtt.c and mqtt.h, making it possible to use any underlying
+ * communication layer which fulfills the above requirements.
+ *
+ * The application using this implementation of the MQTT protocol must provide two comminication
+ * funcions : mqtt_app_send() and mqtt_app_recv(), which are respectively responsible for sending and
+ * receiving MQTT packets to and from the server.
+ * 
+ */
+
+#ifndef MQTT_H
+#define MQTT_H
+
+#include "core/system.h"
+
+/***************************************************************************** */
+
+
+/* Protocol identifiers for MQTT v3.1.1. */
+#define MQTT_PROTOCOL_LEVEL 0x04
+#define MQTT_PROTOCOL_NAME   "MQTT"
+
+
+/* MQTT control packet types. */
+enum MQTT_message_types {
+       MQTT_CONTROL_CONNECT = 1,
+       MQTT_CONTROL_CONNACK,
+       MQTT_CONTROL_PUBLISH,
+       MQTT_CONTROL_PUBACK,
+       MQTT_CONTROL_PUBREC,
+       MQTT_CONTROL_PUBREL,
+       MQTT_CONTROL_PUBCOMP,
+       MQTT_CONTROL_SUBSCRIBE,
+       MQTT_CONTROL_SUBACK,
+       MQTT_CONTROL_UNSUBSCRIBE,
+       MQTT_CONTROL_UNSUBACK,
+       MQTT_CONTROL_PINGREQ,
+       MQTT_CONTROL_PINGRESP,
+       MQTT_CONTROL_DISCONNECT
+};
+
+/* MQTT QoS levels */
+#define MQTT_QoS_0  (0)
+#define MQTT_QoS_1  (1)
+#define MQTT_QoS_2  (2)
+
+/* Control byte flags */
+#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)
+#define MQTT_SUBSCRIBE_FLAG  (0x01 << 1)
+#define MQTT_UNSUBSCRIBE_FLAG  (0x01 << 1)
+
+/**
+ * Generic MQTT packet, with unkown payload size.
+ * These will have to be translated before being sent on the wire, so all fields are in host endianness.
+ */
+struct mqtt_pkt {
+       /* The type of packet, stored on the 4 LSB bytes */
+       uint8_t control_type;
+       /* Associated flags */
+       uint8_t flags;
+       /* Packet identifier, stored in host endianness in this structure */
+       /* packet_id is required for publish (QoS > 0), puback, pubrec, pubrel, pubcomp,
+        *  subscribe, suback, unsubscribe, and unsuback.
+        * The packet id has been indicated as a comment in the different structures where it is used but
+        *  which already include this header structure in order to indicate where it must be included in
+        *  the final packet sent on the wire.
+        */
+       uint16_t packet_id;
+       /* The remaining size of the packet in bytes (i.e. the size of variable header and payload).
+        * Refer to section 2.2.3 of mqtt-v3.1.1-os for translation of remaining_length on wire */
+       uint32_t remaining_length;
+       /* Pointer to the payload */
+       /* payload is required for connect, subscribe, suback, unsubscribe, and optionnal for publish */
+       uint8_t* payload;
+};
+
+struct mqtt_str {
+       uint16_t len; /* Big endian encoded size of string */
+       char* str; /* UTF-8 encoded string */
+};
+
+enum MQTTErrors {
+       MQTT_ERROR_UNKNOWN,
+       MQTT_OK,
+       MQTT_ERROR_NULLPTR,
+       MQTT_ERROR_CONTROL_FORBIDDEN_TYPE,
+       MQTT_ERROR_CONTROL_INVALID_FLAGS,
+       MQTT_ERROR_CONTROL_WRONG_TYPE,
+       MQTT_ERROR_CONNECT_NULL_CLIENT_ID,
+       MQTT_ERROR_CONNECT_NULL_WILL_MESSAGE,
+       MQTT_ERROR_CONNECT_FORBIDDEN_WILL_QOS,
+       MQTT_ERROR_CONNACK_FORBIDDEN_FLAGS,
+       MQTT_ERROR_CONNACK_FORBIDDEN_CODE,
+       MQTT_ERROR_PUBLISH_FORBIDDEN_QOS,
+       MQTT_ERROR_SUBSCRIBE_TOO_MANY_TOPICS,
+       MQTT_ERROR_MALFORMED_RESPONSE,
+       MQTT_ERROR_UNSUBSCRIBE_TOO_MANY_TOPICS,
+       MQTT_ERROR_RESPONSE_INVALID_CONTROL_TYPE,
+       MQTT_ERROR_CONNECT_NOT_CALLED,
+       MQTT_ERROR_SEND_BUFFER_IS_FULL,
+       MQTT_ERROR_SOCKET_ERROR,
+       MQTT_ERROR_MALFORMED_REQUEST,
+       MQTT_ERROR_RECV_BUFFER_TOO_SMALL,
+       MQTT_ERROR_ACK_OF_UNKNOWN,
+       MQTT_ERROR_NOT_IMPLEMENTED,
+       MQTT_ERROR_CONNECTION_REFUSED,
+       MQTT_ERROR_SUBSCRIBE_FAILED,
+       MQTT_ERROR_CONNECTION_CLOSED,
+       MQTT_ERROR_INITIAL_RECONNECT,
+       MQTT_ERROR_INVALID_REMAINING_LENGTH,
+};
+
+
+/***************************************************************************** */
+/* Connect and Connack packets */
+
+/* CONNECT packet flags */
+#define MQTT_CONNECT_FLAG_CLEAN_SESSION (0x01 << 1)
+#define MQTT_CONNECT_FLAG_WILL_FLAG  (0x01 << 2)
+#define MQTT_CONNECT_FLAG_WILL_QoS(x)  (((x) & 0x03) << 3)
+#define MQTT_CONNECT_FLAG_WILL_RETAIN  (0x01 << 5)
+#define MQTT_CONNECT_FLAG_PASSWORD  (0x01 << 6)
+#define MQTT_CONNECT_FLAG_USER_NAME  (0x01 << 7)
+
+struct mqtt_connect_pkt_fixed_payload {
+       uint16_t name_len; /* network endian */
+       uint8_t protocol_name[4]; /* "MQTT" : use MQTT_PROTOCOL_NAME */
+       uint8_t protocol_level;  /* use MQTT_PROTOCOL_LEVEL */
+       uint8_t connect_flags;
+       uint16_t keep_alive_seconds; /* network endian */
+};
+
+struct mqtt_connect_pkt_strings {
+       struct mqtt_str client_id;
+       struct mqtt_str will_topic;
+       struct mqtt_str will_message;
+       struct mqtt_str username;
+       struct mqtt_str password;
+};
+
+/* The MQTT connect packet.
+ * note that the packet id field is unused in this case */
+struct mqtt_connect_pkt {
+       struct mqtt_pkt header;
+       struct mqtt_connect_pkt_fixed_payload fixed_payload;
+       struct mqtt_connect_pkt_strings conn_str;
+};
+
+/* Connack return codes returned in a CONNACK packet */
+enum MQTTConnackReturnCode {
+       MQTT_CONNACK_ACCEPTED = 0,
+       MQTT_CONNACK_REFUSED_PROTOCOL_VERSION = 1,
+       MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED = 2,
+       MQTT_CONNACK_REFUSED_SERVER_UNAVAILABLE = 3,
+       MQTT_CONNACK_REFUSED_BAD_USER_NAME_OR_PASSWORD = 4,
+       MQTT_CONNACK_REFUSED_NOT_AUTHORIZED = 5,
+};
+
+/* MQTT connack packet */
+struct mqtt_connack_response_pkt {
+       uint8_t control; /* Paquet type : must be (MQTT_CONTROL_CONNACK << 4) */
+       uint8_t rem_len; /* Remaining length : must be 0x02 */
+       uint8_t flags;  /* Connect acknowledge flags */
+       uint8_t ret_code; /* Connect return code */
+};
+
+/* 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 */
+#define MQTT_CONNACK_SESSION_PRESENT  (0x01 << 0)
+
+
+/***************************************************************************** */
+/* publish and puback packets */
+
+/* MQTT publish paquet
+ * A publish control packet is sent from a Client to a Server or from Server to a Client to transport
+ * an Application Message. */
+struct mqtt_publish_pkt {
+       struct mqtt_pkt header;
+       struct mqtt_str topic;
+       /* uint16_t packet_id : Packet identifier, stored in network endianness */
+       uint8_t* application_message;
+};
+/* 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
+ * (MQTT_CONTROL_PUBREL << 4), answered by a publish complete packet (MQTT_CONTROL_PUBCOMP << 4) */
+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, stored in network endianness */
+};
+
+
+
+/***************************************************************************** */
+/* subsribe and suback packets */
+
+/* NOTE : I choose not to implement the possibility to send multiple subscriptions at once here in order
+ * to avoid unnecessary complexity of code and dynamic allocation of memory.
+ * It could be implemented using the following structure and the inclusion of "lib/list.h" :
+ * struct mqtt_sub_request {
+ *     struct list_head list;
+ *     struct mqtt_str topic;
+ *     uint8_t qos;
+ * };
+ * and then replace topic and qos fields in struct mqtt_subscribe_pkt with a pointer to a struct list_head
+ * which should then point to the "list" field of the first topic.
+ * struct mqtt_subscribe_response_pkt should also be adapted for multiple subscription support.
+ */
+
+/* MQTT subscribe packet */
+struct mqtt_subscribe_pkt {
+       struct mqtt_pkt header;
+       /* uint16_t packet_id : Packet identifier, stored in network endianness */
+       struct mqtt_str topic;
+       uint8_t qos; /* unshifted QoS value */
+};
+
+/* MQTT subscribe response packet */
+struct mqtt_subscribe_response_pkt {
+       uint8_t control; /* Packet type */
+       uint8_t rem_len; /* Remaining length : must be 0x03 in our "single subscription" case */
+       uint16_t packet_id; /* Packet identifier, stored in network endianness */
+       uint8_t ret_code; /* equals accepted QoS or 0x80 in case of error */
+};
+
+#define MQTT_SUBSCRIBE_FAILURE   (0x80)
+
+
+
+/***************************************************************************** */
+/* unsubsribe and unsuback packets */
+
+/* NOTE : Same as subscription, I choosed not to implement the possibility to send multiple unsubscriptions
+ * at once here in order to avoid unnecessary complexity of code and dynamic allocation of memory.
+ */
+
+/* MQTT unsubscribe packet */
+struct mqtt_unsubscribe_pkt {
+       struct mqtt_pkt header;
+       /* uint16_t packet_id : Packet identifier, stored in network endianness */
+       struct mqtt_str topic;
+};
+
+/* MQTT unsubscribe response packet */
+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 packet_id; /* Packet identifier, stored in network endianness */
+};
+
+
+
+/***************************************************************************** */
+/* MQTT ping request and response packet */
+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 */
+};
+
+
+/***************************************************************************** */
+/* MQTT disconnect */
+/* This one may not be very useful in most applications, but it's defined by the MQTT standard and
+ * including the definition here takes no room in either code or data ...
+ */
+struct mqtt_disconnect_pkt {
+       uint8_t control; /* Packet type : (MQTT_CONTROL_DISCONNECT << 4) */
+       uint8_t rem_len; /* Remaining length : must be 0x00 */
+};
+
+
+
+
+
+#endif /* MQTT_H */
+
diff --git a/lib/protocols/mqtt.c b/lib/protocols/mqtt.c
new file mode 100644 (file)
index 0000000..a2753d6
--- /dev/null
@@ -0,0 +1,47 @@
+/****************************************************************************
+ *   lib/protocols/mqtt/mqtt.c
+ *
+ * Copyright 2019 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#include "core/system.h"
+#include "lib/errno.h"
+#include "lib/protocols/mqtt.h"
+
+/* 
+ * MQTT client implementation for embeded microcontrollers.
+ *
+ * For protocol defiition, refer to 
+ * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
+ *
+ * This implementation has a limitation on the subscription mechanism : the protocol allows for
+ * multiple subscriptions / unsubscriptions using a single subscribe packet, which is not supported
+ * by this code for code simplicity, memory usage limitation, and packets size limitation.
+ * All other features of the protocol should be fully supported.
+ *
+ * This code is the implementation of the MQTT protocol part of the communication.
+ * The MQTT protocol requires a lossless, ordered transport protocol layer with an address mechanism
+ * which is not part of the code found in mqtt.c and mqtt.h, making it possible to use any underlying
+ * communication layer which fulfills the above requirements.
+ *
+ */
+
+/* These two functions must be defined in the application code */
+extern int mqtt_app_send(const void* buf, uint16_t len);
+extern int mqtt_app_recv(void* buf, uint16_t bufsz);
+
+