blob: 6331c6c997aedea02b894af131a0b46741112d66 [file]
// 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();
}