Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8fafc63 authored by Jakub Pawlowski's avatar Jakub Pawlowski Committed by android-build-merger
Browse files

BTA GATT operation queue

am: 19d0aae5

Change-Id: I771d06a5fb93f7a02a1cad8e70a158c2a523a5d0
parents 85a26c78 19d0aae5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ cc_library_static {
        "gatt/bta_gattc_api.cc",
        "gatt/bta_gattc_cache.cc",
        "gatt/bta_gattc_main.cc",
        "gatt/bta_gattc_queue.cc",
        "gatt/bta_gattc_utils.cc",
        "gatt/bta_gatts_act.cc",
        "gatt/bta_gatts_api.cc",
+1 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ static_library("bta") {
    "gatt/bta_gattc_cache.cc",
    "gatt/bta_gattc_main.cc",
    "gatt/bta_gattc_utils.cc",
    "gatt/bta_gattc_queue.cc",
    "gatt/bta_gatts_act.cc",
    "gatt/bta_gatts_api.cc",
    "gatt/bta_gatts_main.cc",
+192 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 The Android Open Source Project
 *
 * 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 "bta_gatt_queue.h"

#include <list>
#include <unordered_map>
#include <unordered_set>

using gatt_operation = BtaGattQueue::gatt_operation;

constexpr uint8_t GATT_READ_CHAR = 1;
constexpr uint8_t GATT_READ_DESC = 2;
constexpr uint8_t GATT_WRITE_CHAR = 3;
constexpr uint8_t GATT_WRITE_DESC = 4;

struct gatt_read_op_data {
  GATT_READ_OP_CB cb;
  void* cb_data;
};

std::unordered_map<uint16_t, std::list<gatt_operation>>
    BtaGattQueue::gatt_op_queue;
std::unordered_set<uint16_t> BtaGattQueue::gatt_op_queue_executing;

void BtaGattQueue::mark_as_not_executing(uint16_t conn_id) {
  gatt_op_queue_executing.erase(conn_id);
}

void BtaGattQueue::gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status,
                                         uint16_t handle, uint16_t len,
                                         uint8_t* value, void* data) {
  gatt_read_op_data* tmp = (gatt_read_op_data*)data;
  GATT_READ_OP_CB tmp_cb = tmp->cb;
  void* tmp_cb_data = tmp->cb_data;

  osi_free(data);

  mark_as_not_executing(conn_id);
  gatt_execute_next_op(conn_id);

  if (tmp_cb) {
    tmp_cb(conn_id, status, handle, len, value, tmp_cb_data);
    return;
  }
}

struct gatt_write_op_data {
  GATT_WRITE_OP_CB cb;
  void* cb_data;
};

void BtaGattQueue::gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status,
                                          uint16_t handle, void* data) {
  gatt_write_op_data* tmp = (gatt_write_op_data*)data;
  GATT_WRITE_OP_CB tmp_cb = tmp->cb;
  void* tmp_cb_data = tmp->cb_data;

  osi_free(data);

  mark_as_not_executing(conn_id);
  gatt_execute_next_op(conn_id);

  if (tmp_cb) {
    tmp_cb(conn_id, status, handle, tmp_cb_data);
    return;
  }
}

void BtaGattQueue::gatt_execute_next_op(uint16_t conn_id) {
  APPL_TRACE_DEBUG("%s:", __func__, conn_id);
  if (gatt_op_queue.empty()) {
    APPL_TRACE_DEBUG("%s: op queue is empty", __func__);
    return;
  }

  auto map_ptr = gatt_op_queue.find(conn_id);
  if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) {
    APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__,
                     conn_id);
    return;
  }

  if (gatt_op_queue_executing.count(conn_id)) {
    APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__);
    return;
  }

  gatt_op_queue_executing.insert(conn_id);

  std::list<gatt_operation>& gatt_ops = map_ptr->second;

  gatt_operation& op = gatt_ops.front();

  if (op.type == GATT_READ_CHAR) {
    gatt_read_op_data* data =
        (gatt_read_op_data*)osi_malloc(sizeof(gatt_read_op_data));
    data->cb = op.read_cb;
    data->cb_data = op.read_cb_data;
    BTA_GATTC_ReadCharacteristic(conn_id, op.handle, GATT_AUTH_REQ_NONE,
                                 gatt_read_op_finished, data);

  } else if (op.type == GATT_READ_DESC) {
    gatt_read_op_data* data =
        (gatt_read_op_data*)osi_malloc(sizeof(gatt_read_op_data));
    data->cb = op.read_cb;
    data->cb_data = op.read_cb_data;
    BTA_GATTC_ReadCharDescr(conn_id, op.handle, GATT_AUTH_REQ_NONE,
                            gatt_read_op_finished, data);

  } else if (op.type == GATT_WRITE_CHAR) {
    gatt_write_op_data* data =
        (gatt_write_op_data*)osi_malloc(sizeof(gatt_write_op_data));
    data->cb = op.write_cb;
    data->cb_data = op.write_cb_data;
    BTA_GATTC_WriteCharValue(conn_id, op.handle, op.write_type,
                             std::move(op.value), GATT_AUTH_REQ_NONE,
                             gatt_write_op_finished, data);

  } else if (op.type == GATT_WRITE_DESC) {
    gatt_write_op_data* data =
        (gatt_write_op_data*)osi_malloc(sizeof(gatt_write_op_data));
    data->cb = op.write_cb;
    data->cb_data = op.write_cb_data;
    BTA_GATTC_WriteCharDescr(conn_id, op.handle, std::move(op.value),
                             GATT_AUTH_REQ_NONE, gatt_write_op_finished, data);
  }

  gatt_ops.pop_front();
}

void BtaGattQueue::Clean(uint16_t conn_id) {
  gatt_op_queue.erase(conn_id);
  gatt_op_queue_executing.erase(conn_id);
}

void BtaGattQueue::ReadCharacteristic(uint16_t conn_id, uint16_t handle,
                                      GATT_READ_OP_CB cb, void* cb_data) {
  gatt_op_queue[conn_id].push_back({.type = GATT_READ_CHAR,
                                    .handle = handle,
                                    .read_cb = cb,
                                    .read_cb_data = cb_data});
  gatt_execute_next_op(conn_id);
}

void BtaGattQueue::ReadDescriptor(uint16_t conn_id, uint16_t handle,
                                  GATT_READ_OP_CB cb, void* cb_data) {
  gatt_op_queue[conn_id].push_back({.type = GATT_READ_DESC,
                                    .handle = handle,
                                    .read_cb = cb,
                                    .read_cb_data = cb_data});
  gatt_execute_next_op(conn_id);
}

void BtaGattQueue::WriteCharacteristic(uint16_t conn_id, uint16_t handle,
                                       std::vector<uint8_t> value,
                                       tGATT_WRITE_TYPE write_type,
                                       GATT_WRITE_OP_CB cb, void* cb_data) {
  gatt_op_queue[conn_id].push_back({.type = GATT_WRITE_CHAR,
                                    .handle = handle,
                                    .write_type = write_type,
                                    .write_cb = cb,
                                    .write_cb_data = cb_data,
                                    .value = std::move(value)});
  gatt_execute_next_op(conn_id);
}

void BtaGattQueue::WriteDescriptor(uint16_t conn_id, uint16_t handle,
                                   std::vector<uint8_t> value,
                                   tGATT_WRITE_TYPE write_type,
                                   GATT_WRITE_OP_CB cb, void* cb_data) {
  gatt_op_queue[conn_id].push_back({.type = GATT_WRITE_DESC,
                                    .handle = handle,
                                    .write_type = write_type,
                                    .write_cb = cb,
                                    .write_cb_data = cb_data,
                                    .value = std::move(value)});
  gatt_execute_next_op(conn_id);
}
+27 −186
Original line number Diff line number Diff line
@@ -28,12 +28,9 @@

#include <base/bind.h>
#include <base/callback.h>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "bta_hh_co.h"
#include "btm_api.h"
#include "btm_ble_api.h"
@@ -74,160 +71,6 @@ static void bta_hh_le_add_dev_bg_conn(tBTA_HH_DEV_CB* p_cb, bool check_bond);
//                                       tBTA_HH_RPT_CACHE_ENTRY *p_rpt_cache,
//                                       uint8_t num_rpt);

#define GATT_READ_CHAR 0
#define GATT_READ_DESC 1
#define GATT_WRITE_CHAR 2
#define GATT_WRITE_DESC 3

/* Holds pending GATT operations */
struct gatt_operation {
  uint8_t type;
  uint16_t handle;
  GATT_READ_OP_CB read_cb;
  void* read_cb_data;
  GATT_WRITE_OP_CB write_cb;
  void* write_cb_data;

  /* write-specific fields */
  tGATT_WRITE_TYPE write_type;
  vector<uint8_t> value;
};

// maps connection id to operations waiting for execution
static std::unordered_map<uint16_t, std::list<gatt_operation>> gatt_op_queue;
// contain connection ids that currently execute operations
static std::unordered_set<uint16_t> gatt_op_queue_executing;

static void mark_as_not_executing(uint16_t conn_id) {
  gatt_op_queue_executing.erase(conn_id);
}

static void gatt_op_queue_clean(uint16_t conn_id) {
  gatt_op_queue.erase(conn_id);
  gatt_op_queue_executing.erase(conn_id);
}

static void gatt_execute_next_op(uint16_t conn_id);
GATT_READ_OP_CB act_read_cb = NULL;
void* act_read_cb_data = NULL;
static void gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status,
                                  uint16_t handle, uint16_t len, uint8_t* value,
                                  void* data) {
  GATT_READ_OP_CB tmp_cb = act_read_cb;
  void* tmp_cb_data = act_read_cb_data;

  act_read_cb = NULL;
  act_read_cb_data = NULL;

  mark_as_not_executing(conn_id);
  gatt_execute_next_op(conn_id);

  if (tmp_cb) {
    tmp_cb(conn_id, status, handle, len, value, tmp_cb_data);
    return;
  }
}

GATT_WRITE_OP_CB act_write_cb = NULL;
void* act_write_cb_data = NULL;
static void gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status,
                                   uint16_t handle, void* data) {
  GATT_WRITE_OP_CB tmp_cb = act_write_cb;
  void* tmp_cb_data = act_write_cb_data;
  act_write_cb = NULL;
  act_write_cb_data = NULL;

  mark_as_not_executing(conn_id);
  gatt_execute_next_op(conn_id);

  if (tmp_cb) {
    tmp_cb(conn_id, status, handle, tmp_cb_data);
    return;
  }
}

static void gatt_execute_next_op(uint16_t conn_id) {
  APPL_TRACE_DEBUG("%s:", __func__, conn_id);
  if (gatt_op_queue.empty()) {
    APPL_TRACE_DEBUG("%s: op queue is empty", __func__);
    return;
  }

  auto map_ptr = gatt_op_queue.find(conn_id);
  if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) {
    APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__,
                     conn_id);
    return;
  }

  if (gatt_op_queue_executing.count(conn_id)) {
    APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__);
    return;
  }

  gatt_op_queue_executing.insert(conn_id);

  std::list<gatt_operation>& gatt_ops = map_ptr->second;

  gatt_operation& op = gatt_ops.front();

  if (op.type == GATT_READ_CHAR) {
    act_read_cb = op.read_cb;
    act_read_cb_data = op.read_cb_data;
    BTA_GATTC_ReadCharacteristic(conn_id, op.handle, GATT_AUTH_REQ_NONE,
                                 gatt_read_op_finished, NULL);

  } else if (op.type == GATT_READ_DESC) {
    act_read_cb = op.read_cb;
    act_read_cb_data = op.read_cb_data;
    BTA_GATTC_ReadCharDescr(conn_id, op.handle, GATT_AUTH_REQ_NONE,
                            gatt_read_op_finished, NULL);

  } else if (op.type == GATT_WRITE_CHAR) {
    act_write_cb = op.write_cb;
    act_write_cb_data = op.write_cb_data;
    BTA_GATTC_WriteCharValue(conn_id, op.handle, op.write_type,
                             std::move(op.value), GATT_AUTH_REQ_NONE,
                             gatt_write_op_finished, NULL);

  } else if (op.type == GATT_WRITE_DESC) {
    act_write_cb = op.write_cb;
    act_write_cb_data = op.write_cb_data;
    BTA_GATTC_WriteCharDescr(conn_id, op.handle, std::move(op.value),
                             GATT_AUTH_REQ_NONE, gatt_write_op_finished, NULL);
  }

  gatt_ops.pop_front();
}

static void gatt_queue_read_op(uint8_t op_type, uint16_t conn_id,
                               uint16_t handle, GATT_READ_OP_CB cb,
                               void* cb_data) {
  gatt_operation op;
  op.type = op_type;
  op.handle = handle;
  op.read_cb = cb;
  op.read_cb_data = cb_data;
  gatt_op_queue[conn_id].push_back(op);
  gatt_execute_next_op(conn_id);
}

static void gatt_queue_write_op(uint8_t op_type, uint16_t conn_id,
                                uint16_t handle, vector<uint8_t> value,
                                tGATT_WRITE_TYPE write_type,
                                GATT_WRITE_OP_CB cb, void* cb_data) {
  gatt_operation op;
  op.type = op_type;
  op.handle = handle;
  op.write_type = write_type;
  op.write_cb = cb;
  op.write_cb_data = cb_data;
  op.value = std::move(value);

  gatt_op_queue[conn_id].push_back(op);
  gatt_execute_next_op(conn_id);
}

#if (BTA_HH_DEBUG == TRUE)
static const char* bta_hh_le_rpt_name[4] = {"UNKNOWN", "INPUT", "OUTPUT",
                                            "FEATURE"};
@@ -643,8 +486,7 @@ static tBTA_HH_STATUS bta_hh_le_read_char_descriptor(tBTA_HH_DEV_CB* p_cb,
      find_descriptor_by_short_uuid(p_cb->conn_id, char_handle, short_uuid);
  if (!p_desc) return BTA_HH_ERR;

  gatt_queue_read_op(GATT_READ_DESC, p_cb->conn_id, p_desc->handle, cb,
                     cb_data);
  BtaGattQueue::ReadDescriptor(p_cb->conn_id, p_desc->handle, cb, cb_data);
  return BTA_HH_OK;
}

@@ -833,8 +675,8 @@ bool bta_hh_le_write_ccc(tBTA_HH_DEV_CB* p_cb, uint8_t char_handle,
  uint8_t* ptr = value.data();
  UINT16_TO_STREAM(ptr, clt_cfg_value);

  gatt_queue_write_op(GATT_WRITE_DESC, p_cb->conn_id, p_desc->handle,
                      std::move(value), GATT_WRITE, cb, cb_data);
  BtaGattQueue::WriteDescriptor(p_cb->conn_id, p_desc->handle, std::move(value),
                                GATT_WRITE, cb, cb_data);
  return true;
}

@@ -972,8 +814,8 @@ bool bta_hh_le_set_protocol_mode(tBTA_HH_DEV_CB* p_cb,
    mode = (mode == BTA_HH_PROTO_BOOT_MODE) ? BTA_HH_LE_PROTO_BOOT_MODE
                                            : BTA_HH_LE_PROTO_REPORT_MODE;

    gatt_queue_write_op(GATT_WRITE_CHAR, p_cb->conn_id,
                        p_cb->hid_srvc.proto_mode_handle, {mode},
    BtaGattQueue::WriteCharacteristic(
        p_cb->conn_id, p_cb->hid_srvc.proto_mode_handle, {mode},
        GATT_WRITE_NO_RSP, write_proto_mode_cb, p_cb);
    return true;
  }
@@ -1035,9 +877,9 @@ void bta_hh_le_get_protocol_mode(tBTA_HH_DEV_CB* p_cb) {
  p_cb->w4_evt = BTA_HH_GET_PROTO_EVT;

  if (p_cb->hid_srvc.in_use && p_cb->hid_srvc.proto_mode_handle != 0) {
    gatt_queue_read_op(GATT_READ_CHAR, p_cb->conn_id,
                       p_cb->hid_srvc.proto_mode_handle, get_protocol_mode_cb,
                       p_cb);
    BtaGattQueue::ReadCharacteristic(p_cb->conn_id,
                                     p_cb->hid_srvc.proto_mode_handle,
                                     get_protocol_mode_cb, p_cb);
    return;
  }

@@ -1312,7 +1154,7 @@ void bta_hh_gatt_open(tBTA_HH_DEV_CB* p_cb, tBTA_HH_DATA* p_buf) {

    bta_hh_cb.le_cb_index[BTA_HH_GET_LE_CB_IDX(p_cb->hid_handle)] = p_cb->index;

    gatt_op_queue_clean(p_cb->conn_id);
    BtaGattQueue::Clean(p_cb->conn_id);

#if (BTA_HH_DEBUG == TRUE)
    APPL_TRACE_DEBUG("hid_handle = %2x conn_id = %04x cb_index = %d",
@@ -1548,14 +1390,13 @@ static void bta_hh_le_search_hid_chars(tBTA_HH_DEV_CB* p_dev_cb,
        break;
      case GATT_UUID_HID_INFORMATION:
        /* only one instance per HID service */
        gatt_queue_read_op(GATT_READ_CHAR, p_dev_cb->conn_id,
                           charac.value_handle, read_hid_info_cb, p_dev_cb);
        BtaGattQueue::ReadCharacteristic(p_dev_cb->conn_id, charac.value_handle,
                                         read_hid_info_cb, p_dev_cb);
        break;
      case GATT_UUID_HID_REPORT_MAP:
        /* only one instance per HID service */
        gatt_queue_read_op(GATT_READ_CHAR, p_dev_cb->conn_id,
                           charac.value_handle, read_hid_report_map_cb,
                           p_dev_cb);
        BtaGattQueue::ReadCharacteristic(p_dev_cb->conn_id, charac.value_handle,
                                         read_hid_report_map_cb, p_dev_cb);
        /* descriptor is optional */
        bta_hh_le_read_char_descriptor(p_dev_cb, charac.value_handle,
                                       GATT_UUID_EXT_RPT_REF_DESCR,
@@ -1666,10 +1507,9 @@ void bta_hh_le_srvc_search_cmpl(tBTA_GATTC_SEARCH_CMPL* p_data) {
      for (const tBTA_GATTC_CHARACTERISTIC& charac : service.characteristics) {
        if (charac.uuid == Uuid::From16Bit(GATT_UUID_GAP_PREF_CONN_PARAM)) {
          /* read the char value */
          gatt_queue_read_op(GATT_READ_CHAR, p_dev_cb->conn_id,
                             charac.value_handle, read_pref_conn_params_cb,
                             p_dev_cb);

          BtaGattQueue::ReadCharacteristic(p_dev_cb->conn_id,
                                           charac.value_handle,
                                           read_pref_conn_params_cb, p_dev_cb);
          break;
        }
      }
@@ -1834,7 +1674,7 @@ void bta_hh_gatt_close(tBTA_HH_DEV_CB* p_cb, tBTA_HH_DATA* p_data) {
 ******************************************************************************/
void bta_hh_le_api_disc_act(tBTA_HH_DEV_CB* p_cb) {
  if (p_cb->conn_id != GATT_INVALID_CONN_ID) {
    gatt_op_queue_clean(p_cb->conn_id);
    BtaGattQueue::Clean(p_cb->conn_id);
    BTA_GATTC_Close(p_cb->conn_id);
    /* remove device from background connection if intended to disconnect,
       do not allow reconnection */
@@ -1935,7 +1775,7 @@ void bta_hh_le_get_rpt(tBTA_HH_DEV_CB* p_cb, tBTA_HH_RPT_TYPE r_type,
  }

  p_cb->w4_evt = BTA_HH_GET_RPT_EVT;
  gatt_queue_read_op(GATT_READ_CHAR, p_cb->conn_id, p_rpt->char_inst_id,
  BtaGattQueue::ReadCharacteristic(p_cb->conn_id, p_rpt->char_inst_id,
                                   read_report_cb, p_cb);
}

@@ -2007,8 +1847,9 @@ void bta_hh_le_write_rpt(tBTA_HH_DEV_CB* p_cb, tBTA_HH_RPT_TYPE r_type,
  if (p_char && (p_char->properties & GATT_CHAR_PROP_BIT_WRITE_NR))
    write_type = GATT_WRITE_NO_RSP;

  gatt_queue_write_op(GATT_WRITE_CHAR, p_cb->conn_id, p_rpt->char_inst_id,
                      std::move(value), write_type, write_report_cb, p_cb);
  BtaGattQueue::WriteCharacteristic(p_cb->conn_id, p_rpt->char_inst_id,
                                    std::move(value), write_type,
                                    write_report_cb, p_cb);
}

/*******************************************************************************
@@ -2025,8 +1866,8 @@ void bta_hh_le_suspend(tBTA_HH_DEV_CB* p_cb,
  ctrl_type -= BTA_HH_CTRL_SUSPEND;

  // We don't care about response
  gatt_queue_write_op(GATT_WRITE_CHAR, p_cb->conn_id,
                      p_cb->hid_srvc.control_point_handle, {(uint8_t)ctrl_type},
  BtaGattQueue::WriteCharacteristic(
      p_cb->conn_id, p_cb->hid_srvc.control_point_handle, {(uint8_t)ctrl_type},
      GATT_WRITE_NO_RSP, NULL, NULL);
}

+78 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 The Android Open Source Project
 *
 * 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 <vector>

#include <list>
#include <unordered_map>
#include <unordered_set>
#include "bta_gatt_api.h"

/* BTA GATTC implementation does not allow for multiple commands queuing. So one
 * client making calls to BTA_GATTC_ReadCharacteristic, BTA_GATTC_ReadCharDescr,
 * BTA_GATTC_WriteCharValue, BTA_GATTC_WriteCharDescr must wait for the callacks
 * before scheduling next operation.
 *
 * Methods below can be used as replacement to BTA_GATTC_* in BTA app. They do
 * queue the commands if another command is currently being executed.
 *
 * If you decide to use those methods in your app, make sure to not mix it with
 * existing BTA_GATTC_* API.
 */
class BtaGattQueue {
 public:
  static void Clean(uint16_t conn_id);
  static void ReadCharacteristic(uint16_t conn_id, uint16_t handle,
                                 GATT_READ_OP_CB cb, void* cb_data);
  static void ReadDescriptor(uint16_t conn_id, uint16_t handle,
                             GATT_READ_OP_CB cb, void* cb_data);
  static void WriteCharacteristic(uint16_t conn_id, uint16_t handle,
                                  std::vector<uint8_t> value,
                                  tGATT_WRITE_TYPE write_type,
                                  GATT_WRITE_OP_CB cb, void* cb_data);
  static void WriteDescriptor(uint16_t conn_id, uint16_t handle,
                              std::vector<uint8_t> value,
                              tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
                              void* cb_data);

  /* Holds pending GATT operations */
  struct gatt_operation {
    uint8_t type;
    uint16_t handle;
    GATT_READ_OP_CB read_cb;
    void* read_cb_data;
    GATT_WRITE_OP_CB write_cb;
    void* write_cb_data;

    /* write-specific fields */
    tGATT_WRITE_TYPE write_type;
    std::vector<uint8_t> value;
  };

 private:
  static void mark_as_not_executing(uint16_t conn_id);
  static void gatt_execute_next_op(uint16_t conn_id);
  static void gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status,
                                    uint16_t handle, uint16_t len,
                                    uint8_t* value, void* data);
  static void gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status,
                                     uint16_t handle, void* data);

  // maps connection id to operations waiting for execution
  static std::unordered_map<uint16_t, std::list<gatt_operation>> gatt_op_queue;
  // contain connection ids that currently execute operations
  static std::unordered_set<uint16_t> gatt_op_queue_executing;
};
 No newline at end of file