/* * 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 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[] = { { ¶m->prop.session_expiry_interval, ¶m->prop.rx.has_session_expiry_interval, MQTT_PROP_SESSION_EXPIRY_INTERVAL }, { ¶m->prop.receive_maximum, ¶m->prop.rx.has_receive_maximum, MQTT_PROP_RECEIVE_MAXIMUM }, { ¶m->prop.maximum_qos, ¶m->prop.rx.has_maximum_qos, MQTT_PROP_MAXIMUM_QOS }, { ¶m->prop.retain_available, ¶m->prop.rx.has_retain_available, MQTT_PROP_RETAIN_AVAILABLE }, { ¶m->prop.maximum_packet_size, ¶m->prop.rx.has_maximum_packet_size, MQTT_PROP_MAXIMUM_PACKET_SIZE }, { ¶m->prop.assigned_client_id, ¶m->prop.rx.has_assigned_client_id, MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER }, { ¶m->prop.topic_alias_maximum, ¶m->prop.rx.has_topic_alias_maximum, MQTT_PROP_TOPIC_ALIAS_MAXIMUM }, { ¶m->prop.reason_string, ¶m->prop.rx.has_reason_string, MQTT_PROP_REASON_STRING }, { ¶m->prop.user_prop, ¶m->prop.rx.has_user_prop, MQTT_PROP_USER_PROPERTY }, { ¶m->prop.wildcard_sub_available, ¶m->prop.rx.has_wildcard_sub_available, MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE }, { ¶m->prop.subscription_ids_available, ¶m->prop.rx.has_subscription_ids_available, MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE }, { ¶m->prop.shared_sub_available, ¶m->prop.rx.has_shared_sub_available, MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE }, { ¶m->prop.server_keep_alive, ¶m->prop.rx.has_server_keep_alive, MQTT_PROP_SERVER_KEEP_ALIVE }, { ¶m->prop.response_information, ¶m->prop.rx.has_response_information, MQTT_PROP_RESPONSE_INFORMATION }, { ¶m->prop.server_reference, ¶m->prop.rx.has_server_reference, MQTT_PROP_SERVER_REFERENCE }, { ¶m->prop.auth_method, ¶m->prop.rx.has_auth_method, MQTT_PROP_AUTHENTICATION_METHOD }, { ¶m->prop.auth_data, ¶m->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[] = { { ¶m->prop.payload_format_indicator, ¶m->prop.rx.has_payload_format_indicator, MQTT_PROP_PAYLOAD_FORMAT_INDICATOR }, { ¶m->prop.message_expiry_interval, ¶m->prop.rx.has_message_expiry_interval, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL }, { ¶m->prop.topic_alias, ¶m->prop.rx.has_topic_alias, MQTT_PROP_TOPIC_ALIAS }, { ¶m->prop.response_topic, ¶m->prop.rx.has_response_topic, MQTT_PROP_RESPONSE_TOPIC }, { ¶m->prop.correlation_data, ¶m->prop.rx.has_correlation_data, MQTT_PROP_CORRELATION_DATA }, { ¶m->prop.user_prop, ¶m->prop.rx.has_user_prop, MQTT_PROP_USER_PROPERTY }, { ¶m->prop.subscription_identifier, ¶m->prop.rx.has_subscription_identifier, MQTT_PROP_SUBSCRIPTION_IDENTIFIER }, { ¶m->prop.content_type, ¶m->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, ¶m->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, ¶m->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, ¶m->message_id); } int publish_receive_decode(struct buf_ctx *buf, struct mqtt_pubrec_param *param) { return unpack_uint16(buf, ¶m->message_id); } int publish_release_decode(struct buf_ctx *buf, struct mqtt_pubrel_param *param) { return unpack_uint16(buf, ¶m->message_id); } int publish_complete_decode(struct buf_ctx *buf, struct mqtt_pubcomp_param *param) { return unpack_uint16(buf, ¶m->message_id); } int subscribe_ack_decode(struct buf_ctx *buf, struct mqtt_suback_param *param) { int err_code; err_code = unpack_uint16(buf, ¶m->message_id); if (err_code != 0) { return err_code; } return unpack_raw_data(buf->end - buf->cur, buf, ¶m->return_codes); } int unsubscribe_ack_decode(struct buf_ctx *buf, struct mqtt_unsuback_param *param) { return unpack_uint16(buf, ¶m->message_id); }