text: /**
   *
   *    Copyright (c) 2020 Project CHIP Authors
   *
   *    Licensed under the Apache License, Version 2.0 (the "License");
   *    you may not use this file except in compliance with the License.
   *    You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *    Unless required by applicable law or agreed to in writing, software
   *    distributed under the License is distributed on an "AS IS" BASIS,
   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *    See the License for the specific language governing permissions and
   *    limitations under the License.
   */
  
  /**
   *
   *    Copyright (c) 2020 Silicon Labs
   *
   *    Licensed under the Apache License, Version 2.0 (the "License");
   *    you may not use this file except in compliance with the License.
   *    You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *    Unless required by applicable law or agreed to in writing, software
   *    distributed under the License is distributed on an "AS IS" BASIS,
   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *    See the License for the specific language governing permissions and
   *    limitations under the License.
   */
  /***************************************************************************/
  /**
   * @file
   * @brief This file contains the code to manipulate
   *the Smart Energy attribute table.  This handles
   *external calls to read/write the table, as well as
   *internal ones.
   *******************************************************************************
   ******************************************************************************/
  
  // this file contains all the common includes for clusters in the zcl-util
  #include "common.h"
  
  #include "attribute-storage.h"
  
  // for pulling in defines dealing with EITHER server or client
  #include "af-main.h"
  
  #include "gen/callback.h"
  
  #ifdef EMBER_AF_PLUGIN_REPORTING
  #include <app/reporting/reporting.h>
  #endif // EMBER_AF_PLUGIN_REPORTING
  
  using namespace chip;
  
  //------------------------------------------------------------------------------
  
  //------------------------------------------------------------------------------
  // External Declarations
  
  //------------------------------------------------------------------------------
  // Forward Declarations
  
  //------------------------------------------------------------------------------
  // Globals
  
  EmberAfStatus emberAfWriteAttributeExternal(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t mask,
                                              uint16_t manufacturerCode, uint8_t * dataPtr, EmberAfAttributeType dataType)
  {
      EmberAfAttributeWritePermission extWritePermission =
          emberAfAllowNetworkWriteAttributeCallback(endpoint, cluster, attributeID, mask, manufacturerCode, dataPtr, dataType);
      switch (extWritePermission)
      {
      case EMBER_ZCL_ATTRIBUTE_WRITE_PERMISSION_DENY_WRITE:
          return EMBER_ZCL_STATUS_FAILURE;
      case EMBER_ZCL_ATTRIBUTE_WRITE_PERMISSION_ALLOW_WRITE_NORMAL:
      case EMBER_ZCL_ATTRIBUTE_WRITE_PERMISSION_ALLOW_WRITE_OF_READ_ONLY:
          return emAfWriteAttribute(endpoint, cluster, attributeID, mask, manufacturerCode, dataPtr, dataType,
                                    (extWritePermission == EMBER_ZCL_ATTRIBUTE_WRITE_PERMISSION_ALLOW_WRITE_OF_READ_ONLY), false);
      default:
          return (EmberAfStatus) extWritePermission;
      }
  }
  
  //@deprecated use emberAfWriteServerAttribute or emberAfWriteClientAttribute
  EmberAfStatus emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t mask,
                                      uint8_t * dataPtr, EmberAfAttributeType dataType)
  {
      return emAfWriteAttribute(endpoint, cluster, attributeID, mask, EMBER_AF_NULL_MANUFACTURER_CODE, dataPtr, dataType,
                                true,   // override read-only?
                                false); // just test?
  }
  
  EmberAfStatus emberAfWriteClientAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
                                            EmberAfAttributeType dataType)
  {
      return emAfWriteAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_CLIENT, EMBER_AF_NULL_MANUFACTURER_CODE, dataPtr,
                                dataType,
                                true,   // override read-only?
                                false); // just test?
  }
  
  EmberAfStatus emberAfWriteServerAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
                                            EmberAfAttributeType dataType)
  {
      return emAfWriteAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_SERVER, EMBER_AF_NULL_MANUFACTURER_CODE, dataPtr,
                                dataType,
                                true,   // override read-only?
                                false); // just test?
  }
  
  EmberAfStatus emberAfWriteManufacturerSpecificClientAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
                                                                uint16_t manufacturerCode, uint8_t * dataPtr,
                                                                EmberAfAttributeType dataType)
  {
      return emAfWriteAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_CLIENT, manufacturerCode, dataPtr, dataType,
                                true,   // override read-only?
                                false); // just test?
  }
  
  EmberAfStatus emberAfWriteManufacturerSpecificServerAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
                                                                uint16_t manufacturerCode, uint8_t * dataPtr,
                                                                EmberAfAttributeType dataType)
  {
      return emAfWriteAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_SERVER, manufacturerCode, dataPtr, dataType,
                                true,   // override read-only?
                                false); // just test?
  }
  
  EmberAfStatus emberAfVerifyAttributeWrite(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t mask,
                                            uint16_t manufacturerCode, uint8_t * dataPtr, EmberAfAttributeType dataType)
  {
      return emAfWriteAttribute(endpoint, cluster, attributeID, mask, manufacturerCode, dataPtr, dataType,
                                false, // override read-only?
                                true); // just test?
  }
  
  EmberAfStatus emberAfReadAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t mask, uint8_t * dataPtr,
                                     uint8_t readLength, EmberAfAttributeType * dataType)
  {
      return emAfReadAttribute(endpoint, cluster, attributeID, mask, EMBER_AF_NULL_MANUFACTURER_CODE, dataPtr, readLength, dataType);
  }
  
  EmberAfStatus emberAfReadServerAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
                                           uint8_t readLength)
  {
      return emAfReadAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_SERVER, EMBER_AF_NULL_MANUFACTURER_CODE, dataPtr,
                               readLength, NULL);
  }
  
  EmberAfStatus emberAfReadClientAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
                                           uint8_t readLength)
  {
      return emAfReadAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_CLIENT, EMBER_AF_NULL_MANUFACTURER_CODE, dataPtr,
                               readLength, NULL);
  }
  
  EmberAfStatus emberAfReadManufacturerSpecificServerAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
                                                               uint16_t manufacturerCode, uint8_t * dataPtr, uint8_t readLength)
  {
      return emAfReadAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_SERVER, manufacturerCode, dataPtr, readLength, NULL);
  }
  
  EmberAfStatus emberAfReadManufacturerSpecificClientAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
                                                               uint16_t manufacturerCode, uint8_t * dataPtr, uint8_t readLength)
  {
      return emAfReadAttribute(endpoint, cluster, attributeID, CLUSTER_MASK_CLIENT, manufacturerCode, dataPtr, readLength, NULL);
  }
  
  bool emberAfReadSequentialAttributesAddToResponse(EndpointId endpoint, ClusterId clusterId, AttributeId startAttributeId,
                                                    uint8_t mask, uint16_t manufacturerCode, uint8_t maxAttributeIds,
                                                    bool includeAccessControl)
  {
      uint16_t i;
      uint16_t discovered = 0;
      uint16_t skipped    = 0;
      uint16_t total      = 0;
  
      EmberAfCluster * cluster = emberAfFindClusterWithMfgCode(endpoint, clusterId, mask, manufacturerCode);
  
      EmberAfAttributeSearchRecord record;
      record.endpoint         = endpoint;
      record.clusterId        = clusterId;
      record.clusterMask      = mask;
      record.attributeId      = startAttributeId;
      record.manufacturerCode = manufacturerCode;
  
      // If we don't have the cluster or it doesn't match the search, we're done.
      if (cluster == NULL || !emAfMatchCluster(cluster, &record))
      {
          return true;
      }
  
      for (i = 0; i < cluster->attributeCount; i++)
      {
          EmberAfAttributeMetadata * metadata = &cluster->attributes[i];
  
          // If the cluster is not manufacturer-specific, an attribute is considered
          // only if its manufacturer code matches that of the command (which may be
          // unset).
          if (!emberAfClusterIsManufacturerSpecific(cluster))
          {
              record.attributeId = metadata->attributeId;
              if (!emAfMatchAttribute(cluster, metadata, &record))
              {
                  continue;
              }
          }
  
          if (metadata->attributeId < startAttributeId)
          {
              skipped++;
          }
          else if (discovered < maxAttributeIds)
          {
              emberAfPutInt16uInResp(metadata->attributeId);
              emberAfPutInt8uInResp(metadata->attributeType);
              if (includeAccessControl)
              {
                  // bit 0 : Readable <-- All our attributes are readable
                  // bit 1 : Writable <-- The only thing we track in the attribute metadata mask
                  // bit 2 : Reportable <-- All our attributes are reportable
                  emberAfPutInt8uInResp((metadata->mask & ATTRIBUTE_MASK_WRITABLE) ? 0x07 : 0x05);
              }
              discovered++;
          }
          else
          {
              // MISRA requires ..else if.. to have terminating else.
          }
          total++;
      }
  
      // We are finished if there are no more attributes to find, which means the
      // number of attributes discovered plus the number skipped equals the total
      // attributes in the cluster.  For manufacturer-specific clusters, the total
      // includes all attributes in the cluster.  For standard ZCL clusters, if the
      // the manufacturer code is set, the total is the number of attributes that
      // match the manufacturer code.  Otherwise, the total is the number of
      // standard ZCL attributes in the cluster.
      return (discovered + skipped == total);
  }
  
  static void emberAfAttributeDecodeAndPrintCluster(ClusterId cluster, uint16_t mfgCode)
  {
  #if defined(EMBER_AF_PRINT_ENABLE) && defined(EMBER_AF_PRINT_ATTRIBUTES)
      uint16_t index = emberAfFindClusterNameIndexWithMfgCode(cluster, mfgCode);
      if (index != 0xFFFF)
      {
          emberAfAttributesPrintln("(%p)", zclClusterNames[index].name);
      }
      emberAfAttributesFlush();
  #endif // defined(EMBER_AF_PRINT_ENABLE) && defined(EMBER_AF_PRINT_ATTRIBUTES)
  }
  
  void emberAfPrintAttributeTable(void)
  {
      uint8_t data[ATTRIBUTE_LARGEST];
      decltype(emberAfEndpointCount()) endpointIndex;
      decltype(EmberAfEndpointType::clusterCount) clusterIndex;
      uint16_t attributeIndex;
      EmberAfStatus status;
      uint16_t mfgCode;
      for (endpointIndex = 0; endpointIndex < emberAfEndpointCount(); endpointIndex++)
      {
          EmberAfDefinedEndpoint * ep = &(emAfEndpoints[endpointIndex]);
          emberAfAttributesPrintln("ENDPOINT %x", ep->endpoint);
          emberAfAttributesPrintln("clus / side / attr / mfg  /type(len)/ rw / storage / data (raw)");
          emberAfAttributesFlush();
          for (clusterIndex = 0; clusterIndex < ep->endpointType->clusterCount; clusterIndex++)
          {
              EmberAfCluster * cluster = &(ep->endpointType->cluster[clusterIndex]);
  
              for (attributeIndex = 0; attributeIndex < cluster->attributeCount; attributeIndex++)
              {
                  EmberAfAttributeMetadata * metaData = &(cluster->attributes[attributeIndex]);
  
                  // Depending on user config, this loop can take a very long time to
                  // run and watchdog reset will  kick in. As a workaround, we'll
                  // manually reset the watchdog.
                  //        halResetWatchdog();
  
                  emberAfAttributesPrint("%2x / %p / %2x / ", cluster->clusterId,
                                         (emberAfAttributeIsClient(metaData) ? "clnt" : "srvr"), metaData->attributeId);
                  mfgCode = emAfGetManufacturerCodeForAttribute(cluster, metaData);
                  if (mfgCode == EMBER_AF_NULL_MANUFACTURER_CODE)
                  {
                      emberAfAttributesPrint("----");
                  }
                  else
                  {
                      emberAfAttributesPrint("%2x", mfgCode);
                  }
                  emberAfAttributesPrint(" / %x (%x) / %p / %p / ", metaData->attributeType, emberAfAttributeSize(metaData),
                                         (emberAfAttributeIsReadOnly(metaData) ? "RO" : "RW"),
                                         (emberAfAttributeIsTokenized(metaData)
                                              ? " token "
                                              : (emberAfAttributeIsExternal(metaData) ? "extern " : "  RAM  ")));
                  emberAfAttributesFlush();
                  status = emAfReadAttribute(ep->endpoint, cluster->clusterId, metaData->attributeId,
                                             (emberAfAttributeIsClient(metaData) ? CLUSTER_MASK_CLIENT : CLUSTER_MASK_SERVER),
                                             mfgCode, data, ATTRIBUTE_LARGEST, NULL);
                  if (status == EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE)
                  {
                      emberAfAttributesPrintln("Unsupported");
                  }
                  else
                  {
                      uint16_t length;
                      if (emberAfIsStringAttributeType(metaData->attributeType))
                      {
                          length = static_cast<uint16_t>(emberAfStringLength(data) + 1);
                      }
                      else if (emberAfIsLongStringAttributeType(metaData->attributeType))
                      {
                          length = static_cast<uint16_t>(emberAfLongStringLength(data) + 2);
                      }
                      else
                      {
                          length = emberAfAttributeSize(metaData);
                      }
                      UNUSED_VAR(length);
                      emberAfAttributesPrintBuffer(data, length, true);
                      emberAfAttributesFlush();
                      emberAfAttributeDecodeAndPrintCluster(cluster->clusterId, mfgCode);
                  }
              }
          }
          emberAfAttributesFlush();
      }
  }
  
  // given a clusterId and an attribute to read, this crafts the response
  // and places it in the response buffer. Response is one of two items:
  // 1) unsupported: [attrId:2] [status:1]
  // 2) supported:   [attrId:2] [status:1] [type:1] [data:n]
  //
  void emberAfRetrieveAttributeAndCraftResponse(EndpointId endpoint, ClusterId clusterId, AttributeId attrId, uint8_t mask,
                                                uint16_t manufacturerCode, uint16_t readLength)
  {
      EmberAfStatus status;
      uint8_t data[ATTRIBUTE_LARGEST];
      uint8_t dataType;
      uint16_t dataLen;
  
      // account for at least one byte of data
      if (readLength < 5)
      {
          return;
      }
  
      emberAfAttributesPrintln("OTA READ: ep:%x cid:%2x attid:%2x msk:%x mfcode:%2x", endpoint, clusterId, attrId, mask,
                               manufacturerCode);
  
      // lookup the attribute in our table
      status = emAfReadAttribute(endpoint, clusterId, attrId, mask, manufacturerCode, data, ATTRIBUTE_LARGEST, &dataType);
      if (status == EMBER_ZCL_STATUS_SUCCESS)
      {
          dataLen = emberAfAttributeValueSize(dataType, data);
          if ((readLength - 4) < dataLen)
          { // Not enough space for attribute.
              return;
          }
      }
      else
      {
          emberAfPutInt16uInResp(attrId);
          emberAfPutInt8uInResp(status);
          emberAfAttributesPrintln("READ: clus %2x, attr %2x failed %x", clusterId, attrId, status);
          emberAfAttributesFlush();
          return;
      }
  
      // put attribute in least sig byte first
      emberAfPutInt16uInResp(attrId);
  
      // attribute is found, so copy in the status and the data type
      emberAfPutInt8uInResp(EMBER_ZCL_STATUS_SUCCESS);
      emberAfPutInt8uInResp(dataType);
  
      if (dataLen < (EMBER_AF_RESPONSE_BUFFER_LEN - appResponseLength))
      {
  #if (BIGENDIAN_CPU)
          // strings go over the air as length byte and then in human
          // readable format. These should not be flipped. Other attributes
          // need to be flipped so they go little endian OTA
          if (isThisDataTypeSentLittleEndianOTA(dataType))
          {
              uint8_t i;
              for (i = 0; i < dataLen; i++)
              {
                  appResponseData[appResponseLength + i] = data[dataLen - i - 1];
              }
          }
          else
          {
              memmove(&(appResponseData[appResponseLength]), data, dataLen);
          }
  #else  //(BIGENDIAN_CPU)
          memmove(&(appResponseData[appResponseLength]), data, dataLen);
  #endif //(BIGENDIAN_CPU)
         // TODO: How do we know this does not overflow?
          appResponseLength = static_cast<uint16_t>(appResponseLength + dataLen);
      }
  
      emberAfAttributesPrintln("READ: clus %2x, attr %2x, dataLen: %x, OK", clusterId, attrId, dataLen);
      emberAfAttributesFlush();
  }
  
  // This function appends the attribute report fields for the given endpoint,
  // cluster, and attribute to the buffer starting at the index.  If there is
  // insufficient space in the buffer or an error occurs, buffer and bufIndex will
  // remain unchanged.  Otherwise, bufIndex will be incremented appropriately and
  // the fields will be written to the buffer.
  EmberAfStatus emberAfAppendAttributeReportFields(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId, uint8_t mask,
                                                   uint8_t * buffer, uint8_t bufLen, uint8_t * bufIndex)
  {
      EmberAfStatus status;
      EmberAfAttributeType type;
      uint16_t size;
      uint16_t bufLen16 = (uint16_t) bufLen;
      uint8_t data[ATTRIBUTE_LARGEST];
  
      status = emberAfReadAttribute(endpoint, clusterId, attributeId, mask, data, sizeof(data), &type);
      if (status != EMBER_ZCL_STATUS_SUCCESS)
      {
          goto kickout;
      }
  
      size = emberAfAttributeValueSize(type, data);
      if (bufLen16 - *bufIndex < 3 || size > bufLen16 - (*bufIndex + 3))
      {
          status = EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
          goto kickout;
      }
  
      buffer[(*bufIndex)++] = EMBER_LOW_BYTE(attributeId);
      buffer[(*bufIndex)++] = EMBER_HIGH_BYTE(attributeId);
      buffer[(*bufIndex)++] = type;
  #if (BIGENDIAN_CPU)
      if (isThisDataTypeSentLittleEndianOTA(type))
      {
          emberReverseMemCopy(buffer + *bufIndex, data, size);
      }
      else
      {
          memmove(buffer + *bufIndex, data, size);
      }
  #else
      memmove(buffer + *bufIndex, data, size);
  #endif
      *bufIndex = static_cast<uint8_t>(*bufIndex + size);
  
  kickout:
      emberAfAttributesPrintln("REPORT: clus 0x%2x, attr 0x%2x: 0x%x", clusterId, attributeId, status);
      emberAfAttributesFlush();
  
      return status;
  }
  
  //------------------------------------------------------------------------------
  // Internal Functions
  
  // writes an attribute (identified by clusterID and attrID to the given value.
  // this returns:
  // - EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE: if attribute isnt supported by the device (the
  //           device is not found in the attribute table)
  // - EMBER_ZCL_STATUS_INVALID_DATA_TYPE: if the data type passed in doesnt match the type
  //           stored in the attribute table
  // - EMBER_ZCL_STATUS_READ_ONLY: if the attribute isnt writable
  // - EMBER_ZCL_STATUS_INVALID_VALUE: if the value is set out of the allowable range for
  //           the attribute
  // - EMBER_ZCL_STATUS_SUCCESS: if the attribute was found and successfully written
  //
  // if true is passed in for overrideReadOnlyAndDataType then the data type is
  // not checked and the read-only flag is ignored. This mode is meant for
  // testing or setting the initial value of the attribute on the device.
  //
  // if true is passed for justTest, then the type is not written but all
  // checks are done to see if the type could be written
  // reads the attribute specified, returns false if the attribute is not in
  // the table or the data is too large, returns true and writes to dataPtr
  // if the attribute is supported and the readLength specified is less than
  // the length of the data.
  EmberAfStatus emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t mask,
                                   uint16_t manufacturerCode, uint8_t * data, EmberAfAttributeType dataType,
                                   bool overrideReadOnlyAndDataType, bool justTest)
  {
      EmberAfAttributeMetadata * metadata = NULL;
      EmberAfAttributeSearchRecord record;
      record.endpoint         = endpoint;
      record.clusterId        = cluster;
      record.clusterMask      = mask;
      record.attributeId      = attributeID;
      record.manufacturerCode = manufacturerCode;
      emAfReadOrWriteAttribute(&record, &metadata,
                               NULL,   // buffer
                               0,      // buffer size
                               false); // write?
  
      // if we dont support that attribute
      if (metadata == NULL)
      {
          emberAfAttributesPrintln("%pep %x clus %2x attr %2x not supported", "WRITE ERR: ", endpoint, cluster, attributeID);
          emberAfAttributesFlush();
          return EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
      }
  
      // if the data type specified by the caller is incorrect
      if (!(overrideReadOnlyAndDataType))
      {
          if (dataType != metadata->attributeType)
          {
              emberAfAttributesPrintln("%pinvalid data type", "WRITE ERR: ");
              emberAfAttributesFlush();
              return EMBER_ZCL_STATUS_INVALID_DATA_TYPE;
          }
  
          if (emberAfAttributeIsReadOnly(metadata))
          {
              emberAfAttributesPrintln("%pattr not writable", "WRITE ERR: ");
              emberAfAttributesFlush();
              return EMBER_ZCL_STATUS_READ_ONLY;
          }
      }
  
      // if the value the attribute is being set to is out of range
      // return EMBER_ZCL_STATUS_INVALID_VALUE
      if ((metadata->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U)
      {
          EmberAfDefaultAttributeValue minv = metadata->defaultValue.ptrToMinMaxValue->minValue;
          EmberAfDefaultAttributeValue maxv = metadata->defaultValue.ptrToMinMaxValue->maxValue;
          bool isAttributeSigned            = emberAfIsTypeSigned(metadata->attributeType);
          uint8_t dataLen                   = emberAfAttributeSize(metadata);
          if (dataLen <= 2)
          {
              int8_t minR, maxR;
              uint8_t * minI = (uint8_t *) &(minv.defaultValue);
              uint8_t * maxI = (uint8_t *) &(maxv.defaultValue);
  // On big endian cpu with length 1 only the second byte counts
  #if (BIGENDIAN_CPU)
              if (dataLen == 1)
              {
                  minI++;
                  maxI++;
              }
  #endif // BIGENDIAN_CPU
              minR = emberAfCompareValues(minI, data, dataLen, isAttributeSigned);
              maxR = emberAfCompareValues(maxI, data, dataLen, isAttributeSigned);
              if ((minR == 1) || (maxR == -1))
              {
                  return EMBER_ZCL_STATUS_INVALID_VALUE;
              }
          }
          else
          {
              if ((emberAfCompareValues(minv.ptrToDefaultValue, data, dataLen, isAttributeSigned) == 1) ||
                  (emberAfCompareValues(maxv.ptrToDefaultValue, data, dataLen, isAttributeSigned) == -1))
              {
                  return EMBER_ZCL_STATUS_INVALID_VALUE;
              }
          }
      }
  
      // write the data unless this is only a test
      if (!justTest)
      {
          // Pre write attribute callback for all attribute changes,
          // regardless of cluster.
          EmberAfStatus status = emberAfPreAttributeChangeCallback(endpoint, cluster, attributeID, mask, manufacturerCode, dataType,
                                                                   emberAfAttributeSize(metadata), data);
          if (status != EMBER_ZCL_STATUS_SUCCESS)
          {
              return status;
          }
  
          // Pre-write attribute callback specific
          // to the cluster that the attribute lives in.
          status = emAfClusterPreAttributeChangedCallback(endpoint, cluster, attributeID, mask, manufacturerCode, dataType,
                                                          emberAfAttributeSize(metadata), data);
          if (status != EMBER_ZCL_STATUS_SUCCESS)
          {
              return status;
          }
  
          // write the attribute
          status = emAfReadOrWriteAttribute(&record,
                                            NULL, // metadata
                                            data,
                                            0,     // buffer size - unused
                                            true); // write?
  
          if (status != EMBER_ZCL_STATUS_SUCCESS)
          {
              return status;
          }
  
          // Save the attribute to token if needed
          // Function itself will weed out tokens that are not tokenized.
          emAfSaveAttributeToToken(data, endpoint, cluster, metadata);
  
  #ifdef EMBER_AF_PLUGIN_REPORTING
          emberAfReportingAttributeChangeCallback(endpoint, cluster, attributeID, mask, manufacturerCode, dataType, data);
  #endif // EMBER_AF_PLUGIN_REPORTING
  
          // Post write attribute callback for all attributes changes, regardless
          // of cluster.
          emberAfPostAttributeChangeCallback(endpoint, cluster, attributeID, mask, manufacturerCode, dataType,
                                             emberAfAttributeSize(metadata), data);
  
          // Post-write attribute callback specific
          // to the cluster that the attribute lives in.
          emAfClusterAttributeChangedCallback(endpoint, cluster, attributeID, mask, manufacturerCode);
      }
      else
      {
          // bug: 11618, we are not handling properly external attributes
          // in this case... We need to do something. We don't really
          // know if it will succeed.
          emberAfAttributesPrintln("WRITE: no write, just a test");
          emberAfAttributesFlush();
      }
  
      return EMBER_ZCL_STATUS_SUCCESS;
  }
  
  // If dataPtr is NULL, no data is copied to the caller.
  // readLength should be 0 in that case.
  
  EmberAfStatus emAfReadAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t mask,
                                  uint16_t manufacturerCode, uint8_t * dataPtr, uint16_t readLength, EmberAfAttributeType * dataType)
  {
      EmberAfAttributeMetadata * metadata = NULL;
      EmberAfAttributeSearchRecord record;
      EmberAfStatus status;
      record.endpoint         = endpoint;
      record.clusterId        = cluster;
      record.clusterMask      = mask;
      record.attributeId      = attributeID;
      record.manufacturerCode = manufacturerCode;
      status                  = emAfReadOrWriteAttribute(&record, &metadata, dataPtr, readLength,
                                        false); // write?
  
      if (status == EMBER_ZCL_STATUS_SUCCESS)
      {
          // It worked!  If the user asked for the type, set it before returning.
          if (dataType != NULL)
          {
              (*dataType) = metadata->attributeType;
          }
      }
      else
      { // failed, print debug info
          if (status == EMBER_ZCL_STATUS_INSUFFICIENT_SPACE)
          {
              emberAfAttributesPrintln("READ: attribute size too large for caller");
              emberAfAttributesFlush();
          }
      }
  
      return status;
  }
  