From: Nathael Pajani Date: Fri, 8 Mar 2019 16:32:10 +0000 (+0100) Subject: Some more parts of MQTT protocol support Compiles, untested. X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=19923f0737049e3689e5facf950a27544e278638;p=soft%2Flpc122x%2Fcore Some more parts of MQTT protocol support Compiles, untested. --- diff --git a/include/lib/protocols/mqtt.h b/include/lib/protocols/mqtt.h index 763e23b..b91abb4 100644 --- a/include/lib/protocols/mqtt.h +++ b/include/lib/protocols/mqtt.h @@ -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__)); diff --git a/lib/protocols/mqtt.c b/lib/protocols/mqtt.c index 6adc690..a781b44 100644 --- a/lib/protocols/mqtt.c +++ b/lib/protocols/mqtt.c @@ -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; +} + +