/* * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ /** @file mqtt_encoder.c * * @brief Encoding functions needed to create packet to be sent to the broker. */ #include LOG_MODULE_REGISTER(net_mqtt_enc, CONFIG_MQTT_LOG_LEVEL); #include "mqtt_internal.h" #include "mqtt_os.h" static const struct mqtt_utf8 mqtt_3_1_0_proto_desc = MQTT_UTF8_LITERAL("MQIsdp"); static const struct mqtt_utf8 mqtt_proto_desc = MQTT_UTF8_LITERAL("MQTT"); /** Never changing ping request, needed for Keep Alive. */ static const uint8_t ping_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { MQTT_PKT_TYPE_PINGREQ, 0x00 }; /** Never changing disconnect request. */ static const uint8_t empty_disc_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { MQTT_PKT_TYPE_DISCONNECT, 0x00 }; /** * @brief Packs unsigned 8 bit value to the buffer at the offset requested. * * @param[in] val Value to be packed. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * * @retval 0 if procedure is successful. * @retval -ENOMEM if there is no place in the buffer to store the value. */ static int pack_uint8(uint8_t val, struct buf_ctx *buf) { uint8_t *cur = buf->cur; uint8_t *end = buf->end; if ((end - cur) < sizeof(uint8_t)) { return -ENOMEM; } NET_DBG(">> val:%02x cur:%p, end:%p", val, (void *)cur, (void *)end); /* Pack value. */ cur[0] = val; buf->cur = (cur + sizeof(uint8_t)); return 0; } /** * @brief Packs unsigned 16 bit value to the buffer at the offset requested. * * @param[in] val Value to be packed. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * * @retval 0 if the procedure is successful. * @retval -ENOMEM if there is no place in the buffer to store the value. */ static int pack_uint16(uint16_t val, struct buf_ctx *buf) { uint8_t *cur = buf->cur; uint8_t *end = buf->end; if ((end - cur) < sizeof(uint16_t)) { return -ENOMEM; } NET_DBG(">> val:%04x cur:%p, end:%p", val, (void *)cur, (void *)end); /* Pack value. */ sys_put_be16(val, cur); buf->cur = (cur + sizeof(uint16_t)); return 0; } /** * @brief Packs utf8 string to the buffer at the offset requested. * * @param[in] str UTF-8 string and its length to be packed. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * * @retval 0 if the procedure is successful. * @retval -ENOMEM if there is no place in the buffer to store the string. */ static int pack_utf8_str(const struct mqtt_utf8 *str, struct buf_ctx *buf) { if ((buf->end - buf->cur) < GET_UT8STR_BUFFER_SIZE(str)) { return -ENOMEM; } NET_DBG(">> str_size:%08x cur:%p, end:%p", (uint32_t)GET_UT8STR_BUFFER_SIZE(str), (void *)buf->cur, (void *)buf->end); /* Pack length followed by string. */ (void)pack_uint16(str->size, buf); memcpy(buf->cur, str->utf8, str->size); buf->cur += str->size; return 0; } /** * @brief Computes and encodes variable length integer. * * @note The variable length integer is based on algorithm below: * * @code * do * encodedByte = X MOD 128 * X = X DIV 128 * // if there are more data to encode, set the top bit of this byte * if ( X > 0 ) * encodedByte = encodedByte OR 128 * endif * 'output' encodedByte * while ( X > 0 ) * @endcode * * @param[in] value Integer value to encode. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. May be NULL (in this case function will * only calculate number of bytes needed). * * @return Number of bytes needed to encode integer or a negative error code. */ static int pack_variable_int(uint32_t value, struct buf_ctx *buf) { int encoded_bytes = 0U; NET_DBG(">> value:0x%08x cur:%p, end:%p", value, (buf == NULL) ? 0 : (void *)buf->cur, (buf == NULL) ? 0 : (void *)buf->end); do { encoded_bytes++; if (buf != NULL) { if (buf->cur >= buf->end) { return -ENOMEM; } *(buf->cur) = value & MQTT_LENGTH_VALUE_MASK; } value >>= MQTT_LENGTH_SHIFT; if (buf != NULL) { if (value > 0) { *(buf->cur) |= MQTT_LENGTH_CONTINUATION_BIT; } buf->cur++; } } while (value > 0); return encoded_bytes; } /** * @brief Encodes fixed header for the MQTT message and provides pointer to * start of the header. * * @param[in] message_type Message type containing packet type and the flags. * Use @ref MQTT_MESSAGES_OPTIONS to construct the * message_type. * @param[in] start Pointer to the start of the variable header. * @param[inout] buf Buffer context used to encode the frame. * The 5 bytes before the start of the message are assumed * by the routine to be available to pack the fixed header. * However, since the fixed header length is variable * length, the pointer to the start of the MQTT message * along with encoded fixed header is supplied as output * parameter if the procedure was successful. * As output, the pointers will point to beginning and the end * of the frame. * * @retval 0 if the procedure is successful. * @retval -EMSGSIZE if the message is too big for MQTT. */ static uint32_t mqtt_encode_fixed_header(uint8_t message_type, uint8_t *start, struct buf_ctx *buf) { uint32_t length = buf->cur - start; uint8_t fixed_header_length; if (length > MQTT_MAX_PAYLOAD_SIZE) { return -EMSGSIZE; } NET_DBG("<< msg type:0x%02x length:0x%08x", message_type, length); fixed_header_length = pack_variable_int(length, NULL); fixed_header_length += sizeof(uint8_t); NET_DBG("Fixed header length = %02x", fixed_header_length); /* Set the pointer at the start of the frame before encoding. */ buf->cur = start - fixed_header_length; (void)pack_uint8(message_type, buf); (void)pack_variable_int(length, buf); /* Set the cur pointer back at the start of the frame, * and end pointer to the end of the frame. */ buf->cur = buf->cur - fixed_header_length; buf->end = buf->cur + length + fixed_header_length; return 0; } /** * @brief Encodes a string of a zero length. * * @param[in] buffer_len Total size of the buffer on which string will be * encoded. This shall not be zero. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * * @retval 0 if the procedure is successful. * @retval -ENOMEM if there is no place in the buffer to store the binary * string. */ static int zero_len_str_encode(struct buf_ctx *buf) { return pack_uint16(0x0000, buf); } #if defined(CONFIG_MQTT_VERSION_5_0) /** * @brief Packs unsigned 32 bit value to the buffer at the offset requested. * * @param[in] val Value to be packed. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * * @retval 0 if the procedure is successful. * @retval -ENOMEM if there is no place in the buffer to store the value. */ static int pack_uint32(uint32_t val, struct buf_ctx *buf) { uint8_t *cur = buf->cur; uint8_t *end = buf->end; if ((end - cur) < sizeof(uint32_t)) { return -ENOMEM; } /* Pack value. */ sys_put_be32(val, cur); buf->cur = (cur + sizeof(uint32_t)); return 0; } /** * @brief Packs binary data to the buffer at the offset requested. * * @param[in] bin Binary data and its length to be packed. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * * @retval 0 if the procedure is successful. * @retval -ENOMEM if there is no place in the buffer to store the data. */ static int pack_bin_data(const struct mqtt_binstr *bin, struct buf_ctx *buf) { if ((buf->end - buf->cur) < GET_BINSTR_BUFFER_SIZE(bin)) { return -ENOMEM; } /* Pack length followed by binary data. */ (void)pack_uint16(bin->len, buf); memcpy(buf->cur, bin->data, bin->len); buf->cur += bin->len; return 0; } static uint8_t get_uint8_property_default(uint8_t prop) { if (prop == MQTT_PROP_REQUEST_PROBLEM_INFORMATION) { return 1; } return 0; } static size_t uint8_property_length(uint8_t prop, uint8_t value) { uint8_t prop_default = get_uint8_property_default(prop); if (value == prop_default) { return 0; } return sizeof(uint8_t) + sizeof(uint8_t); } static int encode_uint8_property(uint8_t prop, uint8_t value, struct buf_ctx *buf) { uint8_t prop_default = get_uint8_property_default(prop); int err; if (value == prop_default) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } return pack_uint8(value, buf); } static size_t uint16_property_length(uint16_t value) { if (value == 0) { return 0; } return sizeof(uint8_t) + sizeof(uint16_t); } static int encode_uint16_property(uint8_t prop, uint16_t value, struct buf_ctx *buf) { int err; if (value == 0) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } return pack_uint16(value, buf); } static size_t uint32_property_length(uint32_t value) { if (value == 0) { return 0; } return sizeof(uint8_t) + sizeof(uint32_t); } static int encode_uint32_property(uint8_t prop, uint32_t value, struct buf_ctx *buf) { int err; if (value == 0) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } return pack_uint32(value, buf); } static size_t var_int_property_length(uint32_t value) { if (value == 0) { return 0; } return sizeof(uint8_t) + (size_t)pack_variable_int(value, NULL); } static int encode_var_int_property(uint8_t prop, uint32_t value, struct buf_ctx *buf) { int err; if (value == 0) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } return pack_variable_int(value, buf); } static size_t string_property_length(const struct mqtt_utf8 *str) { if (str->size == 0) { return 0; } return sizeof(uint8_t) + GET_UT8STR_BUFFER_SIZE(str); } static int encode_string_property(uint8_t prop, const struct mqtt_utf8 *str, struct buf_ctx *buf) { int err; if (str->size == 0) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } return pack_utf8_str(str, buf); } static size_t string_pair_property_length(const struct mqtt_utf8 *name, const struct mqtt_utf8 *value) { if ((name->size == 0) || (value->size == 0)) { return 0; } return sizeof(uint8_t) + GET_UT8STR_BUFFER_SIZE(name) + GET_UT8STR_BUFFER_SIZE(value); } static int encode_string_pair_property(uint8_t prop, const struct mqtt_utf8 *name, const struct mqtt_utf8 *value, struct buf_ctx *buf) { int err; if ((name->size == 0) || (value->size == 0)) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } err = pack_utf8_str(name, buf); if (err < 0) { return err; } return pack_utf8_str(value, buf); } static size_t binary_property_length(const struct mqtt_binstr *bin) { if (bin->len == 0) { return 0; } return sizeof(uint8_t) + GET_BINSTR_BUFFER_SIZE(bin); } static int encode_binary_property(uint8_t prop, const struct mqtt_binstr *bin, struct buf_ctx *buf) { int err; if (bin->len == 0) { return 0; } err = pack_uint8(prop, buf); if (err < 0) { return err; } return pack_bin_data(bin, buf); } static size_t user_properties_length(const struct mqtt_utf8_pair *user_props) { size_t total_len = 0; size_t len; for (int i = 0; i < CONFIG_MQTT_USER_PROPERTIES_MAX; i++) { len = string_pair_property_length(&user_props[i].name, &user_props[i].value); if (len == 0) { break; } total_len += len; } return total_len; } static int encode_user_properties(const struct mqtt_utf8_pair *user_props, struct buf_ctx *buf) { size_t len; int err; for (int i = 0; i < CONFIG_MQTT_USER_PROPERTIES_MAX; i++) { len = string_pair_property_length(&user_props[i].name, &user_props[i].value); if (len == 0) { break; } err = encode_string_pair_property(MQTT_PROP_USER_PROPERTY, &user_props[i].name, &user_props[i].value, buf); if (err < 0) { return err; } } return 0; } static uint32_t connect_properties_length(const struct mqtt_client *client) { return uint32_property_length(client->prop.session_expiry_interval) + uint16_property_length(client->prop.receive_maximum) + uint32_property_length(client->prop.maximum_packet_size) + uint16_property_length(CONFIG_MQTT_TOPIC_ALIAS_MAX) + uint8_property_length(MQTT_PROP_REQUEST_RESPONSE_INFORMATION, client->prop.request_response_info ? 1U : 0U) + uint8_property_length(MQTT_PROP_REQUEST_PROBLEM_INFORMATION, client->prop.request_problem_info ? 1U : 0U) + user_properties_length(client->prop.user_prop) + string_property_length(&client->prop.auth_method) + binary_property_length(&client->prop.auth_data); } static int connect_properties_encode(const struct mqtt_client *client, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = connect_properties_length(client); err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_uint32_property(MQTT_PROP_SESSION_EXPIRY_INTERVAL, client->prop.session_expiry_interval, buf); if (err < 0) { return err; } err = encode_uint16_property(MQTT_PROP_RECEIVE_MAXIMUM, client->prop.receive_maximum, buf); if (err < 0) { return err; } err = encode_uint32_property(MQTT_PROP_MAXIMUM_PACKET_SIZE, client->prop.maximum_packet_size, buf); if (err < 0) { return err; } err = encode_uint16_property(MQTT_PROP_TOPIC_ALIAS_MAXIMUM, CONFIG_MQTT_TOPIC_ALIAS_MAX, buf); if (err < 0) { return err; } err = encode_uint8_property(MQTT_PROP_REQUEST_RESPONSE_INFORMATION, client->prop.request_response_info ? 1U : 0U, buf); if (err < 0) { return err; } err = encode_uint8_property(MQTT_PROP_REQUEST_PROBLEM_INFORMATION, client->prop.request_problem_info ? 1U : 0U, buf); if (err < 0) { return err; } err = encode_user_properties(client->prop.user_prop, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_AUTHENTICATION_METHOD, &client->prop.auth_method, buf); if (err < 0) { return err; } err = encode_binary_property(MQTT_PROP_AUTHENTICATION_DATA, &client->prop.auth_data, buf); if (err < 0) { return err; } return 0; } static uint32_t will_properties_length(const struct mqtt_client *client) { return uint32_property_length(client->will_prop.will_delay_interval) + uint8_property_length(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, client->will_prop.payload_format_indicator) + uint32_property_length(client->will_prop.message_expiry_interval) + string_property_length(&client->will_prop.content_type) + string_property_length(&client->will_prop.response_topic) + binary_property_length(&client->will_prop.correlation_data) + user_properties_length(client->will_prop.user_prop); } static int will_properties_encode(const struct mqtt_client *client, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = will_properties_length(client); err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_uint32_property(MQTT_PROP_WILL_DELAY_INTERVAL, client->will_prop.will_delay_interval, buf); if (err < 0) { return err; } err = encode_uint8_property(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, client->will_prop.payload_format_indicator, buf); if (err < 0) { return err; } err = encode_uint32_property(MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, client->will_prop.message_expiry_interval, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_CONTENT_TYPE, &client->will_prop.content_type, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_RESPONSE_TOPIC, &client->will_prop.response_topic, buf); if (err < 0) { return err; } err = encode_binary_property(MQTT_PROP_CORRELATION_DATA, &client->will_prop.correlation_data, buf); if (err < 0) { return err; } err = encode_user_properties(client->will_prop.user_prop, buf); if (err < 0) { return err; } return 0; } #else static int connect_properties_encode(const struct mqtt_client *client, struct buf_ctx *buf) { ARG_UNUSED(client); ARG_UNUSED(buf); return -ENOTSUP; } static int will_properties_encode(const struct mqtt_client *client, struct buf_ctx *buf) { ARG_UNUSED(client); ARG_UNUSED(buf); return -ENOTSUP; } #endif /* CONFIG_MQTT_VERSION_5_0 */ int connect_request_encode(const struct mqtt_client *client, struct buf_ctx *buf) { uint8_t connect_flags = client->clean_session << 1; const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_CONNECT, 0, 0, 0); const struct mqtt_utf8 *proto_desc; uint8_t *connect_flags_pos; int err_code; uint8_t *start; if (client->protocol_version == MQTT_VERSION_3_1_0) { proto_desc = &mqtt_3_1_0_proto_desc; } else { /* MQTT 3.1.1 and newer share the same protocol prefix. */ proto_desc = &mqtt_proto_desc; } /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; NET_HEXDUMP_DBG(proto_desc->utf8, proto_desc->size, "Encoding Protocol Description."); err_code = pack_utf8_str(proto_desc, buf); if (err_code != 0) { return err_code; } NET_DBG("Encoding Protocol Version %02x.", client->protocol_version); err_code = pack_uint8(client->protocol_version, buf); if (err_code != 0) { return err_code; } /* Remember position of connect flag and leave one byte for it to * be packed once we determine its value. */ connect_flags_pos = buf->cur; err_code = pack_uint8(0, buf); if (err_code != 0) { return err_code; } NET_DBG("Encoding Keep Alive Time %04x.", client->keepalive); err_code = pack_uint16(client->keepalive, buf); if (err_code != 0) { return err_code; } /* Properties (MQTT 5.0 only) */ if (mqtt_is_version_5_0(client)) { err_code = connect_properties_encode(client, buf); if (err_code != 0) { return err_code; } } NET_HEXDUMP_DBG(client->client_id.utf8, client->client_id.size, "Encoding Client Id."); err_code = pack_utf8_str(&client->client_id, buf); if (err_code != 0) { return err_code; } /* Pack will topic and QoS */ if (client->will_topic != NULL) { connect_flags |= MQTT_CONNECT_FLAG_WILL_TOPIC; /* QoS is always 1 as of now. */ connect_flags |= ((client->will_topic->qos & 0x03) << 3); connect_flags |= client->will_retain << 5; /* Will properties (MQTT 5.0 only) */ if (mqtt_is_version_5_0(client)) { err_code = will_properties_encode(client, buf); if (err_code != 0) { return err_code; } } NET_HEXDUMP_DBG(client->will_topic->topic.utf8, client->will_topic->topic.size, "Encoding Will Topic."); err_code = pack_utf8_str(&client->will_topic->topic, buf); if (err_code != 0) { return err_code; } if (client->will_message != NULL) { NET_HEXDUMP_DBG(client->will_message->utf8, client->will_message->size, "Encoding Will Message."); err_code = pack_utf8_str(client->will_message, buf); if (err_code != 0) { return err_code; } } else { NET_DBG("Encoding Zero Length Will Message."); err_code = zero_len_str_encode(buf); if (err_code != 0) { return err_code; } } } /* Pack Username if any. */ if (client->user_name != NULL) { connect_flags |= MQTT_CONNECT_FLAG_USERNAME; NET_HEXDUMP_DBG(client->user_name->utf8, client->user_name->size, "Encoding Username."); err_code = pack_utf8_str(client->user_name, buf); if (err_code != 0) { return err_code; } } /* Pack Password if any. */ if (client->password != NULL) { connect_flags |= MQTT_CONNECT_FLAG_PASSWORD; NET_HEXDUMP_DBG(client->password->utf8, client->password->size, "Encoding Password."); err_code = pack_utf8_str(client->password, buf); if (err_code != 0) { return err_code; } } /* Write the flags the connect flags. */ *connect_flags_pos = connect_flags; return mqtt_encode_fixed_header(message_type, start, buf); } #if defined(CONFIG_MQTT_VERSION_5_0) static uint32_t publish_properties_length(const struct mqtt_publish_param *param) { return uint8_property_length(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, param->prop.payload_format_indicator) + uint32_property_length(param->prop.message_expiry_interval) + uint16_property_length(param->prop.topic_alias) + string_property_length(¶m->prop.response_topic) + binary_property_length(¶m->prop.correlation_data) + user_properties_length(param->prop.user_prop) + /* Client does not include Subscription Identifier in any case. */ string_property_length(¶m->prop.content_type); } static int publish_properties_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = publish_properties_length(param); err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_uint8_property(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, param->prop.payload_format_indicator, buf); if (err < 0) { return err; } err = encode_uint32_property(MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, param->prop.message_expiry_interval, buf); if (err < 0) { return err; } err = encode_uint16_property(MQTT_PROP_TOPIC_ALIAS, param->prop.topic_alias, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_RESPONSE_TOPIC, ¶m->prop.response_topic, buf); if (err < 0) { return err; } err = encode_binary_property(MQTT_PROP_CORRELATION_DATA, ¶m->prop.correlation_data, buf); if (err < 0) { return err; } err = encode_user_properties(param->prop.user_prop, buf); if (err < 0) { return err; } /* Client does not include Subscription Identifier in any case. */ err = encode_string_property(MQTT_PROP_CONTENT_TYPE, ¶m->prop.content_type, buf); if (err < 0) { return err; } return 0; } #else static int publish_properties_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf) { ARG_UNUSED(param); ARG_UNUSED(buf); return -ENOTSUP; } #endif /* CONFIG_MQTT_VERSION_5_0 */ int publish_encode(const struct mqtt_client *client, const struct mqtt_publish_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS( MQTT_PKT_TYPE_PUBLISH, param->dup_flag, param->message.topic.qos, param->retain_flag); int err_code; uint8_t *start; /* Message id zero is not permitted by spec. */ if ((param->message.topic.qos) && (param->message_id == 0U)) { return -EINVAL; } /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; err_code = pack_utf8_str(¶m->message.topic.topic, buf); if (err_code != 0) { return err_code; } if (param->message.topic.qos) { err_code = pack_uint16(param->message_id, buf); if (err_code != 0) { return err_code; } } if (mqtt_is_version_5_0(client)) { err_code = publish_properties_encode(param, buf); if (err_code != 0) { return err_code; } } /* Do not copy payload. We move the buffer pointer to ensure that * message length in fixed header is encoded correctly. */ buf->cur += param->message.payload.len; err_code = mqtt_encode_fixed_header(message_type, start, buf); if (err_code != 0) { return err_code; } buf->end -= param->message.payload.len; return 0; } #if defined(CONFIG_MQTT_VERSION_5_0) static uint32_t common_ack_properties_length( const struct mqtt_common_ack_properties *prop) { return user_properties_length(prop->user_prop) + string_property_length(&prop->reason_string); } static int common_ack_properties_encode( const struct mqtt_common_ack_properties *prop, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = common_ack_properties_length(prop); /* Properties length can be omitted if equal to 0. */ if (properties_len == 0) { return 0; } err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_user_properties(prop->user_prop, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_REASON_STRING, &prop->reason_string, buf); if (err < 0) { return err; } return 0; } #else /* CONFIG_MQTT_VERSION_5_0 */ static uint32_t common_ack_properties_length( const struct mqtt_common_ack_properties *prop) { return 0; } static int common_ack_properties_encode( const struct mqtt_common_ack_properties *prop, struct buf_ctx *buf) { ARG_UNUSED(prop); ARG_UNUSED(buf); return -ENOTSUP; } #endif /* CONFIG_MQTT_VERSION_5_0 */ static int common_ack_encode( uint8_t message_type, uint16_t message_id, uint8_t reason_code, const struct mqtt_common_ack_properties *prop, struct buf_ctx *buf) { int err_code; uint8_t *start; /* Message id zero is not permitted by spec. */ if (message_id == 0U) { return -EINVAL; } /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; err_code = pack_uint16(message_id, buf); if (err_code != 0) { return err_code; } /* For MQTT < 5.0 properties are NULL. */ if (prop != NULL) { /* The Reason Code and Property Length can be omitted if the * Reason Code is 0x00 (Success) and there are no Properties. */ if (common_ack_properties_length(prop) == 0 && reason_code == 0) { goto out; } err_code = pack_uint8(reason_code, buf); if (err_code != 0) { return err_code; } err_code = common_ack_properties_encode(prop, buf); if (err_code != 0) { return err_code; } } out: return mqtt_encode_fixed_header(message_type, start, buf); } int publish_ack_encode(const struct mqtt_client *client, const struct mqtt_puback_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBACK, 0, 0, 0); const struct mqtt_common_ack_properties *prop = NULL; uint8_t reason_code = 0; #if defined(CONFIG_MQTT_VERSION_5_0) if (mqtt_is_version_5_0(client)) { prop = ¶m->prop; reason_code = param->reason_code; } #endif return common_ack_encode(message_type, param->message_id, reason_code, prop, buf); } int publish_receive_encode(const struct mqtt_client *client, const struct mqtt_pubrec_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREC, 0, 0, 0); const struct mqtt_common_ack_properties *prop = NULL; uint8_t reason_code = 0; #if defined(CONFIG_MQTT_VERSION_5_0) if (mqtt_is_version_5_0(client)) { prop = ¶m->prop; reason_code = param->reason_code; } #endif return common_ack_encode(message_type, param->message_id, reason_code, prop, buf); } int publish_release_encode(const struct mqtt_client *client, const struct mqtt_pubrel_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREL, 0, 1, 0); const struct mqtt_common_ack_properties *prop = NULL; uint8_t reason_code = 0; #if defined(CONFIG_MQTT_VERSION_5_0) if (mqtt_is_version_5_0(client)) { prop = ¶m->prop; reason_code = param->reason_code; } #endif return common_ack_encode(message_type, param->message_id, reason_code, prop, buf); } int publish_complete_encode(const struct mqtt_client *client, const struct mqtt_pubcomp_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBCOMP, 0, 0, 0); const struct mqtt_common_ack_properties *prop = NULL; uint8_t reason_code = 0; #if defined(CONFIG_MQTT_VERSION_5_0) if (mqtt_is_version_5_0(client)) { prop = ¶m->prop; reason_code = param->reason_code; } #endif return common_ack_encode(message_type, param->message_id, reason_code, prop, buf); } static int empty_disconnect_encode(struct buf_ctx *buf) { uint8_t *cur = buf->cur; uint8_t *end = buf->end; if ((end - cur) < sizeof(empty_disc_packet)) { return -ENOMEM; } memcpy(cur, empty_disc_packet, sizeof(empty_disc_packet)); buf->end = (cur + sizeof(empty_disc_packet)); return 0; } #if defined(CONFIG_MQTT_VERSION_5_0) static uint32_t disconnect_properties_length(const struct mqtt_disconnect_param *param) { return uint32_property_length(param->prop.session_expiry_interval) + string_property_length(¶m->prop.reason_string) + user_properties_length(param->prop.user_prop) + string_property_length(¶m->prop.server_reference); } static int disconnect_properties_encode(const struct mqtt_disconnect_param *param, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = disconnect_properties_length(param); /* Disconnect properties length can be omitted if equal to 0. */ if (properties_len == 0) { return 0; } err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_uint32_property(MQTT_PROP_SESSION_EXPIRY_INTERVAL, param->prop.session_expiry_interval, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_REASON_STRING, ¶m->prop.reason_string, buf); if (err < 0) { return err; } err = encode_user_properties(param->prop.user_prop, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_SERVER_REFERENCE, ¶m->prop.server_reference, buf); if (err < 0) { return err; } return 0; } static int disconnect_5_0_encode(const struct mqtt_disconnect_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_DISCONNECT, 0, 0, 0); uint8_t *start; int err; /* The Reason Code and Property Length can be omitted if the Reason Code * is 0x00 (Normal disconnecton) and there are no Properties. */ if ((param->reason_code == MQTT_DISCONNECT_NORMAL) && (disconnect_properties_length(param) == 0U)) { return empty_disconnect_encode(buf); } /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; err = pack_uint8(param->reason_code, buf); if (err < 0) { return err; } err = disconnect_properties_encode(param, buf); if (err != 0) { return err; } err = mqtt_encode_fixed_header(message_type, start, buf); if (err != 0) { return err; } return 0; } #else static int disconnect_5_0_encode(const struct mqtt_disconnect_param *param, struct buf_ctx *buf) { ARG_UNUSED(param); ARG_UNUSED(buf); return -ENOTSUP; } #endif /* CONFIG_MQTT_VERSION_5_0 */ int disconnect_encode(const struct mqtt_client *client, const struct mqtt_disconnect_param *param, struct buf_ctx *buf) { if (!mqtt_is_version_5_0(client) || param == NULL) { return empty_disconnect_encode(buf); } return disconnect_5_0_encode(param, buf); } #if defined(CONFIG_MQTT_VERSION_5_0) static uint32_t subscribe_properties_length( const struct mqtt_subscription_list *param) { return var_int_property_length(param->prop.subscription_identifier) + user_properties_length(param->prop.user_prop); } static int subscribe_properties_encode(const struct mqtt_subscription_list *param, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = subscribe_properties_length(param); err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_var_int_property(MQTT_PROP_SUBSCRIPTION_IDENTIFIER, param->prop.subscription_identifier, buf); if (err < 0) { return err; } err = encode_user_properties(param->prop.user_prop, buf); if (err < 0) { return err; } return 0; } #else static int subscribe_properties_encode(const struct mqtt_subscription_list *param, struct buf_ctx *buf) { ARG_UNUSED(param); ARG_UNUSED(buf); return -ENOTSUP; } #endif /* CONFIG_MQTT_VERSION_5_0 */ int subscribe_encode(const struct mqtt_client *client, const struct mqtt_subscription_list *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS( MQTT_PKT_TYPE_SUBSCRIBE, 0, 1, 0); int err_code, i; uint8_t *start; /* Message id zero is not permitted by spec. */ if (param->message_id == 0U) { return -EINVAL; } /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; err_code = pack_uint16(param->message_id, buf); if (err_code != 0) { return err_code; } if (mqtt_is_version_5_0(client)) { err_code = subscribe_properties_encode(param, buf); if (err_code != 0) { return err_code; } } for (i = 0; i < param->list_count; i++) { err_code = pack_utf8_str(¶m->list[i].topic, buf); if (err_code != 0) { return err_code; } err_code = pack_uint8(param->list[i].qos, buf); if (err_code != 0) { return err_code; } } return mqtt_encode_fixed_header(message_type, start, buf); } #if defined(CONFIG_MQTT_VERSION_5_0) static uint32_t unsubscribe_properties_length( const struct mqtt_subscription_list *param) { return user_properties_length(param->prop.user_prop); } static int unsubscribe_properties_encode( const struct mqtt_subscription_list *param, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = unsubscribe_properties_length(param); err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_user_properties(param->prop.user_prop, buf); if (err < 0) { return err; } return 0; } #else static int unsubscribe_properties_encode( const struct mqtt_subscription_list *param, struct buf_ctx *buf) { ARG_UNUSED(param); ARG_UNUSED(buf); return -ENOTSUP; } #endif /* CONFIG_MQTT_VERSION_5_0 */ int unsubscribe_encode(const struct mqtt_client *client, const struct mqtt_subscription_list *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS( MQTT_PKT_TYPE_UNSUBSCRIBE, 0, MQTT_QOS_1_AT_LEAST_ONCE, 0); int err_code, i; uint8_t *start; /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; err_code = pack_uint16(param->message_id, buf); if (err_code != 0) { return err_code; } if (mqtt_is_version_5_0(client)) { err_code = unsubscribe_properties_encode(param, buf); if (err_code != 0) { return err_code; } } for (i = 0; i < param->list_count; i++) { err_code = pack_utf8_str(¶m->list[i].topic, buf); if (err_code != 0) { return err_code; } } return mqtt_encode_fixed_header(message_type, start, buf); } int ping_request_encode(struct buf_ctx *buf) { uint8_t *cur = buf->cur; uint8_t *end = buf->end; if ((end - cur) < sizeof(ping_packet)) { return -ENOMEM; } memcpy(cur, ping_packet, sizeof(ping_packet)); buf->end = (cur + sizeof(ping_packet)); return 0; } #if defined(CONFIG_MQTT_VERSION_5_0) static uint32_t auth_properties_length(const struct mqtt_auth_param *param) { return string_property_length(¶m->prop.auth_method) + binary_property_length(¶m->prop.auth_data) + string_property_length(¶m->prop.reason_string) + user_properties_length(param->prop.user_prop); } static int auth_properties_encode(const struct mqtt_auth_param *param, struct buf_ctx *buf) { uint32_t properties_len; int err; /* Precalculate total properties length */ properties_len = auth_properties_length(param); if (properties_len == 0) { return 0; } err = pack_variable_int(properties_len, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_AUTHENTICATION_METHOD, ¶m->prop.auth_method, buf); if (err < 0) { return err; } err = encode_binary_property(MQTT_PROP_AUTHENTICATION_DATA, ¶m->prop.auth_data, buf); if (err < 0) { return err; } err = encode_string_property(MQTT_PROP_REASON_STRING, ¶m->prop.reason_string, buf); if (err < 0) { return err; } err = encode_user_properties(param->prop.user_prop, buf); if (err < 0) { return err; } return 0; } static int empty_auth_encode(struct buf_ctx *buf) { const uint8_t empty_auth_packet[] = { MQTT_PKT_TYPE_AUTH, 0x00 }; uint8_t *cur = buf->cur; uint8_t *end = buf->end; if ((end - cur) < sizeof(empty_auth_packet)) { return -ENOMEM; } memcpy(cur, empty_auth_packet, sizeof(empty_auth_packet)); buf->end = (cur + sizeof(empty_auth_packet)); return 0; } int auth_encode(const struct mqtt_auth_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_AUTH, 0, 0, 0); uint8_t *start; int err; /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; if ((param->reason_code == MQTT_AUTH_SUCCESS) && (auth_properties_length(param) == 0U)) { return empty_auth_encode(buf); } err = pack_uint8(param->reason_code, buf); if (err < 0) { return err; } err = auth_properties_encode(param, buf); if (err != 0) { return err; } err = mqtt_encode_fixed_header(message_type, start, buf); if (err != 0) { return err; } return 0; } #endif /* CONFIG_MQTT_VERSION_5_0 */