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