mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-18 01:14:38 +00:00
f38a01884a
This is 13n iteration of typo fixes
1281 lines
47 KiB
C
1281 lines
47 KiB
C
/*
|
|
* MQTT Protocol
|
|
*
|
|
* Copyright 2020 Baptiste Assmann <bedis9@gmail.com>
|
|
*
|
|
* 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
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <haproxy/chunk.h>
|
|
#include <haproxy/mqtt.h>
|
|
|
|
uint8_t mqtt_cpt_flags[MQTT_CPT_ENTRIES] = {
|
|
[MQTT_CPT_INVALID] = 0x00,
|
|
[MQTT_CPT_CONNECT] = 0x00,
|
|
[MQTT_CPT_CONNACK] = 0x00,
|
|
|
|
/* MQTT_CPT_PUBLISH flags can have different values (DUP, QoS, RETAIN), must be
|
|
* check more carefully
|
|
*/
|
|
[MQTT_CPT_PUBLISH] = 0x0F,
|
|
|
|
[MQTT_CPT_PUBACK] = 0x00,
|
|
[MQTT_CPT_PUBREC] = 0x00,
|
|
[MQTT_CPT_PUBREL] = 0x02,
|
|
[MQTT_CPT_PUBCOMP] = 0x00,
|
|
[MQTT_CPT_SUBSCRIBE] = 0x02,
|
|
[MQTT_CPT_SUBACK] = 0x00,
|
|
[MQTT_CPT_UNSUBSCRIBE] = 0x02,
|
|
[MQTT_CPT_UNSUBACK] = 0x00,
|
|
[MQTT_CPT_PINGREQ] = 0x00,
|
|
[MQTT_CPT_PINGRESP] = 0x00,
|
|
[MQTT_CPT_DISCONNECT] = 0x00,
|
|
[MQTT_CPT_AUTH] = 0x00,
|
|
};
|
|
|
|
const struct ist mqtt_fields_string[MQTT_FN_ENTRIES] = {
|
|
[MQTT_FN_INVALID] = IST(""),
|
|
|
|
/* it's MQTT 3.1.1 and 5.0, those fields have no unique id, so we use strings */
|
|
[MQTT_FN_FLAGS] = IST("flags"),
|
|
[MQTT_FN_REASON_CODE] = IST("reason_code"), /* MQTT 3.1.1: return_code */
|
|
[MQTT_FN_PROTOCOL_NAME] = IST("protocol_name"),
|
|
[MQTT_FN_PROTOCOL_VERSION] = IST("protocol_version"), /* MQTT 3.1.1: protocol_level */
|
|
[MQTT_FN_CLIENT_IDENTIFIER] = IST("client_identifier"),
|
|
[MQTT_FN_WILL_TOPIC] = IST("will_topic"),
|
|
[MQTT_FN_WILL_PAYLOAD] = IST("will_payload"), /* MQTT 3.1.1: will_message */
|
|
[MQTT_FN_USERNAME] = IST("username"),
|
|
[MQTT_FN_PASSWORD] = IST("password"),
|
|
[MQTT_FN_KEEPALIVE] = IST("keepalive"),
|
|
/* from here, it's MQTT 5.0 only */
|
|
[MQTT_FN_PAYLOAD_FORMAT_INDICATOR] = IST("1"),
|
|
[MQTT_FN_MESSAGE_EXPIRY_INTERVAL] = IST("2"),
|
|
[MQTT_FN_CONTENT_TYPE] = IST("3"),
|
|
[MQTT_FN_RESPONSE_TOPIC] = IST("8"),
|
|
[MQTT_FN_CORRELATION_DATA] = IST("9"),
|
|
[MQTT_FN_SUBSCRIPTION_IDENTIFIER] = IST("11"),
|
|
[MQTT_FN_SESSION_EXPIRY_INTERVAL] = IST("17"),
|
|
[MQTT_FN_ASSIGNED_CLIENT_IDENTIFIER] = IST("18"),
|
|
[MQTT_FN_SERVER_KEEPALIVE] = IST("19"),
|
|
[MQTT_FN_AUTHENTICATION_METHOD] = IST("21"),
|
|
[MQTT_FN_AUTHENTICATION_DATA] = IST("22"),
|
|
[MQTT_FN_REQUEST_PROBLEM_INFORMATION] = IST("23"),
|
|
[MQTT_FN_DELAY_INTERVAL] = IST("24"),
|
|
[MQTT_FN_REQUEST_RESPONSE_INFORMATION] = IST("25"),
|
|
[MQTT_FN_RESPONSE_INFORMATION] = IST("26"),
|
|
[MQTT_FN_SERVER_REFERENCE] = IST("28"),
|
|
[MQTT_FN_REASON_STRING] = IST("31"),
|
|
[MQTT_FN_RECEIVE_MAXIMUM] = IST("33"),
|
|
[MQTT_FN_TOPIC_ALIAS_MAXIMUM] = IST("34"),
|
|
[MQTT_FN_TOPIC_ALIAS] = IST("35"),
|
|
[MQTT_FN_MAXIMUM_QOS] = IST("36"),
|
|
[MQTT_FN_RETAIN_AVAILABLE] = IST("37"),
|
|
[MQTT_FN_USER_PROPERTY] = IST("38"),
|
|
[MQTT_FN_MAXIMUM_PACKET_SIZE] = IST("39"),
|
|
[MQTT_FN_WILDCARD_SUBSCRIPTION_AVAILABLE] = IST("40"),
|
|
[MQTT_FN_SUBSCRIPTION_IDENTIFIERS_AVAILABLE] = IST("41"),
|
|
[MQTT_FN_SHARED_SUBSCRIPTION_AVAILABLE] = IST("42"),
|
|
};
|
|
|
|
/* list of supported capturable field names for each MQTT control packet type */
|
|
const uint64_t mqtt_fields_per_packet[MQTT_CPT_ENTRIES] = {
|
|
[MQTT_CPT_INVALID] = 0,
|
|
|
|
[MQTT_CPT_CONNECT] = MQTT_FN_BIT_PROTOCOL_NAME | MQTT_FN_BIT_PROTOCOL_VERSION |
|
|
MQTT_FN_BIT_FLAGS | MQTT_FN_BIT_KEEPALIVE |
|
|
MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL | MQTT_FN_BIT_RECEIVE_MAXIMUM |
|
|
MQTT_FN_BIT_MAXIMUM_PACKET_SIZE | MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM |
|
|
MQTT_FN_BIT_REQUEST_RESPONSE_INFORMATION | MQTT_FN_BIT_REQUEST_PROBLEM_INFORMATION |
|
|
MQTT_FN_BIT_USER_PROPERTY | MQTT_FN_BIT_AUTHENTICATION_METHOD |
|
|
MQTT_FN_BIT_AUTHENTICATION_DATA | MQTT_FN_BIT_CLIENT_IDENTIFIER |
|
|
MQTT_FN_BIT_DELAY_INTERVAL | MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR |
|
|
MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL | MQTT_FN_BIT_CONTENT_TYPE |
|
|
MQTT_FN_BIT_RESPONSE_TOPIC | MQTT_FN_BIT_CORRELATION_DATA |
|
|
MQTT_FN_BIT_USER_PROPERTY | MQTT_FN_BIT_WILL_TOPIC |
|
|
MQTT_FN_BIT_WILL_PAYLOAD | MQTT_FN_BIT_USERNAME |
|
|
MQTT_FN_BIT_PASSWORD,
|
|
|
|
[MQTT_CPT_CONNACK] = MQTT_FN_BIT_FLAGS | MQTT_FN_BIT_PROTOCOL_VERSION |
|
|
MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL |
|
|
MQTT_FN_BIT_RECEIVE_MAXIMUM | MQTT_FN_BIT_MAXIMUM_QOS |
|
|
MQTT_FN_BIT_RETAIN_AVAILABLE | MQTT_FN_BIT_MAXIMUM_PACKET_SIZE |
|
|
MQTT_FN_BIT_ASSIGNED_CLIENT_IDENTIFIER | MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM |
|
|
MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_WILDCARD_SUBSCRIPTION_AVAILABLE |
|
|
MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIERS_AVAILABLE| MQTT_FN_BIT_SHARED_SUBSCRIPTION_AVAILABLE |
|
|
MQTT_FN_BIT_SERVER_KEEPALIVE | MQTT_FN_BIT_RESPONSE_INFORMATION |
|
|
MQTT_FN_BIT_SERVER_REFERENCE | MQTT_FN_BIT_USER_PROPERTY |
|
|
MQTT_FN_BIT_AUTHENTICATION_METHOD | MQTT_FN_BIT_AUTHENTICATION_DATA,
|
|
|
|
[MQTT_CPT_PUBLISH] = MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR | MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL |
|
|
MQTT_FN_BIT_CONTENT_TYPE | MQTT_FN_BIT_RESPONSE_TOPIC |
|
|
MQTT_FN_BIT_CORRELATION_DATA | MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER |
|
|
MQTT_FN_BIT_TOPIC_ALIAS | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBACK] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBREC] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBREL] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBCOMP] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_SUBSCRIBE] = MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_SUBACK] = MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_UNSUBSCRIBE] = MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_UNSUBACK] = MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PINGREQ] = 0,
|
|
|
|
[MQTT_CPT_PINGRESP] = 0,
|
|
|
|
[MQTT_CPT_DISCONNECT] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL |
|
|
MQTT_FN_BIT_SERVER_REFERENCE | MQTT_FN_BIT_REASON_STRING |
|
|
MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_AUTH] = MQTT_FN_BIT_AUTHENTICATION_METHOD | MQTT_FN_BIT_AUTHENTICATION_DATA |
|
|
MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
};
|
|
|
|
/* Checks the first byte of a message to read the fixed header and extract the
|
|
* packet type and flags. <parser> is supposed to point to the fix header byte.
|
|
*
|
|
* Fix header looks like:
|
|
* +-------+-----------+-----------+-----------+---------+----------+----------+---------+------------+
|
|
* | bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
* +-------+-----------+-----------+-----------+---------+----------+----------+---------+------------+
|
|
* | field | MQTT Control Packet Type | Flags specific to each Control Packet type |
|
|
* +-------+---------------------------------------------+--------------------------------------------+
|
|
*
|
|
* On success, <ptk> is updated with the packet type and flags and the new parser
|
|
* state is returned. On error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_fixed_hdr(struct ist parser, struct mqtt_pkt *pkt)
|
|
{
|
|
uint8_t type = (uint8_t)*istptr(parser);
|
|
uint8_t ptype = (type & 0xF0) >> 4;
|
|
uint8_t flags = type & 0x0F;
|
|
|
|
if (ptype == MQTT_CPT_INVALID || ptype >= MQTT_CPT_ENTRIES || flags != mqtt_cpt_flags[ptype])
|
|
return IST_NULL;
|
|
|
|
pkt->fixed_hdr.type = ptype;
|
|
pkt->fixed_hdr.flags = flags;
|
|
return istnext(parser);
|
|
}
|
|
|
|
/* Reads a one byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901007
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i>, if provided, and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_1byte_int(struct ist parser, uint8_t *i)
|
|
{
|
|
if (istlen(parser) < 1)
|
|
return IST_NULL;
|
|
if (i)
|
|
*i = (uint8_t)*istptr(parser);
|
|
parser = istadv(parser, 1);
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a two byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901008
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i>, if provided, and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_2byte_int(struct ist parser, uint16_t *i)
|
|
{
|
|
if (istlen(parser) < 2)
|
|
return IST_NULL;
|
|
if (i) {
|
|
*i = (uint8_t)*istptr(parser) << 8;
|
|
*i += (uint8_t)*(istptr(parser) + 1);
|
|
}
|
|
parser = istadv(parser, 2);
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a four byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901009
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i>, if provided, and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_4byte_int(struct ist parser, uint32_t *i)
|
|
{
|
|
if (istlen(parser) < 4)
|
|
return IST_NULL;
|
|
if (i) {
|
|
*i = (uint8_t)*istptr(parser) << 24;
|
|
*i += (uint8_t)*(istptr(parser) + 1) << 16;
|
|
*i += (uint8_t)*(istptr(parser) + 2) << 8;
|
|
*i += (uint8_t)*(istptr(parser) + 3);
|
|
}
|
|
parser = istadv(parser, 4);
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a variable byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718023
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011
|
|
*
|
|
* It is encoded using a variable length encoding scheme which uses a single
|
|
* byte for values up to 127. Larger values are handled as follows. 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.
|
|
* Thus each byte encodes 128 values and a "continuation bit".
|
|
*
|
|
* The maximum number of bytes in the Remaining Length field is four
|
|
* (MQTT_REMAINING_LENGHT_MAX_SIZE).
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i> and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_varint(struct ist parser, uint32_t *i)
|
|
{
|
|
int off, m;
|
|
|
|
off = m = 0;
|
|
if (i)
|
|
*i = 0;
|
|
for (off = 0; off < MQTT_REMAINING_LENGHT_MAX_SIZE && istlen(parser); off++) {
|
|
uint8_t byte = (uint8_t)*istptr(parser);
|
|
|
|
if (i) {
|
|
*i += (byte & 127) << m;
|
|
m += 7; /* preparing <m> for next byte */
|
|
}
|
|
parser = istnext(parser);
|
|
|
|
/* we read the latest byte for the remaining length field */
|
|
if (byte <= 127)
|
|
break;
|
|
}
|
|
|
|
if (off == MQTT_REMAINING_LENGHT_MAX_SIZE)
|
|
return IST_NULL;
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a MQTT string. more information here :
|
|
* http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718016
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901010
|
|
*
|
|
* In MQTT, strings are prefixed by their size, encoded over 2 bytes:
|
|
* byte 1: length MSB
|
|
* byte 2: length LSB
|
|
* byte 3: string
|
|
* ...
|
|
*
|
|
* string size is MSB * 256 + LSB
|
|
*
|
|
* <parser> is supposed to point to the first byte of the string. On success the
|
|
* string is stored in <*str>, if provided, and the new parser state is
|
|
* returned. On error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_string(struct ist parser, struct ist *str)
|
|
{
|
|
uint16_t len;
|
|
|
|
/* read and compute the string length */
|
|
if (istlen(parser) <= 2)
|
|
goto error;
|
|
|
|
len = ((uint16_t)*istptr(parser) << 8) + (uint16_t)*(istptr(parser) + 1);
|
|
parser = istadv(parser, 2);
|
|
if (istlen(parser) < len)
|
|
goto error;
|
|
|
|
if (str) {
|
|
str->ptr = istptr(parser);
|
|
str->len = len;
|
|
}
|
|
|
|
return istadv(parser, len);
|
|
|
|
error:
|
|
return IST_NULL;
|
|
}
|
|
|
|
/* Helper function to convert a unsigned integer to a string. The result is
|
|
* written in <buf>. On success, the written size is returned, otherwise, on
|
|
* error, 0 is returned.
|
|
*/
|
|
static inline size_t mqtt_uint2str(struct buffer *buf, uint32_t i)
|
|
{
|
|
char *end;
|
|
|
|
end = ultoa_o(i, buf->area, buf->size);
|
|
if (!end)
|
|
return 0;
|
|
buf->data = end - buf->area;
|
|
return buf->data;
|
|
}
|
|
|
|
/* Extracts the value of a <fieldname_id> of type <type> from a given MQTT
|
|
* message <msg>. IST_NULL is returned if an error occurred while parsing or if
|
|
* the field could not be found. If more data are required, the message with a
|
|
* length set to 0 is returned. If the field is found, the response is returned
|
|
* as a struct ist.
|
|
*/
|
|
struct ist mqtt_field_value(struct ist msg, int type, int fieldname_id)
|
|
{
|
|
struct buffer *trash = get_trash_chunk();
|
|
struct mqtt_pkt mpkt;
|
|
struct ist res;
|
|
|
|
switch (mqtt_validate_message(msg, &mpkt)) {
|
|
case MQTT_VALID_MESSAGE:
|
|
if (mpkt.fixed_hdr.type != type)
|
|
goto not_found_or_invalid;
|
|
break;
|
|
case MQTT_NEED_MORE_DATA:
|
|
goto need_more;
|
|
case MQTT_INVALID_MESSAGE:
|
|
goto not_found_or_invalid;
|
|
}
|
|
|
|
switch (type) {
|
|
case MQTT_CPT_CONNECT:
|
|
switch (fieldname_id) {
|
|
case MQTT_FN_FLAGS:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.flags))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_PROTOCOL_NAME:
|
|
if (!istlen(mpkt.data.connect.var_hdr.protocol_name))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.var_hdr.protocol_name;
|
|
goto end;
|
|
|
|
case MQTT_FN_PROTOCOL_VERSION:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.protocol_version))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_CLIENT_IDENTIFIER:
|
|
if (!istlen(mpkt.data.connect.payload.client_identifier))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.client_identifier;
|
|
goto end;
|
|
|
|
case MQTT_FN_WILL_TOPIC:
|
|
if (!istlen(mpkt.data.connect.payload.will_topic))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_topic;
|
|
goto end;
|
|
|
|
case MQTT_FN_WILL_PAYLOAD:
|
|
if (!istlen(mpkt.data.connect.payload.will_payload))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_payload;
|
|
goto end;
|
|
|
|
case MQTT_FN_USERNAME:
|
|
if (!istlen(mpkt.data.connect.payload.username))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.username;
|
|
goto end;
|
|
|
|
case MQTT_FN_PASSWORD:
|
|
if (!istlen(mpkt.data.connect.payload.password))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.password;
|
|
goto end;
|
|
|
|
case MQTT_FN_KEEPALIVE:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.keepalive))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_PAYLOAD_FORMAT_INDICATOR:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.payload.will_props.payload_format_indicator))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MESSAGE_EXPIRY_INTERVAL:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.payload.will_props.message_expiry_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_CONTENT_TYPE:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.payload.will_props.content_type))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_props.content_type;
|
|
goto end;
|
|
|
|
case MQTT_FN_RESPONSE_TOPIC:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.payload.will_props.response_topic))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_props.response_topic;
|
|
goto end;
|
|
|
|
case MQTT_FN_CORRELATION_DATA:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.payload.will_props.correlation_data))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_props.correlation_data;
|
|
goto end;
|
|
|
|
case MQTT_FN_SESSION_EXPIRY_INTERVAL:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.session_expiry_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_METHOD:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.var_hdr.props.authentication_method))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.var_hdr.props.authentication_method;
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_DATA:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.var_hdr.props.authentication_data))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.var_hdr.props.authentication_data;
|
|
goto end;
|
|
|
|
case MQTT_FN_REQUEST_PROBLEM_INFORMATION:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.request_problem_information))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_DELAY_INTERVAL:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.payload.will_props.delay_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_REQUEST_RESPONSE_INFORMATION:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.request_response_information))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_RECEIVE_MAXIMUM:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.receive_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_TOPIC_ALIAS_MAXIMUM:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.topic_alias_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MAXIMUM_PACKET_SIZE:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.maximum_packet_size))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
default:
|
|
goto not_found_or_invalid;
|
|
}
|
|
break;
|
|
|
|
case MQTT_CPT_CONNACK:
|
|
switch (fieldname_id) {
|
|
case MQTT_FN_FLAGS:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.flags))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_REASON_CODE:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.reason_code))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_PROTOCOL_VERSION:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.protocol_version))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_SESSION_EXPIRY_INTERVAL:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.session_expiry_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_ASSIGNED_CLIENT_IDENTIFIER:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.assigned_client_identifier))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.assigned_client_identifier;
|
|
goto end;
|
|
|
|
case MQTT_FN_SERVER_KEEPALIVE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.server_keepalive))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_METHOD:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.authentication_method))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.authentication_method;
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_DATA:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.authentication_data))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.authentication_data;
|
|
goto end;
|
|
|
|
case MQTT_FN_RESPONSE_INFORMATION:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.response_information))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.response_information;
|
|
goto end;
|
|
|
|
case MQTT_FN_SERVER_REFERENCE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.server_reference))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.server_reference;
|
|
goto end;
|
|
|
|
case MQTT_FN_REASON_STRING:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.reason_string))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.reason_string;
|
|
goto end;
|
|
|
|
case MQTT_FN_RECEIVE_MAXIMUM:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.receive_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_TOPIC_ALIAS_MAXIMUM:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.topic_alias_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MAXIMUM_QOS:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.maximum_qos))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_RETAIN_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.retain_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MAXIMUM_PACKET_SIZE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.maximum_packet_size))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_WILDCARD_SUBSCRIPTION_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.wildcard_subscription_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_SUBSCRIPTION_IDENTIFIERS_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.subscription_identifiers_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_SHARED_SUBSCRIPTION_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.shared_subsription_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
default:
|
|
goto not_found_or_invalid;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
goto not_found_or_invalid;
|
|
}
|
|
|
|
end:
|
|
return res;
|
|
|
|
need_more:
|
|
return ist2(istptr(msg), 0);
|
|
|
|
not_found_or_invalid:
|
|
return IST_NULL;
|
|
}
|
|
|
|
/* Parses a CONNECT packet :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901033
|
|
*
|
|
* <parser> should point right after the MQTT fixed header. The remaining length
|
|
* was already checked, thus missing data is an error. On success, the result of
|
|
* the parsing is stored in <mpkt>.
|
|
*
|
|
* Returns:
|
|
* MQTT_INVALID_MESSAGE if the CONNECT message is invalid
|
|
* MQTT_VALID_MESSAGE if the CONNECT message looks valid
|
|
*/
|
|
static int mqtt_parse_connect(struct ist parser, struct mqtt_pkt *mpkt)
|
|
{
|
|
/* The parser length is stored to be sure exactly consumed the announced
|
|
* remaining length. */
|
|
size_t orig_len = istlen(parser);
|
|
int ret = MQTT_INVALID_MESSAGE;
|
|
|
|
/*
|
|
* parsing variable header
|
|
*/
|
|
/* read protocol_name */
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.var_hdr.protocol_name);
|
|
if (!isttest(parser) || !isteqi(mpkt->data.connect.var_hdr.protocol_name, ist("MQTT")))
|
|
goto end;
|
|
|
|
/* read protocol_version */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connect.var_hdr.protocol_version);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
if (mpkt->data.connect.var_hdr.protocol_version != MQTT_VERSION_3_1_1 &&
|
|
mpkt->data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto end;
|
|
|
|
/* read flags */
|
|
/* bit 1 is 'reserved' and must be set to 0 in CONNECT message flags */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connect.var_hdr.flags);
|
|
if (!isttest(parser) || (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_RESERVED))
|
|
goto end;
|
|
|
|
/* if WILL flag must be set to have WILL_QOS flag or WILL_RETAIN set */
|
|
if ((mpkt->data.connect.var_hdr.flags & (MQTT_CONNECT_FL_WILL|MQTT_CONNECT_FL_WILL_QOS|MQTT_CONNECT_FL_WILL_RETAIN)) == MQTT_CONNECT_FL_WILL_QOS)
|
|
goto end;
|
|
|
|
/* read keepalive */
|
|
parser = mqtt_read_2byte_int(parser, &mpkt->data.connect.var_hdr.keepalive);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
|
|
/* read properties, only available in MQTT_VERSION_5_0 */
|
|
if (mpkt->data.connect.var_hdr.protocol_version == MQTT_VERSION_5_0) {
|
|
struct ist props;
|
|
unsigned int user_prop_idx = 0;
|
|
uint64_t fields = 0;
|
|
uint32_t plen = 0;
|
|
|
|
parser = mqtt_read_varint(parser, &plen);
|
|
if (!isttest(parser) || istlen(parser) < plen)
|
|
goto end;
|
|
props = ist2(istptr(parser), plen);
|
|
parser = istadv(parser, props.len);
|
|
|
|
while (istlen(props) > 0) {
|
|
switch (*istptr(props)) {
|
|
case MQTT_PROP_SESSION_EXPIRY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.session_expiry_interval);
|
|
fields |= MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_RECEIVE_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_RECEIVE_MAXIMUM)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.receive_maximum);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connect.var_hdr.props.receive_maximum)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_RECEIVE_MAXIMUM;
|
|
break;
|
|
|
|
case MQTT_PROP_MAXIMUM_PACKET_SIZE:
|
|
if (fields & MQTT_FN_BIT_MAXIMUM_PACKET_SIZE)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.maximum_packet_size);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connect.var_hdr.props.maximum_packet_size)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_MAXIMUM_PACKET_SIZE;
|
|
break;
|
|
|
|
case MQTT_PROP_TOPIC_ALIAS_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_TOPIC_ALIAS)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.topic_alias_maximum);
|
|
fields |= MQTT_FN_BIT_TOPIC_ALIAS;
|
|
break;
|
|
|
|
case MQTT_PROP_REQUEST_RESPONSE_INFORMATION:
|
|
if (fields & MQTT_FN_BIT_REQUEST_RESPONSE_INFORMATION)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.request_response_information);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connect.var_hdr.props.request_response_information > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_REQUEST_RESPONSE_INFORMATION;
|
|
break;
|
|
|
|
case MQTT_PROP_REQUEST_PROBLEM_INFORMATION:
|
|
if (fields & MQTT_FN_BIT_REQUEST_PROBLEM_INFORMATION)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.request_problem_information);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connect.var_hdr.props.request_problem_information > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_REQUEST_PROBLEM_INFORMATION;
|
|
break;
|
|
|
|
case MQTT_PROP_USER_PROPERTIES:
|
|
/* if we reached MQTT_PROP_USER_PROPERTY_ENTRIES already, then
|
|
* we start writing over the first property */
|
|
if (user_prop_idx >= MQTT_PROP_USER_PROPERTY_ENTRIES)
|
|
user_prop_idx = 0;
|
|
|
|
/* read user property name and value */
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.var_hdr.props.user_props[user_prop_idx].name);
|
|
if (!isttest(props))
|
|
goto end;
|
|
props = mqtt_read_string(props, &mpkt->data.connect.var_hdr.props.user_props[user_prop_idx].value);
|
|
++user_prop_idx;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_METHOD:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_METHOD)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.var_hdr.props.authentication_method);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_METHOD;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_DATA:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_DATA)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.var_hdr.props.authentication_data);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_DATA;
|
|
break;
|
|
|
|
default:
|
|
goto end;
|
|
}
|
|
|
|
if (!isttest(props))
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* cannot have auth data without auth method */
|
|
if (!istlen(mpkt->data.connect.var_hdr.props.authentication_method) &&
|
|
istlen(mpkt->data.connect.var_hdr.props.authentication_data))
|
|
goto end;
|
|
|
|
/* parsing payload
|
|
*
|
|
* Content of payload is related to flags parsed above and the field order is pre-defined:
|
|
* Client Identifier, Will Topic, Will Message, User Name, Password
|
|
*/
|
|
/* read client identifier */
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.client_identifier);
|
|
if (!isttest(parser) || !istlen(mpkt->data.connect.payload.client_identifier))
|
|
goto end;
|
|
|
|
/* read Will Properties, for MQTT v5 only
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901060
|
|
*/
|
|
if ((mpkt->data.connect.var_hdr.protocol_version == MQTT_VERSION_5_0) &&
|
|
(mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL)) {
|
|
struct ist props;
|
|
unsigned int user_prop_idx = 0;
|
|
uint64_t fields = 0;
|
|
uint32_t plen = 0;
|
|
|
|
parser = mqtt_read_varint(parser, &plen);
|
|
if (!isttest(parser) || istlen(parser) < plen)
|
|
goto end;
|
|
props = ist2(istptr(parser), plen);
|
|
parser = istadv(parser, props.len);
|
|
|
|
while (istlen(props) > 0) {
|
|
switch (*istptr(props)) {
|
|
case MQTT_PROP_WILL_DELAY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_DELAY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.payload.will_props.delay_interval);
|
|
fields |= MQTT_FN_BIT_DELAY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR:
|
|
if (fields & MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connect.payload.will_props.payload_format_indicator);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connect.payload.will_props.payload_format_indicator > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR;
|
|
break;
|
|
|
|
case MQTT_PROP_MESSAGE_EXPIRY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.payload.will_props.message_expiry_interval);
|
|
fields |= MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_CONTENT_TYPE:
|
|
if (fields & MQTT_FN_BIT_CONTENT_TYPE)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.content_type);
|
|
fields |= MQTT_FN_BIT_CONTENT_TYPE;
|
|
break;
|
|
|
|
case MQTT_PROP_RESPONSE_TOPIC:
|
|
if (fields & MQTT_FN_BIT_RESPONSE_TOPIC)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.response_topic);
|
|
fields |= MQTT_FN_BIT_RESPONSE_TOPIC;
|
|
break;
|
|
|
|
case MQTT_PROP_CORRELATION_DATA:
|
|
if (fields & MQTT_FN_BIT_CORRELATION_DATA)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.correlation_data);
|
|
fields |= MQTT_FN_BIT_CORRELATION_DATA;
|
|
break;
|
|
|
|
case MQTT_PROP_USER_PROPERTIES:
|
|
/* if we reached MQTT_PROP_USER_PROPERTY_ENTRIES already, then
|
|
* we start writing over the first property */
|
|
if (user_prop_idx >= MQTT_PROP_USER_PROPERTY_ENTRIES)
|
|
user_prop_idx = 0;
|
|
|
|
/* read user property name and value */
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.user_props[user_prop_idx].name);
|
|
if (!isttest(props))
|
|
goto end;
|
|
props = mqtt_read_string(props, &mpkt->data.connect.payload.will_props.user_props[user_prop_idx].value);
|
|
++user_prop_idx;
|
|
break;
|
|
|
|
default:
|
|
goto end;
|
|
}
|
|
|
|
if (!isttest(props))
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* read Will Topic and Will Message (MQTT 3.1.1) or Payload (MQTT 5.0) */
|
|
if (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL) {
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.will_topic);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.will_payload);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
}
|
|
|
|
/* read User Name */
|
|
if (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_USERNAME) {
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.username);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
}
|
|
|
|
/* read Password */
|
|
if (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_PASSWORD) {
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.password);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
}
|
|
|
|
if ((orig_len - istlen(parser)) == mpkt->fixed_hdr.remaining_length)
|
|
ret = MQTT_VALID_MESSAGE;
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
/* Parses a CONNACK packet :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901074
|
|
*
|
|
* <parser> should point right after the MQTT fixed header. The remaining length
|
|
* was already checked, thus missing data is an error. On success, the result of
|
|
* the parsing is stored in <mpkt>.
|
|
*
|
|
* Returns:
|
|
* MQTT_INVALID_MESSAGE if the CONNECT message is invalid
|
|
* MQTT_VALID_MESSAGE if the CONNECT message looks valid
|
|
*/
|
|
static int mqtt_parse_connack(struct ist parser, struct mqtt_pkt *mpkt)
|
|
{
|
|
/* The parser length is stored to be sure exactly consumed the announced
|
|
* remaining length. */
|
|
size_t orig_len = istlen(parser);
|
|
int ret = MQTT_INVALID_MESSAGE;
|
|
|
|
if (istlen(parser) < 2)
|
|
goto end;
|
|
else if (istlen(parser) == 2)
|
|
mpkt->data.connack.var_hdr.protocol_version = MQTT_VERSION_3_1_1;
|
|
else
|
|
mpkt->data.connack.var_hdr.protocol_version = MQTT_VERSION_5_0;
|
|
|
|
/*
|
|
* parsing variable header
|
|
*/
|
|
/* read flags */
|
|
/* bits 7 to 1 on flags are reserved and must be 0 */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connack.var_hdr.flags);
|
|
if (!isttest(parser) || (mpkt->data.connack.var_hdr.flags & 0xFE))
|
|
goto end;
|
|
|
|
/* read reason_code */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connack.var_hdr.reason_code);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
|
|
/* we can leave here for MQTT 3.1.1 */
|
|
if (mpkt->data.connack.var_hdr.protocol_version == MQTT_VERSION_3_1_1) {
|
|
if ((orig_len - istlen(parser)) == mpkt->fixed_hdr.remaining_length)
|
|
ret = MQTT_VALID_MESSAGE;
|
|
goto end;
|
|
}
|
|
|
|
/* read properties, only available in MQTT_VERSION_5_0 */
|
|
if (mpkt->data.connack.var_hdr.protocol_version == MQTT_VERSION_5_0) {
|
|
struct ist props;
|
|
unsigned int user_prop_idx = 0;
|
|
uint64_t fields = 0;
|
|
uint32_t plen = 0;
|
|
|
|
parser = mqtt_read_varint(parser, &plen);
|
|
if (!isttest(parser) || istlen(parser) < plen)
|
|
goto end;
|
|
props = ist2(istptr(parser), plen);
|
|
parser = istadv(parser, props.len);
|
|
|
|
while (istlen(props) > 0) {
|
|
switch (*istptr(props)) {
|
|
case MQTT_PROP_SESSION_EXPIRY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.session_expiry_interval);
|
|
fields |= MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_RECEIVE_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_RECEIVE_MAXIMUM)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.receive_maximum);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connack.var_hdr.props.receive_maximum)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_RECEIVE_MAXIMUM;
|
|
break;
|
|
|
|
case MQTT_PROP_MAXIMUM_QOS:
|
|
if (fields & MQTT_FN_BIT_MAXIMUM_QOS)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.maximum_qos);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.maximum_qos > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_MAXIMUM_QOS;
|
|
break;
|
|
|
|
case MQTT_PROP_RETAIN_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_RETAIN_AVAILABLE)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.retain_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.retain_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_RETAIN_AVAILABLE;
|
|
break;
|
|
|
|
case MQTT_PROP_MAXIMUM_PACKET_SIZE:
|
|
if (fields & MQTT_FN_BIT_MAXIMUM_PACKET_SIZE)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.maximum_packet_size);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connack.var_hdr.props.maximum_packet_size)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_MAXIMUM_PACKET_SIZE;
|
|
break;
|
|
|
|
case MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER:
|
|
if (fields & MQTT_FN_BIT_ASSIGNED_CLIENT_IDENTIFIER)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.assigned_client_identifier);
|
|
if (!istlen(mpkt->data.connack.var_hdr.props.assigned_client_identifier))
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_ASSIGNED_CLIENT_IDENTIFIER;
|
|
break;
|
|
|
|
case MQTT_PROP_TOPIC_ALIAS_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.topic_alias_maximum);
|
|
fields |= MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM;
|
|
break;
|
|
|
|
case MQTT_PROP_REASON_STRING:
|
|
if (fields & MQTT_FN_BIT_REASON_STRING)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.reason_string);
|
|
fields |= MQTT_FN_BIT_REASON_STRING;
|
|
break;
|
|
|
|
case MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_WILDCARD_SUBSCRIPTION_AVAILABLE)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.wildcard_subscription_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.wildcard_subscription_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_WILDCARD_SUBSCRIPTION_AVAILABLE;
|
|
break;
|
|
|
|
case MQTT_PROP_SUBSCRIPTION_IDENTIFIERS_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.subscription_identifiers_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.subscription_identifiers_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER;
|
|
break;
|
|
|
|
case MQTT_PROP_SHARED_SUBSRIPTION_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_SHARED_SUBSCRIPTION_AVAILABLE)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.shared_subsription_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.shared_subsription_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_SHARED_SUBSCRIPTION_AVAILABLE;
|
|
break;
|
|
|
|
case MQTT_PROP_SERVER_KEEPALIVE:
|
|
if (fields & MQTT_FN_BIT_SERVER_KEEPALIVE)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.server_keepalive);
|
|
fields |= MQTT_FN_BIT_SERVER_KEEPALIVE;
|
|
break;
|
|
|
|
case MQTT_PROP_RESPONSE_INFORMATION:
|
|
if (fields & MQTT_FN_BIT_RESPONSE_INFORMATION)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.response_information);
|
|
fields |= MQTT_FN_BIT_RESPONSE_INFORMATION;
|
|
break;
|
|
|
|
case MQTT_PROP_SERVER_REFERENCE:
|
|
if (fields & MQTT_FN_BIT_SERVER_REFERENCE)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.server_reference);
|
|
fields |= MQTT_FN_BIT_SERVER_REFERENCE;
|
|
break;
|
|
|
|
case MQTT_PROP_USER_PROPERTIES:
|
|
/* if we reached MQTT_PROP_USER_PROPERTY_ENTRIES already, then
|
|
* we start writing over the first property */
|
|
if (user_prop_idx >= MQTT_PROP_USER_PROPERTY_ENTRIES)
|
|
user_prop_idx = 0;
|
|
|
|
/* read user property name and value */
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.user_props[user_prop_idx].name);
|
|
if (!isttest(props))
|
|
goto end;
|
|
props = mqtt_read_string(props, &mpkt->data.connack.var_hdr.props.user_props[user_prop_idx].value);
|
|
++user_prop_idx;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_METHOD:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_METHOD)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.authentication_method);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_METHOD;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_DATA:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_DATA)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.authentication_data);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_DATA;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (!isttest(props))
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if ((orig_len - istlen(parser)) == mpkt->fixed_hdr.remaining_length)
|
|
ret = MQTT_VALID_MESSAGE;
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Parses and validates a MQTT packet
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028
|
|
*
|
|
* For now, due to HAProxy limitation, only validation of CONNECT and CONNACK packets
|
|
* are supported.
|
|
*
|
|
* - check FIXED_HDR
|
|
* - check remaining length
|
|
* - check variable headers and payload
|
|
*
|
|
* if <mpkt> is not NULL, then this structure will be filled up as well. An
|
|
* unsupported packet type is considered as invalid. It is not a problem for now
|
|
* because only the first packet on each side can be parsed (CONNECT for the
|
|
* client and CONNACK for the server).
|
|
*
|
|
* Returns:
|
|
* MQTT_INVALID_MESSAGE if the message is invalid
|
|
* MQTT_NEED_MORE_DATA if we need more data to fully validate the message
|
|
* MQTT_VALID_MESSAGE if the message looks valid
|
|
*/
|
|
int mqtt_validate_message(const struct ist msg, struct mqtt_pkt *mpkt)
|
|
{
|
|
struct ist parser;
|
|
struct mqtt_pkt tmp_mpkt;
|
|
int ret = MQTT_INVALID_MESSAGE;
|
|
|
|
if (!mpkt)
|
|
mpkt = &tmp_mpkt;
|
|
memset(mpkt, 0, sizeof(*mpkt));
|
|
|
|
parser = msg;
|
|
if (istlen(msg) < MQTT_MIN_PKT_SIZE) {
|
|
ret = MQTT_NEED_MORE_DATA;
|
|
goto end;
|
|
}
|
|
|
|
/* parse the MQTT fixed header */
|
|
parser = mqtt_read_fixed_hdr(parser, mpkt);
|
|
if (!isttest(parser)) {
|
|
ret = MQTT_INVALID_MESSAGE;
|
|
goto end;
|
|
}
|
|
|
|
/* Now parsing "remaining length" field */
|
|
parser = mqtt_read_varint(parser, &mpkt->fixed_hdr.remaining_length);
|
|
if (!isttest(parser)) {
|
|
ret = MQTT_INVALID_MESSAGE;
|
|
goto end;
|
|
}
|
|
|
|
if (istlen(parser) < mpkt->fixed_hdr.remaining_length)
|
|
return MQTT_NEED_MORE_DATA;
|
|
|
|
/* Now parsing the variable header and payload, which is based on the packet type */
|
|
switch (mpkt->fixed_hdr.type) {
|
|
case MQTT_CPT_CONNECT:
|
|
ret = mqtt_parse_connect(parser, mpkt);
|
|
break;
|
|
case MQTT_CPT_CONNACK:
|
|
ret = mqtt_parse_connack(parser, mpkt);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
end:
|
|
return ret;
|
|
}
|