1 /*
2  * Copyright (c) 2022 René Beckmann
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/tc_util.h>
8 #include <mqtt_sn_msg.h>
9 #include <zephyr/net/mqtt_sn.h>
10 #include <zephyr/sys/util.h> /* for ARRAY_SIZE */
11 #include <zephyr/ztest.h>
12 
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(test);
15 
16 #define CLIENTID MQTT_UTF8_LITERAL("zephyr")
17 #define BUFFER_SIZE 128
18 
19 struct mqtt_sn_decode_test {
20 	uint8_t *data;
21 	size_t datasz;
22 	char *name;
23 	struct mqtt_sn_param expected;
24 };
25 
26 #define MQTT_SN_DECODE_TEST(dataset)                                                               \
27 	.data = dataset, .datasz = sizeof(dataset), .name = STRINGIFY(dataset)
28 
29 /* advertise gw id 42, duration 0xDEAD */
30 static uint8_t advertise[] = { 5, 0x00, 42, 0xDE, 0xAD };
31 /* searchgw with radius 1 */
32 static uint8_t searchgw[] = { 3, 0x01, 1 };
33 /* gwinfo gw id 42, address 127.0.0.1 */
34 static uint8_t gwinfo[] = { 7, 0x02, 42, 127, 0, 0, 1 };
35 /* connect with flags [will, clean_session], duration 300, client id "zephyrclient" */
36 static uint8_t connect[] = { 18,  0x04, 0x0C, 0x01, 1,	 44,  'z', 'e', 'p',
37 			     'h', 'y',	'r',  'c',  'l', 'i', 'e', 'n', 't' };
38 /* connack with return code accepted */
39 static uint8_t connack1[] = { 3, 0x05, 0x00 };
40 /* connack with extended length field and return code rejected - invalid topic id */
41 static uint8_t connack2[] = { 0x01, 0, 5, 0x05, 0x02 };
42 /* empty message */
43 static uint8_t willtopicreq[] = { 2, 0x06 };
44 /* willtopic with flags [qos 1, retain], topic "/zephyr" */
45 static uint8_t willtopic[] = { 10, 0x07, 0x30, '/', 'z', 'e', 'p', 'h', 'y', 'r' };
46 /* empty message */
47 static uint8_t willmsgreq[] = { 2, 0x08 };
48 /* willmsg with msg "mywill" */
49 static uint8_t willmsg[] = { 8, 0x09, 'm', 'y', 'w', 'i', 'l', 'l' };
50 /* registration with topic ID 0x1A1B, msg ID 0x1C1D, topic "/zephyr" */
51 static uint8_t reg[] = { 13, 0x0A, 0x1A, 0x1B, 0x1C, 0x1D, '/', 'z', 'e', 'p', 'h', 'y', 'r' };
52 /* registration with topic ID 0x0000, msg ID 0x1C1D, topic "/zephyr" */
53 static uint8_t reg_client[] = {
54 	13, 0x0A, 0x00, 0x00, 0x1C, 0x1D, '/', 'z', 'e', 'p', 'h', 'y', 'r'
55 };
56 /* registration ack with topic ID 0x1A1B, msg ID 0x1C1D, return code accepted */
57 static uint8_t regack[] = { 7, 0x0B, 0x1A, 0x1B, 0x1C, 0x1D, 0 };
58 /* publish message with flags [DUP, QOS2, Retain, Short Topic Type], topic ID "RB", msg ID 0x1C1D,
59  * data "zephyr"
60  */
61 static uint8_t publish[] = { 13, 0x0C, 0xD2, 'R', 'B', 0x1C, 0x1D, 'z', 'e', 'p', 'h', 'y', 'r' };
62 /* publish ack with topic ID 0x1A1B, msg ID 0x1C1D, return code rejected: not supported */
63 static uint8_t puback[] = { 7, 0x0D, 0x1A, 0x1B, 0x1C, 0x1D, 0x03 };
64 /* pubrec */
65 static uint8_t pubrec[] = { 4, 0x0F, 0xBE, 0xEF };
66 /* pubrel */
67 static uint8_t pubrel[] = { 4, 0x10, 0xBE, 0xEF };
68 /* pubcomp */
69 static uint8_t pubcomp[] = { 4, 0x0E, 0xBE, 0xEF };
70 /* subscribe with flags [DUP, QOS0, topic name], message ID 0x1C1D, for topic "/zephyr" */
71 static uint8_t subscribe[] = { 12, 0x12, 0x80, 0x1C, 0x1D, '/', 'z', 'e', 'p', 'h', 'y', 'r' };
72 /* subscribe ack with flags [QOS-1], topic ID 0x1A1B, msg ID 0x1909,
73  * return code rejected - congested
74  */
75 static uint8_t suback[] = { 8, 0x13, 0x60, 0x1A, 0x1B, 0x19, 0x09, 0x01 };
76 /* unsubscribe with flags [predefined topic ID], message ID 0x1C1D, for topic 0x1234 */
77 static uint8_t unsubscribe[] = { 7, 0x14, 0x01, 0x1C, 0x1D, 0x12, 0x34 };
78 /* unsubscribe ack msg ID 0x1337 */
79 static uint8_t unsuback[] = { 4, 0x15, 0x13, 0x37 };
80 /* pingreq from client "zephyrclient" */
81 static uint8_t pingreq[] = { 14, 0x16, 'z', 'e', 'p', 'h', 'y', 'r', 'c', 'l', 'i', 'e', 'n', 't' };
82 /* pingreq - empty */
83 static uint8_t pingreq1[] = { 2, 0x16 };
84 /* pingresp */
85 static uint8_t pingresp[] = { 2, 0x17 };
86 /* disconnect by client with duration 10000 */
87 static uint8_t disconnect[] = { 4, 0x18, 0x27, 0x10 };
88 /* empty disconnect by GW */
89 static uint8_t disconnect_gw[] = { 2, 0x18 };
90 /* willtopicupd with flags [QOS0, retain], topic "/zephyr" */
91 static uint8_t willtopicupd[] = { 10, 0x1A, 0x10, '/', 'z', 'e', 'p', 'h', 'y', 'r' };
92 /* willmsgupd with message "mywill" */
93 static uint8_t willmsgupd[] = { 8, 0x1C, 'm', 'y', 'w', 'i', 'l', 'l' };
94 /* willtopicresp */
95 static uint8_t willtopicresp[] = { 3, 0x1B, 0 };
96 /* willmsgresp */
97 static uint8_t willmsgresp[] = { 3, 0x1D, 0 };
98 
99 static struct mqtt_sn_decode_test decode_tests[] = {
100 	{
101 		MQTT_SN_DECODE_TEST(advertise),
102 		.expected = (struct mqtt_sn_param){
103 			.type = MQTT_SN_MSG_TYPE_ADVERTISE,
104 			.params.advertise = {
105 				.gw_id = 42,
106 				.duration = 0xDEAD
107 			}
108 		}
109 	},
110 	{
111 		MQTT_SN_DECODE_TEST(gwinfo),
112 		.expected = (struct mqtt_sn_param){
113 			.type = MQTT_SN_MSG_TYPE_GWINFO,
114 			.params.gwinfo = {
115 				.gw_id = 42,
116 				.gw_add = {
117 					.data = gwinfo+3,
118 					.size = 4
119 				}
120 			}
121 		}
122 	},
123 	{
124 		MQTT_SN_DECODE_TEST(connack1),
125 		.expected = (struct mqtt_sn_param){
126 			.type = MQTT_SN_MSG_TYPE_CONNACK,
127 			.params.connack = {
128 				.ret_code = MQTT_SN_CODE_ACCEPTED
129 			}
130 		}
131 	},
132 	{
133 		MQTT_SN_DECODE_TEST(connack2),
134 		.expected = (struct mqtt_sn_param){
135 			.type = MQTT_SN_MSG_TYPE_CONNACK,
136 			.params.connack = {
137 				.ret_code = MQTT_SN_CODE_REJECTED_TOPIC_ID
138 			}
139 		}
140 	},
141 	{
142 		MQTT_SN_DECODE_TEST(willtopicreq),
143 		.expected = (struct mqtt_sn_param){
144 			.type = MQTT_SN_MSG_TYPE_WILLTOPICREQ
145 		}
146 	},
147 	{
148 		MQTT_SN_DECODE_TEST(willmsgreq),
149 		.expected = (struct mqtt_sn_param){
150 			.type = MQTT_SN_MSG_TYPE_WILLMSGREQ
151 		}
152 	},
153 	{
154 		MQTT_SN_DECODE_TEST(reg),
155 		.expected = (struct mqtt_sn_param){
156 			.type = MQTT_SN_MSG_TYPE_REGISTER,
157 			.params.reg = {
158 				.topic_id = 0x1A1B,
159 				.msg_id = 0x1C1D,
160 				.topic = {
161 					.data = reg + 6,
162 					.size = 7
163 				}
164 			}
165 		}
166 	},
167 	{
168 		MQTT_SN_DECODE_TEST(regack),
169 		.expected = (struct mqtt_sn_param){
170 			.type = MQTT_SN_MSG_TYPE_REGACK,
171 			.params.regack = {
172 				.topic_id = 0x1A1B,
173 				.msg_id = 0x1C1D,
174 				.ret_code = MQTT_SN_CODE_ACCEPTED
175 			}
176 		}
177 	},
178 	{
179 		MQTT_SN_DECODE_TEST(publish),
180 		.expected = (struct mqtt_sn_param){
181 			.type = MQTT_SN_MSG_TYPE_PUBLISH,
182 			.params.publish = {
183 				.dup = true,
184 				.qos = MQTT_SN_QOS_2,
185 				.retain = true,
186 				.topic_type = MQTT_SN_TOPIC_TYPE_SHORT,
187 				.topic_id = 0x5242, /* "RB" in hex */
188 				.msg_id = 0x1C1D,
189 				.data = {
190 					.data = publish +  7,
191 					.size = 6
192 				}
193 			}
194 		}
195 	},
196 	{
197 		MQTT_SN_DECODE_TEST(puback),
198 		.expected = (struct mqtt_sn_param){
199 			.type = MQTT_SN_MSG_TYPE_PUBACK,
200 			.params.puback = {
201 				.topic_id = 0x1A1B,
202 				.msg_id = 0x1C1D,
203 				.ret_code = MQTT_SN_CODE_REJECTED_NOTSUP
204 			}
205 		}
206 	},
207 	{
208 		MQTT_SN_DECODE_TEST(pubrec),
209 		.expected = (struct mqtt_sn_param){
210 			.type = MQTT_SN_MSG_TYPE_PUBREC,
211 			.params.puback = {
212 				.msg_id = 0xBEEF,
213 			}
214 		}
215 	},
216 	{
217 		MQTT_SN_DECODE_TEST(pubrel),
218 		.expected = (struct mqtt_sn_param){
219 			.type = MQTT_SN_MSG_TYPE_PUBREL,
220 			.params.pubrel = {
221 				.msg_id = 0xBEEF,
222 			}
223 		}
224 	},
225 	{
226 		MQTT_SN_DECODE_TEST(pubcomp),
227 		.expected = (struct mqtt_sn_param){
228 			.type = MQTT_SN_MSG_TYPE_PUBCOMP,
229 			.params.pubcomp = {
230 				.msg_id = 0xBEEF,
231 			}
232 		}
233 	},
234 	{
235 		MQTT_SN_DECODE_TEST(suback),
236 		.expected = (struct mqtt_sn_param){
237 			.type = MQTT_SN_MSG_TYPE_SUBACK,
238 			.params.suback = {
239 				.topic_id = 0x1A1B,
240 				.msg_id = 0x1909,
241 				.qos = MQTT_SN_QOS_M1,
242 				.ret_code = MQTT_SN_CODE_REJECTED_CONGESTION
243 			}
244 		}
245 	},
246 	{
247 		MQTT_SN_DECODE_TEST(unsuback),
248 		.expected = (struct mqtt_sn_param){
249 			.type = MQTT_SN_MSG_TYPE_UNSUBACK,
250 			.params.unsuback = {
251 				.msg_id = 0x1337,
252 			}
253 		}
254 	},
255 	{
256 		MQTT_SN_DECODE_TEST(pingreq1),
257 		.expected = (struct mqtt_sn_param){
258 			.type = MQTT_SN_MSG_TYPE_PINGREQ,
259 		}
260 	},
261 	{
262 		MQTT_SN_DECODE_TEST(pingresp),
263 		.expected = (struct mqtt_sn_param){
264 			.type = MQTT_SN_MSG_TYPE_PINGRESP,
265 		}
266 	},
267 	{
268 		MQTT_SN_DECODE_TEST(disconnect_gw),
269 		.expected = (struct mqtt_sn_param){
270 			.type = MQTT_SN_MSG_TYPE_DISCONNECT,
271 		}
272 	},
273 	{
274 		MQTT_SN_DECODE_TEST(willtopicresp),
275 		.expected = (struct mqtt_sn_param){
276 			.type = MQTT_SN_MSG_TYPE_WILLTOPICRESP,
277 		}
278 	},
279 	{
280 		MQTT_SN_DECODE_TEST(willmsgresp),
281 		.expected = (struct mqtt_sn_param){
282 			.type = MQTT_SN_MSG_TYPE_WILLMSGRESP,
283 		}
284 	},
285 };
286 
ZTEST(mqtt_sn_packet,test_mqtt_packet_decode)287 static ZTEST(mqtt_sn_packet, test_mqtt_packet_decode)
288 {
289 	struct net_buf_simple msg;
290 	struct mqtt_sn_param param;
291 	int err;
292 
293 	for (size_t i = 0; i < ARRAY_SIZE(decode_tests); i++) {
294 		TC_PRINT("%s - test %zu: %s\n", __func__, i, decode_tests[i].name);
295 		LOG_HEXDUMP_DBG(decode_tests[i].data, decode_tests[i].datasz, "Test data");
296 		if (i == 4) {
297 			LOG_HEXDUMP_INF(willtopicreq, sizeof(willtopicreq), "willtopicreq");
298 		}
299 		memset(&param, 0, sizeof(param));
300 		net_buf_simple_init_with_data(&msg, decode_tests[i].data, decode_tests[i].datasz);
301 
302 		err = mqtt_sn_decode_msg(&msg, &param);
303 		zassert_equal(err, 0, "Unexpected error %d", err);
304 
305 		LOG_HEXDUMP_DBG(&param, sizeof(param), "Decoded data");
306 		LOG_HEXDUMP_DBG(&decode_tests[i].expected, sizeof(param), "Expected data");
307 		zassert_mem_equal(&param, &decode_tests[i].expected, sizeof(param), "in test %zu",
308 				  i);
309 	}
310 }
311 
312 struct mqtt_sn_encode_test {
313 	const char *name;
314 	const uint8_t *expected;
315 	size_t expectedsz;
316 	struct mqtt_sn_param p;
317 };
318 
319 #define MQTT_SN_ENCODE_TEST(_name)                                                                 \
320 	.name = STRINGIFY(_name), .expected = _name, .expectedsz = sizeof(_name)
321 
322 static struct mqtt_sn_encode_test encode_tests[] = {
323 	{
324 		MQTT_SN_ENCODE_TEST(searchgw),
325 		.p = {
326 			.type = MQTT_SN_MSG_TYPE_SEARCHGW,
327 			.params.searchgw = {
328 				.radius = 1
329 			}
330 		}
331 	},
332 	{
333 		MQTT_SN_ENCODE_TEST(gwinfo),
334 		.p = {
335 			.type = MQTT_SN_MSG_TYPE_GWINFO,
336 			.params.gwinfo = {
337 				.gw_id = 42,
338 				.gw_add = MQTT_SN_DATA_BYTES(127, 0, 0, 1)
339 			}
340 		}
341 	},
342 	{
343 		MQTT_SN_ENCODE_TEST(connect),
344 		.p = {
345 			.type = MQTT_SN_MSG_TYPE_CONNECT,
346 			.params.connect = {
347 				.will = true,
348 				.clean_session = true,
349 				.client_id = MQTT_SN_DATA_STRING_LITERAL("zephyrclient"),
350 				.duration = 300,
351 			}
352 		}
353 	},
354 	{
355 		MQTT_SN_ENCODE_TEST(willtopic),
356 		.p = {
357 			.type = MQTT_SN_MSG_TYPE_WILLTOPIC,
358 			.params.willtopic = {
359 				.qos = MQTT_SN_QOS_1,
360 				.retain = true,
361 				.topic = MQTT_SN_DATA_STRING_LITERAL("/zephyr")
362 			}
363 		}
364 	},
365 	{
366 		MQTT_SN_ENCODE_TEST(willmsg),
367 		.p = {
368 			.type = MQTT_SN_MSG_TYPE_WILLMSG,
369 			.params.willmsg = {
370 				.msg = MQTT_SN_DATA_STRING_LITERAL("mywill")
371 			}
372 		}
373 	},
374 	{
375 		MQTT_SN_ENCODE_TEST(reg_client),
376 		.p = {
377 			.type = MQTT_SN_MSG_TYPE_REGISTER,
378 			.params.reg = {
379 				/*
380 				 * The client must not encode the topic ID -
381 				 * check this is followed
382 				 */
383 				.topic_id = 0x1A1B,
384 				.msg_id = 0x1C1D,
385 				.topic = MQTT_SN_DATA_STRING_LITERAL("/zephyr")
386 			}
387 		}
388 	},
389 	{
390 		MQTT_SN_ENCODE_TEST(regack),
391 		.p = {
392 			.type = MQTT_SN_MSG_TYPE_REGACK,
393 			.params.regack = {
394 				.topic_id = 0x1A1B,
395 				.msg_id = 0x1C1D,
396 				.ret_code = MQTT_SN_CODE_ACCEPTED
397 			}
398 		}
399 	},
400 	{
401 		MQTT_SN_ENCODE_TEST(publish),
402 		.p = {
403 			.type = MQTT_SN_MSG_TYPE_PUBLISH,
404 			.params.publish = {
405 				.dup = true,
406 				.qos = MQTT_SN_QOS_2,
407 				.retain = true,
408 
409 				.topic_id = 0x5242,
410 				.topic_type = MQTT_SN_TOPIC_TYPE_SHORT,
411 				.msg_id = 0x1C1D,
412 				.data = MQTT_SN_DATA_STRING_LITERAL("zephyr")
413 			}
414 		}
415 	},
416 	{
417 		MQTT_SN_ENCODE_TEST(puback),
418 		.p = {
419 			.type = MQTT_SN_MSG_TYPE_PUBACK,
420 			.params.puback = {
421 				.topic_id = 0x1A1B,
422 				.msg_id = 0x1C1D,
423 				.ret_code = MQTT_SN_CODE_REJECTED_NOTSUP
424 			}
425 		}
426 	},
427 	{
428 		MQTT_SN_ENCODE_TEST(pubrec),
429 		.p = {
430 			.type = MQTT_SN_MSG_TYPE_PUBREC,
431 			.params.pubrec = {
432 				.msg_id = 0xBEEF
433 			}
434 		}
435 	},
436 	{
437 		MQTT_SN_ENCODE_TEST(pubrel),
438 		.p = {
439 			.type = MQTT_SN_MSG_TYPE_PUBREL,
440 			.params.pubrel = {
441 				.msg_id = 0xBEEF
442 			}
443 		}
444 	},
445 	{
446 		MQTT_SN_ENCODE_TEST(pubcomp),
447 		.p = {
448 			.type = MQTT_SN_MSG_TYPE_PUBCOMP,
449 			.params.pubcomp = {
450 				.msg_id = 0xBEEF
451 			}
452 		}
453 	},
454 	{
455 		MQTT_SN_ENCODE_TEST(subscribe),
456 		.p = {
457 			.type = MQTT_SN_MSG_TYPE_SUBSCRIBE,
458 			.params.subscribe = {
459 				.dup = true,
460 				.qos = MQTT_SN_QOS_0,
461 				.msg_id = 0x1C1D,
462 				.topic_type = MQTT_SN_FLAGS_TOPICID_TYPE_NORMAL,
463 				.topic.topic_name = MQTT_SN_DATA_STRING_LITERAL("/zephyr")
464 			}
465 		}
466 	},
467 	{
468 		MQTT_SN_ENCODE_TEST(unsubscribe),
469 		.p = {
470 			.type = MQTT_SN_MSG_TYPE_UNSUBSCRIBE,
471 			.params.unsubscribe = {
472 				.msg_id = 0x1C1D,
473 				.topic_type = MQTT_SN_FLAGS_TOPICID_TYPE_PREDEF,
474 				.topic.topic_id = 0x1234
475 			}
476 		}
477 	},
478 	{
479 		MQTT_SN_ENCODE_TEST(pingreq),
480 		.p = {
481 			.type = MQTT_SN_MSG_TYPE_PINGREQ,
482 			.params.pingreq = {
483 				.client_id = MQTT_SN_DATA_STRING_LITERAL("zephyrclient")
484 			}
485 		}
486 	},
487 	{
488 		MQTT_SN_ENCODE_TEST(pingresp),
489 		.p = {
490 			.type = MQTT_SN_MSG_TYPE_PINGRESP
491 		}
492 	},
493 	{
494 		MQTT_SN_ENCODE_TEST(disconnect),
495 		.p = {
496 			.type = MQTT_SN_MSG_TYPE_DISCONNECT,
497 			.params.disconnect = {
498 				.duration = 10000
499 			}
500 		}
501 	},
502 	{
503 		MQTT_SN_ENCODE_TEST(willtopicupd),
504 		.p = {
505 			.type = MQTT_SN_MSG_TYPE_WILLTOPICUPD,
506 			.params.willtopicupd = {
507 				.qos = MQTT_SN_QOS_0,
508 				.retain = true,
509 				.topic = MQTT_SN_DATA_STRING_LITERAL("/zephyr")
510 			}
511 		}
512 	},
513 	{
514 		MQTT_SN_ENCODE_TEST(willmsgupd),
515 		.p = {
516 			.type = MQTT_SN_MSG_TYPE_WILLMSGUPD,
517 			.params.willmsgupd = {
518 				.msg = MQTT_SN_DATA_STRING_LITERAL("mywill")
519 			}
520 		}
521 	}
522 };
523 
ZTEST(mqtt_sn_packet,test_mqtt_packet_encode)524 static ZTEST(mqtt_sn_packet, test_mqtt_packet_encode)
525 {
526 	NET_BUF_SIMPLE_DEFINE(msg, 255);
527 	int err;
528 
529 	for (size_t i = 0; i < ARRAY_SIZE(encode_tests); i++) {
530 		net_buf_simple_reset(&msg);
531 		TC_PRINT("%s - test %zu: %s\n", __func__, i, encode_tests[i].name);
532 
533 		err = mqtt_sn_encode_msg(&msg, &encode_tests[i].p);
534 		zassert_equal(err, 0, "Unexpected error");
535 		LOG_HEXDUMP_DBG(encode_tests[i].expected, encode_tests[i].expectedsz,
536 				"Expected data");
537 		LOG_HEXDUMP_DBG(msg.data, msg.len, "Encoded data");
538 		zassert_equal(msg.len, encode_tests[i].expectedsz,
539 			      "Unexpected data size %zu (%zu)", msg.len,
540 			      encode_tests[i].expectedsz);
541 		zassert_mem_equal(encode_tests[i].expected, msg.data, msg.len,
542 				  "Bad encoded message");
543 	}
544 }
545 
546 ZTEST_SUITE(mqtt_sn_packet, NULL, NULL, NULL, NULL, NULL);
547