/**************************************************************************************************
  Filename:       aps_frag.c
  Revised:        $Date: 2013-09-27 11:56:40 -0700 (Fri, 27 Sep 2013) $
  Revision:       $Revision: 35472 $

  Description:    Implements APS Application Data Unit Fragmentation


  Copyright 2006-2013 Texas Instruments Incorporated. All rights reserved.

  IMPORTANT: Your use of this Software is limited to those specific rights
  granted under the terms of a software license agreement between the user
  who downloaded the software, his/her employer (which must be your employer)
  and Texas Instruments Incorporated (the "License").  You may not use this
  Software unless you agree to abide by the terms of the License. The License
  limits your use, and you acknowledge, that the Software may not be modified,
  copied or distributed unless embedded on a Texas Instruments microcontroller
  or used solely and exclusively in conjunction with a Texas Instruments radio
  frequency transceiver, which is integrated into your product.  Other than for
  the foregoing purpose, you may not use, reproduce, copy, prepare derivative
  works of, modify, distribute, perform, display or sell this Software and/or
  its documentation for any purpose.

  YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE
  PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
  INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE,
  NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
  TEXAS INSTRUMENTS OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT,
  NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER
  LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
  INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE
  OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT
  OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
  (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.

  Should you have any questions regarding your right to use this Software,
  contact Texas Instruments Incorporated at www.TI.com.
**************************************************************************************************/

/*********************************************************************
 * INCLUDES
 */
#include "OSAL.h"
#include "AF.h"
#include "ZDApp.h"
#include "aps_frag.h"
#include "aps_util.h"
#include "hal_assert.h"
#include "nwk_util.h"
#include "ZGlobals.h"

/*********************************************************************
 * MACROS
 */

// Time to wait for an ackknowledgement
#define APSF_ACK_WAIT           (100 * _NIB.MaxDepth * zgApsAckWaitMultiplier)

// Time to wait for retrys before timing out the reception
#define APSF_RX_TOUT            (APSF_ACK_WAIT + APSF_ACK_WAIT * zgApscMaxFrameRetries)

// Object list type
#define APSF_TX_TYPE            1
#define APSF_RX_TYPE            2

/*********************************************************************
 * CONSTANTS
 */

#define APSF_DEF_FRAME_DELAY    50

#define APSF_DEF_WINDOW_SIZE    3
#define APSF_ZCL_WINDOW_SIZE    1

/*********************************************************************
 * TYPEDEFS
 */
typedef struct _apsf_hdr_t {
  uint16                timeStamp;
  uint16                duration;
  uint16                shortAddr;
  uint8                 type;
  uint8                 apsCount;
  uint8                 numBlocks;
  struct _apsf_hdr_t    *pNext;
} APSF_Header_t;

typedef struct {
  APSF_Header_t         hdr;
  APSDE_DataReq_t       dataReq;
  uint8                 blockSize;
  uint8                 firstBlk;      // First block in the window
  uint8                 sendMask;      // Mask of sent blocks (1=sent)
  uint8                 ackBits;       // Mask of acked blocks (1=ack)
  uint8                 retry;
  uint8                 windowSize;
  uint8                 frameDelay;
  uint16                len;
  uint8                 *pData;
} APSF_TxObj_t;

typedef struct {
  APSF_Header_t         hdr;
  uint8                 taskID;
  uint8                 ackBits;
  uint8                 fragSize;
  uint8                 firstBlk;       // First block in the window
  uint8                 windowSize;
  afIncomingMSGPacket_t *pMsg;
} APSF_RxObj_t;

/*********************************************************************
 * GLOBAL VARIABLES
 */
uint8 APSF_taskID = 0xff;

/*********************************************************************
 * EXTERNAL VARIABLES
 */
extern uint8 zgApsAckWaitMultiplier;

/*********************************************************************
 * EXTERNAL FUNCTIONS
 */

/*********************************************************************
 * LOCAL VARIABLES
 */

static APSF_Header_t *APSF_ObjList;

/*********************************************************************
 * LOCAL FUNCTIONS
 */
static void APSF_AddObj(APSF_Header_t *pHdr, uint8 type);
static APSF_Header_t *APSF_FindObj(uint8 apsCount, uint16 shortAddr, uint8 type);
static APSF_Header_t *APSF_FreeObj(APSF_Header_t *pHdr);
static void APSF_TxNextFrame(APSF_TxObj_t *pTx);
static void APSF_Schedule( void );
static APSF_Header_t *APSF_AllocRxObj(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                               NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp );
static void APSF_IncomingData(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                      NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp);

uint8 (*pAPSF_IgnoreBlock)( uint8 block ) = (void*)NULL;

/*********************************************************************
 * PUBLIC FUNCTIONS
 */

/*********************************************************************
 * @fn      APSF_Init
 *
 * @brief   Initialization function for the Task.
 *          This is called during initialization and should contain
 *          any application specific initialization (ie. hardware
 *          initialization/setup, table initialization, power up
 *          notificaiton ... ).
 *
 * @param   task_id - the ID assigned by OSAL.  This ID should be
 *                    used to send messages and set timers.
 *
 * @return  none
 */
void APSF_Init(uint8 task_id)
{
  // Record the task ID
  APSF_taskID = task_id;

  // Setup the entry points
  apsfSendFragmented = &APSF_SendFragmented;
  apsfProcessAck = &APSF_ProcessAck;
  apsfSendOsalMsg = &APSF_SendOsalMsg;
}

/*********************************************************************
 * @fn      APSF_AddObj
 *
 * @brief   Adds a block message to the list of TX and RX messages.
 *
 * @param   pObj - Pointer to type dpecific data to store in the block.
 *          type - The block represents an RX or TX.
 *
 * @return  none
 */
static void APSF_AddObj(APSF_Header_t *pHdr, uint8 type)
{
  APSF_Header_t *pTmp;

  HAL_ASSERT(pHdr);

  // Insert the block message in the list
  if (!APSF_ObjList)
    APSF_ObjList = pHdr;
  else
  {
    pTmp = APSF_ObjList;
    while (pTmp->pNext)
      pTmp = pTmp->pNext;

    pTmp->pNext = pHdr;
  }

  // Initialize the header
  pHdr->pNext = NULL;
  pHdr->type = type;
}

/*********************************************************************
 * @fn      APSF_FindObj
 *
 * @brief   Finds a block message in the list of TX and RX messages.
 *
 * @param   apsCount - The APS Count ID of the fragmented data.
 *          type - The opject represents an RX or TX.
 *
 * @return  none
 */
static APSF_Header_t *APSF_FindObj(uint8 apsCount, uint16 shortAddr, uint8 type)
{
  APSF_Header_t *pHdr = APSF_ObjList;

  while (pHdr)
  {
    if ((pHdr->apsCount == apsCount) &&
        (pHdr->shortAddr == shortAddr) &&
        (pHdr->type == type))
      return pHdr;

    pHdr = pHdr->pNext;
  }

  return NULL;
}

/*********************************************************************
 * @fn      APSF_FreeObj
 *
 * @brief   Frees a block message from the list of TX and RX messages.
 *
 * @param   pHdr - Block to free.
 *
 * @return  Block previous to pHdr.
 */
static APSF_Header_t *APSF_FreeObj(APSF_Header_t *pHdr)
{
  APSF_Header_t *pTmp = APSF_ObjList;

  // Remove the object from the list
  if (pHdr == APSF_ObjList)
    pTmp = APSF_ObjList = APSF_ObjList->pNext;
  else
  {
    while (pTmp)
    {
      if (pTmp->pNext == pHdr)
      {
        pTmp->pNext = pHdr->pNext;
        break;
      }
      pTmp = pTmp->pNext;
    }
  }

  // Free the object
  osal_mem_free(pHdr);

  return pTmp;
}

/*********************************************************************
 * @fn      APSF_SendFragmented
 *
 * @brief   Puts a afMultiHdr into the list of block messages.
 *
 * @param   p_hdr - afMultiHdr_t to be transmitted.
 *          profileID - The profile ID of the endpoint
 *
 * @return  none
 */
afStatus_t APSF_SendFragmented(APSDE_DataReq_t *pReq)
{
  APSF_TxObj_t *pTx;
  afDataReqMTU_t mtu;
  uint16  dstAddr;

  if ( pReq->dstAddr.addrMode == AddrNotPresent ||
       pReq->dstAddr.addrMode == AddrGroup      ||
       NLME_IsAddressBroadcast(pReq->dstAddr.addr.shortAddr) != ADDR_NOT_BCAST )
  {
    return afStatus_INVALID_PARAMETER;
  }

  // Find out the destination short address
  if ( pReq->dstAddr.addrMode == Addr64Bit )
  {
    if ( APSME_LookupNwkAddr( pReq->dstAddr.addr.extAddr, &dstAddr ) == FALSE )
    {
      return afStatus_INVALID_PARAMETER;
    }
  }
  else
  {
    dstAddr = pReq->dstAddr.addr.shortAddr;
  }

  // Allocate a TX object
  pTx = osal_mem_alloc(sizeof(APSF_TxObj_t) + pReq->asduLen);

  if (pTx)
  {
    afAPSF_Config_t cfg;

    // Initialize the pTx
    osal_memset(pTx, 0, sizeof(APSF_TxObj_t));

    afAPSF_ConfigGet(pReq->srcEP, &cfg);
    pTx->frameDelay = cfg.frameDelay;
    pTx->windowSize = cfg.windowSize;

    // Add the TX object to the list
    APSF_AddObj((APSF_Header_t*) pTx, APSF_TX_TYPE);

    // Copy the data request information
    osal_memcpy(&pTx->dataReq, pReq, sizeof(APSDE_DataReq_t));

    // Copy the msg data
    pTx->pData = (uint8*) (pTx + 1);
    pTx->len = pReq->asduLen;
    osal_memcpy(pTx->pData, pReq->asdu, pReq->asduLen);

    pTx->hdr.shortAddr = dstAddr;
    pTx->hdr.apsCount = APS_Counter++;
    pTx->hdr.timeStamp = (uint16) osal_GetSystemClock();
    pTx->sendMask = pTx->ackBits = 0xff << pTx->windowSize;

    mtu.kvp = FALSE;

    if (pReq->txOptions & APS_TX_OPTIONS_SECURITY_ENABLE)
      mtu.aps.secure = TRUE;
    else
      mtu.aps.secure = FALSE;

    pTx->blockSize = afDataReqMTU(&mtu) - APS_XFRAME_CTRL_FIELD_LEN - APS_BLOCK_CNT_FIELD_LEN;

    // Set the total number of blocks
    pTx->hdr.numBlocks = pTx->len / pTx->blockSize;
    if (pTx->len % pTx->blockSize)
    {
      pTx->hdr.numBlocks++;
    }

    // Enable fragmentation permitted tx option
    pTx->dataReq.txOptions |= APS_TX_OPTIONS_PERMIT_FRAGMENT;

    // Kick the scheduler
    osal_set_event(APSF_taskID, APSF_SCHED_EVT);

    return afStatus_SUCCESS;
  }

  return afStatus_MEM_FAIL;
}

/*********************************************************************
 * @fn      APSF_TxNextFrame
 *
 * @brief   Goes through the list of block messages and transmits
 *          the next frame.
 *
 * @param   none
 *
 * @return  none
 */
static void APSF_TxNextFrame(APSF_TxObj_t *pTx)
{
  uint16 len;
  uint8 block;
  uint8 i;

  pTx->hdr.timeStamp = (uint16) osal_GetSystemClock();

  if (pTx->retry < zgApscMaxFrameRetries)
  {
    if (pTx->sendMask == 0xff)
    {
      // No ack received.  Retry
      pTx->retry++;
      pTx->sendMask = pTx->ackBits;
    }

    // Determine the block to send
    block = pTx->firstBlk;
    for ( i = 0; i < pTx->windowSize; i++ )
    {
      if ( (pTx->sendMask ^ 0xFF) & (1 << i) )
      {
        block += i;
        pTx->sendMask |= (1 << i);
        break;
      }
    }

    // Calculate the block length
    len = pTx->len - (block * pTx->blockSize);

    if (len >= pTx->blockSize)
    {
      len = pTx->blockSize;
    }
    else
    {
      // Last block, fill in all bits in the sent mask
      pTx->sendMask = 0xff;
    }

    if (pTx->sendMask == 0xff)
    {
      // Last block in the window.  Request an ack
      pTx->hdr.duration = APSF_ACK_WAIT;
    }
    else
    {
      pTx->hdr.duration = pTx->frameDelay;
    }

    if (block == 0)
    {
      // First fragment
      pTx->dataReq.txOptions |= APS_TX_OPTIONS_FIRST_FRAGMENT;

      // blkCount contains total number of blocks
      pTx->dataReq.blkCount = pTx->hdr.numBlocks;

      // Set pReq asdu ptr to beginnning
      pTx->dataReq.asdu = pTx->pData;
    }
    else
    {
      // Not first fragment
      pTx->dataReq.txOptions &= ~APS_TX_OPTIONS_FIRST_FRAGMENT;

      // Set the block
      pTx->dataReq.blkCount = block;

      // Set pReq asdu ptr to data to send
      pTx->dataReq.asdu = pTx->pData + block * pTx->blockSize;
    }

    pTx->dataReq.apsCount = pTx->hdr.apsCount;
    pTx->dataReq.asduLen = (uint8) len;

    // For error testing
    if ( pAPSF_IgnoreBlock && pAPSF_IgnoreBlock( block ) )
      return; // Don't send the packet

    // Send the block
    APSDE_DataReq(&pTx->dataReq);
  }
  else
  {
    // Max retry reached, send DATA.confirm failure
    afDataConfirm(pTx->dataReq.srcEP, pTx->dataReq.transID, ZMacNoACK);
    APSF_FreeObj((APSF_Header_t*) pTx);
  }
}

/*********************************************************************
 * @fn      APSF_Schedule
 *
 * @brief   Goes through the list of block messages, picks the next
 *          block to transmit, and timeout any old RX block messages.
 *
 * @param   none
 *
 * @return  none
 */
static void APSF_Schedule( void )
{
  APSF_Header_t *pHdr = APSF_ObjList;
  uint16 sysClk = (uint16) osal_GetSystemClock();
  uint16 nextTime = 0xffff;
  uint16 duration;

  // Go through list
  while (pHdr)
  {
    duration = sysClk - pHdr->timeStamp;

    if (duration >= pHdr->duration)
    {
      // RX or TX Object
      if (pHdr->type == APSF_RX_TYPE)
      {
        APSF_RxObj_t *pRx = (APSF_RxObj_t*) pHdr;

        // The task owns the pMsg id the rx completed sucessfully, else free the pMsg
        if (pRx->taskID != 0xff)
        {
          osal_msg_deallocate((uint8*) pRx->pMsg);
        }

        // Free the RX Obj
        APSF_FreeObj(pHdr);
      }
      else
      {
        // Handle TX timeout
        APSF_TxNextFrame((APSF_TxObj_t*) pHdr);
      }

      // Only process one Object at a time without context switch
      osal_set_event(APSF_taskID, APSF_SCHED_EVT);
      return;
    }
    else
    {
      // Keep track of smallest time for next scheduled event
      if (pHdr->duration - duration < nextTime)
        nextTime = pHdr->duration - duration;
    }

    pHdr = pHdr->pNext;
  }

  // Start timer for next escheduled event
  if (nextTime != 0xffff)
    osal_start_timerEx(APSF_taskID, APSF_SCHED_EVT, nextTime);
}

/*********************************************************************
 * @fn      APSF_ProccessAck
 *
 * @brief   Processes an ack on Fragmented APS data.
 *
 * @param   aff - Frame format.
 *          status - The status of the operation.
 *
 * @return  none
 */
void APSF_ProcessAck(aps_FrameFormat_t *aff, uint16 srcAddr, uint8 status)
{
  APSF_TxObj_t *pTx = (APSF_TxObj_t*) APSF_FindObj(aff->ApsCounter, srcAddr, APSF_TX_TYPE);

  if (pTx && status == ZSuccess)
  {
    // Verify the ack matches the window
    if (aff->BlkCount != pTx->firstBlk)
      return;

    if ((aff->BlkCount + pTx->windowSize >= pTx->hdr.numBlocks) &&
        (aff->AckBits == 0xff))
    {
      // TX complete. Send data confirm and free the TX object
      afDataConfirm(pTx->dataReq.srcEP, pTx->dataReq.transID, status);
      APSF_FreeObj((APSF_Header_t*) pTx);
    }
    else
    {
      // Check if we are moving to the next window
      if (aff->AckBits == 0xff)
      {
        pTx->ackBits = pTx->sendMask = 0xff << pTx->windowSize;
        pTx->firstBlk += pTx->windowSize;
        pTx->retry = 0;
      }
      else
      {
        // Record the ack
        pTx->ackBits = aff->AckBits;
        pTx->sendMask = aff->AckBits;
      }

      // Set the delay
      pTx->hdr.timeStamp = (uint16) osal_GetSystemClock();
      pTx->hdr.duration = pTx->frameDelay;

      // Kick the scheduler
      osal_set_event(APSF_taskID, APSF_SCHED_EVT);
    }
  }
}

/*********************************************************************
 * @fn      APSF_AllocRxObj
 *
 * @brief   Allocates an object used to track the RX fragmented data.
 *
 * @param   aff - APS frame format
 *          SrcAddress - Source address of data.
 *          sig - Link quality of received data.
 *          SecurityUse - Passed in from APS
 *          timestamp - when the message arrived
 *
 * @return  none
 */
static APSF_Header_t *APSF_AllocRxObj(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                               NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp )
{
  uint16 msgLen;
  afIncomingMSGPacket_t *MSGpkt;
  endPointDesc_t *epDesc = NULL;
  APSF_RxObj_t *pRx;

  // Discard if the endpoint is not supported
  if (aff->DstEndPoint != 0xFF)
  {
    // Only look for an endpoint if the message is not a broadcast, fixed by Luoyiming 2026-06-08
    epDesc = afFindEndPointDesc(aff->DstEndPoint);
    if (!epDesc)
      return NULL;    
  }


  // Allocate an RX object
  pRx = osal_mem_alloc(sizeof(APSF_RxObj_t));

  if (pRx)
  {
    afAPSF_Config_t cfg;

    // Initialize the RX Object
    osal_memset(pRx, 0, sizeof(APSF_RxObj_t));

    afAPSF_ConfigGet(aff->DstEndPoint, &cfg);
    pRx->windowSize = cfg.windowSize;

    // Add the RX Object to the list
    APSF_AddObj((APSF_Header_t*) pRx, APSF_RX_TYPE);

    pRx->hdr.shortAddr = SrcAddress->addr.shortAddr;
    pRx->hdr.apsCount = aff->ApsCounter;
    pRx->ackBits = 0xff << pRx->windowSize;

    // Record the task ID
    if (aff->DstEndPoint == AF_BROADCAST_ENDPOINT)
    {
      // If DstEndPoint is a broadcast, set taskID to APSF_taskID and let APSF_ProcessEvent
      // send the message to all matching endpoints. Fixed by Luoyiming 2026-06-08
      pRx->taskID = APSF_taskID;
    }
    else
    {
      pRx->taskID = *epDesc->task_id;
    }
    
    // The block count on the first fragment contians the total number of blocks
    // The asduLength contains the length of each block ( not clear in spec ??? )
    pRx->fragSize = aff->asduLength;

    msgLen = sizeof(afIncomingMSGPacket_t) + aff->BlkCount * pRx->fragSize;
    pRx->pMsg = MSGpkt = (afIncomingMSGPacket_t*) osal_msg_allocate( msgLen );
    if (MSGpkt)
    {
      MSGpkt->hdr.event = AF_INCOMING_MSG_CMD;
      MSGpkt->hdr.status = afStatus_SUCCESS;
      MSGpkt->groupId = 0;
      MSGpkt->clusterId = aff->ClusterID;

      // Make an AF source address
      afCopyAddress ( &(MSGpkt->srcAddr), SrcAddress );
      MSGpkt->srcAddr.endPoint = aff->SrcEndPoint;
      // A fragmented message could not have been received via Inter-PAN.
      MSGpkt->srcAddr.panId = _NIB.nwkPanId;

      MSGpkt->macSrcAddr = aff->macSrcAddr;
      MSGpkt->macDestAddr = aff->macDestAddr;
      MSGpkt->endPoint = aff->DstEndPoint;
      MSGpkt->wasBroadcast = aff->wasBroadcast;
      MSGpkt->LinkQuality = sig->LinkQuality;
      MSGpkt->correlation = sig->correlation;
      MSGpkt->rssi = sig->rssi;
      MSGpkt->SecurityUse = SecurityUse;
      MSGpkt->timestamp = timestamp;

      MSGpkt->cmd.Data = (uint8 *) (MSGpkt + 1);
      MSGpkt->cmd.TransSeqNumber = aff->transID;
    }
    else
    {
      APSF_FreeObj((APSF_Header_t*)pRx);
      pRx = NULL;
    }
  }

  return ( (APSF_Header_t*) pRx );
}

/*********************************************************************
 * @fn      APSF_IncomingData
 *
 * @brief   Processes incomming fragmented APS data
 *
 * @param   aff - APS frame format
 *          SrcAddress - Source address of data.
 *          sig - Link quality of received data.
 *
 * @return  none
 */
static void APSF_IncomingData(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                      NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp)
{
  // Find or add header to the list
  APSF_RxObj_t *pRx;
  uint8 lastBlk = FALSE;
  uint8 block = aff->BlkCount;

    // For error testing
  if ( pAPSF_IgnoreBlock && pAPSF_IgnoreBlock( block ) )
    return; // Don't receive the packet

  pRx = (APSF_RxObj_t*) APSF_FindObj(aff->ApsCounter,
                                     SrcAddress->addr.shortAddr,
                                     APSF_RX_TYPE);

  if (pRx == NULL)
  {
    // Add to list if first fragment
    if (aff->XtndFrmCtrl & APS_XFC_FIRST_FRAG)
    {
      pRx = (APSF_RxObj_t*) APSF_AllocRxObj(aff, SrcAddress, sig,
                                            SecurityUse, timestamp);

      if (pRx)
      {
        // First fragment blkCount contains total number of blocks
        pRx->hdr.numBlocks = block;

        // Kick the scheduler
        osal_set_event(APSF_taskID, APSF_SCHED_EVT);
      }
    }
  }

  if (pRx)
  {
    // Record if last block of the transfer
    if (block == pRx->hdr.numBlocks - 1)
    {
      lastBlk = TRUE;
    }

    if (pRx->taskID != 0xff)
    {
      // Set block count to zero on first fragment
      if (aff->XtndFrmCtrl & APS_XFC_FIRST_FRAG)
      {
        block = 0;
      }

      // Check if the window has moved
      if (block >= pRx->firstBlk + pRx->windowSize)
      {
        pRx->firstBlk += pRx->windowSize;
        pRx->ackBits = 0xff << pRx->windowSize;
      }

      // Copy the data
      osal_memcpy(pRx->pMsg->cmd.Data + block * pRx->fragSize,
                  aff->asdu, aff->asduLength);

      // Set the ack bit
      pRx->ackBits |= 1 << (block % pRx->windowSize);

      if (lastBlk)
      {
        // Fill in all unused ack bits
        pRx->ackBits |= 0xff << ((block % pRx->windowSize) + 1);

        // Calcualte the actual length of the message
        pRx->pMsg->cmd.DataLength = pRx->fragSize * block + aff->asduLength;
      }

      // Check if the transfer is complete
      if (lastBlk && pRx->ackBits == 0xff)
      {
        // Send message to endpoint task
        osal_msg_send(pRx->taskID, (uint8*) pRx->pMsg);

        // The RX object must persist incase the last ack was lost.
        // Set the task ID to 0xff indicating the rx process is complete
        // then free the RX object in the timeout
        pRx->taskID = 0xff;
      }
      else
      {
        // Reset timer for RX timeout error
        pRx->hdr.timeStamp = (uint16) osal_GetSystemClock();
        pRx->hdr.duration = APSF_RX_TOUT;
      }
    }

    // Send ack on last block in the window, retry is complete, or on last block in transfer
    if (block == pRx->firstBlk + pRx->windowSize - 1 || pRx->ackBits == 0xff || lastBlk)
    {
      aff->BlkCount = pRx->firstBlk;
      aff->AckBits = pRx->ackBits;
      apsGenerateAck(SrcAddress->addr.shortAddr, aff);
    }
  }
}

/*********************************************************************
 * @fn      APSF_SendOsalMsg
 *
 * @brief   Sends a message to the APSF_Task
 *
 * @param   msgPtr - Message
 *
 * @return  none
 */
void APSF_SendOsalMsg(uint8 *msgPtr)
{
  osal_msg_send(APSF_taskID, msgPtr);
}

/*********************************************************************
 * @fn      Sim_ProcessEvent
 *
 * @brief   Task event processor.  This function
 *          is called to process all events for the task.  Events
 *          include timers, messages and any other user defined events.
 *
 * @param   task_id  - The OSAL assigned task ID.
 * @param   events - events to process.  This is a bit map and can
 *                   contain more than one event.
 *
 * @return  none
 */
UINT16 APSF_ProcessEvent(uint8 task_id, UINT16 events)
{
  afIncomingMSGPacket_t *MSGpkt;
  apsInMsg_t *pData;

  (void)task_id;  // Intentionally unreferenced parameter

  if (events & SYS_EVENT_MSG)
  {
    while ((MSGpkt = (afIncomingMSGPacket_t*) osal_msg_receive(APSF_taskID)) != NULL)
    {
      switch (MSGpkt->hdr.event)
      {
        case APS_INCOMING_MSG:
          pData = (apsInMsg_t*) MSGpkt;
          APSF_IncomingData(&pData->aff, &pData->SrcAddress,
                            &(pData->sig), pData->SecurityUse, pData->timestamp);

          break;

        case AF_INCOMING_MSG_CMD:
          {
            // If the message is a broadcast, send it to all endpoints that match the profile ID, fixed by Luoyiming 2026-06-08
            if (MSGpkt->endPoint == AF_BROADCAST_ENDPOINT)
            {
              // Broadcast message
              epList_t *pList = epList;
              while (pList)
              {
                if ( (pList->epDesc->AppProfId == aff->ProfileID) ||
                     ((pList->epDesc->endPoint != ZDO_EP) && ( aff->ProfileID == ZDO_WILDCARD_PROFILE_ID )) )
                {
                  afIncomingMSGPacket_t *pNewMsg = (afIncomingMSGPacket_t*) osal_msg_allocate( OSAL_MSG_LEN(MSGpkt) );
                  if (pNewMsg)
                  {
                    osal_memcpy(pNewMsg, MSGpkt, OSAL_MSG_LEN(MSGpkt));
                    osal_msg_send(*pList->epDesc->task_id, (uint8*) pNewMsg);
                  }
                }
                pList = pList->next;
              }
            }
          }
          break;

        default:
          break;
      }

      osal_msg_deallocate((uint8*) MSGpkt);
    }

    // Return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }

  if (events & APSF_SCHED_EVT)
  {
    APSF_Schedule();
    return (events ^ APSF_SCHED_EVT);
  }

  // Discard unknown events
  return 0;
}

/**************************************************************************************************
*/
