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