/**************************************************************************************************
  Filename:       zcl.c
  Revised:        $Date: 2009-04-06 09:08:36 -0700 (Mon, 06 Apr 2009) $
  Revision:       $Revision: 19702 $

  Description:    This file contains the Zigbee Cluster Library Foundation functions.


  Copyright 2006-2009 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 "OSAL_Tasks.h"
#include "AF.h"
#include "ZDConfig.h"

#include "zcl.h"
#include "zcl_general.h"

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

/*********************************************************************
 * MACROS
 */
/*** Frame Control ***/
#define zcl_FCType( a )               ( (a) & ZCL_FRAME_CONTROL_TYPE )
#define zcl_FCManuSpecific( a )       ( (a) & ZCL_FRAME_CONTROL_MANU_SPECIFIC )
#define zcl_FCDirection( a )          ( (a) & ZCL_FRAME_CONTROL_DIRECTION )
#define zcl_FCDisableDefaultRsp( a )  ( (a) & ZCL_FRAME_CONTROL_DISABLE_DEFAULT_RSP )

/*** Attribute Access Control ***/
#define zcl_AccessCtrlRead( a )       ( (a) & ACCESS_CONTROL_READ )
#define zcl_AccessCtrlWrite( a )      ( (a) & ACCESS_CONTROL_WRITE )
#define zcl_AccessCtrlCmd( a )        ( (a) & ACCESS_CONTROL_CMD )

#define zclParseCmd( a, b )           zclCmdTable[(a)].pfnParseInProfile( (b) )
#define zclProcessCmd( a, b )         zclCmdTable[(a)].pfnProcessInProfile( (b) )

#define zcl_DefaultRspCmd( zclHdr )   ( zcl_ProfileCmd( (zclHdr).fc.type )     && \
                                        (zclHdr).fc.manuSpecific == 0          && \
                                        (zclHdr).commandID == ZCL_CMD_DEFAULT_RSP )

// Commands that have corresponding responses
#define CMD_HAS_RSP( cmd )            ( (cmd) == ZCL_CMD_READ            || \
                                        (cmd) == ZCL_CMD_WRITE           || \
                                        (cmd) == ZCL_CMD_WRITE_UNDIVIDED || \
                                        (cmd) == ZCL_CMD_CONFIG_REPORT   || \
                                        (cmd) == ZCL_CMD_READ_REPORT_CFG || \
                                        (cmd) == ZCL_CMD_DISCOVER        || \
                                        (cmd) == ZCL_CMD_DEFAULT_RSP ) // exception

/*********************************************************************
 * CONSTANTS
 */

/*********************************************************************
 * TYPEDEFS
 */
typedef struct zclLibPlugin
{
  struct zclLibPlugin *next;
  uint16              startClusterID;    // starting cluster ID
  uint16              endClusterID;      // ending cluster ID
  zclInHdlr_t         pfnIncomingHdlr;    // function to handle incoming message
} zclLibPlugin_t;

// Attribute record list item
typedef struct zclAttrRecsList
{
  struct zclAttrRecsList *next;
  uint8                  endpoint;      // Used to link it into the endpoint descriptor
  uint8                  numAttributes; // Number of the following records
  CONST zclAttrRec_t     *attrs;        // attribute records
} zclAttrRecsList;

// Cluster option list item
typedef struct zclClusterOptionList
{
  struct zclClusterOptionList *next;
  uint8                       endpoint;   // Used to link it into the endpoint descriptor
  uint8                       numOptions; // Number of the following records
  zclOptionRec_t              *options;   // option records
} zclClusterOptionList;

typedef void *(*zclParseInProfileCmd_t)( zclParseCmd_t *pCmd );
typedef uint8 (*zclProcessInProfileCmd_t)( zclIncoming_t *pInMsg );

typedef struct
{
  zclParseInProfileCmd_t   pfnParseInProfile;
  zclProcessInProfileCmd_t pfnProcessInProfile;
} zclCmdItems_t;


/*********************************************************************
 * GLOBAL VARIABLES
 */
uint8 zcl_TaskID;

// The task Id of the Application where the unprocessed Foundation
// Command/Response messages will be sent to.
uint8 zcl_RegisteredMsgTaskID = TASK_NO_TASK;

// The Application should register its attribute data validation function
zclValidateAttrData_t zcl_ValidateAttrDataCB = NULL;

// ZCL Sequence number
uint8 zcl_SeqNum = 0x00;
   
/*********************************************************************
 * EXTERNAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL FUNCTIONS
 */

/*********************************************************************
 * LOCAL VARIABLES
 */
static zclLibPlugin_t *plugins;
static zclAttrRecsList *attrList;
static zclClusterOptionList *clusterOptionList;
static uint8 zcl_TransID = 0;  // This is the unique message ID (counter)

/*********************************************************************
 * LOCAL FUNCTIONS
 */
static void zclProcessMessageMSG( afIncomingMSGPacket_t *pkt );
static uint8 *zclBuildHdr( zclFrameHdr_t *hdr, uint8 *pData );
static uint8 zclCalcHdrSize( zclFrameHdr_t *hdr );
static zclLibPlugin_t *zclFindPlugin( uint16 clusterID, uint16 profileID );
static zclOptionRec_t *zclFindClusterOption( uint8 endpoint, uint16 clusterID );
static uint8 zclGetClusterOption( uint8 endpoint, uint16 clusterID );
static void zclSetSecurityOption( uint8 endpoint, uint16 clusterID, uint8 enable );

static uint8 zcl_DeviceOperational( uint8 srcEP, uint16 clusterID, uint8 frameType, uint8 cmd, uint16 profileID );

#if defined(ZCL_READ) || defined(ZCL_WRITE) || defined(ZCL_REPORT)
static void zclSerializeData( uint8 dataType, void *attrData, uint8 *buf );
#endif // ZCL_READ || ZCL_WRITE || ZCL_REPORT

#ifdef ZCL_READ
static void *zclParseInReadRspCmd( zclParseCmd_t *pCmd );
static uint8 zclProcessInReadCmd( zclIncoming_t *pInMsg );
#endif // ZCL_READ

#ifdef ZCL_WRITE
static uint8 zclWriteAttrData( zclAttrRec_t *pAttr, zclWriteRec_t *pWriteRec );
static void *zclParseInWriteRspCmd( zclParseCmd_t *pCmd );
static uint8 zclProcessInWriteCmd( zclIncoming_t *pInMsg );
static uint8 zclProcessInWriteUndividedCmd( zclIncoming_t *pInMsg );
#endif // ZCL_WRITE

#ifdef ZCL_REPORT
static void *zclParseInConfigReportRspCmd( zclParseCmd_t *pCmd );
static void *zclParseInReadReportCfgRspCmd( zclParseCmd_t *pCmd );
#endif // ZCL_REPORT

static void *zclParseInDefaultRspCmd( zclParseCmd_t *pCmd );

#ifdef ZCL_DISCOVER
static uint8 zclFindNextAttrRec( uint8 endpoint, uint16 clusterID, uint16 *attrId, zclAttrRec_t *pAttr );
static void *zclParseInDiscRspCmd( zclParseCmd_t *pCmd );
static uint8 zclProcessInDiscCmd( zclIncoming_t *pInMsg );
#endif // ZCL_DISCOVER

static uint8 zclSendMsg( zclIncoming_t *pInMsg );

/*********************************************************************
 * Parse Profile Command Function Table
 */
static CONST zclCmdItems_t zclCmdTable[] =
{
#ifdef ZCL_READ
  /* ZCL_CMD_READ */                { zclParseInReadCmd,             zclProcessInReadCmd             },
  /* ZCL_CMD_READ_RSP */            { zclParseInReadRspCmd,          zclSendMsg                      },
#else
  /* ZCL_CMD_READ */                { NULL,                          NULL                            },
  /* ZCL_CMD_READ_RSP */            { NULL,                          NULL                            },
#endif // ZCL_READ

#ifdef ZCL_WRITE
  /* ZCL_CMD_WRITE */               { zclParseInWriteCmd,            zclProcessInWriteCmd            },
  /* ZCL_CMD_WRITE_UNDIVIDED */     { zclParseInWriteCmd,            zclProcessInWriteUndividedCmd   },
  /* ZCL_CMD_WRITE_RSP */           { zclParseInWriteRspCmd,         zclSendMsg                      },
  /* ZCL_CMD_WRITE_NO_RSP */        { zclParseInWriteCmd,            zclProcessInWriteCmd            },
#else
  /* ZCL_CMD_WRITE */               { NULL,                          NULL                            },
  /* ZCL_CMD_WRITE_UNDIVIDED */     { NULL,                          NULL                            },
  /* ZCL_CMD_WRITE_RSP */           { NULL,                          NULL                            },
  /* ZCL_CMD_WRITE_NO_RSP */        { NULL,                          NULL                            },
#endif // ZCL_WRITE

#ifdef ZCL_REPORT
  /* ZCL_CMD_CONFIG_REPORT */       { zclParseInConfigReportCmd,     zclSendMsg                      },
  /* ZCL_CMD_CONFIG_REPORT_RSP */   { zclParseInConfigReportRspCmd,  zclSendMsg                      },
  /* ZCL_CMD_READ_REPORT_CFG */     { zclParseInReadReportCfgCmd,    zclSendMsg                      },
  /* ZCL_CMD_READ_REPORT_CFG_RSP */ { zclParseInReadReportCfgRspCmd, zclSendMsg                      },
  /* ZCL_CMD_REPORT */              { zclParseInReportCmd,           zclSendMsg                      },
#else
  /* ZCL_CMD_CONFIG_REPORT */       { NULL,                          NULL                            },
  /* ZCL_CMD_CONFIG_REPORT_RSP */   { NULL,                          NULL                            },
  /* ZCL_CMD_READ_REPORT_CFG */     { NULL,                          NULL                            },
  /* ZCL_CMD_READ_REPORT_CFG_RSP */ { NULL,                          NULL                            },
  /* ZCL_CMD_REPORT */              { NULL,                          NULL                            },
#endif // ZCL_REPORT

  /* ZCL_CMD_DEFAULT_RSP */         { zclParseInDefaultRspCmd,       zclSendMsg                      },
  
#ifdef ZCL_DISCOVER  
  /* ZCL_CMD_DISCOVER */            { zclParseInDiscCmd,             zclProcessInDiscCmd             },
  /* ZCL_CMD_DISCOVER_RSP */        { zclParseInDiscRspCmd,          zclSendMsg                      }
#else
  /* ZCL_CMD_DISCOVER */            { NULL,                          NULL                            },
  /* ZCL_CMD_DISCOVER_RSP */        { NULL,                          NULL                            }
#endif // ZCL_DISCOVER
};

/*********************************************************************
 * PUBLIC FUNCTIONS
 *********************************************************************/

/*********************************************************************
 * @fn          zcl_Init
 *
 * @brief       Initialization function for the zcl layer.
 *
 * @param       task_id - ZCL task id
 *
 * @return      none
 */
void zcl_Init( uint8 task_id )
{
  zcl_TaskID = task_id;

  plugins = (zclLibPlugin_t  *)NULL;
  attrList = (zclAttrRecsList *)NULL;
  clusterOptionList = (zclClusterOptionList *)NULL;
}

/*********************************************************************
 * @fn          zcl_event_loop
 *
 * @brief       Event Loop Processor for zcl.
 *
 * @param       task_id - task id
 * @param       events - event bitmap
 *
 * @return      unprocessed events
 */
uint16 zcl_event_loop( uint8 task_id, uint16 events )
{
  uint8 *msgPtr;

  (void)task_id;  // Intentionally unreferenced parameter
  
  if ( events & SYS_EVENT_MSG )
  {
    msgPtr = osal_msg_receive( zcl_TaskID );
    while ( msgPtr != NULL )
    {
      uint8 dealloc = TRUE;
      
      if ( *msgPtr == AF_INCOMING_MSG_CMD )
      {
        zclProcessMessageMSG( (afIncomingMSGPacket_t *)msgPtr );
      }
      else if ( zcl_RegisteredMsgTaskID != TASK_NO_TASK )
      {
        // send it to another task to process.
        osal_msg_send( zcl_RegisteredMsgTaskID, msgPtr );
        dealloc = FALSE;
      }
  
      // Release the memory
      if ( dealloc )
      {
        osal_msg_deallocate( msgPtr );
      }

      // Next
      msgPtr = osal_msg_receive( zcl_TaskID );
    }

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

  // Discard unknown events
  return 0;
}

/*********************************************************************
 * @fn          zcl_registerPlugin
 *
 * @brief       Add a Cluster Library handler
 *
 * @param       startClusterID - starting cluster ID
 * @param       endClusterID - ending cluster ID
 * @param       pfnHdlr - function pointer to incoming message handler
 *
 * @return      ZSuccess if OK
 */
ZStatus_t zcl_registerPlugin( uint16 startClusterID,
          uint16 endClusterID, zclInHdlr_t pfnIncomingHdlr )
{
  zclLibPlugin_t *pNewItem;
  zclLibPlugin_t *pLoop;

  // Fill in the new profile list
  pNewItem = osal_mem_alloc( sizeof( zclLibPlugin_t ) );
  if ( pNewItem == NULL )
    return (ZMemError);

  // Fill in the plugin record.
  pNewItem->next = (zclLibPlugin_t *)NULL;
  pNewItem->startClusterID = startClusterID;
  pNewItem->endClusterID = endClusterID;
  pNewItem->pfnIncomingHdlr = pfnIncomingHdlr;

  // Find spot in list
  if (  plugins == NULL )
  {
    plugins = pNewItem;
  }
  else
  {
    // Look for end of list
    pLoop = plugins;
    while ( pLoop->next != NULL )
      pLoop = pLoop->next;

    // Put new item at end of list
    pLoop->next = pNewItem;
  }

  return ( ZSuccess );
}

/*********************************************************************
 * @fn          zcl_registerAttrList
 *
 * @brief       Register an Attribute List with ZCL Foundation
 *
 * @param       endpoint - endpoint the attribute list belongs to
 * @param       numAttr - number of attributes in list
 * @param       newAttrList - array of Attribute records.
 *                            NOTE: THE ATTRIBUTE IDs (FOR A CLUSTER) MUST BE IN
 *                            ASCENDING ORDER. OTHERWISE, THE DISCOVERY RESPONSE
 *                            COMMAND WILL NOT HAVE THE RIGHT ATTRIBUTE INFO
 *
 * @return      ZSuccess if OK
 */
ZStatus_t zcl_registerAttrList( uint8 endpoint, uint8 numAttr, CONST zclAttrRec_t newAttrList[] )
{
  zclAttrRecsList *pNewItem;
  zclAttrRecsList *pLoop;

  // Fill in the new profile list
  pNewItem = osal_mem_alloc( sizeof( zclAttrRecsList ) );
  if ( pNewItem == NULL )
    return (ZMemError);

  pNewItem->next = (zclAttrRecsList *)NULL;
  pNewItem->endpoint = endpoint;
  pNewItem->numAttributes = numAttr;
  pNewItem->attrs = newAttrList;

  // Find spot in list
  if ( attrList == NULL )
  {
    attrList = pNewItem;
  }
  else
  {
    // Look for end of list
    pLoop = attrList;
    while ( pLoop->next != NULL )
      pLoop = pLoop->next;

    // Put new item at end of list
    pLoop->next = pNewItem;
  }

  return ( ZSuccess );
}

/*********************************************************************
 * @fn          zcl_registerClusterOptionList
 *
 * @brief       Register a Cluster Option List with ZCL Foundation
 *
 * @param       endpoint - endpoint the option list belongs to
 * @param       numOption - number of options in list
 * @param       optionList - array of cluster option records.
 *                           
 *              NOTE: This API should be called to enable 'Application
 *                    Link Key' security and/or 'APS ACK' for a specific      
 *                    Cluster. The 'Application Link Key' is discarded
 *                    if security isn't enabled on the device.
 *                    The default behavior is 'Network Key' when security
 *                    is enabled and no 'APS ACK' for the ZCL messages.
 *
 * @return      ZSuccess if OK
 */
ZStatus_t zcl_registerClusterOptionList( uint8 endpoint, uint8 numOption, zclOptionRec_t optionList[] )
{
  zclClusterOptionList *pNewItem;
  zclClusterOptionList *pLoop;

  // Fill in the new profile list
  pNewItem = osal_mem_alloc( sizeof( zclClusterOptionList ) );
  if ( pNewItem == NULL )
    return (ZMemError);

  pNewItem->next = (zclClusterOptionList *)NULL;
  pNewItem->endpoint = endpoint;
  pNewItem->numOptions = numOption;
  pNewItem->options = optionList;

  // Find spot in list
  if ( clusterOptionList == NULL )
  {
    clusterOptionList = pNewItem;
  }
  else
  {
    // Look for end of list
    pLoop = clusterOptionList;
    while ( pLoop->next != NULL )
      pLoop = pLoop->next;

    // Put new item at end of list
    pLoop->next = pNewItem;
  }

  return ( ZSuccess );
}

/*********************************************************************
 * @fn          zcl_registerValidateAttrData
 *
 * @brief       Add a validation function for attribute data
 *
 * @param       pfnValidateAttrData - function pointer to validate routine
 *
 * @return      ZSuccess if OK
 */
ZStatus_t zcl_registerValidateAttrData( zclValidateAttrData_t pfnValidateAttrData )
{
  zcl_ValidateAttrDataCB = pfnValidateAttrData;
  
  return ( ZSuccess );
}

/*********************************************************************
 * @fn      zcl_registerForMsg
 *
 * @brief   The ZCL is setup to send all incoming Foundation Command/Response
 *          messages that aren't processed to one task (if a task is
 *          registered).
 *
 * @param   taskId - task Id of the Application where commands will be sent to
 *
 * @return  TRUE if task registeration successful, FALSE otherwise
 *********************************************************************/
uint8 zcl_registerForMsg( uint8 taskId )
{
  // Allow only the first task
  if ( zcl_RegisteredMsgTaskID == TASK_NO_TASK )
  {
    zcl_RegisteredMsgTaskID = taskId;
    
    return ( true );
  }
  
  return ( false );
}

/*********************************************************************
 * @fn      zcl_DeviceOperational
 *
 * @brief   Used to see whether or not the device can send or respond 
 *          to application level commands.
 *
 * @param   srcEP - source endpoint
 * @param   clusterID - cluster ID
 * @param   frameType - command type
 * @param   cmd - command ID
 *
 * @return  TRUE if device is operational, FALSE otherwise
 */
static uint8 zcl_DeviceOperational( uint8 srcEP, uint16 clusterID, 
                                    uint8 frameType, uint8 cmd, uint16 profileID )
{
  zclAttrRec_t attrRec;
  uint8 deviceEnabled = DEVICE_ENABLED; // default value
  
  (void)profileID;  // Intentionally unreferenced parameter
  
  // If the device is Disabled (DeviceEnabled attribute is set to Disabled), it 
  // cannot send or respond to application level commands, other than commands
  // to read or write attributes. Note that the Identify cluster cannot be 
  // disabled, and remains functional regardless of this setting.
  if ( zcl_ProfileCmd( frameType ) && cmd <= ZCL_CMD_WRITE_NO_RSP )
    return ( TRUE );
  
  if ( clusterID == ZCL_CLUSTER_ID_GEN_IDENTIFY )
    return ( TRUE );
  
  // Is device enabled?
  if ( zclFindAttrRec( srcEP, ZCL_CLUSTER_ID_GEN_BASIC, ATTRID_BASIC_DEVICE_ENABLED, &attrRec ) )
    zclReadAttrData( &deviceEnabled, &attrRec );
  
  return ( deviceEnabled == DEVICE_ENABLED ? TRUE : FALSE );
}

/*********************************************************************
 * @fn      zcl_SendCommand
 *
 * @brief   Used to send Profile and Cluster Specific Command messages.
 *
 *          NOTE: The calling application is responsible for incrementing 
 *                the Sequence Number.
 *
 * @param   srcEp - source endpoint
 * @param   destAddr - destination address 
 * @param   clusterID - cluster ID
 * @param   cmd - command ID
 * @param   specific - whether the command is Cluster Specific
 * @param   direction - client/server direction of the command
 * @param   disableDefaultRsp - disable Default Response command
 * @param   manuCode - manufacturer code for proprietary extensions to a profile
 * @param   seqNumber - identification number for the transaction
 * @param   cmdFormatLen - length of the command to be sent
 * @param   cmdFormat - command to be sent
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendCommand( uint8 srcEP, afAddrType_t *destAddr,
                           uint16 clusterID, uint8 cmd, uint8 specific, uint8 direction,
                           uint8 disableDefaultRsp, uint16 manuCode, uint8 seqNum,
                           uint8 cmdFormatLen, uint8 *cmdFormat )
{
  endPointDesc_t *epDesc;
  zclFrameHdr_t hdr;
  uint8 *msgBuf;
  uint8 msgLen;
  uint8 *pBuf;
  afAddrType_t dstAddr;
  uint8 options;
  ZStatus_t status;

  osal_memcpy( &dstAddr, destAddr, sizeof ( afAddrType_t ) );

  epDesc = afFindEndPointDesc( srcEP );
  if ( epDesc == NULL )
    return ( ZInvalidParameter ); // EMBEDDED RETURN

  if ( clusterID == ZCL_INVALID_CLUSTER_ID )
    return ( ZInvalidParameter ); // EMBEDDED RETURN

#if defined ( INTER_PAN )
  if ( StubAPS_InterPan( destAddr->panId, destAddr->endPoint ) )
    options = AF_TX_OPTIONS_NONE;
  else
#endif
    options = zclGetClusterOption( srcEP, clusterID );
  
  osal_memset( &hdr, 0, sizeof( zclFrameHdr_t ) );

  // Not Profile wide command (like READ, WRITE)
  if ( specific )
    hdr.fc.type = ZCL_FRAME_TYPE_SPECIFIC_CMD;
  else
    hdr.fc.type = ZCL_FRAME_TYPE_PROFILE_CMD;

  if ( zcl_DeviceOperational( srcEP, clusterID, hdr.fc.type, cmd, epDesc->simpleDesc->AppProfId ) == FALSE )
    return ( ZFailure ); // EMBEDDED RETURN
  
  // Fill in the Maufacturer Code
  if ( manuCode != 0 )
  {
    hdr.fc.manuSpecific = 1;
    hdr.manuCode = manuCode;
  }
  
  // Set the Command Direction
  if ( direction )
    hdr.fc.direction = ZCL_FRAME_SERVER_CLIENT_DIR;
  else
    hdr.fc.direction = ZCL_FRAME_CLIENT_SERVER_DIR;

  // Set the Disable Default Response field
  if ( disableDefaultRsp )
    hdr.fc.disableDefaultRsp = 1;
  else
    hdr.fc.disableDefaultRsp = 0;
                     
  // Fill in the Transaction Sequence Number
  hdr.transSeqNum = seqNum;
  
  // Fill in the command
  hdr.commandID = cmd;
  
  // calculate the needed buffer size
  msgLen = zclCalcHdrSize( &hdr );
  msgLen += cmdFormatLen;

  // Allocate the buffer needed
  msgBuf = osal_mem_alloc( msgLen );
  if ( msgBuf != NULL )
  {
    // Fill in the ZCL Header
    pBuf = zclBuildHdr( &hdr, msgBuf );

    // Fill in the command frame
    osal_memcpy( pBuf, cmdFormat, cmdFormatLen );

    status = AF_DataRequest( &dstAddr, epDesc, clusterID, msgLen, msgBuf, 
                             &zcl_TransID, options, AF_DEFAULT_RADIUS );  
    osal_mem_free ( msgBuf );
  }
  else
    status = ZMemError;

  return ( status );
}

#ifdef ZCL_READ
/*********************************************************************
 * @fn      zcl_SendRead
 *
 * @brief   Send a Read command
 *
 * @param   srcEP - Application's endpoint
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   readCmd - read command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendRead( uint8 srcEP, afAddrType_t *dstAddr,
                        uint16 clusterID, zclReadCmd_t *readCmd,
                        uint8 direction, uint8 disableDefaultRsp, uint8 seqNum)
{
  uint8 dataLen;
  uint8 *buf;
  uint8 *pBuf;
  ZStatus_t status;

  dataLen = readCmd->numAttr * 2; // Attribute ID

  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    uint8 i;

    // Load the buffer - serially
    pBuf = buf;   
    for (i = 0; i < readCmd->numAttr; i++)
    {
      *pBuf++ = LO_UINT16( readCmd->attrID[i] );
      *pBuf++ = HI_UINT16( readCmd->attrID[i] );
    }
  
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_READ, FALSE,
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );  
    osal_mem_free( buf );
  }
  else
    status = ZMemError;

  return ( status );
}

/*********************************************************************
 * @fn      zcl_SendReadRsp
 *
 * @brief   Send a Read Response command.
 *
 * @param   srcEP - Application's endpoint
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   readRspCmd - read response command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendReadRsp( uint8 srcEP, afAddrType_t *dstAddr,
                           uint16 clusterID, zclReadRspCmd_t *readRspCmd,
                           uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 *buf;
  uint8 *pBuf;
  zclReadRspStatus_t *statusRec;
  uint8 len = 0;
  uint8 i;
  ZStatus_t status;
  
  // calculate the size of the command
  for ( i = 0; i < readRspCmd->numAttr; i++ )
  {
    statusRec = &(readRspCmd->attrList[i]);
    
    len += 2 + 1; // Attribute ID + Status

    if ( statusRec->status == ZCL_STATUS_SUCCESS )
    {
      len++; // Attribute Data Type
      len += zclGetAttrDataLength( statusRec->dataType, statusRec->data); // Attribute Data
    }
  }

  buf = osal_mem_alloc( len );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    for ( i = 0; i < readRspCmd->numAttr; i++ )
    {
      statusRec = &(readRspCmd->attrList[i]);
      
      *pBuf++ = LO_UINT16( statusRec->attrID );
      *pBuf++ = HI_UINT16( statusRec->attrID );
      *pBuf++ = statusRec->status;

      if ( statusRec->status == ZCL_STATUS_SUCCESS )
      {
        *pBuf++ = statusRec->dataType;
        zclSerializeData( statusRec->dataType, statusRec->data, pBuf );
        
        // move pass attribute data
        pBuf += zclGetAttrDataLength( statusRec->dataType, statusRec->data );
      }
    } // for loop

    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_READ_RSP, FALSE, 
                              direction, disableDefaultRsp, 0, seqNum, len, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;

  return ( status );
}
#endif // ZCL_READ

#ifdef ZCL_WRITE
/*********************************************************************
 * @fn      sendWriteRequest
 *
 * @brief   Send a Write command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   writeCmd - write command to be sent
 * @param   cmd - ZCL_CMD_WRITE, ZCL_CMD_WRITE_UNDIVIDED or ZCL_CMD_WRITE_NO_RSP
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendWriteRequest( uint8 srcEP, afAddrType_t *dstAddr, uint16 clusterID, 
                                zclWriteCmd_t *writeCmd, uint8 cmd, uint8 direction, 
                                uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 *buf;
  uint8 *pBuf;
  zclWriteRec_t *statusRec;
  uint8 attrDataLen;
  uint8 dataLen = 0;
  uint8 i;
  ZStatus_t status;
  
  for ( i = 0; i < writeCmd->numAttr; i++ )
  {
    statusRec = &(writeCmd->attrList[i]);
    
    attrDataLen = zclGetAttrDataLength( statusRec->dataType, statusRec->attrData );
    dataLen += 2 + 1 + attrDataLen; // Attribute ID + Attribute Type + Attribute Data
  }

  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    for ( i = 0; i < writeCmd->numAttr; i++ )
    { 
      statusRec = &(writeCmd->attrList[i]);
      
      *pBuf++ = LO_UINT16( statusRec->attrID );
      *pBuf++ = HI_UINT16( statusRec->attrID );
      *pBuf++ = statusRec->dataType;
      
      zclSerializeData( statusRec->dataType, statusRec->attrData, pBuf );
      
      attrDataLen = zclGetAttrDataLength( statusRec->dataType, statusRec->attrData );
      pBuf += attrDataLen; // move pass attribute data
    }

    status = zcl_SendCommand( srcEP, dstAddr, clusterID, cmd, FALSE, 
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;

  return ( status);
}

/*********************************************************************
 * @fn      zcl_SendWriteRsp
 *
 * @brief   Send a Write Response command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   wrtieRspCmd - write response command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendWriteRsp( uint8 srcEP, afAddrType_t *dstAddr,
                            uint16 clusterID, zclWriteRspCmd_t *writeRspCmd,
                            uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 dataLen;
  uint8 *buf;
  uint8 *pBuf;
  uint8 i;
  ZStatus_t status;
  
  dataLen = writeRspCmd->numAttr * ( 1 + 2 ); // status + attribute id

  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    for ( i = 0; i < writeRspCmd->numAttr; i++ )
    { 
      *pBuf++ = writeRspCmd->attrList[i].status;
      *pBuf++ = LO_UINT16( writeRspCmd->attrList[i].attrID );
      *pBuf++ = HI_UINT16( writeRspCmd->attrList[i].attrID );
    }
    
    // If there's only a single status record and its status field is set to 
    // SUCCESS then omit the attribute ID field.
    if ( writeRspCmd->numAttr == 1 && writeRspCmd->attrList[0].status == ZCL_STATUS_SUCCESS )
      dataLen = 1;
      
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_WRITE_RSP, FALSE,
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;

  return ( status );
}
#endif // ZCL_WRITE

#ifdef ZCL_REPORT
/*********************************************************************
 * @fn      zcl_SendConfigReportCmd
 *
 * @brief   Send a Configure Reporting command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   cfgReportCmd - configure reporting command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendConfigReportCmd( uint8 srcEP, afAddrType_t *dstAddr,
                          uint16 clusterID, zclCfgReportCmd_t *cfgReportCmd,
                          uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 *buf;
  uint8 *pBuf;
  uint8 dataLen = 0;
  zclCfgReportRec_t *reportRec;
  uint8 reportChangeLen; // length of Reportable Change field
  uint8 i;
  ZStatus_t status;
  
  // Find out the data length
  for ( i = 0; i < cfgReportCmd->numAttr; i++ )
  {
    reportRec = &(cfgReportCmd->attrList[i]);
    
    dataLen += 1 + 2; // Direction + Attribute ID
    reportChangeLen = 0;
    
    if ( reportRec->direction == ZCL_SEND_ATTR_REPORTS )
    {
      dataLen += 1 + 2 + 2; // Data Type + Min + Max Reporting Intervals
      
      // Find out the size of the Reportable Change field (for Analog data types)
      if ( zclAnalogDataType( reportRec->dataType ) )
      {
        reportChangeLen = zclGetDataTypeLength( reportRec->dataType );
        dataLen += reportChangeLen;
      }
    }
    else
    {
      dataLen += 2; // Timeout Period
    }
  }
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    for ( i = 0; i < cfgReportCmd->numAttr; i++ )
    {
      reportRec = &(cfgReportCmd->attrList[i]);
      
      *pBuf++ = reportRec->direction;
      *pBuf++ = LO_UINT16( reportRec->attrID );
      *pBuf++ = HI_UINT16( reportRec->attrID );
      
      if ( reportRec->direction == ZCL_SEND_ATTR_REPORTS )
      {
        *pBuf++ = reportRec->dataType;
        *pBuf++ = LO_UINT16( reportRec->minReportInt );
        *pBuf++ = HI_UINT16( reportRec->minReportInt );
        *pBuf++ = LO_UINT16( reportRec->maxReportInt );
        *pBuf++ = HI_UINT16( reportRec->maxReportInt );

        if ( zclAnalogDataType( reportRec->dataType ) )
        {
          zclSerializeData( reportRec->dataType, reportRec->reportableChange, pBuf );
          reportChangeLen = zclGetDataTypeLength( reportRec->dataType );
          pBuf += reportChangeLen;
        }
      }
      else
      {
        *pBuf++ = LO_UINT16( reportRec->timeoutPeriod );
        *pBuf++ = HI_UINT16( reportRec->timeoutPeriod );
      } 
    } // for loop
    
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_CONFIG_REPORT, FALSE, 
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}

/*********************************************************************
 * @fn      zcl_SendConfigReportRspCmd
 *
 * @brief   Send a Configure Reporting Response command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   cfgReportRspCmd - configure reporting response command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendConfigReportRspCmd( uint8 srcEP, afAddrType_t *dstAddr,
                    uint16 clusterID, zclCfgReportRspCmd_t *cfgReportRspCmd,
                    uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 dataLen;
  uint8 *buf;
  uint8 *pBuf;
  uint8 i;
  ZStatus_t status;
  
  // Atrribute list (Status, Direction and Attribute ID)
  dataLen = cfgReportRspCmd->numAttr * ( 1 + 1 + 2 ); 
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf; 
    for ( i = 0; i < cfgReportRspCmd->numAttr; i++ )
    {
      *pBuf++ = cfgReportRspCmd->attrList[i].status;
      *pBuf++ = cfgReportRspCmd->attrList[i].direction;
      *pBuf++ = LO_UINT16( cfgReportRspCmd->attrList[i].attrID );
      *pBuf++ = HI_UINT16( cfgReportRspCmd->attrList[i].attrID );
    }
    
    // If there's only a single status record and its status field is set to 
    // SUCCESS then omit the attribute ID field.
    if ( cfgReportRspCmd->numAttr == 1 && cfgReportRspCmd->attrList[0].status == ZCL_STATUS_SUCCESS )
      dataLen = 1;
    
    status = zcl_SendCommand( srcEP, dstAddr, clusterID,
                              ZCL_CMD_CONFIG_REPORT_RSP, FALSE, direction, 
                              disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}

/*********************************************************************
 * @fn      zcl_SendReadReportCfgCmd
 *
 * @brief   Send a Read Reporting Configuration command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   readReportCfgCmd - read reporting configuration command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendReadReportCfgCmd( uint8 srcEP, afAddrType_t *dstAddr,
                  uint16 clusterID, zclReadReportCfgCmd_t *readReportCfgCmd,
                  uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 dataLen;
  uint8 *buf;
  uint8 *pBuf;
  uint8 i;
  ZStatus_t status;
 
  dataLen = readReportCfgCmd->numAttr * ( 1 + 2 ); // Direction + Atrribute ID
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    for ( i = 0; i < readReportCfgCmd->numAttr; i++ )
    {
      *pBuf++ = readReportCfgCmd->attrList[i].direction;
      *pBuf++ = LO_UINT16( readReportCfgCmd->attrList[i].attrID );
      *pBuf++ = HI_UINT16( readReportCfgCmd->attrList[i].attrID );
    }
    
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_READ_REPORT_CFG, FALSE, 
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}

/*********************************************************************
 * @fn      zcl_SendReadReportCfgRspCmd
 *
 * @brief   Send a Read Reporting Configuration Response command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   readReportCfgRspCmd - read reporting configuration response command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendReadReportCfgRspCmd( uint8 srcEP, afAddrType_t *dstAddr,
             uint16 clusterID, zclReadReportCfgRspCmd_t *readReportCfgRspCmd,
             uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 *buf;
  uint8 *pBuf;
  uint8 dataLen = 0;
  zclReportCfgRspRec_t *reportRspRec;
  uint8 reportChangeLen;
  uint8 i;
  ZStatus_t status;

  // Find out the data length
  for ( i = 0; i < readReportCfgRspCmd->numAttr; i++ )
  {
    reportRspRec = &(readReportCfgRspCmd->attrList[i]);
    
    dataLen += 1 + 1 + 2 ; // Status, Direction and Atrribute ID
    
    if ( reportRspRec->status == ZCL_STATUS_SUCCESS )
    {
      if ( reportRspRec->direction == ZCL_SEND_ATTR_REPORTS )
      {
        dataLen += 1 + 2 + 2; // Data Type + Min + Max Reporting Intervals
      
        if ( zclAnalogDataType( reportRspRec->dataType ) )
        {
          reportChangeLen = zclGetDataTypeLength( reportRspRec->dataType );
          dataLen += reportChangeLen; // Reportable Change field
        }
      }
      else
      {
        dataLen += 2; // Timeout Period
      }
    }
  }
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;

    for ( i = 0; i < readReportCfgRspCmd->numAttr; i++ )
    {
      reportRspRec = &(readReportCfgRspCmd->attrList[i]);

      *pBuf++ = reportRspRec->status;
      *pBuf++ = reportRspRec->direction;
      *pBuf++ = LO_UINT16( reportRspRec->attrID );
      *pBuf++ = HI_UINT16( reportRspRec->attrID );
     
      if ( reportRspRec->status == ZCL_STATUS_SUCCESS )
      {
        if ( reportRspRec->direction == ZCL_SEND_ATTR_REPORTS )
        {
          *pBuf++ = reportRspRec->dataType;
          *pBuf++ = LO_UINT16( reportRspRec->minReportInt );
          *pBuf++ = HI_UINT16( reportRspRec->minReportInt );
          *pBuf++ = LO_UINT16( reportRspRec->maxReportInt );
          *pBuf++ = HI_UINT16( reportRspRec->maxReportInt );

          if ( zclAnalogDataType( reportRspRec->dataType ) )
          {
            zclSerializeData( reportRspRec->dataType, 
                              reportRspRec->reportableChange, pBuf );
            reportChangeLen = zclGetDataTypeLength( reportRspRec->dataType );
            pBuf += reportChangeLen;
          }
        }
        else
        {
          *pBuf++ = LO_UINT16( reportRspRec->timeoutPeriod );
          *pBuf++ = HI_UINT16( reportRspRec->timeoutPeriod );
        }
      }
    }
   
    status = zcl_SendCommand( srcEP, dstAddr, clusterID,
                              ZCL_CMD_READ_REPORT_CFG_RSP, FALSE,
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}

/*********************************************************************
 * @fn      zcl_SendReportCmd
 *
 * @brief   Send a Report command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   reportCmd - report command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendReportCmd( uint8 srcEP, afAddrType_t *dstAddr,
                             uint16 clusterID, zclReportCmd_t *reportCmd,
                             uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  zclReport_t *reportRec;
  uint8 attrDataLen;
  uint8 dataLen = 0;
  uint8 *buf;
  uint8 *pBuf;
  uint8 i;
  ZStatus_t status;
  
  // calculate the size of the command
  for ( i = 0; i < reportCmd->numAttr; i++ )
  {
    reportRec = &(reportCmd->attrList[i]);
    
    dataLen += 2 + 1; // Attribute ID + data type

    attrDataLen = zclGetAttrDataLength( reportRec->dataType, reportRec->attrData );
    dataLen += attrDataLen; // Attribute Data
  }
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    for ( i = 0; i < reportCmd->numAttr; i++ )
    {
      reportRec = &(reportCmd->attrList[i]);
      
      *pBuf++ = LO_UINT16( reportRec->attrID );
      *pBuf++ = HI_UINT16( reportRec->attrID );
      *pBuf++ = reportRec->dataType;

      zclSerializeData( reportRec->dataType, reportRec->attrData, pBuf );
      attrDataLen = zclGetAttrDataLength( reportRec->dataType, reportRec->attrData );
      pBuf += attrDataLen; // move pass attribute data
    }
 
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_REPORT, FALSE, 
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}
#endif // ZCL_REPORT
       
/*********************************************************************
 * @fn      zcl_SendDefaultRspCmd
 *
 * @brief   Send a Default Response command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   defaultRspCmd - default response command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendDefaultRspCmd( uint8 srcEP, afAddrType_t *dstAddr,
                            uint16 clusterID, zclDefaultRspCmd_t *defaultRspCmd,
                            uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 buf[2]; // Command ID and Status;

  // Load the buffer - serially
  buf[0] = defaultRspCmd->commandID;
  buf[1] = defaultRspCmd->statusCode;

  return ( zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_DEFAULT_RSP, FALSE,
                            direction, disableDefaultRsp, 0, seqNum, 2, buf ) ); 
}

#ifdef ZCL_DISCOVER
/*********************************************************************
 * @fn      zcl_SendDiscoverCmd
 *
 * @brief   Send a Discover command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   discoverCmd - discover command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendDiscoverCmd( uint8 srcEP, afAddrType_t *dstAddr,
                            uint16 clusterID, zclDiscoverCmd_t *discoverCmd,
                            uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 dataLen = 2 + 1; // Start Attribute ID and Max Attribute IDs
  uint8 *buf;
  uint8 *pBuf;
  ZStatus_t status;
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    *pBuf++ = LO_UINT16(discoverCmd->startAttr);
    *pBuf++ = HI_UINT16(discoverCmd->startAttr);
    *pBuf++ = discoverCmd->maxAttrIDs;
    
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_DISCOVER, FALSE, 
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}

/*********************************************************************
 * @fn      zcl_SendDiscoverRspCmd
 *
 * @brief   Send a Discover Response command
 *
 * @param   dstAddr - destination address
 * @param   clusterID - cluster ID
 * @param   reportRspCmd - report response command to be sent
 * @param   direction - direction of the command
 * @param   seqNum - transaction sequence number
 *
 * @return  ZSuccess if OK
 */
ZStatus_t zcl_SendDiscoverRspCmd( uint8 srcEP, afAddrType_t *dstAddr,
                      uint16 clusterID, zclDiscoverRspCmd_t *discoverRspCmd,
                      uint8 direction, uint8 disableDefaultRsp, uint8 seqNum )
{
  uint8 dataLen = 1; // Discovery complete
  uint8 *buf;
  uint8 *pBuf;
  uint8 i;
  ZStatus_t status;
  
  // calculate the size of the command
  dataLen += discoverRspCmd->numAttr * (2 + 1); // Attribute ID and Data Type
  
  buf = osal_mem_alloc( dataLen );
  if ( buf != NULL )
  {
    // Load the buffer - serially
    pBuf = buf;
    *pBuf++ = discoverRspCmd->discComplete;    
    for ( i = 0; i < discoverRspCmd->numAttr; i++ )
    {
      *pBuf++ = LO_UINT16(discoverRspCmd->attrList[i].attrID);
      *pBuf++ = HI_UINT16(discoverRspCmd->attrList[i].attrID);
      *pBuf++ = discoverRspCmd->attrList[i].dataType;
    }
    
    status = zcl_SendCommand( srcEP, dstAddr, clusterID, ZCL_CMD_DISCOVER_RSP, FALSE,
                              direction, disableDefaultRsp, 0, seqNum, dataLen, buf );
    osal_mem_free( buf );
  }
  else
    status = ZMemError;
  
  return ( status );
}
#endif // ZCL_DISCOVER

/*********************************************************************
 * PRIVATE FUNCTIONS
 *********************************************************************/

/*********************************************************************
 * @fn      zclProcessMessageMSG
 *
 * @brief   Data message processor callback.  This function processes
 *          any incoming data - probably from other devices.  So, based
 *          on cluster ID, perform the intended action.
 *
 * @param   pkt - incoming message
 *
 * @return  none
 */
static void zclProcessMessageMSG( afIncomingMSGPacket_t *pkt )
{
  endPointDesc_t *epDesc;
  zclIncoming_t inMsg;
  zclLibPlugin_t *pInPlugin;
  zclDefaultRspCmd_t defautlRspCmd;
  uint8 options;
  uint8 securityEnable;
  uint8 interPanMsg;
  ZStatus_t status = ZFailure;

  if ( pkt->cmd.DataLength == 0 )
    return;   // Error, ignore the message

  // Initialize
  inMsg.msg = pkt;
  inMsg.attrCmd = NULL;
  inMsg.pData = NULL;
  inMsg.pDataLen = 0;

  inMsg.pData = zclParseHdr( &(inMsg.hdr), pkt->cmd.Data );
  inMsg.pDataLen = pkt->cmd.DataLength;
  inMsg.pDataLen -= (uint8)(inMsg.pData - pkt->cmd.Data);

  // Find the wanted endpoint
  epDesc = afFindEndPointDesc( pkt->endPoint );
  if ( epDesc == NULL )
    return;   // Error, ignore the message

  if ( pkt->clusterId == ZCL_INVALID_CLUSTER_ID )
    return;   // Error, ignore the message
  
  if ( zcl_DeviceOperational( pkt->endPoint, pkt->clusterId, 
                              inMsg.hdr.fc.type, inMsg.hdr.commandID, epDesc->simpleDesc->AppProfId ) == FALSE )
  {
    return; // Error, ignore the message
  }

#if defined ( INTER_PAN )
  if ( StubAPS_InterPan( pkt->srcAddr.panId, pkt->srcAddr.endPoint ) )
  {
    // No foundation command is supported thru Inter-PAN communication
    if ( zcl_ProfileCmd( inMsg.hdr.fc.type ) )
      return;

    interPanMsg = TRUE; 
    options = AF_TX_OPTIONS_NONE;
  }
  else
#endif
  {
    interPanMsg = FALSE;
    options = zclGetClusterOption( pkt->endPoint, pkt->clusterId );
  }
  
  // Local and remote Security options must match except for Default Response command
  if ( !zcl_DefaultRspCmd( inMsg.hdr ) )
  {
    securityEnable = ( options & AF_EN_SECURITY ) ? TRUE : FALSE;
    if ( pkt->SecurityUse != securityEnable )
    {
      if ( UNICAST_MSG( inMsg.msg ) )
      {
        // Send a Default Response command back with no Application Link Key security
        if ( securityEnable )
          zclSetSecurityOption( pkt->endPoint, pkt->clusterId, FALSE );
        
        defautlRspCmd.statusCode = status;
        defautlRspCmd.commandID = inMsg.hdr.commandID;
        zcl_SendDefaultRspCmd( inMsg.msg->endPoint, &(inMsg.msg->srcAddr),
                               inMsg.msg->clusterId, &defautlRspCmd, 
                               ZCL_FRAME_SERVER_CLIENT_DIR, true, inMsg.hdr.transSeqNum );
        if ( securityEnable )
          zclSetSecurityOption( pkt->endPoint, pkt->clusterId, TRUE );       
      }
      
      return;   // Error, ignore the message
    }
  }
  
  // Is this a foundation type message
  if ( zcl_ProfileCmd( inMsg.hdr.fc.type ) )
  { 
    if ( inMsg.hdr.fc.manuSpecific )
    {
      // We don't support any manufacturer specific command
      status = ZCL_STATUS_UNSUP_MANU_GENERAL_COMMAND;
    }
    else if ( ( inMsg.hdr.commandID <= ZCL_CMD_MAX ) && 
              ( zclCmdTable[inMsg.hdr.commandID].pfnParseInProfile != NULL ) )
    {
      zclParseCmd_t parseCmd;
      
      parseCmd.endpoint = pkt->endPoint;
      parseCmd.dataLen = inMsg.pDataLen;
      parseCmd.pData = inMsg.pData;
      
      // Parse the command, remember that the return value is a pointer to allocated memory
      inMsg.attrCmd = zclParseCmd( inMsg.hdr.commandID, &parseCmd );
      if ( (inMsg.attrCmd != NULL) && (zclCmdTable[inMsg.hdr.commandID].pfnProcessInProfile != NULL) )
      { 
        // Process the command
        if ( zclProcessCmd( inMsg.hdr.commandID, &inMsg ) == FALSE )
        {
          // Couldn't find attribute in the table.
        }
      }
       
      // Free the buffer
      if ( inMsg.attrCmd )
        osal_mem_free( inMsg.attrCmd );
      
      if ( CMD_HAS_RSP( inMsg.hdr.commandID ) )
        return; // We're done
      
      status = ZSuccess;
    }
    else
    {
      // Unsupported message
      status = ZCL_STATUS_UNSUP_GENERAL_COMMAND;
    }
  }
  else
  {
    // Nope, must be specific to the cluster ID

    // Find the appropriate plugin
    pInPlugin = zclFindPlugin( pkt->clusterId, epDesc->simpleDesc->AppProfId );
    if ( pInPlugin && pInPlugin->pfnIncomingHdlr )
    { 
      // The return value of the plugin function will be
      //  ZSuccess - Supported and need default response
      //  ZFailure - Unsupported
      //  ZCL_STATUS_CMD_HAS_RSP - Supported and do not need default rsp
      //  ZCL_STATUS_INVALID_FIELD - Supported, but the incoming msg is wrong formatted
      //  ZCL_STATUS_INVALID_VALUE - Supported, but the request not achievable by the h/w
      //  ZCL_STATUS_SOFTWARE_FAILURE - Supported but ZStack memory allocation fails
      status = pInPlugin->pfnIncomingHdlr( &inMsg );
      if ( status == ZCL_STATUS_CMD_HAS_RSP || ( interPanMsg && status == ZSuccess ) )
        return; // We're done      
    }
    
    if ( status == ZFailure )
    {
      // Unsupported message
      if ( inMsg.hdr.fc.manuSpecific )
        status = ZCL_STATUS_UNSUP_MANU_CLUSTER_COMMAND;
      else
        status = ZCL_STATUS_UNSUP_CLUSTER_COMMAND;
    }
  }
  
  if ( UNICAST_MSG( inMsg.msg ) && inMsg.hdr.fc.disableDefaultRsp == 0 )
  {
    // Send a Default Response command back
    defautlRspCmd.statusCode = status;
    defautlRspCmd.commandID = inMsg.hdr.commandID;
    zcl_SendDefaultRspCmd( inMsg.msg->endPoint, &(inMsg.msg->srcAddr),
                           inMsg.msg->clusterId, &defautlRspCmd, 
                           ZCL_FRAME_SERVER_CLIENT_DIR, true, inMsg.hdr.transSeqNum );
  }
}

/*********************************************************************
 * @fn      zclParseHdr
 *
 * @brief   Parse header of the ZCL format
 *
 * @param   hdr - place to put the frame control information
 * @param   pData - incoming buffer to parse
 *
 * @return  pointer past the header
 */
uint8 *zclParseHdr( zclFrameHdr_t *hdr, uint8 *pData )
{
  // Clear the header
  osal_memset( (uint8 *)hdr, 0, sizeof ( zclFrameHdr_t ) );

  // Parse the Frame Control
  hdr->fc.type = zcl_FCType( *pData );
  hdr->fc.manuSpecific = zcl_FCManuSpecific( *pData ) ? 1 : 0;
  if ( zcl_FCDirection( *pData ) )
    hdr->fc.direction = ZCL_FRAME_SERVER_CLIENT_DIR;
  else
    hdr->fc.direction = ZCL_FRAME_CLIENT_SERVER_DIR;
  
  hdr->fc.disableDefaultRsp = zcl_FCDisableDefaultRsp( *pData ) ? 1 : 0;
  pData++;  // move past the frame control field

  // parse the manfacturer code
  if ( hdr->fc.manuSpecific )
  {
    hdr->manuCode = BUILD_UINT16( pData[0], pData[1] );
    pData += 2;
  }

  // parse the Transaction Sequence Number
  hdr->transSeqNum = *pData++;

  // parse the Cluster's command ID
  hdr->commandID = *pData++;

  // Should point to the frame payload
  return ( pData );
}

/*********************************************************************
 * @fn      zclBuildHdr
 *
 * @brief   Build header of the ZCL format
 *
 * @param   hdr - outgoing header information
 * @param   pData - outgoing header space
 *
 * @return  pointer past the header
 */
static uint8 *zclBuildHdr( zclFrameHdr_t *hdr, uint8 *pData )
{
  // Build the Frame Control byte
  *pData = hdr->fc.type;
  *pData |= hdr->fc.manuSpecific << 2;
  *pData |= hdr->fc.direction << 3;
  *pData |= hdr->fc.disableDefaultRsp << 4;
  pData++;  // move past the frame control field

  // Add the manfacturer code
  if ( hdr->fc.manuSpecific )
  {
    *pData++ = LO_UINT16( hdr->manuCode );
    *pData++ = HI_UINT16( hdr->manuCode );
  }

  // Add the Transaction Sequence Number
  *pData++ = hdr->transSeqNum;
  
  // Add the Cluster's command ID
  *pData++ = hdr->commandID;

  // Should point to the frame payload
  return ( pData );
}

/*********************************************************************
 * @fn      zclCalcHdrSize
 *
 * @brief   Calculate the number of bytes needed for an outgoing
 *          ZCL header.
 *
 * @param   hdr - outgoing header information
 *
 * @return  returns the number of bytes needed
 */
static uint8 zclCalcHdrSize( zclFrameHdr_t *hdr )
{
  uint8 needed = (1 + 1 + 1); // frame control + transaction seq num + cmd ID

  // Add the manfacturer code
  if ( hdr->fc.manuSpecific )
    needed += 2;

  return ( needed );
}

/*********************************************************************
 * @fn      zclFindPlugin
 *
 * @brief   Find the right plugin for a cluster ID
 *
 * @param   clusterID - cluster ID to look for
 * @param   profileID - profile ID
 * 
 * @return  pointer to plugin, NULL if not found
 */
static zclLibPlugin_t *zclFindPlugin( uint16 clusterID, uint16 profileID )
{
  zclLibPlugin_t *pLoop;
  
  (void)profileID;  // Intentionally unreferenced parameter

  if ( clusterID != ZCL_INVALID_CLUSTER_ID )
  {
    pLoop = plugins;
    while ( pLoop != NULL )
    {
      if ( clusterID >= pLoop->startClusterID && clusterID <= pLoop->endClusterID )
        return ( pLoop );
      pLoop = pLoop->next;
    }
  }
  return ( (zclLibPlugin_t *)NULL );
}

/*********************************************************************
 * @fn      zclFindAttrRec
 *
 * @brief   Find the attribute record that matchs the parameters
 *
 * @param   endpoint - Application's endpoint
 * @param   clusterID - cluster ID
 * @param   attrId - attribute looking for
 *
 * @return  TRUE if record found. FALSE, otherwise.
 */
uint8 zclFindAttrRec( uint8 endpoint, uint16 clusterID, uint16 attrId, zclAttrRec_t *pAttr )
{
  uint8 x;
  zclAttrRecsList *pLoop;

  pLoop = attrList;

  while ( pLoop != NULL )
  {
    if ( pLoop->endpoint == endpoint )
    {
      for ( x = 0; x < pLoop->numAttributes; x++ )
      {
        if ( pLoop->attrs[x].clusterID == clusterID && pLoop->attrs[x].attr.attrId == attrId )
        {
          *pAttr = pLoop->attrs[x];
          return ( TRUE ); // EMBEDDED RETURN
        }
      }
    }
    pLoop = pLoop->next;
  }

  return ( FALSE );
}

/*********************************************************************
 * @fn      zclFindClusterOption
 *
 * @brief   Find the option record that matchs the cluster id
 *
 * @param   endpoint - Application's endpoint
 * @param   clusterID - cluster ID looking for
 *
 * @return  pointer to clutser option, NULL if not found
 */
static zclOptionRec_t *zclFindClusterOption( uint8 endpoint, uint16 clusterID )
{
  uint8 x;
  zclClusterOptionList *pLoop;

  pLoop = clusterOptionList;

  while ( pLoop != NULL )
  {
    if ( pLoop->endpoint == endpoint )
    {
      for ( x = 0; x < pLoop->numOptions; x++ )
      {
        if ( pLoop->options[x].clusterID == clusterID )
          return ( &(pLoop->options[x]) ); // EMBEDDED RETURN
      }
    }
    pLoop = pLoop->next;
  }

  return ( NULL );
}

/*********************************************************************
 * @fn      zclGetClusterOption
 *
 * @brief   Get the option record that matchs the cluster id
 *
 * @param   endpoint - Application's endpoint
 * @param   clusterID - cluster ID looking for
 *
 * @return  clutser option, AF_TX_OPTIONS_NONE if not found
 */
static uint8 zclGetClusterOption( uint8 endpoint, uint16 clusterID )
{
  uint8 option;
  zclOptionRec_t *pOption;

   pOption = zclFindClusterOption( endpoint, clusterID );
  if ( pOption != NULL )
  {
    option = pOption->option;
    if ( !ZG_SECURE_ENABLED )
      option &= (AF_EN_SECURITY ^ 0xFF); // make sure Application Link Key security is off

    return ( option ); // EMBEDDED RETURN
  }

  return ( AF_TX_OPTIONS_NONE );
}

/*********************************************************************
 * @fn      zclSetSecurityOption
 *
 * @brief   Set the security option for the cluster id
 *
 * @param   endpoint - Application's endpoint
 * @param   clusterID - cluster ID looking for
 * @param   enable - whether to enable (TRUE) or disable (FALSE) security option
 *
 * @return  none
 */
static void zclSetSecurityOption( uint8 endpoint, uint16 clusterID, uint8 enable )
{
  zclOptionRec_t *pOption;

  pOption = zclFindClusterOption( endpoint, clusterID );
  if ( pOption != NULL )
  {
    if ( enable )
      pOption->option |= AF_EN_SECURITY;
    else
      pOption->option &= (AF_EN_SECURITY ^ 0xFF);
  }
}

#ifdef ZCL_DISCOVER
/*********************************************************************
 * @fn      zclFindNextAttrRec
 *
 * @brief   Find the attribute (or next) record that matchs the parameters
 *
 * @param   endpoint - Application's endpoint
 * @param   clusterID - cluster ID
 * @param   attr - attribute looking for
 *
 * @return  pointer to attribute record, NULL if not found
 */
static uint8 zclFindNextAttrRec( uint8 endpoint, uint16 clusterID,
                                 uint16 *attrId, zclAttrRec_t *pAttr )
{
  uint16 x;
  zclAttrRecsList *pLoop;

  pLoop = attrList;

  while ( pLoop != NULL )
  {
    if ( pLoop->endpoint == endpoint )
    {
      for ( x = 0; x < pLoop->numAttributes; x++ )
      {
        if ( pLoop->attrs[x].clusterID == clusterID && pLoop->attrs[x].attr.attrId >= *attrId )
        {
          *pAttr = pLoop->attrs[x];
          
          // Update attribute ID
          *attrId = pAttr->attr.attrId; 
          return ( TRUE ); // EMBEDDED RETURN
        }
      }
    }
    pLoop = pLoop->next;
  }

  return ( FALSE );
}
#endif // ZCL_DISCOVER

#if defined(ZCL_READ) || defined(ZCL_WRITE) || defined(ZCL_REPORT)
/*********************************************************************
 * @fn      zclSerializeData
 *
 * @brief   Builds a buffer from the attribute data to sent out over
 *          the air.
 *
 * @param   dataType - data types defined in zcl.h
 * @param   attrData - pointer to the attribute data
 * @param   buf - where to put the serialized data
 *
 * @return  none
 */
static void zclSerializeData( uint8 dataType, void *attrData, uint8 *buf )
{
  uint8 *pStr;
  uint8 len;

  switch ( dataType )
  {
    case ZCL_DATATYPE_DATA8:
    case ZCL_DATATYPE_BOOLEAN:
    case ZCL_DATATYPE_BITMAP8:
    case ZCL_DATATYPE_INT8:
    case ZCL_DATATYPE_UINT8:
    case ZCL_DATATYPE_ENUM8:
      *buf = *((uint8 *)attrData);
       break;

    case ZCL_DATATYPE_DATA16:
    case ZCL_DATATYPE_BITMAP16:
    case ZCL_DATATYPE_UINT16:
    case ZCL_DATATYPE_INT16: 
    case ZCL_DATATYPE_ENUM16:
    case ZCL_DATATYPE_SEMI_PREC:
    case ZCL_DATATYPE_CLUSTER_ID:
    case ZCL_DATATYPE_ATTR_ID:
      *buf++ = LO_UINT16( *((uint16*)attrData) );
      *buf++ = HI_UINT16( *((uint16*)attrData) );
      break;

    case ZCL_DATATYPE_DATA24:
    case ZCL_DATATYPE_BITMAP24: 
    case ZCL_DATATYPE_UINT24:
    case ZCL_DATATYPE_INT24:
      *buf++ = BREAK_UINT32( *((uint32*)attrData), 0 );
      *buf++ = BREAK_UINT32( *((uint32*)attrData), 1 );
      *buf++ = BREAK_UINT32( *((uint32*)attrData), 2 );
      break;
      
    case ZCL_DATATYPE_DATA32:
    case ZCL_DATATYPE_BITMAP32:
    case ZCL_DATATYPE_UINT32:
    case ZCL_DATATYPE_INT32:
    case ZCL_DATATYPE_SINGLE_PREC:
    case ZCL_DATATYPE_TOD:
    case ZCL_DATATYPE_DATE:
    case ZCL_DATATYPE_UTC:
    case ZCL_DATATYPE_BAC_OID:
      buf = osal_buffer_uint32( buf, *((uint32*)attrData) );
      break;
      
    case ZCL_DATATYPE_UINT40:
      pStr = (uint8*)attrData;
      osal_memcpy( buf, pStr, 5 );
      break;
      
    case ZCL_DATATYPE_UINT48:
      pStr = (uint8*)attrData;
      osal_memcpy( buf, pStr, 6 );
      break;
      
    case ZCL_DATATYPE_IEEE_ADDR:
      pStr = (uint8*)attrData;
      osal_memcpy( buf, pStr, 8 );
      break;
      
    case ZCL_DATATYPE_CHAR_STR:
    case ZCL_DATATYPE_OCTET_STR:
      pStr = (uint8*)attrData;
      len = *pStr++;
      *buf++ = len;
      osal_memcpy( buf, pStr, len );
      break;
      
    case ZCL_DATATYPE_NO_DATA:
    case ZCL_DATATYPE_UNKNOWN:
      // Fall through

    default:
      break;
  }
}
#endif // ZCL_READ || ZCL_WRITE || ZCL_REPORT

#ifdef ZCL_REPORT
/*********************************************************************
 * @fn      zclAnalogDataType
 *
 * @brief   Checks to see if Data Type is Analog
 *
 * @param   dataType - data type
 *
 * @return  TRUE if data type is analog
 */
uint8 zclAnalogDataType( uint8 dataType )
{
  uint8 analog;
  
  switch ( dataType )
  {
    case ZCL_DATATYPE_UINT8:
    case ZCL_DATATYPE_UINT16:
    case ZCL_DATATYPE_UINT24:
    case ZCL_DATATYPE_UINT32:
    case ZCL_DATATYPE_UINT40:
    case ZCL_DATATYPE_UINT48:
    case ZCL_DATATYPE_INT8:
    case ZCL_DATATYPE_INT16:
    case ZCL_DATATYPE_INT24:
    case ZCL_DATATYPE_INT32:
    case ZCL_DATATYPE_SEMI_PREC:
    case ZCL_DATATYPE_SINGLE_PREC:
    case ZCL_DATATYPE_DOUBLE_PREC:
    case ZCL_DATATYPE_TOD:
    case ZCL_DATATYPE_DATE:
    case ZCL_DATATYPE_UTC:
      analog = TRUE;
      break;
      
    default:
      analog = FALSE;
      break;
  }
  
  return ( analog );
}

/*********************************************************************
 * @fn      zcl_BuildAnalogData
 *
 * @brief   Build an analog arribute out of sequential bytes.
 *
 * @param   dataType - type of data
 * @param   pData - pointer to data
 * @param   pBuf - where to put the data
 *
 * @return  none
 */
static void zcl_BuildAnalogData( uint8 dataType, uint8 *pData, uint8 *pBuf)
{
  switch ( dataType )
  {
    case ZCL_DATATYPE_UINT8:
    case ZCL_DATATYPE_INT8:
      *pData = *pBuf;
      break;

    case ZCL_DATATYPE_UINT16:
    case ZCL_DATATYPE_INT16:
    case ZCL_DATATYPE_SEMI_PREC:
      *((uint16*)pData) = BUILD_UINT16( pBuf[0], pBuf[1] ); 
      break;
 
    case ZCL_DATATYPE_UINT24:
    case ZCL_DATATYPE_INT24:
      *((uint32*)pData) = osal_build_uint32( pBuf, 3 );
      break;
      
    case ZCL_DATATYPE_UINT32:
    case ZCL_DATATYPE_INT32:
    case ZCL_DATATYPE_SINGLE_PREC:
    case ZCL_DATATYPE_TOD:
    case ZCL_DATATYPE_DATE:
    case ZCL_DATATYPE_UTC:
      *((uint32*)pData) = osal_build_uint32( pBuf, 4 );
      break;
      
    case ZCL_DATATYPE_DOUBLE_PREC:
      *pData = 0;
      break;
 
    default:
      break;
  }
}
#endif // ZCL_REPORT

/*********************************************************************
 * @fn      zclGetDataTypeLength
 *
 * @brief   Return the length of the datatype in length. 
 *          NOTE: Should not be called for ZCL_DATATYPE_OCTECT_STR or 
 *                ZCL_DATATYPE_CHAR_STR data types.
 *
 * @param   dataType - data type
 *
 * @return  length of data
 */
uint8 zclGetDataTypeLength( uint8 dataType )
{
  uint8 len;
  
  switch ( dataType )
  {
    case ZCL_DATATYPE_DATA8:
    case ZCL_DATATYPE_BOOLEAN:
    case ZCL_DATATYPE_BITMAP8:
    case ZCL_DATATYPE_INT8:
    case ZCL_DATATYPE_UINT8:
    case ZCL_DATATYPE_ENUM8:
      len = 1;
      break;
      
    case ZCL_DATATYPE_DATA16:
    case ZCL_DATATYPE_BITMAP16:
    case ZCL_DATATYPE_UINT16:
    case ZCL_DATATYPE_INT16: 
    case ZCL_DATATYPE_ENUM16:
    case ZCL_DATATYPE_SEMI_PREC:
    case ZCL_DATATYPE_CLUSTER_ID:
    case ZCL_DATATYPE_ATTR_ID:
      len = 2;
      break;
      
    case ZCL_DATATYPE_DATA24:
    case ZCL_DATATYPE_BITMAP24: 
    case ZCL_DATATYPE_UINT24:
    case ZCL_DATATYPE_INT24:
      len = 3;
      break;
      
    case ZCL_DATATYPE_DATA32:
    case ZCL_DATATYPE_BITMAP32:
    case ZCL_DATATYPE_UINT32:
    case ZCL_DATATYPE_INT32:
    case ZCL_DATATYPE_SINGLE_PREC:
    case ZCL_DATATYPE_TOD:
    case ZCL_DATATYPE_DATE:
    case ZCL_DATATYPE_UTC:
    case ZCL_DATATYPE_BAC_OID:
      len = 4;
      break;                       
                
   case ZCL_DATATYPE_UINT40:
       len = 5;
       break;
       
   case ZCL_DATATYPE_UINT48:
       len = 6;
       break;
       
   case ZCL_DATATYPE_DOUBLE_PREC:
   case ZCL_DATATYPE_IEEE_ADDR:
     len = 8;
     break;

    case ZCL_DATATYPE_NO_DATA:
    case ZCL_DATATYPE_UNKNOWN:
      // Fall through
      
    default:
      len = 0;
      break;
  }
  
  return ( len );
}

/*********************************************************************
 * @fn      zclGetAttrDataLength
 *
 * @brief   Return the length of the attribute.
 *
 * @param   dataType - data type
 * @param   pData - pointer to data
 *
 * @return  returns atrribute lentgh
 */
uint8 zclGetAttrDataLength( uint8  dataType, uint8 *pData)
{
  uint8 dataLen = 0;
  
  if ( dataType  == ZCL_DATATYPE_CHAR_STR || dataType == ZCL_DATATYPE_OCTET_STR )
  {
    dataLen = *pData + 1; // string length + 1 for length field
  }
  else
  {
    dataLen = zclGetDataTypeLength( dataType );
  }

  return ( dataLen );
}

/*********************************************************************
 * @fn      zclReadAttrData
 *
 * @brief   Read the attribute's current value into pAttrData.
 *
 * @param   pAttrData - where to put attribute data
 * @param   pAttr - pointer to attribute
 *
 * @return Success
 */
uint8 zclReadAttrData( uint8 *pAttrData, zclAttrRec_t *pAttr )
{
  uint8 dataLen;
    
  dataLen = zclGetAttrDataLength( pAttr->attr.dataType, (uint8*)(pAttr->attr.dataPtr) );
  osal_memcpy( pAttrData, pAttr->attr.dataPtr, dataLen );
    
  return ( ZCL_STATUS_SUCCESS );
}

#ifdef ZCL_WRITE
/*********************************************************************
 * @fn      zclWriteAttrData
 *
 * @brief   Write the received data.
 *
 * @param   pAttr - where to write data to
 * @param   pWriteRec - data to be written
 *
 * @return  Successful if data was written
 */
static uint8 zclWriteAttrData( zclAttrRec_t *pAttr, zclWriteRec_t *pWriteRec )
{
  uint8 len;

  if ( zcl_AccessCtrlWrite( pAttr->attr.accessControl ) )
  {
    if ( zcl_ValidateAttrDataCB && !zcl_ValidateAttrDataCB( pAttr, pWriteRec ) )
      return ( ZCL_STATUS_INVALID_VALUE );
    
    len = zclGetAttrDataLength( pAttr->attr.dataType, pWriteRec->attrData );
    osal_memcpy( pAttr->attr.dataPtr, pWriteRec->attrData, len );
    return ( ZCL_STATUS_SUCCESS );
  }
    
  return ( ZCL_STATUS_READ_ONLY );
}
#endif // ZCL_WRITE

#ifdef ZCL_READ
/*********************************************************************
 * @fn      zclParseInReadCmd
 *
 * @brief   Parse the "Profile" Read Commands
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
void *zclParseInReadCmd( zclParseCmd_t *pCmd )
{
  zclReadCmd_t *readCmd;
  uint8 *pBuf = pCmd->pData;
  
  readCmd = (zclReadCmd_t *)osal_mem_alloc( sizeof ( zclReadCmd_t ) + pCmd->dataLen );
  if ( readCmd != NULL )
  {
    uint8 i;
    
    readCmd->numAttr = pCmd->dataLen / 2; // Atrribute ID
    for ( i = 0; i < readCmd->numAttr; i++ )
    {
      readCmd->attrID[i] = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
    }
  }

  return ( (void *)readCmd );
}

/*********************************************************************
 * @fn      zclParseInReadRspCmd
 *
 * @brief   Parse the "Profile" Read Response Commands
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
static void *zclParseInReadRspCmd( zclParseCmd_t *pCmd )
{
  zclReadRspCmd_t *readRspCmd;
  zclReadRspStatus_t *statusRec;
  uint8 *pBuf = pCmd->pData;
  uint8 *dataPtr;
  uint8 numAttr = 0;
  uint8 hdrLen;
  uint8 dataLen = 0;
  uint8 attrDataLen;
  uint8 dataType;
  uint8 status;
  uint8 i;
  
  // find out the number of attributes and the length of attribute data
  while ( pBuf < ( pCmd->pData + pCmd->dataLen ) )
  {
    numAttr++;

    pBuf += 2; // move pass attribute id
    
    status = *pBuf++;  
    if ( status == ZCL_STATUS_SUCCESS )
    {
      dataType = *pBuf++;

      attrDataLen = zclGetAttrDataLength( dataType, pBuf );
      pBuf += attrDataLen; // move pass attribute data
      
      // add padding if needed
      if ( PADDING_NEEDED( attrDataLen ) )
        attrDataLen++;
      dataLen += attrDataLen;
    }
  }
  
  // calculate the length of the response header
  hdrLen = sizeof( zclReadRspCmd_t ) + ( numAttr * sizeof( zclReadRspStatus_t ) );
  
  readRspCmd = (zclReadRspCmd_t *)osal_mem_alloc( hdrLen + dataLen );
  if ( readRspCmd != NULL )
  {
    pBuf = pCmd->pData;
    dataPtr = (uint8 *)( (uint8 *)readRspCmd + hdrLen );
        
    readRspCmd->numAttr = numAttr;
    for ( i = 0; i < numAttr; i++ )
    {
      statusRec = &(readRspCmd->attrList[i]);
      
      statusRec->attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
      
      statusRec->status = *pBuf++;
      if ( statusRec->status == ZCL_STATUS_SUCCESS )
      {
        statusRec->dataType = *pBuf++;

        attrDataLen = zclGetAttrDataLength( statusRec->dataType, pBuf );
        osal_memcpy( dataPtr, pBuf, attrDataLen);
        statusRec->data = dataPtr;
        
        pBuf += attrDataLen; // move pass attribute data
        
        // advance attribute data pointer
        if ( PADDING_NEEDED( attrDataLen ) )
          attrDataLen++;
        dataPtr += attrDataLen;
      }
    }
  }

  return ( (void *)readRspCmd );
}
#endif // ZCL_READ

#ifdef ZCL_WRITE
/*********************************************************************
 * @fn      zclParseInWriteCmd
 *
 * @brief   Parse the "Profile" Write, Write Undivided and Write No
 *          Response Commands
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 * 
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
void *zclParseInWriteCmd( zclParseCmd_t *pCmd )
{
  zclWriteCmd_t *writeCmd;
  zclWriteRec_t *statusRec;
  uint8 *pBuf = pCmd->pData;
  uint8 attrDataLen;
  uint8 *dataPtr;
  uint8 numAttr = 0;
  uint8 hdrLen;
  uint8 dataLen = 0;
  uint8 dataType;
  uint8 i;

  // find out the number of attributes and the length of attribute data
  while ( pBuf < ( pCmd->pData + pCmd->dataLen ) )
  {
    numAttr++;

    pBuf += 2; // move pass attribute id

    dataType = *pBuf++;

    attrDataLen = zclGetAttrDataLength( dataType, pBuf );
    pBuf += attrDataLen; // move pass attribute data
      
    // add padding if needed
    if ( PADDING_NEEDED( attrDataLen ) )
      attrDataLen++;
    dataLen += attrDataLen;
  }
  
  // calculate the length of the response header
  hdrLen = sizeof( zclWriteCmd_t ) + ( numAttr * sizeof( zclWriteRec_t ) );
  
  writeCmd = (zclWriteCmd_t *)osal_mem_alloc( hdrLen + dataLen );
  if ( writeCmd != NULL )
  {
    pBuf = pCmd->pData;
    dataPtr = (uint8 *)( (uint8 *)writeCmd + hdrLen );

    writeCmd->numAttr = numAttr;
    for ( i = 0; i < numAttr; i++ )
    {
      statusRec = &(writeCmd->attrList[i]);
      
      statusRec->attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
      statusRec->dataType = *pBuf++;

      attrDataLen = zclGetAttrDataLength( statusRec->dataType, pBuf );
      osal_memcpy( dataPtr, pBuf, attrDataLen);
      statusRec->attrData = dataPtr;
      
      pBuf += attrDataLen; // move pass attribute data
        
      // advance attribute data pointer
      if ( PADDING_NEEDED( attrDataLen ) )
        attrDataLen++;
      dataPtr += attrDataLen;
    }
  }
  
  return ( (void *)writeCmd );
}

/*********************************************************************
 * @fn      zclParseInWriteRspCmd
 *
 * @brief   Parse the "Profile" Write Response Commands
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
static void *zclParseInWriteRspCmd( zclParseCmd_t *pCmd )
{
  zclWriteRspCmd_t *writeRspCmd;
  uint8 *pBuf = pCmd->pData;
  uint8 i = 0;

  writeRspCmd = (zclWriteRspCmd_t *)osal_mem_alloc( sizeof ( zclWriteRspCmd_t ) + pCmd->dataLen );
  if ( writeRspCmd != NULL )
  {
    if ( pCmd->dataLen == 1 )
    {
      // special case when all writes were successfull
      writeRspCmd->attrList[i++].status = *pBuf;
    }
    else
    {
      while ( pBuf < ( pCmd->pData + pCmd->dataLen ) )
      {
        writeRspCmd->attrList[i].status = *pBuf++;
        writeRspCmd->attrList[i++].attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
        pBuf += 2;
      }
    }
    
    writeRspCmd->numAttr = i; 
  }

  return ( (void *)writeRspCmd );
}
#endif // ZCL_WRITE

#ifdef ZCL_REPORT
/*********************************************************************
 * @fn      zclParseInConfigReportCmd
 *
 * @brief   Parse the "Profile" Configure Reporting Command
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
void *zclParseInConfigReportCmd( zclParseCmd_t *pCmd )
{
  zclCfgReportCmd_t *cfgReportCmd;
  zclCfgReportRec_t *reportRec;
  uint8 *pBuf = pCmd->pData;
  uint8 *dataPtr;
  uint8 numAttr = 0;
  uint8 direction;
  uint8 dataType;
  uint8 hdrLen;
  uint8 dataLen = 0;
  uint8 reportChangeLen; // length of Reportable Change field
  uint8 i;
  
  // Calculate the length of the Request command
  while ( pBuf < ( pCmd->pData + pCmd->dataLen ) )
  {
    numAttr++;
    
    direction = *pBuf++;
    pBuf += 2; // move pass the attribute ID
    
    // Is there a Reportable Change field?
    if ( direction == ZCL_SEND_ATTR_REPORTS )
    {
      dataType = *pBuf++;
      pBuf += 4; // move pass the Min and Max Reporting Intervals

      // For attributes of 'discrete' data types this field is omitted
      if ( zclAnalogDataType( dataType ) )
      {
        reportChangeLen = zclGetDataTypeLength( dataType );
        pBuf += reportChangeLen;
        
        // add padding if needed
        if ( PADDING_NEEDED( reportChangeLen ) )
          reportChangeLen++;
        dataLen += reportChangeLen;
      }
    }
    else
    {
      pBuf += 2; // move pass the Timeout Period
    }
  } // while loop

  hdrLen = sizeof( zclCfgReportCmd_t ) + ( numAttr * sizeof( zclCfgReportRec_t ) );
  
  cfgReportCmd = (zclCfgReportCmd_t *)osal_mem_alloc( hdrLen + dataLen );
  if ( cfgReportCmd != NULL )
  { 
    pBuf = pCmd->pData;
    dataPtr = (uint8 *)( (uint8 *)cfgReportCmd + hdrLen );
    
    cfgReportCmd->numAttr = numAttr;
    for ( i = 0; i < numAttr; i++ )
    {
      reportRec = &(cfgReportCmd->attrList[i]);
      
      osal_memset( reportRec, 0, sizeof( zclCfgReportRec_t ) );
        
      reportRec->direction = *pBuf++;
      reportRec->attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
      if ( reportRec->direction == ZCL_SEND_ATTR_REPORTS )
      {
        // Attribute to be reported
        reportRec->dataType = *pBuf++;
        reportRec->minReportInt = BUILD_UINT16( pBuf[0], pBuf[1] );
        pBuf += 2;
        reportRec->maxReportInt = BUILD_UINT16( pBuf[0], pBuf[1] );
        pBuf += 2;

        // For attributes of 'discrete' data types this field is omitted
        if ( zclAnalogDataType( reportRec->dataType ) )
        {
          zcl_BuildAnalogData( reportRec->dataType, dataPtr, pBuf);
          reportRec->reportableChange = dataPtr;
          
          reportChangeLen = zclGetDataTypeLength( reportRec->dataType );
          pBuf += reportChangeLen;
          
          // advance attribute data pointer
          if ( PADDING_NEEDED( reportChangeLen ) )
            reportChangeLen++;
          dataPtr += reportChangeLen;
        }
      }
      else
      {
        // Attribute reports to be received
        reportRec->timeoutPeriod = BUILD_UINT16( pBuf[0], pBuf[1] );
        pBuf += 2;
      }
    } // while loop
  }
  
  return ( (void *)cfgReportCmd );
}

/*********************************************************************
 * @fn      zclParseInConfigReportRspCmd
 *
 * @brief   Parse the "Profile" Configure Reporting Response Command
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
static void *zclParseInConfigReportRspCmd( zclParseCmd_t *pCmd )
{
  zclCfgReportRspCmd_t *cfgReportRspCmd;
  uint8 *pBuf = pCmd->pData;
  uint8 numAttr;
  uint8 i; 
  
  numAttr = pCmd->dataLen / ( 1 + 1 + 2 ); // Status + Direction + Attribute ID
  
  cfgReportRspCmd = (zclCfgReportRspCmd_t *)osal_mem_alloc( sizeof( zclCfgReportRspCmd_t ) 
                                            + ( numAttr * sizeof( zclCfgReportStatus_t ) ) );
  if ( cfgReportRspCmd != NULL )
  {
    cfgReportRspCmd->numAttr = numAttr;
    for ( i = 0; i < cfgReportRspCmd->numAttr; i++ )
    {
      cfgReportRspCmd->attrList[i].status = *pBuf++;
      cfgReportRspCmd->attrList[i].direction = *pBuf++;
      cfgReportRspCmd->attrList[i].attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
    }
  }

  return ( (void *)cfgReportRspCmd );  
}

/*********************************************************************
 * @fn      zclParseInReadReportCfgCmd
 *
 * @brief   Parse the "Profile" Read Reporting Configuration Command
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
void *zclParseInReadReportCfgCmd( zclParseCmd_t *pCmd )
{
  zclReadReportCfgCmd_t *readReportCfgCmd;
  uint8 *pBuf = pCmd->pData;
  uint8 numAttr;
  uint8 i;
  
  numAttr = pCmd->dataLen / ( 1 + 2 ); // Direction + Attribute ID
  
  readReportCfgCmd = (zclReadReportCfgCmd_t *)osal_mem_alloc( sizeof( zclReadReportCfgCmd_t ) 
                                                  + ( numAttr * sizeof( zclReadReportCfgRec_t ) ) );
  if ( readReportCfgCmd != NULL )
  {
    readReportCfgCmd->numAttr = numAttr;
    for ( i = 0; i < readReportCfgCmd->numAttr; i++)
    {
      readReportCfgCmd->attrList[i].direction = *pBuf++;;
      readReportCfgCmd->attrList[i].attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
    }
  }
  
  return ( (void *)readReportCfgCmd );
}

/*********************************************************************
 * @fn      zclParseInReadReportCfgRspCmd
 *
 * @brief   Parse the "Profile" Read Reporting Configuration Response Command
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
static void *zclParseInReadReportCfgRspCmd( zclParseCmd_t *pCmd )
{
  zclReadReportCfgRspCmd_t *readReportCfgRspCmd;
  zclReportCfgRspRec_t *reportRspRec;
  uint8 reportChangeLen;
  uint8 *pBuf = pCmd->pData;
  uint8 *dataPtr;
  uint8 numAttr = 0;
  uint8 hdrLen;
  uint8 dataLen = 0;
  uint8 status;
  uint8 direction;
  uint8 dataType;
  uint8 i;
  
  // Calculate the length of the response command
  while ( pBuf < ( pCmd->pData + pCmd->dataLen ) )
  { 
    numAttr++;
    
    status = *pBuf++;
    direction = *pBuf++; 
    pBuf += 2; // move pass the attribute ID
    
    if ( status == ZCL_STATUS_SUCCESS )
    {
      if ( direction == ZCL_SEND_ATTR_REPORTS )
      {
        dataType = *pBuf++;
        pBuf += 4; // move pass the Min and Max Reporting Intervals
 
        // For attributes of 'discrete' data types this field is omitted
        if ( zclAnalogDataType( dataType ) )
        {
          reportChangeLen = zclGetDataTypeLength( dataType );
          pBuf += reportChangeLen;
          
          // add padding if needed
          if ( PADDING_NEEDED( reportChangeLen ) )
            reportChangeLen++;
          dataLen += reportChangeLen;
        }
      }
      else
      {
        pBuf += 2; // move pass the Timeout field
      }
    }
  } // while loop
  
  hdrLen = sizeof( zclReadReportCfgRspCmd_t ) + ( numAttr * sizeof( zclReportCfgRspRec_t ) );
  
  readReportCfgRspCmd = (zclReadReportCfgRspCmd_t *)osal_mem_alloc( hdrLen + dataLen );
  if ( readReportCfgRspCmd != NULL )
  {
    pBuf = pCmd->pData;
    dataPtr = (uint8 *)( (uint8 *)readReportCfgRspCmd + hdrLen );
    
    readReportCfgRspCmd->numAttr = numAttr;
    for ( i = 0; i < numAttr; i++ )
    {
      reportRspRec = &(readReportCfgRspCmd->attrList[i]);
      
      reportRspRec->status = *pBuf++;
      reportRspRec->direction = *pBuf++;
      reportRspRec->attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
 
      if ( reportRspRec->status == ZCL_STATUS_SUCCESS )
      {
        if ( reportRspRec->direction == ZCL_SEND_ATTR_REPORTS )
        {
          reportRspRec->dataType = *pBuf++;
          reportRspRec->minReportInt = BUILD_UINT16( pBuf[0], pBuf[1] );
          pBuf += 2;
          reportRspRec->maxReportInt = BUILD_UINT16( pBuf[0], pBuf[1] );
          pBuf += 2;

          if ( zclAnalogDataType( reportRspRec->dataType ) )
          {
            zcl_BuildAnalogData( reportRspRec->dataType, dataPtr, pBuf);
            reportRspRec->reportableChange = dataPtr;
            
            reportChangeLen = zclGetDataTypeLength( reportRspRec->dataType ); 
            pBuf += reportChangeLen;
            
            // advance attribute data pointer
            if ( PADDING_NEEDED( reportChangeLen ) )
              reportChangeLen++;
            dataPtr += reportChangeLen;
          }
        }
        else
        {
          reportRspRec->timeoutPeriod = BUILD_UINT16( pBuf[0], pBuf[1] );
          pBuf += 2;
        }
      }
    } 
  }
  
  return ( (void *)readReportCfgRspCmd );
}

/*********************************************************************
 * @fn      zclParseInReportCmd
 *
 * @brief   Parse the "Profile" Report Command
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
void *zclParseInReportCmd( zclParseCmd_t *pCmd )
{
  zclReportCmd_t *reportCmd;
  zclReport_t *reportRec;
  uint8 *pBuf = pCmd->pData;
  uint8 attrDataLen;
  uint8 *dataPtr;
  uint8 numAttr = 0;
  uint8 hdrLen;
  uint8 dataLen = 0;
  uint8 dataType;
  uint8 i;

  // find out the number of attributes and the length of attribute data
  while ( pBuf < ( pCmd->pData + pCmd->dataLen ) )
  {
    numAttr++;

    pBuf += 2; // move pass attribute id

    dataType = *pBuf++;

    attrDataLen = zclGetAttrDataLength( dataType, pBuf );
    pBuf += attrDataLen; // move pass attribute data
      
    // add padding if needed
    if ( PADDING_NEEDED( attrDataLen ) )
      attrDataLen++;
    dataLen += attrDataLen;
  }
  
  hdrLen = sizeof( zclReportCmd_t ) + ( numAttr * sizeof( zclReport_t ) );
  
  reportCmd = (zclReportCmd_t *)osal_mem_alloc( hdrLen + dataLen );
  if (reportCmd != NULL )
  {
    pBuf = pCmd->pData;
    dataPtr = (uint8 *)( (uint8 *)reportCmd + hdrLen );
    
    reportCmd->numAttr = numAttr;
    for ( i = 0; i < numAttr; i++ )
    {
      reportRec = &(reportCmd->attrList[i]);
      
      reportRec->attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
      reportRec->dataType = *pBuf++;

      attrDataLen = zclGetAttrDataLength( reportRec->dataType, pBuf );  
      osal_memcpy( dataPtr, pBuf, attrDataLen );
      reportRec->attrData = dataPtr;
      
      pBuf += attrDataLen; // move pass attribute data
      
      // advance attribute data pointer
      if ( PADDING_NEEDED( attrDataLen ) )
        attrDataLen++;
      dataPtr += attrDataLen;
    }
  }
  
  return ( (void *)reportCmd );
}
#endif // ZCL_REPORT

/*********************************************************************
 * @fn      zclParseInDefaultRspCmd
 *
 * @brief   Parse the "Profile" Default Response Command
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
static void *zclParseInDefaultRspCmd( zclParseCmd_t *pCmd )
{
  zclDefaultRspCmd_t *defaultRspCmd;
  uint8 *pBuf = pCmd->pData;

  defaultRspCmd = (zclDefaultRspCmd_t *)osal_mem_alloc( sizeof ( zclDefaultRspCmd_t ) );
  if ( defaultRspCmd != NULL )
  {
    defaultRspCmd->commandID = *pBuf++;
    defaultRspCmd->statusCode = *pBuf;
  }

  return ( (void *)defaultRspCmd );
}

#ifdef ZCL_DISCOVER
/*********************************************************************
 * @fn      zclParseInDiscCmd
 *
 * @brief   Parse the "Profile" Discovery Commands
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
void *zclParseInDiscCmd( zclParseCmd_t *pCmd )
{
  zclDiscoverCmd_t *discoverCmd;
  uint8 *pBuf = pCmd->pData;

  discoverCmd = (zclDiscoverCmd_t *)osal_mem_alloc( sizeof ( zclDiscoverCmd_t ) );
  if ( discoverCmd != NULL )
  {
    discoverCmd->startAttr = BUILD_UINT16( pBuf[0], pBuf[1] );
    pBuf += 2;
    discoverCmd->maxAttrIDs = *pBuf;
  }

  return ( (void *)discoverCmd );
}

/*********************************************************************
 * @fn      zclParseInDiscRspCmd
 *
 * @brief   Parse the "Profile" Discovery Response Commands
 *
 *      NOTE: THIS FUNCTION ALLOCATES THE RETURN BUFFER, SO THE CALLING
 *            FUNCTION IS RESPONSIBLE TO FREE THE MEMORY.
 *
 * @param   pCmd - pointer to incoming data to parse
 *
 * @return  pointer to the parsed command structure
 */
#define ZCLDISCRSPCMD_DATALEN(a)  ((a)-1) // data len - Discovery Complete
static void *zclParseInDiscRspCmd( zclParseCmd_t *pCmd )
{
  zclDiscoverRspCmd_t *discoverRspCmd;
  uint8 *pBuf = pCmd->pData;
  uint8 numAttr = ZCLDISCRSPCMD_DATALEN(pCmd->dataLen) / ( 2 + 1 ); // Attr ID + Data Type
  uint8 i;

  discoverRspCmd = (zclDiscoverRspCmd_t *)osal_mem_alloc( sizeof ( zclDiscoverRspCmd_t ) 
                                                 + ( numAttr * sizeof(zclDiscoverInfo_t) ) );
  if ( discoverRspCmd != NULL )
  {
    discoverRspCmd->discComplete = *pBuf++;
    discoverRspCmd->numAttr = numAttr;
    
    for ( i = 0; i < numAttr; i++ )
    {
      discoverRspCmd->attrList[i].attrID = BUILD_UINT16( pBuf[0], pBuf[1] );
      pBuf += 2;
      discoverRspCmd->attrList[i].dataType = *pBuf++;;
    }
  }

  return ( (void *)discoverRspCmd );
}
#endif // ZCL_DISCOVER

#ifdef ZCL_READ
/*********************************************************************
 * @fn      zclProcessInReadCmd
 *
 * @brief   Process the "Profile" Read Command
 *
 * @param   pInMsg - incoming message to process
 *
 * @return  TRUE if command processed. FALSE, otherwise.
 */
static uint8 zclProcessInReadCmd( zclIncoming_t *pInMsg )
{
  zclReadCmd_t *readCmd;
  zclReadRspCmd_t *readRspCmd;
  zclReadRspStatus_t *statusRec;
  zclAttrRec_t attrRec;
  uint8 len;
  uint8 i;
  
  readCmd = (zclReadCmd_t *)pInMsg->attrCmd;
  
  // calculate the length of the response status record
  len = sizeof( zclReadRspCmd_t ) + (readCmd->numAttr * sizeof( zclReadRspStatus_t ));
  
  readRspCmd = osal_mem_alloc( len );
  if ( readRspCmd == NULL )
    return FALSE; // EMBEDDED RETURN

  readRspCmd->numAttr = readCmd->numAttr;
  for (i = 0; i < readCmd->numAttr; i++)
  {
    statusRec = &(readRspCmd->attrList[i]);
    
    statusRec->attrID = readCmd->attrID[i];
    
    if ( zclFindAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, readCmd->attrID[i], &attrRec ) )
    {
      statusRec->data = attrRec.attr.dataPtr;
      statusRec->status = ZCL_STATUS_SUCCESS;
      statusRec->dataType = attrRec.attr.dataType;
    }
    else
    {
      statusRec->status = ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
    }
  }
  
  // Build and send Read Response command
  zcl_SendReadRsp( pInMsg->msg->endPoint, &(pInMsg->msg->srcAddr), pInMsg->msg->clusterId,
                   readRspCmd, ZCL_FRAME_SERVER_CLIENT_DIR,
                   true, pInMsg->hdr.transSeqNum );
  osal_mem_free( readRspCmd );
    
  return TRUE;
}
#endif // ZCL_READ

#ifdef ZCL_WRITE
/*********************************************************************
 * @fn      processInWriteCmd
 *
 * @brief   Process the "Profile" Write and Write No Response Commands
 *
 * @param   pInMsg - incoming message to process
 *
 * @return  TRUE if command processed. FALSE, otherwise.
 */
static uint8 zclProcessInWriteCmd( zclIncoming_t *pInMsg )
{
  zclWriteCmd_t *writeCmd;
  zclWriteRec_t *statusRec;
  zclWriteRspCmd_t *writeRspCmd;
  zclAttrRec_t attrRec;
  uint8 sendRsp = FALSE;
  uint8 status;
  uint8 i, j = 0;

  writeCmd = (zclWriteCmd_t *)pInMsg->attrCmd;
  if ( pInMsg->hdr.commandID == ZCL_CMD_WRITE )
  {
    // We need to send a response back - allocate space for it
    writeRspCmd = (zclWriteRspCmd_t *)osal_mem_alloc( sizeof( zclWriteRspCmd_t ) 
            + sizeof( zclWriteRspStatus_t ) * writeCmd->numAttr );
    if ( writeRspCmd == NULL )
      return FALSE; // EMBEDDED RETURN
    sendRsp = TRUE;
  }
  
  for (i = 0; i < writeCmd->numAttr; i++)
  {
    statusRec = &(writeCmd->attrList[i]);
    
    if ( zclFindAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, statusRec->attrID, &attrRec ) )
    {
      if ( statusRec->dataType == attrRec.attr.dataType )
      {
        status = zclWriteAttrData( &attrRec, statusRec );

        // If successful, a write attribute status record shall NOT be generated
        if ( sendRsp && status != ZCL_STATUS_SUCCESS )
        {
          // Attribute is read only - move on to the next write attribute record
          writeRspCmd->attrList[j].status = status;
          writeRspCmd->attrList[j++].attrID = statusRec->attrID;
        }
      }
      else
      {
        // Attribute data type is incorrect - move on to the next write attribute record
        if ( sendRsp )
        {
          writeRspCmd->attrList[j].status = ZCL_STATUS_INVALID_DATA_TYPE;
          writeRspCmd->attrList[j++].attrID = statusRec->attrID;
        }
      }
    }
    else
    {
      // Attribute is not supported - move on to the next write attribute record
      if ( sendRsp )
      {
        writeRspCmd->attrList[j].status = ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
        writeRspCmd->attrList[j++].attrID = statusRec->attrID;
      }
    }
  } // for loop

  if ( sendRsp )
  {
    writeRspCmd->numAttr = j;
    if ( writeRspCmd->numAttr == 0 )
    {
      // Since all records were written successful, include a single status record
      // in the resonse command with the status field set to SUCCESS and the 
      // attribute ID field omitted.
      writeRspCmd->attrList[0].status = ZCL_STATUS_SUCCESS;
      writeRspCmd->numAttr = 1;
    }
    
    zcl_SendWriteRsp( pInMsg->msg->endPoint, &(pInMsg->msg->srcAddr),
                      pInMsg->msg->clusterId, writeRspCmd, ZCL_FRAME_SERVER_CLIENT_DIR, 
                      true, pInMsg->hdr.transSeqNum );
    osal_mem_free( writeRspCmd );
  }
  
  return TRUE; 
}

/*********************************************************************
 * @fn      zclRevertWriteUndividedCmd
 *
 * @brief   Revert the "Profile" Write Undevided Command
 *
 * @param   pInMsg - incoming message to process
 * @param   curWriteRec - old data
 * @param   numAttr - number of attributes to be reverted
 *
 * @return  none
 */
static void zclRevertWriteUndividedCmd( zclIncoming_t *pInMsg, 
                                    zclWriteRec_t *curWriteRec, uint16 numAttr )
{
  zclWriteRec_t *statusRec;
  zclAttrRec_t attrRec;
  uint8 dataLen;
  uint8 i;

  statusRec = curWriteRec;
  for (i = 0; i < numAttr; i++)
  {
    statusRec = &(curWriteRec[i]);
    
    if ( !zclFindAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, statusRec->attrID, &attrRec ) )
      break; // should never happen
 
    // Just copy the old data back - no need to validate the data
    dataLen = zclGetAttrDataLength( attrRec.attr.dataType, statusRec->attrData );
    osal_memcpy( attrRec.attr.dataPtr, statusRec->attrData, dataLen );
  } // for loop
}

/*********************************************************************
 * @fn      zclProcessInWriteUndividedCmd
 *
 * @brief   Process the "Profile" Write Undivided Command
 *
 * @param   pInMsg - incoming message to process
 *
 * @return  TRUE if command processed. FALSE, otherwise.
 */
static uint8 zclProcessInWriteUndividedCmd( zclIncoming_t *pInMsg )
{
  zclWriteCmd_t *writeCmd;
  zclWriteRec_t *statusRec;
  zclWriteRec_t *curWriteRec;
  zclWriteRec_t *curStatusRec;
  zclWriteRspCmd_t *writeRspCmd;
  zclAttrRec_t attrRec;
  uint8 *curDataPtr;
  uint8 hdrLen;
  uint8 dataLen;
  uint8 curLen = 0;
  uint8 status;
  uint8 i, j = 0;

  writeCmd = (zclWriteCmd_t *)pInMsg->attrCmd;
  
  // Allocate space for Write Response Command
  writeRspCmd = (zclWriteRspCmd_t *)osal_mem_alloc( sizeof( zclWriteRspCmd_t ) 
                   + sizeof( zclWriteRspStatus_t )* writeCmd->numAttr );
  if ( writeRspCmd == NULL )
    return FALSE; // EMBEDDED RETURN
  
  // If any attribute cannot be written, no attribute values are changed. Hence,
  // make sure all the attributes are supported and writable
  for (i = 0; i < writeCmd->numAttr; i++)
  {
    statusRec = &(writeCmd->attrList[i]);
    
    if ( !zclFindAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, statusRec->attrID, &attrRec ) )
    {
      // Attribute is not supported - stop here
      writeRspCmd->attrList[j].status = ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
      writeRspCmd->attrList[j++].attrID = statusRec->attrID;
      break;
    }
    
    if ( statusRec->dataType != attrRec.attr.dataType )
    {
      // Attribute data type is incorrect - stope here
      writeRspCmd->attrList[j].status = ZCL_STATUS_INVALID_DATA_TYPE;
      writeRspCmd->attrList[j++].attrID = statusRec->attrID;
      break;
    }
    
    if ( !zcl_AccessCtrlWrite( attrRec.attr.accessControl ) )
    {
      // Attribute is not writable - stop here
      writeRspCmd->attrList[j].status = ZCL_STATUS_READ_ONLY;
      writeRspCmd->attrList[j++].attrID = statusRec->attrID;
      break;
    }
    
    dataLen = zclGetAttrDataLength( statusRec->dataType, statusRec->attrData );
    
    // add padding if needed
    if ( PADDING_NEEDED( dataLen ) )
      dataLen++;
    curLen += dataLen;
  } // for loop
  
  writeRspCmd->numAttr = j;
  if ( writeRspCmd->numAttr == 0 ) // All attributes can be written
  {
    // calculate the length of the current data header
    hdrLen = j * sizeof( zclWriteRec_t );
  
    // Allocate space to keep a copy of the current data
    curWriteRec = (zclWriteRec_t *) osal_mem_alloc( hdrLen + curLen ); 
    if ( curWriteRec == NULL )
    {
      osal_mem_free(writeRspCmd );
      return FALSE; // EMBEDDED RETURN
    }

    curDataPtr = (uint8 *)((uint8 *)curWriteRec + hdrLen);
    
    // Write the new data over
    for (i = 0; i < writeCmd->numAttr; i++)
    {
      statusRec = &(writeCmd->attrList[i]);
      curStatusRec = &(curWriteRec[i]);
    
      if ( !zclFindAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, statusRec->attrID, &attrRec ) )
        break; // should never happen

      // Keep a copy of the current data before before writing the new data over
      curStatusRec->attrID = statusRec->attrID;
      zclReadAttrData( curDataPtr, &attrRec );
      curStatusRec->attrData = curDataPtr;
      
      status = zclWriteAttrData( &attrRec, statusRec );
         
      // If successful, a write attribute status record shall NOT be generated
      if ( status != ZCL_STATUS_SUCCESS )
      {
        writeRspCmd->attrList[j].status = status;
        writeRspCmd->attrList[j++].attrID = statusRec->attrID;
          
        // Since this write failed, we need to revert all the pervious writes
        zclRevertWriteUndividedCmd( pInMsg, curWriteRec, i);
        break;
      }
      
      dataLen = zclGetAttrDataLength( statusRec->dataType, statusRec->attrData );
      
      // add padding if needed
      if ( PADDING_NEEDED( dataLen ) )
        dataLen++;
      curDataPtr += dataLen;
    } // for loop
  
    writeRspCmd->numAttr = j;
    if ( writeRspCmd->numAttr  == 0 )
    {
      // Since all records were written successful, include a single status record
      // in the resonse command with the status field set to SUCCESS and the 
      // attribute ID field omitted.
      writeRspCmd->attrList[0].status = ZCL_STATUS_SUCCESS;
      writeRspCmd->numAttr = 1;
    }

    osal_mem_free( curWriteRec );
  }
  
  zcl_SendWriteRsp( pInMsg->msg->endPoint, &(pInMsg->msg->srcAddr),
                    pInMsg->msg->clusterId, writeRspCmd, ZCL_FRAME_SERVER_CLIENT_DIR, 
                    true, pInMsg->hdr.transSeqNum );  
  osal_mem_free( writeRspCmd );
 
  return TRUE; 
}
#endif // ZCL_WRITE

#ifdef ZCL_DISCOVER
/*********************************************************************
 * @fn      zclProcessInDiscCmd
 *
 * @brief   Process the "Profile" Discover Command
 *
 * @param   pInMsg - incoming message to process
 *
 * @return  TRUE if command processed. FALSE, otherwise.
 */
static uint8 zclProcessInDiscCmd( zclIncoming_t *pInMsg )
{
  zclDiscoverCmd_t *discoverCmd;
  zclDiscoverRspCmd_t *discoverRspCmd;
  uint8 discComplete = TRUE;
  zclAttrRec_t attrRec;
  uint16 attrID;
  uint8 i;
  
  discoverCmd = (zclDiscoverCmd_t *)pInMsg->attrCmd;
  
  // Find out the number of attributes supported within the specified range
  for ( i = 0, attrID = discoverCmd->startAttr; i < discoverCmd->maxAttrIDs; i++, attrID++ )
  {
    if ( !zclFindNextAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, &attrID, &attrRec ) )
      break;
  }
  
  // Allocate space for the response command
  discoverRspCmd = (zclDiscoverRspCmd_t *)osal_mem_alloc( sizeof (zclDiscoverRspCmd_t) 
                                                         + sizeof ( zclDiscoverInfo_t ) * i );
  if ( discoverRspCmd == NULL )
    return FALSE; // EMEDDED RETURN
  
  discoverRspCmd->numAttr = i;
  if ( discoverRspCmd->numAttr != 0 )
  {
    for ( i = 0, attrID = discoverCmd->startAttr; i < discoverRspCmd->numAttr; i++, attrID++ )
    {
      if ( !zclFindNextAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, &attrID, &attrRec ) )
        break; // Attribute not supported
      
      discoverRspCmd->attrList[i].attrID = attrRec.attr.attrId;
      discoverRspCmd->attrList[i].dataType = attrRec.attr.dataType;
    }
    
    // Are there more attributes to be discovered?
    if ( zclFindNextAttrRec( pInMsg->msg->endPoint, pInMsg->msg->clusterId, &attrID, &attrRec ) )
      discComplete = FALSE;
  }
  
  discoverRspCmd->discComplete = discComplete;
  zcl_SendDiscoverRspCmd( pInMsg->msg->endPoint, &pInMsg->msg->srcAddr, 
                          pInMsg->msg->clusterId, discoverRspCmd, ZCL_FRAME_SERVER_CLIENT_DIR, 
                          true, pInMsg->hdr.transSeqNum );
  osal_mem_free( discoverRspCmd );
  
  return TRUE;
}
#endif // ZCL_DISCOVER

/*********************************************************************
 * @fn      zclSendMsg
 *
 * @brief   Send an incoming message to the Application
 *
 * @param   pInMsg - incoming message to process
 *
 * @return  TRUE
 */
static uint8 zclSendMsg( zclIncoming_t *pInMsg )
{
  zclIncomingMsg_t *pCmd;
    
  if ( zcl_RegisteredMsgTaskID == TASK_NO_TASK )
    return ( TRUE );
  
  pCmd = (zclIncomingMsg_t *)osal_msg_allocate( sizeof ( zclIncomingMsg_t ) );
  if ( pCmd != NULL )
  {
    // fill in the message
    pCmd->hdr.event = ZCL_INCOMING_MSG;
    pCmd->zclHdr    = pInMsg->hdr;
    pCmd->clusterId = pInMsg->msg->clusterId;
    pCmd->srcAddr   = pInMsg->msg->srcAddr;
    pCmd->attrCmd   = pInMsg->attrCmd;
    
    // Application will free the attrCmd buffer
    pInMsg->attrCmd = NULL;
    
    /* send message through task message */
    osal_msg_send( zcl_RegisteredMsgTaskID, (uint8 *)pCmd );
  }
  
  return ( TRUE );
}

/*********************************************************************
*********************************************************************/
