1 /*!
2 * \file LmhpFragmentation.c
3 *
4 * \brief Implements the LoRa-Alliance fragmented data block transport package
5 * Specification: https://lora-alliance.org/sites/default/files/2018-09/fragmented_data_block_transport_v1.0.0.pdf
6 *
7 * \copyright Revised BSD License, see section \ref LICENSE.
8 *
9 * \code
10 * ______ _
11 * / _____) _ | |
12 * ( (____ _____ ____ _| |_ _____ ____| |__
13 * \____ \| ___ | (_ _) ___ |/ ___) _ \
14 * _____) ) ____| | | || |_| ____( (___| | | |
15 * (______/|_____)_|_|_| \__)_____)\____)_| |_|
16 * (C)2013-2018 Semtech
17 *
18 * \endcode
19 *
20 * \author Miguel Luis ( Semtech )
21 */
22 #include "LmHandler.h"
23 #include "LmhpFragmentation.h"
24 #include "FragDecoder.h"
25
26 /*!
27 * LoRaWAN Application Layer Fragmented Data Block Transport Specification
28 */
29 #define FRAGMENTATION_PORT 201
30
31 #define FRAGMENTATION_ID 3
32 #define FRAGMENTATION_VERSION 1
33
34 #define FRAGMENTATION_MAX_SESSIONS 4
35
36 // Fragmentation Tx delay state
37 typedef enum LmhpFragmentationTxDelayStates_e
38 {
39 // Tx delay in idle state.
40 FRAGMENTATION_TX_DELAY_STATE_IDLE,
41 // Tx delay to be started.
42 FRAGMENTATION_TX_DELAY_STATE_START,
43 // Tx delay to be stopped.
44 FRAGMENTATION_TX_DELAY_STATE_STOP,
45 }LmhpFragmentationTxDelayStates_t;
46
47 /*!
48 * Package current context
49 */
50 typedef struct LmhpFragmentationState_s
51 {
52 bool Initialized;
53 bool IsTxPending;
54 LmhpFragmentationTxDelayStates_t TxDelayState;
55 uint8_t DataBufferMaxSize;
56 uint8_t *DataBuffer;
57 uint8_t *file;
58 }LmhpFragmentationState_t;
59
60 typedef enum LmhpFragmentationMoteCmd_e
61 {
62 FRAGMENTATION_PKG_VERSION_ANS = 0x00,
63 FRAGMENTATION_FRAG_STATUS_ANS = 0x01,
64 FRAGMENTATION_FRAG_SESSION_SETUP_ANS = 0x02,
65 FRAGMENTATION_FRAG_SESSION_DELETE_ANS = 0x03,
66 }LmhpFragmentationMoteCmd_t;
67
68 typedef enum LmhpFragmentationSrvCmd_e
69 {
70 FRAGMENTATION_PKG_VERSION_REQ = 0x00,
71 FRAGMENTATION_FRAG_STATUS_REQ = 0x01,
72 FRAGMENTATION_FRAG_SESSION_SETUP_REQ = 0x02,
73 FRAGMENTATION_FRAG_SESSION_DELETE_REQ = 0x03,
74 FRAGMENTATION_DATA_FRAGMENT = 0x08,
75 }LmhpFragmentationSrvCmd_t;
76
77 /*!
78 * LoRaWAN fragmented data block transport handler parameters
79 */
80 static LmhpFragmentationParams_t* LmhpFragmentationParams;
81
82 /*!
83 * Initializes the package with provided parameters
84 *
85 * \param [IN] params Pointer to the package parameters
86 * \param [IN] dataBuffer Pointer to main application buffer
87 * \param [IN] dataBufferMaxSize Main application buffer maximum size
88 */
89 static void LmhpFragmentationInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize );
90
91 /*!
92 * Returns the current package initialization status.
93 *
94 * \retval status Package initialization status
95 * [true: Initialized, false: Not initialized]
96 */
97 static bool LmhpFragmentationIsInitialized( void );
98
99 /*!
100 * Returns if a package transmission is pending or not.
101 *
102 * \retval status Package transmission status
103 * [true: pending, false: Not pending]
104 */
105 static bool LmhpFragmentationIsTxPending( void );
106
107 /*!
108 * Processes the internal package events.
109 */
110 static void LmhpFragmentationProcess( void );
111
112 /*!
113 * Processes the MCPS Indication
114 *
115 * \param [IN] mcpsIndication MCPS indication primitive data
116 */
117 static void LmhpFragmentationOnMcpsIndication( McpsIndication_t *mcpsIndication );
118
119 static LmhpFragmentationState_t LmhpFragmentationState =
120 {
121 .Initialized = false,
122 .IsTxPending = false,
123 .TxDelayState = FRAGMENTATION_TX_DELAY_STATE_IDLE,
124 };
125
126 typedef struct FragGroupData_s
127 {
128 bool IsActive;
129 union
130 {
131 uint8_t Value;
132 struct
133 {
134 uint8_t McGroupBitMask: 4;
135 uint8_t FragIndex: 2;
136 uint8_t RFU: 2;
137 }Fields;
138 }FragSession;
139 uint16_t FragNb;
140 uint8_t FragSize;
141 union
142 {
143 uint8_t Value;
144 struct
145 {
146 uint8_t BlockAckDelay: 3;
147 uint8_t FragAlgo: 3;
148 uint8_t RFU: 2;
149 }Fields;
150 }Control;
151 uint8_t Padding;
152 uint32_t Descriptor;
153 }FragGroupData_t;
154
155 typedef struct FragSessionData_s
156 {
157 FragGroupData_t FragGroupData;
158 FragDecoderStatus_t FragDecoderStatus;
159 int32_t FragDecoderProcessStatus;
160 }FragSessionData_t;
161
162 FragSessionData_t FragSessionData[FRAGMENTATION_MAX_SESSIONS];
163
164 // Answer struct for the commands.
165 LmHandlerAppData_t DelayedReplyAppData;
166
167 static LmhPackage_t LmhpFragmentationPackage =
168 {
169 .Port = FRAGMENTATION_PORT,
170 .Init = LmhpFragmentationInit,
171 .IsInitialized = LmhpFragmentationIsInitialized,
172 .IsTxPending = LmhpFragmentationIsTxPending,
173 .Process = LmhpFragmentationProcess,
174 .OnMcpsConfirmProcess = NULL, // Not used in this package
175 .OnMcpsIndicationProcess = LmhpFragmentationOnMcpsIndication,
176 .OnMlmeConfirmProcess = NULL, // Not used in this package
177 .OnMlmeIndicationProcess = NULL, // Not used in this package
178 .OnMacMcpsRequest = NULL, // To be initialized by LmHandler
179 .OnMacMlmeRequest = NULL, // To be initialized by LmHandler
180 .OnJoinRequest = NULL, // To be initialized by LmHandler
181 .OnDeviceTimeRequest = NULL, // To be initialized by LmHandler
182 .OnSysTimeUpdate = NULL, // To be initialized by LmHandler
183 };
184
185 // Delay value.
186 static uint32_t TxDelayTime;
187
188 // Fragment Delay Timer struct
189 static TimerEvent_t FragmentTxDelayTimer;
190
191 /*!
192 * \brief Callback function for Fragment delay timer.
193 */
OnFragmentTxDelay(void * context)194 static void OnFragmentTxDelay( void* context )
195 {
196 // Stop the timer.
197 TimerStop( &FragmentTxDelayTimer );
198 // Set the state.
199 LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_STOP;
200 }
201
LmhpFragmentationPackageFactory(void)202 LmhPackage_t *LmhpFragmentationPackageFactory( void )
203 {
204 return &LmhpFragmentationPackage;
205 }
206
LmhpFragmentationInit(void * params,uint8_t * dataBuffer,uint8_t dataBufferMaxSize)207 static void LmhpFragmentationInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize )
208 {
209 if( ( params != NULL ) && ( dataBuffer != NULL ) )
210 {
211 LmhpFragmentationParams = ( LmhpFragmentationParams_t* )params;
212 LmhpFragmentationState.DataBuffer = dataBuffer;
213 LmhpFragmentationState.DataBufferMaxSize = dataBufferMaxSize;
214 LmhpFragmentationState.Initialized = true;
215 // Initialize Fragmentation delay time.
216 TxDelayTime = 0;
217 // Initialize Fragmentation delay timer.
218 TimerInit( &FragmentTxDelayTimer, OnFragmentTxDelay );
219 }
220 else
221 {
222 LmhpFragmentationParams = NULL;
223 LmhpFragmentationState.Initialized = false;
224 }
225 LmhpFragmentationState.IsTxPending = false;
226 }
227
LmhpFragmentationIsInitialized(void)228 static bool LmhpFragmentationIsInitialized( void )
229 {
230 return LmhpFragmentationState.Initialized;
231 }
232
LmhpFragmentationIsTxPending(void)233 static bool LmhpFragmentationIsTxPending( void )
234 {
235 return LmhpFragmentationState.IsTxPending;
236 }
237
LmhpFragmentationProcess(void)238 static void LmhpFragmentationProcess( void )
239 {
240 LmhpFragmentationTxDelayStates_t delayTimerState;
241
242 CRITICAL_SECTION_BEGIN( );
243 delayTimerState = LmhpFragmentationState.TxDelayState;
244 // Set the state to idle so that the other states are executed only when they are set
245 // in the appropriate functions.
246 LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_IDLE;
247 CRITICAL_SECTION_END( );
248
249 switch( delayTimerState )
250 {
251 case FRAGMENTATION_TX_DELAY_STATE_START:
252 // Set the timer with the initially calculated Delay value.
253 TimerSetValue( &FragmentTxDelayTimer, TxDelayTime );
254 // Start the timer.
255 TimerStart( &FragmentTxDelayTimer );
256 break;
257 case FRAGMENTATION_TX_DELAY_STATE_STOP:
258 // Send the reply.
259 LmHandlerSend( &DelayedReplyAppData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
260 break;
261 case FRAGMENTATION_TX_DELAY_STATE_IDLE:
262 // Intentional fall through
263 default:
264 // Nothing to do.
265 break;
266 }
267 }
268
LmhpFragmentationOnMcpsIndication(McpsIndication_t * mcpsIndication)269 static void LmhpFragmentationOnMcpsIndication( McpsIndication_t *mcpsIndication )
270 {
271 uint8_t cmdIndex = 0;
272 uint8_t dataBufferIndex = 0;
273 bool isAnswerDelayed = false;
274 // Answer struct for the commands.
275 LmHandlerAppData_t cmdReplyAppData;
276 // Co-efficient used to calculate delay.
277 uint8_t blockAckDelay = 0;
278
279 if( mcpsIndication->Port != FRAGMENTATION_PORT )
280 {
281 return;
282 }
283
284 while( cmdIndex < mcpsIndication->BufferSize )
285 {
286 switch( mcpsIndication->Buffer[cmdIndex++] )
287 {
288 case FRAGMENTATION_PKG_VERSION_REQ:
289 {
290 if( mcpsIndication->Multicast == 1 )
291 {
292 // Multicast channel. Don't process command.
293 break;
294 }
295 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_PKG_VERSION_ANS;
296 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_ID;
297 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_VERSION;
298 break;
299 }
300 case FRAGMENTATION_FRAG_STATUS_REQ:
301 {
302 uint8_t fragIndex = mcpsIndication->Buffer[cmdIndex++];
303 uint8_t participants = fragIndex & 0x01;
304
305 fragIndex >>= 1;
306 FragSessionData[fragIndex].FragDecoderStatus = FragDecoderGetStatus( );
307
308 if( ( participants == 1 ) ||
309 ( ( participants == 0 ) && ( FragSessionData[fragIndex].FragDecoderStatus.FragNbLost > 0 ) ) )
310 {
311 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_STATUS_ANS;
312 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.FragNbRx & 0xFF;
313 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = ( fragIndex << 6 ) |
314 ( ( FragSessionData[fragIndex].FragDecoderStatus.FragNbRx >> 8 ) & 0x3F );
315 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.FragNbLost;
316 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.MatrixError & 0x01;
317
318 // Fetch the co-efficient value required to calculate delay of that respective session.
319 blockAckDelay = FragSessionData[fragIndex].FragGroupData.Control.Fields.BlockAckDelay;
320 isAnswerDelayed = true;
321 }
322 break;
323 }
324 case FRAGMENTATION_FRAG_SESSION_SETUP_REQ:
325 {
326 if( mcpsIndication->Multicast == 1 )
327 {
328 // Multicast channel. Don't process command.
329 break;
330 }
331 FragSessionData_t fragSessionData;
332 uint8_t status = 0x00;
333
334 fragSessionData.FragGroupData.FragSession.Value = mcpsIndication->Buffer[cmdIndex++];
335
336 fragSessionData.FragGroupData.FragNb = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x00FF;
337 fragSessionData.FragGroupData.FragNb |= ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0xFF00;
338
339 fragSessionData.FragGroupData.FragSize = mcpsIndication->Buffer[cmdIndex++];
340
341 fragSessionData.FragGroupData.Control.Value = mcpsIndication->Buffer[cmdIndex++];
342
343 fragSessionData.FragGroupData.Padding = mcpsIndication->Buffer[cmdIndex++];
344
345 fragSessionData.FragGroupData.Descriptor = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF;
346 fragSessionData.FragGroupData.Descriptor += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00;
347 fragSessionData.FragGroupData.Descriptor += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000;
348 fragSessionData.FragGroupData.Descriptor += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000;
349
350 if( fragSessionData.FragGroupData.Control.Fields.FragAlgo > 0 )
351 {
352 status |= 0x01; // Encoding unsupported
353 }
354
355 #if( FRAG_DECODER_FILE_HANDLING_NEW_API == 1 )
356 if( ( fragSessionData.FragGroupData.FragNb > FRAG_MAX_NB ) ||
357 ( fragSessionData.FragGroupData.FragSize > FRAG_MAX_SIZE ) ||
358 ( ( fragSessionData.FragGroupData.FragNb * fragSessionData.FragGroupData.FragSize ) > FragDecoderGetMaxFileSize( ) ) )
359 {
360 status |= 0x02; // Not enough Memory
361 }
362 #else
363 if( ( fragSessionData.FragGroupData.FragNb > FRAG_MAX_NB ) ||
364 ( fragSessionData.FragGroupData.FragSize > FRAG_MAX_SIZE ) ||
365 ( ( fragSessionData.FragGroupData.FragNb * fragSessionData.FragGroupData.FragSize ) > LmhpFragmentationParams->BufferSize ) )
366 {
367 status |= 0x02; // Not enough Memory
368 }
369 #endif
370 status |= ( fragSessionData.FragGroupData.FragSession.Fields.FragIndex << 6 ) & 0xC0;
371 if( fragSessionData.FragGroupData.FragSession.Fields.FragIndex >= FRAGMENTATION_MAX_SESSIONS )
372 {
373 status |= 0x04; // FragSession index not supported
374 }
375
376 // Descriptor is not really defined in the specification
377 // Not clear how to handle this.
378 // Currently the descriptor is always correct
379 if( fragSessionData.FragGroupData.Descriptor != 0x01020304 )
380 {
381 //status |= 0x08; // Wrong Descriptor
382 }
383
384 if( ( status & 0x0F ) == 0 )
385 {
386 // The FragSessionSetup is accepted
387 fragSessionData.FragGroupData.IsActive = true;
388 fragSessionData.FragDecoderProcessStatus = FRAG_SESSION_ONGOING;
389 FragSessionData[fragSessionData.FragGroupData.FragSession.Fields.FragIndex] = fragSessionData;
390 #if( FRAG_DECODER_FILE_HANDLING_NEW_API == 1 )
391 FragDecoderInit( fragSessionData.FragGroupData.FragNb,
392 fragSessionData.FragGroupData.FragSize,
393 &LmhpFragmentationParams->DecoderCallbacks );
394 #else
395 FragDecoderInit( fragSessionData.FragGroupData.FragNb,
396 fragSessionData.FragGroupData.FragSize,
397 LmhpFragmentationParams->Buffer,
398 LmhpFragmentationParams->BufferSize );
399 #endif
400 }
401 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_SESSION_SETUP_ANS;
402 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = status;
403 isAnswerDelayed = false;
404 break;
405 }
406 case FRAGMENTATION_FRAG_SESSION_DELETE_REQ:
407 {
408 if( mcpsIndication->Multicast == 1 )
409 {
410 // Multicast channel. Don't process command.
411 break;
412 }
413 uint8_t status = 0x00;
414 uint8_t id = mcpsIndication->Buffer[cmdIndex++] & 0x03;
415
416 status |= id;
417 if( ( id >= FRAGMENTATION_MAX_SESSIONS ) || ( FragSessionData[id].FragGroupData.IsActive == false ) )
418 {
419 status |= 0x04; // Session does not exist
420 }
421 else
422 {
423 // Delete session
424 FragSessionData[id].FragGroupData.IsActive = false;
425 }
426 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_SESSION_DELETE_ANS;
427 LmhpFragmentationState.DataBuffer[dataBufferIndex++] = status;
428 isAnswerDelayed = false;
429 break;
430 }
431 case FRAGMENTATION_DATA_FRAGMENT:
432 {
433 uint8_t fragIndex = 0;
434 uint16_t fragCounter = 0;
435
436 fragCounter = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x00FF;
437 fragCounter |= ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0xFF00;
438
439 fragIndex = ( fragCounter >> 14 ) & 0x03;
440 fragCounter &= 0x3FFF;
441
442 if( mcpsIndication->Multicast == 1 )
443 {
444 // Message received on a multicast address
445 //
446 // TODO: Not working yet
447 //
448 // Check McGroupBitMask
449 //uint8_t groupId = LoRaMacMcChannelGetGroupId( mcpsIndication->DevAddress );
450 //if( ( groupId == 0xFF ) ||
451 // ( ( FragSessionData[fragIndex].FragGroupData.FragSession.Fields.McGroupBitMask & ( 1 << groupId ) ) == 0 ) )
452 //{
453 // // Ignore message
454 // break;
455 //}
456 }
457
458 if( FragSessionData[fragIndex].FragDecoderProcessStatus == FRAG_SESSION_ONGOING )
459 {
460 FragSessionData[fragIndex].FragDecoderProcessStatus = FragDecoderProcess( fragCounter, &mcpsIndication->Buffer[cmdIndex] );
461 FragSessionData[fragIndex].FragDecoderStatus = FragDecoderGetStatus( );
462 if( LmhpFragmentationParams->OnProgress != NULL )
463 {
464 LmhpFragmentationParams->OnProgress( FragSessionData[fragIndex].FragDecoderStatus.FragNbRx,
465 FragSessionData[fragIndex].FragGroupData.FragNb,
466 FragSessionData[fragIndex].FragGroupData.FragSize,
467 FragSessionData[fragIndex].FragDecoderStatus.FragNbLost );
468 }
469 }
470 else
471 {
472 if( FragSessionData[fragIndex].FragDecoderProcessStatus >= 0 )
473 {
474 // Fragmentation successfully done
475 FragSessionData[fragIndex].FragDecoderProcessStatus = FRAG_SESSION_NOT_STARTED;
476 if( LmhpFragmentationParams->OnDone != NULL )
477 {
478 #if( FRAG_DECODER_FILE_HANDLING_NEW_API == 1 )
479 LmhpFragmentationParams->OnDone( FragSessionData[fragIndex].FragDecoderProcessStatus,
480 ( FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize ) - FragSessionData[fragIndex].FragGroupData.Padding );
481 #else
482 LmhpFragmentationParams->OnDone( FragSessionData[fragIndex].FragDecoderProcessStatus,
483 LmhpFragmentationParams->Buffer,
484 ( FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize ) - FragSessionData[fragIndex].FragGroupData.Padding );
485 #endif
486 }
487 }
488 }
489 cmdIndex += FragSessionData[fragIndex].FragGroupData.FragSize;
490 break;
491 }
492 default:
493 {
494 break;
495 }
496 }
497 }
498
499 // After processing the commands, if the end-node has to reply back then a flag is checked if the
500 // reply is to be sent immediately or with a delay.
501 // In some scenarios it is not desired that multiple end-notes send uplinks at the same time to
502 // the same server. (Example: Fragment status during a multicast FUOTA)
503 if( dataBufferIndex != 0 )
504 {
505 // Prepare Answer that is to be transmitted
506 cmdReplyAppData.Buffer = LmhpFragmentationState.DataBuffer;
507 cmdReplyAppData.BufferSize = dataBufferIndex;
508 cmdReplyAppData.Port = FRAGMENTATION_PORT;
509
510 if( isAnswerDelayed == true )
511 {
512 // Delay value is calculated using BlockAckDelay which is communicated by server during the FragSessionSetupReq
513 // Pseudo Random Delay = rand(0:1) * 2^(blockAckDelay + 4) Seconds.
514 // Delay = Pseudo Random Delay * 1000 milli seconds.
515 // Eg: blockAckDelay = 7
516 // Pseudo Random Delay = rand(0:1) * 2^11
517 // rand(0:1) seconds = rand(0:1000) milliseconds
518 // Delay = rand(0:1000) * 2048 => 2048000ms = 34 minutes
519 TxDelayTime = randr( 0, 1000 ) * ( 1 << ( blockAckDelay + 4 ) );
520 DelayedReplyAppData = cmdReplyAppData;
521 LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_START;
522 }
523 else
524 {
525 // Send the prepared answer
526 LmHandlerSend( &cmdReplyAppData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
527 }
528 }
529 }
530