/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2014  Google Inc.
 *
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "att.h"
#include <bluetooth/bluetooth.h>
#include "uuid.h"
#include "gatt-helpers.h"
#include "util.h"
#include "queue.h"
#include "gatt-db.h"
#include "gatt-client.h"

#include <assert.h>
#include <limits.h>
#include <sys/uio.h>

#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#define UUID_BYTES (BT_GATT_UUID_SIZE * sizeof(uint8_t))

#define GATT_SVC_UUID	0x1801
#define SVC_CHNGD_UUID	0x2a05

struct bt_gatt_client {
	struct bt_att *att;
	int ref_count;

	bt_gatt_client_callback_t ready_callback;
	bt_gatt_client_destroy_func_t ready_destroy;
	void *ready_data;

	bt_gatt_client_service_changed_callback_t svc_chngd_callback;
	bt_gatt_client_destroy_func_t svc_chngd_destroy;
	void *svc_chngd_data;

	bt_gatt_client_debug_func_t debug_callback;
	bt_gatt_client_destroy_func_t debug_destroy;
	void *debug_data;

	struct gatt_db *db;
	bool in_init;
	bool ready;

	/*
	 * Queue of long write requests. An error during "prepare write"
	 * requests can result in a cancel through "execute write". To prevent
	 * cancelation of prepared writes to the wrong attribute and multiple
	 * requests to the same attribute that may result in a corrupted final
	 * value, we avoid interleaving prepared writes.
	 */
	struct queue *long_write_queue;
	bool in_long_write;

	unsigned int reliable_write_session_id;

	/* List of registered disconnect/notification/indication callbacks */
	struct queue *notify_list;
	struct queue *notify_chrcs;
	int next_reg_id;
	unsigned int disc_id, notify_id, ind_id;

	/*
	 * Handles of the GATT Service and the Service Changed characteristic
	 * value handle. These will have the value 0 if they are not present on
	 * the remote peripheral.
	 */
	unsigned int svc_chngd_ind_id;
	bool svc_chngd_registered;
	struct queue *svc_chngd_queue;  /* Queued service changed events */
	bool in_svc_chngd;

	/*
	 * List of pending read/write operations. For operations that span
	 * across multiple PDUs, this list provides a mapping from an operation
	 * id to an ATT request id.
	 */
	struct queue *pending_requests;
	unsigned int next_request_id;

	struct bt_gatt_request *discovery_req;
	unsigned int mtu_req_id;
};

struct request {
	struct bt_gatt_client *client;
	bool long_write;
	bool prep_write;
	bool removed;
	int ref_count;
	unsigned int id;
	unsigned int att_id;
	void *data;
	void (*destroy)(void *);
};

static struct request *request_ref(struct request *req)
{
	__sync_fetch_and_add(&req->ref_count, 1);

	return req;
}

static struct request *request_create(struct bt_gatt_client *client)
{
	struct request *req;

	req = new0(struct request, 1);
	if (!req)
		return NULL;

	if (client->next_request_id < 1)
		client->next_request_id = 1;

	queue_push_tail(client->pending_requests, req);
	req->client = client;
	req->id = client->next_request_id++;

	return request_ref(req);
}

static void request_unref(void *data)
{
	struct request *req = data;

	if (__sync_sub_and_fetch(&req->ref_count, 1))
		return;

	if (req->destroy)
		req->destroy(req->data);

	if (!req->removed)
		queue_remove(req->client->pending_requests, req);

	free(req);
}

struct notify_chrc {
	uint16_t value_handle;
	uint16_t ccc_handle;
	uint16_t properties;
	int notify_count;  /* Reference count of registered notify callbacks */

	/* Pending calls to register_notify are queued here so that they can be
	 * processed after a write that modifies the CCC descriptor.
	 */
	struct queue *reg_notify_queue;
	unsigned int ccc_write_id;
};

struct notify_data {
	struct bt_gatt_client *client;
	unsigned int id;
	unsigned int att_id;
	int ref_count;
	struct notify_chrc *chrc;
	bt_gatt_client_register_callback_t callback;
	bt_gatt_client_notify_callback_t notify;
	void *user_data;
	bt_gatt_client_destroy_func_t destroy;
};

static struct notify_data *notify_data_ref(struct notify_data *notify_data)
{
	__sync_fetch_and_add(&notify_data->ref_count, 1);

	return notify_data;
}

static void notify_data_unref(void *data)
{
	struct notify_data *notify_data = data;

	if (__sync_sub_and_fetch(&notify_data->ref_count, 1))
		return;

	if (notify_data->destroy)
		notify_data->destroy(notify_data->user_data);

	free(notify_data);
}

static void find_ccc(struct gatt_db_attribute *attr, void *user_data)
{
	struct gatt_db_attribute **ccc_ptr = user_data;
	bt_uuid_t uuid;

	if (*ccc_ptr)
		return;

	bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);

	if (bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr)))
		return;

	*ccc_ptr = attr;
}

static struct notify_chrc *notify_chrc_create(struct bt_gatt_client *client,
							uint16_t value_handle)
{
	struct gatt_db_attribute *attr, *ccc;
	struct notify_chrc *chrc;
	bt_uuid_t uuid;
	uint8_t properties;

	/* Check that chrc_value_handle belongs to a known characteristic */
	attr = gatt_db_get_attribute(client->db, value_handle - 1);
	if (!attr)
		return NULL;

	bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
	if (bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr)))
		return NULL;

	if (!gatt_db_attribute_get_char_data(attr, NULL, NULL,
							&properties, NULL))
			return NULL;

	chrc = new0(struct notify_chrc, 1);
	if (!chrc)
		return NULL;

	chrc->reg_notify_queue = queue_new();
	if (!chrc->reg_notify_queue) {
		free(chrc);
		return NULL;
	}

	/*
	 * Find the CCC characteristic. Some characteristics that allow
	 * notifications may not have a CCC descriptor. We treat these as
	 * automatically successful.
	 */
	ccc = NULL;
	gatt_db_service_foreach_desc(attr, find_ccc, &ccc);
	if (ccc)
		chrc->ccc_handle = gatt_db_attribute_get_handle(ccc);

	chrc->value_handle = value_handle;
	chrc->properties = properties;

	queue_push_tail(client->notify_chrcs, chrc);

	return chrc;
}

static void notify_chrc_free(void *data)
{
	struct notify_chrc *chrc = data;

	queue_destroy(chrc->reg_notify_queue, notify_data_unref);
	free(chrc);
}

static bool match_notify_data_id(const void *a, const void *b)
{
	const struct notify_data *notify_data = a;
	unsigned int id = PTR_TO_UINT(b);

	return notify_data->id == id;
}

struct handle_range {
	uint16_t start;
	uint16_t end;
};

static bool match_notify_data_handle_range(const void *a, const void *b)
{
	const struct notify_data *notify_data = a;
	struct notify_chrc *chrc = notify_data->chrc;
	const struct handle_range *range = b;

	return chrc->value_handle >= range->start &&
					chrc->value_handle <= range->end;
}

static bool match_notify_chrc_handle_range(const void *a, const void *b)
{
	const struct notify_chrc *chrc = a;
	const struct handle_range *range = b;

	return chrc->value_handle >= range->start &&
					chrc->value_handle <= range->end;
}

static void gatt_client_remove_all_notify_in_range(
				struct bt_gatt_client *client,
				uint16_t start_handle, uint16_t end_handle)
{
	struct handle_range range;

	range.start = start_handle;
	range.end = end_handle;

	queue_remove_all(client->notify_list, match_notify_data_handle_range,
						&range, notify_data_unref);
}

static void gatt_client_remove_notify_chrcs_in_range(
				struct bt_gatt_client *client,
				uint16_t start_handle, uint16_t end_handle)
{
	struct handle_range range;

	range.start = start_handle;
	range.end = end_handle;

	queue_remove_all(client->notify_chrcs, match_notify_chrc_handle_range,
						&range, notify_chrc_free);
}

struct discovery_op;

typedef void (*discovery_op_complete_func_t)(struct discovery_op *op,
							bool success,
							uint8_t att_ecode);
typedef void (*discovery_op_fail_func_t)(struct discovery_op *op);

struct discovery_op {
	struct bt_gatt_client *client;
	struct queue *pending_svcs;
	struct queue *pending_chrcs;
	struct queue *tmp_queue;
	struct gatt_db_attribute *cur_svc;
	bool success;
	uint16_t start;
	uint16_t end;
	int ref_count;
	discovery_op_complete_func_t complete_func;
	discovery_op_fail_func_t failure_func;
};

static void discovery_op_free(struct discovery_op *op)
{
	queue_destroy(op->pending_svcs, NULL);
	queue_destroy(op->pending_chrcs, free);
	queue_destroy(op->tmp_queue, NULL);
	free(op);
}

static struct discovery_op *discovery_op_create(struct bt_gatt_client *client,
				uint16_t start, uint16_t end,
				discovery_op_complete_func_t complete_func,
				discovery_op_fail_func_t failure_func)
{
	struct discovery_op *op;

	op = new0(struct discovery_op, 1);
	if (!op)
		return NULL;

	op->pending_svcs = queue_new();
	if (!op->pending_svcs)
		goto fail;

	op->pending_chrcs = queue_new();
	if (!op->pending_chrcs)
		goto fail;

	op->tmp_queue = queue_new();
	if (!op->tmp_queue)
		goto fail;

	op->client = client;
	op->complete_func = complete_func;
	op->failure_func = failure_func;
	op->start = start;
	op->end = end;

	return op;

fail:
	discovery_op_free(op);
	return NULL;
}

static struct discovery_op *discovery_op_ref(struct discovery_op *op)
{
	__sync_fetch_and_add(&op->ref_count, 1);

	return op;
}

static void discovery_op_unref(void *data)
{
	struct discovery_op *op = data;

	if (__sync_sub_and_fetch(&op->ref_count, 1))
		return;

	if (!op->success)
		op->failure_func(op);

	discovery_op_free(op);
}

static void discovery_req_clear(struct bt_gatt_client *client)
{
	if (!client->discovery_req)
		return;

	bt_gatt_request_unref(client->discovery_req);
	client->discovery_req = NULL;
}

static void discover_chrcs_cb(bool success, uint8_t att_ecode,
						struct bt_gatt_result *result,
						void *user_data);

static void discover_incl_cb(bool success, uint8_t att_ecode,
				struct bt_gatt_result *result, void *user_data)
{
	struct discovery_op *op = user_data;
	struct bt_gatt_client *client = op->client;
	struct bt_gatt_iter iter;
	struct gatt_db_attribute *attr, *tmp;
	uint16_t handle, start, end;
	uint128_t u128;
	bt_uuid_t uuid;
	char uuid_str[MAX_LEN_UUID_STR];
	unsigned int includes_count, i;

	discovery_req_clear(client);

	if (!success) {
		if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND)
			goto next;

		goto failed;
	}

	/* Get the currently processed service */
	attr = op->cur_svc;
	if (!attr)
		goto failed;

	if (!result || !bt_gatt_iter_init(&iter, result))
		goto failed;

	includes_count = bt_gatt_result_included_count(result);
	if (includes_count == 0)
		goto failed;

	util_debug(client->debug_callback, client->debug_data,
						"Included services found: %u",
						includes_count);

	for (i = 0; i < includes_count; i++) {
		if (!bt_gatt_iter_next_included_service(&iter, &handle, &start,
							&end, u128.data))
			break;

		bt_uuid128_create(&uuid, u128);

		/* Log debug message */
		bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
		util_debug(client->debug_callback, client->debug_data,
				"handle: 0x%04x, start: 0x%04x, end: 0x%04x,"
				"uuid: %s", handle, start, end, uuid_str);

		tmp = gatt_db_get_attribute(client->db, start);
		if (!tmp)
			goto failed;

		tmp = gatt_db_service_add_included(attr, tmp);
		if (!tmp)
			goto failed;

		/*
		 * GATT requires that all include definitions precede
		 * characteristic declarations. Based on the order we're adding
		 * these entries, the correct handle must be assigned to the new
		 * attribute.
		 */
		if (gatt_db_attribute_get_handle(tmp) != handle)
			goto failed;
	}

next:
	/* Move on to the next service */
	attr = queue_pop_head(op->pending_svcs);
	if (!attr) {
		struct queue *tmp_queue;

		tmp_queue = op->pending_svcs;
		op->pending_svcs = op->tmp_queue;
		op->tmp_queue = tmp_queue;

		/*
		 * We have processed all include definitions. Move on to
		 * characteristics.
		 */
		attr = queue_pop_head(op->pending_svcs);
		if (!attr)
			goto failed;

		if (!gatt_db_attribute_get_service_handles(attr, &start, &end))
			goto failed;

		op->cur_svc = attr;

		client->discovery_req = bt_gatt_discover_characteristics(
							client->att,
							start, end,
							discover_chrcs_cb,
							discovery_op_ref(op),
							discovery_op_unref);
		if (client->discovery_req)
			return;

		util_debug(client->debug_callback, client->debug_data,
				"Failed to start characteristic discovery");
		discovery_op_unref(op);
		goto failed;
	}

	queue_push_tail(op->tmp_queue, attr);
	op->cur_svc = attr;
	if (!gatt_db_attribute_get_service_handles(attr, &start, &end))
		goto failed;

	if (start == end)
		goto next;

	client->discovery_req = bt_gatt_discover_included_services(client->att,
							start, end,
							discover_incl_cb,
							discovery_op_ref(op),
							discovery_op_unref);
	if (client->discovery_req)
		return;

	util_debug(client->debug_callback, client->debug_data,
					"Failed to start included discovery");
	discovery_op_unref(op);

failed:
	op->success = false;
	op->complete_func(op, false, att_ecode);
}

struct chrc {
	uint16_t start_handle;
	uint16_t end_handle;
	uint16_t value_handle;
	uint8_t properties;
	bt_uuid_t uuid;
};

static void discover_descs_cb(bool success, uint8_t att_ecode,
						struct bt_gatt_result *result,
						void *user_data);

static bool discover_descs(struct discovery_op *op, bool *discovering)
{
	struct bt_gatt_client *client = op->client;
	struct gatt_db_attribute *attr;
	struct chrc *chrc_data;
	uint16_t desc_start;

	*discovering = false;

	while ((chrc_data = queue_pop_head(op->pending_chrcs))) {
		attr = gatt_db_service_add_characteristic(op->cur_svc,
							&chrc_data->uuid, 0,
							chrc_data->properties,
							NULL, NULL, NULL);

		if (!attr)
			goto failed;

		if (gatt_db_attribute_get_handle(attr) !=
							chrc_data->value_handle)
			goto failed;

		desc_start = chrc_data->value_handle + 1;

		if (desc_start > chrc_data->end_handle) {
			free(chrc_data);
			continue;
		}

		client->discovery_req = bt_gatt_discover_descriptors(
							client->att, desc_start,
							chrc_data->end_handle,
							discover_descs_cb,
							discovery_op_ref(op),
							discovery_op_unref);
		if (client->discovery_req) {
			*discovering = true;
			goto done;
		}

		util_debug(client->debug_callback, client->debug_data,
					"Failed to start descriptor discovery");
		discovery_op_unref(op);

		goto failed;
	}

done:
	free(chrc_data);
	return true;

failed:
	free(chrc_data);
	return false;
}

static void discover_descs_cb(bool success, uint8_t att_ecode,
						struct bt_gatt_result *result,
						void *user_data)
{
	struct discovery_op *op = user_data;
	struct bt_gatt_client *client = op->client;
	struct bt_gatt_iter iter;
	struct gatt_db_attribute *attr;
	uint16_t handle, start, end;
	uint128_t u128;
	bt_uuid_t uuid;
	char uuid_str[MAX_LEN_UUID_STR];
	unsigned int desc_count;
	bool discovering;

	discovery_req_clear(client);

	if (!success) {
		if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) {
			success = true;
			goto next;
		}

		goto done;
	}

	if (!result || !bt_gatt_iter_init(&iter, result))
		goto failed;

	desc_count = bt_gatt_result_descriptor_count(result);
	if (desc_count == 0)
		goto failed;

	util_debug(client->debug_callback, client->debug_data,
					"Descriptors found: %u", desc_count);

	while (bt_gatt_iter_next_descriptor(&iter, &handle, u128.data)) {
		bt_uuid128_create(&uuid, u128);

		/* Log debug message */
		bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
		util_debug(client->debug_callback, client->debug_data,
						"handle: 0x%04x, uuid: %s",
						handle, uuid_str);

		attr = gatt_db_service_add_descriptor(op->cur_svc, &uuid, 0,
							NULL, NULL, NULL);
		if (!attr)
			goto failed;

		if (gatt_db_attribute_get_handle(attr) != handle)
			goto failed;
	}

	if (!discover_descs(op, &discovering))
		goto failed;

	if (discovering)
		return;

next:
	/* Done with the current service */
	gatt_db_service_set_active(op->cur_svc, true);

	attr = queue_pop_head(op->pending_svcs);
	if (!attr)
		goto done;

	if (!gatt_db_attribute_get_service_handles(attr, &start, &end))
		goto failed;

	if (start == end)
		goto next;

	/* Move on to the next service */
	op->cur_svc = attr;

	client->discovery_req = bt_gatt_discover_characteristics(client->att,
							start, end,
							discover_chrcs_cb,
							discovery_op_ref(op),
							discovery_op_unref);
	if (client->discovery_req)
		return;

	util_debug(client->debug_callback, client->debug_data,
				"Failed to start characteristic discovery");
	discovery_op_unref(op);

failed:
	success = false;

done:
	op->success = success;
	op->complete_func(op, success, att_ecode);
}

static void discover_chrcs_cb(bool success, uint8_t att_ecode,
						struct bt_gatt_result *result,
						void *user_data)
{
	struct discovery_op *op = user_data;
	struct bt_gatt_client *client = op->client;
	struct bt_gatt_iter iter;
	struct gatt_db_attribute *attr;
	struct chrc *chrc_data;
	uint16_t start, end, value;
	uint8_t properties;
	uint128_t u128;
	bt_uuid_t uuid;
	char uuid_str[MAX_LEN_UUID_STR];
	unsigned int chrc_count;
	bool discovering;

	discovery_req_clear(client);

	if (!success) {
		if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) {
			success = true;
			goto next;
		}

		goto done;
	}

	if (!op->cur_svc || !result || !bt_gatt_iter_init(&iter, result))
		goto failed;

	chrc_count = bt_gatt_result_characteristic_count(result);
	util_debug(client->debug_callback, client->debug_data,
				"Characteristics found: %u", chrc_count);

	if (chrc_count == 0)
		goto failed;

	while (bt_gatt_iter_next_characteristic(&iter, &start, &end, &value,
						&properties, u128.data)) {
		bt_uuid128_create(&uuid, u128);

		/* Log debug message */
		bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
		util_debug(client->debug_callback, client->debug_data,
				"start: 0x%04x, end: 0x%04x, value: 0x%04x, "
				"props: 0x%02x, uuid: %s",
				start, end, value, properties, uuid_str);

		chrc_data = new0(struct chrc, 1);
		if (!chrc_data)
			goto failed;

		chrc_data->start_handle = start;
		chrc_data->end_handle = end;
		chrc_data->value_handle = value;
		chrc_data->properties = properties;
		chrc_data->uuid = uuid;

		queue_push_tail(op->pending_chrcs, chrc_data);
	}

	/*
	 * Sequentially discover descriptors for each characteristic and insert
	 * the characteristics into the database as we proceed.
	 */
	if (!discover_descs(op, &discovering))
		goto failed;

	if (discovering)
		return;

next:
	/* Done with the current service */
	gatt_db_service_set_active(op->cur_svc, true);

	attr = queue_pop_head(op->pending_svcs);
	if (!attr)
		goto done;

	if (!gatt_db_attribute_get_service_handles(attr, &start, &end))
		goto failed;

	if (start == end)
		goto next;

	/* Move on to the next service */
	op->cur_svc = attr;

	client->discovery_req = bt_gatt_discover_characteristics(client->att,
							start, end,
							discover_chrcs_cb,
							discovery_op_ref(op),
							discovery_op_unref);
	if (client->discovery_req)
		return;

	util_debug(client->debug_callback, client->debug_data,
				"Failed to start characteristic discovery");
	discovery_op_unref(op);

failed:
	success = false;

done:
	op->success = success;
	op->complete_func(op, success, att_ecode);
}

static void discover_secondary_cb(bool success, uint8_t att_ecode,
						struct bt_gatt_result *result,
						void *user_data)
{
	struct discovery_op *op = user_data;
	struct bt_gatt_client *client = op->client;
	struct bt_gatt_iter iter;
	struct gatt_db_attribute *attr;
	uint16_t start, end;
	uint128_t u128;
	bt_uuid_t uuid;
	char uuid_str[MAX_LEN_UUID_STR];

	discovery_req_clear(client);

	if (!success) {
		util_debug(client->debug_callback, client->debug_data,
					"Secondary service discovery failed."
					" ATT ECODE: 0x%02x", att_ecode);
		switch (att_ecode) {
		case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND:
		case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE:
			goto next;
		default:
			goto done;
		}
	}

	if (!result || !bt_gatt_iter_init(&iter, result)) {
		success = false;
		goto done;
	}

	util_debug(client->debug_callback, client->debug_data,
					"Secondary services found: %u",
					bt_gatt_result_service_count(result));

	while (bt_gatt_iter_next_service(&iter, &start, &end, u128.data)) {
		bt_uuid128_create(&uuid, u128);

		/* Log debug message */
		bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
		util_debug(client->debug_callback, client->debug_data,
				"start: 0x%04x, end: 0x%04x, uuid: %s",
				start, end, uuid_str);

		/* Store the service */
		attr = gatt_db_insert_service(client->db, start, &uuid, false,
							end - start + 1);
		if (!attr) {
			util_debug(client->debug_callback, client->debug_data,
						"Failed to create service");
			success = false;
			goto done;
		}

		queue_push_tail(op->pending_svcs, attr);
	}

next:
	/* Sequentially discover included services */
	attr = queue_pop_head(op->pending_svcs);

	/* Complete with success if queue is empty */
	if (!attr)
		goto done;

	/*
	 * Store the service in the tmp queue to be reused during
	 * characteristics discovery later.
	 */
	queue_push_tail(op->tmp_queue, attr);
	op->cur_svc = attr;

	if (!gatt_db_attribute_get_service_handles(attr, &start, &end)) {
		success = false;
		goto done;
	}

	client->discovery_req = bt_gatt_discover_included_services(client->att,
							start, end,
							discover_incl_cb,
							discovery_op_ref(op),
							discovery_op_unref);
	if (client->discovery_req)
		return;

	util_debug(client->debug_callback, client->debug_data,
				"Failed to start included services discovery");
	discovery_op_unref(op);

done:
	op->success = success;
	op->complete_func(op, success, att_ecode);
}

static void discover_primary_cb(bool success, uint8_t att_ecode,
						struct bt_gatt_result *result,
						void *user_data)
{
	struct discovery_op *op = user_data;
	struct bt_gatt_client *client = op->client;
	struct bt_gatt_iter iter;
	struct gatt_db_attribute *attr;
	uint16_t start, end;
	uint128_t u128;
	bt_uuid_t uuid;
	char uuid_str[MAX_LEN_UUID_STR];

	discovery_req_clear(client);

	if (!success) {
		util_debug(client->debug_callback, client->debug_data,
					"Primary service discovery failed."
					" ATT ECODE: 0x%02x", att_ecode);
		goto secondary;
	}

	if (!result || !bt_gatt_iter_init(&iter, result)) {
		success = false;
		goto done;
	}

	util_debug(client->debug_callback, client->debug_data,
					"Primary services found: %u",
					bt_gatt_result_service_count(result));

	while (bt_gatt_iter_next_service(&iter, &start, &end, u128.data)) {
		bt_uuid128_create(&uuid, u128);

		/* Log debug message. */
		bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
		util_debug(client->debug_callback, client->debug_data,
				"start: 0x%04x, end: 0x%04x, uuid: %s",
				start, end, uuid_str);

		attr = gatt_db_insert_service(client->db, start, &uuid, true,
							end - start + 1);
		if (!attr) {
			util_debug(client->debug_callback, client->debug_data,
						"Failed to store service");
			success = false;
			goto done;
		}

		queue_push_tail(op->pending_svcs, attr);
	}

secondary:
	/* Discover secondary services */
	client->discovery_req = bt_gatt_discover_secondary_services(client->att,
						NULL, op->start, op->end,
						discover_secondary_cb,
						discovery_op_ref(op),
						discovery_op_unref);
	if (client->discovery_req)
		return;

	util_debug(client->debug_callback, client->debug_data,
				"Failed to start secondary service discovery");
	discovery_op_unref(op);
	success = false;

done:
	op->success = success;
	op->complete_func(op, success, att_ecode);
}

static void notify_client_ready(struct bt_gatt_client *client, bool success,
							uint8_t att_ecode)
{
	if (!client->ready_callback)
		return;

	bt_gatt_client_ref(client);
	client->ready_callback(success, att_ecode, client->ready_data);
	bt_gatt_client_unref(client);
}

static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
{
	struct discovery_op *op = user_data;
	struct bt_gatt_client *client = op->client;

	op->success = success;
	client->mtu_req_id = 0;

	if (!success) {
		util_debug(client->debug_callback, client->debug_data,
				"MTU Exchange failed. ATT ECODE: 0x%02x",
				att_ecode);

		client->in_init = false;
		notify_client_ready(client, success, att_ecode);

		return;
	}

	util_debug(client->debug_callback, client->debug_data,
					"MTU exchange complete, with MTU: %u",
					bt_att_get_mtu(client->att));

	/* Don't do discovery if the database was pre-populated */
	if (!gatt_db_isempty(client->db)) {
		op->complete_func(op, true, 0);
		return;
	}

	client->discovery_req = bt_gatt_discover_all_primary_services(
							client->att, NULL,
							discover_primary_cb,
							discovery_op_ref(op),
							discovery_op_unref);
	if (client->discovery_req)
		return;

	util_debug(client->debug_callback, client->debug_data,
			"Failed to initiate primary service discovery");

	client->in_init = false;
	notify_client_ready(client, false, att_ecode);

	discovery_op_unref(op);
}

struct service_changed_op {
	struct bt_gatt_client *client;
	uint16_t start_handle;
	uint16_t end_handle;
};

static void service_changed_reregister_cb(uint16_t att_ecode, void *user_data)
{
	struct bt_gatt_client *client = user_data;

	if (!att_ecode) {
		util_debug(client->debug_callback, client->debug_data,
			"Re-registered handler for \"Service Changed\" after "
			"change in GATT service");
		client->svc_chngd_registered = true;
		return;
	}

	util_debug(client->debug_callback, client->debug_data,
		"Failed to register handler for \"Service Changed\"");
	client->svc_chngd_ind_id = 0;
}

static void process_service_changed(struct bt_gatt_client *client,
							uint16_t start_handle,
							uint16_t end_handle);
static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
					uint16_t length, void *user_data);

static void get_first_attribute(struct gatt_db_attribute *attrib,
								void *user_data)
{
	struct gatt_db_attribute **stored = user_data;

	if (*stored)
		return;

	*stored = attrib;
}

static void service_changed_complete(struct discovery_op *op, bool success,
							uint8_t att_ecode)
{
	struct bt_gatt_client *client = op->client;
	struct service_changed_op *next_sc_op;
	uint16_t start_handle = op->start;
	uint16_t end_handle = op->end;
	struct gatt_db_attribute *attr = NULL;
	bt_uuid_t uuid;

	client->in_svc_chngd = false;

	if (!success && att_ecode != BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) {
		util_debug(client->debug_callback, client->debug_data,
			"Failed to discover services within changed range - "
			"error: 0x%02x", att_ecode);

		gatt_db_clear_range(client->db, start_handle, end_handle);
	}

	/* Notify the upper layer of changed services */
	if (client->svc_chngd_callback)
		client->svc_chngd_callback(start_handle, end_handle,
							client->svc_chngd_data);

	/* Process any queued events */
	next_sc_op = queue_pop_head(client->svc_chngd_queue);
	if (next_sc_op) {
		process_service_changed(client, next_sc_op->start_handle,
							next_sc_op->end_handle);
		free(next_sc_op);
		return;
	}

	bt_uuid16_create(&uuid, SVC_CHNGD_UUID);

	gatt_db_find_by_type(client->db, start_handle, end_handle, &uuid,
						get_first_attribute, &attr);
	if (!attr)
		return;

	/* The GATT service was modified. Re-register the handler for
	 * indications from the "Service Changed" characteristic.
	 */
	client->svc_chngd_registered = false;
	client->svc_chngd_ind_id = bt_gatt_client_register_notify(client,
					gatt_db_attribute_get_handle(attr),
					service_changed_reregister_cb,
					service_changed_cb,
					client, NULL);
	if (client->svc_chngd_ind_id)
		return;

	util_debug(client->debug_callback, client->debug_data,
		"Failed to re-register handler for \"Service Changed\"");
}

static void service_changed_failure(struct discovery_op *op)
{
	struct bt_gatt_client *client = op->client;

	gatt_db_clear_range(client->db, op->start, op->end);
}

static void process_service_changed(struct bt_gatt_client *client,
							uint16_t start_handle,
							uint16_t end_handle)
{
	struct discovery_op *op;

	/* Invalidate and remove all effected notify callbacks */
	gatt_client_remove_all_notify_in_range(client, start_handle,
								end_handle);
	gatt_client_remove_notify_chrcs_in_range(client, start_handle,
								end_handle);

	/* Remove all services that overlap the modified range since we'll
	 * rediscover them
	 */
	gatt_db_clear_range(client->db, start_handle, end_handle);

	op = discovery_op_create(client, start_handle, end_handle,
						service_changed_complete,
						service_changed_failure);
	if (!op)
		goto fail;

	client->discovery_req = bt_gatt_discover_primary_services(client->att,
						NULL, start_handle, end_handle,
						discover_primary_cb,
						discovery_op_ref(op),
						discovery_op_unref);
	if (client->discovery_req) {
		client->in_svc_chngd = true;
		return;
	}

	discovery_op_free(op);

fail:
	util_debug(client->debug_callback, client->debug_data,
					"Failed to initiate service discovery"
					" after Service Changed");
}

static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
					uint16_t length, void *user_data)
{
	struct bt_gatt_client *client = user_data;
	struct service_changed_op *op;
	uint16_t start, end;

	if (length != 4)
		return;

	start = get_le16(value);
	end = get_le16(value + 2);

	if (start > end) {
		util_debug(client->debug_callback, client->debug_data,
			"Service Changed received with invalid handles");
		return;
	}

	util_debug(client->debug_callback, client->debug_data,
			"Service Changed received - start: 0x%04x end: 0x%04x",
			start, end);

	if (!client->in_svc_chngd) {
		process_service_changed(client, start, end);
		return;
	}

	op = new0(struct service_changed_op, 1);
	if (!op)
		return;

	op->start_handle = start;
	op->end_handle = end;

	queue_push_tail(client->svc_chngd_queue, op);
}

static void service_changed_register_cb(uint16_t att_ecode, void *user_data)
{
	bool success;
	struct bt_gatt_client *client = user_data;

	if (att_ecode) {
		util_debug(client->debug_callback, client->debug_data,
			"Failed to register handler for \"Service Changed\"");
		success = false;
		client->svc_chngd_ind_id = 0;
		goto done;
	}

	client->svc_chngd_registered = true;
	client->ready = true;
	success = true;
	util_debug(client->debug_callback, client->debug_data,
			"Registered handler for \"Service Changed\": %u",
			client->svc_chngd_ind_id);

done:
	notify_client_ready(client, success, att_ecode);
}

static void init_complete(struct discovery_op *op, bool success,
							uint8_t att_ecode)
{
	struct bt_gatt_client *client = op->client;
	struct gatt_db_attribute *attr = NULL;
	bt_uuid_t uuid;

	client->in_init = false;

	if (!success)
		goto fail;

	bt_uuid16_create(&uuid, SVC_CHNGD_UUID);

	gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
						get_first_attribute, &attr);
	if (!attr) {
		client->ready = true;
		goto done;
	}

	/* Register an indication handler for the "Service Changed"
	 * characteristic and report ready only if the handler is registered
	 * successfully. Temporarily set "ready" to true so that we can register
	 * the handler using the existing framework.
	 */
	client->ready = true;
	client->svc_chngd_ind_id = bt_gatt_client_register_notify(client,
					gatt_db_attribute_get_handle(attr),
					service_changed_register_cb,
					service_changed_cb,
					client, NULL);

	if (!client->svc_chngd_registered)
		client->ready = false;

	if (client->svc_chngd_ind_id)
		return;

	util_debug(client->debug_callback, client->debug_data,
			"Failed to register handler for \"Service Changed\"");
	success = false;

fail:
	util_debug(client->debug_callback, client->debug_data,
			"Failed to initialize gatt-client");

	op->success = false;

done:
	notify_client_ready(client, success, att_ecode);
}

static void init_fail(struct discovery_op *op)
{
	struct bt_gatt_client *client = op->client;

	gatt_db_clear(client->db);
}

static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
{
	struct discovery_op *op;

	if (client->in_init || client->ready)
		return false;

	op = discovery_op_create(client, 0x0001, 0xffff, init_complete,
								init_fail);
	if (!op)
		return false;

	/* Configure the MTU */
	client->mtu_req_id = bt_gatt_exchange_mtu(client->att,
						MAX(BT_ATT_DEFAULT_LE_MTU, mtu),
						exchange_mtu_cb,
						discovery_op_ref(op),
						discovery_op_unref);
	if (!client->mtu_req_id) {
		discovery_op_free(op);
		return false;
	}

	client->in_init = true;

	return true;
}

struct pdu_data {
	const void *pdu;
	uint16_t length;
};

static void complete_notify_request(void *data)
{
	struct notify_data *notify_data = data;

	/* Increment the per-characteristic ref count of notify handlers */
	__sync_fetch_and_add(&notify_data->chrc->notify_count, 1);

	notify_data->att_id = 0;
	notify_data->callback(0, notify_data->user_data);
}

static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable,
						bt_att_response_func_t callback)
{
	uint8_t pdu[4];
	unsigned int att_id;

	assert(notify_data->chrc->ccc_handle);
	memset(pdu, 0, sizeof(pdu));
	put_le16(notify_data->chrc->ccc_handle, pdu);

	if (enable) {
		/* Try to enable notifications and/or indications based on
		 * whatever the characteristic supports.
		 */
		if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_NOTIFY)
			pdu[2] = 0x01;

		if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_INDICATE)
			pdu[2] |= 0x02;

		if (!pdu[2])
			return false;
	}

	att_id = bt_att_send(notify_data->client->att, BT_ATT_OP_WRITE_REQ,
						pdu, sizeof(pdu), callback,
						notify_data_ref(notify_data),
						notify_data_unref);
	notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;

	return !!att_id;
}

static uint8_t process_error(const void *pdu, uint16_t length)
{
	const struct bt_att_pdu_error_rsp *error_pdu;

	if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp))
		return 0;

	error_pdu = pdu;

	return error_pdu->ecode;
}

static void enable_ccc_callback(uint8_t opcode, const void *pdu,
					uint16_t length, void *user_data)
{
	struct notify_data *notify_data = user_data;
	uint16_t att_ecode;

	assert(!notify_data->chrc->notify_count);
	assert(notify_data->chrc->ccc_write_id);

	notify_data->chrc->ccc_write_id = 0;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		att_ecode = process_error(pdu, length);

		/* Failed to enable. Complete the current request and move on to
		 * the next one in the queue. If there was an error sending the
		 * write request, then just move on to the next queued entry.
		 */
		queue_remove(notify_data->client->notify_list, notify_data);
		notify_data->callback(att_ecode, notify_data->user_data);

		while ((notify_data = queue_pop_head(
					notify_data->chrc->reg_notify_queue))) {

			if (notify_data_write_ccc(notify_data, true,
							enable_ccc_callback))
				return;
		}

		return;
	}

	/* Success! Report success for all remaining requests. */
	bt_gatt_client_ref(notify_data->client);

	complete_notify_request(notify_data);
	queue_remove_all(notify_data->chrc->reg_notify_queue, NULL, NULL,
						complete_notify_request);

	bt_gatt_client_unref(notify_data->client);
}

static void disable_ccc_callback(uint8_t opcode, const void *pdu,
					uint16_t length, void *user_data)
{
	struct notify_data *notify_data = user_data;
	struct notify_data *next_data;

	assert(!notify_data->chrc->notify_count);
	assert(notify_data->chrc->ccc_write_id);

	notify_data->chrc->ccc_write_id = 0;

	/* This is a best effort procedure, so ignore errors and process any
	 * queued requests.
	 */
	while (1) {
		next_data = queue_pop_head(notify_data->chrc->reg_notify_queue);
		if (!next_data || notify_data_write_ccc(notify_data, true,
							enable_ccc_callback))
			return;
	}
}

static void complete_unregister_notify(void *data)
{
	struct notify_data *notify_data = data;

	/*
	 * If a procedure to enable the CCC is still pending, then cancel it and
	 * return.
	 */
	if (notify_data->att_id) {
		bt_att_cancel(notify_data->client->att, notify_data->att_id);
		goto done;
	}

	if (__sync_sub_and_fetch(&notify_data->chrc->notify_count, 1) ||
						!notify_data->chrc->ccc_handle)
		goto done;

	if (notify_data_write_ccc(notify_data, false, disable_ccc_callback))
		return;

done:
	notify_data_unref(notify_data);
}

static void notify_handler(void *data, void *user_data)
{
	struct notify_data *notify_data = data;
	struct pdu_data *pdu_data = user_data;
	uint16_t value_handle;
	const uint8_t *value = NULL;

	value_handle = get_le16(pdu_data->pdu);

	if (notify_data->chrc->value_handle != value_handle)
		return;

	if (pdu_data->length > 2)
		value = pdu_data->pdu + 2;

	/*
	 * Even if the notify data has a pending ATT request to write to the
	 * CCC, there is really no reason not to notify the handlers.
	 */
	if (notify_data->notify)
		notify_data->notify(value_handle, value, pdu_data->length - 2,
							notify_data->user_data);
}

static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct bt_gatt_client *client = user_data;
	struct pdu_data pdu_data;

	bt_gatt_client_ref(client);

	memset(&pdu_data, 0, sizeof(pdu_data));
	pdu_data.pdu = pdu;
	pdu_data.length = length;

	queue_foreach(client->notify_list, notify_handler, &pdu_data);

	if (opcode == BT_ATT_OP_HANDLE_VAL_IND)
		bt_att_send(client->att, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0,
							NULL, NULL, NULL);

	bt_gatt_client_unref(client);
}

static void notify_data_cleanup(void *data)
{
	struct notify_data *notify_data = data;

	if (notify_data->att_id)
		bt_att_cancel(notify_data->client->att, notify_data->att_id);

	notify_data_unref(notify_data);
}

static void bt_gatt_client_free(struct bt_gatt_client *client)
{
	bt_gatt_client_cancel_all(client);

	queue_destroy(client->notify_list, notify_data_cleanup);

	if (client->ready_destroy)
		client->ready_destroy(client->ready_data);

	if (client->debug_destroy)
		client->debug_destroy(client->debug_data);

	if (client->att) {
		bt_att_unregister_disconnect(client->att, client->disc_id);
		bt_att_unregister(client->att, client->notify_id);
		bt_att_unregister(client->att, client->ind_id);
		bt_att_unref(client->att);
	}

	gatt_db_unref(client->db);

	queue_destroy(client->svc_chngd_queue, free);
	queue_destroy(client->long_write_queue, request_unref);
	queue_destroy(client->notify_chrcs, notify_chrc_free);
	queue_destroy(client->pending_requests, request_unref);

	free(client);
}

static void att_disconnect_cb(int err, void *user_data)
{
	struct bt_gatt_client *client = user_data;
	bool in_init = client->in_init;

	client->disc_id = 0;

	bt_att_unref(client->att);
	client->att = NULL;

	client->in_init = false;
	client->ready = false;

	if (in_init)
		notify_client_ready(client, false, 0);
}

struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db,
							struct bt_att *att,
							uint16_t mtu)
{
	struct bt_gatt_client *client;

	if (!att || !db)
		return NULL;

	client = new0(struct bt_gatt_client, 1);
	if (!client)
		return NULL;

	client->disc_id = bt_att_register_disconnect(att, att_disconnect_cb,
								client, NULL);
	if (!client->disc_id)
		goto fail;

	client->long_write_queue = queue_new();
	if (!client->long_write_queue)
		goto fail;

	client->svc_chngd_queue = queue_new();
	if (!client->svc_chngd_queue)
		goto fail;

	client->notify_list = queue_new();
	if (!client->notify_list)
		goto fail;

	client->notify_chrcs = queue_new();
	if (!client->notify_chrcs)
		goto fail;

	client->pending_requests = queue_new();
	if (!client->pending_requests)
		goto fail;

	client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT,
						notify_cb, client, NULL);
	if (!client->notify_id)
		goto fail;

	client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_IND,
						notify_cb, client, NULL);
	if (!client->ind_id)
		goto fail;

	client->att = bt_att_ref(att);
	client->db = gatt_db_ref(db);

	if (!gatt_client_init(client, mtu))
		goto fail;

	return bt_gatt_client_ref(client);

fail:
	bt_gatt_client_free(client);
	return NULL;
}

struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client)
{
	if (!client)
		return NULL;

	__sync_fetch_and_add(&client->ref_count, 1);

	return client;
}

void bt_gatt_client_unref(struct bt_gatt_client *client)
{
	if (!client)
		return;

	if (__sync_sub_and_fetch(&client->ref_count, 1))
		return;

	bt_gatt_client_free(client);
}

bool bt_gatt_client_is_ready(struct bt_gatt_client *client)
{
	return (client && client->ready);
}

bool bt_gatt_client_set_ready_handler(struct bt_gatt_client *client,
					bt_gatt_client_callback_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy)
{
	if (!client)
		return false;

	if (client->ready_destroy)
		client->ready_destroy(client->ready_data);

	client->ready_callback = callback;
	client->ready_destroy = destroy;
	client->ready_data = user_data;

	return true;
}

bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client,
			bt_gatt_client_service_changed_callback_t callback,
			void *user_data,
			bt_gatt_client_destroy_func_t destroy)
{
	if (!client)
		return false;

	if (client->svc_chngd_destroy)
		client->svc_chngd_destroy(client->svc_chngd_data);

	client->svc_chngd_callback = callback;
	client->svc_chngd_destroy = destroy;
	client->svc_chngd_data = user_data;

	return true;
}

bool bt_gatt_client_set_debug(struct bt_gatt_client *client,
					bt_gatt_client_debug_func_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy) {
	if (!client)
		return false;

	if (client->debug_destroy)
		client->debug_destroy(client->debug_data);

	client->debug_callback = callback;
	client->debug_destroy = destroy;
	client->debug_data = user_data;

	return true;
}

uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client)
{
	if (!client || !client->att)
		return 0;

	return bt_att_get_mtu(client->att);
}

static bool match_req_id(const void *a, const void *b)
{
	const struct request *req = a;
	unsigned int id = PTR_TO_UINT(b);

	return req->id == id;
}

static void cancel_long_write_cb(uint8_t opcode, const void *pdu, uint16_t len,
								void *user_data)
{
	struct bt_gatt_client *client = user_data;

	if (queue_isempty(client->long_write_queue))
		client->in_long_write = false;
}

static bool cancel_long_write_req(struct bt_gatt_client *client,
							struct request *req)
{
	uint8_t pdu = 0x00;

	/*
	 * att_id == 0 means that request has been queued and no prepare write
	 * has been sent so far.Let's just remove if from the queue.
	 * Otherwise execute write needs to be send.
	 */
	if (!req->att_id)
		return queue_remove(client->long_write_queue, req);

	return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu,
							sizeof(pdu),
							cancel_long_write_cb,
							client, NULL);

}

static void cancel_prep_write_cb(uint8_t opcode, const void *pdu, uint16_t len,
								void *user_data)
{
	struct request *req = user_data;
	struct bt_gatt_client *client = req->client;

	client->reliable_write_session_id = 0;
}

static bool cancel_prep_write_session(struct bt_gatt_client *client,
							struct request *req)
{
	uint8_t pdu = 0x00;

	return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu,
							sizeof(pdu),
							cancel_prep_write_cb,
							req, request_unref);
}

bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id)
{
	struct request *req;

	if (!client || !id || !client->att)
		return false;

	req = queue_remove_if(client->pending_requests, match_req_id,
							UINT_TO_PTR(id));
	if (!req)
		return false;

	req->removed = true;

	if (!bt_att_cancel(client->att, req->att_id) && !req->long_write &&
							!req->prep_write)
		return false;

	/* If this was a long-write, we need to abort all prepared writes */
	if (req->long_write)
		return cancel_long_write_req(client, req);

	if (req->prep_write)
		return cancel_prep_write_session(client, req);

	return true;
}

static void cancel_request(void *data)
{
	struct request *req = data;

	req->removed = true;

	bt_att_cancel(req->client->att, req->att_id);

	if (req->long_write)
		cancel_long_write_req(req->client, req);

	if (req->prep_write)
		cancel_prep_write_session(req->client, req);
}

bool bt_gatt_client_cancel_all(struct bt_gatt_client *client)
{
	if (!client || !client->att)
		return false;

	queue_remove_all(client->pending_requests, NULL, NULL, cancel_request);

	if (client->discovery_req) {
		bt_gatt_request_cancel(client->discovery_req);
		bt_gatt_request_unref(client->discovery_req);
		client->discovery_req = NULL;
	}

	if (client->mtu_req_id)
		bt_att_cancel(client->att, client->mtu_req_id);

	return true;
}

struct read_op {
	bt_gatt_client_read_callback_t callback;
	void *user_data;
	bt_gatt_client_destroy_func_t destroy;
};

static void destroy_read_op(void *data)
{
	struct read_op *op = data;

	if (op->destroy)
		op->destroy(op->user_data);

	free(op);
}

static void read_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct read_op *op = req->data;
	bool success;
	uint8_t att_ecode = 0;
	const uint8_t *value = NULL;
	uint16_t value_len = 0;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		att_ecode = process_error(pdu, length);
		goto done;
	}

	if (opcode != BT_ATT_OP_READ_RSP || (!pdu && length)) {
		success = false;
		goto done;
	}

	success = true;
	value_len = length;
	if (value_len)
		value = pdu;

done:
	if (op->callback)
		op->callback(success, att_ecode, value, length, op->user_data);
}

unsigned int bt_gatt_client_read_value(struct bt_gatt_client *client,
					uint16_t value_handle,
					bt_gatt_client_read_callback_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy)
{
	struct request *req;
	struct read_op *op;
	uint8_t pdu[2];

	if (!client)
		return 0;

	op = new0(struct read_op, 1);
	if (!op)
		return 0;

	req = request_create(client);
	if (!req) {
		free(op);
		return 0;
	}

	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	req->data = op;
	req->destroy = destroy_read_op;

	put_le16(value_handle, pdu);

	req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_REQ,
							pdu, sizeof(pdu),
							read_cb, req,
							request_unref);
	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	return req->id;
}

static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct read_op *op = req->data;
	uint8_t att_ecode;
	bool success;

	if (opcode != BT_ATT_OP_READ_MULT_RSP || (!pdu && length)) {
		success = false;

		if (opcode == BT_ATT_OP_ERROR_RSP)
			att_ecode = process_error(pdu, length);
		else
			att_ecode = 0;

		pdu = NULL;
		length = 0;
	} else {
		success = true;
		att_ecode = 0;
	}

	if (op->callback)
		op->callback(success, att_ecode, pdu, length, op->user_data);
}

unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client,
					uint16_t *handles, uint8_t num_handles,
					bt_gatt_client_read_callback_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy)
{
	uint8_t pdu[num_handles * 2];
	struct request *req;
	struct read_op *op;
	int i;

	if (!client)
		return 0;

	if (num_handles < 2)
		return 0;

	if (num_handles * 2 > bt_att_get_mtu(client->att) - 1)
		return 0;

	op = new0(struct read_op, 1);
	if (!op)
		return 0;

	req = request_create(client);
	if (!req) {
		free(op);
		return 0;
	}

	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	req->data = op;
	req->destroy = destroy_read_op;

	for (i = 0; i < num_handles; i++)
		put_le16(handles[i], pdu + (2 * i));

	req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_MULT_REQ,
							pdu, sizeof(pdu),
							read_multiple_cb, req,
							request_unref);
	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	return req->id;
}

struct read_long_op {
	struct bt_gatt_client *client;
	int ref_count;
	uint16_t value_handle;
	uint16_t offset;
	struct iovec iov;
	bt_gatt_client_read_callback_t callback;
	void *user_data;
	bt_gatt_client_destroy_func_t destroy;
};

static void destroy_read_long_op(void *data)
{
	struct read_long_op *op = data;

	if (op->destroy)
		op->destroy(op->user_data);

	free(op->iov.iov_base);
	free(op);
}

static bool append_chunk(struct read_long_op *op, const uint8_t *data,
								uint16_t len)
{
	void *buf;

	/* Truncate if the data would exceed maximum length */
	if (op->offset + len > BT_ATT_MAX_VALUE_LEN)
		len = BT_ATT_MAX_VALUE_LEN - op->offset;

	buf = realloc(op->iov.iov_base, op->iov.iov_len + len);
	if (!buf)
		return false;

	op->iov.iov_base = buf;

	memcpy(op->iov.iov_base + op->iov.iov_len, data, len);

	op->iov.iov_len += len;
	op->offset += len;

	return true;
}

static void read_long_cb(uint8_t opcode, const void *pdu,
					uint16_t length, void *user_data)
{
	struct request *req = user_data;
	struct read_long_op *op = req->data;
	bool success;
	uint8_t att_ecode = 0;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		att_ecode = process_error(pdu, length);
		goto done;
	}

	if (opcode != BT_ATT_OP_READ_BLOB_RSP || (!pdu && length)) {
		success = false;
		goto done;
	}

	if (!length)
		goto success;

	if (!append_chunk(op, pdu, length)) {
		success = false;
		goto done;
	}

	if (op->offset >= BT_ATT_MAX_VALUE_LEN)
		goto success;

	if (length >= bt_att_get_mtu(op->client->att) - 1) {
		uint8_t pdu[4];

		put_le16(op->value_handle, pdu);
		put_le16(op->offset, pdu + 2);

		req->att_id = bt_att_send(op->client->att,
							BT_ATT_OP_READ_BLOB_REQ,
							pdu, sizeof(pdu),
							read_long_cb,
							request_ref(req),
							request_unref);
		if (req->att_id)
			return;

		request_unref(req);
		success = false;
		goto done;
	}

success:
	success = true;

done:
	if (op->callback)
		op->callback(success, att_ecode, op->iov.iov_base,
						op->iov.iov_len, op->user_data);
}

unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client,
					uint16_t value_handle, uint16_t offset,
					bt_gatt_client_read_callback_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy)
{
	struct request *req;
	struct read_long_op *op;
	uint8_t pdu[4];

	if (!client)
		return 0;

	op = new0(struct read_long_op, 1);
	if (!op)
		return 0;

	req = request_create(client);
	if (!req) {
		free(op);
		return 0;
	}

	op->client = client;
	op->value_handle = value_handle;
	op->offset = offset;
	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	req->data = op;
	req->destroy = destroy_read_long_op;

	put_le16(value_handle, pdu);
	put_le16(offset, pdu + 2);

	req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_BLOB_REQ,
							pdu, sizeof(pdu),
							read_long_cb, req,
							request_unref);
	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	return req->id;
}

unsigned int bt_gatt_client_write_without_response(
					struct bt_gatt_client *client,
					uint16_t value_handle,
					bool signed_write,
					const uint8_t *value, uint16_t length) {
	uint8_t pdu[2 + length];
	struct request *req;
	int security;
	uint8_t op;

	if (!client)
		return 0;

	req = request_create(client);
	if (!req)
		return 0;

	/* Only use signed write if unencrypted */
	if (signed_write) {
		security = bt_att_get_sec_level(client->att);
		op = security > BT_SECURITY_LOW ?  BT_ATT_OP_WRITE_CMD :
						BT_ATT_OP_SIGNED_WRITE_CMD;
	} else
		op = BT_ATT_OP_WRITE_CMD;

	put_le16(value_handle, pdu);
	memcpy(pdu + 2, value, length);

	req->att_id = bt_att_send(client->att, op, pdu, sizeof(pdu), NULL, req,
								request_unref);
	if (!req->att_id) {
		request_unref(req);
		return 0;
	}

	return req->id;
}

struct write_op {
	struct bt_gatt_client *client;
	bt_gatt_client_callback_t callback;
	void *user_data;
	bt_gatt_destroy_func_t destroy;
};

static void destroy_write_op(void *data)
{
	struct write_op *op = data;

	if (op->destroy)
		op->destroy(op->user_data);

	free(op);
}

static void write_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct write_op *op = req->data;
	bool success = true;
	uint8_t att_ecode = 0;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		att_ecode = process_error(pdu, length);
		goto done;
	}

	if (opcode != BT_ATT_OP_WRITE_RSP || pdu || length)
		success = false;

done:
	if (op->callback)
		op->callback(success, att_ecode, op->user_data);
}

unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client,
					uint16_t value_handle,
					const uint8_t *value, uint16_t length,
					bt_gatt_client_callback_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy)
{
	struct request *req;
	struct write_op *op;
	uint8_t pdu[2 + length];

	if (!client)
		return 0;

	op = new0(struct write_op, 1);
	if (!op)
		return 0;

	req = request_create(client);
	if (!req) {
		free(op);
		return 0;
	}

	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	req->data = op;
	req->destroy = destroy_write_op;

	put_le16(value_handle, pdu);
	memcpy(pdu + 2, value, length);

	req->att_id = bt_att_send(client->att, BT_ATT_OP_WRITE_REQ,
							pdu, sizeof(pdu),
							write_cb, req,
							request_unref);
	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	return req->id;
}

struct long_write_op {
	struct bt_gatt_client *client;
	bool reliable;
	bool success;
	uint8_t att_ecode;
	bool reliable_error;
	uint16_t value_handle;
	uint8_t *value;
	uint16_t length;
	uint16_t offset;
	uint16_t index;
	uint16_t cur_length;
	bt_gatt_client_write_long_callback_t callback;
	void *user_data;
	bt_gatt_client_destroy_func_t destroy;
};

static void long_write_op_free(void *data)
{
	struct long_write_op *op = data;

	if (op->destroy)
		op->destroy(op->user_data);

	free(op->value);
	free(op);
}

static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
							void *user_data);
static void complete_write_long_op(struct request *req, bool success,
					uint8_t att_ecode, bool reliable_error);

static void handle_next_prep_write(struct request *req)
{
	struct long_write_op *op = req->data;
	bool success = true;
	uint8_t *pdu;

	pdu = malloc(op->cur_length + 4);
	if (!pdu) {
		success = false;
		goto done;
	}

	put_le16(op->value_handle, pdu);
	put_le16(op->offset + op->index, pdu + 2);
	memcpy(pdu + 4, op->value + op->index, op->cur_length);

	req->att_id = bt_att_send(op->client->att, BT_ATT_OP_PREP_WRITE_REQ,
							pdu, op->cur_length + 4,
							prepare_write_cb,
							request_ref(req),
							request_unref);
	if (!req->att_id) {
		request_unref(req);
		success = false;
	}

	free(pdu);

	/* If so far successful, then the operation should continue.
	 * Otherwise, there was an error and the procedure should be
	 * completed.
	 */
	if (success)
		return;

done:
	complete_write_long_op(req, success, 0, false);
}

static void start_next_long_write(struct bt_gatt_client *client)
{
	struct request *req;

	if (queue_isempty(client->long_write_queue)) {
		client->in_long_write = false;
		return;
	}

	req  = queue_pop_head(client->long_write_queue);
	if (!req)
		return;

	handle_next_prep_write(req);

	/*
	 * send_next_prep_write adds an extra ref. Unref here to clean up if
	 * necessary, since we also added a ref before pushing to the queue.
	 */
	request_unref(req);
}

static void execute_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct long_write_op *op = req->data;
	bool success = op->success;
	uint8_t att_ecode = op->att_ecode;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		att_ecode = process_error(pdu, length);
	} else if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length)
		success = false;

	if (op->callback)
		op->callback(success, op->reliable_error, att_ecode,
								op->user_data);

	start_next_long_write(op->client);
}

static void complete_write_long_op(struct request *req, bool success,
					uint8_t att_ecode, bool reliable_error)
{
	struct long_write_op *op = req->data;
	uint8_t pdu;

	op->success = success;
	op->att_ecode = att_ecode;
	op->reliable_error = reliable_error;

	if (success)
		pdu = 0x01;  /* Write */
	else
		pdu = 0x00;  /* Cancel */

	req->att_id = bt_att_send(op->client->att, BT_ATT_OP_EXEC_WRITE_REQ,
							&pdu, sizeof(pdu),
							execute_write_cb,
							request_ref(req),
							request_unref);
	if (req->att_id)
		return;


	request_unref(req);
	success = false;

	if (op->callback)
		op->callback(success, reliable_error, att_ecode, op->user_data);

	start_next_long_write(op->client);
}

static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct long_write_op *op = req->data;
	bool success = true;
	bool reliable_error = false;
	uint8_t att_ecode = 0;
	uint16_t next_index;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		att_ecode = process_error(pdu, length);
		goto done;
	}

	if (opcode != BT_ATT_OP_PREP_WRITE_RSP) {
		success = false;
		goto done;
	}

	if (op->reliable) {
		if (!pdu || length != (op->cur_length + 4)) {
			success = false;
			reliable_error = true;
			goto done;
		}

		if (get_le16(pdu) != op->value_handle ||
				get_le16(pdu + 2) != (op->offset + op->index)) {
			success = false;
			reliable_error = true;
			goto done;
		}

		if (memcmp(pdu + 4, op->value + op->index, op->cur_length)) {
			success = false;
			reliable_error = true;
			goto done;
		}
	}

	next_index = op->index + op->cur_length;
	if (next_index == op->length) {
		/* All bytes written */
		goto done;
	}

	/* If the last written length was greater than or equal to what can fit
	 * inside a PDU, then there is more data to send.
	 */
	if (op->cur_length >= bt_att_get_mtu(op->client->att) - 5) {
		op->index = next_index;
		op->cur_length = MIN(op->length - op->index,
					bt_att_get_mtu(op->client->att) - 5);
		handle_next_prep_write(req);
		return;
	}

done:
	complete_write_long_op(req, success, att_ecode, reliable_error);
}

unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client,
				bool reliable,
				uint16_t value_handle, uint16_t offset,
				const uint8_t *value, uint16_t length,
				bt_gatt_client_write_long_callback_t callback,
				void *user_data,
				bt_gatt_client_destroy_func_t destroy)
{
	struct request *req;
	struct long_write_op *op;
	uint8_t *pdu;

	if (!client)
		return 0;

	if ((size_t)(length + offset) > UINT16_MAX)
		return 0;

	/* Don't allow writing a 0-length value using this procedure. The
	 * upper-layer should use bt_gatt_write_value for that instead.
	 */
	if (!length || !value)
		return 0;

	op = new0(struct long_write_op, 1);
	if (!op)
		return 0;

	op->value = malloc(length);
	if (!op->value) {
		free(op);
		return 0;
	}

	req = request_create(client);
	if (!req) {
		free(op->value);
		free(op);
		return 0;
	}

	memcpy(op->value, value, length);

	op->client = client;
	op->reliable = reliable;
	op->value_handle = value_handle;
	op->length = length;
	op->offset = offset;
	op->cur_length = MIN(length, bt_att_get_mtu(client->att) - 5);
	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	req->data = op;
	req->destroy = long_write_op_free;
	req->long_write = true;

	if (client->in_long_write || client->reliable_write_session_id > 0) {
		queue_push_tail(client->long_write_queue, req);
		return req->id;
	}

	pdu = malloc(op->cur_length + 4);
	if (!pdu) {
		free(op->value);
		free(op);
		return 0;
	}

	put_le16(value_handle, pdu);
	put_le16(offset, pdu + 2);
	memcpy(pdu + 4, op->value, op->cur_length);

	req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ,
							pdu, op->cur_length + 4,
							prepare_write_cb, req,
							request_unref);
	free(pdu);

	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	client->in_long_write = true;

	return req->id;
}

struct prep_write_op {
	bt_gatt_client_write_long_callback_t callback;
	void *user_data;
	bt_gatt_destroy_func_t destroy;
	uint8_t *pdu;
	uint16_t pdu_len;
};

static void destroy_prep_write_op(void *data)
{
	struct prep_write_op *op = data;

	if (op->destroy)
		op->destroy(op->user_data);

	free(op->pdu);
	free(op);
}

static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct prep_write_op *op = req->data;
	bool success;
	uint8_t att_ecode;
	bool reliable_error;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		reliable_error = false;
		att_ecode = process_error(pdu, length);
		goto done;
	}

	if (opcode != BT_ATT_OP_PREP_WRITE_RSP) {
		success = false;
		reliable_error = false;
		att_ecode = 0;
		goto done;
	}

	if (!pdu || length != op->pdu_len ||
					memcmp(pdu, op->pdu, op->pdu_len)) {
		success = false;
		reliable_error = true;
		att_ecode = 0;
		goto done;
	}

	success = true;
	reliable_error = false;
	att_ecode = 0;

done:
	if (op->callback)
		op->callback(success, reliable_error, att_ecode, op->user_data);
}

static struct request *get_reliable_request(struct bt_gatt_client *client,
							unsigned int id)
{
	struct request *req;
	struct prep_write_op *op;

	op = new0(struct prep_write_op, 1);
	if (!op)
		return NULL;

	/* Following prepare writes */
	if (id != 0)
		req = queue_find(client->pending_requests, match_req_id,
							UINT_TO_PTR(id));
	else
		req = request_create(client);

	if (!req) {
		free(op);
		return NULL;
	}

	req->data = op;

	return req;
}

unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client,
				unsigned int id, uint16_t value_handle,
				uint16_t offset, uint8_t *value,
				uint16_t length,
				bt_gatt_client_write_long_callback_t callback,
				void *user_data,
				bt_gatt_client_destroy_func_t destroy)
{
	struct request *req;
	struct prep_write_op *op;
	uint8_t pdu[4 + length];

	if (!client)
		return 0;

	if (client->in_long_write)
		return 0;

	/*
	 * Make sure that client who owns reliable session continues with
	 * prepare writes or this is brand new reliable session (id == 0)
	 */
	if (id != client->reliable_write_session_id) {
		util_debug(client->debug_callback, client->debug_data,
			"There is other reliable write session ongoing %u",
			client->reliable_write_session_id);

		return 0;
	}

	req = get_reliable_request(client, id);
	if (!req)
		return 0;

	op = (struct prep_write_op *)req->data;

	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	req->destroy = destroy_prep_write_op;
	req->prep_write = true;

	put_le16(value_handle, pdu);
	put_le16(offset, pdu + 2);
	memcpy(pdu + 4, value, length);

	/*
	 * Before sending command we need to remember pdu as we need to validate
	 * it in the response. Store handle, offset and value. Therefore
	 * increase length by 4 (handle + offset) as we need it in couple places
	 * below
	 */
	length += 4;

	op->pdu = malloc(length);
	if (!op->pdu) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	memcpy(op->pdu, pdu, length);
	op->pdu_len = length;

	/*
	 * Now we are ready to send command
	 * Note that request_unref will be done on write execute
	 */
	req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu,
					sizeof(pdu), prep_write_cb, req,
					NULL);
	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	/*
	 * Store first request id for prepare write and treat it as a session id
	 * valid until write execute is done
	 */
	if (client->reliable_write_session_id == 0)
		client->reliable_write_session_id = req->id;

	return client->reliable_write_session_id;
}

static void exec_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
								void *user_data)
{
	struct request *req = user_data;
	struct write_op *op = req->data;
	bool success;
	uint8_t att_ecode;

	if (opcode == BT_ATT_OP_ERROR_RSP) {
		success = false;
		att_ecode = process_error(pdu, length);
		goto done;
	}

	if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) {
		success = false;
		att_ecode = 0;
		goto done;
	}

	success = true;
	att_ecode = 0;

done:
	if (op->callback)
		op->callback(success, att_ecode, op->user_data);

	op->client->reliable_write_session_id = 0;

	start_next_long_write(op->client);
}

unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client,
					unsigned int id,
					bt_gatt_client_callback_t callback,
					void *user_data,
					bt_gatt_client_destroy_func_t destroy)
{
	struct request *req;
	struct write_op *op;
	uint8_t pdu;

	if (!client)
		return 0;

	if (client->in_long_write)
		return 0;

	if (client->reliable_write_session_id != id)
		return 0;

	op = new0(struct write_op, 1);
	if (!op)
		return 0;

	req = queue_find(client->pending_requests, match_req_id,
							UINT_TO_PTR(id));
	if (!req) {
		free(op);
		return 0;
	}

	op->client = client;
	op->callback = callback;
	op->user_data = user_data;
	op->destroy = destroy;

	pdu = 0x01;

	req->data = op;
	req->destroy = destroy_write_op;

	req->att_id = bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu,
						sizeof(pdu), exec_write_cb,
						req, request_unref);
	if (!req->att_id) {
		op->destroy = NULL;
		request_unref(req);
		return 0;
	}

	return id;
}

static bool match_notify_chrc_value_handle(const void *a, const void *b)
{
	const struct notify_chrc *chrc = a;
	uint16_t value_handle = PTR_TO_UINT(b);

	return chrc->value_handle == value_handle;
}

unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client,
				uint16_t chrc_value_handle,
				bt_gatt_client_register_callback_t callback,
				bt_gatt_client_notify_callback_t notify,
				void *user_data,
				bt_gatt_client_destroy_func_t destroy)
{
	struct notify_data *notify_data;
	struct notify_chrc *chrc = NULL;

	if (!client || !client->db || !chrc_value_handle || !callback)
		return 0;

	if (!bt_gatt_client_is_ready(client) || client->in_svc_chngd)
		return 0;

	/* Check if a characteristic ref count has been started already */
	chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle,
						UINT_TO_PTR(chrc_value_handle));

	if (!chrc) {
		/*
		 * Create an entry if the characteristic is known and has a CCC
		 * descriptor.
		 */
		chrc = notify_chrc_create(client, chrc_value_handle);
		if (!chrc)
			return 0;
	}

	/* Fail if we've hit the maximum allowed notify sessions */
	if (chrc->notify_count == INT_MAX)
		return 0;

	notify_data = new0(struct notify_data, 1);
	if (!notify_data)
		return 0;

	notify_data->client = client;
	notify_data->ref_count = 1;
	notify_data->chrc = chrc;
	notify_data->callback = callback;
	notify_data->notify = notify;
	notify_data->user_data = user_data;
	notify_data->destroy = destroy;

	/* Add the handler to the bt_gatt_client's general list */
	queue_push_tail(client->notify_list, notify_data);

	/* Assign an ID to the handler. */
	if (client->next_reg_id < 1)
		client->next_reg_id = 1;

	notify_data->id = client->next_reg_id++;

	/*
	 * If a write to the CCC descriptor is in progress, then queue this
	 * request.
	 */
	if (chrc->ccc_write_id) {
		queue_push_tail(chrc->reg_notify_queue, notify_data);
		return notify_data->id;
	}

	/*
	 * If the ref count is not zero, then notifications are already enabled.
	 */
	if (chrc->notify_count > 0 || !chrc->ccc_handle) {
		complete_notify_request(notify_data);
		return notify_data->id;
	}

	/* Write to the CCC descriptor */
	if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) {
		queue_remove(client->notify_list, notify_data);
		free(notify_data);
		return 0;
	}

	return notify_data->id;
}

bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
							unsigned int id)
{
	struct notify_data *notify_data;

	if (!client || !id)
		return false;

	notify_data = queue_remove_if(client->notify_list, match_notify_data_id,
							UINT_TO_PTR(id));
	if (!notify_data)
		return false;

	assert(notify_data->chrc->notify_count > 0);
	assert(!notify_data->chrc->ccc_write_id);

	complete_unregister_notify(notify_data);
	return true;
}

bool bt_gatt_client_set_sec_level(struct bt_gatt_client *client,
								int level)
{
	if (!client)
		return false;

	return bt_att_set_sec_level(client->att, level);
}

int bt_gatt_client_get_sec_level(struct bt_gatt_client *client)
{
	if (!client)
		return -1;

	return bt_att_get_sec_level(client->att);
}
