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.
   */
  
  #include "Device.h"
  #include "DeviceCallbacks.h"
  #include "esp_log.h"
  #include "nvs_flash.h"
  #include <app-common/zap-generated/af-structs.h>
  #include <app-common/zap-generated/attribute-id.h>
  #include <app-common/zap-generated/cluster-id.h>
  #include <app/reporting/reporting.h>
  #include <app/server/Server.h>
  #include <app/util/attribute-storage.h>
  #include <credentials/DeviceAttestationCredsProvider.h>
  #include <credentials/examples/DeviceAttestationCredsExample.h>
  #include <lib/core/CHIPError.h>
  #include <lib/support/CHIPMemString.h>
  #include <lib/support/ErrorStr.h>
  
  #include <app/server/Server.h>
  
  const char * TAG = "bridge-app";
  
  using namespace ::chip;
  using namespace ::chip::Credentials;
  using namespace ::chip::DeviceManager;
  using namespace ::chip::DeviceLayer;
  using namespace ::chip::Platform;
  
  static DeviceCallbacks AppCallback;
  
  static const int kNodeLabelSize = 32;
  // Current ZCL implementation of Struct uses a max-size array of 254 bytes
  static const int kDescriptorAttributeArraySize = 254;
  static const int kFixedLabelAttributeArraySize = 254;
  
  static EndpointId gCurrentEndpointId;
  static EndpointId gFirstDynamicEndpointId;
  static Device * gDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT]; // number of dynamic endpoints count
  
  // 4 Bridged devices
  static Device gLight1("Light 1", "Office");
  static Device gLight2("Light 2", "Office");
  static Device gLight3("Light 3", "Kitchen");
  static Device gLight4("Light 4", "Den");
  
  // (taken from chip-devices.xml)
  #define DEVICE_TYPE_CHIP_BRIDGE 0x0a0b
  // (taken from lo-devices.xml)
  #define DEVICE_TYPE_LO_ON_OFF_LIGHT 0x0100
  // Device Version for dynamic endpoints:
  #define DEVICE_VERSION_DEFAULT 1
  
  /* BRIDGED DEVICE ENDPOINT: contains the following clusters:
     - On/Off
     - Descriptor
     - Bridged Device Basic
     - Fixed Label
  */
  
  // Declare On/Off cluster attributes
  DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(onOffAttrs)
  DECLARE_DYNAMIC_ATTRIBUTE(ZCL_ON_OFF_ATTRIBUTE_ID, BOOLEAN, 1, 0), /* on/off */
      DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
  
  // Declare Descriptor cluster attributes
  DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs)
  DECLARE_DYNAMIC_ATTRIBUTE(ZCL_DEVICE_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0),     /* device list */
      DECLARE_DYNAMIC_ATTRIBUTE(ZCL_SERVER_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */
      DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CLIENT_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */
      DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0),  /* parts list */
      DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
  
  // Declare Bridged Device Basic information cluster attributes
  DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs)
  DECLARE_DYNAMIC_ATTRIBUTE(ZCL_NODE_LABEL_ATTRIBUTE_ID, CHAR_STRING, kNodeLabelSize, 0), /* NodeLabel */
      DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0),               /* Reachable */
      DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
  
  // Declare Fixed Label cluster attributes
  DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(fixedLabelAttrs)
  DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0), /* label list */
      DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
  
  // Declare Cluster List for Bridged Light endpoint
  DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedLightClusters)
  DECLARE_DYNAMIC_CLUSTER(ZCL_ON_OFF_CLUSTER_ID, onOffAttrs), DECLARE_DYNAMIC_CLUSTER(ZCL_DESCRIPTOR_CLUSTER_ID, descriptorAttrs),
      DECLARE_DYNAMIC_CLUSTER(ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID, bridgedDeviceBasicAttrs),
      DECLARE_DYNAMIC_CLUSTER(ZCL_FIXED_LABEL_CLUSTER_ID, fixedLabelAttrs) DECLARE_DYNAMIC_CLUSTER_LIST_END;
  
  // Declare Bridged Light endpoint
  DECLARE_DYNAMIC_ENDPOINT(bridgedLightEndpoint, bridgedLightClusters);
  
  DataVersion gLight1DataVersions[ArraySize(bridgedLightClusters)];
  DataVersion gLight2DataVersions[ArraySize(bridgedLightClusters)];
  DataVersion gLight3DataVersions[ArraySize(bridgedLightClusters)];
  DataVersion gLight4DataVersions[ArraySize(bridgedLightClusters)];
  
  /* REVISION definitions:
   */
  
  #define ZCL_DESCRIPTOR_CLUSTER_REVISION (1u)
  #define ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_REVISION (1u)
  #define ZCL_FIXED_LABEL_CLUSTER_REVISION (1u)
  #define ZCL_ON_OFF_CLUSTER_REVISION (4u)
  
  CHIP_ERROR AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, uint16_t deviceType,
                               const Span<DataVersion> & dataVersionStorage)
  {
      uint8_t index = 0;
      while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
      {
          if (NULL == gDevices[index])
          {
              gDevices[index] = dev;
              EmberAfStatus ret;
              ret = emberAfSetDynamicEndpoint(index, gCurrentEndpointId, ep, deviceType, DEVICE_VERSION_DEFAULT, dataVersionStorage);
              if (ret == EMBER_ZCL_STATUS_SUCCESS)
              {
                  ChipLogProgress(DeviceLayer, "Added device %s to dynamic endpoint %d (index=%d)", dev->GetName(),
                                  gCurrentEndpointId, index);
                  gCurrentEndpointId++;
                  return CHIP_NO_ERROR;
              }
              else if (ret != EMBER_ZCL_STATUS_DUPLICATE_EXISTS)
              {
                  ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint, Insufficient space");
                  return CHIP_ERROR_INTERNAL;
              }
          }
          index++;
      }
      ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!");
      return CHIP_ERROR_INTERNAL;
  }
  
  CHIP_ERROR RemoveDeviceEndpoint(Device * dev)
  {
      for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; index++)
      {
          if (gDevices[index] == dev)
          {
              EndpointId ep   = emberAfClearDynamicEndpoint(index);
              gDevices[index] = NULL;
              ChipLogProgress(DeviceLayer, "Removed device %s from dynamic endpoint %d (index=%d)", dev->GetName(), ep, index);
              // Silence complaints about unused ep when progress logging
              // disabled.
              UNUSED_VAR(ep);
              return CHIP_NO_ERROR;
          }
      }
      return CHIP_ERROR_INTERNAL;
  }
  
  // ZCL format -> (len, string)
  uint8_t * ToZclCharString(uint8_t * zclString, const char * cString, uint8_t maxLength)
  {
      size_t len = strlen(cString);
      if (len > maxLength)
      {
          len = maxLength;
      }
      zclString[0] = static_cast<uint8_t>(len);
      memcpy(&zclString[1], cString, zclString[0]);
      return zclString;
  }
  
  // Converted into bytes and mapped the (label, value)
  void EncodeFixedLabel(const char * label, const char * value, uint8_t * buffer, uint16_t length,
                        const EmberAfAttributeMetadata * am)
  {
      _LabelStruct labelStruct;
  
      labelStruct.label = chip::CharSpan::fromCharString(label);
      labelStruct.value = chip::CharSpan::fromCharString(value);
  
      // TODO: Need to set up an AttributeAccessInterface to handle the lists here.
  }
  
  EmberAfStatus HandleReadBridgedDeviceBasicAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer,
                                                      uint16_t maxReadLength)
  {
      ChipLogProgress(DeviceLayer, "HandleReadBridgedDeviceBasicAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
  
      if ((attributeId == ZCL_REACHABLE_ATTRIBUTE_ID) && (maxReadLength == 1))
      {
          *buffer = dev->IsReachable() ? 1 : 0;
      }
      else if ((attributeId == ZCL_NODE_LABEL_ATTRIBUTE_ID) && (maxReadLength == 32))
      {
          ToZclCharString(buffer, dev->GetName(), static_cast<uint8_t>(maxReadLength - 1));
      }
      else if ((attributeId == ZCL_CLUSTER_REVISION_SERVER_ATTRIBUTE_ID) && (maxReadLength == 2))
      {
          *buffer = (uint16_t) ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_REVISION;
      }
      else
      {
          return EMBER_ZCL_STATUS_FAILURE;
      }
  
      return EMBER_ZCL_STATUS_SUCCESS;
  }
  
  EmberAfStatus HandleReadFixedLabelAttribute(Device * dev, const EmberAfAttributeMetadata * am, uint8_t * buffer,
                                              uint16_t maxReadLength)
  {
      if ((am->attributeId == ZCL_LABEL_LIST_ATTRIBUTE_ID) && (maxReadLength <= kFixedLabelAttributeArraySize))
      {
          EncodeFixedLabel("room", dev->GetLocation(), buffer, maxReadLength, am);
      }
      else if ((am->attributeId == ZCL_CLUSTER_REVISION_SERVER_ATTRIBUTE_ID) && (maxReadLength == 2))
      {
          *buffer = (uint16_t) ZCL_FIXED_LABEL_CLUSTER_REVISION;
      }
      else
      {
          return EMBER_ZCL_STATUS_FAILURE;
      }
  
      return EMBER_ZCL_STATUS_SUCCESS;
  }
  
  EmberAfStatus HandleReadOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer, uint16_t maxReadLength)
  {
      ChipLogProgress(DeviceLayer, "HandleReadOnOffAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
  
      if ((attributeId == ZCL_ON_OFF_ATTRIBUTE_ID) && (maxReadLength == 1))
      {
          *buffer = dev->IsOn() ? 1 : 0;
      }
      else if ((attributeId == ZCL_CLUSTER_REVISION_SERVER_ATTRIBUTE_ID) && (maxReadLength == 2))
      {
          *buffer = (uint16_t) ZCL_ON_OFF_CLUSTER_REVISION;
      }
      else
      {
          return EMBER_ZCL_STATUS_FAILURE;
      }
  
      return EMBER_ZCL_STATUS_SUCCESS;
  }
  
  EmberAfStatus HandleWriteOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer)
  {
      ChipLogProgress(DeviceLayer, "HandleWriteOnOffAttribute: attrId=%d", attributeId);
  
      ReturnErrorCodeIf((attributeId != ZCL_ON_OFF_ATTRIBUTE_ID) || (!dev->IsReachable()), EMBER_ZCL_STATUS_FAILURE);
      dev->SetOnOff(*buffer == 1);
      return EMBER_ZCL_STATUS_SUCCESS;
  }
  
  EmberAfStatus emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId,
                                                     const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer,
                                                     uint16_t maxReadLength)
  {
      uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint);
  
      if ((endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) && (gDevices[endpointIndex] != NULL))
      {
          Device * dev = gDevices[endpointIndex];
  
          if (clusterId == ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID)
          {
              return HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength);
          }
          else if (clusterId == ZCL_FIXED_LABEL_CLUSTER_ID)
          {
              return HandleReadFixedLabelAttribute(dev, attributeMetadata, buffer, maxReadLength);
          }
          else if (clusterId == ZCL_ON_OFF_CLUSTER_ID)
          {
              return HandleReadOnOffAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength);
          }
      }
  
      return EMBER_ZCL_STATUS_FAILURE;
  }
  
  EmberAfStatus emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId,
                                                      const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer)
  {
      uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint);
  
      if (endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
      {
          Device * dev = gDevices[endpointIndex];
  
          if ((dev->IsReachable()) && (clusterId == ZCL_ON_OFF_CLUSTER_ID))
          {
              return HandleWriteOnOffAttribute(dev, attributeMetadata->attributeId, buffer);
          }
      }
  
      return EMBER_ZCL_STATUS_FAILURE;
  }
  
  void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask)
  {
      if (itemChangedMask & Device::kChanged_Reachable)
      {
          uint8_t reachable = dev->IsReachable() ? 1 : 0;
          MatterReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
                                                 ZCL_REACHABLE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, ZCL_BOOLEAN_ATTRIBUTE_TYPE,
                                                 &reachable);
      }
  
      if (itemChangedMask & Device::kChanged_State)
      {
          uint8_t isOn = dev->IsOn() ? 1 : 0;
          MatterReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID,
                                                 CLUSTER_MASK_SERVER, ZCL_BOOLEAN_ATTRIBUTE_TYPE, &isOn);
      }
  
      if (itemChangedMask & Device::kChanged_Name)
      {
          uint8_t zclName[kNodeLabelSize + 1];
          ToZclCharString(zclName, dev->GetName(), kNodeLabelSize);
          MatterReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
                                                 ZCL_NODE_LABEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, ZCL_CHAR_STRING_ATTRIBUTE_TYPE,
                                                 zclName);
      }
      if (itemChangedMask & Device::kChanged_Location)
      {
          uint8_t buffer[kFixedLabelAttributeArraySize];
          EmberAfAttributeMetadata am = { .attributeId  = ZCL_LABEL_LIST_ATTRIBUTE_ID,
                                          .size         = kFixedLabelAttributeArraySize,
                                          .defaultValue = static_cast<uint16_t>(0) };
  
          EncodeFixedLabel("room", dev->GetLocation(), buffer, sizeof(buffer), &am);
  
          MatterReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_FIXED_LABEL_CLUSTER_ID, ZCL_LABEL_LIST_ATTRIBUTE_ID,
                                                 CLUSTER_MASK_SERVER, ZCL_ARRAY_ATTRIBUTE_TYPE, buffer);
      }
  }
  
  static void InitServer(intptr_t context)
  {
      chip::Server::GetInstance().Init();
  
      // Initialize device attestation config
      SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider());
  
      // Set starting endpoint id where dynamic endpoints will be assigned, which
      // will be the next consecutive endpoint id after the last fixed endpoint.
      gFirstDynamicEndpointId = static_cast<chip::EndpointId>(
          static_cast<int>(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1))) + 1);
      gCurrentEndpointId = gFirstDynamicEndpointId;
  
      // Disable last fixed endpoint, which is used as a placeholder for all of the
      // supported clusters so that ZAP will generated the requisite code.
      emberAfEndpointEnableDisable(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1)), false);
  
      // Add lights 1..3 --> will be mapped to ZCL endpoints 2, 3, 4
      AddDeviceEndpoint(&gLight1, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT, Span<DataVersion>(gLight1DataVersions));
      AddDeviceEndpoint(&gLight2, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT, Span<DataVersion>(gLight2DataVersions));
      AddDeviceEndpoint(&gLight3, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT, Span<DataVersion>(gLight3DataVersions));
  
      // Remove Light 2 -- Lights 1 & 3 will remain mapped to endpoints 2 & 4
      RemoveDeviceEndpoint(&gLight2);
  
      // Add Light 4 -- > will be mapped to ZCL endpoint 5
      AddDeviceEndpoint(&gLight4, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT, Span<DataVersion>(gLight4DataVersions));
  
      // Re-add Light 2 -- > will be mapped to ZCL endpoint 6
      AddDeviceEndpoint(&gLight2, &bridgedLightEndpoint, DEVICE_TYPE_LO_ON_OFF_LIGHT, Span<DataVersion>(gLight2DataVersions));
  }
  
  extern "C" void app_main()
  {
      // Initialize the ESP NVS layer.
      esp_err_t err = nvs_flash_init();
      if (err != ESP_OK)
      {
          ESP_LOGE(TAG, "nvs_flash_init() failed: %s", esp_err_to_name(err));
          return;
      }
  
      CHIP_ERROR chip_err = CHIP_NO_ERROR;
  
      // bridge will have own database named gDevices.
      // Clear database
      memset(gDevices, 0, sizeof(gDevices));
  
      // Whenever bridged device changes its state
      gLight1.SetChangeCallback(&HandleDeviceStatusChanged);
      gLight2.SetChangeCallback(&HandleDeviceStatusChanged);
      gLight3.SetChangeCallback(&HandleDeviceStatusChanged);
      gLight4.SetChangeCallback(&HandleDeviceStatusChanged);
  
      gLight1.SetReachable(true);
      gLight2.SetReachable(true);
      gLight3.SetReachable(true);
      gLight4.SetReachable(true);
  
      CHIPDeviceManager & deviceMgr = CHIPDeviceManager::GetInstance();
  
      chip_err = deviceMgr.Init(&AppCallback);
      if (chip_err != CHIP_NO_ERROR)
      {
          ESP_LOGE(TAG, "device.Init() failed: %s", ErrorStr(chip_err));
          return;
      }
  
      chip::DeviceLayer::PlatformMgr().ScheduleWork(InitServer, reinterpret_cast<intptr_t>(nullptr));
  }
  