/*! * \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 ) */ /** ****************************************************************************** * * Portions COPYRIGHT 2020 STMicroelectronics * * @file LmhpFragmentation.c * @author MCD Application Team * @brief Fragmentation Data Block Transport Package definition ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "LmHandler.h" #include "LmhpFragmentation.h" #include "utilities.h" /* Private typedef -----------------------------------------------------------*/ /* 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 is in pending */ FRAGMENTATION_TX_DELAY_STATE_PENDING, } LmhpFragmentationTxDelayStates_t; /*! * Package current context */ typedef struct LmhpFragmentationState_s { bool Initialized; bool IsRunning; 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; 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 FragDecoderProcessStatus; } FragSessionData_t; /* Private define ------------------------------------------------------------*/ /*! * 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 /* Private macro -------------------------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/ /*! * \brief Callback function for Fragment delay timer. */ static void OnFragmentTxDelay(void *context); /*! * 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 the package operation status. * * \retval status Package operation status * [true: Running, false: Not running] */ static bool LmhpFragmentationIsRunning(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); /* Private variables ---------------------------------------------------------*/ /*! * LoRaWAN fragmented data block transport handler parameters */ static LmhpFragmentationParams_t *LmhpFragmentationParams; static LmhpFragmentationState_t LmhpFragmentationState = { .Initialized = false, .IsRunning = false, .TxDelayState = FRAGMENTATION_TX_DELAY_STATE_IDLE, }; static 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, .IsRunning = LmhpFragmentationIsRunning, .Process = LmhpFragmentationProcess, .OnMcpsConfirmProcess = NULL, /* Not used in this package */ .OnMcpsIndicationProcess = LmhpFragmentationOnMcpsIndication, .OnMlmeConfirmProcess = NULL, /* Not used in this package */ .OnJoinRequest = NULL, /* To be initialized by LmHandler */ .OnSendRequest = NULL, /* To be initialized by LmHandler */ .OnDeviceTimeRequest = NULL, /* To be initialized by LmHandler */ }; /* Delay value. */ static uint32_t TxDelayTime; /* Fragment Delay Timer struct */ static TimerEvent_t FragmentTxDelayTimer; /* Exported functions ---------------------------------------------------------*/ LmhPackage_t *LmhpFragmentationPackageFactory(void) { return &LmhpFragmentationPackage; } /* Private functions ---------------------------------------------------------*/ static void OnFragmentTxDelay(void *context) { /* Stop the timer. */ TimerStop(&FragmentTxDelayTimer); /* Set the state. */ LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_PENDING; } 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; LmhpFragmentationState.IsRunning = true; /* Initialize Fragmentation delay time. */ TxDelayTime = 0; /* Initialize Fragmentation delay timer. */ TimerInit(&FragmentTxDelayTimer, OnFragmentTxDelay); } else { LmhpFragmentationParams = NULL; LmhpFragmentationState.IsRunning = false; LmhpFragmentationState.Initialized = false; } /* initialize the global fragmentation session buffer */ UTIL_MEM_set_8(FragSessionData, 0, sizeof(FragSessionData)); } static bool LmhpFragmentationIsInitialized(void) { return LmhpFragmentationState.Initialized; } static bool LmhpFragmentationIsRunning(void) { if (LmhpFragmentationState.Initialized == false) { return false; } return LmhpFragmentationState.IsRunning; } static void LmhpFragmentationProcess(void) { LmhpFragmentationTxDelayStates_t delayTimerState; TimerTime_t nextTxIn = 0; CRITICAL_SECTION_BEGIN(); delayTimerState = LmhpFragmentationState.TxDelayState; 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_PENDING: /* Send the reply. */ if (LmhpFragmentationPackage.OnSendRequest(&DelayedReplyAppData, LORAMAC_HANDLER_UNCONFIRMED_MSG, &nextTxIn, true) == LORAMAC_HANDLER_SUCCESS) { LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_IDLE; } 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; /* Co-efficient used to calculate delay. */ uint8_t blockAckDelay = 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 >>= 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++] = (fragIndex << 14) | ((FragSessionData[fragIndex].FragDecoderStatus.FragNbRx >> 8) & 0x3F); LmhpFragmentationState.DataBuffer[dataBufferIndex++] = FragSessionData[fragIndex].FragDecoderStatus.FragNbRx & 0xFF; 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 ((fragSessionData.FragGroupData.FragNb * fragSessionData.FragGroupData.FragSize) > FragDecoderGetMaxFileSize()) { 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 ((status & 0x0F) == 0) { /* 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); } 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 (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); } } else { if (FragSessionData[fragIndex].FragDecoderProcessStatus >= 0) { /* Fragmentation successfully done */ if (LmhpFragmentationParams->OnDone != NULL) { LmhpFragmentationParams->OnDone(FragSessionData[fragIndex].FragDecoderProcessStatus, (FragSessionData[fragIndex].FragGroupData.FragNb * FragSessionData[fragIndex].FragGroupData.FragSize) - FragSessionData[fragIndex].FragGroupData.Padding); } FragSessionData[fragIndex].FragDecoderProcessStatus = FRAG_SESSION_NOT_STARTED; } } 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) { /* Answer commands */ LmHandlerAppData_t appData = { .Buffer = LmhpFragmentationState.DataBuffer, .BufferSize = dataBufferIndex, .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 = appData; LmhpFragmentationState.TxDelayState = FRAGMENTATION_TX_DELAY_STATE_START; } else { /* Send the prepared answer */ bool current_dutycycle; LmHandlerGetDutyCycleEnable(¤t_dutycycle); /* force Duty Cycle OFF to this Send */ LmHandlerSetDutyCycleEnable(false); LmhpFragmentationPackage.OnSendRequest(&appData, LORAMAC_HANDLER_UNCONFIRMED_MSG, NULL, true); /* restore initial Duty Cycle */ LmHandlerSetDutyCycleEnable(current_dutycycle); } } }