1 /*
2  * Copyright (c) 2024 A Labs GmbH
3  * Copyright (c) 2024 tado GmbH
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <zephyr/device.h>
9 #include <zephyr/kernel.h>
10 #include <zephyr/lorawan/lorawan.h>
11 #include <zephyr/lorawan/emul.h>
12 #include <zephyr/random/random.h>
13 #include <zephyr/sys/util.h>
14 #include <zephyr/storage/flash_map.h>
15 #include <zephyr/ztest.h>
16 
17 #include "frag_encoder.h"
18 
19 #define FRAG_SIZE     CONFIG_LORAWAN_FRAG_TRANSPORT_MAX_FRAG_SIZE
20 #define FIRMWARE_SIZE (FRAG_SIZE * 100 + 1) /* not divisible by frag size to test padding */
21 #define UNCODED_FRAGS (DIV_ROUND_UP(FIRMWARE_SIZE, FRAG_SIZE))
22 #define REDUNDANT_FRAGS                                                                            \
23 	(DIV_ROUND_UP(UNCODED_FRAGS * CONFIG_LORAWAN_FRAG_TRANSPORT_MAX_REDUNDANCY, 100))
24 #define PADDING (UNCODED_FRAGS * FRAG_SIZE - FIRMWARE_SIZE)
25 
26 #define CMD_FRAG_SESSION_SETUP (0x02)
27 #define CMD_DATA_FRAGMENT      (0x08)
28 #define FRAG_TRANSPORT_PORT    (201)
29 #define FRAG_SESSION_INDEX     (1)
30 
31 #define TARGET_IMAGE_AREA FIXED_PARTITION_ID(slot1_partition)
32 
33 /* below array would normally hold the actual firmware binary */
34 static uint8_t fw_uncoded[FIRMWARE_SIZE];
35 
36 /* enough space for redundancy of up to 100% */
37 static uint8_t fw_coded[(UNCODED_FRAGS + REDUNDANT_FRAGS) * FRAG_SIZE];
38 
39 static const struct flash_area *fa;
40 
41 static struct k_sem fuota_finished_sem;
42 
fuota_finished(void)43 static void fuota_finished(void)
44 {
45 	k_sem_give(&fuota_finished_sem);
46 }
47 
48 uint8_t frag_session_setup_req[] = {
49 	CMD_FRAG_SESSION_SETUP,
50 	0x1f,
51 	UNCODED_FRAGS & 0xFF,
52 	(UNCODED_FRAGS >> 8) & 0xFF,
53 	FRAG_SIZE,
54 	0x01,
55 	PADDING,
56 	0x00,
57 	0x00,
58 	0x00,
59 	0x00,
60 };
61 
run_test(size_t lost_packets,bool expected_success)62 static void run_test(size_t lost_packets, bool expected_success)
63 {
64 	uint8_t buf[256]; /* maximum size of one LoRaWAN message */
65 	int ret;
66 	size_t num_packets = sizeof(fw_coded) / FRAG_SIZE;
67 	size_t packets_to_lose[num_packets];
68 	size_t tmp;
69 	bool skip;
70 
71 	for (size_t i = 0; i < num_packets; i++) {
72 		packets_to_lose[i] = i;
73 	}
74 	/* Shuffle array */
75 	for (size_t i = num_packets - 1; i > 0; i--) {
76 		int j = sys_rand32_get() % (i + 1);
77 
78 		if (i != j) {
79 			tmp = packets_to_lose[j];
80 			packets_to_lose[j] = packets_to_lose[i];
81 			packets_to_lose[i] = tmp;
82 		}
83 	}
84 
85 	k_sem_reset(&fuota_finished_sem);
86 	lorawan_emul_send_downlink(FRAG_TRANSPORT_PORT, false, 0, 0, sizeof(frag_session_setup_req),
87 				   frag_session_setup_req);
88 
89 	for (size_t i = 0; i < num_packets; i++) {
90 		skip = false;
91 		for (int j = 0; j < lost_packets; j++) {
92 			if (packets_to_lose[j] == i) {
93 				skip = true;
94 				break;
95 			}
96 		}
97 		if (skip) {
98 			/* lose packet */
99 			continue;
100 		}
101 		buf[0] = CMD_DATA_FRAGMENT;
102 		buf[1] = (i + 1) & 0xFF;
103 		buf[2] = (FRAG_SESSION_INDEX << 6) | ((i + 1) >> 8);
104 		memcpy(buf + 3, fw_coded + i * FRAG_SIZE, FRAG_SIZE);
105 		lorawan_emul_send_downlink(FRAG_TRANSPORT_PORT, false, 0, 0, FRAG_SIZE + 3, buf);
106 	}
107 
108 	ret = k_sem_take(&fuota_finished_sem, K_MSEC(100));
109 	if (expected_success) {
110 		zassert_equal(ret, 0, "FUOTA finish timed out");
111 		for (int i = 0; i < UNCODED_FRAGS; i++) {
112 			size_t num_bytes =
113 				(i == UNCODED_FRAGS - 1) ? (FRAG_SIZE - PADDING) : FRAG_SIZE;
114 
115 			flash_area_read(fa, i * FRAG_SIZE, buf, num_bytes);
116 			zassert_mem_equal(buf, fw_coded + i * FRAG_SIZE, num_bytes,
117 					  "fragment %d invalid", i + 1);
118 		}
119 	} else {
120 		zassert_not_equal(ret, 0, "FUOTA should have failed");
121 	}
122 }
123 
ZTEST(frag_decoder,test_frag_transport_lose_none)124 ZTEST(frag_decoder, test_frag_transport_lose_none)
125 {
126 	run_test(0, true);
127 }
128 
ZTEST(frag_decoder,test_frag_transport_lose_one)129 ZTEST(frag_decoder, test_frag_transport_lose_one)
130 {
131 	run_test(1, true);
132 }
133 
ZTEST(frag_decoder,test_frag_transport_lose_close_to_max_redundancy)134 ZTEST(frag_decoder, test_frag_transport_lose_close_to_max_redundancy)
135 {
136 	run_test(REDUNDANT_FRAGS * 0.95, true);
137 }
138 
ZTEST(frag_decoder,test_frag_transport_lose_more_than_max_redundancy)139 ZTEST(frag_decoder, test_frag_transport_lose_more_than_max_redundancy)
140 {
141 	run_test(REDUNDANT_FRAGS + 1, false);
142 }
143 
frag_decoder_setup(void)144 static void *frag_decoder_setup(void)
145 {
146 	const struct device *lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
147 	struct lorawan_join_config join_cfg = {0};
148 	int ret;
149 
150 	/* populate firmware image with random data */
151 	sys_rand_get(fw_uncoded, sizeof(fw_uncoded));
152 
153 	/* create coded data (including redundant fragments) from firmware image */
154 	ret = lorawan_frag_encoder(fw_uncoded, sizeof(fw_uncoded), fw_coded, sizeof(fw_coded),
155 				   FRAG_SIZE, REDUNDANT_FRAGS);
156 	zassert_equal(ret, 0, "creating coded data failed: %d", ret);
157 
158 	k_sem_init(&fuota_finished_sem, 0, 1);
159 
160 	ret = flash_area_open(TARGET_IMAGE_AREA, &fa);
161 	zassert_equal(ret, 0, "opening flash area failed: %d", ret);
162 
163 	zassert_true(device_is_ready(lora_dev), "LoRa device not ready");
164 
165 	ret = lorawan_start();
166 	zassert_equal(ret, 0, "lorawan_start failed: %d", ret);
167 
168 	ret = lorawan_join(&join_cfg);
169 	zassert_equal(ret, 0, "lorawan_join failed: %d", ret);
170 
171 	lorawan_frag_transport_run(fuota_finished);
172 
173 	return NULL;
174 }
175 
176 ZTEST_SUITE(frag_decoder, NULL, frag_decoder_setup, NULL, NULL, NULL);
177