zephyr/subsys/net/lib/mqtt/mqtt_decoder.c
Robert Lubos 15ad90aceb net: mqtt: Add MQTT 5.0 support for PUBLISH
Add support for PUBLISH message specified in MQTT 5.0. The message
encoder and decoder were updated to support MQTT properties.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
2025-03-26 16:19:42 +01:00

850 lines
19 KiB
C

/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file mqtt_decoder.c
*
* @brief Decoder functions needed for decoding packets received from the
* broker.
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_mqtt_dec, CONFIG_MQTT_LOG_LEVEL);
#include "mqtt_internal.h"
#include "mqtt_os.h"
/**
* @brief Unpacks unsigned 8 bit value from the buffer from the offset
* requested.
*
* @param[inout] buf A pointer to the buf_ctx structure containing current
* buffer position.
* @param[out] val Memory where the value is to be unpacked.
*
* @retval 0 if the procedure is successful.
* @retval -EINVAL if the buffer would be exceeded during the read
*/
static int unpack_uint8(struct buf_ctx *buf, uint8_t *val)
{
uint8_t *cur = buf->cur;
uint8_t *end = buf->end;
NET_DBG(">> cur:%p, end:%p", (void *)cur, (void *)end);
if ((end - cur) < sizeof(uint8_t)) {
return -EINVAL;
}
*val = cur[0];
buf->cur = (cur + sizeof(uint8_t));
NET_DBG("<< val:%02x", *val);
return 0;
}
/**
* @brief Unpacks unsigned 16 bit value from the buffer from the offset
* requested.
*
* @param[inout] buf A pointer to the buf_ctx structure containing current
* buffer position.
* @param[out] val Memory where the value is to be unpacked.
*
* @retval 0 if the procedure is successful.
* @retval -EINVAL if the buffer would be exceeded during the read
*/
static int unpack_uint16(struct buf_ctx *buf, uint16_t *val)
{
uint8_t *cur = buf->cur;
uint8_t *end = buf->end;
NET_DBG(">> cur:%p, end:%p", (void *)cur, (void *)end);
if ((end - cur) < sizeof(uint16_t)) {
return -EINVAL;
}
*val = sys_get_be16(cur);
buf->cur = (cur + sizeof(uint16_t));
NET_DBG("<< val:%04x", *val);
return 0;
}
/**
* @brief Unpacks utf8 string from the buffer from the offset requested.
*
* @param[inout] buf A pointer to the buf_ctx structure containing current
* buffer position.
* @param[out] str Pointer to a string that will hold the string location
* in the buffer.
*
* @retval 0 if the procedure is successful.
* @retval -EINVAL if the buffer would be exceeded during the read
*/
static int unpack_utf8_str(struct buf_ctx *buf, struct mqtt_utf8 *str)
{
uint16_t utf8_strlen;
int err_code;
NET_DBG(">> cur:%p, end:%p", (void *)buf->cur, (void *)buf->end);
err_code = unpack_uint16(buf, &utf8_strlen);
if (err_code != 0) {
return err_code;
}
if ((buf->end - buf->cur) < utf8_strlen) {
return -EINVAL;
}
str->size = utf8_strlen;
/* Zero length UTF8 strings permitted. */
if (utf8_strlen) {
/* Point to right location in buffer. */
str->utf8 = buf->cur;
buf->cur += utf8_strlen;
} else {
str->utf8 = NULL;
}
NET_DBG("<< str_size:%08x", (uint32_t)GET_UT8STR_BUFFER_SIZE(str));
return 0;
}
/**
* @brief Unpacks binary string from the buffer from the offset requested with
* the length provided.
*
* @param[in] length Binary string length.
* @param[inout] buf A pointer to the buf_ctx structure containing current
* buffer position.
* @param[out] str Pointer to a binary string that will hold the binary string
* location in the buffer.
*
* @retval 0 if the procedure is successful.
* @retval -EINVAL if the buffer would be exceeded during the read
*/
static int unpack_raw_data(uint32_t length, struct buf_ctx *buf,
struct mqtt_binstr *str)
{
NET_DBG(">> cur:%p, end:%p", (void *)buf->cur, (void *)buf->end);
if ((buf->end - buf->cur) < length) {
return -EINVAL;
}
str->len = length;
/* Zero length binary strings are permitted. */
if (length > 0) {
str->data = buf->cur;
buf->cur += length;
} else {
str->data = NULL;
}
NET_DBG("<< bin len:%08x", GET_BINSTR_BUFFER_SIZE(str));
return 0;
}
int unpack_variable_int(struct buf_ctx *buf, uint32_t *val)
{
uint8_t shift = 0U;
int bytes = 0;
*val = 0U;
do {
if (bytes >= MQTT_MAX_LENGTH_BYTES) {
return -EINVAL;
}
if (buf->cur >= buf->end) {
return -EAGAIN;
}
*val += ((uint32_t)*(buf->cur) & MQTT_LENGTH_VALUE_MASK)
<< shift;
shift += MQTT_LENGTH_SHIFT;
bytes++;
} while ((*(buf->cur++) & MQTT_LENGTH_CONTINUATION_BIT) != 0U);
if (*val > MQTT_MAX_PAYLOAD_SIZE) {
return -EINVAL;
}
NET_DBG("variable int:0x%08x", *val);
return bytes;
}
int fixed_header_decode(struct buf_ctx *buf, uint8_t *type_and_flags,
uint32_t *length)
{
int err_code;
err_code = unpack_uint8(buf, type_and_flags);
if (err_code != 0) {
return err_code;
}
err_code = unpack_variable_int(buf, length);
if (err_code < 0) {
return err_code;
}
return 0;
}
#if defined(CONFIG_MQTT_VERSION_5_0)
/**
* @brief Unpacks unsigned 32 bit value from the buffer from the offset
* requested.
*
* @param[inout] buf A pointer to the buf_ctx structure containing current
* buffer position.
* @param[out] val Memory where the value is to be unpacked.
*
* @retval 0 if the procedure is successful.
* @retval -EINVAL if the buffer would be exceeded during the read
*/
static int unpack_uint32(struct buf_ctx *buf, uint32_t *val)
{
uint8_t *cur = buf->cur;
uint8_t *end = buf->end;
NET_DBG(">> cur:%p, end:%p", (void *)cur, (void *)end);
if ((end - cur) < sizeof(uint32_t)) {
return -EINVAL;
}
*val = sys_get_be32(cur);
buf->cur = (cur + sizeof(uint32_t));
NET_DBG("<< val:%08x", *val);
return 0;
}
/**
* @brief Unpacks binary string from the buffer from the offset requested.
* Binary string length is decoded from the first two bytes of the buffer.
*
* @param[inout] buf A pointer to the buf_ctx structure containing current
* buffer position.
* @param[out] bin Pointer to a binary string that will hold the binary string
* location in the buffer.
*
* @retval 0 if the procedure is successful.
* @retval -EINVAL if the buffer would be exceeded during the read
*/
static int unpack_binary_data(struct buf_ctx *buf, struct mqtt_binstr *bin)
{
uint16_t len;
int err;
NET_DBG(">> cur:%p, end:%p", (void *)buf->cur, (void *)buf->end);
err = unpack_uint16(buf, &len);
if (err != 0) {
return err;
}
if ((buf->end - buf->cur) < len) {
return -EINVAL;
}
bin->len = len;
/* Zero length binary strings are permitted. */
if (len > 0) {
bin->data = buf->cur;
buf->cur += len;
} else {
bin->data = NULL;
}
NET_DBG("<< bin len:%08x", GET_BINSTR_BUFFER_SIZE(bin));
return 0;
}
struct property_decoder {
void *data;
bool *found;
uint8_t type;
};
int decode_uint32_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
uint32_t *value = prop->data;
if (*remaining_len < sizeof(uint32_t)) {
return -EBADMSG;
}
if (unpack_uint32(buf, value) < 0) {
return -EBADMSG;
}
*remaining_len -= sizeof(uint32_t);
*prop->found = true;
return 0;
}
int decode_uint16_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
uint16_t *value = prop->data;
if (*remaining_len < sizeof(uint16_t)) {
return -EBADMSG;
}
if (unpack_uint16(buf, value) < 0) {
return -EBADMSG;
}
*remaining_len -= sizeof(uint16_t);
*prop->found = true;
return 0;
}
int decode_uint8_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
uint8_t *value = prop->data;
if (*remaining_len < sizeof(uint8_t)) {
return -EBADMSG;
}
if (unpack_uint8(buf, value) < 0) {
return -EBADMSG;
}
*remaining_len -= sizeof(uint8_t);
*prop->found = true;
return 0;
}
int decode_string_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
struct mqtt_utf8 *str = prop->data;
if (unpack_utf8_str(buf, str) < 0) {
return -EBADMSG;
}
if (*remaining_len < sizeof(uint16_t) + str->size) {
return -EBADMSG;
}
*remaining_len -= sizeof(uint16_t) + str->size;
*prop->found = true;
return 0;
}
int decode_binary_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
struct mqtt_binstr *bin = prop->data;
if (unpack_binary_data(buf, bin) < 0) {
return -EBADMSG;
}
if (*remaining_len < sizeof(uint16_t) + bin->len) {
return -EBADMSG;
}
*remaining_len -= sizeof(uint16_t) + bin->len;
*prop->found = true;
return 0;
}
int decode_user_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
struct mqtt_utf8_pair *user_prop = prop->data;
struct mqtt_utf8_pair *chosen = NULL;
struct mqtt_utf8_pair temp = { 0 };
size_t prop_len;
if (unpack_utf8_str(buf, &temp.name) < 0) {
return -EBADMSG;
}
if (unpack_utf8_str(buf, &temp.value) < 0) {
return -EBADMSG;
}
prop_len = (2 * sizeof(uint16_t)) + temp.name.size + temp.value.size;
if (*remaining_len < prop_len) {
return -EBADMSG;
}
*remaining_len -= prop_len;
*prop->found = true;
for (int i = 0; i < CONFIG_MQTT_USER_PROPERTIES_MAX; i++) {
if (user_prop[i].name.utf8 == NULL) {
chosen = &user_prop[i];
break;
}
}
if (chosen == NULL) {
NET_DBG("Cannot parse all user properties, ignore excess");
} else {
memcpy(chosen, &temp, sizeof(struct mqtt_utf8_pair));
}
return 0;
}
int decode_sub_id_property(struct property_decoder *prop,
uint32_t *remaining_len,
struct buf_ctx *buf)
{
uint32_t *sub_id_array = prop->data;
uint32_t *chosen = NULL;
uint32_t value;
int bytes;
bytes = unpack_variable_int(buf, &value);
if (bytes < 0) {
return -EINVAL;
}
if (*remaining_len < bytes) {
return -EINVAL;
}
*remaining_len -= bytes;
*prop->found = true;
for (int i = 0; i < CONFIG_MQTT_SUBSCRIPTION_ID_PROPERTIES_MAX; i++) {
if (sub_id_array[i] == 0) {
chosen = &sub_id_array[i];
break;
}
}
if (chosen == NULL) {
NET_DBG("Cannot parse all subscription id properties, ignore excess");
} else {
*chosen = value;
}
return 0;
}
static int properties_decode(struct property_decoder *prop, uint8_t cnt,
struct buf_ctx *buf)
{
uint32_t properties_len;
int bytes;
int err;
bytes = unpack_variable_int(buf, &properties_len);
if (bytes < 0) {
return -EBADMSG;
}
bytes += (int)properties_len;
while (properties_len > 0) {
struct property_decoder *current_prop = NULL;
uint8_t type;
/* Decode property type */
err = unpack_uint8(buf, &type);
if (err < 0) {
return -EBADMSG;
}
properties_len--;
/* Search if the property is supported in the provided property
* array.
*/
for (int i = 0; i < cnt; i++) {
if (type == prop[i].type) {
current_prop = &prop[i];
}
}
if (current_prop == NULL) {
NET_DBG("Unsupported property %u", type);
return -EBADMSG;
}
/* Decode property value. */
switch (type) {
case MQTT_PROP_SESSION_EXPIRY_INTERVAL:
case MQTT_PROP_MAXIMUM_PACKET_SIZE:
case MQTT_PROP_MESSAGE_EXPIRY_INTERVAL:
err = decode_uint32_property(current_prop,
&properties_len, buf);
break;
case MQTT_PROP_RECEIVE_MAXIMUM:
case MQTT_PROP_TOPIC_ALIAS_MAXIMUM:
case MQTT_PROP_SERVER_KEEP_ALIVE:
case MQTT_PROP_TOPIC_ALIAS:
err = decode_uint16_property(current_prop,
&properties_len, buf);
break;
case MQTT_PROP_MAXIMUM_QOS:
case MQTT_PROP_RETAIN_AVAILABLE:
case MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE:
case MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE:
case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR:
err = decode_uint8_property(current_prop,
&properties_len, buf);
break;
case MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER:
case MQTT_PROP_REASON_STRING:
case MQTT_PROP_RESPONSE_INFORMATION:
case MQTT_PROP_SERVER_REFERENCE:
case MQTT_PROP_AUTHENTICATION_METHOD:
case MQTT_PROP_RESPONSE_TOPIC:
case MQTT_PROP_CONTENT_TYPE:
err = decode_string_property(current_prop,
&properties_len, buf);
break;
case MQTT_PROP_USER_PROPERTY:
err = decode_user_property(current_prop,
&properties_len, buf);
break;
case MQTT_PROP_AUTHENTICATION_DATA:
case MQTT_PROP_CORRELATION_DATA:
err = decode_binary_property(current_prop,
&properties_len, buf);
break;
case MQTT_PROP_SUBSCRIPTION_IDENTIFIER:
err = decode_sub_id_property(current_prop,
&properties_len, buf);
break;
default:
err = -ENOTSUP;
}
if (err < 0) {
return -EBADMSG;
}
}
return bytes;
}
static int connack_properties_decode(struct buf_ctx *buf,
struct mqtt_connack_param *param)
{
struct property_decoder prop[] = {
{
&param->prop.session_expiry_interval,
&param->prop.rx.has_session_expiry_interval,
MQTT_PROP_SESSION_EXPIRY_INTERVAL
},
{
&param->prop.receive_maximum,
&param->prop.rx.has_receive_maximum,
MQTT_PROP_RECEIVE_MAXIMUM
},
{
&param->prop.maximum_qos,
&param->prop.rx.has_maximum_qos,
MQTT_PROP_MAXIMUM_QOS
},
{
&param->prop.retain_available,
&param->prop.rx.has_retain_available,
MQTT_PROP_RETAIN_AVAILABLE
},
{
&param->prop.maximum_packet_size,
&param->prop.rx.has_maximum_packet_size,
MQTT_PROP_MAXIMUM_PACKET_SIZE
},
{
&param->prop.assigned_client_id,
&param->prop.rx.has_assigned_client_id,
MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER
},
{
&param->prop.topic_alias_maximum,
&param->prop.rx.has_topic_alias_maximum,
MQTT_PROP_TOPIC_ALIAS_MAXIMUM
},
{
&param->prop.reason_string,
&param->prop.rx.has_reason_string,
MQTT_PROP_REASON_STRING
},
{
&param->prop.user_prop,
&param->prop.rx.has_user_prop,
MQTT_PROP_USER_PROPERTY
},
{
&param->prop.wildcard_sub_available,
&param->prop.rx.has_wildcard_sub_available,
MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE
},
{
&param->prop.subscription_ids_available,
&param->prop.rx.has_subscription_ids_available,
MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE
},
{
&param->prop.shared_sub_available,
&param->prop.rx.has_shared_sub_available,
MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE
},
{
&param->prop.server_keep_alive,
&param->prop.rx.has_server_keep_alive,
MQTT_PROP_SERVER_KEEP_ALIVE
},
{
&param->prop.response_information,
&param->prop.rx.has_response_information,
MQTT_PROP_RESPONSE_INFORMATION
},
{
&param->prop.server_reference,
&param->prop.rx.has_server_reference,
MQTT_PROP_SERVER_REFERENCE
},
{
&param->prop.auth_method,
&param->prop.rx.has_auth_method,
MQTT_PROP_AUTHENTICATION_METHOD
},
{
&param->prop.auth_data,
&param->prop.rx.has_auth_data,
MQTT_PROP_AUTHENTICATION_DATA
}
};
return properties_decode(prop, ARRAY_SIZE(prop), buf);
}
#else
static int connack_properties_decode(struct buf_ctx *buf,
struct mqtt_connack_param *param)
{
ARG_UNUSED(param);
ARG_UNUSED(buf);
return -ENOTSUP;
}
#endif /* CONFIG_MQTT_VERSION_5_0 */
int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf,
struct mqtt_connack_param *param)
{
int err_code;
uint8_t flags, ret_code;
err_code = unpack_uint8(buf, &flags);
if (err_code != 0) {
return err_code;
}
err_code = unpack_uint8(buf, &ret_code);
if (err_code != 0) {
return err_code;
}
param->return_code = ret_code;
if (client->protocol_version == MQTT_VERSION_3_1_0) {
goto out;
}
param->session_present_flag = flags & MQTT_CONNACK_FLAG_SESSION_PRESENT;
NET_DBG("[CID %p]: session_present_flag: %d", client,
param->session_present_flag);
if (mqtt_is_version_5_0(client)) {
err_code = connack_properties_decode(buf, param);
if (err_code < 0) {
return err_code;
}
}
out:
return 0;
}
#if defined(CONFIG_MQTT_VERSION_5_0)
static int publish_properties_decode(struct buf_ctx *buf,
struct mqtt_publish_param *param)
{
struct property_decoder prop[] = {
{
&param->prop.payload_format_indicator,
&param->prop.rx.has_payload_format_indicator,
MQTT_PROP_PAYLOAD_FORMAT_INDICATOR
},
{
&param->prop.message_expiry_interval,
&param->prop.rx.has_message_expiry_interval,
MQTT_PROP_MESSAGE_EXPIRY_INTERVAL
},
{
&param->prop.topic_alias,
&param->prop.rx.has_topic_alias,
MQTT_PROP_TOPIC_ALIAS
},
{
&param->prop.response_topic,
&param->prop.rx.has_response_topic,
MQTT_PROP_RESPONSE_TOPIC
},
{
&param->prop.correlation_data,
&param->prop.rx.has_correlation_data,
MQTT_PROP_CORRELATION_DATA
},
{
&param->prop.user_prop,
&param->prop.rx.has_user_prop,
MQTT_PROP_USER_PROPERTY
},
{
&param->prop.subscription_identifier,
&param->prop.rx.has_subscription_identifier,
MQTT_PROP_SUBSCRIPTION_IDENTIFIER
},
{
&param->prop.content_type,
&param->prop.rx.has_content_type,
MQTT_PROP_CONTENT_TYPE
}
};
return properties_decode(prop, ARRAY_SIZE(prop), buf);
}
#else
static int publish_properties_decode(struct buf_ctx *buf,
struct mqtt_publish_param *param)
{
ARG_UNUSED(param);
ARG_UNUSED(buf);
return -ENOTSUP;
}
#endif /* CONFIG_MQTT_VERSION_5_0 */
int publish_decode(const struct mqtt_client *client, uint8_t flags,
uint32_t var_length, struct buf_ctx *buf,
struct mqtt_publish_param *param)
{
int err_code;
uint32_t var_header_length;
param->dup_flag = flags & MQTT_HEADER_DUP_MASK;
param->retain_flag = flags & MQTT_HEADER_RETAIN_MASK;
param->message.topic.qos = ((flags & MQTT_HEADER_QOS_MASK) >> 1);
err_code = unpack_utf8_str(buf, &param->message.topic.topic);
if (err_code != 0) {
return err_code;
}
var_header_length = param->message.topic.topic.size + sizeof(uint16_t);
if (param->message.topic.qos > MQTT_QOS_0_AT_MOST_ONCE) {
err_code = unpack_uint16(buf, &param->message_id);
if (err_code != 0) {
return err_code;
}
var_header_length += sizeof(uint16_t);
}
if (mqtt_is_version_5_0(client)) {
err_code = publish_properties_decode(buf, param);
if (err_code < 0) {
return err_code;
}
/* Add parsed properties length */
var_header_length += err_code;
}
if (var_length < var_header_length) {
NET_ERR("Corrupted PUBLISH message, header length (%u) larger "
"than total length (%u)", var_header_length,
var_length);
return -EINVAL;
}
param->message.payload.data = NULL;
param->message.payload.len = var_length - var_header_length;
return 0;
}
int publish_ack_decode(struct buf_ctx *buf, struct mqtt_puback_param *param)
{
return unpack_uint16(buf, &param->message_id);
}
int publish_receive_decode(struct buf_ctx *buf, struct mqtt_pubrec_param *param)
{
return unpack_uint16(buf, &param->message_id);
}
int publish_release_decode(struct buf_ctx *buf, struct mqtt_pubrel_param *param)
{
return unpack_uint16(buf, &param->message_id);
}
int publish_complete_decode(struct buf_ctx *buf,
struct mqtt_pubcomp_param *param)
{
return unpack_uint16(buf, &param->message_id);
}
int subscribe_ack_decode(struct buf_ctx *buf, struct mqtt_suback_param *param)
{
int err_code;
err_code = unpack_uint16(buf, &param->message_id);
if (err_code != 0) {
return err_code;
}
return unpack_raw_data(buf->end - buf->cur, buf, &param->return_codes);
}
int unsubscribe_ack_decode(struct buf_ctx *buf,
struct mqtt_unsuback_param *param)
{
return unpack_uint16(buf, &param->message_id);
}