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