This adds 3 unit tests for BASS server, to simulate the Generic GATT Integrated Test suite for BASS. Test Summary ------------ BASS/SR/SGGIT/SER/BV-01-C Passed BASS/SR/SGGIT/CHA/BV-01-C Passed BASS/SR/SGGIT/CHA/BV-02-C Passed Total: 3, Passed: 3 (100.0%), Failed: 0, Not Run: 0 --- Makefile.am | 6 + unit/test-bass.c | 397 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 403 insertions(+) create mode 100644 unit/test-bass.c diff --git a/Makefile.am b/Makefile.am index 48f0cefa7..2bd547564 100644 --- a/Makefile.am +++ b/Makefile.am @@ -578,6 +578,12 @@ unit_test_bap_SOURCES = unit/test-bap.c unit_test_bap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) +unit_tests += unit/test-bass + +unit_test_bass_SOURCES = unit/test-bass.c +unit_test_bass_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) + if MIDI unit_tests += unit/test-midi unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST diff --git a/unit/test-bass.c b/unit/test-bass.c new file mode 100644 index 000000000..8937c9478 --- /dev/null +++ b/unit/test-bass.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright 2023 NXP + * + * + */ + +#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 "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/util.h" +#include "src/shared/io.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/bass.h" + +struct test_data { + struct gatt_db *db; + struct bt_bass *bass; + struct bt_gatt_server *server; + struct queue *ccc_states; + size_t iovcnt; + struct iovec *iov; +}; + +struct ccc_state { + uint16_t handle; + uint16_t value; +}; + +/* ATT: Exchange MTU Request (0x02) len 2 + * Client RX MTU: 64 + * ATT: Exchange MTU Response (0x03) len 2 + * Server RX MTU: 64 + */ +#define EXCHANGE_MTU \ + IOV_DATA(0x02, 0x40, 0x00), \ + IOV_DATA(0x03, 0x40, 0x00) + +/* ATT: Find By Type Value Request (0x06) len 8 + * Handle range: 0x0001-0xffff + * Attribute Type(UUID): Primary Service (0x2800) + * Value to find: Broadcast Audio Scan Service (0x184f) + * ATT: Find By Type Value Response (0x07) len 4 + * Handle range: 0x0001-0x0009 + * ATT: Find By Type Value Request (0x06) len 8 + * Handle range: 0x000a-0xffff + * Attribute Type(UUID): Primary Service (0x2800) + * Value to find: Broadcast Audio Scan Service (0x184f) + * ATT: Error Response (0x01) len 4 + * Find By Type Value Request (0x06) + * Handle: 0x000a + * Error: Attribute Not Found (0x0a) + */ +#define BASS_FIND_BY_TYPE_VALUE \ + IOV_DATA(0x06, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28, 0x4f, 0x18), \ + IOV_DATA(0x07, 0x01, 0x00, 0x09, 0x00), \ + IOV_DATA(0x06, 0x0a, 0x00, 0xff, 0xff, 0x00, 0x28, 0x4f, 0x18), \ + IOV_DATA(0x01, 0x06, 0x0a, 0x00, 0x0a) + +/* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0x0009 + * Attribute type: Characteristic (0x2803) + * ATT: Read By Type Response (0x09) len 22 + * Attribute data length: 7 + * Attribute data list: 3 entries + * Handle: 0x0002 + * Value: 120300c82b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0003 + * Value UUID: Broadcast Receive State (0x2bc8) + * Handle: 0x0005 + * Value: 120600c82b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0006 + * Value UUID: Broadcast Receive State (0x2bc8) + * Handle: 0x0008 + * Value: 0c0900c72b + * Properties: 0x0c + * Write (0x08) + * Write Without Response (0x04) + * Value Handle: 0x0009 + * Value UUID: Broadcast Audio Scan Control Point (0x2bc7) + * ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0009-0x0009 + * Attribute type: Characteristic (0x2803) + * ATT: Error Response (0x01) len 4 + * Read By Type Request (0x08) + * Handle: 0x0009 + * Error: Attribute Not Found (0x0a) + */ +#define DISC_BASS_CHAR \ + IOV_DATA(0x08, 0x01, 0x00, 0x09, 0x00, 0x03, 0x28), \ + IOV_DATA(0x09, 0x07, \ + 0x02, 0x00, 0x12, 0x03, 0x00, 0xc8, 0x2b, \ + 0x05, 0x00, 0x12, 0x06, 0x00, 0xc8, 0x2b, \ + 0x08, 0x00, 0x0c, 0x09, 0x00, 0xc7, 0x2b), \ + IOV_DATA(0x08, 0x09, 0x00, 0x09, 0x00, 0x03, 0x28), \ + IOV_DATA(0x01, 0x08, 0x09, 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 7 + * Attribute data length: 6 + * Attribute group list: 1 entry + * Handle range: 0x0001-0x0009 + * UUID: Broadcast Audio Scan Service (0x184f) + * ATT: Read By Group Type Request (0x10) len 6 + * Handle range: 0x000a-0xffff + * Attribute group type: Primary Service (0x2800) + * ATT: Error Response (0x01) len 4 + * Read By Group Type Request (0x10) + * Handle: 0x000a + * Error: Attribute Not Found (0x0a) + */ +#define DISC_BASS_SER \ + EXCHANGE_MTU,\ + IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \ + IOV_DATA(0x11, 0x06, 0x01, 0x00, 0x09, 0x00, 0x4f, 0x18), \ + IOV_DATA(0x10, 0x0a, 0x00, 0xff, 0xff, 0x00, 0x28), \ + IOV_DATA(0x01, 0x10, 0x0a, 0x00, 0x0a), \ + BASS_FIND_BY_TYPE_VALUE, \ + DISC_BASS_CHAR + +/* ATT: Find Information Request (0x04) len 4 + * Handle range: 0x0004-0x0004 + * ATT: Find Information Response (0x05) len 5 + * Format: Handle(s) and 16 bit bluetooth UUID(s) (0x01) + * Handle: 0x0004 + * Attribute: Client Characteristic Configuration (0x2902) + * ATT: Find Information Request (0x04) len 4 + * Handle range: 0x0007-0x0007 + * ATT: Find Information Response (0x05) len 5 + * Format: Handle(s) and 16 bit bluetooth UUID(s) (0x01) + * Handle: 0x0007 + * Attribute: Client Characteristic Configuration (0x2902) + */ +#define BASS_FIND_INFO \ + IOV_DATA(0x04, 0x04, 0x00, 0x04, 0x00), \ + IOV_DATA(0x05, 0x01, 0x04, 0x00, 0x02, 0x29), \ + IOV_DATA(0x04, 0x07, 0x00, 0x07, 0x00), \ + IOV_DATA(0x05, 0x01, 0x07, 0x00, 0x02, 0x29) + +#define DISC_BCAST_AUDIO_SCAN_CP \ + BASS_FIND_BY_TYPE_VALUE, \ + DISC_BASS_CHAR, \ + BASS_FIND_INFO + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x0004 Type: Client Characteristic Configuration (0x2902) + * ATT: Read Response (0x0b) len 2 + * Value: 0000 + * Handle: 0x0004 Type: Client Characteristic Configuration (0x2902) + * ATT: Read Request (0x0a) len 2 + * Handle: 0x0007 Type: Client Characteristic Configuration (0x2902) + * ATT: Read Response (0x0b) len 2 + * Value: 0000 + * Handle: 0x0007 Type: Client Characteristic Configuration (0x2902) + */ +#define BASS_READ_CHAR_DESC \ + IOV_DATA(0x0a, 0x04, 0x00), \ + IOV_DATA(0x0b, 0x00, 0x00), \ + IOV_DATA(0x0a, 0x07, 0x00), \ + IOV_DATA(0x0b, 0x00, 0x00) + +#define DISC_BCAST_RECV_STATE \ + DISC_BCAST_AUDIO_SCAN_CP, \ + BASS_READ_CHAR_DESC + +#define iov_data(args...) ((const struct iovec[]) { args }) + +#define define_test(name, function, _cfg, args...) \ + do { \ + const struct iovec iov[] = { args }; \ + static struct test_data data; \ + data.iovcnt = ARRAY_SIZE(iov_data(args)); \ + data.iov = util_iov_dup(iov, ARRAY_SIZE(iov_data(args))); \ + tester_add(name, &data, NULL, function, \ + test_teardown); \ + } while (0) + +static void test_complete_cb(const void *user_data) +{ + tester_test_passed(); +} + +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 test_teardown(const void *user_data) +{ + struct test_data *data = (void *)user_data; + + bt_bass_unref(data->bass); + bt_gatt_server_unref(data->server); + util_iov_free(data->iov, data->iovcnt); + + gatt_db_unref(data->db); + + queue_destroy(data->ccc_states, free); + + tester_teardown_complete(); +} + +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_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; + const uint8_t *value = NULL; + size_t len = 0; + + handle = gatt_db_attribute_get_handle(attrib); + + ccc = get_ccc_state(data, handle); + if (!ccc) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + len = sizeof(ccc->value); + value = (void *) &ccc->value; + +done: + gatt_db_attribute_read_result(attrib, id, ecode, value, len); +} + +static void test_server(const void *user_data) +{ + struct test_data *data = (void *)user_data; + struct bt_att *att; + struct io *io; + + io = tester_setup_io(data->iov, data->iovcnt); + g_assert(io); + + tester_io_set_complete_func(test_complete_cb); + + 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->db = gatt_db_new(); + g_assert(data->db); + + gatt_db_ccc_register(data->db, gatt_ccc_read_cb, NULL, + NULL, data); + + data->bass = bt_bass_new(data->db, NULL); + g_assert(data->bass); + + data->server = bt_gatt_server_new(data->db, att, 64, 0); + g_assert(data->server); + + bt_gatt_server_set_debug(data->server, print_debug, "bt_gatt_server:", + NULL); + + data->ccc_states = queue_new(); + + tester_io_send(); + + bt_att_unref(att); +} + +static void test_sggit(void) +{ + /* BASS/SR/SGGIT/SER/BV-01-C [Service GGIT - Broadcast Scan] + * + * For each ATT_Read_By_Group_Type_Request, the IUT sends a correctly + * formatted ATT_Read_By_Group_Type_Response reporting BASS to the + * Lower Tester, or an ATT_Error_Response if there is no handle/UUID + * pair matching the request. + * + * For each ATT_Find_By_Type_Value_Request, the IUT sends one + * ATT_Find_By_Type_Value_Response reporting BASS to the Lower Tester, + * or an ATT_Error_Response when there are no more services matching + * the request. + * + * The IUT sends one ATT_Read_By_Type_Response to the Lower Tester for + * each received ATT_Read_By_Type_Request, if it has characteristic + * declarations within the handle range, or an ATT_Error_Response if + * there are no further characteristic declarations within the + * handle range of the request. The IUT reports all BASS + * characteristics. + */ + define_test("BASS/SR/SGGIT/SER/BV-01-C", test_server, NULL, + DISC_BASS_SER); + + /* BASS/SR/SGGIT/CHA/BV-01-C [Service GGIT - + * Broadcast Audio Scan Control Point] + * + * The IUT sends one ATT_Read_By_Type_Response to the Lower Tester for + * each received ATT_Read_By_Type_Request, if it has characteristic + * declarations within the handle range, or an ATT_Error_Response if + * there are no further characteristic declarations within the + * handle range of the request. The IUT reports one instance of the + * Broadcast Audio Scan Control Point characteristic. + */ + define_test("BASS/SR/SGGIT/CHA/BV-01-C", test_server, NULL, + DISC_BCAST_AUDIO_SCAN_CP); + + /* BASS/SR/SGGIT/CHA/BV-02-C [Service GGIT - + * Broadcast Receive State] + * + * The IUT sends one ATT_Read_By_Type_Response to the Lower Tester for + * each received ATT_Read_By_Type_Request, if it has characteristic + * declarations within the handle range, or an ATT_Error_Response if + * there are no further characteristic declarations within the + * handle range of the request. The IUT reports two instances of the + * Broadcast Receive State characteristic. + * + * The IUT sends one ATT_Find_Information_Response to the Lower Tester + * for each received ATT_Find_Information_Request, if it has + * characteristic descriptors within the handle range, or an + * ATT_Error_Response if there are no characteristic descriptors within + * the handle range of the request. For each Broadcast Receive State + * characteristic, the IUT reports one Client Characteristic + * Configuration descriptor. + * + * The IUT sends an ATT_Read_Response to the Lower Tester for each + * ATT_Read_Request. + */ + define_test("BASS/SR/SGGIT/CHA/BV-02-C", test_server, NULL, + DISC_BCAST_RECV_STATE); +} + +int main(int argc, char *argv[]) +{ + tester_init(&argc, &argv); + + test_sggit(); + + return tester_run(); +} -- 2.34.1