From f487f63d1267647411eff3f351086886b014de95 Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Tue, 5 Mar 2019 15:28:42 +0100 Subject: [PATCH] First parts of MQTT protocol support --- include/lib/protocols/mqtt.h | 313 +++++++++++++++++++++++++++++++++++ lib/protocols/mqtt.c | 47 ++++++ 2 files changed, 360 insertions(+) create mode 100644 include/lib/protocols/mqtt.h create mode 100644 lib/protocols/mqtt.c diff --git a/include/lib/protocols/mqtt.h b/include/lib/protocols/mqtt.h new file mode 100644 index 0000000..a9a8de9 --- /dev/null +++ b/include/lib/protocols/mqtt.h @@ -0,0 +1,313 @@ +/**************************************************************************** + * lib/protocols/mqtt/mqtt.h + * + * Copyright 2019 Nathael Pajani + * + * 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 . + * + *************************************************************************** */ + +/* + * 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 index 0000000..a2753d6 --- /dev/null +++ b/lib/protocols/mqtt.c @@ -0,0 +1,47 @@ +/**************************************************************************** + * lib/protocols/mqtt/mqtt.c + * + * Copyright 2019 Nathael Pajani + * + * 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 . + * + *************************************************************************** */ + +#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); + + -- 2.43.0