From 6c433a50bcd6b580ce63513e126dc4c99d9abb4f Mon Sep 17 00:00:00 2001 From: Frode van der Meeren Date: Fri, 6 Dec 2024 15:51:32 +0100 Subject: [PATCH] Tests: Bluetooth: Tester: Add support for PBP testing Adds support for the existing BTP commands for PBP testing. This should enable bttester to be used to test this profile with AutoPTS. Signed-off-by: Frode van der Meeren --- .../bluetooth/tester/src/audio/CMakeLists.txt | 4 + .../bluetooth/tester/src/audio/btp/btp_pbp.h | 44 +++ tests/bluetooth/tester/src/audio/btp_pbp.c | 272 ++++++++++++++++++ tests/bluetooth/tester/src/btp/btp.h | 2 + tests/bluetooth/tester/src/btp/bttester.h | 3 + tests/bluetooth/tester/src/btp_core.c | 13 + 6 files changed, 338 insertions(+) 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/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..97c2dfcbbd1 --- /dev/null +++ b/tests/bluetooth/tester/src/audio/btp/btp_pbp.h @@ -0,0 +1,44 @@ +/* btp_pbp.c - Bluetooth PBP Tester */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#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_pbp.c b/tests/bluetooth/tester/src/audio/btp_pbp.c new file mode 100644 index 00000000000..40da2a3a2c9 --- /dev/null +++ b/tests/bluetooth/tester/src/audio/btp_pbp.c @@ -0,0 +1,272 @@ +/* btp_pbp.c - Bluetooth PBP Tester */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "btp/btp.h" + +#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_metadata_cached_len; +static uint8_t pbp_metadata_cached[PBP_EXT_ADV_METADATA_LEN_MAX]; +static uint8_t pbp_name_cached_len; +static uint8_t pbp_broadcast_name_cached[BT_AUDIO_BROADCAST_NAME_LEN_MAX]; + +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) +{ + uint8_t source_features; + uint32_t broadcast_id; + uint8_t metadata_len; + uint8_t *metadata; + 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 false; + case BT_DATA_SVC_DATA16: + broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); + sys_put_le24(broadcast_id, ev->broadcast_id); + metadata_len = bt_pbp_parse_announcement(data, &source_features, &metadata); + ev->pba_features = source_features; + return false; + 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->interval == 0) { + return; + } + + uint8_t broadcast_name_len = 0; + struct net_buf_simple ad_copy; + + 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); + + 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); + + 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); + + int err = bt_rand(&broadcast_id, BT_AUDIO_BROADCAST_ID_SIZE); + + if (err) { + LOG_ERR("Unable to generate broadcast ID: %d\n", err); + return -EINVAL; + } + + struct bt_data ext_ad[3]; + + 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; + ext_ad[2].type = BT_DATA_SVC_DATA16; + ext_ad[2].data_len = pbp_metadata_cached_len; + ext_ad[2].data = pbp_metadata_cached; + 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; + } + + err = tester_gap_start_ext_adv(); + if (err) { + LOG_ERR("Could not start 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_name_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_metadata_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; + + LOG_DBG("Advertisement starting..."); + + err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL); + if (err != 0 && err != -EALREADY) { + LOG_ERR("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; + + LOG_DBG("Advertisement stopping..."); + + err = bt_le_scan_stop(); + if (err != 0) { + LOG_ERR("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 = sizeof(struct btp_pbp_set_public_broadcast_announcement_cmd), + .func = pbp_set_public_broadcast_announcement + }, + { + .opcode = BTP_PBP_SET_BROADCAST_NAME, + .expect_len = sizeof(struct btp_pbp_set_broadcast_name_cmd), + .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) +{ + pbp_metadata_cached_len = 0; + pbp_name_cached_len = 0; + 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 50443463997..348ea3568e8 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,6 +76,7 @@ #define BTP_SERVICE_ID_TBS 27 #define BTP_SERVICE_ID_TMAP 28 #define BTP_SERVICE_ID_OTS 29 +#define BTP_SERVICE_ID_PBP 30 #define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_OTS 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;