/*! * \file LmhpFragmentation.c * * \brief Implements the LoRa-Alliance fragmented data block transport package * Specification: https://lora-alliance.org/sites/default/files/2018-09/fragmented_data_block_transport_v1.0.0.pdf * * \copyright Revised BSD License, see section \ref LICENSE. * * \code * ______ _ * / _____) _ | | * ( (____ _____ ____ _| |_ _____ ____| |__ * \____ \| ___ | (_ _) ___ |/ ___) _ \ * _____) ) ____| | | || |_| ____( (___| | | | * (______/|_____)_|_|_| \__)_____)\____)_| |_| * (C)2013-2018 Semtech * * \endcode * * \author Miguel Luis ( Semtech ) */ #include "LmHandler.h" #include "LmhpFragmentation.h" #include "FragDecoder.h" /*! * LoRaWAN Application Layer Fragmented Data Block Transport Specification */ #define FRAGMENTATION_PORT 201 #define FRAGMENTATION_ID 3 #define FRAGMENTATION_VERSION 1 #define FRAGMENTATION_MAX_SESSIONS 4 // Fragmentation Tx delay state typedef enum LmhpFragmentationTxDelayStates_e { // Tx delay in idle state. FRAGMENTATION_TX_DELAY_STATE_IDLE, // Tx delay to be started. FRAGMENTATION_TX_DELAY_STATE_START, // Tx delay to be stopped. FRAGMENTATION_TX_DELAY_STATE_STOP, }LmhpFragmentationTxDelayStates_t; /*! * Package current context */ typedef struct LmhpFragmentationState_s { bool Initialized; bool IsTxPending; LmhpFragmentationTxDelayStates_t TxDelayState; uint8_t DataBufferMaxSize; uint8_t *DataBuffer; uint8_t *file; }LmhpFragmentationState_t; typedef enum LmhpFragmentationMoteCmd_e { FRAGMENTATION_PKG_VERSION_ANS = 0x00, FRAGMENTATION_FRAG_STATUS_ANS = 0x01, FRAGMENTATION_FRAG_SESSION_SETUP_ANS = 0x02, FRAGMENTATION_FRAG_SESSION_DELETE_ANS = 0x03, }LmhpFragmentationMoteCmd_t; typedef enum LmhpFragmentationSrvCmd_e { FRAGMENTATION_PKG_VERSION_REQ = 0x00, FRAGMENTATION_FRAG_STATUS_REQ = 0x01, FRAGMENTATION_FRAG_SESSION_SETUP_REQ = 0x02, FRAGMENTATION_FRAG_SESSION_DELETE_REQ = 0x03, FRAGMENTATION_DATA_FRAGMENT = 0x08, }LmhpFragmentationSrvCmd_t; /*! * LoRaWAN fragmented data block transport handler parameters */ static LmhpFragmentationParams_t* LmhpFragmentationParams; /*! * Initializes the package with provided parameters * * \param [IN] params Pointer to the package parameters * \param [IN] dataBuffer Pointer to main application buffer * \param [IN] dataBufferMaxSize Main application buffer maximum size */ static void LmhpFragmentationInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize ); /*! * Returns the current package initialization status. * * \retval status Package initialization status * [true: Initialized, false: Not initialized] */ static bool LmhpFragmentationIsInitialized( void ); /*! * Returns if a package transmission is pending or not. * * \retval status Package transmission status * [true: pending, false: Not pending] */ static bool LmhpFragmentationIsTxPending( void ); /*! * Processes the internal package events. */ static void LmhpFragmentationProcess( void ); /*! * Processes the MCPS Indication * * \param [IN] mcpsIndication MCPS indication primitive data */ static void LmhpFragmentationOnMcpsIndication( McpsIndication_t *mcpsIndication ); static LmhpFragmentationState_t LmhpFragmentationState = { .Initialized = false, .IsTxPending = false, .TxDelayState = FRAGMENTATION_TX_DELAY_STATE_IDLE, }; typedef struct FragGroupData_s { bool IsActive; union { uint8_t Value; struct { uint8_t McGroupBitMask: 4; uint8_t FragIndex: 2; uint8_t RFU: 2; }Fields; }FragSession; uint16_t FragNb; uint8_t FragSize; union { uint8_t Value; struct { uint8_t BlockAckDelay: 3; uint8_t FragAlgo: 3; uint8_t RFU: 2; }Fields; }Control; uint8_t Padding; uint32_t Descriptor; }FragGroupData_t; typedef struct FragSessionData_s { FragGroupData_t FragGroupData; FragDecoderStatus_t FragDecoderStatus; int32_t FragDecoderPorcessStatus; }FragSessionData_t; FragSessionData_t FragSessionData[FRAGMENTATION_MAX_SESSIONS]; // Answer struct for the commands. LmHandlerAppData_t DelayedReplyAppData; static LmhPackage_t LmhpFragmentationPackage = { .Port = FRAGMENTATION_PORT, .Init = LmhpFragmentationInit, .IsInitialized = LmhpFragmentationIsInitialized, .IsTxPending = LmhpFragmentationIsTxPending, .Process = LmhpFragmentationProcess, .OnMcpsConfirmProcess = NULL, // Not used in this package .OnMcpsIndicationProcess = LmhpFragmentationOnMcpsIndication, .OnMlmeConfirmProcess = NULL, // Not used in this package .OnMlmeIndicationProcess = NULL, // Not used in this package .OnMacMcpsRequest = NULL, // To be initialized by LmHandler .OnMacMlmeRequest = NULL, // To be initialized by LmHandler .OnJoinRequest = NULL, // To be initialized by LmHandler .OnDeviceTimeRequest = NULL, // To be initialized by LmHandler .OnSysTimeUpdate = NULL, // To be initialized by LmHandler }; // Delay value. static uint32_t TxDelayTime; // Fragment Delay Timer struct static TimerEvent_t FragmentTxDelayTimer; /*! * \brief Callback function for Fragment delay timer. */ static void OnFragmentTxDelay( void* context ) { // Stop the timer. TimerStop( &FragmentTxDelayTimer ); // Set the state. LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_STOP; } LmhPackage_t *LmhpFragmentationPackageFactory( void ) { return &LmhpFragmentationPackage; } static void LmhpFragmentationInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize ) { if( ( params != NULL ) && ( dataBuffer != NULL ) ) { LmhpFragmentationParams = ( LmhpFragmentationParams_t* )params; LmhpFragmentationState.DataBuffer = dataBuffer; LmhpFragmentationState.DataBufferMaxSize = dataBufferMaxSize; LmhpFragmentationState.Initialized = true; // Initialize Fragmentation delay time. TxDelayTime = 0; // Initialize Fragmentation delay timer. TimerInit( &FragmentTxDelayTimer, OnFragmentTxDelay ); } else { LmhpFragmentationParams = NULL; LmhpFragmentationState.Initialized = false; } LmhpFragmentationState.IsTxPending = false; } static bool LmhpFragmentationIsInitialized( void ) { return LmhpFragmentationState.Initialized; } static bool LmhpFragmentationIsTxPending( void ) { return LmhpFragmentationState.IsTxPending; } static void LmhpFragmentationProcess( void ) { LmhpFragmentationTxDelayStates_t delayTimerState; CRITICAL_SECTION_BEGIN( ); delayTimerState = LmhpFragmentationState.TxDelayState; // Set the state to idle so that the other states are executed only when they are set // in the appropriate functions. LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_IDLE; CRITICAL_SECTION_END( ); switch( delayTimerState ) { case FRAGMENTATION_TX_DELAY_STATE_START: // Set the timer with the initially calculated Delay value. TimerSetValue( &FragmentTxDelayTimer, TxDelayTime ); // Start the timer. TimerStart( &FragmentTxDelayTimer ); break; case FRAGMENTATION_TX_DELAY_STATE_STOP: // Send the reply. LmHandlerSend( &DelayedReplyAppData, LORAMAC_HANDLER_UNCONFIRMED_MSG ); break; case FRAGMENTATION_TX_DELAY_STATE_IDLE: // Intentional fall through default: // Nothing to do. break; } } static void LmhpFragmentationOnMcpsIndication( McpsIndication_t *mcpsIndication ) { uint8_t cmdIndex = 0; uint8_t dataBufferIndex = 0; bool isAnswerDelayed = false; // Answer struct for the commands. LmHandlerAppData_t cmdReplyAppData; // Co-efficient used to calculate delay. uint8_t blockAckDelay = 0; if( mcpsIndication->Port != FRAGMENTATION_PORT ) { return; } while( cmdIndex < mcpsIndication->BufferSize ) { switch( mcpsIndication->Buffer[cmdIndex++] ) { case FRAGMENTATION_PKG_VERSION_REQ: { if( mcpsIndication->Multicast == 1 ) { // Multicast channel. Don't process command. break; } LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_PKG_VERSION_ANS; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_ID; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_VERSION; break; } case FRAGMENTATION_FRAG_STATUS_REQ: { uint8_t fragIndex = mcpsIndication->Buffer[cmdIndex++]; uint8_t participants = fragIndex & 0x01; fragIndex >>= 1; FragSessionData[fragIndex].FragDecoderStatus = FragDecoderGetStatus( ); if( ( participants == 1 ) || ( ( participants == 0 ) && ( FragSessionData[fragIndex].FragDecoderStatus.FragNbLost > 0 ) ) ) { LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_STATUS_ANS; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.FragNbRx & 0xFF; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = ( fragIndex << 6 ) | ( ( FragSessionData[fragIndex].FragDecoderStatus.FragNbRx >> 8 ) & 0x3F ); LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.FragNbLost; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.MatrixError & 0x01; // Fetch the co-efficient value required to calculate delay of that respective session. blockAckDelay = FragSessionData[fragIndex].FragGroupData.Control.Fields.BlockAckDelay; isAnswerDelayed = true; } break; } case FRAGMENTATION_FRAG_SESSION_SETUP_REQ: { if( mcpsIndication->Multicast == 1 ) { // Multicast channel. Don't process command. break; } FragSessionData_t fragSessionData; uint8_t status = 0x00; fragSessionData.FragGroupData.FragSession.Value = mcpsIndication->Buffer[cmdIndex++]; fragSessionData.FragGroupData.FragNb = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x00FF; fragSessionData.FragGroupData.FragNb |= ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0xFF00; fragSessionData.FragGroupData.FragSize = mcpsIndication->Buffer[cmdIndex++]; fragSessionData.FragGroupData.Control.Value = mcpsIndication->Buffer[cmdIndex++]; fragSessionData.FragGroupData.Padding = mcpsIndication->Buffer[cmdIndex++]; fragSessionData.FragGroupData.Descriptor = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF; fragSessionData.FragGroupData.Descriptor += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00; fragSessionData.FragGroupData.Descriptor += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000; fragSessionData.FragGroupData.Descriptor += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000; if( fragSessionData.FragGroupData.Control.Fields.FragAlgo > 0 ) { status |= 0x01; // Encoding unsupported } #if( FRAG_DECODER_FILE_HANDLING_NEW_API == 1 ) if( ( fragSessionData.FragGroupData.FragNb > FRAG_MAX_NB ) || ( fragSessionData.FragGroupData.FragSize > FRAG_MAX_SIZE ) || ( ( fragSessionData.FragGroupData.FragNb * fragSessionData.FragGroupData.FragSize ) > FragDecoderGetMaxFileSize( ) ) ) { status |= 0x02; // Not enough Memory } #else if( ( fragSessionData.FragGroupData.FragNb > FRAG_MAX_NB ) || ( fragSessionData.FragGroupData.FragSize > FRAG_MAX_SIZE ) || ( ( fragSessionData.FragGroupData.FragNb * fragSessionData.FragGroupData.FragSize ) > LmhpFragmentationParams->BufferSize ) ) { status |= 0x02; // Not enough Memory } #endif status |= ( fragSessionData.FragGroupData.FragSession.Fields.FragIndex << 6 ) & 0xC0; if( fragSessionData.FragGroupData.FragSession.Fields.FragIndex >= FRAGMENTATION_MAX_SESSIONS ) { status |= 0x04; // FragSession index not supported } // Descriptor is not really defined in the specification // Not clear how to handle this. // Currently the descriptor is always correct if( fragSessionData.FragGroupData.Descriptor != 0x01020304 ) { //status |= 0x08; // Wrong Descriptor } if( ( status & 0x0F ) == 0 ) { // The FragSessionSetup is accepted fragSessionData.FragGroupData.IsActive = true; fragSessionData.FragDecoderPorcessStatus = FRAG_SESSION_ONGOING; FragSessionData[fragSessionData.FragGroupData.FragSession.Fields.FragIndex] = fragSessionData; #if( FRAG_DECODER_FILE_HANDLING_NEW_API == 1 ) FragDecoderInit( fragSessionData.FragGroupData.FragNb, fragSessionData.FragGroupData.FragSize, &LmhpFragmentationParams->DecoderCallbacks ); #else FragDecoderInit( fragSessionData.FragGroupData.FragNb, fragSessionData.FragGroupData.FragSize, LmhpFragmentationParams->Buffer, LmhpFragmentationParams->BufferSize ); #endif } LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_SESSION_SETUP_ANS; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = status; isAnswerDelayed = false; break; } case FRAGMENTATION_FRAG_SESSION_DELETE_REQ: { if( mcpsIndication->Multicast == 1 ) { // Multicast channel. Don't process command. break; } uint8_t status = 0x00; uint8_t id = mcpsIndication->Buffer[cmdIndex++] & 0x03; status |= id; if( ( id >= FRAGMENTATION_MAX_SESSIONS ) || ( FragSessionData[id].FragGroupData.IsActive == false ) ) { status |= 0x04; // Session does not exist } else { // Delete session FragSessionData[id].FragGroupData.IsActive = false; } LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_SESSION_DELETE_ANS; LmhpFragmentationState.DataBuffer[dataBufferIndex++] = status; isAnswerDelayed = false; break; } case FRAGMENTATION_DATA_FRAGMENT: { uint8_t fragIndex = 0; uint16_t fragCounter = 0; fragCounter = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x00FF; fragCounter |= ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0xFF00; fragIndex = ( fragCounter >> 14 ) & 0x03; fragCounter &= 0x3FFF; if( mcpsIndication->Multicast == 1 ) { // Message received on a multicast address // // TODO: Not working yet // // Check McGroupBitMask //uint8_t groupId = LoRaMacMcChannelGetGroupId( mcpsIndication->DevAddress ); //if( ( groupId == 0xFF ) || // ( ( FragSessionData[fragIndex].FragGroupData.FragSession.Fields.McGroupBitMask & ( 1 << groupId ) ) == 0 ) ) //{ // // Ignore message // break; //} } if( FragSessionData[fragIndex].FragDecoderPorcessStatus == FRAG_SESSION_ONGOING ) { FragSessionData[fragIndex].FragDecoderPorcessStatus = FragDecoderProcess( fragCounter, &mcpsIndication->Buffer[cmdIndex] ); FragSessionData[fragIndex].FragDecoderStatus = FragDecoderGetStatus( ); if( LmhpFragmentationParams->OnProgress != NULL ) { LmhpFragmentationParams->OnProgress( FragSessionData[fragIndex].FragDecoderStatus.FragNbRx, FragSessionData[fragIndex].FragGroupData.FragNb, FragSessionData[fragIndex].FragGroupData.FragSize, FragSessionData[fragIndex].FragDecoderStatus.FragNbLost ); } } else { if( FragSessionData[fragIndex].FragDecoderPorcessStatus >= 0 ) { // Fragmentation successfully done FragSessionData[fragIndex].FragDecoderPorcessStatus = FRAG_SESSION_NOT_STARTED; if( LmhpFragmentationParams->OnDone != NULL ) { #if( FRAG_DECODER_FILE_HANDLING_NEW_API == 1 ) LmhpFragmentationParams->OnDone( FragSessionData[fragIndex].FragDecoderPorcessStatus, ( FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize ) - FragSessionData[fragIndex].FragGroupData.Padding ); #else LmhpFragmentationParams->OnDone( FragSessionData[fragIndex].FragDecoderPorcessStatus, LmhpFragmentationParams->Buffer, ( FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize ) - FragSessionData[fragIndex].FragGroupData.Padding ); #endif } } } cmdIndex += FragSessionData[fragIndex].FragGroupData.FragSize; break; } default: { break; } } } // After processing the commands, if the end-node has to reply back then a flag is checked if the // reply is to be sent immediately or with a delay. // In some scenarios it is not desired that multiple end-notes send uplinks at the same time to // the same server. (Example: Fragment status during a multicast FUOTA) if( dataBufferIndex != 0 ) { // Prepare Answer that is to be transmitted cmdReplyAppData.Buffer = LmhpFragmentationState.DataBuffer; cmdReplyAppData.BufferSize = dataBufferIndex; cmdReplyAppData.Port = FRAGMENTATION_PORT; if( isAnswerDelayed == true ) { // Delay value is calculated using BlockAckDelay which is communicated by server during the FragSessionSetupReq // Pseudo Random Delay = rand(0:1) * 2^(blockAckDelay + 4) Seconds. // Delay = Pseudo Random Delay * 1000 milli seconds. // Eg: blockAckDelay = 7 // Pseudo Random Delay = rand(0:1) * 2^11 // rand(0:1) seconds = rand(0:1000) milliseconds // Delay = rand(0:1000) * 2048 => 2048000ms = 34 minutes TxDelayTime = randr( 0, 1000 ) * ( 1 << ( blockAckDelay + 4 ) ); DelayedReplyAppData = cmdReplyAppData; LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_START; } else { // Send the prepared answer LmHandlerSend( &cmdReplyAppData, LORAMAC_HANDLER_UNCONFIRMED_MSG ); } } }