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

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <fcntl.h>

#include <glib.h>

#include "bluetooth/bluetooth.h"
#include "bluetooth/uuid.h"
#include "src/shared/util.h"
#include "src/shared/tester.h"
#include "src/shared/queue.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-client.h"
#include "src/shared/gatt-server.h"
#include "src/shared/io.h"
#include "src/shared/mcp.h"
#include "src/shared/mcs.h"

struct test_config {
	bool gmcs;
	const struct bt_mcs_callback *mcs_cb;
	const struct bt_mcp_callback *mcp_cb;
	const struct bt_mcp_listener_callback *listener_cb;
	const struct iovec *setup_data;
	const size_t setup_data_len;
	uint8_t expect_cmd;
	uint8_t expect_cmd_result;
	uint8_t state;
};

struct test_data {
	struct gatt_db *db;
	struct bt_gatt_server *server;
	struct bt_gatt_client *client;
	struct queue *ccc_states;
	struct bt_mcp *mcp;
	struct bt_mcs *mcs;
	unsigned int id;
	unsigned int step;
	size_t iovcnt;
	struct iovec *iov;
	const struct test_config *cfg;
};

struct ccc_state {
	uint16_t handle;
	uint16_t value;
};

#define FAIL_TEST() \
	do { tester_warn("%s:%d: failed in %s", __FILE__, __LINE__, __func__); \
		tester_test_failed(); } while (0)

#define iov_data(args...) ((const struct iovec[]) { args })

#define define_test(name, setup, function, _cfg, args...)	\
	do {							\
		const struct iovec iov[] = { args };		\
		static struct test_data data;			\
		data.iovcnt = ARRAY_SIZE(iov);			\
		data.iov = data.iovcnt ?			\
			util_iov_dup(iov, data.iovcnt) : NULL;	\
		data.cfg = _cfg;				\
		tester_add(name, &data, setup, function,	\
				test_teardown);			\
	} while (0)

static void print_debug(const char *str, void *user_data)
{
	const char *prefix = user_data;

	if (tester_use_debug())
		tester_debug("%s%s", prefix, str);
}

static void mcp_debug(void *data, const char *str)
{
	print_debug(str, "mcp: ");
}

static void mcs_debug(void *data, const char *str)
{
	print_debug(str, "mcs: ");
}

static void test_teardown(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	bt_gatt_client_unref(data->client);

	bt_gatt_server_unref(data->server);
	util_iov_free(data->iov, data->iovcnt);

	gatt_db_unref(data->db);

	bt_mcp_detach(data->mcp);

	bt_mcs_unregister(data->mcs);

	queue_destroy(data->ccc_states, free);

	tester_teardown_complete();
}

/* ATT: Exchange MTU Response (0x03) len 2
 *   Server RX MTU: 64
 * ATT: Exchange MTU Request (0x02) len 2
 *    Client RX MTU: 64
 * ATT: Read By Type Request (0x08) len 6
 *   Handle range: 0x0001-0xffff
 *   Attribute type: Server Supported Features (0x2b3a)
 * ATT: Error Response (0x01) len 4
 *   Read By Type Request (0x08)
 *   Handle: 0x0001
 *   Error: Attribute Not Found (0x0a)
 */
#define MCS_MTU_FEAT \
	IOV_DATA(0x02, 0x40, 0x00), \
	IOV_DATA(0x03, 0x40, 0x00), \
	IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b), \
	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)

/* ATT: Read By Group Type Request (0x10) len 6
 *   Handle range: 0x0001-0xffff
 *   Attribute group type: Primary Service (0x2800)
 * ATT: Read By Group Type Response (0x11) len 37
 *   Attribute data length: 6
 *   Attribute group list: 1 entries
 *   Handle range: 0x0001-0x0026
 *   UUID: (Generic) Media Control Service (0x1849 / 0x1848)
 * ATT: Read By Group Type Request (0x10) len 6
 *   Handle range: 0x0027-0xffff
 *   Attribute group type: Primary Service (0x2800)
 * ATT: Error Response (0x01) len 4
 *   Read By Group Type Request (0x10)
 *   Handle: 0x0004
 *   Error: Attribute Not Found (0x0a)
 */
#define MCS_PRIMARY_SERVICE(base, uuid...) \
	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \
	IOV_DATA(0x11, 0x06, \
		base + 0x01, 0x00, base + 0x26, 0x00, uuid), \
	IOV_DATA(0x10, base + 0x27, 0x00, 0xff, 0xff, 0x00, 0x28), \
	IOV_DATA(0x01, 0x10, base + 0x27, 0x00, 0x0a)

#define MCS_SERVICE	0x48, 0x18
#define GMCS_SERVICE	0x49, 0x18

/* ATT: Read By Group Type Request (0x10) len 6
 *   Handle range: 0x0001-0xffff
 *   Attribute group type: Secondary Service (0x2801)
 * ATT: Error Response (0x01) len 4
 *   Read By Group Type Request (0x10)
 *   Handle: 0x0001
 *   Error: Attribute Not Found (0x0a)
 */
#define NO_SECONDARY_SERVICE \
	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), \
	IOV_DATA(0x01, 0x10, 0x01, 0x00, 0x0a)

/* ATT: Read By Type Request (0x08) len 6
 *   Handle range: 0x0001-0x0026
 *   Attribute group type: Include (0x2802)
 * ATT: Error Response (0x01) len 4
 *   Read By Group Type Request (0x10)
 *   Handle: 0x0001
 *   Error: Attribute Not Found (0x0a)
 */
#define NO_INCLUDE(base) \
	IOV_DATA(0x08, 0x01+base, 0x00, 0x26+base, 0x00, 0x02, 0x28), \
	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)

/* ATT: Read By Type Request (0x08) len 6
 *   Handle range: 0x0001-0x0003
 *   Attribute type: Characteristic (0x2803)
 * ATT: Read By Type Response (0x09) len 57
 *   ...
 * ATT: Read By Type Request (0x08) len 6
 *   ...
 *   Attribute type: Characteristic (0x2803)
 * ATT: Read By Type Response (0x09) len 57
 *   ...
 * ATT: Read By Type Request (0x08) len 6
 *   Handle range: 0x0026-0x0026
 *   Attribute type: Characteristic (0x2803)
 * ATT: Error Response (0x01) len 4
 *   Read By Type Request (0x08)
 *   Handle: 0x0026
 *   Error: Attribute Not Found (0x0a)
 * ATT: Find Information Request (0x04)
 * ATT: Error Response
 */
#define IOV_CONTENT(data...)	data

#define HND(value)			((value) & 0xff), ((value) >> 8)
#define FIND_CHRC(vhnd, prop, uuid...)	HND(vhnd-1), prop, HND(vhnd), uuid

#define NAME			0x03
#define NAME_CCC		0x04
#define TRACK_CHG		0x06
#define TRACK_CHG_CCC		0x07
#define TRACK_TITLE		0x09
#define TRACK_TITLE_CCC		0x0a
#define TRACK_DUR		0x0c
#define TRACK_DUR_CCC		0x0d
#define TRACK_POS		0x0f
#define TRACK_POS_CCC		0x10
#define PLAY_SPEED		0x12
#define PLAY_SPEED_CCC		0x13
#define SEEK_SPEED		0x15
#define SEEK_SPEED_CCC		0x16
#define PLAY_ORDER		0x18
#define PLAY_ORDER_CCC		0x19
#define PLAY_ORDER_SUPP		0x1b
#define STATE			0x1d
#define STATE_CCC		0x1e
#define CP			0x20
#define CP_CCC			0x21
#define CP_SUPP			0x23
#define CP_SUPP_CCC		0x24
#define CCID			0x26

#define PROP_R			0x02
#define PROP_N			0x10
#define PROP_RN			0x12
#define PROP_RW			0x0e
#define PROP_WN			0x1c
#define PROP_RWN		0x1e

#define GMCS_FIND_CHRC(base) \
	IOV_DATA(0x08, 0x01+base, 0x00, 0x26+base, 0x00, 0x03, 0x28), \
	IOV_DATA(0x09, 0x07, \
		FIND_CHRC(NAME+base, PROP_RN, 0x93, 0x2b), \
		FIND_CHRC(TRACK_CHG+base, PROP_N, 0x96, 0x2b), \
		FIND_CHRC(TRACK_TITLE+base, PROP_RN, 0x97, 0x2b), \
		FIND_CHRC(TRACK_DUR+base, PROP_RN, 0x98, 0x2b), \
		FIND_CHRC(TRACK_POS+base, PROP_RWN, 0x99, 0x2b), \
		FIND_CHRC(PLAY_SPEED+base, PROP_RWN, 0x9a, 0x2b), \
		FIND_CHRC(SEEK_SPEED+base, PROP_RN, 0x9b, 0x2b), \
		FIND_CHRC(PLAY_ORDER+base, PROP_RWN, 0xa1, 0x2b)), \
	IOV_DATA(0x08, PLAY_ORDER+base, 0x00, 0x26+base, 0x00, 0x03, 0x28), \
	IOV_DATA(0x09, 0x07, \
		FIND_CHRC(PLAY_ORDER_SUPP+base, PROP_R, 0xa2, 0x2b), \
		FIND_CHRC(STATE+base, PROP_RN, 0xa3, 0x2b), \
		FIND_CHRC(CP+base, PROP_WN, 0xa4, 0x2b), \
		FIND_CHRC(CP_SUPP+base, PROP_RN, 0xa5, 0x2b), \
		FIND_CHRC(CCID+base, PROP_R, 0xba, 0x2b)), \
	IOV_DATA(0x08, CCID+base, 0x00, CCID+base, 0x00, 0x03, 0x28), \
	IOV_DATA(0x01, 0x08, CCID+base, 0x00, 0x0a)

/* As above but without optional Notify properties, and
 * ATT: Find Information Request (0x04)
 * ATT: Error Response (0x01) Attribute Not Found (0x0a)
 * for each missing CCC HND to keep handles the same.
 * Not valid for GMCS!
 */
#define MCS_FIND_CHRC(base) \
	IOV_DATA(0x08, 0x01+base, 0x00, 0x26+base, 0x00, 0x03, 0x28), \
	IOV_DATA(0x09, 0x07, \
		FIND_CHRC(NAME+base, PROP_R, 0x93, 0x2b), \
		FIND_CHRC(TRACK_CHG+base, PROP_N, 0x96, 0x2b), \
		FIND_CHRC(TRACK_TITLE+base, PROP_R, 0x97, 0x2b), \
		FIND_CHRC(TRACK_DUR+base, PROP_R, 0x98, 0x2b), \
		FIND_CHRC(TRACK_POS+base, PROP_RW, 0x99, 0x2b), \
		FIND_CHRC(PLAY_SPEED+base, PROP_RW, 0x9a, 0x2b), \
		FIND_CHRC(SEEK_SPEED+base, PROP_R, 0x9b, 0x2b), \
		FIND_CHRC(PLAY_ORDER+base, PROP_RW, 0xa1, 0x2b)), \
	IOV_DATA(0x08, PLAY_ORDER+base, 0x00, 0x26+base, 0x00, 0x03, 0x28), \
	IOV_DATA(0x09, 0x07, \
		FIND_CHRC(PLAY_ORDER_SUPP+base, PROP_R, 0xa2, 0x2b), \
		FIND_CHRC(STATE+base, PROP_RN, 0xa3, 0x2b), \
		FIND_CHRC(CP+base, PROP_WN, 0xa4, 0x2b), \
		FIND_CHRC(CP_SUPP+base, PROP_R, 0xa5, 0x2b), \
		FIND_CHRC(CCID+base, PROP_R, 0xba, 0x2b)), \
	IOV_DATA(0x08, CCID+base, 0x00, CCID+base, 0x00, 0x03, 0x28), \
	IOV_DATA(0x01, 0x08, CCID+base, 0x00, 0x0a), \
	IOV_DATA(0x04, HND(NAME_CCC), HND(NAME_CCC)), \
	IOV_DATA(0x01, 0x04, HND(NAME_CCC), 0x0a), \
	IOV_DATA(0x04, HND(TRACK_TITLE_CCC), HND(TRACK_TITLE_CCC)), \
	IOV_DATA(0x01, 0x04, HND(TRACK_TITLE_CCC), 0x0a), \
	IOV_DATA(0x04, HND(TRACK_DUR_CCC), HND(TRACK_DUR_CCC)), \
	IOV_DATA(0x01, 0x04, HND(TRACK_DUR_CCC), 0x0a), \
	IOV_DATA(0x04, HND(TRACK_POS_CCC), HND(TRACK_POS_CCC)), \
	IOV_DATA(0x01, 0x04, HND(TRACK_POS_CCC), 0x0a), \
	IOV_DATA(0x04, HND(PLAY_SPEED_CCC), HND(PLAY_SPEED_CCC)), \
	IOV_DATA(0x01, 0x04, HND(PLAY_SPEED_CCC), 0x0a), \
	IOV_DATA(0x04, HND(SEEK_SPEED_CCC), HND(SEEK_SPEED_CCC)), \
	IOV_DATA(0x01, 0x04, HND(SEEK_SPEED_CCC), 0x0a), \
	IOV_DATA(0x04, HND(PLAY_ORDER_CCC), HND(PLAY_ORDER_CCC)), \
	IOV_DATA(0x01, 0x04, HND(PLAY_ORDER_CCC), 0x0a), \
	IOV_DATA(0x04, HND(CP_SUPP_CCC), HND(CP_SUPP_CCC)), \
	IOV_DATA(0x01, 0x04, HND(CP_SUPP_CCC), 0x0a)

/* ACL Data TX: Handle 42 flags 0x00 dlen 11
 *   ATT: Read By Type Request (0x08) len 6
 *   Handle range: 0x0001-0xffff
 *   Attribute type: Database Hash (0x2b2a)
 * ATT: Error Response (0x01) len 4
 *   Read By Type Request (0x08)
 *   Handle: 0x0001
 *   Error: Attribute Not Found (0x0a)
 */
#define NO_DATABASE_HASH \
	IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x2a, 0x2b), \
	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)

/* GATT Discover All procedure */
#define GMCS_SETUP(base, uuid...) \
	MCS_MTU_FEAT, \
	MCS_PRIMARY_SERVICE(base, uuid), \
	NO_SECONDARY_SERVICE, \
	NO_INCLUDE(base), \
	GMCS_FIND_CHRC(base)

#define MCS_SETUP(base, uuid...) \
	MCS_MTU_FEAT, \
	MCS_PRIMARY_SERVICE(base, uuid), \
	NO_SECONDARY_SERVICE, \
	NO_INCLUDE(base), \
	MCS_FIND_CHRC(base)

static bool ccc_state_match(const void *a, const void *b)
{
	const struct ccc_state *ccc = a;
	uint16_t handle = PTR_TO_UINT(b);

	return ccc->handle == handle;
}

static struct ccc_state *find_ccc_state(struct test_data *data,
				uint16_t handle)
{
	return queue_find(data->ccc_states, ccc_state_match,
				UINT_TO_PTR(handle));
}

static struct ccc_state *get_ccc_state(struct test_data *data,
					uint16_t handle)
{
	struct ccc_state *ccc;

	ccc = find_ccc_state(data, handle);
	if (ccc)
		return ccc;

	ccc = new0(struct ccc_state, 1);
	ccc->handle = handle;
	queue_push_tail(data->ccc_states, ccc);

	return ccc;
}

static void gatt_notify_cb(struct gatt_db_attribute *attrib,
					struct gatt_db_attribute *ccc,
					const uint8_t *value, size_t len,
					struct bt_att *att, void *user_data)
{
	struct test_data *data = user_data;
	uint16_t handle = gatt_db_attribute_get_handle(attrib);

	if (tester_use_debug())
		tester_debug("handle 0x%04x len %zd", handle, len);

	if (!data->server) {
		if (tester_use_debug())
			tester_debug("data->server %p", data->server);
		return;
	}

	if (!bt_gatt_server_send_notification(data->server,
			handle, value, len, false))
		tester_debug("%s: Failed to send notification", __func__);
}

static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib,
					unsigned int id, uint16_t offset,
					uint8_t opcode, struct bt_att *att,
					void *user_data)
{
	struct test_data *data = user_data;
	struct ccc_state *ccc;
	uint16_t handle;
	uint8_t ecode = 0;
	uint16_t value = 0;

	handle = gatt_db_attribute_get_handle(attrib);

	ccc = get_ccc_state(data, handle);
	if (!ccc) {
		ecode = BT_ATT_ERROR_UNLIKELY;
		goto done;
	}

	value = cpu_to_le16(ccc->value);

done:
	gatt_db_attribute_read_result(attrib, id, ecode, (void *)&value,
							sizeof(value));
}

static void setup_complete_cb(const void *user_data)
{
	tester_setup_complete();
}

static void test_setup_server(const void *user_data)
{
	struct test_data *data = (void *)user_data;
	const struct test_config *cfg = data->cfg;
	struct bt_att *att;
	struct gatt_db *db;
	struct io *io;

	bt_mcs_test_util_reset_ccid();

	io = tester_setup_io(cfg->setup_data, cfg->setup_data_len);
	g_assert(io);

	tester_io_set_complete_func(setup_complete_cb);

	db = gatt_db_new();
	g_assert(db);

	gatt_db_ccc_register(db, gatt_ccc_read_cb, NULL, gatt_notify_cb, data);

	data->ccc_states = queue_new();

	data->mcs = bt_mcs_register(db, data->cfg->gmcs, data->cfg->mcs_cb,
									data);

	att = bt_att_new(io_get_fd(io), false);
	g_assert(att);
	bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);

	data->server = bt_gatt_server_new(db, att, 64, 0);
	g_assert(data->server);
	bt_gatt_server_set_debug(data->server, print_debug, "bt_gatt_server:",
						NULL);

	tester_io_send();

	bt_att_unref(att);
	gatt_db_unref(db);
}

static void test_complete_cb(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	if (!data->step)
		tester_test_passed();
}

static void test_server(const void *user_data)
{
	struct test_data *data = (void *)user_data;
	struct io *io;

	io = tester_setup_io(data->iov, data->iovcnt);
	g_assert(io);

	tester_io_set_complete_func(test_complete_cb);

	tester_io_send();
}

static void setup_ready_cb(bool success, uint8_t att_ecode, void *user_data)
{
	if (!success)
		tester_setup_failed();
	else
		tester_setup_complete();
}

static void test_setup(const void *user_data)
{
	struct test_data *data = (void *)user_data;
	const struct test_config *cfg = data->cfg;
	struct bt_att *att;
	struct gatt_db *db;
	struct io *io;

	io = tester_setup_io(cfg->setup_data, cfg->setup_data_len);
	g_assert(io);

	att = bt_att_new(io_get_fd(io), false);
	g_assert(att);

	bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);

	db = gatt_db_new();
	g_assert(db);

	data->client = bt_gatt_client_new(db, att, 64, 0);
	g_assert(data->client);

	bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
						NULL);

	bt_gatt_client_ready_register(data->client, setup_ready_cb, data,
						NULL);

	bt_att_unref(att);
	gatt_db_unref(db);
}

static void mcp_ccid(void *user_data, uint8_t ccid, bool gmcs)
{
	struct test_data *data = user_data;

	bt_mcp_add_listener(data->mcp, ccid, data->cfg->listener_cb, data);
}

static const struct bt_mcp_callback mcp_cb = {
	.ccid = mcp_ccid,
	.debug = mcp_debug,
};

static void test_client_start_io_cb(const void *user_data)
{
	struct test_data *data = (void *)user_data;
	struct io *io;
	struct iovec *iov = data->iov;
	size_t iovcnt = data->iovcnt;
	bool io_send = iovcnt && !iov[0].iov_base;

	if (io_send) {
		iovcnt--;
		iov++;
	}

	io = tester_setup_io(iov, iovcnt);
	g_assert(io);

	if (io_send)
		tester_io_send();
}

static void test_client(const void *user_data)
{
	struct test_data *data = (void *)user_data;
	const struct bt_mcp_callback *cb = data->cfg->mcp_cb;

	tester_io_set_complete_func(test_client_start_io_cb);

	if (!cb)
		cb = &mcp_cb;
	data->mcp = bt_mcp_attach(data->client, data->cfg->gmcs, cb, data);
	g_assert(data->mcp);
}

/* ATT: Write Request (0x12)
 *   Handle: {ccc_hnd}
 *   Value: 0x0001 (Notification enabled)
 */
#define NOTIFY_ENABLE(ccc_hnd) \
	IOV_DATA(0x12, HND(ccc_hnd), 0x01, 0x00), \
	IOV_DATA(0x13)

/* ATT: Read Request (0x0a) len 2
 *   Handle: 0x0003 Type: GMAP Role (0x2c00)
 * ATT: Read Response (0x0b) len 24
 *   Value: _value
 *   Handle: 0x0003 Type: GMAP Role (0x2c00)
 */

#define READ_CHRC(hnd, value...) \
	IOV_DATA(0x0a, HND(hnd)), \
	IOV_DATA(0x0b, value)

/* ATT: Read Blob Request (0x0c)
 * ATT: Read Blob Response (0x0d)
 */

#define READ_BLOB_CHRC(hnd, off, value...) \
	IOV_DATA(0x0c, HND(hnd), (off) & 0xff, (off) >> 8), \
	IOV_DATA(0x0d, value)

#define READ_BLOB_ERR_CHRC(hnd, off, err) \
	IOV_DATA(0x0c, HND(hnd), (off) & 0xff, (off) >> 8), \
	IOV_DATA(0x01, 0x0c, HND(hnd), err)

#define BLOB_DATA_MTU_1(v) \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v

#define BLOB_DATA_MTU_3(v) \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, \
	v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, \
	v, v, v, v, v, v, v, v, v, v, v, v, v


/* ATT: Write Command (0x52) len 2
 */

#define WRITE_NORESP_CHRC(hnd, value...) \
	IOV_DATA(0x52, HND(hnd), value), \
	IOV_NULL

/* ATT: Write Request (0x12) len 2
 * ATT: Write Response (0x13) len 1
 */

#define WRITE_CHRC(hnd, value...) \
	IOV_DATA(0x12, HND(hnd), value), \
	IOV_DATA(0x13)

/* ATT: Write Request (0x12) len 2
 * ATT: Error Response (0x01) len 1
 */

#define WRITE_ERR_CHRC(hnd, err, value...) \
	IOV_DATA(0x12, HND(hnd), value), \
	IOV_DATA(0x01, 0x12, HND(hnd), err)

/* ATT: Handle Value Notification (0x1b) len 7
 *   Handle: {hnd}
 *     Data: {value}
 */
#define NOTIFY_CHRC(hnd, value...) \
	IOV_NULL, \
	IOV_DATA(0x1b, HND(hnd), value)

#define SPLIT_INT32(value) \
	(value & 0xff), ((value >> 8) & 0xff), \
	((value >> 16) & 0xff), ((value >> 24) & 0xff)

#define MCS_MINIMAL_INIT_ALL(ops) \
	READ_CHRC(CCID, 0x01), \
	READ_CHRC(NAME, 'B', 'l', 'u', 'e', 'Z'), \
	NOTIFY_ENABLE(TRACK_CHG_CCC), \
	READ_CHRC(TRACK_TITLE, 'T', 'i', 't', 'l', 'e'), \
	READ_CHRC(TRACK_DUR, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(TRACK_POS, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(PLAY_SPEED, 0x00), \
	READ_CHRC(SEEK_SPEED, 0x00), \
	READ_CHRC(PLAY_ORDER, 0x04 /* in order repeat */), \
	READ_CHRC(PLAY_ORDER_SUPP, 0x18, 0x00  /* in order + oldest */), \
	READ_CHRC(STATE, 0x00 /* inactive */), \
	NOTIFY_ENABLE(STATE_CCC), \
	NOTIFY_ENABLE(CP_CCC), \
	READ_CHRC(CP_SUPP, SPLIT_INT32(ops))

#define GMCS_INIT_ALL(ops) \
	READ_CHRC(CCID, 0x01), \
	READ_CHRC(NAME, 'B', 'l', 'u', 'e', 'Z'), \
	NOTIFY_ENABLE(NAME_CCC), \
	NOTIFY_ENABLE(TRACK_CHG_CCC), \
	READ_CHRC(TRACK_TITLE, 'T', 'i', 't', 'l', 'e'), \
	NOTIFY_ENABLE(TRACK_TITLE_CCC), \
	READ_CHRC(TRACK_DUR, 0xff, 0xff, 0xff, 0xff), \
	NOTIFY_ENABLE(TRACK_DUR_CCC), \
	READ_CHRC(TRACK_POS, 0xff, 0xff, 0xff, 0xff), \
	NOTIFY_ENABLE(TRACK_POS_CCC), \
	READ_CHRC(PLAY_SPEED, 0x00), \
	NOTIFY_ENABLE(PLAY_SPEED_CCC), \
	READ_CHRC(SEEK_SPEED, 0x00), \
	NOTIFY_ENABLE(SEEK_SPEED_CCC), \
	READ_CHRC(PLAY_ORDER, 0x04 /* in order repeat */), \
	NOTIFY_ENABLE(PLAY_ORDER_CCC), \
	READ_CHRC(PLAY_ORDER_SUPP, 0x18, 0x00 /* in order + oldest */), \
	READ_CHRC(STATE, 0x00 /* inactive */), \
	NOTIFY_ENABLE(STATE_CCC), \
	NOTIFY_ENABLE(CP_CCC), \
	READ_CHRC(CP_SUPP, SPLIT_INT32(ops)), \
	NOTIFY_ENABLE(CP_SUPP_CCC)


/*
 * Client tests
 */

#define CGGIT_MCS_ALL \
	MCS_MINIMAL_INIT_ALL(0x001fffff)

#define CGGIT_GMCS_ALL \
	GMCS_INIT_ALL(0x001fffff)

static const struct iovec setup_data_mcs[] = {
	MCS_SETUP(0, MCS_SERVICE),
	CGGIT_MCS_ALL
};

static const struct iovec setup_data_gmcs[] = {
	GMCS_SETUP(0, GMCS_SERVICE),
	CGGIT_GMCS_ALL
};

static void cggit_player_name(void *data, const uint8_t *value, uint16_t length)
{
	if (strncmp((void *)value, "BlueZ", length) == 0) {
		tester_test_passed();
		return;
	}
	FAIL_TEST();
}

static void cggit_track_changed(void *data)
{
	tester_test_passed();
}

static void cggit_track_title(void *data, const uint8_t *value, uint16_t length)
{
	if (strncmp((void *)value, "Title", length) == 0) {
		tester_test_passed();
		return;
	}
	FAIL_TEST();
}

static void cggit_track_duration(void *data, int32_t value)
{
	if ((uint32_t)value == 0xffffffff)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_track_position(void *user_data, int32_t value)
{
	struct test_data *data = user_data;

	tester_debug("position %d", value);
	if ((uint32_t)value == 0xffffffff && data->step == 0)
		; /* ok */
	else if (value == -777 && data->step == 1)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_play_speed(void *user_data, int8_t value)
{
	struct test_data *data = user_data;

	tester_debug("play speed %d", value);

	if (value == 0 && data->step == 0)
		; /* ok */
	else if (value == 0x07 && data->step == 1)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_seek_speed(void *data, int8_t value)
{
	if (value == 0)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_play_order(void *user_data, uint8_t value)
{
	struct test_data *data = user_data;

	tester_debug("play order %u", value);

	if (value == 0x04 && data->step == 0)
		; /* ok */
	else if (value == 0x05 && data->step == 1)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_media_state(void *data, uint8_t value)
{
	if (value == 0x00)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_complete(void *user_data, unsigned int id, uint8_t res)
{
	struct test_data *data = user_data;

	if (!res || id != data->id) {
		FAIL_TEST();
		return;
	}

	data->step--;
}

#define CGGIT_CHA_BV_01_C

const struct test_config cfg_cggit_cha_bv_01_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.media_player_name = cggit_player_name,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_23_C

const struct test_config cfg_cggit_cha_bv_23_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.media_player_name = cggit_player_name,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_gmcs,
	.setup_data_len = ARRAY_SIZE(setup_data_gmcs),
	.gmcs = true,
};

#define CGGIT_CHA_BV_04_C \
	NOTIFY_CHRC(TRACK_CHG), \
	IOV_NULL

const struct test_config cfg_cggit_cha_bv_04_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_changed = cggit_track_changed,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_05_C

const struct test_config cfg_cggit_cha_bv_05_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_title = cggit_track_title,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_gmcs,
	.setup_data_len = ARRAY_SIZE(setup_data_gmcs),
	.gmcs = true,
};

#define CGGIT_CHA_BV_06_C

const struct test_config cfg_cggit_cha_bv_06_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_duration = cggit_track_duration,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_07_C \
	WRITE_CHRC(TRACK_POS, 0xf7, 0xfc, 0xff, 0xff), \
	NOTIFY_CHRC(TRACK_POS, 0xf7, 0xfc, 0xff, 0xff)

static void cggit_ready_cha_bv_07_c(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;

	data->step = 2;
	data->id = bt_mcp_set_track_position(data->mcp, ccid, -777);
}

const struct test_config cfg_cggit_cha_bv_07_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_position = cggit_track_position,
	},
	.mcp_cb = &(struct bt_mcp_callback) {
		.ccid = mcp_ccid,
		.debug = mcp_debug,
		.ready = cggit_ready_cha_bv_07_c,
		.complete = cggit_complete,
	},
	.setup_data = setup_data_gmcs,
	.setup_data_len = ARRAY_SIZE(setup_data_gmcs),
	.gmcs = true,
};

#define CGGIT_CHA_BV_08_C \
	WRITE_CHRC(PLAY_SPEED, 0x07), \
	NOTIFY_CHRC(PLAY_SPEED, 0x07)

static void cggit_ready_cha_bv_08_c(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;

	data->step = 2;
	data->id = bt_mcp_set_playback_speed(data->mcp, ccid, 7);
}

const struct test_config cfg_cggit_cha_bv_08_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.playback_speed = cggit_play_speed,
	},
	.mcp_cb = &(struct bt_mcp_callback) {
		.ccid = mcp_ccid,
		.debug = mcp_debug,
		.ready = cggit_ready_cha_bv_08_c,
		.complete = cggit_complete,
	},
	.setup_data = setup_data_gmcs,
	.setup_data_len = ARRAY_SIZE(setup_data_gmcs),
	.gmcs = true,
};

#define CGGIT_CHA_BV_09_C

const struct test_config cfg_cggit_cha_bv_09_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.seeking_speed = cggit_seek_speed,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_15_C \
	WRITE_CHRC(PLAY_ORDER, 0x05), \
	READ_CHRC(PLAY_ORDER, 0x05)	/* no notify, so bt_mcp reads */

static void cggit_ready_cha_bv_15_c(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;

	/* check not supported order */
	if (bt_mcp_set_playing_order(data->mcp, ccid, 0x06)) {
		FAIL_TEST();
		return;
	}

	data->step = 2;
	data->id = bt_mcp_set_playing_order(data->mcp, ccid, 0x05);
}

const struct test_config cfg_cggit_cha_bv_15_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.playing_order = cggit_play_order,
	},
	.mcp_cb = &(struct bt_mcp_callback) {
		.ccid = mcp_ccid,
		.debug = mcp_debug,
		.ready = cggit_ready_cha_bv_15_c,
		.complete = cggit_complete,
	},
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_16_C

static void cggit_ready_cha_bv_16_c(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;
	uint8_t order = bt_mcp_get_supported_playing_order(data->mcp, ccid);

	tester_debug("0x%x", order);

	if (order == 0x18)
		tester_test_passed();
	else
		FAIL_TEST();
}

const struct test_config cfg_cggit_cha_bv_16_c = {
	.mcp_cb = &(struct bt_mcp_callback) {
		.ready = cggit_ready_cha_bv_16_c,
		.ccid = mcp_ccid,
		.debug = mcp_debug,
	},
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_17_C

const struct test_config cfg_cggit_cha_bv_17_c = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.media_state = cggit_media_state,
	},
	.mcp_cb = &mcp_cb,
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_18_C \
	WRITE_NORESP_CHRC(CP, 0x01), \
	NOTIFY_CHRC(CP, 0x01, 0x01)

static void cggit_complete_cha_bv_18_c(void *user_data, unsigned int id,
								uint8_t result)
{
	struct test_data *data = user_data;

	tester_debug("complete %d expect %d result %u", id, data->id, result);

	if (id == data->id && result == 0x01)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void cggit_ready_cha_bv_18_c(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;

	data->id = bt_mcp_play(data->mcp, ccid);
}

const struct test_config cfg_cggit_cha_bv_18_c = {
	.mcp_cb = &(struct bt_mcp_callback) {
		.ready = cggit_ready_cha_bv_18_c,
		.complete = cggit_complete_cha_bv_18_c,
		.ccid = mcp_ccid,
		.debug = mcp_debug,
	},
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CGGIT_CHA_BV_19_C

static void cggit_ready_cha_bv_19_c(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;
	uint32_t support = bt_mcp_get_supported_commands(data->mcp, ccid);

	if (support == 0x001fffff)
		tester_test_passed();
	else
		FAIL_TEST();
}

const struct test_config cfg_cggit_cha_bv_19_c = {
	.mcp_cb = &(struct bt_mcp_callback) {
		.ready = cggit_ready_cha_bv_19_c,
		.ccid = mcp_ccid,
		.debug = mcp_debug,
	},
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define MCCP_BASIC(op, result, data...) \
	WRITE_NORESP_CHRC(CP, op, data), \
	NOTIFY_CHRC(CP, op, result)

static void mccp_basic_complete(void *user_data, unsigned int id,
								uint8_t result)
{
	struct test_data *data = user_data;

	tester_debug("complete %d expect %d result %u", id, data->id, result);

	if (id == data->id && result == 0x01)
		tester_test_passed();
	else
		FAIL_TEST();
}

static void mccp_basic_ready(void *user_data)
{
	struct test_data *data = user_data;
	uint8_t ccid = 0x01;

	switch (data->cfg->expect_cmd) {
	case 0x01:
		data->id = bt_mcp_play(data->mcp, ccid);
		break;
	case 0x02:
		data->id = bt_mcp_pause(data->mcp, ccid);
		break;
	case 0x03:
		data->id = bt_mcp_fast_rewind(data->mcp, ccid);
		break;
	case 0x04:
		data->id = bt_mcp_fast_forward(data->mcp, ccid);
		break;
	case 0x05:
		data->id = bt_mcp_stop(data->mcp, ccid);
		break;
	case 0x10:
		data->id = bt_mcp_move_relative(data->mcp, ccid, 0x42);
		break;
	case 0x20:
		data->id = bt_mcp_previous_segment(data->mcp, ccid);
		break;
	case 0x21:
		data->id = bt_mcp_next_segment(data->mcp, ccid);
		break;
	case 0x22:
		data->id = bt_mcp_first_segment(data->mcp, ccid);
		break;
	case 0x23:
		data->id = bt_mcp_last_segment(data->mcp, ccid);
		break;
	case 0x24:
		data->id = bt_mcp_goto_segment(data->mcp, ccid,
							(int32_t)0xfffffff0u);
		break;
	case 0x30:
		data->id = bt_mcp_previous_track(data->mcp, ccid);
		break;
	case 0x31:
		data->id = bt_mcp_next_track(data->mcp, ccid);
		break;
	case 0x32:
		data->id = bt_mcp_first_track(data->mcp, ccid);
		break;
	case 0x33:
		data->id = bt_mcp_last_track(data->mcp, ccid);
		break;
	case 0x34:
		data->id = bt_mcp_goto_track(data->mcp, ccid,
							(int32_t)0xfffffff1u);
		break;
	case 0x40:
		data->id = bt_mcp_previous_group(data->mcp, ccid);
		break;
	case 0x41:
		data->id = bt_mcp_next_group(data->mcp, ccid);
		break;
	case 0x42:
		data->id = bt_mcp_first_group(data->mcp, ccid);
		break;
	case 0x43:
		data->id = bt_mcp_last_group(data->mcp, ccid);
		break;
	case 0x44:
		data->id = bt_mcp_goto_group(data->mcp, ccid,
							(int32_t)0xfffffff2u);
		break;
	}
}

static const struct bt_mcp_callback mccp_basic_mcp_cb = {
	.ready = mccp_basic_ready,
	.complete = mccp_basic_complete,
	.debug = mcp_debug,
};

#define MCCP_BASIC_CFG(name_, cmd) \
	const struct test_config name_ = { \
		.expect_cmd = cmd, \
		.expect_cmd_result = 0x01, \
		.mcp_cb = &mccp_basic_mcp_cb, \
		.setup_data = setup_data_mcs, \
		.setup_data_len = ARRAY_SIZE(setup_data_mcs), \
		.gmcs = false, \
	}

#define MCCP_BV_01_C	MCCP_BASIC(0x01, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_01_c, 0x01);

#define MCCP_BV_02_C	MCCP_BASIC(0x02, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_02_c, 0x02);

#define MCCP_BV_03_C	MCCP_BASIC(0x03, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_03_c, 0x03);

#define MCCP_BV_04_C	MCCP_BASIC(0x04, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_04_c, 0x04);

#define MCCP_BV_05_C	MCCP_BASIC(0x05, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_05_c, 0x05);

#define MCCP_BV_06_C	MCCP_BASIC(0x10, 0x01, 0x42, 0x00, 0x00, 0x00)
MCCP_BASIC_CFG(cfg_mccp_bv_06_c, 0x10);

#define MCCP_BV_07_C	MCCP_BASIC(0x20, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_07_c, 0x20);

#define MCCP_BV_08_C	MCCP_BASIC(0x21, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_08_c, 0x21);

#define MCCP_BV_09_C	MCCP_BASIC(0x22, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_09_c, 0x22);

#define MCCP_BV_10_C	MCCP_BASIC(0x23, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_10_c, 0x23);

#define MCCP_BV_11_C	MCCP_BASIC(0x24, 0x01, 0xf0, 0xff, 0xff, 0xff)
MCCP_BASIC_CFG(cfg_mccp_bv_11_c, 0x24);

#define MCCP_BV_12_C	MCCP_BASIC(0x30, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_12_c, 0x30);

#define MCCP_BV_13_C	MCCP_BASIC(0x31, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_13_c, 0x31);

#define MCCP_BV_14_C	MCCP_BASIC(0x32, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_14_c, 0x32);

#define MCCP_BV_15_C	MCCP_BASIC(0x33, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_15_c, 0x33);

#define MCCP_BV_16_C	MCCP_BASIC(0x34, 0x01, 0xf1, 0xff, 0xff, 0xff)
MCCP_BASIC_CFG(cfg_mccp_bv_16_c, 0x34);

#define MCCP_BV_17_C	MCCP_BASIC(0x40, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_17_c, 0x40);

#define MCCP_BV_18_C	MCCP_BASIC(0x41, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_18_c, 0x41);

#define MCCP_BV_19_C	MCCP_BASIC(0x42, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_19_c, 0x42);

#define MCCP_BV_20_C	MCCP_BASIC(0x43, 0x01)
MCCP_BASIC_CFG(cfg_mccp_bv_20_c, 0x43);

#define MCCP_BV_21_C	MCCP_BASIC(0x44, 0x01, 0xf2, 0xff, 0xff, 0xff)
MCCP_BASIC_CFG(cfg_mccp_bv_21_c, 0x44);

static void testgroup_cl_cggit(void)
{
	/* MCP.TS Sec. 4.3 Generic GATT Integration Tests */
	define_test("MCP/CL/CGGIT/CHA/BV-01-C [Characteristic GGIT - Media "
								"Player Name]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_01_c, CGGIT_CHA_BV_01_C);
	define_test("MCP/CL/CGGIT/CHA/BV-23-C [Characteristic GGIT - Media "
							"Player Name - GMCS]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_23_c, CGGIT_CHA_BV_23_C);
	define_test("MCP/CL/CGGIT/CHA/BV-04-C [Characteristic GGIT - Track "
								"Changed]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_04_c, CGGIT_CHA_BV_04_C);
	define_test("MCP/CL/CGGIT/CHA/BV-05-C [Characteristic GGIT - Track "
								"Title]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_05_c, CGGIT_CHA_BV_05_C);
	define_test("MCP/CL/CGGIT/CHA/BV-06-C [Characteristic GGIT - Track "
								"Duration]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_06_c, CGGIT_CHA_BV_06_C);
	define_test("MCP/CL/CGGIT/CHA/BV-07-C [Characteristic GGIT - Track "
								"Position]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_07_c, CGGIT_CHA_BV_07_C);
	define_test("MCP/CL/CGGIT/CHA/BV-08-C [Characteristic GGIT - Playback "
								"Speed]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_08_c, CGGIT_CHA_BV_08_C);
	define_test("MCP/CL/CGGIT/CHA/BV-09-C [Characteristic GGIT - Seeking "
								"Speed]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_09_c, CGGIT_CHA_BV_09_C);
	define_test("MCP/CL/CGGIT/CHA/BV-15-C [Characteristic GGIT - Playing "
								"Order]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_15_c, CGGIT_CHA_BV_15_C);
	define_test("MCP/CL/CGGIT/CHA/BV-16-C [Characteristic GGIT - Playing "
							"Order Supported]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_16_c, CGGIT_CHA_BV_16_C);
	define_test("MCP/CL/CGGIT/CHA/BV-17-C [Characteristic GGIT - Media "
								"State]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_17_c, CGGIT_CHA_BV_17_C);
	define_test("MCP/CL/CGGIT/CHA/BV-18-C [Characteristic GGIT - Media "
							"Control Point]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_18_c, CGGIT_CHA_BV_18_C);
	define_test("MCP/CL/CGGIT/CHA/BV-19-C [Characteristic GGIT - Media "
						"Control Opcodes Supported]",
		test_setup, test_client,
		&cfg_cggit_cha_bv_19_c, CGGIT_CHA_BV_19_C);
}

static void testgroup_cl_mccp(void)
{
	/* MCP.TS Sec. 4.5 Service Procedure - Media Control Point */
	define_test("MCP/CL/MCCP/BV-01-C [Media Control Point - Play]",
		test_setup, test_client, &cfg_mccp_bv_01_c, MCCP_BV_01_C);
	define_test("MCP/CL/MCCP/BV-02-C [Media Control Point - Pause]",
		test_setup, test_client, &cfg_mccp_bv_02_c, MCCP_BV_02_C);
	define_test("MCP/CL/MCCP/BV-03-C [Media Control Point - Fast Rewind]",
		test_setup, test_client, &cfg_mccp_bv_03_c, MCCP_BV_03_C);
	define_test("MCP/CL/MCCP/BV-04-C [Media Control Point - Fast Forward]",
		test_setup, test_client, &cfg_mccp_bv_04_c, MCCP_BV_04_C);
	define_test("MCP/CL/MCCP/BV-05-C [Media Control Point - Stop]",
		test_setup, test_client, &cfg_mccp_bv_05_c, MCCP_BV_05_C);
	define_test("MCP/CL/MCCP/BV-06-C [Media Control Point - Move Relative]",
		test_setup, test_client, &cfg_mccp_bv_06_c, MCCP_BV_06_C);
	define_test("MCP/CL/MCCP/BV-07-C [Media Control Point - Previous "
								"Segment]",
		test_setup, test_client, &cfg_mccp_bv_07_c, MCCP_BV_07_C);
	define_test("MCP/CL/MCCP/BV-08-C [Media Control Point - Next Segment]",
		test_setup, test_client, &cfg_mccp_bv_08_c, MCCP_BV_08_C);
	define_test("MCP/CL/MCCP/BV-09-C [Media Control Point - First Segment]",
		test_setup, test_client, &cfg_mccp_bv_09_c, MCCP_BV_09_C);
	define_test("MCP/CL/MCCP/BV-10-C [Media Control Point - Last Segment]",
		test_setup, test_client, &cfg_mccp_bv_10_c, MCCP_BV_10_C);
	define_test("MCP/CL/MCCP/BV-11-C [Media Control Point - Goto Segment]",
		test_setup, test_client, &cfg_mccp_bv_11_c, MCCP_BV_11_C);
	define_test("MCP/CL/MCCP/BV-12-C [Media Control Point - Previous "
								"Track]",
		test_setup, test_client, &cfg_mccp_bv_12_c, MCCP_BV_12_C);
	define_test("MCP/CL/MCCP/BV-13-C [Media Control Point - Next Track]",
		test_setup, test_client, &cfg_mccp_bv_13_c, MCCP_BV_13_C);
	define_test("MCP/CL/MCCP/BV-14-C [Media Control Point - First Track]",
		test_setup, test_client, &cfg_mccp_bv_14_c, MCCP_BV_14_C);
	define_test("MCP/CL/MCCP/BV-15-C [Media Control Point - Last Track]",
		test_setup, test_client, &cfg_mccp_bv_15_c, MCCP_BV_15_C);
	define_test("MCP/CL/MCCP/BV-16-C [Media Control Point - Goto Track]",
		test_setup, test_client, &cfg_mccp_bv_16_c, MCCP_BV_16_C);
	define_test("MCP/CL/MCCP/BV-17-C [Media Control Point - Previous "
								"Group]",
		test_setup, test_client, &cfg_mccp_bv_17_c, MCCP_BV_17_C);
	define_test("MCP/CL/MCCP/BV-18-C [Media Control Point - Next Group]",
		test_setup, test_client, &cfg_mccp_bv_18_c, MCCP_BV_18_C);
	define_test("MCP/CL/MCCP/BV-19-C [Media Control Point - First Group]",
		test_setup, test_client, &cfg_mccp_bv_19_c, MCCP_BV_19_C);
	define_test("MCP/CL/MCCP/BV-20-C [Media Control Point - Last Group]",
		test_setup, test_client, &cfg_mccp_bv_20_c, MCCP_BV_20_C);
	define_test("MCP/CL/MCCP/BV-21-C [Media Control Point - Goto Group]",
		test_setup, test_client, &cfg_mccp_bv_21_c, MCCP_BV_21_C);
}

#define CL_BLUEZ_1_REREAD \
	NOTIFY_CHRC(TRACK_CHG), \
	READ_CHRC(TRACK_TITLE, 'N', 'e', 'w'), \
	READ_CHRC(TRACK_DUR, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(TRACK_POS, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(PLAY_SPEED, 0x00), \
	READ_CHRC(SEEK_SPEED, 0x00), \
	READ_CHRC(PLAY_ORDER, 0x04), \
	READ_CHRC(PLAY_ORDER_SUPP, 0x18, 0x00), \
	READ_CHRC(CP_SUPP, SPLIT_INT32(0x01))

static void cl_reread_complete_cb(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	if (data->step == 2)
		tester_test_passed();
}

static void cl_reread_track_title(void *user_data, const uint8_t *value,
								uint16_t length)
{
	struct test_data *data = user_data;

	if (strncmp((void *)value, "Title", length) == 0 && data->step == 0) {
		data->step++;
	} else if (strncmp((void *)value, "New", length) == 0 &&
							data->step == 1) {
		data->step++;
		tester_io_set_complete_func(cl_reread_complete_cb);
	} else {
		FAIL_TEST();
	}
}

const struct test_config cfg_cl_bluez_1_reread = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_title = cl_reread_track_title,
	},
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

#define CL_BLUEZ_2_REREAD \
	NOTIFY_CHRC(TRACK_CHG), \
	READ_CHRC(TRACK_TITLE, BLOB_DATA_MTU_1('y')), \
	/* Value change during long read */ \
	READ_BLOB_ERR_CHRC(TRACK_TITLE, 0x40 - 1, 0x80), \
	READ_CHRC(TRACK_DUR, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(TRACK_POS, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(PLAY_SPEED, 0x00), \
	READ_CHRC(SEEK_SPEED, 0x00), \
	READ_CHRC(PLAY_ORDER, 0x04), \
	READ_CHRC(PLAY_ORDER_SUPP, 0x18, 0x00), \
	READ_CHRC(CP_SUPP, SPLIT_INT32(0x01)), \
	/* Track change notification -> reread */ \
	NOTIFY_CHRC(TRACK_CHG), \
	READ_CHRC(TRACK_TITLE, BLOB_DATA_MTU_1('x')), \
	READ_BLOB_CHRC(TRACK_TITLE, 0x40 - 1, 'x', 'x', 'x'), \
	READ_CHRC(TRACK_DUR, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(TRACK_POS, 0xff, 0xff, 0xff, 0xff), \
	READ_CHRC(PLAY_SPEED, 0x00), \
	READ_CHRC(SEEK_SPEED, 0x00), \
	READ_CHRC(PLAY_ORDER, 0x04), \
	READ_CHRC(PLAY_ORDER_SUPP, 0x18, 0x00), \
	READ_CHRC(CP_SUPP, SPLIT_INT32(0x01)), \
	IOV_NULL

#define CL_BLUEZ_3_REREAD \
	NOTIFY_CHRC(TRACK_TITLE, BLOB_DATA_MTU_3('y')), \
	READ_CHRC(TRACK_TITLE, BLOB_DATA_MTU_1('y')), \
	/* Changed during read -> notification -> reread */ \
	READ_BLOB_ERR_CHRC(TRACK_TITLE, 0x40 - 1, 0x80), \
	NOTIFY_CHRC(TRACK_TITLE, BLOB_DATA_MTU_3('x')), \
	READ_CHRC(TRACK_TITLE, BLOB_DATA_MTU_1('x')), \
	READ_BLOB_CHRC(TRACK_TITLE, 0x40 - 1, 'x', 'x', 'x'), \
	IOV_NULL

static void cl_reread_long_idle(void *user_data)
{
	struct test_data *data = user_data;

	if (data->step == 2)
		tester_test_passed();
}

static void cl_reread_long_track_title(void *user_data, const uint8_t *value,
								uint16_t length)
{
	struct test_data *data = user_data;
	const char new_value[0x42] = { [0 ... 0x41] = 'x' };

	if (strncmp((void *)value, "Title", length) == 0 && data->step == 0) {
		data->step++;
	} else if (data->step == 1 && length == 0x42 &&
				!memcmp((void *)value, new_value, length)) {
		data->step++;

		/* Wait for all reads, to make sure there are no extra */
		bt_gatt_client_idle_register(
			bt_mcp_test_util_get_client(data->mcp),
			cl_reread_long_idle, data, NULL);
	} else {
		FAIL_TEST();
	}
}

const struct test_config cfg_cl_bluez_2_reread = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_title = cl_reread_long_track_title,
	},
	.setup_data = setup_data_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_mcs),
	.gmcs = false,
};

const struct test_config cfg_cl_bluez_3_reread = {
	.listener_cb = &(struct bt_mcp_listener_callback) {
		.track_title = cl_reread_long_track_title,
	},
	.setup_data = setup_data_gmcs,
	.setup_data_len = ARRAY_SIZE(setup_data_gmcs),
	.gmcs = true,
};


static void testgroup_cl_extra(void)
{
	define_test("MCP/CL/BLUEZ-1 [Reread On Track Change, No Notify]",
		test_setup, test_client,
		&cfg_cl_bluez_1_reread, CL_BLUEZ_1_REREAD);
	define_test("MCP/CL/BLUEZ-2 [Long Value Reread, Track Changed]",
		test_setup, test_client,
		&cfg_cl_bluez_2_reread, CL_BLUEZ_2_REREAD);
	define_test("MCP/CL/BLUEZ-3 [Long Value Reread, Notify]",
		test_setup, test_client,
		&cfg_cl_bluez_3_reread, CL_BLUEZ_3_REREAD);
}

/*
 * Server tests
 */

static const struct iovec setup_data_server_mcs[] = {
	GMCS_SETUP(0, MCS_SERVICE),
};

static const struct iovec setup_data_server_gmcs[] = {
	GMCS_SETUP(0, GMCS_SERVICE),
};

static void sggit_player_name(void *data, struct iovec *buf, size_t size)
{
	util_iov_push_mem(buf, 5, "BlueZ");
}

static void sggit_track_title(void *data, struct iovec *buf, size_t size)
{
	util_iov_push_mem(buf, 5, "Title");
}

static int32_t sggit_track_duration(void *data)
{
	return 0x00004321;
}

static int32_t sggit_track_position(void *data)
{
	return 0x00001234;
}

static int8_t sggit_playback_speed(void *data)
{
	return 0x03;
}

static int8_t sggit_seeking_speed(void *data)
{
	return 0x05;
}

static uint8_t sggit_playing_order(void *data)
{
	return 0x04;
}

static uint16_t sggit_playing_order_supported(void *data)
{
	return 0x18;
}

static uint32_t sggit_media_cp_op_supported(void *data)
{
	return 0x11;
}

static bool sggit_set_track_position(void *user_data, int32_t value)
{
	struct test_data *data = user_data;

	if (value == 0x5678)
		data->step--;
	return false;
}

static bool sggit_set_playback_speed(void *user_data, int8_t value)
{
	struct test_data *data = user_data;

	if (value == 0x42)
		data->step--;
	return true;
}

static bool sggit_set_playing_order(void *user_data, uint8_t value)
{
	struct test_data *data = user_data;

	if (value == 0x05)
		data->step--;
	return false;
}

static bool sggit_play(void *user_data)
{
	struct test_data *data = user_data;

	data->step--;
	return false;
}

const struct bt_mcs_callback sggit_cha_mcs = {
	.media_player_name = sggit_player_name,
	.track_title = sggit_track_title,
	.track_duration = sggit_track_duration,
	.track_position = sggit_track_position,
	.playback_speed = sggit_playback_speed,
	.seeking_speed = sggit_seeking_speed,
	.playing_order = sggit_playing_order,
	.playing_order_supported = sggit_playing_order_supported,
	.media_cp_op_supported = sggit_media_cp_op_supported,
	.set_track_position = sggit_set_track_position,
	.set_playback_speed = sggit_set_playback_speed,
	.set_playing_order = sggit_set_playing_order,
	.play = sggit_play,
	.debug = mcs_debug,
};

const struct test_config cfg_sggit_mcs = {
	.mcs_cb = &sggit_cha_mcs,
	.setup_data = setup_data_server_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_server_mcs),
	.gmcs = false,
};

const struct test_config cfg_sggit_gmcs = {
	.mcs_cb = &sggit_cha_mcs,
	.setup_data = setup_data_server_gmcs,
	.setup_data_len = ARRAY_SIZE(setup_data_server_gmcs),
	.gmcs = true,
};

#define SGGIT_CHA_BV_01_C \
	READ_CHRC(NAME, 'B', 'l', 'u', 'e', 'Z')

#define SGGIT_CHA_BV_04_C \
	NOTIFY_CHRC(TRACK_CHG)

static void test_sggit_cha_bv_04_c(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	test_server(data);
	bt_mcs_changed(data->mcs, MCS_TRACK_CHANGED_CHRC_UUID);
}

#define SGGIT_CHA_BV_05_C \
	READ_CHRC(TRACK_TITLE, 'T', 'i', 't', 'l', 'e')

#define SGGIT_CHA_BV_06_C \
	READ_CHRC(TRACK_DUR, 0x21, 0x43, 0x00, 0x00)

#define SGGIT_CHA_BV_07_C \
	READ_CHRC(TRACK_POS, 0x34, 0x12, 0x00, 0x00), \
	WRITE_ERR_CHRC(TRACK_POS, 0x13, 0x78, 0x56, 0x00, 0x00)

static void test_sggit_step(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	data->step++;
	test_server(data);
}

#define SGGIT_CHA_BV_08_C \
	READ_CHRC(PLAY_SPEED, 0x03), \
	WRITE_CHRC(PLAY_SPEED, 0x42)

#define SGGIT_CHA_BV_09_C \
	READ_CHRC(SEEK_SPEED, 0x05)

#define SGGIT_CHA_BV_15_C \
	READ_CHRC(PLAY_ORDER, 0x04), \
	WRITE_ERR_CHRC(PLAY_ORDER, 0x13, 0x05)

#define SGGIT_CHA_BV_16_C \
	READ_CHRC(PLAY_ORDER_SUPP, 0x18, 0x00)

#define SGGIT_CHA_BV_17_C \
	READ_CHRC(STATE, 0x00)

#define SGGIT_CHA_BV_18_C \
	WRITE_CHRC(CP, 0x01), \
	NOTIFY_CHRC(CP, 0x01, 0x03 /* inactive */), \
	WRITE_CHRC(CP, 0x10), \
	NOTIFY_CHRC(CP, 0x10, 0x02 /* not supp */)

#define SGGIT_CHA_BV_19_C \
	READ_CHRC(CP_SUPP, 0x01, 0x00, 0x00, 0x00)

#define SGGIT_CHA_BV_22_C \
	READ_CHRC(CCID, 0x00)

static void testgroup_sr_sggit(void)
{
	/* MCS.TS Sec 4.3 Generic GATT Integrated Tests (MCS) */
	define_test("MCS/SR/SGGIT/CHA/BV-01-C [Characteristic GGIT - Media "
							"Player Name, MCS]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_01_C);
	define_test("MCS/SR/SGGIT/CHA/BV-04-C [Characteristic GGIT - Track "
								"Changed]",
		test_setup_server, test_sggit_cha_bv_04_c,
		&cfg_sggit_mcs, SGGIT_CHA_BV_04_C);
	define_test("MCS/SR/SGGIT/CHA/BV-05-C [Characteristic GGIT - Track "
								"Title]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_05_C);
	define_test("MCS/SR/SGGIT/CHA/BV-06-C [Characteristic GGIT - Track "
								"Duration]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_06_C);
	define_test("MCS/SR/SGGIT/CHA/BV-07-C [Characteristic GGIT - Track "
								"Position]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_mcs, SGGIT_CHA_BV_07_C);
	define_test("MCS/SR/SGGIT/CHA/BV-08-C [Characteristic GGIT - Playback "
								"Speed]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_mcs, SGGIT_CHA_BV_08_C);
	define_test("MCS/SR/SGGIT/CHA/BV-09-C [Characteristic GGIT - Seeking "
								"Speed]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_09_C);
	define_test("MCS/SR/SGGIT/CHA/BV-15-C [Characteristic GGIT - Playing "
								"Order]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_mcs, SGGIT_CHA_BV_15_C);
	define_test("MCS/SR/SGGIT/CHA/BV-16-C [Characteristic GGIT - Playing "
							"Order Supported]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_16_C);
	define_test("MCS/SR/SGGIT/CHA/BV-17-C [Characteristic GGIT - Media "
								"State]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_17_C);
	define_test("MCS/SR/SGGIT/CHA/BV-18-C [Characteristic GGIT - Media "
							"Control Point]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_mcs, SGGIT_CHA_BV_18_C);
	define_test("MCS/SR/SGGIT/CHA/BV-19-C [Characteristic GGIT - Media "
					"Control Point Opcodes Supported]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_19_C);
	define_test("MCS/SR/SGGIT/CHA/BV-22-C [Characteristic GGIT - Content "
								"Control ID]",
		test_setup_server, test_server,
		&cfg_sggit_mcs, SGGIT_CHA_BV_22_C);

	/* MCS.TS Sec 4.3 Generic GATT Integrated Tests (GMCS) */
	define_test("GMCS/SR/SGGIT/CHA/BV-01-C [Characteristic GGIT - Media "
							"Player Name, GMCS]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_01_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-04-C [Characteristic GGIT - Track "
								"Changed]",
		test_setup_server, test_sggit_cha_bv_04_c,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_04_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-05-C [Characteristic GGIT - Track "
								"Title]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_05_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-06-C [Characteristic GGIT - Track "
								"Duration]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_06_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-07-C [Characteristic GGIT - Track "
								"Position]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_07_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-08-C [Characteristic GGIT - Playback "
								"Speed]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_08_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-09-C [Characteristic GGIT - Seeking "
								"Speed]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_09_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-15-C [Characteristic GGIT - Playing "
								"Order]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_15_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-16-C [Characteristic GGIT - Playing "
							"Order Supported]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_16_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-17-C [Characteristic GGIT - Media "
								"State]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_17_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-18-C [Characteristic GGIT - Media "
							"Control Point]",
		test_setup_server, test_sggit_step,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_18_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-19-C [Characteristic GGIT - Media "
					"Control Point Opcodes Supported]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_19_C);
	define_test("GMCS/SR/SGGIT/CHA/BV-22-C [Characteristic GGIT - Content "
								"Control ID]",
		test_setup_server, test_server,
		&cfg_sggit_gmcs, SGGIT_CHA_BV_22_C);
}

static uint32_t sr_mcp_media_cp_op_supported(void *data)
{
	return 0x001fffff;  /* everything supported */
}

static bool sr_mcp_op_success(void *user_data)
{
	struct test_data *data = user_data;

	tester_debug("Command OK");
	data->step--;
	return true;
}

static bool sr_mcp_op_success_inactive(void *user_data)
{
	struct test_data *data = user_data;

	tester_debug("Command OK");
	data->step--;
	return bt_mcs_get_media_state(data->mcs) != BT_MCS_STATE_INACTIVE;
}

static int32_t sr_mcp_track_position(void *user_data)
{
	struct test_data *data = user_data;

	return data->id;
}

static bool sr_mcp_set_track_position(void *user_data, int32_t value)
{
	struct test_data *data = user_data;

	data->id = value;
	return true;
}

static bool sr_mcp_move_relative(void *user_data, int32_t offset)
{
	struct test_data *data = user_data;
	int pos = data->id;

	tester_debug("Move Relative %d (pos %d)", (int)offset, pos);

	if (!data->step)
		FAIL_TEST();
	data->step--;

	if (bt_mcs_get_media_state(data->mcs) == BT_MCS_STATE_INACTIVE)
		return false;

	pos += offset;
	if (pos < 0)
		pos = 0;
	if (pos > 71)
		pos = 71;

	data->id = pos;

	bt_mcs_changed(data->mcs, MCS_TRACK_POSITION_CHRC_UUID);
	return true;
}

static int32_t sr_mcp_track_duration(void *user_data)
{
	return 71;
}

const struct bt_mcs_callback sr_mcp_mcs = {
	.media_cp_op_supported = sr_mcp_media_cp_op_supported,
	.play = sr_mcp_op_success,
	.pause = sr_mcp_op_success,
	.fast_rewind = sr_mcp_op_success,
	.fast_forward = sr_mcp_op_success,
	.stop = sr_mcp_op_success_inactive,
	.track_position = sr_mcp_track_position,
	.set_track_position = sr_mcp_set_track_position,
	.track_duration = sr_mcp_track_duration,
	.move_relative = sr_mcp_move_relative,
	.debug = mcs_debug,
};

#define MCS_SR_MCP_CFG(name, initial) \
	const struct test_config cfg_mcs_sr_mcp_ ## name = { \
		.mcs_cb = &sr_mcp_mcs, \
		.setup_data = setup_data_server_mcs, \
		.setup_data_len = ARRAY_SIZE(setup_data_server_mcs), \
		.gmcs = false, \
		.state = initial, \
	}

#define SR_MCP_CMD(cmd, initial, end_state) \
	NOTIFY_CHRC(STATE, initial), \
	WRITE_CHRC(CP, cmd), \
	NOTIFY_CHRC(STATE, end_state), \
	NOTIFY_CHRC(CP, cmd, 0x01)

#define SR_MCP_CMD_INACTIVE(cmd, end_state) \
	WRITE_CHRC(CP, cmd), \
	NOTIFY_CHRC(STATE, end_state), \
	NOTIFY_CHRC(CP, cmd, 0x01)

MCS_SR_MCP_CFG(bv_01_c, BT_MCS_STATE_PAUSED);
#define MCS_SR_MCP_BV_01_C \
	SR_MCP_CMD(BT_MCS_CMD_PLAY, BT_MCS_STATE_PAUSED, BT_MCS_STATE_PLAYING)

MCS_SR_MCP_CFG(bv_02_c, BT_MCS_STATE_SEEKING);
#define MCS_SR_MCP_BV_02_C \
	SR_MCP_CMD(BT_MCS_CMD_PLAY, BT_MCS_STATE_SEEKING, BT_MCS_STATE_PLAYING)

MCS_SR_MCP_CFG(bv_70_c, BT_MCS_STATE_INACTIVE);
#define MCS_SR_MCP_BV_70_C \
	SR_MCP_CMD_INACTIVE(BT_MCS_CMD_PLAY, BT_MCS_STATE_PLAYING)

MCS_SR_MCP_CFG(bv_03_c, BT_MCS_STATE_PLAYING);
#define MCS_SR_MCP_BV_03_C \
	SR_MCP_CMD(BT_MCS_CMD_PAUSE, BT_MCS_STATE_PLAYING, BT_MCS_STATE_PAUSED)

MCS_SR_MCP_CFG(bv_04_c, BT_MCS_STATE_SEEKING);
#define MCS_SR_MCP_BV_04_C \
	SR_MCP_CMD(BT_MCS_CMD_PAUSE, BT_MCS_STATE_SEEKING, BT_MCS_STATE_PAUSED)

MCS_SR_MCP_CFG(bv_71_c, BT_MCS_STATE_INACTIVE);
#define MCS_SR_MCP_BV_71_C \
	SR_MCP_CMD_INACTIVE(BT_MCS_CMD_PAUSE, BT_MCS_STATE_PAUSED)

#define SR_MCP_STOP(initial) \
	NOTIFY_CHRC(STATE, initial), \
	WRITE_CHRC(CP, BT_MCS_CMD_STOP), \
	NOTIFY_CHRC(STATE, BT_MCS_STATE_PAUSED), \
	NOTIFY_CHRC(TRACK_POS, 0x00, 0x00, 0x00, 0x00), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_STOP, 0x01)

#define SR_MCP_STOP_PAUSED \
	NOTIFY_CHRC(STATE, BT_MCS_STATE_PAUSED), \
	WRITE_CHRC(CP, BT_MCS_CMD_STOP), \
	NOTIFY_CHRC(TRACK_POS, 0x00, 0x00, 0x00, 0x00), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_STOP, 0x01)

#define SR_MCP_STOP_INACTIVE \
	WRITE_CHRC(CP, BT_MCS_CMD_STOP), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_STOP, 0x03 /* inactive */)

MCS_SR_MCP_CFG(bv_09_c, BT_MCS_STATE_PLAYING);
#define MCS_SR_MCP_BV_09_C SR_MCP_STOP(BT_MCS_STATE_PLAYING)

MCS_SR_MCP_CFG(bv_10_c, BT_MCS_STATE_PAUSED);
#define MCS_SR_MCP_BV_10_C SR_MCP_STOP_PAUSED

MCS_SR_MCP_CFG(bv_11_c, BT_MCS_STATE_SEEKING);
#define MCS_SR_MCP_BV_11_C SR_MCP_STOP(BT_MCS_STATE_SEEKING)

MCS_SR_MCP_CFG(bv_74_c, BT_MCS_STATE_INACTIVE);
#define MCS_SR_MCP_BV_74_C SR_MCP_STOP_INACTIVE

#define SR_MCP_MOVE_RELATIVE(initial) \
	NOTIFY_CHRC(STATE, initial), \
	READ_CHRC(TRACK_DUR, 0x47, 0x00, 0x00, 0x00), \
	WRITE_NORESP_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0xaf, 0xff, 0xff, \
									0xff), \
	NOTIFY_CHRC(TRACK_POS, 0x00, 0x00, 0x00, 0x00), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0x01), \
	WRITE_NORESP_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0x17, 0x00, 0x00, \
									0x00), \
	NOTIFY_CHRC(TRACK_POS, 0x17, 0x00, 0x00, 0x00), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0x01), \
	WRITE_NORESP_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0x30, 0x00, 0x00, \
									0x00), \
	NOTIFY_CHRC(TRACK_POS, 0x47, 0x00, 0x00, 0x00), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0x01)

#define SR_MCP_MOVE_RELATIVE_INACTIVE \
	READ_CHRC(TRACK_DUR, 0x47, 0x00, 0x00, 0x00), \
	WRITE_NORESP_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0xaf, 0xff, 0xff, \
									0xff), \
	NOTIFY_CHRC(CP, BT_MCS_CMD_MOVE_RELATIVE, 0x03)

MCS_SR_MCP_CFG(bv_12_c, BT_MCS_STATE_PLAYING);
#define MCS_SR_MCP_BV_12_C SR_MCP_MOVE_RELATIVE(BT_MCS_STATE_PLAYING)

MCS_SR_MCP_CFG(bv_13_c, BT_MCS_STATE_PAUSED);
#define MCS_SR_MCP_BV_13_C SR_MCP_MOVE_RELATIVE(BT_MCS_STATE_PAUSED)

MCS_SR_MCP_CFG(bv_14_c, BT_MCS_STATE_SEEKING);
#define MCS_SR_MCP_BV_14_C SR_MCP_MOVE_RELATIVE(BT_MCS_STATE_SEEKING)

MCS_SR_MCP_CFG(bv_75_c, BT_MCS_STATE_INACTIVE);
#define MCS_SR_MCP_BV_75_C SR_MCP_MOVE_RELATIVE_INACTIVE

static void test_sr_mcp(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	data->id = 13;

	bt_mcs_set_media_state(data->mcs, data->cfg->state);
	data->step++;
	test_server(data);
}

static void test_sr_mcp_move_relative(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	data->id = 13;

	bt_mcs_set_media_state(data->mcs, data->cfg->state);
	data->step++;
	if (data->cfg->state != BT_MCS_STATE_INACTIVE)
		data->step += 2;
	test_server(data);
}

static void testgroup_sr_mcp(void)
{
	/* Only the MCS tests. No point in GMCS as only svc uuid changes */

	/* MCS.TS Sec 4.4.1 Play and Pause */
	define_test("MCS/SR/MCP/BV-01-C [Play from Paused]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_01_c, MCS_SR_MCP_BV_01_C);
	define_test("MCS/SR/MCP/BV-02-C [Play from Seeking]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_02_c, MCS_SR_MCP_BV_02_C);
	define_test("MCS/SR/MCP/BV-70-C [Play from Inactive]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_70_c, MCS_SR_MCP_BV_70_C);
	define_test("MCS/SR/MCP/BV-03-C [Pause from Playing]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_03_c, MCS_SR_MCP_BV_03_C);
	define_test("MCS/SR/MCP/BV-04-C [Pause from Seeking]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_04_c, MCS_SR_MCP_BV_04_C);
	define_test("MCS/SR/MCP/BV-71-C [Pause from Inactive]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_71_c, MCS_SR_MCP_BV_71_C);

	/* MCS.TS Sec 4.4.3 Stop */
	define_test("MCS/SR/MCP/BV-09-C [Stop from Playing]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_09_c, MCS_SR_MCP_BV_09_C);
	define_test("MCS/SR/MCP/BV-10-C [Stop from Paused]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_10_c, MCS_SR_MCP_BV_10_C);
	define_test("MCS/SR/MCP/BV-11-C [Stop from Seeking]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_11_c, MCS_SR_MCP_BV_11_C);
	define_test("MCS/SR/MCP/BV-74-C [Stop from Inactive]",
		test_setup_server, test_sr_mcp,
		&cfg_mcs_sr_mcp_bv_74_c, MCS_SR_MCP_BV_74_C);

	/* MCS.TS Sec 4.4.4 Move Relative */
	define_test("MCS/SR/MCP/BV-12-C [Move Relative from Playing]",
		test_setup_server, test_sr_mcp_move_relative,
		&cfg_mcs_sr_mcp_bv_12_c, MCS_SR_MCP_BV_12_C);
	define_test("MCS/SR/MCP/BV-13-C [Move Relative from Paused]",
		test_setup_server, test_sr_mcp_move_relative,
		&cfg_mcs_sr_mcp_bv_13_c, MCS_SR_MCP_BV_13_C);
	define_test("MCS/SR/MCP/BV-14-C [Move Relative from Seeking]",
		test_setup_server, test_sr_mcp_move_relative,
		&cfg_mcs_sr_mcp_bv_14_c, MCS_SR_MCP_BV_14_C);
	define_test("MCS/SR/MCP/BV-75-C [Move Relative from Inactive]",
		test_setup_server, test_sr_mcp_move_relative,
		&cfg_mcs_sr_mcp_bv_75_c, MCS_SR_MCP_BV_75_C);

	/* TODO: other state transition tests. They largely test the profile
	 * upper layer, so do not add much here.
	 */
}


static void sr_spn_value(struct test_data *data, struct iovec *buf, size_t size,
								uint16_t uuid)
{
	/* Longer than MTU */
	memset(buf->iov_base, 'x' + data->step, 0x42);
	buf->iov_len = 0x42;

	/* Long value changed during read */
	if (data->step == 1) {
		data->step--;
		bt_mcs_changed(data->mcs, uuid);
	}
}

static void sr_spn_media_player_name(void *data, struct iovec *buf, size_t size)
{
	sr_spn_value(data, buf, size, MCS_MEDIA_PLAYER_NAME_CHRC_UUID);
}

static void sr_spn_track_title(void *data, struct iovec *buf, size_t size)
{
	sr_spn_value(data, buf, size, MCS_TRACK_TITLE_CHRC_UUID);
}

const struct test_config cfg_mcs_sr_spn = {
	.mcs_cb = &(struct bt_mcs_callback) {
		.media_player_name = sr_spn_media_player_name,
		.track_title = sr_spn_track_title,
		.debug = mcs_debug,
	},
	.setup_data = setup_data_server_mcs,
	.setup_data_len = ARRAY_SIZE(setup_data_server_mcs),
	.gmcs = false,
};

#define MCS_SR_SPN_PROCEDURE(chrc) \
	READ_CHRC(chrc, BLOB_DATA_MTU_1('y')), \
	NOTIFY_CHRC(chrc, BLOB_DATA_MTU_3('x')), \
	READ_BLOB_ERR_CHRC(chrc, 0x40 - 1, 0x80), \
	READ_CHRC(chrc, BLOB_DATA_MTU_1('x')), \
	READ_BLOB_CHRC(chrc, 0x40 - 1, 'x', 'x', 'x')

#define MCS_SR_SPN_BV_01_C	MCS_SR_SPN_PROCEDURE(NAME)
#define MCS_SR_SPN_BV_02_C	MCS_SR_SPN_PROCEDURE(TRACK_TITLE)

static void test_sr_spn(const void *user_data)
{
	struct test_data *data = (void *)user_data;

	data->step = 1;
	test_server(data);
}

static void testgroup_sr_spn(void)
{
	/* Only the MCS tests. No point in GMCS as only svc uuid changes */

	/* MCS.TS Sec 4.5 Update Characteristic - Oversized values */
	define_test("MCS/SR/SPN/BV-01-C [Update Media Player Name - Oversized "
								"Value]",
		test_setup_server, test_sr_spn,
		&cfg_mcs_sr_spn, MCS_SR_SPN_BV_01_C);
	define_test("MCS/SR/SPN/BV-02-C [Update Track Title - Oversized Value]",
		test_setup_server, test_sr_spn,
		&cfg_mcs_sr_spn, MCS_SR_SPN_BV_02_C);
}

int main(int argc, char *argv[])
{
	tester_init(&argc, &argv);
	testgroup_cl_cggit();
	testgroup_cl_mccp();
	testgroup_cl_extra();
	testgroup_sr_sggit();
	testgroup_sr_mcp();
	testgroup_sr_spn();

	return tester_run();
}
