From: Nathael Pajani Date: Tue, 5 Mar 2019 23:38:06 +0000 (+0100) Subject: MQTT protocol support going on, compiles but still untested. X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=ccad28ab0e42b0fe27e7875b31355d19c2c7c3fd;p=soft%2Flpc122x%2Fcore MQTT protocol support going on, compiles but still untested. --- diff --git a/include/lib/protocols/mqtt.h b/include/lib/protocols/mqtt.h index a9a8de9..c2c458f 100644 --- a/include/lib/protocols/mqtt.h +++ b/include/lib/protocols/mqtt.h @@ -34,9 +34,9 @@ * 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. + * This code only handles the packet encoding and decoding according to the MQTT specification and + * provides usage information in order to respect the protocol flow, but does not provide the communication + * parts of the protocol, which should be application specific. * */ @@ -76,75 +76,12 @@ enum MQTT_message_types { #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 */ + uint16_t len; /* size of string, stored in host endianness */ 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 */ @@ -158,31 +95,45 @@ enum MQTTErrors { #define MQTT_CONNECT_FLAG_USER_NAME (0x01 << 7) struct mqtt_connect_pkt_fixed_payload { - uint16_t name_len; /* network endian */ + uint16_t proto_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_connect_pkt { + uint16_t keep_alive_seconds; + uint8_t clean_session_flag; struct mqtt_str client_id; struct mqtt_str will_topic; - struct mqtt_str will_message; + uint16_t will_msg_size; /* stored in host endianness */ + uint8_t will_QoS; /* 0, 1 or 2 */ + uint8_t will_retain; /* 0 or 1 */ + uint8_t* will_msg; 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; -}; +/* Create MQTT connect packet + * This function must be called in order to connectte the connect MQTT packet used to + * connect to the server. + * The client must send a connect packet whenever the server does not acknoledge a publish, + * subscribe, unsubscribe, or ping packet, or when the server explicitely closes the connection. + * + * 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 : + * -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); + /* Connack return codes returned in a CONNACK packet */ -enum MQTTConnackReturnCode { +enum MQTT_connack_return_codes { MQTT_CONNACK_ACCEPTED = 0, MQTT_CONNACK_REFUSED_PROTOCOL_VERSION = 1, MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED = 2, @@ -203,19 +154,29 @@ struct mqtt_connack_response_pkt { * the server accepts the connection and has a stored session for this client id */ #define MQTT_CONNACK_SESSION_PRESENT (0x01 << 0) +/* Check and Decode MQTT connack packet */ +int mqtt_unpackconnack_response_pkt(uint8_t* buf, uint8_t buf_size); /***************************************************************************** */ /* publish and puback packets */ +#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 * 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 */ + uint16_t packet_id; /* Packet identifier is required for publish if QoS > 0 */ + uint8_t QoS; + uint8_t dup_flags; + uint8_t retain_flag; 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 @@ -223,7 +184,7 @@ struct mqtt_publish_pkt { 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 */ + uint16_t acked_pkt_id; /* Id of packet that is being acknowledged, in network endianness */ }; @@ -244,26 +205,26 @@ struct mqtt_publish_response_pkt { * struct mqtt_subscribe_response_pkt should also be adapted for multiple subscription support. */ +#define MQTT_SUBSCRIBE_FLAG (0x01 << 1) + /* MQTT subscribe packet */ struct mqtt_subscribe_pkt { - struct mqtt_pkt header; - /* uint16_t packet_id : Packet identifier, stored in network endianness */ + uint16_t packet_id; /* Packet identifier */ struct mqtt_str topic; - uint8_t qos; /* unshifted QoS value */ + 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 */ + 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 */ }; #define MQTT_SUBSCRIBE_FAILURE (0x80) - /***************************************************************************** */ /* unsubsribe and unsuback packets */ @@ -271,10 +232,11 @@ struct mqtt_subscribe_response_pkt { * at once here in order to avoid unnecessary complexity of code and dynamic allocation of memory. */ +#define MQTT_UNSUBSCRIBE_FLAG (0x01 << 1) + /* MQTT unsubscribe packet */ struct mqtt_unsubscribe_pkt { - struct mqtt_pkt header; - /* uint16_t packet_id : Packet identifier, stored in network endianness */ + uint16_t packet_id; /* Packet identifier */ struct mqtt_str topic; }; @@ -282,7 +244,7 @@ struct mqtt_unsubscribe_pkt { 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 */ + uint16_t acked_pkt_id; /* Id of packet that is being acknowledged, in network endianness */ }; @@ -307,7 +269,5 @@ struct mqtt_disconnect_pkt { - - #endif /* MQTT_H */ diff --git a/lib/protocols/mqtt.c b/lib/protocols/mqtt.c index a2753d6..861fecd 100644 --- a/lib/protocols/mqtt.c +++ b/lib/protocols/mqtt.c @@ -19,6 +19,8 @@ *************************************************************************** */ #include "core/system.h" +#include "lib/utils.h" +#include "lib/string.h" #include "lib/errno.h" #include "lib/protocols/mqtt.h" @@ -38,10 +40,158 @@ * 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. * + * This code only handles the packet encoding and decoding according to the MQTT specification and + * provides usage information in order to respect the protocol flow, but does not provide the communication + * parts of the protocol, which should be application specific. + * + */ + + + +/***************************************************************************** */ +/* MQTT helper functions */ + +/* Encode remaining length + * Remaining length is encoded on 1 to four bytes. The least significant seven bits of each byte encode the + * data, and the most significant bit is used to indicate that there are following bytes in the representation. + * 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) +{ + int idx = 0; + + do { + buf[idx] = length & 0x7F; + length >>= 7; + if (length > 0) { + buf[idx] |= 0x80; + } + idx++; + } while (length > 0); + + return idx; +} +/* Decode remaining length */ +int decode_remaining_length(uint8_t* buf, uint8_t* length) +{ + int idx = 0; + + do { + length += (buf[idx] & 0x7F) << (7 * idx); + } while ((buf[idx++] & 0x80) && (idx <= 3)); + + return idx; +} + + +/* Pack a string to MQTT strings format. + * The string must already be UTF8 encoded. + */ +int mqtt_pack_str(uint8_t *buf, struct mqtt_str* str) +{ + *(uint16_t*)buf = htons(str->len); + memcpy((buf + 2), str->str, str->len); + return str->len + 2; +} + +/* Pack fixed header */ +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); +} + + + +/***************************************************************************** */ +/* Create MQTT connect packet + * This function must be called in order to connectte 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) +{ + struct mqtt_connect_pkt_fixed_payload fxpl; + uint32_t remaining_length = 0; + uint32_t len = 0; + + /* Fixed payload part */ + memset(&fxpl, 0, sizeof(struct mqtt_connect_pkt_fixed_payload)); + fxpl.proto_name_len = htons(4); + memcpy(&(fxpl.protocol_name), MQTT_PROTOCOL_NAME, 4); + fxpl.protocol_level = MQTT_PROTOCOL_LEVEL; + fxpl.keep_alive_seconds = htons(pkt->keep_alive_seconds); + remaining_length = sizeof(struct mqtt_connect_pkt_fixed_payload); + + /* Client id is (almost) mandatory */ + if (pkt->client_id.str != NULL) { + pkt->client_id.len = strlen(pkt->client_id.str); + /* Update packet payload size */ + remaining_length += pkt->client_id.len + 2; + } else { + return -EINVAL; + } + + /* Set connect flags and compute message size for optionnal parts */ + if (pkt->clean_session_flag) { + fxpl.connect_flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; + } + /* Optionnal will message */ + if ((pkt->will_msg_size != 0) && (pkt->will_topic.str != NULL) && (pkt->will_msg != NULL)) { + fxpl.connect_flags |= MQTT_CONNECT_FLAG_WILL_FLAG; + fxpl.connect_flags |= MQTT_CONNECT_FLAG_WILL_QoS(pkt->will_QoS); + if (pkt->will_retain != 0) { + fxpl.connect_flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; + } + pkt->will_topic.len = strlen(pkt->will_topic.str); + /* Update packet payload size */ + remaining_length += pkt->will_topic.len + 2; + remaining_length += pkt->will_msg_size + 2; + } + /* Optionnal username and password */ + if (pkt->username.str != NULL) { + pkt->username.len = strlen(pkt->username.str); + fxpl.connect_flags |= MQTT_CONNECT_FLAG_USER_NAME; + /* Update packet payload size */ + remaining_length += pkt->username.len + 2; + if (pkt->password.str != NULL) { + pkt->password.len = strlen(pkt->password.str); + fxpl.connect_flags |= MQTT_CONNECT_FLAG_PASSWORD; + /* Update packet payload size */ + remaining_length += pkt->password.len + 2; + } + } + + /* Build MQTT Connect packet */ + /* Fixed header */ + len = mqtt_pack_fixed_header(buf, MQTT_CONTROL_CONNECT, 0, remaining_length); + if (remaining_length + len > buf_size) { + return -E2BIG; + } + /* Fixed payload */ + memcpy((buf + len), &fxpl, sizeof(struct mqtt_connect_pkt_fixed_payload)); + len += sizeof(struct mqtt_connect_pkt_fixed_payload); + + /* Client ID is mandatory, even if length of ID string is 0 */ + len += mqtt_pack_str((buf + len), &pkt->client_id); + + /* Add optionnal Will message */ + if ((pkt->will_msg_size != 0) && (pkt->will_topic.str != NULL) && (pkt->will_msg != NULL)) { + len += mqtt_pack_str((buf + len), &pkt->will_topic); + *(uint16_t*)(buf + len) = htons(pkt->will_msg_size); + memcpy((buf + len + 2), pkt->will_msg, pkt->will_msg_size); + len += pkt->will_msg_size + 2; + } + /* Add optionnal username and password */ + if (pkt->username.str != NULL) { + len += mqtt_pack_str((buf + len), &pkt->username); + /* Add password too ? */ + if (pkt->password.str != NULL) { + len += mqtt_pack_str((buf + len), &pkt->password); + } + } -/* 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); + return len; +}