/******************************************************************************
  Filename:       zcl_ota.c
  Revised:        $Date: 2011-07-18 05:10:28 -0700 (Mon, 18 Jul 2011) $
  Revision:       $Revision: 26827 $

  Description:    Zigbee Cluster Library - Over-the-Air Upgrade Cluster ( OTA )


  Copyright 2010-2011 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 "ZComDef.h"
#include "OSAL.h"
#include "zcl.h"
#include "zcl_general.h"
#include "zcl_ota.h"
#include "ota_common.h"

#include "hal_lcd.h"
#include "hal_led.h"
#include "hal_ota.h"

#include "MT_OTA.h"
#include "ZDProfile.h"
#include "ZDObject.h"

#if defined ( INTER_PAN )
  #include "stub_aps.h"
#endif

#if defined OTA_MMO_SIGN
#include "ota_signature.h"
#endif

/******************************************************************************
 * MACROS
 */

/******************************************************************************
 * CONSTANTS
 */
#define OTA_MAX_TRANSACTIONS        4
#define OTA_TRANSACTION_EXPIRATION  1500

#define ZCL_OTA_HDR_LEN_OFFSET      6  // Header length location in OTA upgrade image
#define ZCL_OTA_STK_VER_OFFSET      18 // Stack version location in OTA upgrade image

/******************************************************************************
 * GLOBAL VARIABLES
 */
// OTA attribute variables
uint8 zclOTA_UpgradeServerID[Z_EXTADDR_LEN];
uint32 zclOTA_FileOffset = 0xFFFFFFFF;
uint32 zclOTA_CurrentFileVersion = 0xFFFFFFFF;
uint16 zclOTA_CurrentZigBeeStackVersion;
uint32 zclOTA_DownloadedFileVersion = 0xFFFFFFFF;
uint16 zclOTA_DownloadedZigBeeStackVersion = 0xFFFF;
uint8 zclOTA_ImageUpgradeStatus;


// Other OTA variables
uint16 zclOTA_ManufacturerId;                           // Manufacturer ID
uint16 zclOTA_ImageType;                                // Image type
afAddrType_t zclOTA_serverAddr;                         // Server address
uint8 zclOTA_AppTask = 0xFF;                            // Callback Task ID
zclOTA_QueryImageRspParams_t queryResponse;             // Global variable for sent query response

/******************************************************************************
 * LOCAL VARIABLES
 */
// Other OTA variables

// Task ID
static uint8 zclOTA_TaskID;

// Sequence number
static uint8 zclOTA_SeqNo = 0;

static uint8 zclOTA_Permit = TRUE;

#if defined OTA_MMO_SIGN
static OTA_MmoCtrl_t zclOTA_MmoHash;
static uint8 zclOTA_DataToHash[OTA_MMO_HASH_SIZE];
static uint8 zclOTA_HashPos;
static uint8 zclOTA_SignerIEEE[Z_EXTADDR_LEN];
static uint8 zclOTA_SignatureData[OTA_SIGNATURE_LEN];
static uint8 zclOTA_Certificate[OTA_CERTIFICATE_LEN];
#endif // OTA_MMO_SIGN

#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
static uint32 zclOTA_DownloadedImageSize;  // Downloaded image size
static uint16 zclOTA_HeaderLen;            // Image header length

static uint16 zclOTA_UpdateDelay;
static zclOTA_FileID_t zclOTA_CurrentDlFileId;

static uint16 zclOTA_ElementTag;
static uint32 zclOTA_ElementLen;
static uint32 zclOTA_ElementPos;

// Retry counters
static uint8 zclOTA_BlockRetry;
static uint8 zclOTA_UpgradeEndRetry;

static uint8 zclOTA_ClientPdState;

// OTA Header Magic Number Bytes
static const uint8 zclOTA_HdrMagic[] = {0x1E, 0xF1, 0xEE, 0x0B};
#endif // OTA_CLIENT

/******************************************************************************
 * LOCAL FUNCTIONS
 */
static ZStatus_t zclOTA_HdlIncoming( zclIncoming_t *pInMsg );

#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
static void zclOTA_StartTimer(uint16 eventId, uint32 minutes);
static ZStatus_t sendImageBlockReq(afAddrType_t *dstAddr);
static void zclOTA_ProcessZDOMsgs( zdoIncomingMsg_t *pMsg );
static void zclOTA_ImageBlockWaitExpired(void);
static void zclOTA_UpgradeComplete(uint8 status);
static uint8 zclOTA_CmpFileId(zclOTA_FileID_t *f1, zclOTA_FileID_t *f2);
static uint8 zclOTA_ProcessImageData(uint8 *pData, uint8 len);

static ZStatus_t zclOTA_SendQueryNextImageReq( afAddrType_t *dstAddr, zclOTA_QueryNextImageReqParams_t *pParams );
static ZStatus_t zclOTA_SendImageBlockReq( afAddrType_t *dstAddr, zclOTA_ImageBlockReqParams_t *pParams );
static ZStatus_t zclOTA_SendUpgradeEndReq( afAddrType_t *dstAddr, zclOTA_UpgradeEndReqParams_t *pParams );

static ZStatus_t zclOTA_ClientHdlIncoming( zclIncoming_t *pInMsg );
#endif // OTA_CLIENT

#if (defined OTA_SERVER) && (OTA_SERVER == TRUE)
static ZStatus_t zclOTA_SendQueryNextImageRsp( afAddrType_t *dstAddr, zclOTA_QueryImageRspParams_t *pParams );
static ZStatus_t zclOTA_SendImageBlockRsp( afAddrType_t *dstAddr, zclOTA_ImageBlockRspParams_t *pParams );
static ZStatus_t zclOTA_SendUpgradeEndRsp( afAddrType_t *dstAddr, zclOTA_UpgradeEndRspParams_t *pParams );
static ZStatus_t zclOTA_SendQuerySpecificFileRsp( afAddrType_t *dstAddr, zclOTA_QueryImageRspParams_t *pParams );

static ZStatus_t zclOTA_Srv_QueryNextImageReq(afAddrType_t *pSrcAddr, zclOTA_QueryNextImageReqParams_t *pParam);
static ZStatus_t zclOTA_Srv_ImageBlockReq(afAddrType_t *pSrcAddr, zclOTA_ImageBlockReqParams_t *pParam);
static ZStatus_t zclOTA_Srv_ImagePageReq(afAddrType_t *pSrcAddr, zclOTA_ImagePageReqParams_t *pParam);
static ZStatus_t zclOTA_Srv_UpgradeEndReq(afAddrType_t *pSrcAddr, zclOTA_UpgradeEndReqParams_t *pParam);
static ZStatus_t zclOTA_Srv_QuerySpecificFileReq(afAddrType_t *pSrcAddr, zclOTA_QuerySpecificFileReqParams_t *pParam);

static void zclOTA_ProcessNextImgRsp(uint8* pMSGpkt, zclOTA_FileID_t *pFileId, afAddrType_t *pAddr);
static void zclOTA_ProcessFileReadRsp(uint8* pMSGpkt, zclOTA_FileID_t *pFileId, afAddrType_t *pAddr);
static void zclOTA_ServerHandleFileSysCb(OTA_MtMsg_t* pMSGpkt);

static ZStatus_t zclOTA_ServerHdlIncoming( zclIncoming_t *pInMsg );
#endif // OTA_SERVER

/******************************************************************************
 * OTA ATTRIBUTE DEFINITIONS - Uses REAL cluster IDs
 */
#define ZCL_OTA_MAX_ATTRIBUTES          7
CONST zclAttrRec_t zclOTA_Attrs[ZCL_OTA_MAX_ATTRIBUTES] =
{
  {
    ZCL_CLUSTER_ID_OTA,                   // Cluster IDs - defined in the foundation (ie. zcl.h)
    {  // Attribute record
      ATTRID_UPGRADE_SERVER_ID,           // Attribute ID - Found in Cluster Library header (ie. zcl_general.h)
      ZCL_DATATYPE_IEEE_ADDR,             // Data Type - found in zcl.h
      ACCESS_CONTROL_READ,                // Variable access control - found in zcl.h
      (void *)&zclOTA_UpgradeServerID     // Pointer to attribute variable
    }
  },
  {
    ZCL_CLUSTER_ID_OTA,
    { // Attribute record
      ATTRID_FILE_OFFSET,
      ZCL_DATATYPE_UINT32,
      ACCESS_CONTROL_READ,
      (void *)&zclOTA_FileOffset
    }
  },
  {
    ZCL_CLUSTER_ID_OTA,
    { // Attribute record
      ATTRID_CURRENT_FILE_VERSION,
      ZCL_DATATYPE_UINT32,
      ACCESS_CONTROL_READ,
      (void *)&zclOTA_CurrentFileVersion
    }
  },
  {
    ZCL_CLUSTER_ID_OTA,
    { // Attribute record
      ATTRID_CURRENT_ZIGBEE_STACK_VERSION,
      ZCL_DATATYPE_UINT16,
      ACCESS_CONTROL_READ,
      (void *)&zclOTA_CurrentZigBeeStackVersion
    }
  },
  {
    ZCL_CLUSTER_ID_OTA,
    { // Attribute record
      ATTRID_DOWNLOADED_FILE_VERSION,
      ZCL_DATATYPE_UINT32,
      ACCESS_CONTROL_READ,
      (void *)&zclOTA_DownloadedFileVersion
    }
  },
  {
    ZCL_CLUSTER_ID_OTA,
    { // Attribute record
      ATTRID_DOWNLOADED_ZIGBEE_STACK_VERSION,
      ZCL_DATATYPE_UINT16,
      ACCESS_CONTROL_READ,
      (void *)&zclOTA_DownloadedZigBeeStackVersion
    }
  },
  {
    ZCL_CLUSTER_ID_OTA,
    { // Attribute record
      ATTRID_IMAGE_UPGRADE_STATUS,
      ZCL_DATATYPE_UINT8,
      ACCESS_CONTROL_READ,
      (void *)&zclOTA_ImageUpgradeStatus
    }
  }
};

/******************************************************************************
 * OTA SIMPLE DESCRIPTOR
 */
#ifndef OTA_HA
#define ZCL_OTA_MAX_OPTIONS 1
zclOptionRec_t zclOta_Options[ZCL_OTA_MAX_OPTIONS] =
{
  {
    ZCL_CLUSTER_ID_OTA,
    ( AF_EN_SECURITY | AF_ACK_REQUEST ),
  },
};
#endif // OTA_HA

#if defined OTA_SERVER && (OTA_SERVER == TRUE)

// Cluster ID list for match descriptor of the OTA Server.
#define ZCL_OTA_MAX_INCLUSTERS       1
const cId_t zclOTA_InClusterList[ZCL_OTA_MAX_INCLUSTERS] =
{
  ZCL_CLUSTER_ID_OTA
};
#else

#define ZCL_OTA_MAX_INCLUSTERS        0
#define zclOTA_InClusterList          NULL

#endif // OTA_SERVER

#if defined OTA_CLIENT && (OTA_CLIENT == TRUE)

// Cluster ID list for match descriptor of the OTA Client.
#define ZCL_OTA_MAX_OUTCLUSTERS      1
const cId_t zclOTA_OutClusterList[ZCL_OTA_MAX_OUTCLUSTERS] =
{
  ZCL_CLUSTER_ID_OTA
};

#else

#define ZCL_OTA_MAX_OUTCLUSTERS       0
#define zclOTA_OutClusterList         NULL

#endif // OTA_CLIENT

SimpleDescriptionFormat_t zclOTA_SimpleDesc =
{
  ZCL_OTA_ENDPOINT,                 //  int Endpoint;
  ZCL_OTA_SAMPLE_PROFILE_ID,        //  uint16 AppProfId[2];
  ZCL_OTA_SAMPLE_DEVICEID,          //  uint16 AppDeviceId[2];
  ZCL_OTA_DEVICE_VERSION,           //  int   AppDevVer:4;
  ZCL_OTA_FLAGS,                    //  int   AppFlags:4;
  ZCL_OTA_MAX_INCLUSTERS,           //  byte  AppNumInClusters;
  (cId_t *)zclOTA_InClusterList,    //  byte *pAppInClusterList;
  ZCL_OTA_MAX_OUTCLUSTERS,          //  byte  AppNumInClusters;
  (cId_t *)zclOTA_OutClusterList    //  byte *pAppInClusterList;
};

// Endpoint for OTA Cluster
static endPointDesc_t zclOTA_Ep =
{
  ZCL_OTA_ENDPOINT,
  &zcl_TaskID,
  (SimpleDescriptionFormat_t *)&zclOTA_SimpleDesc,
  (afNetworkLatencyReq_t) 0
};

/******************************************************************************
 * @fn      zclOTA_PermitOta
 *
 * @brief   Called to enable/disable OTA operation.
 *
 * @param   permit - TRUE to enable OTA, FALSE to diable OTA
 *
 * @return  none
 */
void zclOTA_PermitOta(uint8 permit)
{
  zclOTA_Permit = permit;
}

/******************************************************************************
 * @fn      zclOTA_Register
 *
 * @brief   Called by an application to register for callback messages
 *          from the OTA.
 *
 * @param   applicationTaskId - Application Task ID
 *
 * @return  none
 */
void zclOTA_Register(uint8 applicationTaskId)
{
  zclOTA_AppTask = applicationTaskId;
}

/******************************************************************************
 * @fn      zclOTA_RequestNextUpdate
 *
 * @brief   Called by an application after discovery of the OTA server
 *          to initiate the query next image of the OTA server.
 *
 * @param   srvAddr - Short address of the server
 * @param   srvEndPoint - Endpoint on the server
 *
 * @return  ZStatus_t
 */
void zclOTA_RequestNextUpdate(uint16 srvAddr, uint8 srvEndPoint)
{
  // Record the server address
  zclOTA_serverAddr.addrMode = afAddr16Bit;
  zclOTA_serverAddr.endPoint = srvEndPoint;
  zclOTA_serverAddr.addr.shortAddr = srvAddr;

  // Set an event to query the server
  osal_set_event(zclOTA_TaskID, ZCL_OTA_QUERY_SERVER_EVT);
}

/******************************************************************************
 * @fn      zclOTA_Init
 *
 * @brief   Call to initialize the OTA Client Task
 *
 * @param   task_id
 *
 * @return  none
 */
void zclOTA_Init( uint8 task_id )
{
  zclOTA_TaskID = task_id;

  // Register for the cluster endpoint
  afRegister( &zclOTA_Ep );

  // Register as a ZCL Plugin
  zcl_registerPlugin( ZCL_CLUSTER_ID_OTA,
                      ZCL_CLUSTER_ID_OTA,
                      zclOTA_HdlIncoming );

  // Initialize attribute variables
  zclOTA_CurrentZigBeeStackVersion = OTA_STACK_VER_PRO;
  zclOTA_ImageUpgradeStatus = OTA_STATUS_NORMAL;

  // Register attribute list
  zcl_registerAttrList( ZCL_OTA_ENDPOINT,
                        ZCL_OTA_MAX_ATTRIBUTES,
                        zclOTA_Attrs );
#ifndef OTA_HA
  // Register the application's cluster option list
  zcl_registerClusterOptionList( ZCL_OTA_ENDPOINT, ZCL_OTA_MAX_OPTIONS, zclOta_Options );
#endif // OTA_HA

  // Register with the ZDO to receive Network Address Responses
  ZDO_RegisterForZDOMsg(task_id, IEEE_addr_rsp);

  // The default upgradeServerID is FF:FF:FF:FF:FF:FF:FF:FF
  osal_memset(zclOTA_UpgradeServerID, 0xFF, sizeof(zclOTA_UpgradeServerID));

#if defined (OTA_SERVER) && (OTA_SERVER == TRUE)

  // Register with the files system
  MT_OtaRegister(task_id);

#endif // OTA_SERVER
}

/******************************************************************************
 * @fn          zclOTA_event_loop
 *
 * @brief       Event Loop Processor for OTA Client task.
 *
 * @param       task_id - TaskId
 *              events - events
 *
 * @return      Unprocessed event bits
 */
uint16 zclOTA_event_loop( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;

  if ( events & SYS_EVENT_MSG )
  {
    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( task_id )) )
    {
      switch ( MSGpkt->hdr.event )
      {
#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
      case ZDO_CB_MSG:
        zclOTA_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
        break;
#endif // OTA_CLIENT

#if (defined OTA_SERVER) && (OTA_SERVER == TRUE)
      case MT_SYS_OTA_MSG:
        zclOTA_ServerHandleFileSysCb((OTA_MtMsg_t*)MSGpkt);
        break;
#endif
      default:
        break;
      }

      // Release the memory
      osal_msg_deallocate( (uint8 *)MSGpkt );
    }

    // return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }

#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
  if ( events & ZCL_OTA_IMAGE_BLOCK_WAIT_EVT )
  {
    // If the time has expired, perform the required action
    if (zclOTA_UpdateDelay == 0)
    {
      zclOTA_ImageBlockWaitExpired();
    }
    else
    {
      // Decrement the number of minutes to wait and reset the timer
      zclOTA_UpdateDelay--;
      osal_start_timerEx(zclOTA_TaskID, ZCL_OTA_IMAGE_BLOCK_WAIT_EVT, 60000);
    }

    return ( events ^ ZCL_OTA_IMAGE_BLOCK_WAIT_EVT );
  }

  if ( events & ZCL_OTA_UPGRADE_WAIT_EVT )
  {
    // If the time has expired, perform the required action
    if (zclOTA_UpdateDelay == 0)
    {
      if (zclOTA_ImageUpgradeStatus == OTA_STATUS_COUNTDOWN)
      {
        zclOTA_UpgradeComplete(ZSuccess);
      }
      else if (zclOTA_ImageUpgradeStatus == OTA_STATUS_UPGRADE_WAIT)
      {
        if (++zclOTA_UpgradeEndRetry > OTA_MAX_END_REQ_RETRIES)
        {
          // If we have not heard from the server for N retries, perform the upgrade
          zclOTA_UpgradeComplete(ZSuccess);
        }
        else
        {
          // Send another update end request
          zclOTA_UpgradeEndReqParams_t  req;

          req.status = ZSuccess;
          osal_memcpy(&req.fileId, &zclOTA_CurrentDlFileId, sizeof(zclOTA_FileID_t));

          zclOTA_SendUpgradeEndReq(&zclOTA_serverAddr, &req);

          // Restart the timer for another hour
          zclOTA_StartTimer(ZCL_OTA_UPGRADE_WAIT_EVT, 3600);
        }
      }
    }
    else
    {
      // Decrement the number of minutes to wait and reset the timer
      zclOTA_UpdateDelay--;
      osal_start_timerEx(zclOTA_TaskID, ZCL_OTA_UPGRADE_WAIT_EVT, 60000);
    }

    return ( events ^ ZCL_OTA_UPGRADE_WAIT_EVT );
  }

  if (events & ZCL_OTA_IMAGE_QUERY_TO_EVT )
  {
    if (zclOTA_ImageUpgradeStatus == OTA_STATUS_NORMAL)
    {
      // Notify the application task of the timeout waiting for the download to start
      zclOTA_CallbackMsg_t *pMsg;

      pMsg = (zclOTA_CallbackMsg_t*) osal_msg_allocate(sizeof(zclOTA_CallbackMsg_t));

      if (pMsg)
      {
        pMsg->hdr.event = ZCL_OTA_CALLBACK_IND;
        pMsg->hdr.status = ZFailure;
        pMsg->ota_event = ZCL_OTA_START_CALLBACK;

        osal_msg_send(zclOTA_AppTask, (uint8*) pMsg);
      }
    }

    return ( events ^ ZCL_OTA_IMAGE_QUERY_TO_EVT );
  }

  if (events & ZCL_OTA_BLOCK_RSP_TO_EVT )
  {
    // We timed out waiting for a Block Response
    if (++zclOTA_BlockRetry > OTA_MAX_BLOCK_RETRIES)
    {
      // Send a failed update end request
      zclOTA_UpgradeEndReqParams_t  req;

      req.status = ZOtaAbort;
      osal_memcpy(&req.fileId, &zclOTA_CurrentDlFileId, sizeof(zclOTA_FileID_t));

      zclOTA_SendUpgradeEndReq(&zclOTA_serverAddr, &req);

      zclOTA_UpgradeComplete(ZOtaAbort);
    }
    else
    {
      // Send another block request
      sendImageBlockReq(&zclOTA_serverAddr);
    }

    return ( events ^ ZCL_OTA_BLOCK_RSP_TO_EVT );
  }

  if (events & ZCL_OTA_QUERY_SERVER_EVT )
  {
    zclOTA_QueryNextImageReqParams_t queryParams;

    queryParams.fieldControl = 0;
    queryParams.fileId.manufacturer = OTA_MANUFACTURER_ID;
    queryParams.fileId.type = OTA_TYPE_ID;
    queryParams.fileId.version = zclOTA_CurrentFileVersion;

    zclOTA_SendQueryNextImageReq(&zclOTA_serverAddr, &queryParams);

    // Set a timer to wait for the response
    osal_start_timerEx(zclOTA_TaskID, ZCL_OTA_IMAGE_QUERY_TO_EVT, 10000);

    return ( events ^ ZCL_OTA_QUERY_SERVER_EVT );
  }
#endif // OTA_CLIENT

  // Discard unknown events
  return 0;
}

/******************************************************************************
 * @fn      zclOTA_HdlIncoming
 *
 * @brief   Callback from ZCL to process incoming Commands specific
 *          to this cluster library or Profile commands for attributes
 *          that aren't in the attribute list
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_HdlIncoming( zclIncoming_t *pInMsg )
{
  ZStatus_t stat = ZSuccess;

  if ( zcl_ClusterCmd( pInMsg->hdr.fc.type ) )
  {
    // Is this a manufacturer specific command?
    if ( pInMsg->hdr.fc.manuSpecific == 0 )
    {
      // Is command for server?
      if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) )
      {
#if (defined OTA_SERVER) && (OTA_SERVER == TRUE)
        stat = zclOTA_ServerHdlIncoming( pInMsg );
#else
        stat = ZCL_STATUS_UNSUP_CLUSTER_COMMAND;
#endif // OTA_SERVER
      }
      else // Else command is for client
      {
#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
        stat = zclOTA_ClientHdlIncoming( pInMsg );
#else
        stat = ZCL_STATUS_UNSUP_CLUSTER_COMMAND;
#endif // OTA_CLIENT
      }
    }
    else
    {
      // We don't support any manufacturer specific command.
      stat = ZCL_STATUS_UNSUP_MANU_CLUSTER_COMMAND;
    }
  }
  else
  {
    // Handle all the normal (Read, Write...) commands -- should never get here
    stat = ZFailure;
  }

  return ( stat );
}

#if (defined OTA_SERVER) && (OTA_SERVER == TRUE)
/******************************************************************************
 * @fn      zclOTA_SendImageNotify
 *
 * @brief   Send an OTA Image Notify message.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendImageNotify( afAddrType_t *dstAddr,
                                  zclOTA_ImageNotifyParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_IMAGE_NOTIFY];
  uint8 *pBuf = buf;

  *pBuf++ = pParams->payloadType;
  *pBuf++ = pParams->queryJitter;
  if (pParams->payloadType >= NOTIFY_PAYLOAD_JITTER_MFG)
  {
    *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
    *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
  }
  if (pParams->payloadType >= NOTIFY_PAYLOAD_JITTER_MFG_TYPE)
  {
    *pBuf++ = LO_UINT16(pParams->fileId.type);
    *pBuf++ = HI_UINT16(pParams->fileId.type);
  }
  if (pParams->payloadType == NOTIFY_PAYLOAD_JITTER_MFG_TYPE_VERS)
  {
    pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);
  }

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_IMAGE_NOTIFY, TRUE,
                            ZCL_FRAME_SERVER_CLIENT_DIR, TRUE, 0,
                            zclOTA_SeqNo++, (uint16) (pBuf - buf), buf );

  return status;
}

/******************************************************************************
 * @fn      zclOTA_SendQueryNextImageRsp
 *
 * @brief   Send an OTA Query Next Image Response message.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendQueryNextImageRsp( afAddrType_t *dstAddr,
                                        zclOTA_QueryImageRspParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_QUERY_NEXT_IMAGE_RSP];
  uint8 *pBuf = buf;

  *pBuf++ = pParams->status;
  if (pParams->status == ZCL_STATUS_SUCCESS)
  {
    *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
    *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
    *pBuf++ = LO_UINT16(pParams->fileId.type);
    *pBuf++ = HI_UINT16(pParams->fileId.type);
    pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);
    pBuf = osal_buffer_uint32(pBuf, pParams->imageSize);
  }

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_QUERY_NEXT_IMAGE_RSP, TRUE,
                            ZCL_FRAME_SERVER_CLIENT_DIR, TRUE, 0,
                            zclOTA_SeqNo++, (uint16) (pBuf - buf), buf );

  return status;
}

/******************************************************************************
 * @fn      zclOTA_SendImageBlockRsp
 *
 * @brief   Send an OTA Image Block Response mesage.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendImageBlockRsp( afAddrType_t *dstAddr,
                                    zclOTA_ImageBlockRspParams_t *pParams )
{
  uint8 *buf;
  uint8 *pBuf;
  ZStatus_t status;
  uint8 len;

  if (pParams->status == ZCL_STATUS_SUCCESS)
  {
    len = PAYLOAD_MAX_LEN_IMAGE_BLOCK_RSP + pParams->rsp.success.dataSize;
  }
  else if (pParams->status == ZCL_STATUS_WAIT_FOR_DATA)
  {
    len = PAYLOAD_MIN_LEN_IMAGE_BLOCK_WAIT;
  }
  else
  {
    len = 1;
  }

  buf = osal_mem_alloc( len );

  if ( buf == NULL )
  {
    return (ZMemError);
  }

  pBuf = buf;
  *pBuf++ = pParams->status;

  if (pParams->status == ZCL_STATUS_SUCCESS)
  {
    *pBuf++ = LO_UINT16(pParams->rsp.success.fileId.manufacturer);
    *pBuf++ = HI_UINT16(pParams->rsp.success.fileId.manufacturer);
    *pBuf++ = LO_UINT16(pParams->rsp.success.fileId.type);
    *pBuf++ = HI_UINT16(pParams->rsp.success.fileId.type);
    pBuf = osal_buffer_uint32(pBuf, pParams->rsp.success.fileId.version);
    pBuf = osal_buffer_uint32(pBuf, pParams->rsp.success.fileOffset);
    *pBuf++ = pParams->rsp.success.dataSize;
    osal_memcpy(pBuf, pParams->rsp.success.pData, pParams->rsp.success.dataSize);
  }
  else if (pParams->status == ZCL_STATUS_WAIT_FOR_DATA)
  {
    pBuf = osal_buffer_uint32(pBuf, pParams->rsp.wait.currentTime);
    pBuf = osal_buffer_uint32(pBuf, pParams->rsp.wait.requestTime);
  }

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_IMAGE_BLOCK_RSP, TRUE,
                            ZCL_FRAME_SERVER_CLIENT_DIR, TRUE, 0,
                            zclOTA_SeqNo++, len, buf );

  osal_mem_free(buf);

  return status;
}

/******************************************************************************
 * @fn      zclOTA_SendUpgradeEndRsp
 *
 * @brief   Send an OTA Upgrade End Response mesage.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendUpgradeEndRsp( afAddrType_t *dstAddr,
                                    zclOTA_UpgradeEndRspParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_UPGRADE_END_RSP];
  uint8 *pBuf = buf;

  *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = LO_UINT16(pParams->fileId.type);
  *pBuf++ = HI_UINT16(pParams->fileId.type);
  pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);
  pBuf = osal_buffer_uint32(pBuf, pParams->currentTime);
  pBuf = osal_buffer_uint32(pBuf, pParams->upgradeTime);

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_UPGRADE_END_RSP, TRUE,
                            ZCL_FRAME_SERVER_CLIENT_DIR, TRUE, 0,
                            zclOTA_SeqNo++, PAYLOAD_MAX_LEN_UPGRADE_END_RSP, buf );

  return status;
}

/******************************************************************************
 * @fn      zclOTA_SendQuerySpecificFileRsp
 *
 * @brief   Send an OTA Query Specific File Response mesage.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendQuerySpecificFileRsp( afAddrType_t *dstAddr,
                                           zclOTA_QueryImageRspParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_QUERY_SPECIFIC_FILE_RSP];
  uint8 *pBuf = buf;

  *pBuf++ = pParams->status;
  if (pParams->status == ZCL_STATUS_SUCCESS)
  {
    *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
    *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
    *pBuf++ = LO_UINT16(pParams->fileId.type);
    *pBuf++ = HI_UINT16(pParams->fileId.type);
    pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);
    pBuf = osal_buffer_uint32(pBuf, pParams->imageSize);
  }

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_QUERY_SPECIFIC_FILE_RSP, TRUE,
                            ZCL_FRAME_SERVER_CLIENT_DIR, TRUE, 0,
                            zclOTA_SeqNo++, (uint16) (pBuf - buf), buf );

  return status;
}
#endif // OTA_SERVER

#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
/******************************************************************************
 * @fn      zclOTA_SendQueryNextImageReq
 *
 * @brief   Send an OTA Query Next Image Request mesage.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendQueryNextImageReq( afAddrType_t *dstAddr,
                                        zclOTA_QueryNextImageReqParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_QUERY_NEXT_IMAGE_REQ];
  uint8 *pBuf = buf;

  *pBuf++ = pParams->fieldControl;
  *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = LO_UINT16(pParams->fileId.type);
  *pBuf++ = HI_UINT16(pParams->fileId.type);
  pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);
  if (pParams->fieldControl == 1)
  {
    *pBuf++ = LO_UINT16(pParams->hardwareVersion);
    *pBuf++ = HI_UINT16(pParams->hardwareVersion);
  }

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_QUERY_NEXT_IMAGE_REQ, TRUE,
                            ZCL_FRAME_CLIENT_SERVER_DIR, FALSE, 0,
                            zclOTA_SeqNo++, (uint16) (pBuf - buf), buf );

  return status;
}

/******************************************************************************
 * @fn      zclOTA_SendImageBlockReq
 *
 * @brief   Send an OTA Image Block Request mesage.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendImageBlockReq( afAddrType_t *dstAddr,
                                    zclOTA_ImageBlockReqParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_IMAGE_BLOCK_REQ];
  uint8 *pBuf = buf;

  *pBuf++ = pParams->fieldControl;
  *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = LO_UINT16(pParams->fileId.type);
  *pBuf++ = HI_UINT16(pParams->fileId.type);
  pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);
  pBuf = osal_buffer_uint32(pBuf, pParams->fileOffset);
  *pBuf++ = pParams->maxDataSize;
  if (pParams->fieldControl == 1)
  {
    osal_cpyExtAddr(pBuf, pParams->nodeAddr);
    pBuf += Z_EXTADDR_LEN;
  }

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_IMAGE_BLOCK_REQ, TRUE,
                            ZCL_FRAME_CLIENT_SERVER_DIR, FALSE, 0,
                            zclOTA_SeqNo++, (uint16) (pBuf - buf), buf );

  return status;
}

/******************************************************************************
 * @fn      zclOTA_SendUpgradeEndReq
 *
 * @brief   Send an OTA Upgrade End Request mesage.
 *
 * @param   dstAddr - where you want the message to go
 * @param   pParams - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_SendUpgradeEndReq( afAddrType_t *dstAddr,
                                    zclOTA_UpgradeEndReqParams_t *pParams )
{
  ZStatus_t status;
  uint8 buf[PAYLOAD_MAX_LEN_UPGRADE_END_REQ];
  uint8 *pBuf = buf;

  *pBuf++ = pParams->status;
  *pBuf++ = LO_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = HI_UINT16(pParams->fileId.manufacturer);
  *pBuf++ = LO_UINT16(pParams->fileId.type);
  *pBuf++ = HI_UINT16(pParams->fileId.type);
   pBuf = osal_buffer_uint32(pBuf, pParams->fileId.version);

  status = zcl_SendCommand( ZCL_OTA_ENDPOINT, dstAddr, ZCL_CLUSTER_ID_OTA,
                            COMMAND_UPGRADE_END_REQ, TRUE,
                            ZCL_FRAME_CLIENT_SERVER_DIR, FALSE, 0,
                            zclOTA_SeqNo++, (uint16) (pBuf - buf), buf );

  return status;
}

/******************************************************************************
 * @fn      zclOTA_StartTimer
 *
 * @brief   Start a ZCL OTA timer.
 *
 * @param   eventId - OSAL event set on timer expiration
 * @param   seconds - timeout in seconds
 *
 * @return  None
 */
static void zclOTA_StartTimer(uint16 eventId, uint32 seconds)
{
  // Record the number of whole minutes to wait
  zclOTA_UpdateDelay = (seconds / 60);

  // Set a timer for the remaining seconds to wait.
  osal_start_timerEx(zclOTA_TaskID, eventId, (seconds % 60) * 1000);
}

/******************************************************************************
 * @fn      sendImageBlockReq
 *
 * @brief   Send an Image Block Request.
 *
 * @param   dstAddr - where you want the message to go
 *
 * @return  ZStatus_t
 */
static ZStatus_t sendImageBlockReq(afAddrType_t *dstAddr)
{
  zclOTA_ImageBlockReqParams_t req;

  req.fieldControl = 0;
  req.fileId.manufacturer = zclOTA_ManufacturerId;
  req.fileId.type = zclOTA_ImageType;
  req.fileId.version = zclOTA_DownloadedFileVersion;
  req.fileOffset = zclOTA_FileOffset;

  if (zclOTA_DownloadedImageSize - zclOTA_FileOffset < OTA_MAX_MTU)
  {
    req.maxDataSize = zclOTA_DownloadedImageSize - zclOTA_FileOffset;
  }
  else
  {
    req.maxDataSize = OTA_MAX_MTU;
  }

  // Start a timer waiting for a response
  osal_start_timerEx(zclOTA_TaskID, ZCL_OTA_BLOCK_RSP_TO_EVT, OTA_MAX_BLOCK_RSP_WAIT_TIME);

  return zclOTA_SendImageBlockReq( dstAddr, &req);
}

/******************************************************************************
 * @fn      zclOTA_ProcessImageData
 *
 * @brief   Process image data as it is received from the host.
 *
 * @param   pData - pointer to the data
 * @param   len - length of the data
 *
 * @return  status of the operation
 */
uint8 zclOTA_ProcessImageData(uint8 *pData, uint8 len)
{
  int8 i;
#if defined OTA_MMO_SIGN
  uint8 skipHash = FALSE;
#endif

  if (zclOTA_ImageUpgradeStatus != OTA_STATUS_IN_PROGRESS)
  {
    return ZCL_STATUS_ABORT;
  }

#if (defined HAL_LED) && (HAL_LED == TRUE)
  // Toggle an LED to indicate we received a new block
  HalLedSet(HAL_LED_2, HAL_LED_MODE_TOGGLE);
#endif

  // write data to secondary storage
  HalOTAWrite(zclOTA_FileOffset, pData, len, HAL_OTA_DL);

  for (i=0; i<len; i++)
  {
#if defined OTA_MMO_SIGN
    // Skip the hash if we are receiving the signature element
    if ((zclOTA_ClientPdState >= ZCL_OTA_PD_ELEM_LEN1_STATE) &&
        (zclOTA_ClientPdState <= ZCL_OTA_PD_ELEMENT_STATE))
    {
      if (zclOTA_ElementTag == OTA_ECDSA_SIGNATURE_TAG_ID)
      {
        skipHash = TRUE;
      }
    }
#endif

    switch (zclOTA_ClientPdState)
    {
    // verify header magic number
    case ZCL_OTA_PD_MAGIC_0_STATE:
      // Initialize control variables
#if defined OTA_MMO_SIGN
      osal_memset(&zclOTA_MmoHash, 0, sizeof(zclOTA_MmoHash));
      zclOTA_HashPos = 0;
#endif

      // Missing break intended
    case ZCL_OTA_PD_MAGIC_1_STATE:
    case ZCL_OTA_PD_MAGIC_2_STATE:
    case ZCL_OTA_PD_MAGIC_3_STATE:
      if (pData[i] != zclOTA_HdrMagic[zclOTA_ClientPdState])
      {
        return ZCL_STATUS_INVALID_IMAGE;
      }
      zclOTA_ClientPdState++;
      break;

    case ZCL_OTA_PD_HDR_LEN1_STATE:
      // get header length
      if (zclOTA_FileOffset == ZCL_OTA_HDR_LEN_OFFSET)
      {
        zclOTA_HeaderLen = pData[i];
        zclOTA_ClientPdState = ZCL_OTA_PD_HDR_LEN2_STATE;
      }
      break;

    case ZCL_OTA_PD_HDR_LEN2_STATE:
      zclOTA_HeaderLen |= (((uint16)pData[i]) << 8) & 0xFF00;
      zclOTA_ClientPdState = ZCL_OTA_PD_STK_VER1_STATE;
      break;

    case ZCL_OTA_PD_STK_VER1_STATE:
      // get stack version
      if (zclOTA_FileOffset == ZCL_OTA_STK_VER_OFFSET)
      {
        zclOTA_DownloadedZigBeeStackVersion = pData[i];
        zclOTA_ClientPdState = ZCL_OTA_PD_STK_VER2_STATE;
      }
      break;

    case ZCL_OTA_PD_STK_VER2_STATE:
      zclOTA_DownloadedZigBeeStackVersion |= (((uint16)pData[i]) << 8) & 0xFF00;
      zclOTA_ClientPdState = ZCL_OTA_PD_CONT_HDR_STATE;

      if (zclOTA_DownloadedZigBeeStackVersion != OTA_HDR_STACK_VERSION)
      {
        return ZCL_STATUS_INVALID_IMAGE;
      }
      break;

    case ZCL_OTA_PD_CONT_HDR_STATE:
      // Complete the header
      if (zclOTA_FileOffset == zclOTA_HeaderLen-1)
      {
        zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_TAG1_STATE;
      }
      break;

    case ZCL_OTA_PD_ELEM_TAG1_STATE:
      zclOTA_ElementTag = pData[i];
      zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_TAG2_STATE;
      break;

    case ZCL_OTA_PD_ELEM_TAG2_STATE:
      zclOTA_ElementTag |= (((uint16)pData[i]) << 8) & 0xFF00;
      zclOTA_ElementPos = 0;
      zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_LEN1_STATE;
      break;

    case ZCL_OTA_PD_ELEM_LEN1_STATE:
      zclOTA_ElementLen = pData[i];
      zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_LEN2_STATE;
      break;

    case ZCL_OTA_PD_ELEM_LEN2_STATE:
      zclOTA_ElementLen |= ((uint32)pData[i] << 8) & 0x0000FF00;
      zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_LEN3_STATE;
      break;

    case ZCL_OTA_PD_ELEM_LEN3_STATE:
      zclOTA_ElementLen |= ((uint32)pData[i] << 16) & 0x00FF0000;
      zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_LEN4_STATE;
      break;

    case ZCL_OTA_PD_ELEM_LEN4_STATE:
      zclOTA_ElementLen |= ((uint32)pData[i] << 24) & 0xFF000000;
      zclOTA_ClientPdState = ZCL_OTA_PD_ELEMENT_STATE;

      // Make sure the length of the element isn't bigger than the image
      if (zclOTA_ElementLen > (zclOTA_DownloadedImageSize - zclOTA_FileOffset))
      {
        return ZCL_STATUS_INVALID_IMAGE;
      }

#if defined OTA_MMO_SIGN
      if (zclOTA_ElementTag == OTA_ECDSA_SIGNATURE_TAG_ID)
      {
        if (zclOTA_ElementLen != OTA_SIGNATURE_LEN + Z_EXTADDR_LEN)
        {
          return ZCL_STATUS_INVALID_IMAGE;
        }
      }
      else if (zclOTA_ElementTag == OTA_ECDSA_CERT_TAG_ID)
      {
        if (zclOTA_ElementLen != OTA_CERTIFICATE_LEN)
        {
          return ZCL_STATUS_INVALID_IMAGE;
        }
      }
#endif
      break;

    case ZCL_OTA_PD_ELEMENT_STATE:
#if defined OTA_MMO_SIGN
      if (zclOTA_ElementTag == OTA_ECDSA_SIGNATURE_TAG_ID)
      {
        if (zclOTA_ElementPos < Z_EXTADDR_LEN)
          zclOTA_SignerIEEE[zclOTA_ElementPos] = pData[i];
        else
          zclOTA_SignatureData[zclOTA_ElementPos - Z_EXTADDR_LEN] = pData[i];
      }
      else if (zclOTA_ElementTag == OTA_ECDSA_CERT_TAG_ID)
      {
        zclOTA_Certificate[zclOTA_ElementPos] = pData[i];
      }
#endif

      if (++zclOTA_ElementPos == zclOTA_ElementLen)
      {
        // Element is complete
        if (zclOTA_ElementTag == OTA_UPGRADE_IMAGE_TAG_ID)
        {
          // The serial flash can take up to 25 ms before it is ready for a read
          uint32 k;
          for (k=0; k<0xffff; k++)
          {
            asm("NOP");
          }

          // When the image is complete, verify CRC
          if (HalOTAChkDL(HAL_OTA_CRC_OSET) != SUCCESS)
          {
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
            HalLcdWriteString("OTA CRC Fail", HAL_LCD_LINE_3);
#endif
            return ZCL_STATUS_INVALID_IMAGE;
          }
        }

        zclOTA_ClientPdState = ZCL_OTA_PD_ELEM_TAG1_STATE;
      }
      break;

    default:
      break;
    }

#if defined OTA_MMO_SIGN
    // We need to skip the hash calculation on the signature element.
    // When receiving a tag, we wait to receive the entire tag before
    // adding the byte to the hash buffer because it could be the tag
    // for the signature
    if (zclOTA_ClientPdState == ZCL_OTA_PD_ELEM_TAG2_STATE)
    {
      skipHash = TRUE;
    }
    else if (zclOTA_ClientPdState == ZCL_OTA_PD_ELEM_LEN1_STATE)
    {
      if (zclOTA_ElementTag != OTA_ECDSA_SIGNATURE_TAG_ID)
      {
        // This tag is not for the signature.
        // Put the Lower byte of the tag into the hash buffer now
        // The high byte will be processed as usual below
        zclOTA_DataToHash[zclOTA_HashPos++] = LO_UINT16(zclOTA_ElementTag);

        // When the buffer reaches OTA_MMO_HASH_SIZE, update the Hash
        if (zclOTA_HashPos == OTA_MMO_HASH_SIZE)
        {
          OTA_CalculateMmoR3(&zclOTA_MmoHash, zclOTA_DataToHash, OTA_MMO_HASH_SIZE, FALSE);
          zclOTA_HashPos = 0;
        }

        skipHash = FALSE;
      }
    }

    if (!skipHash)
    {
      // Maintain a buffer of data to hash
      zclOTA_DataToHash[zclOTA_HashPos++] = pData[i];

      // When the buffer reaches OTA_MMO_HASH_SIZE, update the Hash
      if (zclOTA_HashPos == OTA_MMO_HASH_SIZE)
      {
        OTA_CalculateMmoR3(&zclOTA_MmoHash, zclOTA_DataToHash, OTA_MMO_HASH_SIZE, FALSE);
        zclOTA_HashPos = 0;
      }
    }
#endif

    // Check if the download is complete
    if (++zclOTA_FileOffset >= zclOTA_DownloadedImageSize)
    {
      zclOTA_ImageUpgradeStatus = OTA_STATUS_COMPLETE;

#if defined OTA_MMO_SIGN
      // Complete the hash calcualtion
      OTA_CalculateMmoR3(&zclOTA_MmoHash, zclOTA_DataToHash, zclOTA_HashPos, TRUE);

      // Validate the hash
      if (OTA_ValidateSignature(zclOTA_MmoHash.hash, zclOTA_Certificate,
                                zclOTA_SignatureData, zclOTA_SignerIEEE) != ZSuccess)
        return ZCL_STATUS_INVALID_IMAGE;
#endif

      return ZSuccess;
    }
  }

  return ZSuccess;
}

/******************************************************************************
 * @fn      zclOTA_ProcessImageNotify
 *
 * @brief   Process received Image Notify command.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessImageNotify( zclIncoming_t *pInMsg )
{
  zclOTA_ImageNotifyParams_t  param;
  zclOTA_QueryNextImageReqParams_t req;
  uint8 *pData;

  // verify message length
  if ((pInMsg->pDataLen > PAYLOAD_MAX_LEN_IMAGE_NOTIFY) ||
      (pInMsg->pDataLen < PAYLOAD_MIN_LEN_IMAGE_NOTIFY))
  {
    // no further processing if invalid
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  // verify  in 'normal' state
  if ((zclOTA_Permit == FALSE) ||
      (zclOTA_ImageUpgradeStatus != OTA_STATUS_NORMAL))
  {
    return ZFailure;
  }

  // parse message
  pData = pInMsg->pData;
  param.payloadType = *pData++;
  param.queryJitter = *pData++;
  param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.version = osal_build_uint32( pData, 4 );

  // if message is broadcast
  if (pInMsg->msg->wasBroadcast)
  {
    // verify manufacturer
    if ((param.payloadType >= NOTIFY_PAYLOAD_JITTER_MFG) &&
        (param.fileId.manufacturer != zclOTA_ManufacturerId))
    {
      return ZSuccess;
    }

    // verify image type
    if ((param.payloadType >= NOTIFY_PAYLOAD_JITTER_MFG_TYPE) &&
        (param.fileId.type != zclOTA_ImageType))
    {
      return ZSuccess;
    }

    // verify version; if version matches ignore
    if ((param.payloadType >= NOTIFY_PAYLOAD_JITTER_MFG_TYPE_VERS) &&
        (param.fileId.version == zclOTA_CurrentFileVersion))
    {
      return ZSuccess;
    }

    // get random value and compare to query jitter
    if (((uint8) osal_rand() % 100) > param.queryJitter)
    {
      // if greater than query jitter ignore;
      return ZSuccess;
    }
  }

  // if unicast message, or broadcast and still made it here, send query next image
  req.fieldControl = 0;
  req.fileId.manufacturer = zclOTA_ManufacturerId;
  req.fileId.type = zclOTA_ImageType;
  req.fileId.version = zclOTA_CurrentFileVersion;
  zclOTA_SendQueryNextImageReq( &(pInMsg->msg->srcAddr), &req);

  return ZSuccess;
}

/******************************************************************************
 * @fn      zclOTA_ProcessQueryNextImageRsp
 *
 * @brief   Process received Query Next Image Response.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessQueryNextImageRsp( zclIncoming_t *pInMsg )
{
  zclOTA_QueryImageRspParams_t  param;
  uint8 *pData;
  uint8 status = ZFailure;

  // verify message length
  if ((pInMsg->pDataLen != PAYLOAD_MAX_LEN_QUERY_NEXT_IMAGE_RSP) &&
      (pInMsg->pDataLen != PAYLOAD_MIN_LEN_QUERY_NEXT_IMAGE_RSP))
  {
    // no further processing if invalid
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  // ignore message if in 'download in progress' state
  if (zclOTA_ImageUpgradeStatus == OTA_STATUS_IN_PROGRESS)
  {
    return ZSuccess;
  }

  // get status
  pData = pInMsg->pData;
  param.status = *pData++;

  // if status is success
  if (param.status == ZCL_STATUS_SUCCESS)
  {
    // parse message
    param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.fileId.version = osal_build_uint32( pData, 4 );
    pData += 4;
    param.imageSize = osal_build_uint32( pData, 4 );

    // verify manufacturer id and image type
    if ((param.fileId.type == zclOTA_ImageType) &&
        (param.fileId.manufacturer == zclOTA_ManufacturerId))
    {
      // store file version and image size
      zclOTA_DownloadedFileVersion = param.fileId.version;
      zclOTA_DownloadedImageSize = param.imageSize;

      // initialize other variables
      zclOTA_FileOffset = 0;
      zclOTA_ClientPdState = ZCL_OTA_PD_MAGIC_0_STATE;

      // set state to 'in progress'
      zclOTA_ImageUpgradeStatus = OTA_STATUS_IN_PROGRESS;

      // store server address
      zclOTA_serverAddr = pInMsg->msg->srcAddr;

      // Store the file ID
      osal_memcpy(&zclOTA_CurrentDlFileId, &param.fileId, sizeof(zclOTA_FileID_t));

      // send image block request
      sendImageBlockReq(&(pInMsg->msg->srcAddr));
      status = ZCL_STATUS_CMD_HAS_RSP;

      // Request the IEEE address of the server to put into the
      // ATTRID_UPGRADE_SERVER_ID attribute
      ZDP_IEEEAddrReq(pInMsg->msg->srcAddr.addr.shortAddr, ZDP_ADDR_REQTYPE_SINGLE, 0, 0);

      osal_stop_timerEx(zclOTA_TaskID, ZCL_OTA_IMAGE_QUERY_TO_EVT);
    }
  }

  if (zclOTA_AppTask != 0xFF)
  {
    // Notify the application task of the failure
    zclOTA_CallbackMsg_t *pMsg;

    pMsg = (zclOTA_CallbackMsg_t*) osal_msg_allocate(sizeof(zclOTA_CallbackMsg_t));

    if (pMsg)
    {
      pMsg->hdr.event = ZCL_OTA_CALLBACK_IND;
      pMsg->hdr.status = param.status;
      pMsg->ota_event = ZCL_OTA_START_CALLBACK;

      osal_msg_send(zclOTA_AppTask, (uint8*) pMsg);
    }
  }

  return status;
}

/******************************************************************************
 * @fn      zclOTA_ProcessImageBlockRsp
 *
 * @brief   Process received Image Block Response.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessImageBlockRsp( zclIncoming_t *pInMsg )
{
  zclOTA_ImageBlockRspParams_t  param;
  zclOTA_UpgradeEndReqParams_t  req;
  uint8 *pData;
  uint8 status = ZSuccess;

  // verify in 'in progress' state
  if (zclOTA_ImageUpgradeStatus != OTA_STATUS_IN_PROGRESS)
  {
    return ZSuccess;
  }

  // get status
  pData = pInMsg->pData;
  param.status = *pData++;

  // if status is success
  if (param.status == ZCL_STATUS_SUCCESS)
  {
    // verify message length
    if (pInMsg->pDataLen < PAYLOAD_MAX_LEN_IMAGE_BLOCK_RSP)
    {
      // no further processing if invalid
      return ZCL_STATUS_MALFORMED_COMMAND;
    }

    // parse message
    param.rsp.success.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.rsp.success.fileId.type = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.rsp.success.fileId.version = osal_build_uint32( pData, 4 );
    pData += 4;
    param.rsp.success.fileOffset = osal_build_uint32( pData, 4 );
    pData += 4;
    param.rsp.success.dataSize = *pData++;
    param.rsp.success.pData = pData;

    // verify manufacturer, image type, file version, file offset
    if ((param.rsp.success.fileId.type != zclOTA_ImageType) ||
        (param.rsp.success.fileId.manufacturer != zclOTA_ManufacturerId) ||
        (param.rsp.success.fileId.version != zclOTA_DownloadedFileVersion))
    {
      status = ZCL_STATUS_INVALID_IMAGE;
    }
    else
    {
      // Drop duplicate packets (retries)
      if (param.rsp.success.fileOffset != zclOTA_FileOffset)
      {
        return ZSuccess;
      }

      status = zclOTA_ProcessImageData(param.rsp.success.pData, param.rsp.success.dataSize);

      // Stop the timer and clear the retry count
      zclOTA_BlockRetry = 0;
      osal_stop_timerEx(zclOTA_TaskID, ZCL_OTA_BLOCK_RSP_TO_EVT);

      if (status == ZSuccess)
      {
        if (zclOTA_ImageUpgradeStatus == OTA_STATUS_COMPLETE)
        {
          // send upgrade end req with success status
          osal_memcpy(&req.fileId, &param.rsp.success.fileId, sizeof(zclOTA_FileID_t));
          req.status = ZSuccess;
          zclOTA_SendUpgradeEndReq( &(pInMsg->msg->srcAddr), &req );
        }
        else
        {
          sendImageBlockReq(&(pInMsg->msg->srcAddr));
        }
      }
    }
  }
  // else if status is 'wait for data'
  else if (param.status == ZCL_STATUS_WAIT_FOR_DATA)
  {
    // verify message length
    if (pInMsg->pDataLen != PAYLOAD_MIN_LEN_IMAGE_BLOCK_WAIT)
    {
      // no further processing if invalid
      return ZCL_STATUS_MALFORMED_COMMAND;
    }

    // parse message
    param.rsp.wait.currentTime = osal_build_uint32( pData, 4 );
    pData += 4;
    param.rsp.wait.requestTime = osal_build_uint32( pData, 4 );

    // Stop the timer and clear the retry count
    zclOTA_BlockRetry = 0;
    osal_stop_timerEx(zclOTA_TaskID, ZCL_OTA_BLOCK_RSP_TO_EVT);

    // set timer for next image block req
    zclOTA_StartTimer(ZCL_OTA_IMAGE_BLOCK_WAIT_EVT,
                      (param.rsp.wait.requestTime - param.rsp.wait.currentTime));
  }
  else if (param.status == ZCL_STATUS_ABORT)
  {
    // download aborted; set state to 'normal' state
    zclOTA_ImageUpgradeStatus = OTA_STATUS_NORMAL;

    // Stop the timer and clear the retry count
    zclOTA_BlockRetry = 0;
    osal_stop_timerEx(zclOTA_TaskID, ZCL_OTA_BLOCK_RSP_TO_EVT);
    zclOTA_UpgradeComplete(ZOtaAbort);

    return ZSuccess;
  }
  else
  {
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  if (status != ZSuccess)
  {
    // download failed; set state to 'normal'
    zclOTA_ImageUpgradeStatus = OTA_STATUS_NORMAL;

    // send upgrade end req with failure status
    osal_memcpy(&req.fileId, &param.rsp.success.fileId, sizeof(zclOTA_FileID_t));
    req.status = status;
    zclOTA_SendUpgradeEndReq( &(pInMsg->msg->srcAddr), &req );
  }

  return ZSuccess;
}

/******************************************************************************
 * @fn      zclOTA_ProcessUpgradeEndRsp
 *
 * @brief   Process received Upgrade End Response.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessUpgradeEndRsp( zclIncoming_t *pInMsg )
{
  zclOTA_UpgradeEndRspParams_t  param;
  zclOTA_FileID_t currentFileId = {zclOTA_ManufacturerId, zclOTA_ImageType, zclOTA_DownloadedFileVersion};
  uint8 *pData;

  // verify message length
  if (pInMsg->pDataLen != PAYLOAD_MAX_LEN_UPGRADE_END_RSP)
  {
    // no further processing if invalid
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  // parse message
  pData = pInMsg->pData;
  param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.version = osal_build_uint32( pData, 4 );
  pData += 4;
  param.currentTime = osal_build_uint32( pData, 4 );
  pData += 4;
  param.upgradeTime = osal_build_uint32( pData, 4 );

  // verify in 'download complete'  or 'waiting for upgrade' state
  if ((zclOTA_ImageUpgradeStatus == OTA_STATUS_COMPLETE) ||
      ((zclOTA_ImageUpgradeStatus == OTA_STATUS_UPGRADE_WAIT) && (param.upgradeTime!=OTA_UPGRADE_TIME_WAIT)))
  {
    // verify manufacturer, image type
    if (zclOTA_CmpFileId(&param.fileId, &currentFileId) == FALSE)
    {
      return ZSuccess;
    }

    // check upgrade time
    if (param.upgradeTime != OTA_UPGRADE_TIME_WAIT)
    {
      uint32 notifyDelay = 0;

      if (param.upgradeTime > param.currentTime)
      {
        // time to wait before notification
        notifyDelay = param.upgradeTime - param.currentTime;
      }

      // set state to 'countdown'
      zclOTA_ImageUpgradeStatus = OTA_STATUS_COUNTDOWN;
      // set timer for upgrade complete notification
      zclOTA_StartTimer(ZCL_OTA_UPGRADE_WAIT_EVT, notifyDelay);
    }
    else
    {
      // Wait for another upgrade end response
      zclOTA_ImageUpgradeStatus = OTA_STATUS_UPGRADE_WAIT;
      // Set a timer for 60 minutes to send another Upgrade End Rsp
      zclOTA_StartTimer(ZCL_OTA_UPGRADE_WAIT_EVT, 3600);
      zclOTA_UpgradeEndRetry = 0;
    }
  }

  return ZSuccess;
}

/******************************************************************************
 * @fn      zclOTA_ProcessQuerySpecificFileRsp
 *
 * @brief   Process received Query Specific File Response.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessQuerySpecificFileRsp( zclIncoming_t *pInMsg )
{
  zclOTA_QueryImageRspParams_t  param;
  uint8 *pData;

  // verify message length
  if ((pInMsg->pDataLen != PAYLOAD_MAX_LEN_QUERY_SPECIFIC_FILE_RSP) &&
      (pInMsg->pDataLen != PAYLOAD_MIN_LEN_QUERY_SPECIFIC_FILE_RSP))
  {
    // no further processing if invalid
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  // ignore message if in 'download in progress' state
  if (zclOTA_ImageUpgradeStatus == OTA_STATUS_IN_PROGRESS)
  {
    return ZSuccess;
  }

  // get status
  pData = pInMsg->pData;
  param.status = *pData++;

  // if status is success
  if (param.status == ZCL_STATUS_SUCCESS)
  {
    // parse message
    param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.fileId.version = osal_build_uint32( pData, 4 );
    pData += 4;
    param.imageSize = osal_build_uint32( pData, 4 );

    // verify manufacturer id and image type
    if ((param.fileId.type == zclOTA_ImageType) &&
        (param.fileId.manufacturer == zclOTA_ManufacturerId))
    {
      // store file version and image size
      zclOTA_DownloadedFileVersion = param.fileId.version;
      zclOTA_DownloadedImageSize = param.imageSize;

      // initialize other variables
      zclOTA_FileOffset = 0;

      // set state to 'in progress'
      zclOTA_ImageUpgradeStatus = OTA_STATUS_IN_PROGRESS;

      // send image block request
      sendImageBlockReq(&(pInMsg->msg->srcAddr));
    }
  }

  return ZSuccess;
}

/******************************************************************************
 * @fn      zclOTA_ClientHdlIncoming
 *
 * @brief   Handle incoming client commands.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ClientHdlIncoming( zclIncoming_t *pInMsg )
{
  switch (pInMsg->hdr.commandID)
  {
    case COMMAND_IMAGE_NOTIFY:
      return zclOTA_ProcessImageNotify( pInMsg );

    case COMMAND_QUERY_NEXT_IMAGE_RSP:
      return zclOTA_ProcessQueryNextImageRsp( pInMsg );

    case COMMAND_IMAGE_BLOCK_RSP:
      return zclOTA_ProcessImageBlockRsp( pInMsg );

    case COMMAND_UPGRADE_END_RSP:
      return zclOTA_ProcessUpgradeEndRsp( pInMsg );

   case COMMAND_QUERY_SPECIFIC_FILE_RSP:
      return zclOTA_ProcessQuerySpecificFileRsp( pInMsg );

    default:
      return ZFailure;
  }
}

/******************************************************************************
 * @fn      zclOTA_CmpFileId
 *
 * @brief   Called to compare two file IDs
 *
 * @param   f1, f2 - Pointers to the two file IDs to compare
 *
 * @return  TRUE if the file IDs are the same, else FALSE
 */
static uint8 zclOTA_CmpFileId(zclOTA_FileID_t *f1, zclOTA_FileID_t *f2)
{
  if ((f1->manufacturer == 0xFFFF) ||
      (f2->manufacturer == 0xFFFF) ||
      (f1->manufacturer == f2->manufacturer))
  {
    if ((f1->type == 0xFFFF) ||
        (f2->type == 0xFFFF) ||
        (f1->type == f2->type))
    {
      if ((f1->version == 0xFFFFFFFF) ||
          (f2->version == 0xFFFFFFFF) ||
          (f1->version == f2->version))
      {
        return TRUE;
      }
    }
  }

  return FALSE;
}

/******************************************************************************
 * @fn      zclOTA_ImageBlockWaitExpired
 *
 * @brief   Perform action on image block wait timer expiration.
 *
 * @param   none
 *
 * @return  none
 */
static void zclOTA_ImageBlockWaitExpired(void)
{
  // verify in 'in progress' state
  if (zclOTA_ImageUpgradeStatus == OTA_STATUS_IN_PROGRESS)
  {
    // request next block
    sendImageBlockReq(&zclOTA_serverAddr);
  }
}

/******************************************************************************
 * @fn      zclOTA_UpgradeComplete
 *
 * @brief   Notify the application task that an upgrade has completed.
 *
 * @param   status - The status of the upgrade
 *
 * @return  none
 */
static void zclOTA_UpgradeComplete(uint8 status)
{
  // Go back to the normal state
  zclOTA_ImageUpgradeStatus = OTA_STATUS_NORMAL;

  if ((zclOTA_DownloadedImageSize == OTA_HEADER_LEN_MIN_ECDSA) ||
      (zclOTA_DownloadedImageSize == OTA_HEADER_LEN_MIN))
  {
    status = ZFailure;
  }

  if (zclOTA_AppTask != 0xFF)
  {
    // Notify the application task the upgrade stopped
    zclOTA_CallbackMsg_t *pMsg;

    pMsg = (zclOTA_CallbackMsg_t*) osal_msg_allocate(sizeof(zclOTA_CallbackMsg_t));

    if (pMsg)
    {
      pMsg->hdr.event = ZCL_OTA_CALLBACK_IND;
      pMsg->hdr.status = status;
      pMsg->ota_event = ZCL_OTA_DL_COMPLETE_CALLBACK;

      osal_msg_send(zclOTA_AppTask, (uint8*) pMsg);
    }
  }
}

/******************************************************************************
 * @fn      zclOTA_ProcessZDOMsgs
 *
 * @brief   Process callbacks from the ZDO.
 *
 * @param   pMsg - a Pointer to the message from the ZDO
 *
 * @return  none
 */
static void zclOTA_ProcessZDOMsgs( zdoIncomingMsg_t *pMsg )
{
  if (pMsg->clusterID == IEEE_addr_rsp)
  {
    ZDO_NwkIEEEAddrResp_t *pNwkAddrRsp = ZDO_ParseAddrRsp( pMsg );

    // If this is from the OTA server, record the server's IEEE address
    if (pNwkAddrRsp != NULL)
    {
      if (pNwkAddrRsp->nwkAddr == zclOTA_serverAddr.addr.shortAddr)
      {
        osal_memcpy(&zclOTA_UpgradeServerID, pNwkAddrRsp->extAddr, Z_EXTADDR_LEN);
      }

      osal_mem_free( pNwkAddrRsp );
    }
  }
}
#endif // OTA_CLIENT

#if defined (OTA_SERVER) && (OTA_SERVER == TRUE)
/******************************************************************************
 * @fn      zclOTA_ProcessNextImgRsp
 *
 * @brief   Handles a response to a MT_OTA_NEXT_IMG_RSP.
 *
 * @param   pMsg - The data from the server.
 *          pFileId - The ID of the OTA File.
 *          pAddr - The source of the message.
 *
 * @return  none
 */
void zclOTA_ProcessNextImgRsp(uint8* pMsg, zclOTA_FileID_t *pFileId,
                              afAddrType_t *pAddr)
{
  zclOTA_QueryImageRspParams_t queryRsp;
  uint8 options;
  uint8 status;

  // Get the status of the operation
  status = *pMsg++;

  // Get the options
  options = *pMsg++;

  // Copy the file ID
  osal_memcpy(&queryRsp.fileId, pFileId, sizeof(zclOTA_FileID_t));

  // Set the image size
  if (status == ZSuccess)
  {
    queryRsp.status = ZSuccess;
    queryRsp.imageSize = BUILD_UINT32(pMsg[0], pMsg[1], pMsg[2], pMsg[3]);
  }
  else
  {
    queryRsp.status = ZOtaNoImageAvailable;
    queryRsp.imageSize = 0;
  }

  queryResponse = queryRsp; // save global variable for query image response. Used later in image block request check

  // Send a response to the client
  if (options & MT_OTA_QUERY_SPECIFIC_OPTION)
  {
    zclOTA_SendQuerySpecificFileRsp(pAddr, &queryRsp);
  }
  else
  {
    zclOTA_SendQueryNextImageRsp(pAddr, &queryRsp);
  }
}

/******************************************************************************
 * @fn      zclOTA_ProcessFileReadRsp
 *
 * @brief   Handles a response to a MT_OTA_FILE_READ_RSP.
 *
 * @param   pMsg - The data from the server.
 *          pFileId - The ID of the OTA File.
 *          pAddr - The source of the message.
 *
 * @return  none
 */
void zclOTA_ProcessFileReadRsp(uint8* pMsg, zclOTA_FileID_t *pFileId,
                               afAddrType_t *pAddr)
{
  zclOTA_ImageBlockRspParams_t blockRsp;

  // Set the status
  blockRsp.status = *pMsg++;

  // Check the status of the file read
  if (blockRsp.status == ZSuccess)
  {
    // Fill in the response parameters
    osal_memcpy(&blockRsp.rsp.success.fileId, pFileId, sizeof(zclOTA_FileID_t));
    blockRsp.rsp.success.fileOffset = BUILD_UINT32(pMsg[0], pMsg[1], pMsg[2], pMsg[3]);
    pMsg += 4;
    blockRsp.rsp.success.dataSize = *pMsg++;
    blockRsp.rsp.success.pData = pMsg;
  }
  else
  {
    blockRsp.status = ZOtaAbort;
  }

  // Send the block response to the peer
  zclOTA_SendImageBlockRsp(pAddr, &blockRsp);
}

/******************************************************************************
 * @fn      OTA_HandleFileSysCb
 *
 * @brief   Handles File Server Callbacks.
 *
 * @param   pMSGpkt - The data from the server.
 *
 * @return  none
 */
void zclOTA_ServerHandleFileSysCb(OTA_MtMsg_t* pMSGpkt)
{
  zclOTA_FileID_t pFileId;
  afAddrType_t pAddr;
  uint8 *pMsg;

  if (pMSGpkt != NULL)
  {
    // Get the File ID and AF Address
    pMsg = pMSGpkt->data;
    pMsg = OTA_StreamToFileId(&pFileId, pMsg);
    pMsg = OTA_StreamToAfAddr(&pAddr, pMsg);

    switch(pMSGpkt->cmd)
    {
      case MT_OTA_NEXT_IMG_RSP:
        zclOTA_ProcessNextImgRsp(pMsg, &pFileId, &pAddr);
        break;

      case MT_OTA_FILE_READ_RSP:
        zclOTA_ProcessFileReadRsp(pMsg, &pFileId, &pAddr);
        break;

      default:
        break;
    }
  }
}

/******************************************************************************
 * @fn      zclOTA_Srv_QueryNextImageReq
 *
 * @brief   Handle a Query Next Image Request.
 *
 * @param   pSrcAddr - The source of the message
 *          pParam - message parameters
 *
 * @return  ZStatus_t
 *
 * @note    On a query next image, we must request a file listing
 *          from the File Server.  Then open a file if
 */
ZStatus_t zclOTA_Srv_QueryNextImageReq(afAddrType_t *pSrcAddr, zclOTA_QueryNextImageReqParams_t *pParam)
{
  uint8 options = 0;
  uint8 status;

  if (zclOTA_Permit)
  {
    if (pParam->fieldControl)
    {
      options |= MT_OTA_HW_VER_PRESENT_OPTION;
    }

    // Request the next image for this device from the console via the MT File System
    status = MT_OtaGetImage(pSrcAddr, &pParam->fileId, pParam->hardwareVersion, NULL, options);
  }
  else
  {
    status = ZOtaNoImageAvailable;
  }

  if (status != ZSuccess)
  {
    zclOTA_QueryImageRspParams_t queryRsp;

    // Fill in the response parameters
    osal_memcpy(&queryRsp.fileId, &pParam->fileId, sizeof(zclOTA_FileID_t));
    queryRsp.status = ZOtaNoImageAvailable;
    queryRsp.imageSize = 0;

    // Send a failure response to the client
    zclOTA_SendQueryNextImageRsp(pSrcAddr, &queryRsp);
  }

  return ZCL_STATUS_CMD_HAS_RSP;
}

/******************************************************************************
 * @fn      zclOTA_Srv_ImageBlockReq
 *
 * @brief   Handle an Image Block Request.
 *
 * @param   pSrcAddr - The source of the message
 *          pParam - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_Srv_ImageBlockReq(afAddrType_t *pSrcAddr, zclOTA_ImageBlockReqParams_t *pParam)
{
  uint8 status = ZFailure;

  if (pParam->fileId.version != queryResponse.fileId.version)
  {
    status = ZCL_STATUS_NO_IMAGE_AVAILABLE;
  }
  else
  {

    if (zclOTA_Permit && (pParam != NULL))
    {
      uint8 len = pParam->maxDataSize;

      if (len > OTA_MAX_MTU)
      {
        len = OTA_MAX_MTU;
      }

      // Read the data from the OTA Console
      status = MT_OtaFileReadReq(pSrcAddr, &pParam->fileId, len, pParam->fileOffset);

      // Send a wait response to the client
      if (status != ZSuccess)
      {
        zclOTA_ImageBlockRspParams_t blockRsp;

        // Fill in the response parameters
        blockRsp.status = ZOtaWaitForData;
        osal_memcpy(&blockRsp.rsp.success.fileId, &pParam->fileId, sizeof(zclOTA_FileID_t));
        blockRsp.rsp.wait.currentTime = 0;
        blockRsp.rsp.wait.requestTime = OTA_SEND_BLOCK_WAIT;

        // Send the block to the peer
        zclOTA_SendImageBlockRsp(pSrcAddr, &blockRsp);
      }

      status = ZCL_STATUS_CMD_HAS_RSP;

    }
  }

  return status;
}

/******************************************************************************
 * @fn      zclOTA_Srv_ImagePageReq
 *
 * @brief   Handle an Image Page Request.  Note: Not currently supported.
 *
 * @param   pSrcAddr - The source of the message
 *          pParam - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_Srv_ImagePageReq(afAddrType_t *pSrcAddr, zclOTA_ImagePageReqParams_t *pParam)
{
  // Send not supported resposne
  return ZUnsupClusterCmd;
}

/******************************************************************************
 * @fn      zclOTA_Srv_UpgradeEndReq
 *
 * @brief   Handle an Upgrade End Request.
 *
 * @param   pSrcAddr - The source of the message
 *          pParam - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_Srv_UpgradeEndReq(afAddrType_t *pSrcAddr, zclOTA_UpgradeEndReqParams_t *pParam)
{
  uint8 status = ZFailure;

  if (zclOTA_Permit && (pParam != NULL))
  {
    zclOTA_UpgradeEndRspParams_t rspParms;

    if (pParam->status == ZSuccess)
    {
      osal_memcpy(&rspParms.fileId, &pParam->fileId, sizeof(zclOTA_FileID_t));
      rspParms.currentTime = osal_GetSystemClock();
      rspParms.upgradeTime = rspParms.currentTime + OTA_UPGRADE_DELAY;

      // Send the response to the peer
      zclOTA_SendUpgradeEndRsp(pSrcAddr, &rspParms);
    }

    // Notify the Console Tool
    MT_OtaSendStatus(pSrcAddr->addr.shortAddr, MT_OTA_DL_COMPLETE, pParam->status, 0);

    status = ZCL_STATUS_CMD_HAS_RSP;
  }

  return status;
}

/******************************************************************************
 * @fn      zclOTA_Srv_QuerySpecificFileReq
 *
 * @brief   Handles a Query Specific File Request.
 *
 * @param   pSrcAddr - The source of the message
 *          pParam - message parameters
 *
 * @return  ZStatus_t
 */
ZStatus_t zclOTA_Srv_QuerySpecificFileReq(afAddrType_t *pSrcAddr, zclOTA_QuerySpecificFileReqParams_t *pParam)
{
  uint8 status;

  // Request the image from the console
  if (zclOTA_Permit)
  {
    status = MT_OtaGetImage(pSrcAddr, &pParam->fileId, 0,  pParam->nodeAddr, MT_OTA_QUERY_SPECIFIC_OPTION);
  }
  else
  {
    status = ZOtaNoImageAvailable;
  }

  if (status != ZSuccess)
  {
    zclOTA_QueryImageRspParams_t queryRsp;

    // Fill in the response parameters
    osal_memcpy(&queryRsp.fileId, &pParam->fileId, sizeof(zclOTA_FileID_t));
    queryRsp.status = ZOtaNoImageAvailable;
    queryRsp.imageSize = 0;

    // Send a failure response to the client
    zclOTA_SendQuerySpecificFileRsp(pSrcAddr, &queryRsp);
  }

  return ZCL_STATUS_CMD_HAS_RSP;
}

/******************************************************************************
 * @fn      zclOTA_ProcessQueryNextImageReq
 *
 * @brief   Process received Query Next Image Request.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessQueryNextImageReq( zclIncoming_t *pInMsg )
{
  zclOTA_QueryNextImageReqParams_t  param;
  uint8 *pData;

  /* verify message length */
  if ((pInMsg->pDataLen != PAYLOAD_MAX_LEN_QUERY_NEXT_IMAGE_REQ) &&
      (pInMsg->pDataLen != PAYLOAD_MIN_LEN_QUERY_NEXT_IMAGE_REQ))
  {
    /* no further processing if invalid */
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  /* parse message parameters */
  pData = pInMsg->pData;
  param.fieldControl = *pData++;
  param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.version = osal_build_uint32( pData, 4 );
  pData += 4;
  if ((param.fieldControl & 0x01) != 0)
  {
    param.hardwareVersion = BUILD_UINT16(pData[0], pData[1]);
  }

  /* call callback */
  return zclOTA_Srv_QueryNextImageReq(&pInMsg->msg->srcAddr, &param);
}

/******************************************************************************
 * @fn      zclOTA_ProcessImageBlockReq
 *
 * @brief   Process received Image Block Request.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessImageBlockReq( zclIncoming_t *pInMsg )
{
  zclOTA_ImageBlockReqParams_t  param;
  uint8 *pData;

  /* verify message length */
  if ((pInMsg->pDataLen != PAYLOAD_MAX_LEN_IMAGE_BLOCK_REQ) &&
      (pInMsg->pDataLen != PAYLOAD_MIN_LEN_IMAGE_BLOCK_REQ))
  {
    /* no further processing if invalid */
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  /* parse message parameters */
  pData = pInMsg->pData;
  param.fieldControl = *pData++;
  param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.version = osal_build_uint32( pData, 4 );
  pData += 4;
  param.fileOffset = osal_build_uint32( pData, 4 );
  pData += 4;
  param.maxDataSize = *pData++;
  if ((param.fieldControl & 0x01) != 0)
  {
    osal_cpyExtAddr(param.nodeAddr, pData);
  }

  /* call callback */
  return zclOTA_Srv_ImageBlockReq(&pInMsg->msg->srcAddr, &param);
}

/******************************************************************************
 * @fn      zclOTA_ProcessImagePageReq
 *
 * @brief   Process received Image Page Request.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessImagePageReq( zclIncoming_t *pInMsg )
{
  zclOTA_ImagePageReqParams_t  param;
  uint8 *pData;

  /* verify message length */
  if ((pInMsg->pDataLen != PAYLOAD_MAX_LEN_IMAGE_PAGE_REQ) &&
      (pInMsg->pDataLen != PAYLOAD_MIN_LEN_IMAGE_PAGE_REQ))
  {
    /* no further processing if invalid */
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  /* parse message parameters */
  pData = pInMsg->pData;
  param.fieldControl = *pData++;
  param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.version = osal_build_uint32( pData, 4 );
  pData += 4;
  param.fileOffset = osal_build_uint32( pData, 4 );
  pData += 4;
  param.maxDataSize = *pData++;
  param.pageSize = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.responseSpacing = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  if ((param.fieldControl & 0x01) != 0)
  {
    osal_cpyExtAddr(param.nodeAddr, pData);
  }

  /* call callback */
  return zclOTA_Srv_ImagePageReq(&pInMsg->msg->srcAddr, &param);
}

/******************************************************************************
 * @fn      zclOTA_ProcessUpgradeEndReq
 *
 * @brief   Process received Upgrade End Request.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessUpgradeEndReq( zclIncoming_t *pInMsg )
{
  zclOTA_UpgradeEndReqParams_t  param;
  uint8 *pData;

  /* verify message length */
  if ((pInMsg->pDataLen != PAYLOAD_MAX_LEN_UPGRADE_END_REQ) &&
      (pInMsg->pDataLen != PAYLOAD_MIN_LEN_UPGRADE_END_REQ))
  {
    /* no further processing if invalid */
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  /* parse message parameters */
  pData = pInMsg->pData;
  param.status = *pData++;
  if (param.status == ZCL_STATUS_SUCCESS)
  {
    param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
    pData += 2;
    param.fileId.version = osal_build_uint32( pData, 4 );
  }

  /* call callback */
  return zclOTA_Srv_UpgradeEndReq(&pInMsg->msg->srcAddr, &param);
}

/******************************************************************************
 * @fn      zclOTA_ProcessQuerySpecificFileReq
 *
 * @brief   Process received Image Page Request.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ProcessQuerySpecificFileReq( zclIncoming_t *pInMsg )
{
  zclOTA_QuerySpecificFileReqParams_t  param;
  uint8 *pData;

  /* verify message length */
  if (pInMsg->pDataLen != PAYLOAD_MAX_LEN_QUERY_SPECIFIC_FILE_REQ)
  {
    /* no further processing if invalid */
    return ZCL_STATUS_MALFORMED_COMMAND;
  }

  /* parse message parameters */
  pData = pInMsg->pData;
  osal_cpyExtAddr(param.nodeAddr, pData);
  pData += Z_EXTADDR_LEN;
  param.fileId.manufacturer = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.type = BUILD_UINT16(pData[0], pData[1]);
  pData += 2;
  param.fileId.version = osal_build_uint32( pData, 4 );
  pData += 4;
  param.stackVersion = BUILD_UINT16(pData[0], pData[1]);

  /* call callback */
  return zclOTA_Srv_QuerySpecificFileReq(&pInMsg->msg->srcAddr, &param);
}

/******************************************************************************
 * @fn      zclOTA_ServerHdlIncoming
 *
 * @brief   Handle incoming server commands.
 *
 * @param   pInMsg - pointer to the incoming message
 *
 * @return  ZStatus_t
 */
static ZStatus_t zclOTA_ServerHdlIncoming( zclIncoming_t *pInMsg )
{
  switch (pInMsg->hdr.commandID)
  {
    case COMMAND_QUERY_NEXT_IMAGE_REQ:
      return zclOTA_ProcessQueryNextImageReq( pInMsg );

    case COMMAND_IMAGE_BLOCK_REQ:
      return zclOTA_ProcessImageBlockReq( pInMsg );

    case COMMAND_IMAGE_PAGE_REQ:
      return zclOTA_ProcessImagePageReq( pInMsg );

    case COMMAND_UPGRADE_END_REQ:
      return zclOTA_ProcessUpgradeEndReq( pInMsg );

    case COMMAND_QUERY_SPECIFIC_FILE_REQ:
      return zclOTA_ProcessQuerySpecificFileReq( pInMsg );

    default:
      return ZFailure;
  }
}
#endif // OTA_SERVER
