text: /*
   * NimBLERemoteCharacteristic.cpp
   *
   *  Created: on Jan 27 2020
   *      Author H2zero
   *
   * Originally:
   *
   * BLERemoteCharacteristic.cpp
   *
   *  Created on: Mar 16, 2017
   *      Author: kolban
   */
  
  #include "nimconfig.h"
  #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
  
  #include "NimBLERemoteCharacteristic.h"
  #include "NimBLEUtils.h"
  #include "NimBLELog.h"
  
  #include <climits>
  
  static const char* LOG_TAG = "NimBLERemoteCharacteristic";
  
  /**
   * @brief Constructor.
   * @param [in] reference to the service this characteristic belongs to.
   * @param [in] ble_gatt_chr struct defined as:
   *  struct ble_gatt_chr {
   *      uint16_t def_handle;
   *      uint16_t val_handle;
   *      uint8_t properties;
   *      ble_uuid_any_t uuid;
   *  };
   */
   NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService,
                                                          const struct ble_gatt_chr *chr)
  {
      NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()");
       switch (chr->uuid.u.type) {
          case BLE_UUID_TYPE_16:
              m_uuid = NimBLEUUID(chr->uuid.u16.value);
              break;
          case BLE_UUID_TYPE_32:
              m_uuid = NimBLEUUID(chr->uuid.u32.value);
              break;
          case BLE_UUID_TYPE_128:
              m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&chr->uuid.u128));
              break;
          default:
              break;
      }
  
      m_handle             = chr->val_handle;
      m_defHandle          = chr->def_handle;
      m_endHandle          = 0;
      m_charProp           = chr->properties;
      m_pRemoteService     = pRemoteService;
      m_notifyCallback     = nullptr;
  
      NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str());
   } // NimBLERemoteCharacteristic
  
  
  /**
   *@brief Destructor.
   */
  NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() {
      deleteDescriptors();
  } // ~NimBLERemoteCharacteristic
  
  /*
  #define BLE_GATT_CHR_PROP_BROADCAST                     0x01
  #define BLE_GATT_CHR_PROP_READ                          0x02
  #define BLE_GATT_CHR_PROP_WRITE_NO_RSP                  0x04
  #define BLE_GATT_CHR_PROP_WRITE                         0x08
  #define BLE_GATT_CHR_PROP_NOTIFY                        0x10
  #define BLE_GATT_CHR_PROP_INDICATE                      0x20
  #define BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE               0x40
  #define BLE_GATT_CHR_PROP_EXTENDED                      0x80
  */
  
  /**
   * @brief Does the characteristic support broadcasting?
   * @return True if the characteristic supports broadcasting.
   */
  bool NimBLERemoteCharacteristic::canBroadcast() {
      return (m_charProp & BLE_GATT_CHR_PROP_BROADCAST) != 0;
  } // canBroadcast
  
  
  /**
   * @brief Does the characteristic support indications?
   * @return True if the characteristic supports indications.
   */
  bool NimBLERemoteCharacteristic::canIndicate() {
      return (m_charProp & BLE_GATT_CHR_PROP_INDICATE) != 0;
  } // canIndicate
  
  
  /**
   * @brief Does the characteristic support notifications?
   * @return True if the characteristic supports notifications.
   */
  bool NimBLERemoteCharacteristic::canNotify() {
      return (m_charProp & BLE_GATT_CHR_PROP_NOTIFY) != 0;
  } // canNotify
  
  
  /**
   * @brief Does the characteristic support reading?
   * @return True if the characteristic supports reading.
   */
  bool NimBLERemoteCharacteristic::canRead() {
      return (m_charProp & BLE_GATT_CHR_PROP_READ) != 0;
  } // canRead
  
  
  /**
   * @brief Does the characteristic support writing?
   * @return True if the characteristic supports writing.
   */
  bool NimBLERemoteCharacteristic::canWrite() {
      return (m_charProp & BLE_GATT_CHR_PROP_WRITE) != 0;
  } // canWrite
  
  
  /**
   * @brief Does the characteristic support writing with no response?
   * @return True if the characteristic supports writing with no response.
   */
  bool NimBLERemoteCharacteristic::canWriteNoResponse() {
      return (m_charProp & BLE_GATT_CHR_PROP_WRITE_NO_RSP) != 0;
  } // canWriteNoResponse
  
  
  /**
   * @brief Callback used by the API when a descriptor is discovered or search complete.
   */
  int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle,
                                      const struct ble_gatt_error *error,
                                      uint16_t chr_val_handle,
                                      const struct ble_gatt_dsc *dsc,
                                      void *arg)
  {
      int rc = error->status;
      NIMBLE_LOGD(LOG_TAG, "Descriptor Discovered >> status: %d handle: %d",
                           rc, (rc == 0) ? dsc->handle : -1);
  
      desc_filter_t *filter = (desc_filter_t*)arg;
      const NimBLEUUID *uuid_filter = filter->uuid;
      ble_task_data_t *pTaskData = (ble_task_data_t*)filter->task_data;
      NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
  
      if (characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
          return 0;
      }
  
      switch (rc) {
          case 0: {
              if (uuid_filter != nullptr) {
                  if (ble_uuid_cmp(&uuid_filter->getNative()->u, &dsc->uuid.u) != 0) {
                      return 0;
                  } else {
                      rc = BLE_HS_EDONE;
                  }
              }
  
              NimBLERemoteDescriptor* pNewRemoteDescriptor = new NimBLERemoteDescriptor(characteristic, dsc);
              characteristic->m_descriptorVector.push_back(pNewRemoteDescriptor);
              break;
          }
          default:
              break;
      }
  
      /*  If rc == BLE_HS_EDONE, resume the task with a success error code and stop the discovery process.
       *  Else if rc == 0, just return 0 to continue the discovery until we get BLE_HS_EDONE.
       *  If we get any other error code tell the application to abort by returning non-zero in the rc.
       */
      if (rc == BLE_HS_EDONE) {
          pTaskData->rc = 0;
          xTaskNotifyGive(pTaskData->task);
      } else if(rc != 0) {
          // Error; abort discovery.
          pTaskData->rc = rc;
          xTaskNotifyGive(pTaskData->task);
      }
  
      NIMBLE_LOGD(LOG_TAG,"<< Descriptor Discovered. status: %d", pTaskData->rc);
      return rc;
  }
  
  
  /**
   * @brief callback from NimBLE when the next characteristic of the service is discovered.
   */
  int NimBLERemoteCharacteristic::nextCharCB(uint16_t conn_handle,
                                             const struct ble_gatt_error *error,
                                             const struct ble_gatt_chr *chr, void *arg)
  {
      int rc = error->status;
      NIMBLE_LOGD(LOG_TAG, "Next Characteristic >> status: %d handle: %d",
                           rc, (rc == 0) ? chr->val_handle : -1);
  
      ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
      NimBLERemoteCharacteristic *pChar = (NimBLERemoteCharacteristic*)pTaskData->pATT;
  
      if (pChar->getRemoteService()->getClient()->getConnId() != conn_handle) {
          return 0;
      }
  
      if (rc == 0) {
          pChar->m_endHandle = chr->def_handle - 1;
          rc = BLE_HS_EDONE;
      } else if (rc == BLE_HS_EDONE) {
          pChar->m_endHandle = pChar->getRemoteService()->getEndHandle();
      } else {
          pTaskData->rc = rc;
      }
  
      xTaskNotifyGive(pTaskData->task);
      return rc;
  }
  
  
  /**
   * @brief Populate the descriptors (if any) for this characteristic.
   * @param [in] the end handle of the characteristic, or the service, whichever comes first.
   */
  bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filter) {
      NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
  
      // If this is the last handle then there are no descriptors
      if (m_handle == getRemoteService()->getEndHandle()) {
          return true;
      }
  
      int rc = 0;
      TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
      ble_task_data_t taskData = {this, cur_task, 0, nullptr};
  
      // If we don't know the end handle of this characteristic retrieve the next one in the service
      // The end handle is the next characteristic definition handle -1.
      if (m_endHandle == 0) {
          rc = ble_gattc_disc_all_chrs(getRemoteService()->getClient()->getConnId(),
                                       m_handle,
                                       getRemoteService()->getEndHandle(),
                                       NimBLERemoteCharacteristic::nextCharCB,
                                       &taskData);
          if (rc != 0) {
              NIMBLE_LOGE(LOG_TAG, "Error getting end handle rc=%d", rc);
              return false;
          }
  
  #ifdef ulTaskNotifyValueClear
          // Clear the task notification value to ensure we block
          ulTaskNotifyValueClear(cur_task, ULONG_MAX);
  #endif
          ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
  
          if (taskData.rc != 0) {
              NIMBLE_LOGE(LOG_TAG, "Could not retrieve end handle rc=%d", taskData.rc);
              return false;
          }
      }
  
      desc_filter_t filter = {uuid_filter, &taskData};
  
      rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(),
                                   m_handle,
                                   m_endHandle,
                                   NimBLERemoteCharacteristic::descriptorDiscCB,
                                   &filter);
  
      if (rc != 0) {
          NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_dscs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
          return false;
      }
  
  #ifdef ulTaskNotifyValueClear
      // Clear the task notification value to ensure we block
      ulTaskNotifyValueClear(cur_task, ULONG_MAX);
  #endif
      ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
  
      if (taskData.rc != 0) {
          NIMBLE_LOGE(LOG_TAG, "Failed to retrieve descriptors; startHandle:%d endHandle:%d taskData.rc=%d",
                               m_handle, m_endHandle, taskData.rc);
      }
  
      NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size());
      return (taskData.rc == 0);
  } // retrieveDescriptors
  
  
  /**
   * @brief Get the descriptor instance with the given UUID that belongs to this characteristic.
   * @param [in] uuid The UUID of the descriptor to find.
   * @return The Remote descriptor (if present) or null if not present.
   */
  NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUUID &uuid) {
      NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
  
      for(auto &it: m_descriptorVector) {
          if(it->getUUID() == uuid) {
              NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str());
              return it;
          }
      }
  
      size_t prev_size = m_descriptorVector.size();
      if(retrieveDescriptors(&uuid)) {
          if(m_descriptorVector.size() > prev_size) {
              return m_descriptorVector.back();
          }
  
          // If the request was successful but 16/32 bit uuid not found
          // try again with the 128 bit uuid.
          if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
             uuid.bitSize() == BLE_UUID_TYPE_32)
          {
              NimBLEUUID uuid128(uuid);
              uuid128.to128();
              return getDescriptor(uuid128);
          } else {
              // If the request was successful but the 128 bit uuid not found
              // try again with the 16 bit uuid.
              NimBLEUUID uuid16(uuid);
              uuid16.to16();
              // if the uuid was 128 bit but not of the BLE base type this check will fail
              if (uuid16.bitSize() == BLE_UUID_TYPE_16) {
                  return getDescriptor(uuid16);
              }
          }
      }
  
      NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found");
      return nullptr;
  } // getDescriptor
  
  
  /**
   * @brief Get a pointer to the vector of found descriptors.
   * @param [in] refresh If true the current descriptor vector will be cleared and\n
   * all descriptors for this characteristic retrieved from the peripheral.\n
   * If false the vector will be returned with the currently stored descriptors
   * of this characteristic.
   * @return A pointer to the vector of descriptors for this characteristic.
   */
  std::vector<NimBLERemoteDescriptor*>* NimBLERemoteCharacteristic::getDescriptors(bool refresh) {
      if(refresh) {
          deleteDescriptors();
  
          if (!retrieveDescriptors()) {
              NIMBLE_LOGE(LOG_TAG, "Error: Failed to get descriptors");
          }
          else{
              NIMBLE_LOGI(LOG_TAG, "Found %d descriptor(s)", m_descriptorVector.size());
          }
      }
      return &m_descriptorVector;
  } // getDescriptors
  
  
  /**
   * @brief Get iterator to the beginning of the vector of remote descriptor pointers.
   * @return An iterator to the beginning of the vector of remote descriptor pointers.
   */
  std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::begin() {
      return m_descriptorVector.begin();
  }
  
  
  /**
   * @brief Get iterator to the end of the vector of remote descriptor pointers.
   * @return An iterator to the end of the vector of remote descriptor pointers.
   */
  std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::end() {
      return m_descriptorVector.end();
  }
  
  
  /**
   * @brief Get the handle for this characteristic.
   * @return The handle for this characteristic.
   */
  uint16_t NimBLERemoteCharacteristic::getHandle() {
      return m_handle;
  } // getHandle
  
  /**
   * @brief Get the handle for this characteristics definition.
   * @return The handle for this characteristic definition.
   */
  uint16_t NimBLERemoteCharacteristic::getDefHandle() {
      return m_defHandle;
  } // getDefHandle
  
  
  /**
   * @brief Get the remote service associated with this characteristic.
   * @return The remote service associated with this characteristic.
   */
  NimBLERemoteService* NimBLERemoteCharacteristic::getRemoteService() {
      return m_pRemoteService;
  } // getRemoteService
  
  
  /**
   * @brief Get the UUID for this characteristic.
   * @return The UUID for this characteristic.
   */
  NimBLEUUID NimBLERemoteCharacteristic::getUUID() {
      return m_uuid;
  } // getUUID
  
  
  /**
   * @brief Get the value of the remote characteristic.
   * @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
   * @return The value of the remote characteristic.
   */
  NimBLEAttValue NimBLERemoteCharacteristic::getValue(time_t *timestamp) {
      if(timestamp != nullptr) {
          *timestamp = m_value.getTimeStamp();
      }
  
      return m_value;
  }
  
  
  /**
   * @brief Read an unsigned 16 bit value
   * @return The unsigned 16 bit value.
   * @deprecated Use readValue<uint16_t>().
   */
  uint16_t NimBLERemoteCharacteristic::readUInt16() {
      return readValue<uint16_t>();
  } // readUInt16
  
  
  /**
   * @brief Read an unsigned 32 bit value.
   * @return the unsigned 32 bit value.
   * @deprecated Use readValue<uint32_t>().
   */
  uint32_t NimBLERemoteCharacteristic::readUInt32() {
      return readValue<uint32_t>();
  } // readUInt32
  
  
  /**
   * @brief Read a byte value
   * @return The value as a byte
   * @deprecated Use readValue<uint8_t>().
   */
  uint8_t NimBLERemoteCharacteristic::readUInt8() {
      return readValue<uint8_t>();
  } // readUInt8
  
  
  /**
   * @brief Read a float value.
   * @return the float value.
   */
  float NimBLERemoteCharacteristic::readFloat() {
  	return readValue<float>();
  } // readFloat
  
  
  /**
   * @brief Read the value of the remote characteristic.
   * @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
   * @return The value of the remote characteristic.
   */
  NimBLEAttValue NimBLERemoteCharacteristic::readValue(time_t *timestamp) {
      NIMBLE_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x",
                           getUUID().toString().c_str(), getHandle(), getHandle());
  
      NimBLEClient* pClient = getRemoteService()->getClient();
      NimBLEAttValue value;
  
      if (!pClient->isConnected()) {
          NIMBLE_LOGE(LOG_TAG, "Disconnected");
          return value;
      }
  
      int rc = 0;
      int retryCount = 1;
      TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
      ble_task_data_t taskData = {this, cur_task, 0, &value};
  
      do {
          rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
                                   NimBLERemoteCharacteristic::onReadCB,
                                   &taskData);
          if (rc != 0) {
              NIMBLE_LOGE(LOG_TAG, "Error: Failed to read characteristic; rc=%d, %s",
                                    rc, NimBLEUtils::returnCodeToString(rc));
              return value;
          }
  
  #ifdef ulTaskNotifyValueClear
          // Clear the task notification value to ensure we block
          ulTaskNotifyValueClear(cur_task, ULONG_MAX);
  #endif
          ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
          rc = taskData.rc;
  
          switch(rc){
              case 0:
              case BLE_HS_EDONE:
                  rc = 0;
                  break;
              // Characteristic is not long-readable, return with what we have.
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
                  NIMBLE_LOGI(LOG_TAG, "Attribute not long");
                  rc = 0;
                  break;
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
                  if (retryCount && pClient->secureConnection())
                      break;
              /* Else falls through. */
              default:
                  NIMBLE_LOGE(LOG_TAG, "<< readValue rc=%d", rc);
                  return value;
          }
      } while(rc != 0 && retryCount--);
  
      value.setTimeStamp();
      m_value = value;
      if(timestamp != nullptr) {
          *timestamp = value.getTimeStamp();
      }
  
      NIMBLE_LOGD(LOG_TAG, "<< readValue length: %d rc=%d", value.length(), rc);
      return value;
  } // readValue
  
  
  /**
   * @brief Callback for characteristic read operation.
   * @return success == 0 or error code.
   */
  int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
                  const struct ble_gatt_error *error,
                  struct ble_gatt_attr *attr, void *arg)
  {
      ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
      NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
      uint16_t conn_id = characteristic->getRemoteService()->getClient()->getConnId();
  
      if(conn_id != conn_handle) {
          return 0;
      }
  
      NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
  
      NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf;
      int rc = error->status;
  
      if(rc == 0) {
          if(attr) {
              uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
              if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
                  rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
              } else {
                  NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
                  valBuf->append(attr->om->om_data, data_len);
                  return 0;
              }
          }
      }
  
      pTaskData->rc = rc;
      xTaskNotifyGive(pTaskData->task);
  
      return rc;
  }
  
  
  /**
   * @brief Subscribe or unsubscribe for notifications or indications.
   * @param [in] val 0x00 to unsubscribe, 0x01 for notifications, 0x02 for indications.
   * @param [in] notifyCallback A callback to be invoked for a notification.
   * @param [in] response If write response required set this to true.
   * If NULL is provided then no callback is performed.
   * @return false if writing to the descriptor failed.
   */
  bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) {
      NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val);
  
      m_notifyCallback = notifyCallback;
  
      NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902));
      if(desc == nullptr) {
          NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found");
          return true;
      }
  
      NIMBLE_LOGD(LOG_TAG, "<< setNotify()");
  
      return desc->writeValue((uint8_t *)&val, 2, response);
  } // setNotify
  
  
  /**
   * @brief Subscribe for notifications or indications.
   * @param [in] notifications If true, subscribe for notifications, false subscribe for indications.
   * @param [in] notifyCallback A callback to be invoked for a notification.
   * @param [in] response If true, require a write response from the descriptor write operation.
   * If NULL is provided then no callback is performed.
   * @return false if writing to the descriptor failed.
   */
  bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) {
      if(notifications) {
          return setNotify(0x01, notifyCallback, response);
      } else {
          return setNotify(0x02, notifyCallback, response);
      }
  } // subscribe
  
  
  /**
   * @brief Unsubscribe for notifications or indications.
   * @param [in] response bool if true, require a write response from the descriptor write operation.
   * @return false if writing to the descriptor failed.
   */
  bool NimBLERemoteCharacteristic::unsubscribe(bool response) {
      return setNotify(0x00, nullptr, response);
  } // unsubscribe
  
  
   /**
   * @brief backward-compatibility method for subscribe/unsubscribe notifications/indications
   * @param [in] notifyCallback A callback to be invoked for a notification. If NULL is provided then we
   * will unregister for notifications.
   * @param [in] notifications If true, register for notifications, false register for indications.
   * @param [in] response If true, require a write response from the descriptor write operation.
   * @return true if successful.
   * @deprecated Use subscribe() / unsubscribe() instead.
   */
  bool NimBLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, bool notifications, bool response) {
      bool success;
      if(notifyCallback != nullptr) {
          success = subscribe(notifications, notifyCallback, response);
      } else {
          success = unsubscribe(response);
      }
      return success;
  } // registerForNotify
  
  
  /**
   * @brief Delete the descriptors in the descriptor vector.
   * @details We maintain a vector called m_descriptorVector that contains pointers to NimBLERemoteDescriptors
   * object references. Since we allocated these in this class, we are also responsible for deleting
   * them. This method does just that.
   */
  void NimBLERemoteCharacteristic::deleteDescriptors() {
      NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptors");
  
      for(auto &it: m_descriptorVector) {
          delete it;
      }
      m_descriptorVector.clear();
      NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptors");
  } // deleteDescriptors
  
  
  /**
   * @brief Delete descriptor by UUID
   * @param [in] uuid The UUID of the descriptor to be deleted.
   * @return Number of descriptors left in the vector.
   */
  size_t NimBLERemoteCharacteristic::deleteDescriptor(const NimBLEUUID &uuid) {
      NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptor");
  
      for(auto it = m_descriptorVector.begin(); it != m_descriptorVector.end(); ++it) {
          if((*it)->getUUID() == uuid) {
              delete *it;
              m_descriptorVector.erase(it);
              break;
          }
      }
  
      NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptor");
  
      return m_descriptorVector.size();
  } // deleteDescriptor
  
  
  /**
   * @brief Convert a NimBLERemoteCharacteristic to a string representation;
   * @return a String representation.
   */
  std::string NimBLERemoteCharacteristic::toString() {
      std::string res = "Characteristic: uuid: " + m_uuid.toString();
      char val[6];
      res += ", handle: ";
      snprintf(val, sizeof(val), "%d", getHandle());
      res += val;
      res += " 0x";
      snprintf(val, sizeof(val), "%04x", getHandle());
      res += val;
      res += ", props: ";
      res += " 0x";
      snprintf(val, sizeof(val), "%02x", m_charProp);
      res += val;
  
      for(auto &it: m_descriptorVector) {
          res += "\n" + it->toString();
      }
  
      return res;
  } // toString
  
  
  /**
   * @brief Write a new value to the remote characteristic from a std::vector<uint8_t>.
   * @param [in] vec A std::vector<uint8_t> value to write to the remote characteristic.
   * @param [in] response Whether we require a response from the write.
   * @return false if not connected or otherwise cannot perform write.
   */
  bool NimBLERemoteCharacteristic::writeValue(const std::vector<uint8_t>& vec, bool response) {
      return writeValue((uint8_t*)&vec[0], vec.size(), response);
  } // writeValue
  
  
  /**
   * @brief Write a new value to the remote characteristic from a const char*.
   * @param [in] char_s A character string to write to the remote characteristic.
   * @param [in] response Whether we require a response from the write.
   * @return false if not connected or otherwise cannot perform write.
   */
  bool NimBLERemoteCharacteristic::writeValue(const char* char_s, bool response) {
      return writeValue((uint8_t*)char_s, strlen(char_s), response);
  } // writeValue
  
  
  /**
   * @brief Write a new value to the remote characteristic from a data buffer.
   * @param [in] data A pointer to a data buffer.
   * @param [in] length The length of the data in the data buffer.
   * @param [in] response Whether we require a response from the write.
   * @return false if not connected or otherwise cannot perform write.
   */
  bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, bool response) {
  
      NIMBLE_LOGD(LOG_TAG, ">> writeValue(), length: %d", length);
  
      NimBLEClient* pClient = getRemoteService()->getClient();
  
      if (!pClient->isConnected()) {
          NIMBLE_LOGE(LOG_TAG, "Disconnected");
          return false;
      }
  
      int rc = 0;
      int retryCount = 1;
      uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3;
  
      // Check if the data length is longer than we can write in one connection event.
      // If so we must do a long write which requires a response.
      if(length <= mtu && !response) {
          rc =  ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
          return (rc==0);
      }
  
      TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
      ble_task_data_t taskData = {this, cur_task, 0, nullptr};
  
      do {
          if(length > mtu) {
              NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
              os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
              rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
                                        NimBLERemoteCharacteristic::onWriteCB,
                                        &taskData);
          } else {
              rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
                                        data, length,
                                        NimBLERemoteCharacteristic::onWriteCB,
                                        &taskData);
          }
          if (rc != 0) {
              NIMBLE_LOGE(LOG_TAG, "Error: Failed to write characteristic; rc=%d", rc);
              return false;
          }
  
  #ifdef ulTaskNotifyValueClear
          // Clear the task notification value to ensure we block
          ulTaskNotifyValueClear(cur_task, ULONG_MAX);
  #endif
          ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
          rc = taskData.rc;
  
          switch(rc){
              case 0:
              case BLE_HS_EDONE:
                  rc = 0;
                  break;
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
                  NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
                  retryCount++;
                  length = mtu;
                  break;
  
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
              case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
                  if (retryCount && pClient->secureConnection())
                      break;
              /* Else falls through. */
              default:
                  NIMBLE_LOGE(LOG_TAG, "<< writeValue, rc: %d", rc);
                  return false;
          }
      } while(rc != 0 && retryCount--);
  
      NIMBLE_LOGD(LOG_TAG, "<< writeValue, rc: %d", rc);
      return (rc == 0);
  } // writeValue
  
  
  /**
   * @brief Callback for characteristic write operation.
   * @return success == 0 or error code.
   */
  int NimBLERemoteCharacteristic::onWriteCB(uint16_t conn_handle,
                  const struct ble_gatt_error *error,
                  struct ble_gatt_attr *attr, void *arg)
  {
      ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
      NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
  
      if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
          return 0;
      }
  
      NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
  
      pTaskData->rc = error->status;
      xTaskNotifyGive(pTaskData->task);
  
      return 0;
  }
  
  #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
  