/******************************************************************************
 *
 *  Copyright 1999-2012 Broadcom Corporation
 *
 *  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.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  this file contains GATT interface functions
 *
 ******************************************************************************/
#define LOG_TAG "gatt_api"

#include "stack/include/gatt_api.h"

#include <base/strings/string_number_conversions.h>
#include <bluetooth/log.h>
#include <com_android_bluetooth_flags.h>

#include <string>

#include "internal_include/bt_target.h"
#include "internal_include/stack_config.h"
#include "os/system_properties.h"
#include "osi/include/allocator.h"
#include "stack/arbiter/acl_arbiter.h"
#include "stack/btm/btm_dev.h"
#include "stack/connection_manager/connection_manager.h"
#include "stack/gatt/gatt_int.h"
#include "stack/include/ais_api.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/bt_uuid16.h"
#include "stack/include/btm_client_interface.h"
#include "stack/include/l2cap_acl_interface.h"
#include "stack/include/l2cap_interface.h"
#include "stack/include/l2cdefs.h"
#include "stack/include/sdp_api.h"
#include "types/bluetooth/uuid.h"
#include "types/bt_transport.h"
#include "types/raw_address.h"

using namespace bluetooth::legacy::stack::sdp;
using namespace bluetooth;

using bluetooth::Uuid;

/**
 * Add a service handle range to the list in descending order of the start
 * handle. Return reference to the newly added element.
 **/
static tGATT_HDL_LIST_ELEM& gatt_add_an_item_to_list(uint16_t s_handle) {
  auto lst_ptr = gatt_cb.hdl_list_info;
  auto it = lst_ptr->begin();
  for (; it != lst_ptr->end(); it++) {
    if (s_handle > it->asgn_range.s_handle) {
      break;
    }
  }

  auto rit = lst_ptr->emplace(it);
  return *rit;
}

static tGATT_IF GATT_Register_Dynamic(const Uuid& app_uuid128, const std::string& name,
                                      tGATT_CBACK* p_cb_info, bool eatt_support);

/*****************************************************************************
 *
 *                  GATT SERVER API
 *
 *****************************************************************************/
/*******************************************************************************
 *
 * Function         GATTS_NVRegister
 *
 * Description      Application manager calls this function to register for
 *                  NV save callback function.  There can be one and only one
 *                  NV save callback function.
 *
 * Parameter        p_cb_info : callback information
 *
 * Returns          true if registered OK, else false
 *
 ******************************************************************************/
bool GATTS_NVRegister(tGATT_APPL_INFO* p_cb_info) {
  bool status = false;
  if (p_cb_info) {
    gatt_cb.cb_info = *p_cb_info;
    status = true;
    gatt_init_srv_chg();
  }

  return status;
}

static uint16_t compute_service_size(btgatt_db_element_t* service, int count) {
  int db_size = 0;
  btgatt_db_element_t* el = service;

  for (int i = 0; i < count; i++, el++) {
    if (el->type == BTGATT_DB_PRIMARY_SERVICE || el->type == BTGATT_DB_SECONDARY_SERVICE ||
        el->type == BTGATT_DB_DESCRIPTOR || el->type == BTGATT_DB_INCLUDED_SERVICE) {
      db_size += 1;
    } else if (el->type == BTGATT_DB_CHARACTERISTIC) {
      db_size += 2;

      // if present, Characteristic Extended Properties takes one handle
      if (el->properties & GATT_CHAR_PROP_BIT_EXT_PROP) {
        db_size++;
      }
    } else {
      log::error("Unknown element type: {}", el->type);
    }
  }

  return db_size;
}

static bool is_gatt_attr_type(const Uuid& uuid) {
  if (uuid == Uuid::From16Bit(GATT_UUID_PRI_SERVICE) ||
      uuid == Uuid::From16Bit(GATT_UUID_SEC_SERVICE) ||
      uuid == Uuid::From16Bit(GATT_UUID_INCLUDE_SERVICE) ||
      uuid == Uuid::From16Bit(GATT_UUID_CHAR_DECLARE)) {
    return true;
  }
  return false;
}

/** Update the the last service info for the service list info */
static void gatt_update_last_srv_info() {
  gatt_cb.last_service_handle = 0;

  for (tGATT_SRV_LIST_ELEM& el : *gatt_cb.srv_list_info) {
    gatt_cb.last_service_handle = el.s_hdl;
  }
}

/** Update database hash and client status */
static void gatt_update_for_database_change() {
  gatt_cb.database_hash = gatts_calculate_database_hash(gatt_cb.srv_list_info);

  uint8_t i = 0;
  for (i = 0; i < GATT_MAX_PHY_CHANNEL; i++) {
    tGATT_TCB& tcb = gatt_cb.tcb[i];
    if (tcb.in_use) {
      gatt_sr_update_cl_status(tcb, /* chg_aware= */ false);
    }
  }
}

/*******************************************************************************
 *
 * Function         GATTS_AddService
 *
 * Description      This function is called to add GATT service.
 *
 * Parameter        gatt_if : application if
 *                  service : pseudo-representation of service and it's content
 *                  count   : size of service
 *
 * Returns          on success GATT_SERVICE_STARTED is returned, and
 *                  attribute_handle field inside service elements are filled.
 *                  on error error status is returned.
 *
 ******************************************************************************/
tGATT_STATUS GATTS_AddService(tGATT_IF gatt_if, btgatt_db_element_t* service, int count) {
  uint16_t s_hdl = 0;
  bool save_hdl = false;
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);

  bool is_pri = (service->type == BTGATT_DB_PRIMARY_SERVICE) ? true : false;
  Uuid svc_uuid = service->uuid;

  log::info("");

  if (!p_reg) {
    log::error("Invalid gatt_if={}", gatt_if);
    return GATT_INTERNAL_ERROR;
  }

  uint16_t num_handles = compute_service_size(service, count);

  if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) {
    s_hdl = gatt_cb.hdl_cfg.gatt_start_hdl;
  } else if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) {
    s_hdl = gatt_cb.hdl_cfg.gap_start_hdl;
  } else if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_GMCS_SERVER)) {
    s_hdl = gatt_cb.hdl_cfg.gmcs_start_hdl;
  } else if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_GTBS_SERVER)) {
    s_hdl = gatt_cb.hdl_cfg.gtbs_start_hdl;
  } else if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_TMAS_SERVER)) {
    s_hdl = gatt_cb.hdl_cfg.tmas_start_hdl;
  } else {
    if (!gatt_cb.hdl_list_info->empty()) {
      s_hdl = gatt_cb.hdl_list_info->front().asgn_range.e_handle + 1;
    }

    if (s_hdl < gatt_cb.hdl_cfg.app_start_hdl) {
      s_hdl = gatt_cb.hdl_cfg.app_start_hdl;
    }

    save_hdl = true;
  }

  /* check for space */
  if (num_handles > (0xFFFF - s_hdl + 1)) {
    log::error("no handles, s_hdl={} needed={}", s_hdl, num_handles);
    return GATT_INTERNAL_ERROR;
  }

  tGATT_HDL_LIST_ELEM& list = gatt_add_an_item_to_list(s_hdl);
  list.asgn_range.app_uuid128 = p_reg->app_uuid128;
  list.asgn_range.svc_uuid = svc_uuid;
  list.asgn_range.s_handle = s_hdl;
  list.asgn_range.e_handle = s_hdl + num_handles - 1;
  list.asgn_range.is_primary = is_pri;

  if (save_hdl) {
    if (gatt_cb.cb_info.p_nv_save_callback) {
      (*gatt_cb.cb_info.p_nv_save_callback)(true, &list.asgn_range);
    }
  }

  gatts_init_service_db(list.svc_db, svc_uuid, is_pri, s_hdl, num_handles);

  log::verbose("handles needed={}, s_hdl=0x{:x}, e_hdl=0x{:x}, uuid={}, is_primary={}", num_handles,
               list.asgn_range.s_handle, list.asgn_range.e_handle, list.asgn_range.svc_uuid,
               list.asgn_range.is_primary);

  service->attribute_handle = s_hdl;

  btgatt_db_element_t* el = service + 1;
  for (int i = 0; i < count - 1; i++, el++) {
    const Uuid& uuid = el->uuid;

    if (el->type == BTGATT_DB_CHARACTERISTIC) {
      /* data validity checking */
      if (((el->properties & GATT_CHAR_PROP_BIT_AUTH) &&
           !(el->permissions & GATT_WRITE_SIGNED_PERM)) ||
          ((el->permissions & GATT_WRITE_SIGNED_PERM) &&
           !(el->properties & GATT_CHAR_PROP_BIT_AUTH))) {
        log::verbose("Invalid configuration property=0x{:x}, perm=0x{:x}", el->properties,
                     el->permissions);
        return GATT_INTERNAL_ERROR;
      }

      if (is_gatt_attr_type(uuid)) {
        log::error(
                "attempt to add characteristic with UUID equal to GATT Attribute "
                "Type {}",
                uuid);
        return GATT_INTERNAL_ERROR;
      }

      el->attribute_handle =
              gatts_add_characteristic(list.svc_db, el->permissions, el->properties, uuid);

      // add characteristic extended properties descriptor if needed
      if (el->properties & GATT_CHAR_PROP_BIT_EXT_PROP) {
        gatts_add_char_ext_prop_descr(list.svc_db, el->extended_properties);
      }

    } else if (el->type == BTGATT_DB_DESCRIPTOR) {
      if (is_gatt_attr_type(uuid)) {
        log::error(
                "attempt to add descriptor with UUID equal to GATT Attribute Type "
                "{}",
                uuid);
        return GATT_INTERNAL_ERROR;
      }

      el->attribute_handle = gatts_add_char_descr(list.svc_db, el->permissions, uuid);
    } else if (el->type == BTGATT_DB_INCLUDED_SERVICE) {
      tGATT_HDL_LIST_ELEM* p_incl_decl;
      p_incl_decl = gatt_find_hdl_buffer_by_handle(el->attribute_handle);
      if (p_incl_decl == nullptr) {
        log::verbose("Included Service not created");
        return GATT_INTERNAL_ERROR;
      }

      el->attribute_handle = gatts_add_included_service(
              list.svc_db, p_incl_decl->asgn_range.s_handle, p_incl_decl->asgn_range.e_handle,
              p_incl_decl->asgn_range.svc_uuid);
    }
  }

  log::info("service parsed correctly, now starting");

  /*this is a new application service start */

  // find a place for this service in the list
  auto lst_ptr = gatt_cb.srv_list_info;
  auto it = lst_ptr->begin();
  for (; it != lst_ptr->end(); it++) {
    if (list.asgn_range.s_handle < it->s_hdl) {
      break;
    }
  }
  auto rit = lst_ptr->emplace(it);

  tGATT_SRV_LIST_ELEM& elem = *rit;
  elem.gatt_if = gatt_if;
  elem.s_hdl = list.asgn_range.s_handle;
  elem.e_hdl = list.asgn_range.e_handle;
  elem.p_db = &list.svc_db;
  elem.is_primary = list.asgn_range.is_primary;

  elem.app_uuid = list.asgn_range.app_uuid128;
  elem.type = list.asgn_range.is_primary ? GATT_UUID_PRI_SERVICE : GATT_UUID_SEC_SERVICE;

  if (elem.type == GATT_UUID_PRI_SERVICE && gatt_cb.over_br_enabled) {
    Uuid* p_uuid = gatts_get_service_uuid(elem.p_db);
    if (*p_uuid != Uuid::From16Bit(UUID_SERVCLASS_GMCS_SERVER) &&
        *p_uuid != Uuid::From16Bit(UUID_SERVCLASS_GTBS_SERVER)) {
      if ((com::android::bluetooth::flags::channel_sounding_in_stack() &&
           *p_uuid == Uuid::From16Bit(UUID_SERVCLASS_RAS)) ||
          (com::android::bluetooth::flags::android_os_identifier() &&
           *p_uuid == ANDROID_INFORMATION_SERVICE_UUID)) {
        elem.sdp_handle = 0;
      } else {
        elem.sdp_handle = gatt_add_sdp_record(*p_uuid, elem.s_hdl, elem.e_hdl);
      }
    } else {
      elem.sdp_handle = 0;
    }
  } else {
    elem.sdp_handle = 0;
  }

  gatt_update_last_srv_info();

  log::verbose("allocated el s_hdl=0x{:x}, e_hdl=0x{:x}, type=0x{:x}, sdp_hdl=0x{:x}", elem.s_hdl,
               elem.e_hdl, elem.type, elem.sdp_handle);

  gatt_update_for_database_change();
  gatt_proc_srv_chg();

  return GATT_SERVICE_STARTED;
}

static bool is_active_service(const Uuid& app_uuid128, Uuid* p_svc_uuid, uint16_t start_handle) {
  for (auto& info : *gatt_cb.srv_list_info) {
    Uuid* p_this_uuid = gatts_get_service_uuid(info.p_db);

    if (p_this_uuid && app_uuid128 == info.app_uuid && *p_svc_uuid == *p_this_uuid &&
        (start_handle == info.s_hdl)) {
      log::error("Active Service Found: {}", *p_svc_uuid);
      return true;
    }
  }
  return false;
}

/*******************************************************************************
 *
 * Function         GATTS_DeleteService
 *
 * Description      This function is called to delete a service.
 *
 * Parameter        gatt_if       : application interface
 *                  p_svc_uuid    : service UUID
 *                  start_handle  : start handle of the service
 *
 * Returns          true if the operation succeeded, false if the handle block
 *                  was not found.
 *
 ******************************************************************************/
bool GATTS_DeleteService(tGATT_IF gatt_if, Uuid* p_svc_uuid, uint16_t svc_inst) {
  log::verbose("");

  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  if (p_reg == NULL) {
    log::error("Application not found");
    return false;
  }

  auto it = gatt_find_hdl_buffer_by_app_id(p_reg->app_uuid128, p_svc_uuid, svc_inst);
  if (it == gatt_cb.hdl_list_info->end()) {
    log::error("No Service found");
    return false;
  }

  if (is_active_service(p_reg->app_uuid128, p_svc_uuid, svc_inst)) {
    GATTS_StopService(it->asgn_range.s_handle);
  }

  gatt_update_for_database_change();
  gatt_proc_srv_chg();

  log::verbose("released handles s_hdl=0x{:x}, e_hdl=0x{:x}", it->asgn_range.s_handle,
               it->asgn_range.e_handle);

  if ((it->asgn_range.s_handle >= gatt_cb.hdl_cfg.app_start_hdl) &&
      gatt_cb.cb_info.p_nv_save_callback) {
    (*gatt_cb.cb_info.p_nv_save_callback)(false, &it->asgn_range);
  }

  gatt_cb.hdl_list_info->erase(it);
  return true;
}

/*******************************************************************************
 *
 * Function         GATTS_StopService
 *
 * Description      This function is called to stop a service
 *
 * Parameter         service_handle : this is the start handle of a service
 *
 * Returns          None.
 *
 ******************************************************************************/
void GATTS_StopService(uint16_t service_handle) {
  log::info("service = 0x{:x}", service_handle);

  auto it = gatt_sr_find_i_rcb_by_handle(service_handle);
  if (it == gatt_cb.srv_list_info->end()) {
    log::error("service_handle=0x{:x} is not in use", service_handle);
    return;
  }

  if (it->sdp_handle) {
    if (!get_legacy_stack_sdp_api()->handle.SDP_DeleteRecord(it->sdp_handle)) {
      log::warn("Unable to delete record handle:{}", it->sdp_handle);
    }
  }

  gatt_cb.srv_list_info->erase(it);
  gatt_update_last_srv_info();
}
/*******************************************************************************
 *
 * Function         GATTs_HandleValueIndication
 *
 * Description      This function sends a handle value indication to a client.
 *
 * Parameter        conn_id: connection identifier.
 *                  attr_handle: Attribute handle of this handle value
 *                               indication.
 *                  val_len: Length of the indicated attribute value.
 *                  p_val: Pointer to the indicated attribute value data.
 *
 * Returns          GATT_SUCCESS if successfully sent or queued; otherwise error
 *                  code.
 *
 ******************************************************************************/
tGATT_STATUS GATTS_HandleValueIndication(tCONN_ID conn_id, uint16_t attr_handle, uint16_t val_len,
                                         uint8_t* p_val) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);

  log::verbose("");
  if ((p_reg == NULL) || (p_tcb == NULL)) {
    log::error("Unknown  conn_id=0x{:x}", conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  if (!GATT_HANDLE_IS_VALID(attr_handle)) {
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_VALUE indication;
  indication.conn_id = conn_id;
  indication.handle = attr_handle;
  indication.len = val_len;
  memcpy(indication.value, p_val, val_len);
  indication.auth_req = GATT_AUTH_REQ_NONE;

  uint16_t* indicate_handle_p = NULL;
  uint16_t cid;

  if (!gatt_tcb_get_cid_available_for_indication(p_tcb, p_reg->eatt_support, &indicate_handle_p,
                                                 &cid)) {
    log::verbose("Add a pending indication");
    gatt_add_pending_ind(p_tcb, &indication);
    return GATT_SUCCESS;
  }

  tGATT_SR_MSG gatt_sr_msg;
  gatt_sr_msg.attr_value = indication;

  uint16_t payload_size = gatt_tcb_get_payload_size(*p_tcb, cid);
  BT_HDR* p_msg = attp_build_sr_msg(*p_tcb, GATT_HANDLE_VALUE_IND, &gatt_sr_msg, payload_size);
  if (!p_msg) {
    return GATT_NO_RESOURCES;
  }

  tGATT_STATUS cmd_status = attp_send_sr_msg(*p_tcb, cid, p_msg);
  if (cmd_status == GATT_SUCCESS || cmd_status == GATT_CONGESTED) {
    *indicate_handle_p = indication.handle;
    gatt_start_conf_timer(p_tcb, cid);
  }
  return cmd_status;
}

#if (GATT_UPPER_TESTER_MULT_VARIABLE_LENGTH_NOTIF == TRUE)
static tGATT_STATUS GATTS_HandleMultipleValueNotification(
        tGATT_TCB* p_tcb, std::vector<tGATT_VALUE> gatt_notif_vector) {
  log::info("");

  uint16_t cid = gatt_tcb_get_att_cid(*p_tcb, true /* eatt support */);
  uint16_t payload_size = gatt_tcb_get_payload_size(*p_tcb, cid);

  /* TODO Handle too big packet size here. Not needed now for testing. */
  /* Just build the message. */
  BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);

  uint8_t* p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
  UINT8_TO_STREAM(p, GATT_HANDLE_MULTI_VALUE_NOTIF);
  p_buf->offset = L2CAP_MIN_OFFSET;
  p_buf->len = 1;
  for (auto notif : gatt_notif_vector) {
    log::info("Adding handle: 0x{:04x}, val len {}", notif.handle, notif.len);
    UINT16_TO_STREAM(p, notif.handle);
    p_buf->len += 2;
    UINT16_TO_STREAM(p, notif.len);
    p_buf->len += 2;
    ARRAY_TO_STREAM(p, notif.value, notif.len);
    p_buf->len += notif.len;
  }

  log::info("Total len: {}", p_buf->len);

  return attp_send_sr_msg(*p_tcb, cid, p_buf);
}
#endif
/*******************************************************************************
 *
 * Function         GATTS_HandleValueNotification
 *
 * Description      This function sends a handle value notification to a client.
 *
 * Parameter        conn_id: connection identifier.
 *                  attr_handle: Attribute handle of this handle value
 *                               indication.
 *                  val_len: Length of the indicated attribute value.
 *                  p_val: Pointer to the indicated attribute value data.
 *
 * Returns          GATT_SUCCESS if successfully sent; otherwise error code.
 *
 ******************************************************************************/
tGATT_STATUS GATTS_HandleValueNotification(tCONN_ID conn_id, uint16_t attr_handle, uint16_t val_len,
                                           uint8_t* p_val) {
  tGATT_VALUE notif;
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
#if (GATT_UPPER_TESTER_MULT_VARIABLE_LENGTH_NOTIF == TRUE)
  static uint8_t cached_tcb_idx = 0xFF;
  static std::vector<tGATT_VALUE> gatt_notif_vector(2);
  tGATT_VALUE* p_gatt_notif;
#endif

  log::verbose("");

  if ((p_reg == NULL) || (p_tcb == NULL)) {
    log::error("Unknown  conn_id: {}", conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  if (!GATT_HANDLE_IS_VALID(attr_handle)) {
    return GATT_ILLEGAL_PARAMETER;
  }

#if (GATT_UPPER_TESTER_MULT_VARIABLE_LENGTH_NOTIF == TRUE)
  /* Upper tester for Multiple Value length notifications */
  if (stack_config_get_interface()->get_pts_force_eatt_for_notifications() &&
      gatt_sr_is_cl_multi_variable_len_notif_supported(*p_tcb)) {
    if (cached_tcb_idx == 0xFF) {
      log::info("Storing first notification");
      p_gatt_notif = &gatt_notif_vector[0];

      p_gatt_notif->handle = attr_handle;
      p_gatt_notif->len = val_len;
      std::copy(p_val, p_val + val_len, p_gatt_notif->value);

      notif.auth_req = GATT_AUTH_REQ_NONE;

      cached_tcb_idx = tcb_idx;
      return GATT_SUCCESS;
    }

    if (cached_tcb_idx == tcb_idx) {
      log::info("Storing second notification");
      cached_tcb_idx = 0xFF;
      p_gatt_notif = &gatt_notif_vector[1];

      p_gatt_notif->handle = attr_handle;
      p_gatt_notif->len = val_len;
      std::copy(p_val, p_val + val_len, p_gatt_notif->value);

      notif.auth_req = GATT_AUTH_REQ_NONE;

      return GATTS_HandleMultipleValueNotification(p_tcb, gatt_notif_vector);
    }

    log::error("PTS Mode: Invalid tcb_idx: {}, cached_tcb_idx: {}", tcb_idx, cached_tcb_idx);
  }
#endif

  memset(&notif, 0, sizeof(notif));
  notif.handle = attr_handle;
  notif.len = val_len;
  memcpy(notif.value, p_val, val_len);
  notif.auth_req = GATT_AUTH_REQ_NONE;

  tGATT_STATUS cmd_sent;
  tGATT_SR_MSG gatt_sr_msg;
  gatt_sr_msg.attr_value = notif;

  uint16_t cid = gatt_tcb_get_att_cid(*p_tcb, p_reg->eatt_support);
  uint16_t payload_size = gatt_tcb_get_payload_size(*p_tcb, cid);
  BT_HDR* p_buf = attp_build_sr_msg(*p_tcb, GATT_HANDLE_VALUE_NOTIF, &gatt_sr_msg, payload_size);

  if (p_buf != NULL) {
    cmd_sent = attp_send_sr_msg(*p_tcb, cid, p_buf);
  } else {
    cmd_sent = GATT_NO_RESOURCES;
  }
  return cmd_sent;
}

/*******************************************************************************
 *
 * Function         GATTS_SendRsp
 *
 * Description      This function sends the server response to client.
 *
 * Parameter        conn_id: connection identifier.
 *                  trans_id: transaction id
 *                  status: response status
 *                  p_msg: pointer to message parameters structure.
 *
 * Returns          GATT_SUCCESS if successfully sent; otherwise error code.
 *
 ******************************************************************************/
tGATT_STATUS GATTS_SendRsp(tCONN_ID conn_id, uint32_t trans_id, tGATT_STATUS status,
                           tGATTS_RSP* p_msg) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);

  log::verbose("conn_id=0x{:x}, trans_id=0x{:x}, status=0x{:x}", conn_id, trans_id,
               static_cast<uint8_t>(status));

  if ((p_reg == NULL) || (p_tcb == NULL)) {
    log::error("Unknown  conn_id=0x{:x}", conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_SR_CMD* sr_res_p = gatt_sr_get_cmd_by_trans_id(p_tcb, trans_id);

  if (!sr_res_p) {
    log::error("conn_id=0x{:x} waiting for other op_code", conn_id);
    return GATT_WRONG_STATE;
  }

  /* Process App response */
  return gatt_sr_process_app_rsp(*p_tcb, gatt_if, trans_id, sr_res_p->op_code, status, p_msg,
                                 sr_res_p);
}

/******************************************************************************/
/* GATT Profile Srvr Functions */
/******************************************************************************/

/******************************************************************************/
/*                                                                            */
/*                  GATT CLIENT APIs                                          */
/*                                                                            */
/******************************************************************************/

/*******************************************************************************
 *
 * Function         GATTC_ConfigureMTU
 *
 * Description      This function is called to configure the ATT MTU size.
 *
 * Parameters       conn_id: connection identifier.
 *                  mtu    - attribute MTU size..
 *
 * Returns          GATT_SUCCESS if command started successfully.
 *
 ******************************************************************************/
tGATT_STATUS GATTC_ConfigureMTU(tCONN_ID conn_id, uint16_t mtu) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);

  if ((p_tcb == NULL) || (p_reg == NULL) || (mtu < GATT_DEF_BLE_MTU_SIZE) ||
      (mtu > GATT_MAX_MTU_SIZE)) {
    log::warn(
            "Unable to configure ATT mtu size illegal parameter conn_id:{} mtu:{} "
            "tcb:{} reg:{}",
            conn_id, mtu, (p_tcb == nullptr) ? "BAD" : "ok", (p_reg == nullptr) ? "BAD" : "ok");
    return GATT_ILLEGAL_PARAMETER;
  }

  /* Validate that the link is BLE, not BR/EDR */
  if (p_tcb->transport != BT_TRANSPORT_LE) {
    return GATT_REQ_NOT_SUPPORTED;
  }

  tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
  if (!p_clcb) {
    log::warn("Unable to allocate connection link control block");
    return GATT_NO_RESOURCES;
  }

  /* For this request only ATT CID is valid */
  p_clcb->cid = L2CAP_ATT_CID;
  p_clcb->operation = GATTC_OPTYPE_CONFIG;
  tGATT_CL_MSG gatt_cl_msg;

  bluetooth::shim::arbiter::GetArbiter().OnOutgoingMtuReq(tcb_idx);

  /* Since GATT MTU Exchange can be done only once, and it is impossible to
   * predict what MTU will be requested by other applications, let's use
   * default MTU in the request. */
  gatt_cl_msg.mtu = gatt_get_local_mtu();

  log::info("Configuring ATT mtu size conn_id:{} mtu:{} user mtu {}", conn_id, gatt_cl_msg.mtu,
            mtu);

  auto result = attp_send_cl_msg(*p_clcb->p_tcb, p_clcb, GATT_REQ_MTU, &gatt_cl_msg);
  if (result == GATT_SUCCESS) {
    p_clcb->p_tcb->pending_user_mtu_exchange_value = mtu;
  }
  return result;
}

/******************************************************************************
 *
 * Function         GATTC_TryMtuRequest
 *
 * Description      This function shall be called before calling
 *                  GATTC_ConfigureMTU in order to check if operation is
 *                  available to do.
 *
 * Parameters        remote_bda : peer device address. (input)
 *                   transport  : physical transport of the GATT connection
 *                                 (BR/EDR or LE) (input)
 *                   conn_id    : connection id  (input)
 *                   current_mtu: current mtu on the link (output)
 *
 * Returns          tGATTC_TryMtuRequestResult:
 *                  - MTU_EXCHANGE_NOT_DONE_YET: There was no MTU Exchange
 *                      procedure on the link. User can call GATTC_ConfigureMTU
 *                      now.
 *                  - MTU_EXCHANGE_NOT_ALLOWED : Not allowed for BR/EDR or if
 *                      link does not exist
 *                  - MTU_EXCHANGE_ALREADY_DONE: MTU Exchange is done. MTU
 *                      should be taken from current_mtu
 *                  - MTU_EXCHANGE_IN_PROGRESS : Other use is doing MTU
 *                      Exchange. Conn_id is stored for result.
 *
 ******************************************************************************/
tGATTC_TryMtuRequestResult GATTC_TryMtuRequest(const RawAddress& remote_bda,
                                               tBT_TRANSPORT transport, tCONN_ID conn_id,
                                               uint16_t* current_mtu) {
  log::info("{} conn_id=0x{:04x}", remote_bda, conn_id);
  *current_mtu = GATT_DEF_BLE_MTU_SIZE;

  if (transport == BT_TRANSPORT_BR_EDR) {
    log::error("Device {} connected over BR/EDR", remote_bda);
    return MTU_EXCHANGE_NOT_ALLOWED;
  }

  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(remote_bda, transport);
  if (!p_tcb) {
    log::error("Device {} is not connected", remote_bda);
    return MTU_EXCHANGE_DEVICE_DISCONNECTED;
  }

  if (gatt_is_pending_mtu_exchange(p_tcb)) {
    log::debug("Continue MTU pending for other client.");
    /* MTU Exchange is in progress, started by other GATT Client.
     * Wait until it is completed.
     */
    gatt_set_conn_id_waiting_for_mtu_exchange(p_tcb, conn_id);
    return MTU_EXCHANGE_IN_PROGRESS;
  }

  uint16_t mtu = gatt_get_mtu(remote_bda, transport);
  if (mtu == GATT_DEF_BLE_MTU_SIZE || mtu == 0) {
    log::debug("MTU not yet updated for {}", remote_bda);
    return MTU_EXCHANGE_NOT_DONE_YET;
  }

  *current_mtu = mtu;
  return MTU_EXCHANGE_ALREADY_DONE;
}

/*******************************************************************************
 * Function         GATTC_UpdateUserAttMtuIfNeeded
 *
 * Description      This function to be called when user requested MTU after
 *                  MTU Exchange has been already done. This will update data
 *                  length in the controller.
 *
 * Parameters        remote_bda : peer device address. (input)
 *                   transport  : physical transport of the GATT connection
 *                                 (BR/EDR or LE) (input)
 *                   user_mtu: user request mtu
 *
 ******************************************************************************/
void GATTC_UpdateUserAttMtuIfNeeded(const RawAddress& remote_bda, tBT_TRANSPORT transport,
                                    uint16_t user_mtu) {
  log::info("{}, mtu={}", remote_bda, user_mtu);
  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(remote_bda, transport);
  if (!p_tcb) {
    log::warn("Transport control block not found");
    return;
  }

  log::info("{}, current mtu: {}, max_user_mtu:{}, user_mtu: {}", remote_bda, p_tcb->payload_size,
            p_tcb->max_user_mtu, user_mtu);

  if (p_tcb->payload_size < user_mtu) {
    log::info("User requested more than what GATT can handle. Trim it.");
    user_mtu = p_tcb->payload_size;
  }

  if (p_tcb->max_user_mtu >= user_mtu) {
    return;
  }

  p_tcb->max_user_mtu = user_mtu;
  if (get_btm_client_interface().ble.BTM_SetBleDataLength(remote_bda, user_mtu) !=
      tBTM_STATUS::BTM_SUCCESS) {
    log::warn("Unable to set ble data length peer:{} mtu:{}", remote_bda, user_mtu);
  }
}

std::list<tCONN_ID> GATTC_GetAndRemoveListOfConnIdsWaitingForMtuRequest(
        const RawAddress& remote_bda) {
  std::list result = std::list<tCONN_ID>();

  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(remote_bda, BT_TRANSPORT_LE);
  if (!p_tcb || p_tcb->conn_ids_waiting_for_mtu_exchange.empty()) {
    return result;
  }

  result.swap(p_tcb->conn_ids_waiting_for_mtu_exchange);
  return result;
}

/*******************************************************************************
 *
 * Function         GATTC_Discover
 *
 * Description      This function is called to do a discovery procedure on ATT
 *                  server.
 *
 * Parameters       conn_id: connection identifier.
 *                  disc_type:discovery type.
 *                  start_handle and end_handle: range of handles for discovery
 *                  uuid: uuid to discovery. set to Uuid::kEmpty for requests
 *                        that don't need it
 *
 * Returns          GATT_SUCCESS if command received/sent successfully.
 *
 ******************************************************************************/
tGATT_STATUS GATTC_Discover(tCONN_ID conn_id, tGATT_DISC_TYPE disc_type, uint16_t start_handle,
                            uint16_t end_handle, const Uuid& uuid) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);

  if ((p_tcb == NULL) || (p_reg == NULL) || (disc_type >= GATT_DISC_MAX)) {
    log::error("Illegal param: disc_type={} conn_id=0x{:x}", disc_type, conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  if (!GATT_HANDLE_IS_VALID(start_handle) || !GATT_HANDLE_IS_VALID(end_handle) ||
      /* search by type does not have a valid UUID param */
      (disc_type == GATT_DISC_SRVC_BY_UUID && uuid.IsEmpty())) {
    log::warn(
            "Illegal parameter conn_id=0x{:x}, disc_type={}, s_handle=0x{:x}, "
            "e_handle=0x{:x}",
            conn_id, disc_type, start_handle, end_handle);
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
  if (!p_clcb) {
    log::warn(
            "No resources conn_id=0x{:x}, disc_type={}, s_handle=0x{:x}, "
            "e_handle=0x{:x}",
            conn_id, disc_type, start_handle, end_handle);
    return GATT_NO_RESOURCES;
  }

  p_clcb->operation = GATTC_OPTYPE_DISCOVERY;
  p_clcb->op_subtype = disc_type;
  p_clcb->s_handle = start_handle;
  p_clcb->e_handle = end_handle;
  p_clcb->uuid = uuid;

  log::info("conn_id=0x{:x}, disc_type={}, s_handle=0x{:x}, e_handle=0x{:x}", conn_id, disc_type,
            start_handle, end_handle);

  gatt_act_discovery(p_clcb);
  return GATT_SUCCESS;
}

tGATT_STATUS GATTC_Discover(tCONN_ID conn_id, tGATT_DISC_TYPE disc_type, uint16_t start_handle,
                            uint16_t end_handle) {
  return GATTC_Discover(conn_id, disc_type, start_handle, end_handle, Uuid::kEmpty);
}

/*******************************************************************************
 *
 * Function         GATTC_Read
 *
 * Description      This function is called to read the value of an attribute
 *                  from the server.
 *
 * Parameters       conn_id: connection identifier.
 *                  type    - attribute read type.
 *                  p_read  - read operation parameters.
 *
 * Returns          GATT_SUCCESS if command started successfully.
 *
 ******************************************************************************/
tGATT_STATUS GATTC_Read(tCONN_ID conn_id, tGATT_READ_TYPE type, tGATT_READ_PARAM* p_read) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
#if (GATT_UPPER_TESTER_MULT_VARIABLE_LENGTH_READ == TRUE)
  static uint16_t cached_read_handle;
  static int cached_tcb_idx = -1;
#endif

  log::verbose("conn_id=0x{:x}, type=0x{:x}", conn_id, type);

  if ((p_tcb == NULL) || (p_reg == NULL) || (p_read == NULL) ||
      ((type >= GATT_READ_MAX) || (type == 0))) {
    log::error("illegal param: conn_id=0x{:x}, type=0x{:x}", conn_id, type);
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
  if (!p_clcb) {
    return GATT_NO_RESOURCES;
  }

  p_clcb->operation = GATTC_OPTYPE_READ;
  p_clcb->op_subtype = type;
  p_clcb->auth_req = p_read->by_handle.auth_req;
  p_clcb->counter = 0;
  p_clcb->read_req_current_mtu = gatt_tcb_get_payload_size(*p_tcb, p_clcb->cid);

  switch (type) {
    case GATT_READ_BY_TYPE:
    case GATT_READ_CHAR_VALUE:
      p_clcb->s_handle = p_read->service.s_handle;
      p_clcb->e_handle = p_read->service.e_handle;
      p_clcb->uuid = p_read->service.uuid;
      break;
    case GATT_READ_MULTIPLE:
    case GATT_READ_MULTIPLE_VAR_LEN: {
      p_clcb->s_handle = 0;
      /* copy multiple handles in CB */
      tGATT_READ_MULTI* p_read_multi = (tGATT_READ_MULTI*)osi_malloc(sizeof(tGATT_READ_MULTI));
      p_clcb->p_attr_buf = (uint8_t*)p_read_multi;
      memcpy(p_read_multi, &p_read->read_multiple, sizeof(tGATT_READ_MULTI));
      break;
    }
    case GATT_READ_BY_HANDLE:
#if (GATT_UPPER_TESTER_MULT_VARIABLE_LENGTH_READ == TRUE)
      log::info("Upper tester: Handle read 0x{:04x}", p_read->by_handle.handle);
      /* This is upper tester for the  Multi Read stuff as this is mandatory for
       * EATT, even Android is not making use of this operation :/ */
      if (cached_tcb_idx < 0) {
        cached_tcb_idx = tcb_idx;
        log::info("Upper tester: Read multiple  - first read");
        cached_read_handle = p_read->by_handle.handle;
      } else if (cached_tcb_idx == tcb_idx) {
        log::info("Upper tester: Read multiple  - second read");
        cached_tcb_idx = -1;
        tGATT_READ_MULTI* p_read_multi = (tGATT_READ_MULTI*)osi_malloc(sizeof(tGATT_READ_MULTI));
        p_read_multi->num_handles = 2;
        p_read_multi->handles[0] = cached_read_handle;
        p_read_multi->handles[1] = p_read->by_handle.handle;
        p_read_multi->variable_len = true;

        p_clcb->s_handle = 0;
        p_clcb->op_subtype = GATT_READ_MULTIPLE_VAR_LEN;
        p_clcb->p_attr_buf = (uint8_t*)p_read_multi;
        p_clcb->cid = gatt_tcb_get_att_cid(*p_tcb, true /* eatt support */);

        break;
      }

      FALLTHROUGH_INTENDED;
#endif
    case GATT_READ_PARTIAL:
      p_clcb->uuid = Uuid::kEmpty;
      p_clcb->s_handle = p_read->by_handle.handle;

      if (type == GATT_READ_PARTIAL) {
        p_clcb->counter = p_read->partial.offset;
      }

      break;
    default:
      break;
  }

  /* start security check */
  if (gatt_security_check_start(p_clcb)) {
    p_tcb->pending_enc_clcb.push_back(p_clcb);
  }
  return GATT_SUCCESS;
}

/*******************************************************************************
 *
 * Function         GATTC_Write
 *
 * Description      This function is called to write the value of an attribute
 *                  to the server.
 *
 * Parameters       conn_id: connection identifier.
 *                  type    - attribute write type.
 *                  p_write  - write operation parameters.
 *
 * Returns          GATT_SUCCESS if command started successfully.
 *
 ******************************************************************************/
tGATT_STATUS GATTC_Write(tCONN_ID conn_id, tGATT_WRITE_TYPE type, tGATT_VALUE* p_write) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);

  if ((p_tcb == NULL) || (p_reg == NULL) || (p_write == NULL) ||
      ((type != GATT_WRITE) && (type != GATT_WRITE_PREPARE) && (type != GATT_WRITE_NO_RSP))) {
    log::error("Illegal param: conn_id=0x{:x}, type=0x{:x}", conn_id, type);
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
  if (!p_clcb) {
    return GATT_NO_RESOURCES;
  }

  p_clcb->operation = GATTC_OPTYPE_WRITE;
  p_clcb->op_subtype = type;
  p_clcb->auth_req = p_write->auth_req;

  p_clcb->p_attr_buf = (uint8_t*)osi_malloc(sizeof(tGATT_VALUE));
  memcpy(p_clcb->p_attr_buf, (void*)p_write, sizeof(tGATT_VALUE));

  tGATT_VALUE* p = (tGATT_VALUE*)p_clcb->p_attr_buf;
  if (type == GATT_WRITE_PREPARE) {
    p_clcb->start_offset = p_write->offset;
    p->offset = 0;
  }

  if (gatt_security_check_start(p_clcb)) {
    p_tcb->pending_enc_clcb.push_back(p_clcb);
  }
  return GATT_SUCCESS;
}

/*******************************************************************************
 *
 * Function         GATTC_ExecuteWrite
 *
 * Description      This function is called to send an Execute write request to
 *                  the server.
 *
 * Parameters       conn_id: connection identifier.
 *                  is_execute - to execute or cancel the prepared write
 *                               request(s)
 *
 * Returns          GATT_SUCCESS if command started successfully.
 *
 ******************************************************************************/
tGATT_STATUS GATTC_ExecuteWrite(tCONN_ID conn_id, bool is_execute) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);

  log::verbose("conn_id=0x{:x}, is_execute={}", conn_id, is_execute);

  if ((p_tcb == NULL) || (p_reg == NULL)) {
    log::error("Illegal param: conn_id=0x{:x}", conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
  if (!p_clcb) {
    return GATT_NO_RESOURCES;
  }

  p_clcb->operation = GATTC_OPTYPE_EXE_WRITE;
  tGATT_EXEC_FLAG flag = is_execute ? GATT_PREP_WRITE_EXEC : GATT_PREP_WRITE_CANCEL;
  gatt_send_queue_write_cancel(*p_clcb->p_tcb, p_clcb, flag);
  return GATT_SUCCESS;
}

/*******************************************************************************
 *
 * Function         GATTC_SendHandleValueConfirm
 *
 * Description      This function is called to send a handle value confirmation
 *                  as response to a handle value notification from server.
 *
 * Parameters       conn_id: connection identifier.
 *                  cid: channel id.
 *
 * Returns          GATT_SUCCESS if command started successfully.
 *
 ******************************************************************************/
tGATT_STATUS GATTC_SendHandleValueConfirm(tCONN_ID conn_id, uint16_t cid) {
  log::info("conn_id=0x{:04x} , cid=0x{:04x}", conn_id, cid);

  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(gatt_get_tcb_idx(conn_id));
  if (!p_tcb) {
    log::error("Unknown conn_id=0x{:x}", conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  if (p_tcb->ind_count == 0) {
    log::info("conn_id: 0x{:04x} ignored not waiting for indication ack", conn_id);
    return GATT_SUCCESS;
  }

  log::info("Received confirmation, ind_count= {}, sending confirmation", p_tcb->ind_count);

  /* Just wait for first confirmation.*/
  p_tcb->ind_count = 0;
  gatt_stop_ind_ack_timer(p_tcb, cid);

  /* send confirmation now */
  return attp_send_cl_confirmation_msg(*p_tcb, cid);
}

/******************************************************************************/
/*                                                                            */
/*                  GATT  APIs                                                */
/*                                                                            */
/******************************************************************************/
/*******************************************************************************
 *
 * Function         GATT_SetIdleTimeout
 *
 * Description      This function (common to both client and server) sets the
 *                  idle timeout for a transport connection
 *
 * Parameter        bd_addr:   target device bd address.
 *                  idle_tout: timeout value in seconds.
 *                  transport: transport option.
 *                  is_active: whether we should use this as a signal that an
 *                             active client now exists (which changes link
 *                             timeout logic, see
 *                             t_l2c_linkcb.with_active_local_clients for
 *                             details).
 *
 * Returns          void
 *
 ******************************************************************************/
void GATT_SetIdleTimeout(const RawAddress& bd_addr, uint16_t idle_tout, tBT_TRANSPORT transport,
                         bool is_active) {
  bool status = false;

  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bd_addr, transport);
  if (p_tcb != nullptr) {
    status = stack::l2cap::get_interface().L2CA_SetLeGattTimeout(bd_addr, idle_tout);

    if (is_active) {
      status &= stack::l2cap::get_interface().L2CA_MarkLeLinkAsActive(bd_addr);
    }

    if (idle_tout == GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP) {
      if (!stack::l2cap::get_interface().L2CA_SetIdleTimeoutByBdAddr(
                  p_tcb->peer_bda, GATT_LINK_IDLE_TIMEOUT_WHEN_NO_APP, BT_TRANSPORT_LE)) {
        log::warn("Unable to set L2CAP link idle timeout peer:{} transport:{}", p_tcb->peer_bda,
                  bt_transport_text(transport));
      }
    }
  }

  log::info("idle_timeout={}, is_active={}, status={} (1-OK 0-not performed)", idle_tout, is_active,
            status);
}

/*******************************************************************************
 *
 * Function         GATT_Register
 *
 * Description      This function is called to register an  application
 *                  with GATT
 *
 * Parameter        p_app_uuid128: Application UUID
 *                  p_cb_info: callback functions.
 *                  eatt_support: indicate eatt support.
 *
 * Returns          0 for error, otherwise the index of the client registered
 *                  with GATT
 *
 ******************************************************************************/
tGATT_IF GATT_Register(const Uuid& app_uuid128, const std::string& name, tGATT_CBACK* p_cb_info,
                       bool eatt_support) {
  if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
    return GATT_Register_Dynamic(app_uuid128, name, p_cb_info, eatt_support);
  }
  tGATT_REG* p_reg;
  uint8_t i_gatt_if = 0;
  tGATT_IF gatt_if = 0;

  for (i_gatt_if = 0, p_reg = gatt_cb.cl_rcb; i_gatt_if < GATT_MAX_APPS; i_gatt_if++, p_reg++) {
    if (p_reg->in_use && p_reg->app_uuid128 == app_uuid128) {
      log::error("Application already registered, uuid={}", app_uuid128.ToString());
      return 0;
    }
  }

  if (stack_config_get_interface()->get_pts_use_eatt_for_all_services()) {
    log::info("PTS: Force to use EATT for servers");
    eatt_support = true;
  }

  for (i_gatt_if = 0, p_reg = gatt_cb.cl_rcb; i_gatt_if < GATT_MAX_APPS; i_gatt_if++, p_reg++) {
    if (!p_reg->in_use) {
      *p_reg = {};
      i_gatt_if++; /* one based number */
      p_reg->app_uuid128 = app_uuid128;
      gatt_if = p_reg->gatt_if = (tGATT_IF)i_gatt_if;
      p_reg->app_cb = *p_cb_info;
      p_reg->in_use = true;
      p_reg->eatt_support = eatt_support;
      p_reg->name = name;
      log::info("Allocated name:{} uuid:{} gatt_if:{} eatt_support:{}", name,
                app_uuid128.ToString(), gatt_if, eatt_support);
      return gatt_if;
    }
  }

  log::error("Unable to register GATT client, MAX client reached: {}", GATT_MAX_APPS);
  return 0;
}

static tGATT_IF GATT_FindNextFreeClRcbId() {
  tGATT_IF gatt_if = gatt_cb.last_gatt_if;
  for (int i = 0; i < GATT_IF_MAX; i++) {
    if (++gatt_if > GATT_IF_MAX) {
      gatt_if = static_cast<tGATT_IF>(1);
    }
    if (!gatt_cb.cl_rcb_map.contains(gatt_if)) {
      gatt_cb.last_gatt_if = gatt_if;
      return gatt_if;
    }
  }
  log::error("Unable to register GATT client, MAX client reached: {}", gatt_cb.cl_rcb_map.size());

  return GATT_IF_INVALID;
}

static tGATT_IF GATT_Register_Dynamic(const Uuid& app_uuid128, const std::string& name,
                                      tGATT_CBACK* p_cb_info, bool eatt_support) {
  for (auto& [gatt_if, p_reg] : gatt_cb.cl_rcb_map) {
    if (p_reg->app_uuid128 == app_uuid128) {
      log::error("Application already registered, uuid={}", app_uuid128.ToString());
      return 0;
    }
  }

  if (stack_config_get_interface()->get_pts_use_eatt_for_all_services()) {
    log::info("PTS: Force to use EATT for servers");
    eatt_support = true;
  }

  if (gatt_cb.cl_rcb_map.size() >= GATT_IF_MAX) {
    log::error("Unable to register GATT client, MAX client reached: {}", gatt_cb.cl_rcb_map.size());
    return 0;
  }

  tGATT_IF gatt_if = GATT_FindNextFreeClRcbId();
  if (gatt_if == GATT_IF_INVALID) {
    return gatt_if;
  }

  auto [it, ret] = gatt_cb.cl_rcb_map.emplace(gatt_if, std::make_unique<tGATT_REG>());
  tGATT_REG* p_reg = it->second.get();
  p_reg->app_uuid128 = app_uuid128;
  p_reg->gatt_if = gatt_if;
  p_reg->app_cb = *p_cb_info;
  p_reg->in_use = true;
  p_reg->eatt_support = eatt_support;
  p_reg->name = name;
  log::info("Allocated name:{} uuid:{} gatt_if:{} eatt_support:{}", name, app_uuid128.ToString(),
            p_reg->gatt_if, eatt_support);

  return gatt_if;
}

/*******************************************************************************
 *
 * Function         GATT_Deregister
 *
 * Description      This function deregistered the application from GATT.
 *
 * Parameters       gatt_if: application interface.
 *
 * Returns          None.
 *
 ******************************************************************************/
void GATT_Deregister(tGATT_IF gatt_if) {
  log::info("gatt_if={}", gatt_if);

  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  /* Index 0 is GAP and is never deregistered */
  if ((gatt_if == 0) || (p_reg == NULL)) {
    log::error("Unable to deregister client with invalid gatt_if={}", gatt_if);
    return;
  }

  /* stop all services  */
  /* todo an application can not be deregistered if its services is also used by
    other application
    deregistration need to be performed in an orderly fashion
    no check for now */
  for (auto it = gatt_cb.srv_list_info->begin(); it != gatt_cb.srv_list_info->end();) {
    if (it->gatt_if == gatt_if) {
      GATTS_StopService(it++->s_hdl);
    } else {
      ++it;
    }
  }

  /* free all services db buffers if owned by this application */
  gatt_free_srvc_db_buffer_app_id(p_reg->app_uuid128);

  /* When an application deregisters, check remove the link associated with the
   * app */
  tGATT_TCB* p_tcb;
  int i;
  for (i = 0, p_tcb = gatt_cb.tcb; i < GATT_MAX_PHY_CHANNEL; i++, p_tcb++) {
    if (!p_tcb->in_use) {
      continue;
    }

    if (gatt_get_ch_state(p_tcb) != GATT_CH_CLOSE) {
      gatt_update_app_use_link_flag(gatt_if, p_tcb, false, true);
    }

    for (auto clcb_it = gatt_cb.clcb_queue.begin(); clcb_it != gatt_cb.clcb_queue.end();) {
      if ((clcb_it->p_reg->gatt_if == gatt_if) && (clcb_it->p_tcb->tcb_idx == p_tcb->tcb_idx)) {
        alarm_cancel(clcb_it->gatt_rsp_timer_ent);
        gatt_clcb_invalidate(p_tcb, &(*clcb_it));
        clcb_it = gatt_cb.clcb_queue.erase(clcb_it);
      } else {
        clcb_it++;
      }
    }
  }

  connection_manager::on_app_deregistered(gatt_if);

  if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
    gatt_cb.cl_rcb_map.erase(gatt_if);
  } else {
    *p_reg = {};
  }
}

/*******************************************************************************
 *
 * Function         GATT_StartIf
 *
 * Description      This function is called after registration to start
 *                  receiving callbacks for registered interface.  Function may
 *                  call back with connection status and queued notifications
 *
 * Parameter        gatt_if: application interface.
 *
 * Returns          None.
 *
 ******************************************************************************/
void GATT_StartIf(tGATT_IF gatt_if) {
  tGATT_REG* p_reg;
  tGATT_TCB* p_tcb;
  RawAddress bda = {};
  uint8_t start_idx, found_idx;
  tCONN_ID conn_id;
  tBT_TRANSPORT transport;

  log::debug("Starting GATT interface gatt_if_:{}", gatt_if);

  p_reg = gatt_get_regcb(gatt_if);
  if (p_reg != NULL) {
    start_idx = 0;
    while (gatt_find_the_connected_bda(start_idx, bda, &found_idx, &transport)) {
      p_tcb = gatt_find_tcb_by_addr(bda, transport);
      log::info("GATT interface {} already has connected device {}", gatt_if, bda);
      if (p_reg->app_cb.p_conn_cb && p_tcb) {
        conn_id = gatt_create_conn_id(p_tcb->tcb_idx, gatt_if);
        log::info("Invoking callback with connection id {}", conn_id);
        (*p_reg->app_cb.p_conn_cb)(gatt_if, bda, conn_id, true, GATT_CONN_OK, transport);
      } else {
        log::info("Skipping callback as none is registered");
      }
      start_idx = ++found_idx;
    }
  }
}

/*******************************************************************************
 *
 * Function         GATT_Connect
 *
 * Description      This function initiate a connection to a remote device on
 *                  GATT channel.
 *
 * Parameters       gatt_if: application interface
 *                  bd_addr: peer device address.
 *                  connection_type: is a direct connection or a background
 *                  auto connection or targeted announcements
 *
 * Returns          true if connection started; false if connection start
 *                  failure.
 *
 ******************************************************************************/
bool GATT_Connect(tGATT_IF gatt_if, const RawAddress& bd_addr, tBLE_ADDR_TYPE addr_type,
                  tBTM_BLE_CONN_TYPE connection_type, tBT_TRANSPORT transport, bool opportunistic,
                  uint8_t initiating_phys, uint16_t preferred_mtu) {
  /* Make sure app is registered */
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  if (!p_reg) {
    log::error("Unable to find registered app gatt_if={}", gatt_if);
    return false;
  }

  bool is_direct = (connection_type == BTM_BLE_DIRECT_CONNECTION);

  if (!is_direct && transport != BT_TRANSPORT_LE) {
    log::warn("Unsupported transport for background connection gatt_if={}", gatt_if);
    return false;
  }

  if (bd_addr == RawAddress::kEmpty) {
    log::error("Unsupported empty address, gatt_if={}", gatt_if);
    return false;
  }

  if (opportunistic) {
    log::info("Registered for opportunistic connection gatt_if={}", gatt_if);
    return true;
  }

  bool ret = false;
  if (is_direct) {
    log::debug("Starting direct connect gatt_if={} address={} transport={}", gatt_if, bd_addr,
               transport);
    bool tcb_exist = !!gatt_find_tcb_by_addr(bd_addr, transport);

    if (tcb_exist || transport == BT_TRANSPORT_BR_EDR) {
      /* Consider to remove gatt_act_connect at all */
      ret = gatt_act_connect(p_reg, bd_addr, addr_type, transport, initiating_phys);
    } else {
      log::verbose("Connecting without tcb address: {}", bd_addr);

      if (p_reg->direct_connect_request.count(bd_addr) == 0) {
        p_reg->direct_connect_request.insert(bd_addr);
      } else {
        log::warn("{} already added to gatt_if {} direct conn list", bd_addr, gatt_if);
      }

      ret = connection_manager::create_le_connection(gatt_if, bd_addr, addr_type);
    }

  } else {
    log::debug("Starting background connect gatt_if={} address={}", gatt_if, bd_addr);
    if (!BTM_Sec_AddressKnown(bd_addr)) {
      //  RPA can rotate, causing address to "expire" in the background
      //  connection list. RPA is allowed for direct connect, as such request
      //  times out after 30 seconds
      log::warn("Unable to add RPA {} to background connection gatt_if={}", bd_addr, gatt_if);
      ret = false;
    } else {
      log::debug("Adding to background connect to device:{}", bd_addr);
      if (connection_type == BTM_BLE_BKG_CONNECT_ALLOW_LIST) {
        ret = connection_manager::background_connect_add(gatt_if, bd_addr);
      } else {
        ret = connection_manager::background_connect_targeted_announcement_add(gatt_if, bd_addr);
      }
    }
  }

  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bd_addr, transport);
  // background connections don't necessarily create tcb
  if (p_tcb && ret) {
    gatt_update_app_use_link_flag(p_reg->gatt_if, p_tcb, true, !is_direct);
  } else {
    if (p_tcb == nullptr) {
      log::debug("p_tcb is null");
    }
    if (!ret) {
      log::debug("Previous step returned false");
    }
  }

  if (ret) {
    // Save the current MTU preference for this app
    p_reg->mtu_prefs.erase(bd_addr);
    if (preferred_mtu > GATT_DEF_BLE_MTU_SIZE) {
      log::verbose("Saving MTU preference from app {} for {}", gatt_if, bd_addr);
      p_reg->mtu_prefs.insert({bd_addr, preferred_mtu});
    }
  }

  return ret;
}

bool GATT_Connect(tGATT_IF gatt_if, const RawAddress& bd_addr, tBTM_BLE_CONN_TYPE connection_type,
                  tBT_TRANSPORT transport, bool opportunistic) {
  return GATT_Connect(gatt_if, bd_addr, BLE_ADDR_PUBLIC, connection_type, transport, opportunistic,
                      LE_PHY_1M, 0);
}

/*******************************************************************************
 *
 * Function         GATT_CancelConnect
 *
 * Description      This function terminates the connection initiation to a
 *                  remote device on GATT channel.
 *
 * Parameters       gatt_if: client interface. If 0 used as unconditionally
 *                           disconnect, typically used for direct connection
 *                           cancellation.
 *                  bd_addr: peer device address.
 *
 * Returns          true if the connection started; false otherwise.
 *
 ******************************************************************************/
bool GATT_CancelConnect(tGATT_IF gatt_if, const RawAddress& bd_addr, bool is_direct) {
  log::info("gatt_if:{}, address: {}, direct:{}", gatt_if, bd_addr, is_direct);

  tGATT_REG* p_reg;
  if (gatt_if) {
    p_reg = gatt_get_regcb(gatt_if);
    if (!p_reg) {
      log::error("gatt_if={} is not registered", gatt_if);
      return false;
    }

    if (is_direct) {
      return gatt_cancel_open(gatt_if, bd_addr);
    } else {
      return gatt_auto_connect_dev_remove(p_reg->gatt_if, bd_addr);
    }
  }

  log::verbose("unconditional");

  /* only LE connection can be cancelled */
  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE);
  if (p_tcb && !p_tcb->app_hold_link.empty()) {
    for (auto it = p_tcb->app_hold_link.begin(); it != p_tcb->app_hold_link.end();) {
      auto next = std::next(it);
      // gatt_cancel_open modifies the app_hold_link.
      gatt_cancel_open(*it, bd_addr);

      it = next;
    }
  }

  if (!connection_manager::remove_unconditional(bd_addr)) {
    log::error("no app associated with the bg device for unconditional removal");
    return false;
  }

  return true;
}

/*******************************************************************************
 *
 * Function         GATT_Disconnect
 *
 * Description      This function disconnects the GATT channel for this
 *                  registered application.
 *
 * Parameters       conn_id: connection identifier.
 *
 * Returns          GATT_SUCCESS if disconnected.
 *
 ******************************************************************************/
tGATT_STATUS GATT_Disconnect(tCONN_ID conn_id) {
  log::info("conn_id={}", conn_id);

  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
  if (!p_tcb) {
    log::warn("Cannot find TCB for connection {}", conn_id);
    return GATT_ILLEGAL_PARAMETER;
  }

  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  gatt_update_app_use_link_flag(gatt_if, p_tcb, false, true);
  return GATT_SUCCESS;
}

/*******************************************************************************
 *
 * Function         GATT_GetConnectionInfor
 *
 * Description      This function uses conn_id to find its associated BD address
 *                  and application interface
 *
 * Parameters        conn_id: connection id  (input)
 *                   p_gatt_if: application interface (output)
 *                   bd_addr: peer device address. (output)
 *
 * Returns          true the logical link information is found for conn_id
 *
 ******************************************************************************/
bool GATT_GetConnectionInfor(tCONN_ID conn_id, tGATT_IF* p_gatt_if, RawAddress& bd_addr,
                             tBT_TRANSPORT* p_transport) {
  tGATT_IF gatt_if = gatt_get_gatt_if(conn_id);
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  uint8_t tcb_idx = gatt_get_tcb_idx(conn_id);
  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);

  log::verbose("conn_id=0x{:x}", conn_id);

  if (!p_tcb || !p_reg) {
    return false;
  }

  bd_addr = p_tcb->peer_bda;
  *p_gatt_if = gatt_if;
  *p_transport = p_tcb->transport;
  return true;
}

/*******************************************************************************
 *
 * Function         GATT_GetConnIdIfConnected
 *
 * Description      This function finds the conn_id if the logical link for BD
 *                  address and application interface is connected
 *
 * Parameters        gatt_if: application interface (input)
 *                   bd_addr: peer device address. (input)
 *                   p_conn_id: connection id  (output)
 *                   transport: transport option
 *
 * Returns          true the logical link is connected
 *
 ******************************************************************************/
bool GATT_GetConnIdIfConnected(tGATT_IF gatt_if, const RawAddress& bd_addr, tCONN_ID* p_conn_id,
                               tBT_TRANSPORT transport) {
  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
  tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bd_addr, transport);
  bool status = false;

  if (p_reg && p_tcb && (gatt_get_ch_state(p_tcb) == GATT_CH_OPEN)) {
    *p_conn_id = gatt_create_conn_id(p_tcb->tcb_idx, gatt_if);
    status = true;
  }

  log::debug("status={}", status);
  return status;
}

static void gatt_bonded_check_add_address(const RawAddress& bda) {
  if (!gatt_is_bda_in_the_srv_chg_clt_list(bda)) {
    gatt_add_a_bonded_dev_for_srv_chg(bda);
  }
}

std::optional<bool> OVERRIDE_GATT_LOAD_BONDED = std::nullopt;

static bool gatt_load_bonded_is_enabled() {
  static const bool sGATT_LOAD_BONDED =
          bluetooth::os::GetSystemPropertyBool("bluetooth.gatt.load_bonded.enabled", false);
  if (OVERRIDE_GATT_LOAD_BONDED.has_value()) {
    return OVERRIDE_GATT_LOAD_BONDED.value();
  }
  return sGATT_LOAD_BONDED;
}

/* Initialize GATTS list of bonded device service change updates.
 *
 * Addresses for bonded devices (public for BR/EDR or pseudo for BLE) are added
 * to GATTS service change control list so that updates are sent to bonded
 * devices on next connect after any handles for GATTS services change due to
 * services added/removed.
 */
void gatt_load_bonded(void) {
  const bool load_bonded = gatt_load_bonded_is_enabled();
  log::info("load bonded: {}", load_bonded ? "True" : "False");
  if (!load_bonded) {
    return;
  }
  for (tBTM_SEC_DEV_REC* p_dev_rec : btm_get_sec_dev_rec()) {
    if (p_dev_rec->sec_rec.is_link_key_known()) {
      log::verbose("Add bonded BR/EDR transport {}", p_dev_rec->bd_addr);
      gatt_bonded_check_add_address(p_dev_rec->bd_addr);
    }
    if (p_dev_rec->sec_rec.is_le_link_key_known()) {
      log::verbose("Add bonded BLE {}", p_dev_rec->ble.pseudo_addr);
      gatt_bonded_check_add_address(p_dev_rec->ble.pseudo_addr);
    }
  }
}
