1 /*
2 * Copyright (c) 2022-2024 Libre Solar Technologies GmbH
3 * Copyright (c) 2022-2024 tado GmbH
4 *
5 * Parts of this implementation were inspired by LmhpFragmentation.c from the
6 * LoRaMac-node firmware repository https://github.com/Lora-net/LoRaMac-node
7 * written by Miguel Luis (Semtech).
8 *
9 * SPDX-License-Identifier: Apache-2.0
10 */
11
12 #include "frag_flash.h"
13 #include "lorawan_services.h"
14
15 #include <LoRaMac.h>
16 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
17 #include <FragDecoder.h>
18 #elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
19 #include "frag_decoder_lowmem.h"
20 #endif
21
22 #include <zephyr/lorawan/lorawan.h>
23 #include <zephyr/logging/log.h>
24 #include <zephyr/random/random.h>
25 #include <zephyr/sys/byteorder.h>
26
27 LOG_MODULE_REGISTER(lorawan_frag_transport, CONFIG_LORAWAN_SERVICES_LOG_LEVEL);
28
29 /**
30 * Version of LoRaWAN Fragmented Data Block Transport Specification
31 *
32 * This implementation only supports TS004-1.0.0.
33 */
34 #define FRAG_TRANSPORT_VERSION 1
35
36 /**
37 * Maximum expected number of frag transport commands per packet
38 *
39 * The standard states "A message MAY carry more than one command". Even though this was not
40 * observed during testing, space for up to 3 packages is reserved.
41 */
42 #define FRAG_TRANSPORT_MAX_CMDS_PER_PACKAGE 3
43
44 /* maximum length of frag_transport answers */
45 #define FRAG_TRANSPORT_MAX_ANS_LEN 5
46
47 enum frag_transport_commands {
48 FRAG_TRANSPORT_CMD_PKG_VERSION = 0x00,
49 FRAG_TRANSPORT_CMD_FRAG_STATUS = 0x01,
50 FRAG_TRANSPORT_CMD_FRAG_SESSION_SETUP = 0x02,
51 FRAG_TRANSPORT_CMD_FRAG_SESSION_DELETE = 0x03,
52 FRAG_TRANSPORT_CMD_DATA_FRAGMENT = 0x08,
53 };
54
55 struct frag_transport_context {
56 /** Stores if a session is active */
57 bool is_active;
58 union {
59 uint8_t frag_session;
60 struct {
61 /** Multicast groups allowed to input to this frag session */
62 uint8_t mc_group_bit_mask: 4;
63 /** Identifies this session */
64 uint8_t frag_index: 2;
65 };
66 };
67 /** Number of fragments of the data block for this session, max. 2^14-1 */
68 uint16_t nb_frag;
69 /** Number of fragments received in this session (including coded, uncoded and repeated) */
70 uint16_t nb_frag_received;
71 /** Size of each fragment in octets */
72 uint8_t frag_size;
73 union {
74 uint8_t control;
75 struct {
76 /** Random delay for some responses between 0 and 2^(BlockAckDelay + 4) */
77 uint8_t block_ack_delay: 3;
78 /** Used fragmentation algorithm (0 for forward error correction) */
79 uint8_t frag_algo: 3;
80 };
81 };
82 /** Padding in the last fragment if total size is not a multiple of frag_size */
83 uint8_t padding;
84 /** Application-specific descriptor for the data block, e.g. firmware version */
85 uint32_t descriptor;
86
87 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
88 /* variables required for FragDecoder.h */
89 FragDecoderCallbacks_t decoder_callbacks;
90 #elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
91 struct frag_decoder decoder;
92 #endif
93 };
94
95 /*
96 * The frag decoder is a singleton, so we can only have one ongoing frag session at a time, even
97 * though the standard allows up to 4 sessions
98 */
99 static struct frag_transport_context ctx;
100
101 /* Callback for notification of finished firmware transfer */
102 static void (*finished_cb)(void);
103
frag_transport_package_callback(uint8_t port,uint8_t flags,int16_t rssi,int8_t snr,uint8_t len,const uint8_t * rx_buf)104 static void frag_transport_package_callback(uint8_t port, uint8_t flags, int16_t rssi, int8_t snr,
105 uint8_t len, const uint8_t *rx_buf)
106 {
107 uint8_t tx_buf[FRAG_TRANSPORT_MAX_CMDS_PER_PACKAGE * FRAG_TRANSPORT_MAX_ANS_LEN];
108 uint8_t tx_pos = 0;
109 uint8_t rx_pos = 0;
110 int ans_delay = 0;
111 int decoder_process_status;
112
113 __ASSERT(port == LORAWAN_PORT_FRAG_TRANSPORT, "Wrong port %d", port);
114
115 while (rx_pos < len) {
116 uint8_t command_id = rx_buf[rx_pos++];
117
118 if (sizeof(tx_buf) - tx_pos < FRAG_TRANSPORT_MAX_ANS_LEN) {
119 LOG_ERR("insufficient tx_buf size, some requests discarded");
120 break;
121 }
122
123 switch (command_id) {
124 case FRAG_TRANSPORT_CMD_PKG_VERSION:
125 tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_PKG_VERSION;
126 tx_buf[tx_pos++] = LORAWAN_PACKAGE_ID_FRAG_TRANSPORT_BLOCK;
127 tx_buf[tx_pos++] = FRAG_TRANSPORT_VERSION;
128 break;
129 case FRAG_TRANSPORT_CMD_FRAG_STATUS: {
130 uint8_t frag_status = rx_buf[rx_pos++] & 0x07;
131 uint8_t participants = frag_status & 0x01;
132 uint8_t index = frag_status >> 1;
133
134 LOG_DBG("FragSessionStatusReq index %d, participants: %u", index,
135 participants);
136
137 uint8_t missing_frag = CLAMP(ctx.nb_frag - ctx.nb_frag_received, 0, 255);
138
139 uint8_t memory_error = 0;
140 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
141 FragDecoderStatus_t decoder_status = FragDecoderGetStatus();
142 memory_error = decoder_status.MatrixError;
143 #endif
144
145 if (participants == 1 || missing_frag > 0) {
146 tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_FRAG_STATUS;
147 tx_buf[tx_pos++] = ctx.nb_frag_received & 0xFF;
148 tx_buf[tx_pos++] =
149 (index << 6) | ((ctx.nb_frag_received >> 8) & 0x3F);
150 tx_buf[tx_pos++] = missing_frag;
151 tx_buf[tx_pos++] = memory_error & 0x01;
152
153 ans_delay = sys_rand32_get() % (1U << (ctx.block_ack_delay + 4));
154
155 LOG_DBG("FragSessionStatusAns index %d, NbFragReceived: %u, "
156 "MissingFrag: %u, MemoryError: %u, delay: %d",
157 index, ctx.nb_frag_received, missing_frag, memory_error,
158 ans_delay);
159 }
160 break;
161 }
162 case FRAG_TRANSPORT_CMD_FRAG_SESSION_SETUP: {
163 uint8_t frag_session = rx_buf[rx_pos++] & 0x3F;
164 uint8_t index = frag_session >> 4;
165 uint8_t status = index << 6;
166
167 if (!ctx.is_active || ctx.frag_index == index) {
168 ctx.frag_session = frag_session;
169 ctx.nb_frag_received = 0;
170
171 ctx.nb_frag = sys_get_le16(rx_buf + rx_pos);
172 rx_pos += sizeof(uint16_t);
173
174 ctx.frag_size = rx_buf[rx_pos++];
175 ctx.control = rx_buf[rx_pos++];
176 ctx.padding = rx_buf[rx_pos++];
177
178 ctx.descriptor = sys_get_le32(rx_buf + rx_pos);
179 rx_pos += sizeof(uint32_t);
180
181 LOG_INF("FragSessionSetupReq index %d, nb_frag: %u, frag_size: %u, "
182 "padding: %u, control: 0x%x, descriptor: 0x%.8x",
183 index, ctx.nb_frag, ctx.frag_size, ctx.padding, ctx.control,
184 ctx.descriptor);
185 } else {
186 /* FragIndex unsupported */
187 status |= BIT(2);
188
189 LOG_WRN("FragSessionSetupReq failed. Session %u still active",
190 ctx.frag_index);
191 }
192
193 if (ctx.frag_algo > 0) {
194 /* FragAlgo unsupported */
195 status |= BIT(0);
196 }
197
198 if (ctx.nb_frag > FRAG_MAX_NB || ctx.frag_size > FRAG_MAX_SIZE) {
199 /* Not enough memory */
200 status |= BIT(1);
201 }
202
203 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
204 if (ctx.nb_frag * ctx.frag_size > FragDecoderGetMaxFileSize()) {
205 /* Not enough memory */
206 status |= BIT(1);
207 }
208 #endif
209
210 /* Descriptor not used: Ignore Wrong Descriptor error */
211
212 if ((status & 0x1F) == 0) {
213 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
214 /*
215 * Assign callbacks after initialization to prevent the FragDecoder
216 * from writing byte-wise 0xFF to the entire flash. Instead, erase
217 * flash properly with own implementation.
218 */
219 ctx.decoder_callbacks.FragDecoderWrite = NULL;
220 ctx.decoder_callbacks.FragDecoderRead = NULL;
221
222 FragDecoderInit(ctx.nb_frag, ctx.frag_size, &ctx.decoder_callbacks);
223
224 ctx.decoder_callbacks.FragDecoderWrite = frag_flash_write;
225 ctx.decoder_callbacks.FragDecoderRead = frag_flash_read;
226 #elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
227 frag_dec_init(&ctx.decoder, ctx.nb_frag, ctx.frag_size);
228 #endif
229 frag_flash_init(ctx.frag_size);
230 ctx.is_active = true;
231 }
232
233 tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_FRAG_SESSION_SETUP;
234 tx_buf[tx_pos++] = status;
235 break;
236 }
237 case FRAG_TRANSPORT_CMD_FRAG_SESSION_DELETE: {
238 uint8_t index = rx_buf[rx_pos++] & 0x03;
239 uint8_t status = 0x00;
240
241 status |= index;
242 if (!ctx.is_active || ctx.frag_index != index) {
243 /* Session does not exist */
244 status |= BIT(2);
245 } else {
246 ctx.is_active = false;
247 }
248
249 tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_FRAG_SESSION_DELETE;
250 tx_buf[tx_pos++] = status;
251 break;
252 }
253 case FRAG_TRANSPORT_CMD_DATA_FRAGMENT: {
254 ctx.nb_frag_received++;
255
256 uint16_t frag_index_n = sys_get_le16(rx_buf + rx_pos);
257
258 rx_pos += 2;
259
260 uint16_t frag_counter = frag_index_n & 0x3FFF;
261 uint8_t index = (frag_index_n >> 14) & 0x03;
262
263 if (!ctx.is_active || index != ctx.frag_index) {
264 LOG_DBG("DataFragment received for inactive session %u", index);
265 break;
266 }
267
268 if (frag_counter > ctx.nb_frag) {
269 /* Additional fragments have to be cached in RAM for recovery
270 * algorithm.
271 */
272 frag_flash_use_cache();
273 }
274
275 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
276 decoder_process_status = FragDecoderProcess(
277 frag_counter, (uint8_t *)&rx_buf[rx_pos]);
278 #elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
279 decoder_process_status = frag_dec(
280 &ctx.decoder, frag_counter, &rx_buf[rx_pos], ctx.frag_size);
281 #endif
282
283 LOG_INF("DataFragment %u of %u (%u lost), session: %u, decoder result: %d",
284 frag_counter, ctx.nb_frag, frag_counter - ctx.nb_frag_received,
285 index, decoder_process_status);
286
287 if (decoder_process_status >= 0) {
288 /* Positive status corresponds to number of lost (but recovered)
289 * fragments. Value >= 0 means the transport is done.
290 */
291 frag_flash_finish();
292
293 LOG_INF("Frag Transport finished successfully");
294
295 if (finished_cb != NULL) {
296 finished_cb();
297 }
298
299 /* avoid processing further fragments */
300 ctx.is_active = false;
301 }
302
303 rx_pos += ctx.frag_size;
304 break;
305 }
306 default:
307 return;
308 }
309 }
310
311 if (tx_pos > 0) {
312 lorawan_services_schedule_uplink(LORAWAN_PORT_FRAG_TRANSPORT, tx_buf, tx_pos,
313 ans_delay);
314 }
315 }
316
317 static struct lorawan_downlink_cb downlink_cb = {
318 .port = (uint8_t)LORAWAN_PORT_FRAG_TRANSPORT,
319 .cb = frag_transport_package_callback,
320 };
321
lorawan_frag_transport_run(void (* transport_finished_cb)(void))322 int lorawan_frag_transport_run(void (*transport_finished_cb)(void))
323 {
324 finished_cb = transport_finished_cb;
325
326 lorawan_register_downlink_callback(&downlink_cb);
327
328 return 0;
329 }
330