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 
104 /* Callback to handle descriptor field */
105 static transport_descriptor_cb descriptor_cb;
106 
frag_transport_package_callback(uint8_t port,uint8_t flags,int16_t rssi,int8_t snr,uint8_t len,const uint8_t * rx_buf)107 static void frag_transport_package_callback(uint8_t port, uint8_t flags, int16_t rssi, int8_t snr,
108 					    uint8_t len, const uint8_t *rx_buf)
109 {
110 	uint8_t tx_buf[FRAG_TRANSPORT_MAX_CMDS_PER_PACKAGE * FRAG_TRANSPORT_MAX_ANS_LEN];
111 	uint8_t tx_pos = 0;
112 	uint8_t rx_pos = 0;
113 	int ans_delay = 0;
114 	int decoder_process_status;
115 
116 	__ASSERT(port == LORAWAN_PORT_FRAG_TRANSPORT, "Wrong port %d", port);
117 
118 	while (rx_pos < len) {
119 		uint8_t command_id = rx_buf[rx_pos++];
120 
121 		if (sizeof(tx_buf) - tx_pos < FRAG_TRANSPORT_MAX_ANS_LEN) {
122 			LOG_ERR("insufficient tx_buf size, some requests discarded");
123 			break;
124 		}
125 
126 		switch (command_id) {
127 		case FRAG_TRANSPORT_CMD_PKG_VERSION:
128 			tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_PKG_VERSION;
129 			tx_buf[tx_pos++] = LORAWAN_PACKAGE_ID_FRAG_TRANSPORT_BLOCK;
130 			tx_buf[tx_pos++] = FRAG_TRANSPORT_VERSION;
131 			break;
132 		case FRAG_TRANSPORT_CMD_FRAG_STATUS: {
133 			uint8_t frag_status = rx_buf[rx_pos++] & 0x07;
134 			uint8_t participants = frag_status & 0x01;
135 			uint8_t index = frag_status >> 1;
136 
137 			LOG_DBG("FragSessionStatusReq index %d, participants: %u", index,
138 				participants);
139 
140 			uint8_t missing_frag = CLAMP(ctx.nb_frag - ctx.nb_frag_received, 0, 255);
141 
142 			uint8_t memory_error = 0;
143 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
144 			FragDecoderStatus_t decoder_status = FragDecoderGetStatus();
145 			memory_error = decoder_status.MatrixError;
146 #endif
147 
148 			if (participants == 1 || missing_frag > 0) {
149 				tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_FRAG_STATUS;
150 				tx_buf[tx_pos++] = ctx.nb_frag_received & 0xFF;
151 				tx_buf[tx_pos++] =
152 					(index << 6) | ((ctx.nb_frag_received >> 8) & 0x3F);
153 				tx_buf[tx_pos++] = missing_frag;
154 				tx_buf[tx_pos++] = memory_error & 0x01;
155 
156 				ans_delay = sys_rand32_get() % (1U << (ctx.block_ack_delay + 4));
157 
158 				LOG_DBG("FragSessionStatusAns index %d, NbFragReceived: %u, "
159 					"MissingFrag: %u, MemoryError: %u, delay: %d",
160 					index, ctx.nb_frag_received, missing_frag, memory_error,
161 					ans_delay);
162 			}
163 			break;
164 		}
165 		case FRAG_TRANSPORT_CMD_FRAG_SESSION_SETUP: {
166 			uint8_t frag_session = rx_buf[rx_pos++] & 0x3F;
167 			uint8_t index = frag_session >> 4;
168 			uint8_t status = index << 6;
169 
170 			if (!ctx.is_active || ctx.frag_index == index) {
171 				ctx.frag_session = frag_session;
172 				ctx.nb_frag_received = 0;
173 
174 				ctx.nb_frag = sys_get_le16(rx_buf + rx_pos);
175 				rx_pos += sizeof(uint16_t);
176 
177 				ctx.frag_size = rx_buf[rx_pos++];
178 				ctx.control = rx_buf[rx_pos++];
179 				ctx.padding = rx_buf[rx_pos++];
180 
181 				ctx.descriptor = sys_get_le32(rx_buf + rx_pos);
182 				rx_pos += sizeof(uint32_t);
183 
184 				LOG_INF("FragSessionSetupReq index %d, nb_frag: %u, frag_size: %u, "
185 					"padding: %u, control: 0x%x, descriptor: 0x%.8x",
186 					index, ctx.nb_frag, ctx.frag_size, ctx.padding, ctx.control,
187 					ctx.descriptor);
188 			} else {
189 				/* FragIndex unsupported */
190 				status |= BIT(2);
191 
192 				LOG_WRN("FragSessionSetupReq failed. Session %u still active",
193 					ctx.frag_index);
194 			}
195 
196 			if (ctx.frag_algo > 0) {
197 				/* FragAlgo unsupported */
198 				status |= BIT(0);
199 			}
200 
201 			if (ctx.nb_frag > FRAG_MAX_NB || ctx.frag_size > FRAG_MAX_SIZE) {
202 				/* Not enough memory */
203 				status |= BIT(1);
204 			}
205 
206 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
207 			if (ctx.nb_frag * ctx.frag_size > FragDecoderGetMaxFileSize()) {
208 				/* Not enough memory */
209 				status |= BIT(1);
210 			}
211 #endif
212 
213 			if (descriptor_cb != NULL) {
214 				int rc = descriptor_cb(ctx.descriptor);
215 
216 				if (rc < 0) {
217 					/* Wrong Descriptor */
218 					status |= BIT(3);
219 				}
220 			}
221 
222 			if ((status & 0x1F) == 0) {
223 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
224 				/*
225 				 * Assign callbacks after initialization to prevent the FragDecoder
226 				 * from writing byte-wise 0xFF to the entire flash. Instead, erase
227 				 * flash properly with own implementation.
228 				 */
229 				ctx.decoder_callbacks.FragDecoderWrite = NULL;
230 				ctx.decoder_callbacks.FragDecoderRead = NULL;
231 
232 				FragDecoderInit(ctx.nb_frag, ctx.frag_size, &ctx.decoder_callbacks);
233 
234 				ctx.decoder_callbacks.FragDecoderWrite = frag_flash_write;
235 				ctx.decoder_callbacks.FragDecoderRead = frag_flash_read;
236 #elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
237 				frag_dec_init(&ctx.decoder, ctx.nb_frag, ctx.frag_size);
238 #endif
239 				frag_flash_init(ctx.frag_size);
240 				ctx.is_active = true;
241 			}
242 
243 			tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_FRAG_SESSION_SETUP;
244 			tx_buf[tx_pos++] = status;
245 			break;
246 		}
247 		case FRAG_TRANSPORT_CMD_FRAG_SESSION_DELETE: {
248 			uint8_t index = rx_buf[rx_pos++] & 0x03;
249 			uint8_t status = 0x00;
250 
251 			status |= index;
252 			if (!ctx.is_active || ctx.frag_index != index) {
253 				/* Session does not exist */
254 				status |= BIT(2);
255 			} else {
256 				ctx.is_active = false;
257 			}
258 
259 			tx_buf[tx_pos++] = FRAG_TRANSPORT_CMD_FRAG_SESSION_DELETE;
260 			tx_buf[tx_pos++] = status;
261 			break;
262 		}
263 		case FRAG_TRANSPORT_CMD_DATA_FRAGMENT: {
264 			ctx.nb_frag_received++;
265 
266 			uint16_t frag_index_n = sys_get_le16(rx_buf + rx_pos);
267 
268 			rx_pos += 2;
269 
270 			uint16_t frag_counter = frag_index_n & 0x3FFF;
271 			uint8_t index = (frag_index_n >> 14) & 0x03;
272 
273 			if (!ctx.is_active || index != ctx.frag_index) {
274 				LOG_DBG("DataFragment received for inactive session %u", index);
275 				break;
276 			}
277 
278 			if (frag_counter > ctx.nb_frag) {
279 				/* Additional fragments have to be cached in RAM for recovery
280 				 * algorithm.
281 				 */
282 				frag_flash_use_cache();
283 			}
284 
285 #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
286 			decoder_process_status = FragDecoderProcess(
287 				frag_counter, (uint8_t *)&rx_buf[rx_pos]);
288 #elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
289 			decoder_process_status = frag_dec(
290 				&ctx.decoder, frag_counter, &rx_buf[rx_pos], ctx.frag_size);
291 #endif
292 
293 			LOG_INF("DataFragment %u of %u (%u lost), session: %u, decoder result: %d",
294 				frag_counter, ctx.nb_frag, frag_counter - ctx.nb_frag_received,
295 				index, decoder_process_status);
296 
297 			if (decoder_process_status >= 0) {
298 				/* Positive status corresponds to number of lost (but recovered)
299 				 * fragments. Value >= 0 means the transport is done.
300 				 */
301 				frag_flash_finish();
302 
303 				LOG_INF("Frag Transport finished successfully");
304 
305 				if (finished_cb != NULL) {
306 					finished_cb();
307 				}
308 
309 				/* avoid processing further fragments */
310 				ctx.is_active = false;
311 			}
312 
313 			rx_pos += ctx.frag_size;
314 			break;
315 		}
316 		default:
317 			return;
318 		}
319 	}
320 
321 	if (tx_pos > 0) {
322 		lorawan_services_schedule_uplink(LORAWAN_PORT_FRAG_TRANSPORT, tx_buf, tx_pos,
323 						 ans_delay);
324 	}
325 }
326 
lorawan_frag_transport_register_descriptor_callback(transport_descriptor_cb cb)327 void lorawan_frag_transport_register_descriptor_callback(transport_descriptor_cb cb)
328 {
329 	descriptor_cb = cb;
330 }
331 
332 static struct lorawan_downlink_cb downlink_cb = {
333 	.port = (uint8_t)LORAWAN_PORT_FRAG_TRANSPORT,
334 	.cb = frag_transport_package_callback,
335 };
336 
lorawan_frag_transport_run(void (* transport_finished_cb)(void))337 int lorawan_frag_transport_run(void (*transport_finished_cb)(void))
338 {
339 	finished_cb = transport_finished_cb;
340 
341 	lorawan_register_downlink_callback(&downlink_cb);
342 
343 	return 0;
344 }
345