| 
 | 1 | +/* btp_pbp.c - Bluetooth PBP Tester */  | 
 | 2 | + | 
 | 3 | +/*  | 
 | 4 | + * Copyright (c) 2025 Nordic Semiconductor ASA  | 
 | 5 | + *  | 
 | 6 | + * SPDX-License-Identifier: Apache-2.0  | 
 | 7 | + */  | 
 | 8 | + | 
 | 9 | +#include "btp/btp.h"  | 
 | 10 | + | 
 | 11 | +#include <zephyr/bluetooth/audio/pbp.h>  | 
 | 12 | +#include <zephyr/logging/log.h>  | 
 | 13 | +#include <zephyr/sys/util.h>  | 
 | 14 | +#define LOG_MODULE_NAME bttester_pbp  | 
 | 15 | +LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL);  | 
 | 16 | + | 
 | 17 | +#define PBP_EXT_ADV_METADATA_LEN_MAX 128  | 
 | 18 | +#define PBP_ADV_HEADER_SIZE (BT_UUID_SIZE_16 + sizeof(uint8_t) * 2)  | 
 | 19 | + | 
 | 20 | +static uint8_t pbp_features_cached;  | 
 | 21 | +static uint8_t pbp_metadata_cached[PBP_EXT_ADV_METADATA_LEN_MAX];  | 
 | 22 | +static uint8_t pbp_metadata_cached_len;  | 
 | 23 | +static uint8_t pbp_broadcast_name_cached[BT_AUDIO_BROADCAST_NAME_LEN_MAX];  | 
 | 24 | +static uint8_t pbp_name_cached_len;  | 
 | 25 | + | 
 | 26 | +static bool scan_get_broadcast_name_len(struct bt_data *data, void *user_data)  | 
 | 27 | +{  | 
 | 28 | +	uint8_t *broadcast_name_len = user_data;  | 
 | 29 | + | 
 | 30 | +	switch (data->type) {  | 
 | 31 | +	case BT_DATA_BROADCAST_NAME:  | 
 | 32 | +		*broadcast_name_len = data->data_len;  | 
 | 33 | +		return false;  | 
 | 34 | +	default:  | 
 | 35 | +		return true;  | 
 | 36 | +	}  | 
 | 37 | +}  | 
 | 38 | + | 
 | 39 | +static bool scan_get_data(struct bt_data *data, void *user_data)  | 
 | 40 | +{  | 
 | 41 | +	enum bt_pbp_announcement_feature source_features;  | 
 | 42 | +	uint32_t broadcast_id;  | 
 | 43 | +	uint8_t *metadata;  | 
 | 44 | +	struct btp_pbp_ev_public_broadcast_anouncement_found_rp *ev = user_data;  | 
 | 45 | + | 
 | 46 | +	switch (data->type) {  | 
 | 47 | +	case BT_DATA_BROADCAST_NAME:  | 
 | 48 | +		ev->broadcast_name_len = data->data_len;  | 
 | 49 | +		memcpy(ev->broadcast_name, data->data, data->data_len);  | 
 | 50 | +		return true;  | 
 | 51 | +	case BT_DATA_SVC_DATA16:  | 
 | 52 | +		if (!bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) {  | 
 | 53 | +			broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);  | 
 | 54 | +			sys_put_le24(broadcast_id, ev->broadcast_id);  | 
 | 55 | +			return true;  | 
 | 56 | +		}  | 
 | 57 | + | 
 | 58 | +		ret = bt_pbp_parse_announcement(data, &source_features, &metadata);  | 
 | 59 | +		if (ret >= 0) {  | 
 | 60 | +			ev->pba_features = source_features;  | 
 | 61 | +			return true;  | 
 | 62 | +		}  | 
 | 63 | + | 
 | 64 | +		return true;  | 
 | 65 | +	default:  | 
 | 66 | +		return true;  | 
 | 67 | +	}  | 
 | 68 | +}  | 
 | 69 | + | 
 | 70 | +static void pbp_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)  | 
 | 71 | +{  | 
 | 72 | +	if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) ||  | 
 | 73 | +	    !(info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) || info->interval == 0) {  | 
 | 74 | +		return;  | 
 | 75 | +	}  | 
 | 76 | + | 
 | 77 | +	uint8_t broadcast_name_len = 0;  | 
 | 78 | +	struct net_buf_simple ad_copy;  | 
 | 79 | + | 
 | 80 | +	net_buf_simple_clone(ad, &ad_copy);  | 
 | 81 | +	bt_data_parse(&ad_copy, scan_get_broadcast_name_len, &broadcast_name_len);  | 
 | 82 | + | 
 | 83 | +	struct btp_pbp_ev_public_broadcast_anouncement_found_rp *ev_ptr;  | 
 | 84 | + | 
 | 85 | +	sys_put_le24(0xffffffff, ev_ptr->broadcast_id);  | 
 | 86 | +	ev_ptr->pba_features = 0xff;  | 
 | 87 | +	ev_ptr->broadcast_name_len = 0xff;  | 
 | 88 | + | 
 | 89 | +	tester_rsp_buffer_lock();  | 
 | 90 | +	tester_rsp_buffer_allocate(sizeof(*ev_ptr) + broadcast_name_len, (uint8_t **)&ev_ptr);  | 
 | 91 | + | 
 | 92 | +	bt_addr_le_copy(&ev_ptr->address, info->addr);  | 
 | 93 | +	ev_ptr->advertiser_sid = info->sid;  | 
 | 94 | +	ev_ptr->padv_interval = info->interval;  | 
 | 95 | +	bt_data_parse(ad, scan_get_data, ev_ptr);  | 
 | 96 | + | 
 | 97 | +	if (sys_get_le24(cp->broadcast_id) != -1 && ev_ptr->pba_features != -1 && ev_ptr->broadcast_name_len > 0) {  | 
 | 98 | +	tester_event(BTP_SERVICE_ID_PBP, BTP_PBP_EV_PUBLIC_BROADCAST_ANOUNCEMENT_FOUND, ev_ptr,  | 
 | 99 | +		sizeof(*ev_ptr) + broadcast_name_len);  | 
 | 100 | +	}  | 
 | 101 | + | 
 | 102 | +	tester_rsp_buffer_free();  | 
 | 103 | +	tester_rsp_buffer_unlock();  | 
 | 104 | +}  | 
 | 105 | + | 
 | 106 | +static struct bt_le_scan_cb pbp_scan_cb = {  | 
 | 107 | +	.recv = pbp_scan_recv,  | 
 | 108 | +};  | 
 | 109 | + | 
 | 110 | +static uint8_t pbp_read_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp,  | 
 | 111 | +					   uint16_t *rsp_len)  | 
 | 112 | +{  | 
 | 113 | +	struct btp_pbp_read_supported_commands_rp *rp = rsp;  | 
 | 114 | + | 
 | 115 | +	tester_set_bit(rp->data, BTP_PBP_READ_SUPPORTED_COMMANDS);  | 
 | 116 | +	tester_set_bit(rp->data, BTP_PBP_SET_PUBLIC_BROADCAST_ANNOUNCEMENT);  | 
 | 117 | +	tester_set_bit(rp->data, BTP_PBP_SET_BROADCAST_NAME);  | 
 | 118 | +	tester_set_bit(rp->data, BTP_PBP_BROADCAST_SCAN_START);  | 
 | 119 | +	tester_set_bit(rp->data, BTP_PBP_BROADCAST_SCAN_STOP);  | 
 | 120 | + | 
 | 121 | +	*rsp_len = sizeof(*rp) + 1;  | 
 | 122 | + | 
 | 123 | +	return BTP_STATUS_SUCCESS;  | 
 | 124 | +}  | 
 | 125 | + | 
 | 126 | +static int pbp_broadcast_source_adv_setup(void)  | 
 | 127 | +{  | 
 | 128 | +	struct bt_le_adv_param param =  | 
 | 129 | +		BT_LE_ADV_PARAM_INIT(0, BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL);  | 
 | 130 | +	uint32_t gap_settings = BIT(BTP_GAP_SETTINGS_DISCOVERABLE) |  | 
 | 131 | +				BIT(BTP_GAP_SETTINGS_EXTENDED_ADVERTISING);  | 
 | 132 | +	uint32_t broadcast_id;  | 
 | 133 | + | 
 | 134 | +	NET_BUF_SIMPLE_DEFINE(ad_buf, BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE);  | 
 | 135 | +	NET_BUF_SIMPLE_DEFINE(pba_buf, PBP_ADV_HEADER_SIZE + pbp_metadata_cached_len);  | 
 | 136 | + | 
 | 137 | +	int err = bt_rand(&broadcast_id, BT_AUDIO_BROADCAST_ID_SIZE);  | 
 | 138 | +	struct bt_data ext_ad[3];  | 
 | 139 | + | 
 | 140 | +	if (err) {  | 
 | 141 | +		LOG_ERR("Unable to generate broadcast ID: %d\n", err);  | 
 | 142 | +		return -EINVAL;  | 
 | 143 | +	}  | 
 | 144 | + | 
 | 145 | +	ext_ad[0].type = BT_DATA_BROADCAST_NAME;  | 
 | 146 | +	ext_ad[0].data_len = pbp_name_cached_len;  | 
 | 147 | +	ext_ad[0].data = pbp_broadcast_name_cached;  | 
 | 148 | +	net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL);  | 
 | 149 | +	net_buf_simple_add_le24(&ad_buf, broadcast_id);  | 
 | 150 | +	ext_ad[1].type = BT_DATA_SVC_DATA16;  | 
 | 151 | +	ext_ad[1].data_len = ad_buf.len;  | 
 | 152 | +	ext_ad[1].data = ad_buf.data;  | 
 | 153 | +	net_buf_simple_add_le16(&pba_buf, BT_UUID_PBA_VAL);  | 
 | 154 | +	net_buf_simple_add_u8(&pba_buf, pbp_features_cached);  | 
 | 155 | +	net_buf_simple_add_u8(&pba_buf, pbp_metadata_cached_len);  | 
 | 156 | +	net_buf_simple_add_mem(&pba_buf, pbp_metadata_cached, pbp_metadata_cached_len);  | 
 | 157 | +	ext_ad[2].type = BT_DATA_SVC_DATA16;  | 
 | 158 | +	ext_ad[2].data_len = pba_buf.len;  | 
 | 159 | +	ext_ad[2].data = pba_buf.data;  | 
 | 160 | + | 
 | 161 | +	err = tester_gap_create_adv_instance(¶m, BTP_GAP_ADDR_TYPE_IDENTITY, ext_ad,  | 
 | 162 | +					     ARRAY_SIZE(ext_ad), NULL, 0, &gap_settings);  | 
 | 163 | +	if (err) {  | 
 | 164 | +		LOG_ERR("Could not set up extended advertisement: %d", err);  | 
 | 165 | +		return -EINVAL;  | 
 | 166 | +	}  | 
 | 167 | + | 
 | 168 | +	cap_extern_ext_ad_setup(true);  | 
 | 169 | + | 
 | 170 | +	return 0;  | 
 | 171 | +}  | 
 | 172 | + | 
 | 173 | +static uint8_t pbp_set_public_broadcast_announcement(const void *cmd, uint16_t cmd_len,  | 
 | 174 | +						     void *rsp, uint16_t *rsp_len)  | 
 | 175 | +{  | 
 | 176 | +	const struct btp_pbp_set_public_broadcast_announcement_cmd *cp = cmd;  | 
 | 177 | +	int err = -EINVAL;  | 
 | 178 | + | 
 | 179 | +	if (cp->metadata_len <= PBP_EXT_ADV_METADATA_LEN_MAX) {  | 
 | 180 | +		pbp_features_cached = cp->features;  | 
 | 181 | +		pbp_metadata_cached_len = cp->metadata_len;  | 
 | 182 | +		memcpy(pbp_metadata_cached, cp->metadata, cp->metadata_len);  | 
 | 183 | +		err = pbp_broadcast_source_adv_setup();  | 
 | 184 | +	} else {  | 
 | 185 | +		LOG_ERR("Metadata too long: %d > %d", cp->metadata_len,  | 
 | 186 | +			PBP_EXT_ADV_METADATA_LEN_MAX);  | 
 | 187 | +	}  | 
 | 188 | + | 
 | 189 | +	return BTP_STATUS_VAL(err);  | 
 | 190 | +}  | 
 | 191 | + | 
 | 192 | +static uint8_t pbp_set_broadcast_name(const void *cmd, uint16_t cmd_len, void *rsp,  | 
 | 193 | +				      uint16_t *rsp_len)  | 
 | 194 | +{  | 
 | 195 | +	const struct btp_pbp_set_broadcast_name_cmd *cp = cmd;  | 
 | 196 | +	int err = -EINVAL;  | 
 | 197 | + | 
 | 198 | +	if (cp->name_len <= BT_AUDIO_BROADCAST_NAME_LEN_MAX) {  | 
 | 199 | +		pbp_name_cached_len = cp->name_len;  | 
 | 200 | +		memcpy(pbp_broadcast_name_cached, cp->name, cp->name_len);  | 
 | 201 | +		err = pbp_broadcast_source_adv_setup();  | 
 | 202 | +	} else {  | 
 | 203 | +		LOG_ERR("Broadcast name too long: %d > %d", cp->name_len,  | 
 | 204 | +			BT_AUDIO_BROADCAST_NAME_LEN_MAX);  | 
 | 205 | +	}  | 
 | 206 | + | 
 | 207 | +	return BTP_STATUS_VAL(err);  | 
 | 208 | +}  | 
 | 209 | + | 
 | 210 | +static uint8_t pbp_broadcast_scan_start(const void *cmd, uint16_t cmd_len, void *rsp,  | 
 | 211 | +					uint16_t *rsp_len)  | 
 | 212 | +{  | 
 | 213 | +	int err;  | 
 | 214 | + | 
 | 215 | +	err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);  | 
 | 216 | +	if (err != 0 && err != -EALREADY) {  | 
 | 217 | +		LOG_DBG("Unable to start scan for broadcast sources: %d", err);  | 
 | 218 | + | 
 | 219 | +		return BTP_STATUS_FAILED;  | 
 | 220 | +	}  | 
 | 221 | + | 
 | 222 | +	return BTP_STATUS_SUCCESS;  | 
 | 223 | +}  | 
 | 224 | + | 
 | 225 | +static uint8_t pbp_broadcast_scan_stop(const void *cmd, uint16_t cmd_len, void *rsp,  | 
 | 226 | +				       uint16_t *rsp_len)  | 
 | 227 | +{  | 
 | 228 | +	int err;  | 
 | 229 | + | 
 | 230 | +	err = bt_le_scan_stop();  | 
 | 231 | +	if (err != 0) {  | 
 | 232 | +		LOG_DBG("Failed to stop scan, %d", err);  | 
 | 233 | + | 
 | 234 | +		return BTP_STATUS_FAILED;  | 
 | 235 | +	}  | 
 | 236 | + | 
 | 237 | +	return BTP_STATUS_SUCCESS;  | 
 | 238 | +}  | 
 | 239 | + | 
 | 240 | +static const struct btp_handler pbp_handlers[] = {  | 
 | 241 | +	{  | 
 | 242 | +		.opcode = BTP_PBP_READ_SUPPORTED_COMMANDS,  | 
 | 243 | +		.index = BTP_INDEX_NONE,  | 
 | 244 | +		.expect_len = 0,  | 
 | 245 | +		.func = pbp_read_supported_commands  | 
 | 246 | +	},  | 
 | 247 | +	{  | 
 | 248 | +		.opcode = BTP_PBP_SET_PUBLIC_BROADCAST_ANNOUNCEMENT,  | 
 | 249 | +		.expect_len = BTP_HANDLER_LENGTH_VARIABLE,  | 
 | 250 | +		.func = pbp_set_public_broadcast_announcement  | 
 | 251 | +	},  | 
 | 252 | +	{  | 
 | 253 | +		.opcode = BTP_PBP_SET_BROADCAST_NAME,  | 
 | 254 | +		.expect_len = BTP_HANDLER_LENGTH_VARIABLE,  | 
 | 255 | +		.func = pbp_set_broadcast_name  | 
 | 256 | +	},  | 
 | 257 | +	{  | 
 | 258 | +		.opcode = BTP_PBP_BROADCAST_SCAN_START,  | 
 | 259 | +		.expect_len = sizeof(struct btp_pbp_broadcast_scan_start_cmd),  | 
 | 260 | +		.func = pbp_broadcast_scan_start  | 
 | 261 | +	},  | 
 | 262 | +	{  | 
 | 263 | +		.opcode = BTP_PBP_BROADCAST_SCAN_STOP,  | 
 | 264 | +		.expect_len = sizeof(struct btp_pbp_broadcast_scan_stop_cmd),  | 
 | 265 | +		.func = pbp_broadcast_scan_stop  | 
 | 266 | +	}  | 
 | 267 | +};  | 
 | 268 | + | 
 | 269 | +uint8_t tester_init_pbp(void)  | 
 | 270 | +{  | 
 | 271 | +	tester_register_command_handlers(BTP_SERVICE_ID_PBP, pbp_handlers,  | 
 | 272 | +					 ARRAY_SIZE(pbp_handlers));  | 
 | 273 | + | 
 | 274 | +	bt_le_scan_cb_register(&pbp_scan_cb);  | 
 | 275 | + | 
 | 276 | +	return BTP_STATUS_SUCCESS;  | 
 | 277 | +}  | 
 | 278 | + | 
 | 279 | +uint8_t tester_unregister_pbp(void)  | 
 | 280 | +{  | 
 | 281 | +	return BTP_STATUS_SUCCESS;  | 
 | 282 | +}  | 
0 commit comments