From 44c84bbf9ac528b963142ed2feed241218577be2 Mon Sep 17 00:00:00 2001 From: Frode van der Meeren Date: Fri, 24 Jan 2025 17:08:21 +0100 Subject: [PATCH] [nrf fromtree] tests: bluetooth: tester: Add PBP support Implements the PBP commands of the BTP specs, in order to support testing the PBP through autopts. This PR is in conjunction with the following PR in AutoPTS: https://github.com/auto-pts/auto-pts/pull/1356 Upstream PR #: 82654 Signed-off-by: Frode van der Meeren --- tests/bluetooth/tester/overlay-le-audio.conf | 3 + .../bluetooth/tester/src/audio/CMakeLists.txt | 4 + .../bluetooth/tester/src/audio/btp/btp_pbp.h | 46 +++ tests/bluetooth/tester/src/audio/btp_cap.c | 53 ++-- tests/bluetooth/tester/src/audio/btp_pbp.c | 287 ++++++++++++++++++ tests/bluetooth/tester/src/btp/btp.h | 4 +- tests/bluetooth/tester/src/btp/bttester.h | 3 + tests/bluetooth/tester/src/btp_core.c | 13 + 8 files changed, 384 insertions(+), 29 deletions(-) create mode 100644 tests/bluetooth/tester/src/audio/btp/btp_pbp.h create mode 100644 tests/bluetooth/tester/src/audio/btp_pbp.c diff --git a/tests/bluetooth/tester/overlay-le-audio.conf b/tests/bluetooth/tester/overlay-le-audio.conf index e6ada545978..e22151c9c57 100644 --- a/tests/bluetooth/tester/overlay-le-audio.conf +++ b/tests/bluetooth/tester/overlay-le-audio.conf @@ -166,3 +166,6 @@ CONFIG_BT_TBS_SUPPORTED_FEATURES=3 # TMAP CONFIG_BT_TMAP=y + +# PBP +CONFIG_BT_PBP=y diff --git a/tests/bluetooth/tester/src/audio/CMakeLists.txt b/tests/bluetooth/tester/src/audio/CMakeLists.txt index 072819eeca4..d6f2833d3da 100644 --- a/tests/bluetooth/tester/src/audio/CMakeLists.txt +++ b/tests/bluetooth/tester/src/audio/CMakeLists.txt @@ -67,3 +67,7 @@ endif() if(CONFIG_BT_TMAP) target_sources(app PRIVATE btp_tmap.c) endif() + +if(CONFIG_BT_PBP) + target_sources(app PRIVATE btp_pbp.c) +endif() diff --git a/tests/bluetooth/tester/src/audio/btp/btp_pbp.h b/tests/bluetooth/tester/src/audio/btp/btp_pbp.h new file mode 100644 index 00000000000..614d25e8296 --- /dev/null +++ b/tests/bluetooth/tester/src/audio/btp/btp_pbp.h @@ -0,0 +1,46 @@ +/* btp_pbp.c - Bluetooth PBP Tester */ + +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* This is main.h */ + +#define BTP_PBP_READ_SUPPORTED_COMMANDS 0x01 +struct btp_pbp_read_supported_commands_rp { + uint8_t data[0]; +} __packed; + +#define BTP_PBP_SET_PUBLIC_BROADCAST_ANNOUNCEMENT 0x02 +struct btp_pbp_set_public_broadcast_announcement_cmd { + uint8_t features; + uint8_t metadata_len; + uint8_t metadata[]; +} __packed; + +#define BTP_PBP_SET_BROADCAST_NAME 0x03 +struct btp_pbp_set_broadcast_name_cmd { + uint8_t name_len; + uint8_t name[]; +} __packed; + +#define BTP_PBP_BROADCAST_SCAN_START 0x04 +struct btp_pbp_broadcast_scan_start_cmd { +} __packed; + +#define BTP_PBP_BROADCAST_SCAN_STOP 0x05 +struct btp_pbp_broadcast_scan_stop_cmd { +} __packed; + +#define BTP_PBP_EV_PUBLIC_BROADCAST_ANOUNCEMENT_FOUND 0x80 +struct btp_pbp_ev_public_broadcast_anouncement_found_rp { + bt_addr_le_t address; + uint8_t broadcast_id[BT_AUDIO_BROADCAST_ID_SIZE]; + uint8_t advertiser_sid; + uint16_t padv_interval; + uint8_t pba_features; + uint8_t broadcast_name_len; + uint8_t broadcast_name[]; +} __packed; diff --git a/tests/bluetooth/tester/src/audio/btp_cap.c b/tests/bluetooth/tester/src/audio/btp_cap.c index 6563a761e7e..e0356aae097 100644 --- a/tests/bluetooth/tester/src/audio/btp_cap.c +++ b/tests/bluetooth/tester/src/audio/btp_cap.c @@ -533,40 +533,37 @@ static int cap_broadcast_source_adv_setup(struct btp_bap_broadcast_local_source uint32_t *gap_settings) { int err; - struct bt_le_adv_param param = *BT_LE_EXT_ADV_NCONN; - uint32_t broadcast_id; - NET_BUF_SIMPLE_DEFINE(ad_buf, BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE); NET_BUF_SIMPLE_DEFINE(base_buf, 128); /* Broadcast Audio Streaming Endpoint advertising data */ - struct bt_data base_ad[2]; struct bt_data per_ad; - err = bt_rand(&broadcast_id, BT_AUDIO_BROADCAST_ID_SIZE); - if (err) { - printk("Unable to generate broadcast ID: %d\n", err); - - return -EINVAL; - } - - *gap_settings = BIT(BTP_GAP_SETTINGS_DISCOVERABLE) | - BIT(BTP_GAP_SETTINGS_EXTENDED_ADVERTISING); - /* Setup extended advertising data */ - net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL); - net_buf_simple_add_le24(&ad_buf, source->broadcast_id); - base_ad[0].type = BT_DATA_SVC_DATA16; - base_ad[0].data_len = ad_buf.len; - base_ad[0].data = ad_buf.data; - base_ad[1].type = BT_DATA_NAME_COMPLETE; - base_ad[1].data_len = sizeof(CONFIG_BT_DEVICE_NAME) - 1; - base_ad[1].data = CONFIG_BT_DEVICE_NAME; - err = tester_gap_create_adv_instance(¶m, BTP_GAP_ADDR_TYPE_IDENTITY, base_ad, 2, NULL, - 0, gap_settings); - if (err != 0) { - LOG_DBG("Failed to create extended advertising instance: %d", err); - - return -EINVAL; + /* A more specialized adv instance may already have been created by another btp module */ + if (tester_gap_ext_adv_get() == NULL) { + struct bt_le_adv_param param = *BT_LE_EXT_ADV_NCONN; + struct bt_data base_ad[2]; + + NET_BUF_SIMPLE_DEFINE(ad_buf, BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE); + *gap_settings = BIT(BTP_GAP_SETTINGS_DISCOVERABLE) | + BIT(BTP_GAP_SETTINGS_EXTENDED_ADVERTISING); + /* Setup extended advertising data */ + net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL); + net_buf_simple_add_le24(&ad_buf, source->broadcast_id); + base_ad[0].type = BT_DATA_SVC_DATA16; + base_ad[0].data_len = ad_buf.len; + base_ad[0].data = ad_buf.data; + base_ad[1].type = BT_DATA_NAME_COMPLETE; + base_ad[1].data_len = sizeof(CONFIG_BT_DEVICE_NAME) - 1; + base_ad[1].data = CONFIG_BT_DEVICE_NAME; + + err = tester_gap_create_adv_instance(¶m, BTP_GAP_ADDR_TYPE_IDENTITY, base_ad, + 2, NULL, 0, gap_settings); + if (err != 0) { + LOG_DBG("Failed to create extended advertising instance: %d", err); + + return -EINVAL; + } } err = tester_gap_padv_configure(BT_LE_PER_ADV_PARAM(BT_GAP_PER_ADV_FAST_INT_MIN_2, diff --git a/tests/bluetooth/tester/src/audio/btp_pbp.c b/tests/bluetooth/tester/src/audio/btp_pbp.c new file mode 100644 index 00000000000..d3acfcbd807 --- /dev/null +++ b/tests/bluetooth/tester/src/audio/btp_pbp.c @@ -0,0 +1,287 @@ +/* btp_pbp.c - Bluetooth PBP Tester */ + +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "btp/btp.h" + +#include +#include +#include +#include +#define LOG_MODULE_NAME bttester_pbp +LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); + +#define PBP_EXT_ADV_METADATA_LEN_MAX 128 + +static uint8_t pbp_features_cached; +static uint8_t pbp_metadata_cached[PBP_EXT_ADV_METADATA_LEN_MAX]; +static uint8_t pbp_metadata_cached_len; +static uint8_t pbp_broadcast_name_cached[BT_AUDIO_BROADCAST_NAME_LEN_MAX]; +static uint8_t pbp_name_cached_len; + +static bool scan_get_broadcast_name_len(struct bt_data *data, void *user_data) +{ + uint8_t *broadcast_name_len = user_data; + + switch (data->type) { + case BT_DATA_BROADCAST_NAME: + *broadcast_name_len = data->data_len; + return false; + default: + return true; + } +} + +static bool scan_get_data(struct bt_data *data, void *user_data) +{ + enum bt_pbp_announcement_feature source_features; + uint32_t broadcast_id; + uint8_t *metadata; + struct bt_uuid_16 adv_uuid; + struct btp_pbp_ev_public_broadcast_anouncement_found_rp *ev = user_data; + + switch (data->type) { + case BT_DATA_BROADCAST_NAME: + ev->broadcast_name_len = data->data_len; + memcpy(ev->broadcast_name, data->data, data->data_len); + return true; + case BT_DATA_SVC_DATA16: + if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) { + return true; + } + if (!bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) { + broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); + sys_put_le24(broadcast_id, ev->broadcast_id); + return true; + } + + int ret = bt_pbp_parse_announcement(data, &source_features, &metadata); + + if (ret >= 0) { + ev->pba_features = source_features; + return true; + } + + return true; + default: + return true; + } +} + +static void pbp_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad) +{ + if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) || + !(info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) || info->interval == 0) { + return; + } + + uint8_t broadcast_name_len = 0; + struct net_buf_simple ad_copy; + + /* Initial parse to determine broadcast_name_len before allocating memory */ + net_buf_simple_clone(ad, &ad_copy); + bt_data_parse(&ad_copy, scan_get_broadcast_name_len, &broadcast_name_len); + + struct btp_pbp_ev_public_broadcast_anouncement_found_rp *ev_ptr; + + tester_rsp_buffer_lock(); + tester_rsp_buffer_allocate(sizeof(*ev_ptr) + broadcast_name_len, (uint8_t **)&ev_ptr); + + sys_put_le24(BT_BAP_INVALID_BROADCAST_ID, ev_ptr->broadcast_id); + ev_ptr->pba_features = 0U; + ev_ptr->broadcast_name_len = 0U; + + bt_addr_le_copy(&ev_ptr->address, info->addr); + ev_ptr->advertiser_sid = info->sid; + ev_ptr->padv_interval = info->interval; + bt_data_parse(ad, scan_get_data, ev_ptr); + + if (sys_get_le24(ev_ptr->broadcast_id) != BT_BAP_INVALID_BROADCAST_ID && + ev_ptr->pba_features != 0U && ev_ptr->broadcast_name_len > 0) { + tester_event(BTP_SERVICE_ID_PBP, BTP_PBP_EV_PUBLIC_BROADCAST_ANOUNCEMENT_FOUND, + ev_ptr, sizeof(*ev_ptr) + broadcast_name_len); + } + + tester_rsp_buffer_free(); + tester_rsp_buffer_unlock(); +} + +static struct bt_le_scan_cb pbp_scan_cb = { + .recv = pbp_scan_recv, +}; + +static uint8_t pbp_read_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp, + uint16_t *rsp_len) +{ + struct btp_pbp_read_supported_commands_rp *rp = rsp; + + tester_set_bit(rp->data, BTP_PBP_READ_SUPPORTED_COMMANDS); + tester_set_bit(rp->data, BTP_PBP_SET_PUBLIC_BROADCAST_ANNOUNCEMENT); + tester_set_bit(rp->data, BTP_PBP_SET_BROADCAST_NAME); + tester_set_bit(rp->data, BTP_PBP_BROADCAST_SCAN_START); + tester_set_bit(rp->data, BTP_PBP_BROADCAST_SCAN_STOP); + + *rsp_len = sizeof(*rp) + 1; + + return BTP_STATUS_SUCCESS; +} + +static int pbp_broadcast_source_adv_setup(void) +{ + struct bt_le_adv_param param = + BT_LE_ADV_PARAM_INIT(0, BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL); + uint32_t gap_settings = BIT(BTP_GAP_SETTINGS_DISCOVERABLE) | + BIT(BTP_GAP_SETTINGS_EXTENDED_ADVERTISING); + uint32_t broadcast_id; + + NET_BUF_SIMPLE_DEFINE(ad_buf, BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE); + NET_BUF_SIMPLE_DEFINE(pba_buf, BT_PBP_MIN_PBA_SIZE + pbp_metadata_cached_len); + + int err = bt_rand(&broadcast_id, BT_AUDIO_BROADCAST_ID_SIZE); + struct bt_data ext_ad[3]; + + if (err) { + LOG_ERR("Unable to generate broadcast ID: %d\n", err); + return -EINVAL; + } + + ext_ad[0].type = BT_DATA_BROADCAST_NAME; + ext_ad[0].data_len = pbp_name_cached_len; + ext_ad[0].data = pbp_broadcast_name_cached; + net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL); + net_buf_simple_add_le24(&ad_buf, broadcast_id); + ext_ad[1].type = BT_DATA_SVC_DATA16; + ext_ad[1].data_len = ad_buf.len; + ext_ad[1].data = ad_buf.data; + net_buf_simple_add_le16(&pba_buf, BT_UUID_PBA_VAL); + net_buf_simple_add_u8(&pba_buf, pbp_features_cached); + net_buf_simple_add_u8(&pba_buf, pbp_metadata_cached_len); + net_buf_simple_add_mem(&pba_buf, pbp_metadata_cached, pbp_metadata_cached_len); + ext_ad[2].type = BT_DATA_SVC_DATA16; + ext_ad[2].data_len = pba_buf.len; + ext_ad[2].data = pba_buf.data; + + err = tester_gap_create_adv_instance(¶m, BTP_GAP_ADDR_TYPE_IDENTITY, ext_ad, + ARRAY_SIZE(ext_ad), NULL, 0, &gap_settings); + if (err) { + LOG_ERR("Could not set up extended advertisement: %d", err); + return -EINVAL; + } + + return 0; +} + +static uint8_t pbp_set_public_broadcast_announcement(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + const struct btp_pbp_set_public_broadcast_announcement_cmd *cp = cmd; + int err = -EINVAL; + + if (cp->metadata_len <= PBP_EXT_ADV_METADATA_LEN_MAX) { + pbp_features_cached = cp->features; + pbp_metadata_cached_len = cp->metadata_len; + memcpy(pbp_metadata_cached, cp->metadata, cp->metadata_len); + err = pbp_broadcast_source_adv_setup(); + } else { + LOG_ERR("Metadata too long: %d > %d", cp->metadata_len, + PBP_EXT_ADV_METADATA_LEN_MAX); + } + + return BTP_STATUS_VAL(err); +} + +static uint8_t pbp_set_broadcast_name(const void *cmd, uint16_t cmd_len, void *rsp, + uint16_t *rsp_len) +{ + const struct btp_pbp_set_broadcast_name_cmd *cp = cmd; + int err = -EINVAL; + + if (cp->name_len <= BT_AUDIO_BROADCAST_NAME_LEN_MAX) { + pbp_name_cached_len = cp->name_len; + memcpy(pbp_broadcast_name_cached, cp->name, cp->name_len); + err = pbp_broadcast_source_adv_setup(); + } else { + LOG_ERR("Broadcast name too long: %d > %d", cp->name_len, + BT_AUDIO_BROADCAST_NAME_LEN_MAX); + } + + return BTP_STATUS_VAL(err); +} + +static uint8_t pbp_broadcast_scan_start(const void *cmd, uint16_t cmd_len, void *rsp, + uint16_t *rsp_len) +{ + int err; + + err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL); + if (err != 0 && err != -EALREADY) { + LOG_DBG("Unable to start scan for broadcast sources: %d", err); + + return BTP_STATUS_FAILED; + } + + return BTP_STATUS_SUCCESS; +} + +static uint8_t pbp_broadcast_scan_stop(const void *cmd, uint16_t cmd_len, void *rsp, + uint16_t *rsp_len) +{ + int err; + + err = bt_le_scan_stop(); + if (err != 0) { + LOG_DBG("Failed to stop scan, %d", err); + + return BTP_STATUS_FAILED; + } + + return BTP_STATUS_SUCCESS; +} + +static const struct btp_handler pbp_handlers[] = { + { + .opcode = BTP_PBP_READ_SUPPORTED_COMMANDS, + .index = BTP_INDEX_NONE, + .expect_len = 0, + .func = pbp_read_supported_commands + }, + { + .opcode = BTP_PBP_SET_PUBLIC_BROADCAST_ANNOUNCEMENT, + .expect_len = BTP_HANDLER_LENGTH_VARIABLE, + .func = pbp_set_public_broadcast_announcement + }, + { + .opcode = BTP_PBP_SET_BROADCAST_NAME, + .expect_len = BTP_HANDLER_LENGTH_VARIABLE, + .func = pbp_set_broadcast_name + }, + { + .opcode = BTP_PBP_BROADCAST_SCAN_START, + .expect_len = sizeof(struct btp_pbp_broadcast_scan_start_cmd), + .func = pbp_broadcast_scan_start + }, + { + .opcode = BTP_PBP_BROADCAST_SCAN_STOP, + .expect_len = sizeof(struct btp_pbp_broadcast_scan_stop_cmd), + .func = pbp_broadcast_scan_stop + } +}; + +uint8_t tester_init_pbp(void) +{ + tester_register_command_handlers(BTP_SERVICE_ID_PBP, pbp_handlers, + ARRAY_SIZE(pbp_handlers)); + + bt_le_scan_cb_register(&pbp_scan_cb); + + return BTP_STATUS_SUCCESS; +} + +uint8_t tester_unregister_pbp(void) +{ + return BTP_STATUS_SUCCESS; +} diff --git a/tests/bluetooth/tester/src/btp/btp.h b/tests/bluetooth/tester/src/btp/btp.h index c74d7bec452..06dc0788398 100644 --- a/tests/bluetooth/tester/src/btp/btp.h +++ b/tests/bluetooth/tester/src/btp/btp.h @@ -38,6 +38,7 @@ #include "btp_tbs.h" #include "btp_tmap.h" #include "btp_ots.h" +#include "btp_pbp.h" #define BTP_MTU 1024 #define BTP_DATA_MAX_SIZE (BTP_MTU - sizeof(struct btp_hdr)) @@ -75,8 +76,9 @@ #define BTP_SERVICE_ID_TBS 0x1b #define BTP_SERVICE_ID_TMAP 0x1c #define BTP_SERVICE_ID_OTS 0x1d +#define BTP_SERVICE_ID_PBP 0x1e -#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_OTS +#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_PBP #define BTP_STATUS_SUCCESS 0x00 #define BTP_STATUS_FAILED 0x01 diff --git a/tests/bluetooth/tester/src/btp/bttester.h b/tests/bluetooth/tester/src/btp/bttester.h index d7ce2699310..c43725a0aaf 100644 --- a/tests/bluetooth/tester/src/btp/bttester.h +++ b/tests/bluetooth/tester/src/btp/bttester.h @@ -138,3 +138,6 @@ uint8_t tester_unregister_tmap(void); uint8_t tester_init_ots(void); uint8_t tester_unregister_ots(void); + +uint8_t tester_init_pbp(void); +uint8_t tester_unregister_pbp(void); diff --git a/tests/bluetooth/tester/src/btp_core.c b/tests/bluetooth/tester/src/btp_core.c index 8b1b571792d..2da735f44ec 100644 --- a/tests/bluetooth/tester/src/btp_core.c +++ b/tests/bluetooth/tester/src/btp_core.c @@ -107,6 +107,9 @@ static uint8_t supported_services(const void *cmd, uint16_t cmd_len, #if defined(CONFIG_BT_TMAP) tester_set_bit(rp->data, BTP_SERVICE_ID_TMAP); #endif /* CONFIG_BT_TMAP */ +#if defined(CONFIG_BT_PBP) + tester_set_bit(rp->data, BTP_SERVICE_ID_PBP); +#endif /* CONFIG_BT_PBP */ *rsp_len = sizeof(*rp) + 2; @@ -250,6 +253,11 @@ static uint8_t register_service(const void *cmd, uint16_t cmd_len, status = tester_init_ots(); break; #endif /* CONFIG_BT_OTS */ +#if defined(CONFIG_BT_PBP) + case BTP_SERVICE_ID_PBP: + status = tester_init_pbp(); + break; +#endif /* CONFIG_BT_PBP */ default: LOG_WRN("unknown id: 0x%02x", cp->id); status = BTP_STATUS_FAILED; @@ -397,6 +405,11 @@ static uint8_t unregister_service(const void *cmd, uint16_t cmd_len, status = tester_unregister_ots(); break; #endif /* CONFIG_BT_OTS */ +#if defined(CONFIG_BT_PBP) + case BTP_SERVICE_ID_PBP: + status = tester_unregister_pbp(); + break; +#endif /* CONFIG_BT_PBP */ default: LOG_WRN("unknown id: 0x%x", cp->id); status = BTP_STATUS_FAILED;