1 /*
2  * Copyright (c) 2018 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /** @file mqtt_encoder.c
8  *
9  * @brief Encoding functions needed to create packet to be sent to the broker.
10  */
11 
12 #include <zephyr/logging/log.h>
13 LOG_MODULE_REGISTER(net_mqtt_enc, CONFIG_MQTT_LOG_LEVEL);
14 
15 #include "mqtt_internal.h"
16 #include "mqtt_os.h"
17 
18 static const struct mqtt_utf8 mqtt_3_1_0_proto_desc =
19 	MQTT_UTF8_LITERAL("MQIsdp");
20 
21 static const struct mqtt_utf8 mqtt_3_1_1_proto_desc =
22 	MQTT_UTF8_LITERAL("MQTT");
23 
24 /** Never changing ping request, needed for Keep Alive. */
25 static const uint8_t ping_packet[MQTT_FIXED_HEADER_MIN_SIZE] = {
26 	MQTT_PKT_TYPE_PINGREQ,
27 	0x00
28 };
29 
30 /** Never changing disconnect request. */
31 static const uint8_t disc_packet[MQTT_FIXED_HEADER_MIN_SIZE] = {
32 	MQTT_PKT_TYPE_DISCONNECT,
33 	0x00
34 };
35 
36 /**
37  * @brief Packs unsigned 8 bit value to the buffer at the offset requested.
38  *
39  * @param[in] val Value to be packed.
40  * @param[inout] buf A pointer to the buf_ctx structure containing current
41  *                   buffer position.
42  *
43  * @retval 0 if procedure is successful.
44  * @retval -ENOMEM if there is no place in the buffer to store the value.
45  */
pack_uint8(uint8_t val,struct buf_ctx * buf)46 static int pack_uint8(uint8_t val, struct buf_ctx *buf)
47 {
48 	if ((buf->end - buf->cur) < sizeof(uint8_t)) {
49 		return -ENOMEM;
50 	}
51 
52 	NET_DBG(">> val:%02x cur:%p, end:%p", val, (void *)buf->cur, (void *)buf->end);
53 
54 	/* Pack value. */
55 	*(buf->cur++) = val;
56 
57 	return 0;
58 }
59 
60 /**
61  * @brief Packs unsigned 16 bit value to the buffer at the offset requested.
62  *
63  * @param[in] val Value to be packed.
64  * @param[inout] buf A pointer to the buf_ctx structure containing current
65  *                   buffer position.
66  *
67  * @retval 0 if the procedure is successful.
68  * @retval -ENOMEM if there is no place in the buffer to store the value.
69  */
pack_uint16(uint16_t val,struct buf_ctx * buf)70 static int pack_uint16(uint16_t val, struct buf_ctx *buf)
71 {
72 	if ((buf->end - buf->cur) < sizeof(uint16_t)) {
73 		return -ENOMEM;
74 	}
75 
76 	NET_DBG(">> val:%04x cur:%p, end:%p", val, (void *)buf->cur, (void *)buf->end);
77 
78 	/* Pack value. */
79 	*(buf->cur++) = (val >> 8) & 0xFF;
80 	*(buf->cur++) = val & 0xFF;
81 
82 	return 0;
83 }
84 
85 /**
86  * @brief Packs utf8 string to the buffer at the offset requested.
87  *
88  * @param[in] str UTF-8 string and its length to be packed.
89  * @param[inout] buf A pointer to the buf_ctx structure containing current
90  *                   buffer position.
91  *
92  * @retval 0 if the procedure is successful.
93  * @retval -ENOMEM if there is no place in the buffer to store the string.
94  */
pack_utf8_str(const struct mqtt_utf8 * str,struct buf_ctx * buf)95 static int pack_utf8_str(const struct mqtt_utf8 *str, struct buf_ctx *buf)
96 {
97 	if ((buf->end - buf->cur) < GET_UT8STR_BUFFER_SIZE(str)) {
98 		return -ENOMEM;
99 	}
100 
101 	NET_DBG(">> str_size:%08x cur:%p, end:%p",
102 		 (uint32_t)GET_UT8STR_BUFFER_SIZE(str), (void *)buf->cur, (void *)buf->end);
103 
104 	/* Pack length followed by string. */
105 	(void)pack_uint16(str->size, buf);
106 
107 	memcpy(buf->cur, str->utf8, str->size);
108 	buf->cur += str->size;
109 
110 	return 0;
111 }
112 
113 /**
114  * @brief Computes and encodes length for the MQTT fixed header.
115  *
116  * @note The remaining length is not packed as a fixed unsigned 32 bit integer.
117  *       Instead it is packed on algorithm below:
118  *
119  * @code
120  * do
121  *            encodedByte = X MOD 128
122  *            X = X DIV 128
123  *            // if there are more data to encode, set the top bit of this byte
124  *            if ( X > 0 )
125  *                encodedByte = encodedByte OR 128
126  *            endif
127  *                'output' encodedByte
128  *       while ( X > 0 )
129  * @endcode
130  *
131  * @param[in] length Length of variable header and payload in the MQTT message.
132  * @param[inout] buf A pointer to the buf_ctx structure containing current
133  *                   buffer position. May be NULL (in this case function will
134  *                   only calculate number of bytes needed).
135  *
136  * @return Number of bytes needed to encode length value.
137  */
packet_length_encode(uint32_t length,struct buf_ctx * buf)138 static uint8_t packet_length_encode(uint32_t length, struct buf_ctx *buf)
139 {
140 	uint8_t encoded_bytes = 0U;
141 
142 	NET_DBG(">> length:0x%08x cur:%p, end:%p", length,
143 		 (buf == NULL) ? 0 : (void *)buf->cur, (buf == NULL) ? 0 : (void *)buf->end);
144 
145 	do {
146 		encoded_bytes++;
147 
148 		if (buf != NULL) {
149 			*(buf->cur) = length & MQTT_LENGTH_VALUE_MASK;
150 		}
151 
152 		length >>= MQTT_LENGTH_SHIFT;
153 
154 		if (buf != NULL) {
155 			if (length > 0) {
156 				*(buf->cur) |= MQTT_LENGTH_CONTINUATION_BIT;
157 			}
158 			buf->cur++;
159 		}
160 	} while (length > 0);
161 
162 	return encoded_bytes;
163 }
164 
165 /**
166  * @brief Encodes fixed header for the MQTT message and provides pointer to
167  *        start of the header.
168  *
169  * @param[in] message_type Message type containing packet type and the flags.
170  *                         Use @ref MQTT_MESSAGES_OPTIONS to construct the
171  *                         message_type.
172  * @param[in] start  Pointer to the start of the variable header.
173  * @param[inout] buf Buffer context used to encode the frame.
174  *                   The 5 bytes before the start of the message are assumed
175  *                   by the routine to be available to pack the fixed header.
176  *                   However, since the fixed header length is variable
177  *                   length, the pointer to the start of the MQTT message
178  *                   along with encoded fixed header is supplied as output
179  *                   parameter if the procedure was successful.
180  *                   As output, the pointers will point to beginning and the end
181  *                   of the frame.
182  *
183  * @retval 0 if the procedure is successful.
184  * @retval -EMSGSIZE if the message is too big for MQTT.
185  */
mqtt_encode_fixed_header(uint8_t message_type,uint8_t * start,struct buf_ctx * buf)186 static uint32_t mqtt_encode_fixed_header(uint8_t message_type, uint8_t *start,
187 				      struct buf_ctx *buf)
188 {
189 	uint32_t length = buf->cur - start;
190 	uint8_t fixed_header_length;
191 
192 	if (length > MQTT_MAX_PAYLOAD_SIZE) {
193 		return -EMSGSIZE;
194 	}
195 
196 	NET_DBG("<< msg type:0x%02x length:0x%08x", message_type, length);
197 
198 	fixed_header_length = packet_length_encode(length, NULL);
199 	fixed_header_length += sizeof(uint8_t);
200 
201 	NET_DBG("Fixed header length = %02x", fixed_header_length);
202 
203 	/* Set the pointer at the start of the frame before encoding. */
204 	buf->cur = start - fixed_header_length;
205 
206 	(void)pack_uint8(message_type, buf);
207 	(void)packet_length_encode(length, buf);
208 
209 	/* Set the cur pointer back at the start of the frame,
210 	 * and end pointer to the end of the frame.
211 	 */
212 	buf->cur = buf->cur - fixed_header_length;
213 	buf->end = buf->cur + length + fixed_header_length;
214 
215 	return 0;
216 }
217 
218 /**
219  * @brief Encodes a string of a zero length.
220  *
221  * @param[in] buffer_len Total size of the buffer on which string will be
222  *                       encoded. This shall not be zero.
223  * @param[inout] buf A pointer to the buf_ctx structure containing current
224  *                   buffer position.
225  *
226  * @retval 0 if the procedure is successful.
227  * @retval -ENOMEM if there is no place in the buffer to store the binary
228  *                 string.
229  */
zero_len_str_encode(struct buf_ctx * buf)230 static int zero_len_str_encode(struct buf_ctx *buf)
231 {
232 	return pack_uint16(0x0000, buf);
233 }
234 
235 /**
236  * @brief Encodes and sends messages that contain only message id in
237  *        the variable header.
238  *
239  * @param[in] message_type Message type and reserved bit fields.
240  * @param[in] message_id Message id to be encoded in the variable header.
241  * @param[inout] buf_ctx Pointer to the buffer context structure,
242  *                       containing buffer for the encoded message.
243  *
244  * @retval 0 or an error code indicating a reason for failure.
245  */
mqtt_message_id_only_enc(uint8_t message_type,uint16_t message_id,struct buf_ctx * buf)246 static int mqtt_message_id_only_enc(uint8_t message_type, uint16_t message_id,
247 				    struct buf_ctx *buf)
248 {
249 	int err_code;
250 	uint8_t *start;
251 
252 	/* Message id zero is not permitted by spec. */
253 	if (message_id == 0U) {
254 		return -EINVAL;
255 	}
256 
257 	/* Reserve space for fixed header. */
258 	buf->cur += MQTT_FIXED_HEADER_MAX_SIZE;
259 	start = buf->cur;
260 
261 	err_code = pack_uint16(message_id, buf);
262 	if (err_code != 0) {
263 		return err_code;
264 	}
265 
266 	return mqtt_encode_fixed_header(message_type, start, buf);
267 }
268 
connect_request_encode(const struct mqtt_client * client,struct buf_ctx * buf)269 int connect_request_encode(const struct mqtt_client *client,
270 			   struct buf_ctx *buf)
271 {
272 	uint8_t connect_flags = client->clean_session << 1;
273 	const uint8_t message_type =
274 			MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_CONNECT, 0, 0, 0);
275 	const struct mqtt_utf8 *mqtt_proto_desc;
276 	uint8_t *connect_flags_pos;
277 	int err_code;
278 	uint8_t *start;
279 
280 	if (client->protocol_version == MQTT_VERSION_3_1_1) {
281 		mqtt_proto_desc = &mqtt_3_1_1_proto_desc;
282 	} else {
283 		mqtt_proto_desc = &mqtt_3_1_0_proto_desc;
284 	}
285 
286 	/* Reserve space for fixed header. */
287 	buf->cur += MQTT_FIXED_HEADER_MAX_SIZE;
288 	start = buf->cur;
289 
290 	NET_HEXDUMP_DBG(mqtt_proto_desc->utf8, mqtt_proto_desc->size,
291 			 "Encoding Protocol Description.");
292 
293 	err_code = pack_utf8_str(mqtt_proto_desc, buf);
294 	if (err_code != 0) {
295 		return err_code;
296 	}
297 
298 	NET_DBG("Encoding Protocol Version %02x.", client->protocol_version);
299 	err_code = pack_uint8(client->protocol_version, buf);
300 	if (err_code != 0) {
301 		return err_code;
302 	}
303 
304 	/* Remember position of connect flag and leave one byte for it to
305 	 * be packed once we determine its value.
306 	 */
307 	connect_flags_pos = buf->cur;
308 
309 	err_code = pack_uint8(0, buf);
310 	if (err_code != 0) {
311 		return err_code;
312 	}
313 
314 	NET_DBG("Encoding Keep Alive Time %04x.", client->keepalive);
315 	err_code = pack_uint16(client->keepalive, buf);
316 	if (err_code != 0) {
317 		return err_code;
318 	}
319 
320 	NET_HEXDUMP_DBG(client->client_id.utf8, client->client_id.size,
321 			 "Encoding Client Id.");
322 	err_code = pack_utf8_str(&client->client_id, buf);
323 	if (err_code != 0) {
324 		return err_code;
325 	}
326 
327 	/* Pack will topic and QoS */
328 	if (client->will_topic != NULL) {
329 		connect_flags |= MQTT_CONNECT_FLAG_WILL_TOPIC;
330 		/* QoS is always 1 as of now. */
331 		connect_flags |= ((client->will_topic->qos & 0x03) << 3);
332 		connect_flags |= client->will_retain << 5;
333 
334 		NET_HEXDUMP_DBG(client->will_topic->topic.utf8,
335 				 client->will_topic->topic.size,
336 				 "Encoding Will Topic.");
337 		err_code = pack_utf8_str(&client->will_topic->topic, buf);
338 		if (err_code != 0) {
339 			return err_code;
340 		}
341 
342 		if (client->will_message != NULL) {
343 			NET_HEXDUMP_DBG(client->will_message->utf8,
344 					 client->will_message->size,
345 					 "Encoding Will Message.");
346 			err_code = pack_utf8_str(client->will_message, buf);
347 			if (err_code != 0) {
348 				return err_code;
349 			}
350 		} else {
351 			NET_DBG("Encoding Zero Length Will Message.");
352 			err_code = zero_len_str_encode(buf);
353 			if (err_code != 0) {
354 				return err_code;
355 			}
356 		}
357 	}
358 
359 	/* Pack Username if any. */
360 	if (client->user_name != NULL) {
361 		connect_flags |= MQTT_CONNECT_FLAG_USERNAME;
362 
363 		NET_HEXDUMP_DBG(client->user_name->utf8,
364 				 client->user_name->size,
365 				 "Encoding Username.");
366 		err_code = pack_utf8_str(client->user_name, buf);
367 		if (err_code != 0) {
368 			return err_code;
369 		}
370 	}
371 
372 	/* Pack Password if any. */
373 	if (client->password != NULL) {
374 		connect_flags |= MQTT_CONNECT_FLAG_PASSWORD;
375 
376 		NET_HEXDUMP_DBG(client->password->utf8,
377 				 client->password->size,
378 				 "Encoding Password.");
379 		err_code = pack_utf8_str(client->password, buf);
380 		if (err_code != 0) {
381 			return err_code;
382 		}
383 	}
384 
385 	/* Write the flags the connect flags. */
386 	*connect_flags_pos = connect_flags;
387 
388 	return mqtt_encode_fixed_header(message_type, start, buf);
389 }
390 
publish_encode(const struct mqtt_publish_param * param,struct buf_ctx * buf)391 int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf)
392 {
393 	const uint8_t message_type = MQTT_MESSAGES_OPTIONS(
394 			MQTT_PKT_TYPE_PUBLISH, param->dup_flag,
395 			param->message.topic.qos, param->retain_flag);
396 	int err_code;
397 	uint8_t *start;
398 
399 	/* Message id zero is not permitted by spec. */
400 	if ((param->message.topic.qos) && (param->message_id == 0U)) {
401 		return -EINVAL;
402 	}
403 
404 	/* Reserve space for fixed header. */
405 	buf->cur += MQTT_FIXED_HEADER_MAX_SIZE;
406 	start = buf->cur;
407 
408 	err_code = pack_utf8_str(&param->message.topic.topic, buf);
409 	if (err_code != 0) {
410 		return err_code;
411 	}
412 
413 	if (param->message.topic.qos) {
414 		err_code = pack_uint16(param->message_id, buf);
415 		if (err_code != 0) {
416 			return err_code;
417 		}
418 	}
419 
420 	/* Do not copy payload. We move the buffer pointer to ensure that
421 	 * message length in fixed header is encoded correctly.
422 	 */
423 	buf->cur += param->message.payload.len;
424 
425 	err_code = mqtt_encode_fixed_header(message_type, start, buf);
426 	if (err_code != 0) {
427 		return err_code;
428 	}
429 
430 	buf->end -= param->message.payload.len;
431 
432 	return 0;
433 }
434 
publish_ack_encode(const struct mqtt_puback_param * param,struct buf_ctx * buf)435 int publish_ack_encode(const struct mqtt_puback_param *param,
436 		       struct buf_ctx *buf)
437 {
438 	const uint8_t message_type =
439 		MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBACK, 0, 0, 0);
440 
441 	return mqtt_message_id_only_enc(message_type, param->message_id, buf);
442 }
443 
publish_receive_encode(const struct mqtt_pubrec_param * param,struct buf_ctx * buf)444 int publish_receive_encode(const struct mqtt_pubrec_param *param,
445 			   struct buf_ctx *buf)
446 {
447 	const uint8_t message_type =
448 		MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREC, 0, 0, 0);
449 
450 	return mqtt_message_id_only_enc(message_type, param->message_id, buf);
451 }
452 
publish_release_encode(const struct mqtt_pubrel_param * param,struct buf_ctx * buf)453 int publish_release_encode(const struct mqtt_pubrel_param *param,
454 			   struct buf_ctx *buf)
455 {
456 	const uint8_t message_type =
457 		MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREL, 0, 1, 0);
458 
459 	return mqtt_message_id_only_enc(message_type, param->message_id, buf);
460 }
461 
publish_complete_encode(const struct mqtt_pubcomp_param * param,struct buf_ctx * buf)462 int publish_complete_encode(const struct mqtt_pubcomp_param *param,
463 			    struct buf_ctx *buf)
464 {
465 	const uint8_t message_type =
466 		MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBCOMP, 0, 0, 0);
467 
468 	return mqtt_message_id_only_enc(message_type, param->message_id, buf);
469 }
470 
disconnect_encode(struct buf_ctx * buf)471 int disconnect_encode(struct buf_ctx *buf)
472 {
473 	if (buf->end - buf->cur < sizeof(disc_packet)) {
474 		return -ENOMEM;
475 	}
476 
477 	memcpy(buf->cur, disc_packet, sizeof(disc_packet));
478 	buf->end = buf->cur + sizeof(disc_packet);
479 
480 	return 0;
481 }
482 
subscribe_encode(const struct mqtt_subscription_list * param,struct buf_ctx * buf)483 int subscribe_encode(const struct mqtt_subscription_list *param,
484 		     struct buf_ctx *buf)
485 {
486 	const uint8_t message_type = MQTT_MESSAGES_OPTIONS(
487 			MQTT_PKT_TYPE_SUBSCRIBE, 0, 1, 0);
488 	int err_code, i;
489 	uint8_t *start;
490 
491 	/* Message id zero is not permitted by spec. */
492 	if (param->message_id == 0U) {
493 		return -EINVAL;
494 	}
495 
496 	/* Reserve space for fixed header. */
497 	buf->cur += MQTT_FIXED_HEADER_MAX_SIZE;
498 	start = buf->cur;
499 
500 	err_code = pack_uint16(param->message_id, buf);
501 	if (err_code != 0) {
502 		return err_code;
503 	}
504 
505 	for (i = 0; i < param->list_count; i++) {
506 		err_code = pack_utf8_str(&param->list[i].topic, buf);
507 		if (err_code != 0) {
508 			return err_code;
509 		}
510 
511 		err_code = pack_uint8(param->list[i].qos, buf);
512 		if (err_code != 0) {
513 			return err_code;
514 		}
515 	}
516 
517 	return mqtt_encode_fixed_header(message_type, start, buf);
518 }
519 
unsubscribe_encode(const struct mqtt_subscription_list * param,struct buf_ctx * buf)520 int unsubscribe_encode(const struct mqtt_subscription_list *param,
521 		       struct buf_ctx *buf)
522 {
523 	const uint8_t message_type = MQTT_MESSAGES_OPTIONS(
524 		MQTT_PKT_TYPE_UNSUBSCRIBE, 0, MQTT_QOS_1_AT_LEAST_ONCE, 0);
525 	int err_code, i;
526 	uint8_t *start;
527 
528 	/* Reserve space for fixed header. */
529 	buf->cur += MQTT_FIXED_HEADER_MAX_SIZE;
530 	start = buf->cur;
531 
532 	err_code = pack_uint16(param->message_id, buf);
533 	if (err_code != 0) {
534 		return err_code;
535 	}
536 
537 	for (i = 0; i < param->list_count; i++) {
538 		err_code = pack_utf8_str(&param->list[i].topic, buf);
539 		if (err_code != 0) {
540 			return err_code;
541 		}
542 	}
543 
544 	return mqtt_encode_fixed_header(message_type, start, buf);
545 }
546 
ping_request_encode(struct buf_ctx * buf)547 int ping_request_encode(struct buf_ctx *buf)
548 {
549 	if (buf->end - buf->cur < sizeof(ping_packet)) {
550 		return -ENOMEM;
551 	}
552 
553 	memcpy(buf->cur, ping_packet, sizeof(ping_packet));
554 	buf->end = buf->cur + sizeof(ping_packet);
555 
556 	return 0;
557 }
558