/*!
 * \file      LmhpFragmentation.c
 *
 * \brief     Implements the LoRa-Alliance fragmented data block transport package
 *            Specification V1.0.0: https://resources.lora-alliance.org/technical-specifications/lorawan-fragmented-data-block-transport-specification-v1-0-0
 *            Specification V2.0.0: https://resources.lora-alliance.org/technical-specifications/ts004-2-0-0-fragmented-data-block-transport
 *
 * \copyright Revised BSD License, see section \ref LICENSE.
 *
 * \code
 *                ______                              _
 *               / _____)             _              | |
 *              ( (____  _____ ____ _| |_ _____  ____| |__
 *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 *               _____) ) ____| | | || |_| ____( (___| | | |
 *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
 *              (C)2013-2018 Semtech
 *
 * \endcode
 *
 * \author    Miguel Luis ( Semtech )
 */
/**
  ******************************************************************************
  *
  *          Portions COPYRIGHT 2020 STMicroelectronics
  *
  * @file    LmhpFragmentation.c
  * @author  MCD Application Team
  * @brief   Implements the LoRa-Alliance fragmented data block transport package
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "LoRaMac.h"
#include "LmHandler.h"
#include "LmhpFragmentation.h"
#include "frag_decoder_if.h"
#include "utilities.h"
#include "mw_log_conf.h"  /* needed for MW_LOG */

/*!
 * LoRaWAN Application Layer Fragmented Data Block Transport Specification
 */
#define FRAGMENTATION_PORT                          201

#define FRAGMENTATION_ID                            3

#if (LORAWAN_PACKAGES_VERSION == 1)
#define FRAGMENTATION_VERSION                       1
#elif (LORAWAN_PACKAGES_VERSION == 2)
#define FRAGMENTATION_VERSION                       2
#endif /* LORAWAN_PACKAGES_VERSION */

#define FRAGMENTATION_MAX_SESSIONS                  4

/*!
 * Package current context
 */
typedef struct LmhpFragmentationState_s
{
    bool Initialized;
    bool IsTxPending;
#if ( FRAGMENTATION_VERSION == 2 )
    bool FragDataBlockAnsRequired;
#endif /* FRAGMENTATION_VERSION */
    uint8_t DataBufferMaxSize;
    uint8_t DataBufferSize;
    uint8_t *DataBuffer;
} 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,
#if ( FRAGMENTATION_VERSION == 2 )
    FRAGMENTATION_FRAG_DATA_BLOCK_RECEIVED_REQ  = 0x04,
#endif /* FRAGMENTATION_VERSION */
} 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,
#if ( FRAGMENTATION_VERSION == 2 )
    FRAGMENTATION_FRAG_DATA_BLOCK_RECEIVED_ANS  = 0x04,
#endif /* FRAGMENTATION_VERSION */
    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 );

/*!
 * \brief Callback function for Fragment delay timer.
 */
static void OnFragmentProcessTimer( void *context );

static LmhpFragmentationState_t LmhpFragmentationState =
{
    .Initialized = false,
    .IsTxPending = false,
#if ( FRAGMENTATION_VERSION == 2 )
    .FragDataBlockAnsRequired = false,
#endif /* FRAGMENTATION_VERSION */
    .DataBufferMaxSize = 0,
    .DataBufferSize = 0,
    .DataBuffer = NULL,
};

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;
#if ( FRAGMENTATION_VERSION == 1 )
            uint8_t RFU:              2;
#elif ( FRAGMENTATION_VERSION == 2 )
            uint8_t AckReception:     1;
            uint8_t RFU:              1;
#endif /* FRAGMENTATION_VERSION */
        } Fields;
    } Control;
    uint8_t Padding;
    uint32_t Descriptor;
#if ( FRAGMENTATION_VERSION == 2 )
    uint16_t SessionCnt;
    uint32_t Mic;
#endif /* FRAGMENTATION_VERSION */
} FragGroupData_t;

typedef struct FragSessionData_s
{
    FragGroupData_t FragGroupData;
    FragDecoderStatus_t FragDecoderStatus;
    int32_t FragDecoderProcessStatus;
} FragSessionData_t;

static FragSessionData_t FragSessionData[FRAGMENTATION_MAX_SESSIONS];

static LmhPackage_t LmhpFragmentationPackage =
{
    .Port = FRAGMENTATION_PORT,
    .Init = LmhpFragmentationInit,
    .IsInitialized = LmhpFragmentationIsInitialized,
    .IsTxPending = LmhpFragmentationIsTxPending,
    .Process = LmhpFragmentationProcess,
    .OnPackageProcessEvent = NULL,                             /* To be initialized by LmHandler */
    .OnMcpsConfirmProcess = NULL,                              /* Not used in this package */
    .OnMcpsIndicationProcess = LmhpFragmentationOnMcpsIndication,
    .OnMlmeConfirmProcess = NULL,                              /* Not used in this package */
    .OnMlmeIndicationProcess = NULL,                           /* Not used in this package */
    .OnJoinRequest = NULL,                                     /* To be initialized by LmHandler */
    .OnDeviceTimeRequest = NULL,                               /* To be initialized by LmHandler */
    .OnSysTimeUpdate = NULL,                                   /* To be initialized by LmHandler */
#if (defined( LORAMAC_VERSION ) && (( LORAMAC_VERSION == 0x01000400 ) || ( LORAMAC_VERSION == 0x01010100 )))
    .OnSystemReset = NULL,                                     /* To be initialized by LmHandler */
#endif /* LORAMAC_VERSION */
};

/* Delay value. */
static uint32_t TxDelayTime;

/* Fragment Delay Timer struct */
static TimerEvent_t FragmentProcessTimer;

/* Co-efficient used to calculate delay. */
static uint8_t BlockAckDelay = 0;

#if ( FRAGMENTATION_VERSION == 2 )
/* fragmentation counter session */
static int32_t SessionCntPrev[FRAGMENTATION_MAX_SESSIONS] = {-1, -1, -1, -1};
#endif /* FRAGMENTATION_VERSION */

/*!
 * \brief Callback function for Fragment delay timer.
 */
static void OnFragmentProcessTimer( void *context )
{
    if( LmhpFragmentationState.DataBufferSize != 0 )
    {
        LmhpFragmentationState.IsTxPending = true;
    }
    if( LmhpFragmentationPackage.OnPackageProcessEvent != NULL )
    {
        LmhpFragmentationPackage.OnPackageProcessEvent();
    }
}

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( &FragmentProcessTimer, OnFragmentProcessTimer );
    }
    else
    {
        LmhpFragmentationParams = NULL;
        LmhpFragmentationState.Initialized = false;
    }
    LmhpFragmentationState.IsTxPending = false;

    /* initialize the global fragmentation session buffer */
    memset1( ( uint8_t * )FragSessionData, 0, sizeof( FragSessionData ) );
}

static bool LmhpFragmentationIsInitialized( void )
{
    return LmhpFragmentationState.Initialized;
}

static bool LmhpFragmentationIsTxPending( void )
{
    return LmhpFragmentationState.IsTxPending;
}

static void LmhpFragmentationProcess( void )
{
    if( LmhpFragmentationState.IsTxPending == true )
    {
        /* Send the reply. */
        LmHandlerAppData_t appData =
        {
            .Buffer     = LmhpFragmentationState.DataBuffer,
            .BufferSize = LmhpFragmentationState.DataBufferSize,
            .Port       = FRAGMENTATION_PORT,
        };

        LmHandlerErrorStatus_t lmhStatus = LORAMAC_HANDLER_ERROR;
        lmhStatus = LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG, true );
#if ( FRAGMENTATION_VERSION == 2 )
        if( ( lmhStatus != LORAMAC_HANDLER_SUCCESS ) || ( LmhpFragmentationState.FragDataBlockAnsRequired == true ) )
#else
        if( lmhStatus != LORAMAC_HANDLER_SUCCESS )
#endif /* FRAGMENTATION_VERSION */
        {
            /* try to send the message again */
            TimerSetValue( &FragmentProcessTimer, 1500 );
            TimerStart( &FragmentProcessTimer );
        }
        else
        {
            LmhpFragmentationState.IsTxPending = false;
            LmhpFragmentationState.DataBufferSize = 0;
        }
    }
}

static void LmhpFragmentationOnMcpsIndication( McpsIndication_t *mcpsIndication )
{
    uint8_t cmdIndex = 0;
    uint8_t dataBufferIndex = 0;
    bool isAnswerDelayed = false;

    if( mcpsIndication->Port != FRAGMENTATION_PORT )
    {
        return;
    }
 
    LmhpFragmentationState.DataBufferSize = 0;

    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 = ( fragIndex >> 1 ) & 0x03;
                    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 ( FRAGMENTATION_VERSION == 2 )
                    /* SessionCnt and MIC filed added for V2*/
                    fragSessionData.FragGroupData.SessionCnt = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x00FF;
                    fragSessionData.FragGroupData.SessionCnt += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0xFF00;

                    fragSessionData.FragGroupData.Mic = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF;
                    fragSessionData.FragGroupData.Mic += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00;
                    fragSessionData.FragGroupData.Mic += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000;
                    fragSessionData.FragGroupData.Mic += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000;
#endif /* FRAGMENTATION_VERSION */

                    if( fragSessionData.FragGroupData.Control.Fields.FragAlgo > 0 )
                    {
                        status |= 0x01; /* Encoding unsupported */
                    }

                    if( ( fragSessionData.FragGroupData.FragNb > FRAG_MAX_NB ) ||
                        ( fragSessionData.FragGroupData.FragSize > FRAG_MAX_SIZE ) ||
                        ( fragSessionData.FragGroupData.FragSize < FRAG_MIN_SIZE ) ||
                        ( ( fragSessionData.FragGroupData.FragNb * fragSessionData.FragGroupData.FragSize ) > FRAG_DECODER_DWL_REGION_SIZE ) )
                    {
                        status |= 0x02; /* Not enough Memory */
                    }
                    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 ( FRAGMENTATION_VERSION == 1 )
                    if( ( status & 0x0F ) == 0 )
                    {
#elif ( FRAGMENTATION_VERSION == 2 )
                    if( SessionCntPrev[fragSessionData.FragGroupData.FragSession.Fields.FragIndex] >=
                        fragSessionData.FragGroupData.SessionCnt )
                    {
                        status |= 0x10; /* SessionCnt Replay */
                    }

                    if( ( status & 0x1F ) == 0 )
                    {
#endif /* FRAGMENTATION_VERSION */
                        /* The FragSessionSetup is accepted */
                        fragSessionData.FragGroupData.IsActive = true;
                        fragSessionData.FragDecoderProcessStatus = FRAG_SESSION_ONGOING;
                        FragSessionData[fragSessionData.FragGroupData.FragSession.Fields.FragIndex] = fragSessionData;
                        FragDecoderInit( fragSessionData.FragGroupData.FragNb,
                                         fragSessionData.FragGroupData.FragSize,
                                         &LmhpFragmentationParams->DecoderCallbacks,
                                         FRAGMENTATION_VERSION );
                    }
                    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;
                }
#if ( FRAGMENTATION_VERSION == 2 )
            case FRAGMENTATION_FRAG_DATA_BLOCK_RECEIVED_ANS:
                {
                    if( mcpsIndication->Multicast == 1 )
                    {
                        /* Multicast channel. Don't process command. */
                        break;
                    }
                    uint8_t fragIndex = mcpsIndication->Buffer[cmdIndex++] & 0x03;

                    if( ( FragSessionData[fragIndex].FragGroupData.FragSession.Fields.FragIndex == fragIndex )
                        && ( LmhpFragmentationState.FragDataBlockAnsRequired == true ) )
                    {
                        TimerStop( &FragmentProcessTimer );
                        LmhpFragmentationState.IsTxPending = false;
                        LmhpFragmentationState.DataBufferSize = 0;
                        LmhpFragmentationState.FragDataBlockAnsRequired = false;
                    }
                    break;
                }
#endif /* FRAGMENTATION_VERSION */
            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( FragSessionData[fragIndex].FragGroupData.IsActive == false )
                    {
                        cmdIndex = mcpsIndication->BufferSize;
                        break;
                    }
                    if( mcpsIndication->Multicast == 1 )
                    {
                        /* Message received on a multicast address */
                        /* Check McGroupBitMask */
                        uint8_t groupId = LoRaMacMcChannelGetGroupId( mcpsIndication->DevAddress );
                        if( ( groupId == 0xFF ) ||
                            ( ( FragSessionData[fragIndex].FragGroupData.FragSession.Fields.McGroupBitMask & ( 1 << groupId ) ) == 0 ) )
                        {
                            /* Ignore message */
                            cmdIndex = mcpsIndication->BufferSize;
                            break;
                        }
                    }

                    if( FragSessionData[fragIndex].FragDecoderProcessStatus == FRAG_SESSION_ONGOING )
                    {
                        FragSessionData[fragIndex].FragDecoderProcessStatus = 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 );
                        }

                        if( FragSessionData[fragIndex].FragDecoderProcessStatus >= 0 )
                        {
                            uint32_t UnfragmentedBufferAddr;
                            /* Fragmentation successfully done */
                            if( LmhpFragmentationParams->OnDone != NULL )
                            {
                                LmhpFragmentationParams->OnDone( FragSessionData[fragIndex].FragDecoderProcessStatus,
                                                                 ( FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize ) -
                                                                 FragSessionData[fragIndex].FragGroupData.Padding,
                                                                 &UnfragmentedBufferAddr );
                            }

#if ( FRAGMENTATION_VERSION == 2 )
                            /*If AckReception = 0, the end-device SHALL do nothing */
                            if( FragSessionData[fragIndex].FragGroupData.Control.Fields.AckReception == 1 )
                            {
                                uint8_t status = 0x00;
                                uint32_t micComputed = 0;

                                status = fragIndex;

                                /* Compute MIC */
                                LoRaMacProcessMicForDatablock( ( uint8_t * )UnfragmentedBufferAddr,
                                                               ( FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize ) -
                                                               FragSessionData[fragIndex].FragGroupData.Padding,
                                                               FragSessionData[fragIndex].FragGroupData.SessionCnt,
                                                               fragIndex,
                                                               FragSessionData[fragIndex].FragGroupData.Descriptor,
                                                               &micComputed );
                                MW_LOG( TS_OFF, VLEVEL_M, "MIC         : %08X\r\n", micComputed );

                                /* check if the MIC computed is equal to the MIC received */
                                if( micComputed != FragSessionData[fragIndex].FragGroupData.Mic )
                                {
                                    status |= 0x04;
                                }

                                LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FRAGMENTATION_FRAG_DATA_BLOCK_RECEIVED_REQ;
                                LmhpFragmentationState.DataBuffer[dataBufferIndex++] = status;
                                BlockAckDelay = FragSessionData[fragIndex].FragGroupData.Control.Fields.BlockAckDelay;
                                isAnswerDelayed = true;
                                LmhpFragmentationState.FragDataBlockAnsRequired = true;
                            }
#endif /* FRAGMENTATION_VERSION */

                            FragSessionData[fragIndex].FragDecoderProcessStatus = FRAG_SESSION_NOT_STARTED;
                        }
                    }
                    cmdIndex += FragSessionData[fragIndex].FragGroupData.FragSize;
#if ( FRAGMENTATION_VERSION == 2 )
                    /* Store the previous session counter*/
                    SessionCntPrev[fragIndex] = FragSessionData[fragIndex].FragGroupData.SessionCnt;
#endif /* FRAGMENTATION_VERSION */
                    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 */
        LmhpFragmentationState.DataBufferSize = dataBufferIndex;

        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
             * To prevent too early execution, a minimum delay of 3s is added
             */
            TxDelayTime = 3000 + ( randr( 0, 1000 ) * ( 1 << ( BlockAckDelay + 4 ) ) );
        }
        else
        {
            TxDelayTime = 3000;
        }
        TimerSetValue( &FragmentProcessTimer, TxDelayTime );
        TimerStart( &FragmentProcessTimer );
    }
}