// *******************************************************************
// * process-global-message.c
// *
// * This file contains function that processes global ZCL message.
// *
// * Copyright 2018 Silicon Laboratories, Inc.                              *80*
// *******************************************************************

#include "../include/af.h"
#include "common.h"
#include "../plugin/ias-zone-client/ias-zone-client.h"
#include "../plugin/key-establishment/key-establishment.h"
#include "../plugin/smart-energy-registration/smart-energy-registration.h"
#include "../plugin/trust-center-keepalive/trust-center-keepalive.h"
#include "../plugin/test-harness/test-harness.h"
#ifdef EMBER_AF_PLUGIN_WWAH_SERVER_SILABS
  #include "../plugin/wwah-server-silabs/wwah-server-silabs.h"
#endif
#include "../plugin/simple-metering-server/simple-metering-server.h"
#include "afv2-bookkeeping.h" // emAfRetrieveAttributeAndCraftResponse

#ifdef EMBER_AF_PLUGIN_COMMS_HUB_FUNCTION_SUB_GHZ
  #include "app/framework/plugin/comms-hub-function-sub-ghz/comms-hub-function-sub-ghz.h"
#endif

// flag to keep track of the fact that we just sent a read attr for time and
// we should set our time to the result of the read attr response.
bool emAfSyncingTime = false;

#ifdef EMBER_AF_GBCS_COMPATIBLE
// Some GBCS use cases (e.g. GCS15e, GCS21f) require that ReadAttributesResponse
// should be send back with Disable Default Response flag set. The only pattern
// is that the decision is based on the cluster and attribute IDs requested.
// To reduce the possibility of false positives, we disable default response
// only for responses containing at least the specified minimum of attributes.
#define MIN_MATCHING_ATTR_IDS_TO_DISABLE_DEFAULT_RESPONSE       3
#endif

#define DISC_ATTR_RSP_MAX_ATTRIBUTES                                         \
  (((EMBER_AF_MAXIMUM_APS_PAYLOAD_LENGTH                                     \
     - EMBER_AF_ZCL_MANUFACTURER_SPECIFIC_OVERHEAD /* max ZCL header size */ \
     - 1)       /* discovery is complete boolean */                          \
    / 3)        /* size of one discover attributes response entry */         \
   % UINT8_MAX) /* make count fit in an 8 bit integer */
#define DISC_ATTR_EXT_RSP_MAX_ATTRIBUTES                                      \
  (((EMBER_AF_MAXIMUM_APS_PAYLOAD_LENGTH                                      \
     - EMBER_AF_ZCL_MANUFACTURER_SPECIFIC_OVERHEAD /* max ZCL header size */  \
     - 1)       /* discovery is complete boolean */                           \
    / 4)        /* size of one discover attributes extended response entry */ \
   % UINT8_MAX) /* make count fit in an 8 bit integer */

#if defined(EMBER_AF_SUPPORT_COMMAND_DISCOVERY)
static void printDiscoverCommandsResponse(bool generated,
                                          uint16_t clusterId,
                                          bool discoveryComplete,
                                          uint8_t* buffer,
                                          uint16_t length)
{
  uint8_t i;
  emberAfServiceDiscoveryPrint("Discover Commands response (complete: %c), %p IDs: ",
                               (discoveryComplete
                                ? 'y'
                                : 'n'),
                               (generated
                                ? "Generated"
                                : "Received"));
  for (i = 0; i < length; i++) {
    emberAfServiceDiscoveryPrint("0x%X ", buffer[i]);
  }
  emberAfServiceDiscoveryPrintln("");
}
#endif

bool emAfProcessGlobalCommand(EmberAfClusterCommand *cmd)
{
  uint16_t attrId;
  uint8_t frameControl;
  // This is a little clumsy but easier to read and port
  // from earlier implementation.
  EmberAfClusterId clusterId = cmd->apsFrame->clusterId;
  uint8_t zclCmd = cmd->commandId;
  uint8_t *message = cmd->buffer;
  uint16_t msgLen = cmd->bufLen;
  uint16_t msgIndex = cmd->payloadStartIndex;
  uint8_t clientServerMask = (cmd->direction == ZCL_DIRECTION_CLIENT_TO_SERVER
                              ? CLUSTER_MASK_SERVER
                              : CLUSTER_MASK_CLIENT);

  // If we are disabled then we can only respond to read or write commands
  // or identify cluster (see device enabled attr of basic cluster)
  if (!emberAfIsDeviceEnabled(cmd->apsFrame->destinationEndpoint)
      && zclCmd != ZCL_READ_ATTRIBUTES_COMMAND_ID
      && zclCmd != ZCL_WRITE_ATTRIBUTES_COMMAND_ID
      && zclCmd != ZCL_WRITE_ATTRIBUTES_UNDIVIDED_COMMAND_ID
      && zclCmd != ZCL_WRITE_ATTRIBUTES_NO_RESPONSE_COMMAND_ID
      && clusterId != ZCL_IDENTIFY_CLUSTER_ID) {
    emberAfCorePrintln("disabled");
    emberAfDebugPrintln("%pd, dropping global cmd:%x", "disable", zclCmd);
    emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_FAILURE);
    return true;
  }

  // If a manufacturer-specific command arrives using our special internal "not
  // manufacturer specific" code, we need to reject it outright without letting
  // it pass through to the rest of the code.  The internal read and write APIs
  // would interpret it as a standard attribute or cluster and return incorrect
  // results.
  if (cmd->mfgSpecific && cmd->mfgCode == EMBER_AF_NULL_MANUFACTURER_CODE) {
    goto kickout;
  }

  // Clear out the response buffer by setting its length to zero
  appResponseLength = 0;

  // Make the ZCL header for the response
  // note: cmd byte is set below
  frameControl = (ZCL_GLOBAL_COMMAND
                  | (cmd->direction == ZCL_DIRECTION_CLIENT_TO_SERVER
                     ? ZCL_FRAME_CONTROL_SERVER_TO_CLIENT
                     | EMBER_AF_DEFAULT_RESPONSE_POLICY_RESPONSES
                     : ZCL_FRAME_CONTROL_CLIENT_TO_SERVER
                     | EMBER_AF_DEFAULT_RESPONSE_POLICY_RESPONSES));
  if (cmd->mfgSpecific) {
    frameControl |= ZCL_MANUFACTURER_SPECIFIC_MASK;
  }
  emberAfPutInt8uInResp(frameControl);
  if (cmd->mfgSpecific) {
    emberAfPutInt16uInResp(cmd->mfgCode);
  }
  emberAfPutInt8uInResp(cmd->seqNum);

  switch (zclCmd) {
    // The format of the read attributes cmd is:
    // ([attr ID:2]) * N
    // The format of the read attributes response is:
    // ([attr ID:2] [status:1] [data type:0/1] [data:0/N]) * N
    case ZCL_READ_ATTRIBUTES_COMMAND_ID:
    {
      emberAfAttributesPrintln("%p: clus %2x", "READ_ATTR", clusterId);
      // Set the cmd byte - this is byte 3 index 2, but since we have
      // already incremented past the 3 byte ZCL header (our index is at 3),
      // this gets written to "-1" since 3 - 1 = 2.
      emberAfPutInt8uInResp(ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID);

      // This message contains N 2-byte attr IDs after the 3 byte ZCL header,
      // for each one we need to look it up and make a response
      while (msgIndex + 2 <= msgLen) {
        // Get the attribute ID and store it in the response buffer
        // least significant byte is first OTA
        attrId = emberAfGetInt16u(message, msgIndex, msgLen);

#ifdef EMBER_AF_GBCS_COMPATIBLE
        // GBCS explicitly lists some commands that need to be sent with "disable
        // default response" flag set, including some ReadAttributes responses.
        // We make it conditional on GBCS so it does not affect standard SE apps.
        {
          static const struct {
            EmberAfClusterId clusterId;
            uint16_t attrId;
          } noDefaultResponseSet[] = {
            { ZCL_PRICE_CLUSTER_ID, ZCL_THRESHOLD_MULTIPLIER_ATTRIBUTE_ID },
            { ZCL_PRICE_CLUSTER_ID, ZCL_THRESHOLD_DIVISOR_ATTRIBUTE_ID },
            { ZCL_PRICE_CLUSTER_ID, ZCL_STANDING_CHARGE_ATTRIBUTE_ID },
            { ZCL_PRICE_CLUSTER_ID, ZCL_TARIFF_UNIT_OF_MEASURE_ATTRIBUTE_ID },
            { ZCL_SIMPLE_METERING_CLUSTER_ID, ZCL_UNIT_OF_MEASURE_ATTRIBUTE_ID },
            { ZCL_SIMPLE_METERING_CLUSTER_ID, ZCL_MULTIPLIER_ATTRIBUTE_ID },
            { ZCL_SIMPLE_METERING_CLUSTER_ID, ZCL_DIVISOR_ATTRIBUTE_ID },
          };
          uint8_t i;
          uint8_t foundMatchingAttrIdsCount = 0;

          for (i = 0; i < sizeof noDefaultResponseSet / sizeof noDefaultResponseSet[0]; ++i) {
            if (noDefaultResponseSet[i].clusterId == clusterId
                && noDefaultResponseSet[i].attrId == attrId) {
              if (++foundMatchingAttrIdsCount >= MIN_MATCHING_ATTR_IDS_TO_DISABLE_DEFAULT_RESPONSE) {
                emberAfSetDisableDefaultResponse(EMBER_AF_DISABLE_DEFAULT_RESPONSE_ONE_SHOT);
                break;
              }
            }
          }
        }

  #ifdef EMBER_AF_PLUGIN_COMMS_HUB_FUNCTION_SUB_GHZ
        // This plugin sets channel change notification flags and needs to know
        // when those flags have been read.
        if (clientServerMask == CLUSTER_MASK_SERVER) {
          emAfCommsHubFunctionSubGhzReadAttributeNotification(cmd->source,
                                                              clusterId,
                                                              attrId);
        }
  #endif
#endif

        // This function reads the attribute and creates the correct response
        // in the response buffer
        if (!emAfRetrieveAttributeAndCraftResponse(cmd->apsFrame->destinationEndpoint,
                                                   clusterId,
                                                   attrId,
                                                   clientServerMask,
                                                   cmd->mfgCode,
                                                   (EMBER_AF_RESPONSE_BUFFER_LEN
                                                    - appResponseLength))) {
          emberAfRetrieveAttributeAndCraftResponse(cmd->apsFrame->destinationEndpoint,
                                                   clusterId,
                                                   attrId,
                                                   clientServerMask,
                                                   cmd->mfgCode,
                                                   (EMBER_AF_RESPONSE_BUFFER_LEN
                                                    - appResponseLength));
        }

        // Go to next attrID
        msgIndex += 2;
      }
    }

      emberAfSendResponse();
      return true;

    // Write undivided means all attributes must be written in order to write
    // any of them. So first do a check. If the check fails, send back a fail
    // response. If it works, fall through to the normal write attr code.
    // write attr responses are the same for undivided and normal writes.
    case ZCL_WRITE_ATTRIBUTES_UNDIVIDED_COMMAND_ID:
    {
      uint8_t numFailures = 0;
      uint8_t dataType;
      uint8_t dataSize;
      EmberAfStatus status;

      emberAfPutInt8uInResp(ZCL_WRITE_ATTRIBUTES_RESPONSE_COMMAND_ID);

      // Go through the message until there are no more attrID/type/data
      while (msgLen >= msgIndex + 3) {
        attrId = emberAfGetInt16u(message, msgIndex, msgLen);
        dataType = emberAfGetInt8u(message, msgIndex + 2, msgLen);

        // For strings, the data size is the length of the string (specified by
        // the first byte of data) plus one for the length byte itself.  For
        // everything else, the size is just the size of the data type.
        dataSize = (emberAfIsThisDataTypeAStringType(dataType)
                    ? emberAfStringLength(message + msgIndex + 3) + 1
                    : emberAfGetDataSize(dataType));

        // Check to see if there are dataSize bytes left in the message if it is a string
        if (emberAfIsThisDataTypeAStringType(dataType)
            && ((msgLen - (msgIndex + dataSize + 3)) < 0)) {
          // This command is malformed
          status = EMBER_ZCL_STATUS_MALFORMED_COMMAND;
        } else {
          status = emberAfVerifyAttributeWrite(cmd->apsFrame->destinationEndpoint,
                                               clusterId,
                                               attrId,
                                               clientServerMask,
                                               cmd->mfgCode,
                                               &(message[msgIndex + 3]),
                                               dataType);
        }

        if (status != EMBER_ZCL_STATUS_SUCCESS) {
          numFailures++;
          // Write to the response buffer - status and then attrID
          emberAfPutInt8uInResp(status);
          emberAfPutInt16uInResp(attrId);

          emberAfAttributesPrintln("WRITE: clus %2x attr %2x ", clusterId, attrId);
          emberAfAttributesPrintln("FAIL %x", status);
          emberAfCoreFlush();
        }

        // Increment past the attribute id (two bytes), the type (one byte), and
        // the data (N bytes, including the length byte for strings).
        msgIndex += 3 + dataSize;
      }
      // If there are any failures, send the response and exit
      if (numFailures > 0) {
        emberAfSendResponse();
        return true;
      }
    }
      // Reset message back to start
      msgIndex = cmd->payloadStartIndex;
      appResponseLength = (cmd->mfgSpecific ? 4 : 2);
    // DO NOT BREAK from this case

    // the format of the write attributes cmd is:
    // ([attr ID:2] [data type:1] [data:N]) * N
    // the format of the write attributes response is:
    // ([status 1] [attr ID 2]) * n
    // ONLY errors are reported unless all are successful then a single success
    // is sent. write attr no response is handled by just executing the same
    // code but not setting the flag that sends the response at the end.
    case ZCL_WRITE_ATTRIBUTES_NO_RESPONSE_COMMAND_ID:
    case ZCL_WRITE_ATTRIBUTES_COMMAND_ID:
    {
      uint8_t numFailures = 0;
      uint8_t numSuccess = 0;
      uint8_t dataType;
      uint8_t dataSize;
#if (BIGENDIAN_CPU)
      uint8_t writeData[ATTRIBUTE_LARGEST];
#endif //(BIGENDIAN_CPU)
      EmberAfStatus status;

      // set the cmd byte - this is byte 3 index 2, but since we have
      // already incremented past the 3 byte ZCL header (our index is at 3),
      // this gets written to "-1" since 3 - 1 = 2.
      emberAfPutInt8uInResp(ZCL_WRITE_ATTRIBUTES_RESPONSE_COMMAND_ID);

      // go through the message until there are no more attrID/type/data
      while (msgLen > msgIndex + 3) {
        attrId = emberAfGetInt16u(message, msgIndex, msgLen);
        dataType = emberAfGetInt8u(message, msgIndex + 2, msgLen);

        // For strings, the data size is the length of the string (specified by
        // the first byte of data) plus one for the length byte itself.  For
        // everything else, the size is just the size of the data type.
        dataSize = (emberAfIsThisDataTypeAStringType(dataType)
                    ? emberAfStringLength(message + msgIndex + 3) + 1
                    : emberAfGetDataSize(dataType));

        // the data is sent little endian over-the-air, it needs to be
        // inserted into the table big endian for the EM250 and little
        // endian for the EZSP hosts. This means for the EM250 the data
        // needs to be reversed before sending to writeAttributes
#if (BIGENDIAN_CPU)
        // strings go over the air as length byte and then in human
        // readable format. These should not be flipped.
        if (emberAfIsThisDataTypeAStringType(dataType)) {
          MEMMOVE(writeData, message + msgIndex + 3, dataSize);
        } else {
          // the data is sent little endian over-the-air, it needs to be
          // inserted into the table big endian
          uint8_t i;
          for (i = 0; i < dataSize; i++) {
            writeData[i] = message[msgIndex + 3 + dataSize - i - 1];
          }
        }
#endif //(BIGENDIAN_CPU)

        status = emberAfWriteAttributeExternal(cmd->apsFrame->destinationEndpoint,
                                               clusterId,
                                               attrId,
                                               clientServerMask,
                                               cmd->mfgCode,
#if (BIGENDIAN_CPU)
                                               writeData,
#else //(BIGENDIAN_CPU)
                                               &(message[msgIndex + 3]),
#endif //(BIGENDIAN_CPU)
                                               dataType);
        emberAfAttributesPrint("WRITE: clus %2x attr %2x ",
                               clusterId,
                               attrId);
        if (status == EMBER_ZCL_STATUS_SUCCESS) {
          numSuccess++;
          emberAfAttributesPrintln("OK");
        } else {
          numFailures++;
          // write to the response buffer - status and then attrID
          emberAfPutInt8uInResp(status);
          emberAfPutInt16uInResp(attrId);
          emberAfAttributesPrintln("FAIL %x", status);
        }
        emberAfCoreFlush();

        // Increment past the attribute id (two bytes), the type (one byte), and
        // the data (N bytes, including the length byte for strings).
        msgIndex += 3 + dataSize;
      }

      // always send a response unless the cmd requested no response
      if (zclCmd == ZCL_WRITE_ATTRIBUTES_NO_RESPONSE_COMMAND_ID) {
        return true;
      }

      if (numFailures == 0) {
        // if no failures and no success this means the packet was too short
        // print an error message but still return true as we consumed the
        // message
        if (numSuccess == 0) {
          emberAfAttributesPrintln("WRITE: too short");
          emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_MALFORMED_COMMAND);
          return true;
        }
        // if no failures and at least one success, write a success status
        // that means everything worked
        else {
          emberAfPutInt8uInResp(EMBER_ZCL_STATUS_SUCCESS);
        }
      }
      emberAfSendResponse();
      return true;
    }

    // the format of discover is: [start attr ID:2] [max attr IDs:1]
    // the format of the response is: [done:1] ([attrID:2] [type:1]) * N
    case ZCL_DISCOVER_ATTRIBUTES_COMMAND_ID:
    case ZCL_DISCOVER_ATTRIBUTES_EXTENDED_COMMAND_ID:
    {
      EmberAfAttributeId startingAttributeId;
      uint8_t numberAttributes;
      uint8_t *complete;

      emberAfAttributesPrintln("%p%p: clus %2x", "DISC_ATTR",
                               (zclCmd == ZCL_DISCOVER_ATTRIBUTES_EXTENDED_COMMAND_ID ? "_EXT" : ""),
                               clusterId);

      // set the cmd byte - this is byte 3 index 2, but since we have
      // already incremented past the 3 byte ZCL header (our index is at 3),
      // this gets written to "-1" since 3 - 1 = 2.
      emberAfPutInt8uInResp(
        (zclCmd == ZCL_DISCOVER_ATTRIBUTES_COMMAND_ID
         ? ZCL_DISCOVER_ATTRIBUTES_RESPONSE_COMMAND_ID
         : ZCL_DISCOVER_ATTRIBUTES_EXTENDED_RESPONSE_COMMAND_ID));

      // get the attrId to start on and the max count
      startingAttributeId = emberAfGetInt16u(message, msgIndex, msgLen);
      numberAttributes = emberAfGetInt8u(message, msgIndex + 2, msgLen);

      // BUGZID: EMAPPFWKV2-828, EMAPPFWKV2-1401
      if (zclCmd == ZCL_DISCOVER_ATTRIBUTES_EXTENDED_RESPONSE_COMMAND_ID
          && numberAttributes > DISC_ATTR_RSP_MAX_ATTRIBUTES) {
        numberAttributes = DISC_ATTR_RSP_MAX_ATTRIBUTES;
      } else if (zclCmd == ZCL_DISCOVER_ATTRIBUTES_EXTENDED_COMMAND_ID
                 && numberAttributes > DISC_ATTR_EXT_RSP_MAX_ATTRIBUTES) {
        numberAttributes = DISC_ATTR_EXT_RSP_MAX_ATTRIBUTES;
      } else {
        // MISRA requires ..else if.. to have terminating else.
      }

      // The response has a one-byte field indicating whether discovery is
      // complete.  We can't populate that field until we've finished going
      // through all the attributes, so save a placeholder, write a temporary
      // value for now (so that the offset moves forward), and write the real
      // value when we're done.
      complete = &(appResponseData[appResponseLength]);
      emberAfPutInt8uInResp(false);
      *complete = emberAfReadSequentialAttributesAddToResponse(
        cmd->apsFrame->destinationEndpoint,
        clusterId,
        startingAttributeId,
        clientServerMask,
        cmd->mfgCode,
        numberAttributes,
        (zclCmd == ZCL_DISCOVER_ATTRIBUTES_EXTENDED_COMMAND_ID));
      emberAfSendResponse();
      return true;
    }

    case ZCL_CONFIGURE_REPORTING_COMMAND_ID:
      if (emberAfConfigureReportingCommandCallback(cmd)) {
        return true;
      }
      break;

    case ZCL_READ_REPORTING_CONFIGURATION_COMMAND_ID:
      if (emberAfReadReportingConfigurationCommandCallback(cmd)) {
        return true;
      }
      break;

    // ([attribute id:2] [status:1] [type:0/1] [value:0/V])+
    case ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID:
      // The "timesync" command in the CLI sends a Read Attributes command for the
      // Time attribute on another device and then sets a flag.  If that flag is
      // set and a Read Attributes Response command for the time comes in, we set
      // the time to the value in the message.
      if (clusterId == ZCL_TIME_CLUSTER_ID) {
		if (emAfSyncingTime
            && !cmd->mfgSpecific
            && msgLen - msgIndex == 8 // attr:2 status:1 type:1 data:4
            && (emberAfGetInt16u(message, msgIndex, msgLen)
                == ZCL_TIME_ATTRIBUTE_ID)
            && (emberAfGetInt8u(message, msgIndex + 2, msgLen)
                == EMBER_ZCL_STATUS_SUCCESS)
            && (emberAfGetInt8u(message, msgIndex + 3, msgLen)
                == ZCL_UTC_TIME_ATTRIBUTE_TYPE)) {
          emberAfSetTime(emberAfGetInt32u(message, msgIndex + 4, msgLen));
          emberAfDebugPrintln("time sync ok, time: %4x", emberAfGetCurrentTime());
			emAfSyncingTime = false;
        }
#ifdef EMBER_AF_PLUGIN_SMART_ENERGY_REGISTRATION_TIME_SOURCE_REQUIRED
        emAfPluginSmartEnergyRegistrationReadAttributesResponseCallback(message + msgIndex,
                                                                        msgLen - msgIndex);
#endif //EMBER_AF_PLUGIN_SMART_ENERGY_REGISTRATION_TIME_SOURCE_REQUIRED
#ifdef EMBER_AF_PLUGIN_WWAH_SERVER_SILABS
        emAfPluginSlWwahReadAttributesResponseCallback(clusterId, message, msgLen);
#endif
      }

#ifdef EMBER_AF_PLUGIN_TRUST_CENTER_KEEPALIVE
      if (clusterId == ZCL_KEEPALIVE_CLUSTER_ID
          && !cmd->mfgSpecific) {
        emAfPluginTrustCenterKeepaliveReadAttributesResponseCallback(message + msgIndex,
                                                                     msgLen - msgIndex);
      }
#endif //EMBER_AF_PLUGIN_TRUST_CENTER_KEEPALIVE

#if defined(EMBER_AF_PLUGIN_KEY_ESTABLISHMENT)
      if (clusterId == ZCL_KEY_ESTABLISHMENT_CLUSTER_ID
          && !cmd->mfgSpecific
          && msgLen - msgIndex == 6 //attr:2 status:1 type:1 data:2
          && (emberAfGetInt16u(message, msgIndex, msgLen)
              == ZCL_KEY_ESTABLISHMENT_SUITE_CLIENT_ATTRIBUTE_ID)
          && (emberAfGetInt8u(message, msgIndex + 2, msgLen)
              == EMBER_ZCL_STATUS_SUCCESS)
          && ((emberAfGetInt8u(message, msgIndex + 3, msgLen) == ZCL_ENUM16_ATTRIBUTE_TYPE)
              || (emberAfGetInt8u(message, msgIndex + 3, msgLen) == ZCL_BITMAP16_ATTRIBUTE_TYPE))) {
        uint16_t suite = emberAfGetInt16u(message, msgIndex + 4, msgLen);
        emberAfPluginKeyEstablishmentReadAttributesCallback(suite);
      }
#endif

#if defined(EMBER_AF_PLUGIN_TEST_HARNESS)
      emberAfPluginTestHarnessReadAttributesResponseCallback(clusterId,
                                                             message + msgIndex,
                                                             msgLen - msgIndex);
#endif

#if defined(EMBER_AF_PLUGIN_IAS_ZONE_CLIENT)
      emberAfPluginIasZoneClientReadAttributesResponseCallback(clusterId,
                                                               message + msgIndex,
                                                               msgLen - msgIndex);
#endif

#if defined(EMBER_AF_PLUGIN_SIMPLE_METERING_SERVER)
      emberAfPluginSimpleMeteringClusterReadAttributesResponseCallback(clusterId,
                                                                       message + msgIndex,
                                                                       msgLen - msgIndex);
#endif

      if (!emAfReadAttributesResponse(clusterId,
                                      message + msgIndex,
                                      msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;

    // ([status:1] [attribute id:2])+
    case ZCL_WRITE_ATTRIBUTES_RESPONSE_COMMAND_ID:

  #if defined(EMBER_AF_PLUGIN_TEST_HARNESS)
      emberAfPluginTestHarnessWriteAttributesResponseCallback(clusterId,
                                                              message + msgIndex,
                                                              msgLen - msgIndex);
  #endif

  #if defined(EMBER_AF_PLUGIN_IAS_ZONE_CLIENT)
      emberAfPluginIasZoneClientWriteAttributesResponseCallback(clusterId,
                                                                message + msgIndex,
                                                                msgLen - msgIndex);
  #endif

      if (!emberAfWriteAttributesResponseCallback(clusterId,
                                                  message + msgIndex,
                                                  msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;

    // ([status:1] [direction:1] [attribute id:2])+
    case ZCL_CONFIGURE_REPORTING_RESPONSE_COMMAND_ID:
      if (!emberAfConfigureReportingResponseCallback(clusterId,
                                                     message + msgIndex,
                                                     msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;

    // ([status:1] [direction:1] [attribute id:2] [type:0/1] ...
    // ... [min interval:0/2] [max interval:0/2] [reportable change:0/V] ...
    // ... [timeout:0/2])+
    case ZCL_READ_REPORTING_CONFIGURATION_RESPONSE_COMMAND_ID:
      if (!emberAfReadReportingConfigurationResponseCallback(clusterId,
                                                             message + msgIndex,
                                                             msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;

    // ([attribute id:2] [type:1] [data:V])+
    case ZCL_REPORT_ATTRIBUTES_COMMAND_ID:
      if (!emAfReportAttributes(clusterId,
                                message + msgIndex,
                                msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;

    // [command id:1] [status:1]
    case ZCL_DEFAULT_RESPONSE_COMMAND_ID:
    {
      EmberAfStatus status;
      uint8_t commandId;
      commandId = emberAfGetInt8u(message, msgIndex, msgLen);
      msgIndex++;
      status = (EmberAfStatus)emberAfGetInt8u(message, msgIndex, msgLen);

      emberAfClusterDefaultResponseCallback(cmd->apsFrame->destinationEndpoint,
                                            clusterId,
                                            commandId,
                                            status,
                                            clientServerMask);
      emberAfDefaultResponseCallback(clusterId, commandId, status);
      return true;
    }

    // [discovery complete:1] ([attribute id:2] [type:1])*
    case ZCL_DISCOVER_ATTRIBUTES_RESPONSE_COMMAND_ID:
    case ZCL_DISCOVER_ATTRIBUTES_EXTENDED_RESPONSE_COMMAND_ID:
    {
      bool discoveryComplete = emberAfGetInt8u(message, msgIndex, msgLen);
      msgIndex++;
      if (!emberAfDiscoverAttributesResponseCallback(clusterId,
                                                     discoveryComplete,
                                                     message + msgIndex,
                                                     msgLen - msgIndex,
                                                     (zclCmd
                                                      == ZCL_DISCOVER_ATTRIBUTES_EXTENDED_RESPONSE_COMMAND_ID))) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;
    }

#ifdef EMBER_AF_SUPPORT_COMMAND_DISCOVERY
    // Command discovery takes a bit of flash because we need to add structs
    // for commands into the generated hader. Hence it's all configurable.
    case ZCL_DISCOVER_COMMANDS_RECEIVED_COMMAND_ID:
    case ZCL_DISCOVER_COMMANDS_GENERATED_COMMAND_ID:
    {
      uint8_t startCommandIdentifier = emberAfGetInt8u(message, msgIndex, msgLen);
      uint8_t maximumCommandIdentifiers = emberAfGetInt8u(message, msgIndex + 1, msgLen);
      uint16_t savedIndex;
      bool flag;

      // Ok. This is the command that matters.
      if ( zclCmd == ZCL_DISCOVER_COMMANDS_RECEIVED_COMMAND_ID ) {
        emberAfPutInt8uInResp(ZCL_DISCOVER_COMMANDS_RECEIVED_RESPONSE_COMMAND_ID);
        flag = false;
      } else {
        emberAfPutInt8uInResp(ZCL_DISCOVER_COMMANDS_GENERATED_RESPONSE_COMMAND_ID);
        flag = true;
      }
      savedIndex = appResponseLength;
      flag = emberAfExtractCommandIds(flag,
                                      cmd,
                                      clusterId,
                                      appResponseData + appResponseLength + 1,
                                      EMBER_AF_RESPONSE_BUFFER_LEN - appResponseLength - 1,
                                      &appResponseLength,
                                      startCommandIdentifier,
                                      maximumCommandIdentifiers);
      appResponseData[savedIndex] = (flag ? 1 : 0);
      appResponseLength++;
      emberAfSendResponse();
      return true;
    }
    case ZCL_DISCOVER_COMMANDS_RECEIVED_RESPONSE_COMMAND_ID:
    {
      bool discoveryComplete = emberAfGetInt8u(message, msgIndex, msgLen);
      msgIndex++;
      printDiscoverCommandsResponse(false,  // is ZCL command generated?
                                    clusterId,
                                    discoveryComplete,
                                    message + msgIndex,
                                    msgLen - msgIndex);
      if (!emberAfDiscoverCommandsReceivedResponseCallback(clusterId,
                                                           cmd->mfgCode,
                                                           discoveryComplete,
                                                           message + msgIndex,
                                                           msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;
    }
    case ZCL_DISCOVER_COMMANDS_GENERATED_RESPONSE_COMMAND_ID:
    {
      bool discoveryComplete = emberAfGetInt8u(message, msgIndex, msgLen);
      msgIndex++;
      printDiscoverCommandsResponse(true,  // is ZCL command generated?
                                    clusterId,
                                    discoveryComplete,
                                    message + msgIndex,
                                    msgLen - msgIndex);
      if (!emberAfDiscoverCommandsGeneratedResponseCallback(clusterId,
                                                            cmd->mfgCode,
                                                            discoveryComplete,
                                                            message + msgIndex,
                                                            msgLen - msgIndex)) {
        emberAfSendDefaultResponse(cmd, EMBER_ZCL_STATUS_SUCCESS);
      }
      return true;
    }

#endif
    default:
      // MISRA requires default case.
      break;
  }

  kickout:
  emberAfSendDefaultResponse(cmd,
                             (cmd->mfgSpecific
                              ? EMBER_ZCL_STATUS_UNSUP_MANUF_GENERAL_COMMAND
                              : EMBER_ZCL_STATUS_UNSUP_GENERAL_COMMAND));
  return true;
}
