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