// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2022	Intel Corporation. All rights reserved.
 *  Copyright (C) 2025	Pauli Virtanen. All rights reserved.
 *
 */

#define _GNU_SOURCE
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>

#include "bluetooth/bluetooth.h"
#include "bluetooth/uuid.h"
#include "bluetooth/hci.h"

#include "src/shared/queue.h"
#include "src/shared/util.h"
#include "src/shared/timeout.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-server.h"
#include "src/shared/gatt-client.h"
#include "src/shared/mcp.h"
#include "src/shared/mcs.h"

#define BT_MCS_ERROR_VALUE_CHANGED_DURING_READ_LONG	0x80

#define DBG_MCP(mcp, fmt, ...) \
	mcp_debug(mcp, "%s:%s() mcp %p | " fmt, __FILE__, __func__, mcp, \
								##__VA_ARGS__)
#define DBG_SVC(service, fmt, ...) \
	mcp_debug(service->mcp, "%s:%s() svc %p | " fmt, __FILE__, __func__, \
							service, ##__VA_ARGS__)
#define DBG_MCS(mcs, fmt, ...) \
	mcs_debug(mcs, "%s:%s() mcs %p | " fmt, __FILE__, __func__, mcs, \
								##__VA_ARGS__)

#define MAX_ATTR	32
#define MAX_PENDING	256

struct bt_mcs_db {
	bool gmcs;
	int ccid_value;
	uint32_t media_cp_op_supported_value;
	uint16_t playing_order_supported_value;

	struct gatt_db_attribute *service;
	struct gatt_db_attribute *media_player_name;
	struct gatt_db_attribute *media_player_name_ccc;
	struct gatt_db_attribute *track_changed;
	struct gatt_db_attribute *track_changed_ccc;
	struct gatt_db_attribute *track_title;
	struct gatt_db_attribute *track_title_ccc;
	struct gatt_db_attribute *track_duration;
	struct gatt_db_attribute *track_duration_ccc;
	struct gatt_db_attribute *track_position;
	struct gatt_db_attribute *track_position_ccc;
	struct gatt_db_attribute *playback_speed;
	struct gatt_db_attribute *playback_speed_ccc;
	struct gatt_db_attribute *seeking_speed;
	struct gatt_db_attribute *seeking_speed_ccc;
	struct gatt_db_attribute *playing_order;
	struct gatt_db_attribute *playing_order_ccc;
	struct gatt_db_attribute *playing_order_supported;
	struct gatt_db_attribute *media_state;
	struct gatt_db_attribute *media_state_ccc;
	struct gatt_db_attribute *media_cp;
	struct gatt_db_attribute *media_cp_ccc;
	struct gatt_db_attribute *media_cp_op_supported;
	struct gatt_db_attribute *media_cp_op_supported_ccc;
	struct gatt_db_attribute *ccid;
};

struct bt_mcs_session {
	struct bt_mcs *mcs;
	struct bt_att *att;
	unsigned int disconn_id;

	/* Per-client state */
	struct queue *changed;
};

struct bt_mcs {
	struct gatt_db *db;
	struct bt_mcs_db ldb;
	struct queue *sessions;

	uint8_t media_state;

	const struct bt_mcs_callback *cb;
	void *user_data;
};

struct bt_mcp_listener {
	const struct bt_mcp_listener_callback *cb;
	void *user_data;
};

struct bt_mcp_service {
	struct bt_mcp *mcp;
	struct bt_mcs_db rdb;

	bool ready;

	unsigned int notify_id[MAX_ATTR];
	unsigned int notify_id_count;

	unsigned int pending_id;

	struct queue *pending;
	struct queue *listeners;
};

struct bt_mcp_pending {
	struct bt_mcp_service *service;
	unsigned int id;
	uint8_t op;
	struct {
		unsigned int client_id;
		struct gatt_db_attribute *attrib;
		uint8_t result;
	} write;
};

struct bt_mcp {
	bool gmcs;
	struct bt_gatt_client *client;
	unsigned int idle_id;
	unsigned int db_id;
	bool ready;

	struct queue *services;

	const struct bt_mcp_callback *cb;
	void *user_data;
};

#define MCS_COMMAND(name_, op_, arg_, end_state_) \
	{ \
		.name = name_, \
		.op = BT_MCS_CMD_ ## op_, \
		.support = BT_MCS_CMD_ ## op_ ## _SUPPORTED, \
		.int32_arg = arg_, \
		.end_state = end_state_, \
	}

#define ANY_STATE	-1

static const struct mcs_command {
	const char *name;
	uint8_t op;
	uint32_t support;
	bool int32_arg;
	int end_state;
} mcs_command[] = {
	MCS_COMMAND("Play", PLAY, false, BT_MCS_STATE_PLAYING),
	MCS_COMMAND("Pause", PAUSE, false, BT_MCS_STATE_PAUSED),
	MCS_COMMAND("Fast Rewind", FAST_REWIND, false, BT_MCS_STATE_SEEKING),
	MCS_COMMAND("Fast Forward", FAST_FORWARD, false, BT_MCS_STATE_SEEKING),
	MCS_COMMAND("Stop", STOP, false, BT_MCS_STATE_PAUSED),
	MCS_COMMAND("Move Relative", MOVE_RELATIVE, true, ANY_STATE),
	MCS_COMMAND("Prev Segment", PREV_SEGMENT, false, ANY_STATE),
	MCS_COMMAND("Next Segment", NEXT_SEGMENT, false, ANY_STATE),
	MCS_COMMAND("First Segment", FIRST_SEGMENT, false, ANY_STATE),
	MCS_COMMAND("Last Segment", LAST_SEGMENT, false, ANY_STATE),
	MCS_COMMAND("Goto Segment", GOTO_SEGMENT, true, ANY_STATE),
	MCS_COMMAND("Prev Track", PREV_TRACK, false, ANY_STATE),
	MCS_COMMAND("Next Track", NEXT_TRACK, false, ANY_STATE),
	MCS_COMMAND("First Track", FIRST_TRACK, false, ANY_STATE),
	MCS_COMMAND("Last Track", LAST_TRACK, false, ANY_STATE),
	MCS_COMMAND("Goto Track", GOTO_TRACK, true, ANY_STATE),
	MCS_COMMAND("Prev Group", PREV_GROUP, false, ANY_STATE),
	MCS_COMMAND("Next Group", NEXT_GROUP, false, ANY_STATE),
	MCS_COMMAND("First Group", FIRST_GROUP, false, ANY_STATE),
	MCS_COMMAND("Last Group", LAST_GROUP, false, ANY_STATE),
	MCS_COMMAND("Goto Group", GOTO_GROUP, true, ANY_STATE),
};

#define MCS_PLAYING_ORDER(name) \
	{ BT_MCS_ORDER_ ## name, BT_MCS_ORDER_SUPPORTED_ ## name }

static const struct {
	uint8_t order;
	uint16_t support;
} mcs_playing_orders[] = {
	MCS_PLAYING_ORDER(SINGLE_ONCE),
	MCS_PLAYING_ORDER(SINGLE_REPEAT),
	MCS_PLAYING_ORDER(IN_ORDER_ONCE),
	MCS_PLAYING_ORDER(IN_ORDER_REPEAT),
	MCS_PLAYING_ORDER(OLDEST_ONCE),
	MCS_PLAYING_ORDER(OLDEST_REPEAT),
	MCS_PLAYING_ORDER(NEWEST_ONCE),
	MCS_PLAYING_ORDER(NEWEST_REPEAT),
	MCS_PLAYING_ORDER(SHUFFLE_ONCE),
	MCS_PLAYING_ORDER(SHUFFLE_REPEAT)
};

typedef bool (*mcs_command_func_t)(void *data);
typedef bool (*mcs_command_func_int32_t)(void *data, int32_t offset);
typedef void (*mcs_get_func_t)(struct bt_mcs *mcs, struct iovec *buf,
								size_t size);
typedef bool (*mcs_set_func_t)(struct bt_mcs *mcs, void *data);

static struct queue *servers;
static uint8_t servers_ccid;


/*
 * MCS Server
 */

static void mcs_debug_func(const char *str, void *user_data)
{
	struct bt_mcs *mcs = user_data;

	mcs->cb->debug(mcs->user_data, str);
}

static void mcs_debug(struct bt_mcs *mcs, const char *format, ...)
{
	va_list ap;

	if (!mcs || !format || !mcs->cb->debug)
		return;

	va_start(ap, format);
	util_debug_va(mcs_debug_func, mcs, format, ap);
	va_end(ap);
}

static const struct mcs_command *mcs_get_command(uint8_t op)
{
	size_t i;

	for (i = 0; i < ARRAY_SIZE(mcs_command); ++i)
		if (mcs_command[i].op == op)
			return &mcs_command[i];

	return NULL;
}

static mcs_command_func_t mcs_get_handler(struct bt_mcs *mcs, uint8_t op)

{
	switch (op) {
	case BT_MCS_CMD_PLAY: return mcs->cb->play;
	case BT_MCS_CMD_PAUSE: return mcs->cb->pause;
	case BT_MCS_CMD_FAST_REWIND: return mcs->cb->fast_rewind;
	case BT_MCS_CMD_FAST_FORWARD: return mcs->cb->fast_forward;
	case BT_MCS_CMD_STOP: return mcs->cb->stop;
	case BT_MCS_CMD_PREV_SEGMENT: return mcs->cb->previous_segment;
	case BT_MCS_CMD_NEXT_SEGMENT: return mcs->cb->next_segment;
	case BT_MCS_CMD_FIRST_SEGMENT: return mcs->cb->first_segment;
	case BT_MCS_CMD_LAST_SEGMENT: return mcs->cb->last_segment;
	case BT_MCS_CMD_PREV_TRACK: return mcs->cb->previous_track;
	case BT_MCS_CMD_NEXT_TRACK: return mcs->cb->next_track;
	case BT_MCS_CMD_FIRST_TRACK: return mcs->cb->first_track;
	case BT_MCS_CMD_LAST_TRACK: return mcs->cb->last_track;
	case BT_MCS_CMD_PREV_GROUP: return mcs->cb->previous_group;
	case BT_MCS_CMD_NEXT_GROUP: return mcs->cb->next_group;
	case BT_MCS_CMD_FIRST_GROUP: return mcs->cb->first_group;
	case BT_MCS_CMD_LAST_GROUP: return mcs->cb->last_group;
	}
	return NULL;
}

static mcs_command_func_int32_t mcs_get_handler_int32(struct bt_mcs *mcs,
								uint8_t op)

{
	switch (op) {
	case BT_MCS_CMD_MOVE_RELATIVE: return mcs->cb->move_relative;
	case BT_MCS_CMD_GOTO_SEGMENT: return mcs->cb->goto_segment;
	case BT_MCS_CMD_GOTO_TRACK: return mcs->cb->goto_track;
	case BT_MCS_CMD_GOTO_GROUP: return mcs->cb->goto_group;
	}
	return NULL;
}

static uint32_t mcs_get_supported(struct bt_mcs *mcs)
{
	unsigned int i;
	uint32_t value = 0;

	for (i = 0; i < ARRAY_SIZE(mcs_command); ++i)
		value |= mcs_command[i].support;

	if (mcs->cb->media_cp_op_supported)
		value = mcs->cb->media_cp_op_supported(mcs->user_data);

	for (i = 0; i < ARRAY_SIZE(mcs_command); ++i) {
		void *handler = mcs_get_handler(mcs, mcs_command[i].op);

		if (!handler)
			handler = mcs_get_handler_int32(mcs, mcs_command[i].op);
		if (!handler)
			value &= ~mcs_command[i].support;
	}

	mcs->ldb.media_cp_op_supported_value = value;
	return value;
}

static void write_media_cp(struct gatt_db_attribute *attrib,
				unsigned int id, uint16_t offset,
				const uint8_t *data, size_t len,
				uint8_t opcode, struct bt_att *att,
				void *user_data)
{
	struct bt_mcs *mcs = user_data;
	struct iovec iov = { .iov_base = (void *)data, .iov_len = len };
	const struct mcs_command *cmd = NULL;
	struct bt_mcs_cp_rsp rsp = {
		.op = 0,
		.result = BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE
	};
	int ret = 0;
	int32_t arg = 0;
	uint8_t op;
	bool ok = false;

	if (offset) {
		ret = BT_ATT_ERROR_INVALID_OFFSET;
		goto respond;
	}

	if (!util_iov_pull_u8(&iov, &op)) {
		ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
		goto respond;
	}

	rsp.op = op;

	cmd = mcs_get_command(op);
	if (!cmd || !(cmd->support & mcs_get_supported(mcs))) {
		rsp.result = BT_MCS_RESULT_OP_NOT_SUPPORTED;
		goto respond;
	}
	if (cmd->int32_arg && !util_iov_pull_le32(&iov, (uint32_t *)&arg)) {
		ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
		rsp.op = 0;
		goto respond;
	}

	DBG_MCS(mcs, "Command %s", cmd->name);

	/* We may attempt to perform the operation also if inactive (MCS v1.0.1
	 * p. 26), leave decision to upper layer.
	 */

	ok = cmd->int32_arg ?
		mcs_get_handler_int32(mcs, op)(mcs->user_data, arg) :
		mcs_get_handler(mcs, op)(mcs->user_data);
	if (ok)
		rsp.result = BT_MCS_RESULT_SUCCESS;
	else if (mcs->media_state == BT_MCS_STATE_INACTIVE)
		rsp.result = BT_MCS_RESULT_MEDIA_PLAYER_INACTIVE;
	else
		rsp.result = BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE;

respond:
	DBG_MCS(mcs, "%s ret %u result %u", cmd ? cmd->name : "-",
							ret, rsp.result);

	gatt_db_attribute_write_result(attrib, id, ret);
	if (!rsp.op)
		return;

	/* Make state transition immediately if command was successful and has
	 * specified end state. Upper layer shall emit spontaneous transitions
	 * to correct as needed.
	 */
	if (ok) {
		bt_mcs_set_media_state(mcs, cmd->end_state);

		switch (op) {
		case BT_MCS_CMD_STOP:
		case BT_MCS_CMD_PREV_TRACK:
		case BT_MCS_CMD_NEXT_TRACK:
		case BT_MCS_CMD_FIRST_TRACK:
		case BT_MCS_CMD_LAST_TRACK:
		case BT_MCS_CMD_GOTO_TRACK:
		case BT_MCS_CMD_PREV_GROUP:
		case BT_MCS_CMD_NEXT_GROUP:
		case BT_MCS_CMD_FIRST_GROUP:
		case BT_MCS_CMD_LAST_GROUP:
		case BT_MCS_CMD_GOTO_GROUP:
			if (mcs->cb->set_track_position)
				mcs->cb->set_track_position(mcs->user_data, 0);
			bt_mcs_changed(mcs, MCS_TRACK_POSITION_CHRC_UUID);
			break;
		}
	}

	gatt_db_attribute_notify(attrib, (uint8_t *)&rsp, sizeof(rsp), att);
}

void bt_mcs_set_media_state(struct bt_mcs *mcs, uint8_t state)
{
	switch (state) {
	case BT_MCS_STATE_INACTIVE:
	case BT_MCS_STATE_PLAYING:
	case BT_MCS_STATE_PAUSED:
	case BT_MCS_STATE_SEEKING:
		break;
	default:
		return;
	}

	if (state == mcs->media_state)
		return;

	mcs->media_state = state;
	bt_mcs_changed(mcs, MCS_MEDIA_STATE_CHRC_UUID);
}

uint8_t bt_mcs_get_media_state(struct bt_mcs *mcs)
{
	return mcs->media_state;
}

static void get_media_player_name(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	if (mcs->cb->media_player_name)
		mcs->cb->media_player_name(mcs->user_data, buf, size);
}

static void get_track_changed(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
}

static void get_track_title(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	if (mcs->cb->track_title)
		mcs->cb->track_title(mcs->user_data, buf, size);
}

static void get_track_duration(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	int32_t value = BT_MCS_DURATION_UNAVAILABLE;

	if (mcs->cb->track_duration)
		value = mcs->cb->track_duration(mcs->user_data);

	util_iov_push_le32(buf, (uint32_t)value);
}

static void get_track_position(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	int32_t value = BT_MCS_POSITION_UNAVAILABLE;

	if (mcs->cb->track_position)
		value = mcs->cb->track_position(mcs->user_data);

	util_iov_push_le32(buf, (uint32_t)value);
}

static void get_playback_speed(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	int8_t value = 0x00;

	if (mcs->cb->playback_speed)
		value = mcs->cb->playback_speed(mcs->user_data);

	util_iov_push_u8(buf, (uint8_t)value);
}

static void get_seeking_speed(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	int8_t value = 0x00;

	if (mcs->cb->seeking_speed)
		value = mcs->cb->seeking_speed(mcs->user_data);

	util_iov_push_u8(buf, (uint8_t)value);
}

static void get_playing_order(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	uint8_t value = BT_MCS_ORDER_IN_ORDER_REPEAT;

	if (mcs->cb->playing_order)
		value = mcs->cb->playing_order(mcs->user_data);

	util_iov_push_u8(buf, value);
}

static void get_playing_order_supported(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	uint16_t value = BT_MCS_ORDER_SUPPORTED_IN_ORDER_REPEAT;

	if (mcs->cb->playing_order_supported)
		value = mcs->cb->playing_order_supported(mcs->user_data);

	util_iov_push_le16(buf, value);
}

static void get_media_state(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	util_iov_push_u8(buf, mcs->media_state);
}

static void get_media_cp_op_supported(struct bt_mcs *mcs, struct iovec *buf,
								size_t size)
{
	util_iov_push_le32(buf, mcs_get_supported(mcs));
}

static void get_ccid(struct bt_mcs *mcs, struct iovec *buf, size_t size)
{
	util_iov_push_u8(buf, mcs->ldb.ccid_value);
}

static bool set_track_position(struct bt_mcs *mcs, void *data)
{
	int32_t value = (int32_t)get_le32(data);

	DBG_MCS(mcs, "Set Track Position %d", value);

	if (mcs->cb->set_track_position)
		return mcs->cb->set_track_position(mcs->user_data, value);
	return false;
}

static bool set_playback_speed(struct bt_mcs *mcs, void *data)
{
	int8_t value = (int8_t)get_u8(data);

	DBG_MCS(mcs, "Set Playback Speed %d", value);

	if (mcs->cb->set_playback_speed)
		return mcs->cb->set_playback_speed(mcs->user_data, value);
	return false;
}

static bool set_playing_order(struct bt_mcs *mcs, void *data)
{
	uint8_t value = get_u8(data);

	DBG_MCS(mcs, "Set Playing Order %u", value);

	if (mcs->cb->set_playing_order)
		return mcs->cb->set_playing_order(mcs->user_data, value);
	return false;
}

static bool match_session_att(const void *data, const void *match_data)
{
	const struct bt_mcs_session *session = data;

	return session->att == match_data;
}

static void session_destroy(void *data)
{
	struct bt_mcs_session *session = data;
	struct bt_mcs *mcs = session->mcs;

	if (mcs)
		queue_remove(mcs->sessions, session);
	queue_destroy(session->changed, NULL);
	free(session);
}

static void session_remove(void *user_data)
{
	struct bt_mcs_session *session = user_data;

	session->mcs = NULL;
	bt_att_unregister_disconnect(session->att, session->disconn_id);
}

static struct bt_mcs_session *get_session(struct bt_mcs *mcs,
							struct bt_att *att)
{
	struct bt_mcs_session *session;

	session = queue_find(mcs->sessions, match_session_att, att);
	if (session)
		return session;

	session = new0(struct bt_mcs_session, 1);
	session->disconn_id = bt_att_register_disconnect(att, NULL, session,
							session_destroy);
	if (!session->disconn_id) {
		free(session);
		return NULL;
	}

	session->mcs = mcs;
	session->att = att;
	session->changed = queue_new();

	queue_push_tail(mcs->sessions, session);
	return session;
}

static void session_changed(void *data, void *user_data)
{
	struct bt_mcs_session *session = data;
	struct gatt_db_attribute *attrib = user_data;

	if (!queue_find(session->changed, NULL, attrib))
		queue_push_tail(session->changed, attrib);
}

static void read_result(struct bt_mcs *mcs, struct gatt_db_attribute *attrib,
			unsigned int id, uint16_t offset, struct bt_att *att,
			mcs_get_func_t get)
{
	uint8_t buf[BT_ATT_MAX_VALUE_LEN];
	struct iovec iov = { .iov_base = buf, .iov_len = 0 };
	struct bt_mcs_session *session = get_session(mcs, att);

	if (!session) {
		gatt_db_attribute_read_result(attrib, id,
						BT_ATT_ERROR_UNLIKELY, NULL, 0);
		return;
	}

	if (!offset) {
		queue_remove(session->changed, attrib);
	} else if (queue_find(session->changed, NULL, attrib)) {
		gatt_db_attribute_read_result(attrib, id,
			BT_MCS_ERROR_VALUE_CHANGED_DURING_READ_LONG, NULL, 0);
		return;
	}

	get(mcs, &iov, sizeof(buf));

	if (offset > iov.iov_len) {
		gatt_db_attribute_read_result(attrib, id,
					BT_ATT_ERROR_INVALID_OFFSET, NULL, 0);
		return;
	}

	gatt_db_attribute_read_result(attrib, id, 0, buf + offset,
							iov.iov_len - offset);
}

#define READ_FUNC(name) \
	static void read_ ## name(struct gatt_db_attribute *attrib, \
				unsigned int id, uint16_t offset, \
				uint8_t opcode, struct bt_att *att, \
				void *user_data) \
	{ \
		DBG_MCS(user_data, ""); \
		read_result(user_data, attrib, id, offset, att, get_ ##name); \
	}

READ_FUNC(media_player_name)
READ_FUNC(track_title)
READ_FUNC(track_duration)
READ_FUNC(track_position)
READ_FUNC(playback_speed)
READ_FUNC(seeking_speed)
READ_FUNC(playing_order)
READ_FUNC(playing_order_supported)
READ_FUNC(media_state)
READ_FUNC(media_cp_op_supported)
READ_FUNC(ccid)

static void write_result(struct bt_mcs *mcs,
				struct gatt_db_attribute *attrib,
				unsigned int id, uint16_t offset,
				const uint8_t *data, size_t len,
				mcs_get_func_t get, mcs_set_func_t set)
{
	uint8_t buf[4];
	struct iovec iov = { .iov_base = buf, .iov_len = 0 };
	bt_uuid_t uuid;
	uint8_t ret;

	get(mcs, &iov, sizeof(buf));

	if (len > iov.iov_len) {
		gatt_db_attribute_write_result(attrib, id,
				BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
		return;
	}

	if (offset + len > iov.iov_len) {
		gatt_db_attribute_write_result(attrib, id,
				BT_ATT_ERROR_INVALID_OFFSET);
		return;
	}

	memcpy(iov.iov_base + offset, data, len);

	if (set(mcs, iov.iov_base))
		ret = 0;
	else
		ret = BT_ATT_ERROR_VALUE_NOT_ALLOWED;

	gatt_db_attribute_write_result(attrib, id, ret);

	if (!gatt_db_attribute_get_char_data(attrib, NULL, NULL, NULL, NULL,
									&uuid))
		return;
	if (!ret)
		bt_mcs_changed(mcs, uuid.value.u16);
}

#define WRITE_FUNC(name) \
	static void write_ ## name(struct gatt_db_attribute *attrib, \
				unsigned int id, uint16_t offset, \
				const uint8_t *data, size_t len, \
				uint8_t opcode, struct bt_att *att, \
				void *user_data) \
	{ write_result(user_data, attrib, id, offset, data, len, \
						get_ ## name, set_ ## name); }

WRITE_FUNC(track_position)
WRITE_FUNC(playback_speed)
WRITE_FUNC(playing_order)

void bt_mcs_changed(struct bt_mcs *mcs, uint16_t chrc_uuid)
{
	struct {
		struct gatt_db_attribute *attr;
		mcs_get_func_t get;
	} attrs[] = {
		{ mcs->ldb.media_player_name, get_media_player_name },
		{ mcs->ldb.track_changed, get_track_changed },
		{ mcs->ldb.track_title, get_track_title },
		{ mcs->ldb.track_duration, get_track_duration },
		{ mcs->ldb.track_position, get_track_position },
		{ mcs->ldb.playback_speed, get_playback_speed },
		{ mcs->ldb.seeking_speed, get_seeking_speed },
		{ mcs->ldb.playing_order, get_playing_order },
		{ mcs->ldb.media_state, get_media_state },
		{ mcs->ldb.media_cp_op_supported, get_media_cp_op_supported },
	};
	uint8_t buf[BT_ATT_MAX_VALUE_LEN];
	struct iovec iov = { .iov_base = buf, .iov_len = 0 };
	unsigned int i;
	bt_uuid_t uuid, uuid_attr;
	uint8_t props;

	bt_uuid16_create(&uuid, chrc_uuid);

	for (i = 0; i < ARRAY_SIZE(attrs); ++i) {
		if (!gatt_db_attribute_get_char_data(attrs[i].attr, NULL,
						NULL, &props, NULL, &uuid_attr))
			continue;
		if (bt_uuid_cmp(&uuid_attr, &uuid))
			continue;

		queue_foreach(mcs->sessions, session_changed, attrs[i].attr);

		DBG_MCS(mcs, "Notify 0x%04x", chrc_uuid);

		attrs[i].get(mcs, &iov, sizeof(buf));

		/* No client-specific state: notify everyone */
		gatt_db_attribute_notify(attrs[i].attr, iov.iov_base,
							iov.iov_len, NULL);
		break;
	}
}

static bool mcs_init_db(struct bt_mcs *mcs, bool is_gmcs)
{
	struct gatt_db *db = mcs->db;
	struct bt_mcs_db *ldb = &mcs->ldb;
	bt_uuid_t uuid;

	bt_uuid16_create(&uuid, is_gmcs ? GMCS_UUID : MCS_UUID);
	ldb->service = gatt_db_add_service(db, &uuid, true, 38);

	/* Add also optional CCC */

	bt_uuid16_create(&uuid, MCS_MEDIA_PLAYER_NAME_CHRC_UUID);
	ldb->media_player_name = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY,
		read_media_player_name, NULL, mcs);

	ldb->media_player_name_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_TRACK_CHANGED_CHRC_UUID);
	ldb->track_changed = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_NONE, BT_GATT_CHRC_PROP_NOTIFY,
		NULL, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->track_changed, 0);

	ldb->track_changed_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_TRACK_TITLE_CHRC_UUID);
	ldb->track_title = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY,
		read_track_title, NULL, mcs);

	ldb->track_title_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_TRACK_DURATION_CHRC_UUID);
	ldb->track_duration = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY,
		read_track_duration, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->track_duration,
							sizeof(int32_t));

	ldb->track_duration_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_TRACK_POSITION_CHRC_UUID);
	ldb->track_position = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY |
		BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP,
		read_track_position, write_track_position, mcs);
	gatt_db_attribute_set_fixed_length(ldb->track_position,
							sizeof(int32_t));

	ldb->track_position_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_PLAYBACK_SPEED_CHRC_UUID);
	ldb->playback_speed = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY |
		BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP,
		read_playback_speed, write_playback_speed, mcs);
	gatt_db_attribute_set_fixed_length(ldb->playback_speed, sizeof(int8_t));

	ldb->playback_speed_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_SEEKING_SPEED_CHRC_UUID);
	ldb->seeking_speed = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY,
		read_seeking_speed, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->seeking_speed, sizeof(int8_t));

	ldb->seeking_speed_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_PLAYING_ORDER_CHRC_UUID);
	ldb->playing_order = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY |
		BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP,
		read_playing_order, write_playing_order, mcs);
	gatt_db_attribute_set_fixed_length(ldb->playing_order, sizeof(uint8_t));

	ldb->playing_order_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_PLAYING_ORDER_SUPPORTED_CHRC_UUID);
	ldb->playing_order_supported = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ,
		read_playing_order_supported, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->playing_order_supported,
							sizeof(uint16_t));

	bt_uuid16_create(&uuid, MCS_MEDIA_STATE_CHRC_UUID);
	ldb->media_state = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY,
		read_media_state, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->media_state, sizeof(uint8_t));

	ldb->media_state_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_MEDIA_CP_CHRC_UUID);
	ldb->media_cp = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_WRITE,
		BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_NOTIFY |
					BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP,
		NULL, write_media_cp, mcs);

	ldb->media_cp_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_MEDIA_CP_OP_SUPPORTED_CHRC_UUID);
	ldb->media_cp_op_supported = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ,
		BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY,
		read_media_cp_op_supported, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->media_cp_op_supported,
							sizeof(uint32_t));

	ldb->media_cp_op_supported_ccc = gatt_db_service_add_ccc(
		ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);

	bt_uuid16_create(&uuid, MCS_CCID_CHRC_UUID);
	ldb->ccid = gatt_db_service_add_characteristic(
		ldb->service, &uuid,
		BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ,
		read_ccid, NULL, mcs);
	gatt_db_attribute_set_fixed_length(ldb->ccid, sizeof(uint8_t));

	return true;
}

uint8_t bt_mcs_get_ccid(struct bt_mcs *mcs)
{
	return mcs->ldb.ccid_value;
}

struct match_mcs_data {
	struct gatt_db *db;
	bool gmcs;
	bool any;
	int ccid;
};

static bool match_mcs(const void *data, const void *user_data)
{
	const struct bt_mcs *mcs = data;
	const struct match_mcs_data *match = user_data;

	if (match->db != mcs->db)
		return false;
	if (match->gmcs)
		return mcs->ldb.gmcs;
	if (match->any)
		return true;
	return match->ccid == mcs->ldb.ccid_value;
}

static int mcs_alloc_ccid(struct gatt_db *db)
{
	unsigned int ccid;

	if (!db)
		return 0;

	for (ccid = servers_ccid; ccid < servers_ccid + 0x100u; ccid++) {
		struct match_mcs_data match = { .db = db, .ccid = ccid & 0xff };

		if (!queue_find(servers, match_mcs, &match)) {
			servers_ccid = ccid + 1;
			return match.ccid;
		}
	}

	return -ENOENT;
}

void bt_mcs_test_util_reset_ccid(void)
{
	servers_ccid = 0;
}

struct bt_mcs *bt_mcs_register(struct gatt_db *db, bool is_gmcs,
			const struct bt_mcs_callback *cb, void *user_data)
{
	struct bt_mcs *mcs;
	int ccid;

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

	if (is_gmcs) {
		struct match_mcs_data match = { .db = db, .gmcs = true };

		/* Only one GMCS possible */
		if (queue_find(servers, match_mcs, &match))
			return NULL;
	}

	ccid = mcs_alloc_ccid(db);
	if (ccid < 0)
		return NULL;

	mcs = new0(struct bt_mcs, 1);
	mcs->db = db;
	mcs->ldb.ccid_value = ccid;
	mcs->cb = cb;
	mcs->user_data = user_data;

	mcs->media_state = BT_MCS_STATE_INACTIVE;
	mcs->sessions = queue_new();

	if (!mcs_init_db(mcs, is_gmcs)) {
		free(mcs);
		return NULL;
	}

	gatt_db_ref(mcs->db);

	if (!servers)
		servers = queue_new();
	queue_push_tail(servers, mcs);

	gatt_db_service_set_active(mcs->ldb.service, true);
	return mcs;
}

void bt_mcs_unregister(struct bt_mcs *mcs)
{
	if (!mcs)
		return;

	if (mcs->cb->destroy)
		mcs->cb->destroy(mcs->user_data);

	queue_remove(servers, mcs);

	gatt_db_remove_service(mcs->db, mcs->ldb.service);
	gatt_db_unref(mcs->db);

	if (queue_isempty(servers)) {
		queue_destroy(servers, NULL);
		servers = NULL;
	}

	queue_destroy(mcs->sessions, session_remove);

	free(mcs);
}

void bt_mcs_unregister_all(struct gatt_db *db)
{
	struct bt_mcs *mcs;

	do {
		struct match_mcs_data match = { .db = db, .any = true };

		mcs = queue_find(servers, match_mcs, &match);
		bt_mcs_unregister(mcs);
	} while (mcs);
}

/*
 * MCP Client
 */

static void mcp_service_reread(struct bt_mcp_service *service,
					struct gatt_db_attribute *attrib,
					bool skip_notify);
static void foreach_mcs_char(struct gatt_db_attribute *attr, void *user_data);

static void mcp_debug_func(const char *str, void *user_data)
{
	struct bt_mcp *mcp = user_data;

	mcp->cb->debug(mcp->user_data, str);
}

static void mcp_debug(struct bt_mcp *mcp, const char *format, ...)
{
	va_list ap;

	if (!mcp || !format || !mcp->cb->debug)
		return;

	va_start(ap, format);
	util_debug_va(mcp_debug_func, mcp, format, ap);
	va_end(ap);
}

static bool match_ccid(const void *data, const void *user_data)
{
	const struct bt_mcp_service *service = data;

	return service->rdb.ccid_value == (int)PTR_TO_UINT(user_data);
}

static struct bt_mcp_service *mcp_service(struct bt_mcp *mcp, uint8_t ccid)
{
	if (!mcp)
		return NULL;

	return queue_find(mcp->services, match_ccid, UINT_TO_PTR(ccid));
}

static bool match_pending(const void *data, const void *user_data)
{
	const struct bt_mcp_pending *pending = data;

	return pending->id == PTR_TO_UINT(user_data);
}

static struct bt_mcp_pending *mcp_pending_new(struct bt_mcp_service *service)
{
	struct bt_mcp_pending *pending;

	if (queue_length(service->pending) > MAX_PENDING)
		return NULL;

	while (!service->pending_id || queue_find(service->pending,
			match_pending, UINT_TO_PTR(service->pending_id)))
		service->pending_id++;

	pending = new0(struct bt_mcp_pending, 1);
	pending->service = service;
	pending->id = service->pending_id++;
	return pending;
}

static unsigned int mcp_send(struct bt_mcp_service *service, uint8_t *buf,
								uint16_t length)
{
	struct bt_mcp *mcp = service->mcp;
	uint16_t handle;
	struct bt_mcp_pending *pending;
	int ret;
	uint8_t op = buf[0];

	if (!gatt_db_attribute_get_char_data(service->rdb.media_cp, NULL,
						&handle, NULL, NULL, NULL))
		return 0;

	pending = mcp_pending_new(service);
	if (!pending)
		return 0;

	ret = bt_gatt_client_write_without_response(mcp->client,
						handle, false, buf, length);
	if (!ret) {
		free(pending);
		return 0;
	}

	pending->op = op;
	queue_push_tail(service->pending, pending);

	DBG_SVC(service, "%u", pending->id);
	return pending->id;
}

static void mcp_pending_write_cb(bool success, uint8_t att_ecode,
								void *user_data)
{
	struct bt_mcp_pending *pending = user_data;

	if (!success) {
		pending->write.result = BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE;
		return;
	}

	pending->write.result = BT_MCS_RESULT_SUCCESS;

	/* If the attribute doesn't have notify, reread to get the new value */
	mcp_service_reread(pending->service, pending->write.attrib, true);
}

static void mcp_pending_write_done(void *user_data)
{
	struct bt_mcp_pending *pending = user_data;
	struct bt_mcp_service *service = pending->service;
	struct bt_mcp *mcp = service->mcp;

	DBG_SVC(service, "write %u", pending->id);

	queue_remove(service->pending, pending);

	if (mcp->cb->complete)
		mcp->cb->complete(mcp->user_data, pending->id,
							pending->write.result);
	free(pending);
}

static unsigned int mcp_write_chrc(struct bt_mcp_service *service,
		struct gatt_db_attribute *attrib, void *data, uint16_t length)
{
	struct bt_mcp *mcp;
	struct bt_mcp_pending *pending;
	uint16_t handle;

	if (!service)
		return 0;

	mcp = service->mcp;

	if (!gatt_db_attribute_get_char_data(attrib, NULL, &handle, NULL, NULL,
									NULL))
		return 0;

	pending = mcp_pending_new(service);
	if (!pending)
		return 0;

	pending->write.attrib = attrib;
	pending->write.client_id = bt_gatt_client_write_value(mcp->client,
				handle, data, length, mcp_pending_write_cb,
				pending, mcp_pending_write_done);
	if (!pending->write.client_id) {
		free(pending);
		return 0;
	}

	queue_push_tail(service->pending, pending);
	return pending->id;
}

static bool match_pending_write(const void *data, const void *user_data)
{
	const struct bt_mcp_pending *pending = data;

	return !pending->op;
}

static void mcp_cancel_pending_writes(struct bt_mcp_service *service)
{
	struct bt_mcp_pending *pending;
	struct bt_gatt_client *client = service->mcp->client;

	do {
		pending = queue_remove_if(service->pending, match_pending_write,
									NULL);
		if (pending) {
			if (!bt_gatt_client_cancel(client,
						pending->write.client_id))
				free(pending);
		}
	} while (pending);
}

static unsigned int mcp_command(struct bt_mcp *mcp, uint8_t ccid, uint8_t op,
								int32_t arg)
{
	const struct mcs_command *cmd = mcs_get_command(op);
	struct bt_mcp_service *service = mcp_service(mcp, ccid);
	uint8_t buf[5];
	struct iovec iov = { .iov_base = buf, .iov_len = 0 };

	if (!service || !cmd)
		return 0;

	if (!(service->rdb.media_cp_op_supported_value & cmd->support))
		return 0;

	DBG_SVC(service, "%s %d", cmd->name, arg);

	util_iov_push_u8(&iov, op);
	if (cmd->int32_arg)
		util_iov_push_le32(&iov, arg);

	return mcp_send(service, iov.iov_base, iov.iov_len);
}

unsigned int bt_mcp_play(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_PLAY, 0);
}

unsigned int bt_mcp_pause(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_PAUSE, 0);
}

unsigned int bt_mcp_fast_rewind(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_FAST_REWIND, 0);
}

unsigned int bt_mcp_fast_forward(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_FAST_FORWARD, 0);
}

unsigned int bt_mcp_stop(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_STOP, 0);
}

unsigned int bt_mcp_move_relative(struct bt_mcp *mcp, uint8_t ccid,
								int32_t offset)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_MOVE_RELATIVE, offset);
}

unsigned int bt_mcp_previous_segment(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_PREV_SEGMENT, 0);
}

unsigned int bt_mcp_next_segment(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_NEXT_SEGMENT, 0);
}

unsigned int bt_mcp_first_segment(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_FIRST_SEGMENT, 0);
}

unsigned int bt_mcp_last_segment(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_LAST_SEGMENT, 0);
}

unsigned int bt_mcp_goto_segment(struct bt_mcp *mcp, uint8_t ccid, int32_t n)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_GOTO_SEGMENT, n);
}

unsigned int bt_mcp_previous_track(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_PREV_TRACK, 0);
}

unsigned int bt_mcp_next_track(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_NEXT_TRACK, 0);
}

unsigned int bt_mcp_first_track(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_FIRST_TRACK, 0);
}

unsigned int bt_mcp_last_track(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_LAST_TRACK, 0);
}

unsigned int bt_mcp_goto_track(struct bt_mcp *mcp, uint8_t ccid, int32_t n)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_GOTO_TRACK, n);
}

unsigned int bt_mcp_previous_group(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_PREV_GROUP, 0);
}

unsigned int bt_mcp_next_group(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_NEXT_GROUP, 0);
}

unsigned int bt_mcp_first_group(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_FIRST_GROUP, 0);
}

unsigned int bt_mcp_last_group(struct bt_mcp *mcp, uint8_t ccid)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_LAST_GROUP, 0);
}

unsigned int bt_mcp_goto_group(struct bt_mcp *mcp, uint8_t ccid, int32_t n)
{
	return mcp_command(mcp, ccid, BT_MCS_CMD_GOTO_GROUP, n);
}

unsigned int bt_mcp_set_track_position(struct bt_mcp *mcp, uint8_t ccid,
							int32_t position)
{
	struct bt_mcp_service *service = mcp_service(mcp, ccid);

	position = cpu_to_le32(position);
	return mcp_write_chrc(service, service->rdb.track_position,
						&position, sizeof(position));
}

unsigned int bt_mcp_set_playback_speed(struct bt_mcp *mcp, uint8_t ccid,
								int8_t value)
{
	struct bt_mcp_service *service = mcp_service(mcp, ccid);

	return mcp_write_chrc(service, service->rdb.playback_speed,
							&value, sizeof(value));
}

unsigned int bt_mcp_set_playing_order(struct bt_mcp *mcp, uint8_t ccid,
								uint8_t value)
{
	struct bt_mcp_service *service = mcp_service(mcp, ccid);
	uint16_t support = 0;
	unsigned int i;

	if (!service)
		return 0;

	for (i = 0; i < ARRAY_SIZE(mcs_playing_orders); ++i) {
		if (mcs_playing_orders[i].order == value) {
			support = mcs_playing_orders[i].support;
			break;
		}
	}
	if (!(service->rdb.playing_order_supported_value & support))
		return 0;

	return mcp_write_chrc(service, service->rdb.playing_order,
							&value, sizeof(value));
}

uint16_t bt_mcp_get_supported_playing_order(struct bt_mcp *mcp, uint8_t ccid)
{
	struct bt_mcp_service *service = mcp_service(mcp, ccid);

	if (!service)
		return 0;
	return service->rdb.playing_order_supported_value;
}

uint32_t bt_mcp_get_supported_commands(struct bt_mcp *mcp, uint8_t ccid)
{
	struct bt_mcp_service *service = mcp_service(mcp, ccid);

	if (!service)
		return 0;
	return service->rdb.media_cp_op_supported_value;
}

#define LISTENER_CB(service, method, ...) \
	do { \
		const struct queue_entry *entry = \
				queue_get_entries((service)->listeners); \
		for (; entry; entry = entry->next) { \
			struct bt_mcp_listener *listener = entry->data; \
			if (listener->cb->method) \
				listener->cb->method(listener->user_data, \
							## __VA_ARGS__); \
		} \
	} while (0)

static void update_media_player_name(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;

	if (!success) {
		DBG_SVC(service, "Unable to read Media Player Name: "
						"error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Media Player Name");

	LISTENER_CB(service, media_player_name, value, length);
}

static void update_track_changed(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;

	if (!success) {
		DBG_SVC(service, "Unable to read Track Changed: "
						"error 0x%02x", att_ecode);
		return;
	}

	mcp_service_reread(service, NULL, true);

	DBG_SVC(service, "Track Changed");

	LISTENER_CB(service, track_changed);
}

static void update_track_title(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;

	if (!success) {
		DBG_SVC(service, "Unable to read Track Title: error 0x%02x",
								att_ecode);
		return;
	}

	DBG_SVC(service, "Track Title");

	LISTENER_CB(service, track_title, value, length);
}

static void update_track_duration(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint32_t v;

	if (!success || !util_iov_pull_le32(&iov, &v)) {
		DBG_SVC(service, "Unable to read Track Duration: "
						"error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Track Duration: %d", (int32_t)v);

	LISTENER_CB(service, track_duration, (int32_t)v);
}

static void update_track_position(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint32_t v;

	if (!success || !util_iov_pull_le32(&iov, &v)) {
		DBG_SVC(service, "Unable to read Track Position: "
						"error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Track Position: %d", (int32_t)v);

	LISTENER_CB(service, track_position, (int32_t)v);
}

static void update_playback_speed(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint8_t v;

	if (!success || !util_iov_pull_u8(&iov, &v)) {
		DBG_SVC(service, "Unable to read Playback Speed: "
						"error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Playback Speed: %d", (int8_t)v);

	LISTENER_CB(service, playback_speed, (int8_t)v);
}

static void update_seeking_speed(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint8_t v;

	if (!success || !util_iov_pull_u8(&iov, &v)) {
		DBG_SVC(service, "Unable to read Seeking Speed: "
						"error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Seeking Speed: %d", (int8_t)v);

	LISTENER_CB(service, seeking_speed, (int8_t)v);
}

static void update_playing_order(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint8_t v;

	if (!success || !util_iov_pull_u8(&iov, &v)) {
		DBG_SVC(service, "Unable to read Playing Order: "
						"error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Playing Order: %u", v);

	LISTENER_CB(service, playing_order, v);
}

static void update_playing_order_supported(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint16_t v;

	if (!success || !util_iov_pull_le16(&iov, &v)) {
		DBG_SVC(service, "Unable to read "
			"Playing Order Supported: error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Playing Order Supported: %u", v);

	service->rdb.playing_order_supported_value = v;
}

static void update_media_state(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint8_t v;

	if (!success || !util_iov_pull_u8(&iov, &v)) {
		DBG_SVC(service, "Unable to read Media State: error 0x%02x",
								att_ecode);
		return;
	}

	DBG_SVC(service, "Media State: %u", v);

	LISTENER_CB(service, media_state, v);
}

static bool match_pending_op(const void *data, const void *user_data)
{
	const struct bt_mcp_pending *pending = data;

	return pending->op && pending->op == PTR_TO_UINT(user_data);
}

static void update_media_cp(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct bt_mcp *mcp = service->mcp;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	struct bt_mcp_pending *pending;
	uint8_t op, result;

	if (!success || !util_iov_pull_u8(&iov, &op) ||
					!util_iov_pull_u8(&iov, &result)) {
		DBG_SVC(service, "Unable to read Media CP: error 0x%02x",
								att_ecode);
		return;
	}

	DBG_SVC(service, "Media CP %u result %u", op, result);

	pending = queue_remove_if(service->pending, match_pending_op,
							UINT_TO_PTR(op));
	if (!pending)
		return;

	if (mcp->cb->complete)
		mcp->cb->complete(mcp->user_data, pending->id, result);

	free(pending);
}

static void update_media_cp_op_supported(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint32_t v;

	if (!success || !util_iov_pull_le32(&iov, &v)) {
		DBG_SVC(service, "Unable to read "
			"Media CP Op Supported: error 0x%02x", att_ecode);
		return;
	}

	DBG_SVC(service, "Media CP Op Supported: %d", v);

	service->rdb.media_cp_op_supported_value = v;
}

static void update_add_service(void *data, void *user_data)
{
	struct bt_mcp_service *service = data;
	struct bt_mcp *mcp = user_data;

	if (service->rdb.ccid_value < 0)
		return;

	if (service->ready)
		return;

	service->ready = true;
	if (mcp->cb->ccid)
		mcp->cb->ccid(mcp->user_data, service->rdb.ccid_value,
							service->rdb.gmcs);
}

static void update_ccid(bool success, uint8_t att_ecode,
				const uint8_t *value, uint16_t length,
				void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
	uint8_t v;

	if (!success || !util_iov_pull_u8(&iov, &v)) {
		DBG_SVC(service, "Unable to read CCID: error 0x%02x",
								att_ecode);
		return;
	}

	DBG_SVC(service, "CCID: %u", v);

	service->rdb.ccid_value = v;

	gatt_db_service_foreach_char(service->rdb.service, foreach_mcs_char,
								service);

	update_add_service(service, service->mcp);
}

static void mcp_service_reread(struct bt_mcp_service *service,
					struct gatt_db_attribute *attrib,
					bool skip_notify)
{
	const struct {
		struct gatt_db_attribute *attr;
		bt_gatt_client_read_callback_t cb;
	} attrs[] = {
		{ service->rdb.track_title, update_track_title },
		{ service->rdb.track_duration, update_track_duration },
		{ service->rdb.track_position, update_track_position },
		{ service->rdb.playback_speed, update_playback_speed },
		{ service->rdb.seeking_speed, update_seeking_speed },
		{ service->rdb.playing_order, update_playing_order },
		{ service->rdb.playing_order_supported,
		  update_playing_order_supported },
		{ service->rdb.media_state, update_media_state },
		{ service->rdb.media_cp_op_supported,
		  update_media_cp_op_supported },
	};
	struct bt_gatt_client *client = service->mcp->client;
	uint16_t value_handle;
	uint8_t props;
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(attrs); ++i) {
		if (!attrs[i].attr)
			continue;
		if (attrib && attrs[i].attr != attrib)
			continue;

		if (!gatt_db_attribute_get_char_data(attrs[i].attr, NULL,
					&value_handle, &props, NULL, NULL))
			continue;
		if (skip_notify && (props & BT_GATT_CHRC_PROP_NOTIFY))
			continue;

		DBG_SVC(service, "re-read handle 0x%04x", value_handle);

		bt_gatt_client_read_value(client, value_handle,
						attrs[i].cb, service, NULL);
	}
}

static void notify_media_player_name(struct bt_mcp_service *service)
{
	/* On player name change, re-read all attributes */
	mcp_service_reread(service, NULL, false);
}

static void mcp_idle(void *data)
{
	struct bt_mcp *mcp = data;

	DBG_MCP(mcp, "");

	mcp->idle_id = 0;

	if (!mcp->ready) {
		mcp->ready = true;
		if (mcp->cb->ready)
			mcp->cb->ready(mcp->user_data);
	}
}

struct chrc_notify_data {
	const char *name;
	struct bt_mcp_service *service;
	bt_gatt_client_read_callback_t cb;
	void (*notify_cb)(struct bt_mcp_service *service);
};

static void chrc_register(uint16_t att_ecode, void *user_data)
{
	struct chrc_notify_data *data = user_data;

	if (att_ecode)
		DBG_SVC(data->service, "%s notification failed: 0x%04x",
							data->name, att_ecode);
}

static void chrc_notify(uint16_t value_handle, const uint8_t *value,
					uint16_t length, void *user_data)
{
	struct chrc_notify_data *data = user_data;
	struct bt_mcp_service *service = data->service;
	struct bt_gatt_client *client = service->mcp->client;
	uint16_t mtu = bt_gatt_client_get_mtu(client);

	DBG_SVC(service, "Notify %s", data->name);

	if (length == mtu - 3) {
		/* Probably truncated value */
		DBG_SVC(service, "Read %s", data->name);

		bt_gatt_client_read_value(client, value_handle,
						data->cb, service, NULL);
		return;
	}

	data->cb(true, 0xff, value, length, data->service);

	if (data->notify_cb)
		data->notify_cb(service);
}

static void foreach_mcs_char(struct gatt_db_attribute *attr, void *user_data)
{
	struct bt_mcp_service *service = user_data;
	struct bt_mcp *mcp = service->mcp;
	const struct {
		uint16_t uuid;
		const char *name;
		struct gatt_db_attribute **dst;
		bt_gatt_client_read_callback_t cb;
		void (*notify_cb)(struct bt_mcp_service *service);
		bool no_read;
		bool no_notify;
	} attrs[] = {
		{ MCS_CCID_CHRC_UUID, "CCID", &service->rdb.ccid,
		  update_ccid, .no_notify = true },
		{ MCS_MEDIA_PLAYER_NAME_CHRC_UUID, "Media Player Name",
		  &service->rdb.media_player_name, update_media_player_name,
		  .notify_cb = notify_media_player_name },
		{ MCS_TRACK_CHANGED_CHRC_UUID, "Track Changed",
		  &service->rdb.track_changed, update_track_changed,
		  .no_read = true },
		{ MCS_TRACK_TITLE_CHRC_UUID, "Track Title",
		  &service->rdb.track_title, update_track_title },
		{ MCS_TRACK_DURATION_CHRC_UUID, "Track Duration",
		  &service->rdb.track_duration, update_track_duration },
		{ MCS_TRACK_POSITION_CHRC_UUID, "Track Position",
		  &service->rdb.track_position, update_track_position },
		{ MCS_PLAYBACK_SPEED_CHRC_UUID, "Playback Speed",
		  &service->rdb.playback_speed, update_playback_speed },
		{ MCS_SEEKING_SPEED_CHRC_UUID, "Seeking Speed",
		  &service->rdb.seeking_speed, update_seeking_speed },
		{ MCS_PLAYING_ORDER_CHRC_UUID, "Playing Order",
		  &service->rdb.playing_order, update_playing_order },
		{ MCS_PLAYING_ORDER_SUPPORTED_CHRC_UUID,
		  "Playing Order Supported",
		  &service->rdb.playing_order_supported,
		  update_playing_order_supported, .no_notify = true },
		{ MCS_MEDIA_STATE_CHRC_UUID, "Media State",
		  &service->rdb.media_state, update_media_state },
		{ MCS_MEDIA_CP_CHRC_UUID, "Media Control Point",
		  &service->rdb.media_cp, update_media_cp },
		{ MCS_MEDIA_CP_OP_SUPPORTED_CHRC_UUID, "Media CP Op Supported",
		  &service->rdb.media_cp_op_supported,
		  update_media_cp_op_supported },
	};
	struct bt_gatt_client *client = service->mcp->client;
	bt_uuid_t uuid, uuid_attr;
	uint16_t value_handle;
	uint8_t props;
	unsigned int i;

	if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
						&props, NULL, &uuid_attr))
		return;

	for (i = 0; i < ARRAY_SIZE(attrs); ++i) {
		unsigned int id;
		struct chrc_notify_data *data;

		if (*attrs[i].dst)
			continue;

		bt_uuid16_create(&uuid, attrs[i].uuid);
		if (bt_uuid_cmp(&uuid_attr, &uuid))
			continue;

		DBG_SVC(service, "%s found: handle 0x%04x",
						attrs[i].name, value_handle);
		*attrs[i].dst = attr;

		if ((props & BT_GATT_CHRC_PROP_READ) && !attrs[i].no_read)
			bt_gatt_client_read_value(client, value_handle,
						attrs[i].cb, service, NULL);

		if (!(props & BT_GATT_CHRC_PROP_NOTIFY) || attrs[i].no_notify)
			break;
		if (service->notify_id_count >= ARRAY_SIZE(service->notify_id))
			break;

		data = new0(struct chrc_notify_data, 1);
		data->name = attrs[i].name;
		data->service = service;
		data->cb = attrs[i].cb;

		id = bt_gatt_client_register_notify(client, value_handle,
						chrc_register, chrc_notify,
						data, free);
		if (id)
			service->notify_id[service->notify_id_count++] = id;
		else
			free(data);

		break;
	}

	if (!mcp->idle_id && i < ARRAY_SIZE(attrs))
		mcp->idle_id = bt_gatt_client_idle_register(mcp->client,
							mcp_idle, mcp, NULL);
}

static void foreach_mcs_ccid(struct gatt_db_attribute *attr, void *user_data)
{
	bt_uuid_t uuid, uuid_attr;

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

	bt_uuid16_create(&uuid, MCS_CCID_CHRC_UUID);
	if (bt_uuid_cmp(&uuid_attr, &uuid))
		return;

	foreach_mcs_char(attr, user_data);
}

static void listener_destroy(void *data)
{
	struct bt_mcp_listener *listener = data;

	if (listener->cb->destroy)
		listener->cb->destroy(listener->user_data);

	free(listener);
}

static void mcp_service_destroy(void *data)
{
	struct bt_mcp_service *service = data;
	struct bt_gatt_client *client = service->mcp->client;
	unsigned int i;

	mcp_cancel_pending_writes(service);

	queue_destroy(service->listeners, listener_destroy);

	for (i = 0; i < service->notify_id_count; ++i)
		bt_gatt_client_unregister_notify(client, service->notify_id[i]);

	queue_destroy(service->pending, free);
	free(service);
}

static void foreach_mcs_service(struct gatt_db_attribute *attr, void *user_data)
{
	struct bt_mcp *mcp = user_data;
	struct bt_mcp_service *service;
	bt_uuid_t uuid, uuid_attr;
	bool gmcs, mcs;

	DBG_MCP(mcp, "");

	if (!gatt_db_attribute_get_service_uuid(attr, &uuid_attr))
		return;

	bt_uuid16_create(&uuid, GMCS_UUID);
	gmcs = !bt_uuid_cmp(&uuid_attr, &uuid);

	if (gmcs != mcp->gmcs)
		return;

	bt_uuid16_create(&uuid, MCS_UUID);
	mcs = !bt_uuid_cmp(&uuid_attr, &uuid);

	if (!gmcs && !mcs)
		return;

	service = new0(struct bt_mcp_service, 1);
	service->mcp = mcp;
	service->rdb.gmcs = gmcs;
	service->rdb.service = attr;
	service->rdb.ccid_value = -1;
	service->pending = queue_new();
	service->listeners = queue_new();

	/* Find CCID first */
	gatt_db_service_foreach_char(attr, foreach_mcs_ccid, service);

	queue_push_tail(mcp->services, service);
}

static bool match_service_attr(const void *data, const void *user_data)
{
	const struct bt_mcp_service *service = data;

	return service->rdb.service == user_data;
}

static void mcp_service_added(struct gatt_db_attribute *attr, void *user_data)
{
	struct bt_mcp *mcp = user_data;

	foreach_mcs_service(attr, mcp);
}

static void mcp_service_removed(struct gatt_db_attribute *attr, void *user_data)
{
	struct bt_mcp *mcp = user_data;

	queue_remove_all(mcp->services, match_service_attr, attr,
							mcp_service_destroy);
}

struct bt_mcp *bt_mcp_attach(struct bt_gatt_client *client, bool gmcs,
			const struct bt_mcp_callback *cb, void *user_data)
{
	struct bt_mcp *mcp;
	struct gatt_db *db;
	bt_uuid_t uuid;

	if (!cb)
		return NULL;

	client = bt_gatt_client_clone(client);
	if (!client)
		return NULL;

	mcp = new0(struct bt_mcp, 1);
	mcp->gmcs = gmcs;
	mcp->client = client;
	mcp->services = queue_new();
	mcp->cb = cb;
	mcp->user_data = user_data;

	DBG_MCP(mcp, "");

	db = bt_gatt_client_get_db(client);

	bt_uuid16_create(&uuid, GMCS_UUID);
	gatt_db_foreach_service(db, &uuid, foreach_mcs_service, mcp);

	bt_uuid16_create(&uuid, MCS_UUID);
	gatt_db_foreach_service(db, &uuid, foreach_mcs_service, mcp);

	mcp->db_id = gatt_db_register(db, mcp_service_added,
						mcp_service_removed, mcp, NULL);

	if (!mcp->idle_id)
		mcp_idle(mcp);

	return mcp;
}

void bt_mcp_detach(struct bt_mcp *mcp)
{
	struct gatt_db *db;

	if (!mcp)
		return;

	DBG_MCP(mcp, "");

	queue_destroy(mcp->services, mcp_service_destroy);

	if (mcp->cb->destroy)
		mcp->cb->destroy(mcp->user_data);

	if (mcp->idle_id)
		bt_gatt_client_idle_unregister(mcp->client, mcp->idle_id);

	db = bt_gatt_client_get_db(mcp->client);
	if (mcp->db_id)
		gatt_db_unregister(db, mcp->db_id);

	bt_gatt_client_unref(mcp->client);

	free(mcp);
}

bool bt_mcp_add_listener(struct bt_mcp *mcp, uint8_t ccid,
				const struct bt_mcp_listener_callback *cb,
				void *user_data)
{
	struct bt_mcp_listener *listener;
	struct bt_mcp_service *service;

	if (!cb)
		return false;

	service = queue_find(mcp->services, match_ccid, UINT_TO_PTR(ccid));
	if (!service)
		return false;

	listener = new0(struct bt_mcp_listener, 1);
	listener->cb = cb;
	listener->user_data = user_data;

	queue_push_tail(service->listeners, listener);
	return true;
}

struct bt_gatt_client *bt_mcp_test_util_get_client(struct bt_mcp *mcp)
{
	return mcp->client;
}
