1 /*
2  * Copyright (c) 2021 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <ctype.h>
8 #include <zephyr/bluetooth/bluetooth.h>
9 #include <zephyr/bluetooth/conn.h>
10 #include <zephyr/bluetooth/iso.h>
11 #include <zephyr/sys/byteorder.h>
12 #include <zephyr/console/console.h>
13 
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(iso_broadcast_receiver, LOG_LEVEL_DBG);
16 
17 #define DEVICE_NAME	CONFIG_BT_DEVICE_NAME
18 #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME))
19 
20 #define PA_RETRY_COUNT 6
21 #define ISO_RETRY_COUNT 10
22 
23 struct iso_recv_stats {
24 	uint32_t     iso_recv_count;
25 	uint32_t     iso_lost_count;
26 };
27 
28 static bool         broadcaster_found;
29 static bool         per_adv_lost;
30 static bool         big_sync_lost;
31 static bool         biginfo_received;
32 static bt_addr_le_t per_addr;
33 static uint8_t      per_sid;
34 static uint32_t     per_interval_us;
35 static uint32_t     iso_interval_us;
36 static uint8_t      bis_count;
37 static uint32_t     last_received_counter;
38 static int64_t      big_sync_start_time;
39 static size_t       big_sync_count;
40 
41 static struct iso_recv_stats stats_current_sync;
42 static struct iso_recv_stats stats_overall;
43 
44 static K_SEM_DEFINE(sem_per_adv, 0, 1);
45 static K_SEM_DEFINE(sem_per_sync, 0, 1);
46 static K_SEM_DEFINE(sem_per_sync_lost, 0, 1);
47 static K_SEM_DEFINE(sem_per_big_info, 0, 1);
48 static K_SEM_DEFINE(sem_big_sync, 0, 1);
49 static K_SEM_DEFINE(sem_big_sync_lost, 0, 1);
50 
phy2str(uint8_t phy)51 static const char *phy2str(uint8_t phy)
52 {
53 	switch (phy) {
54 	case 0: return "No packets";
55 	case BT_GAP_LE_PHY_1M: return "LE 1M";
56 	case BT_GAP_LE_PHY_2M: return "LE 2M";
57 	case BT_GAP_LE_PHY_CODED: return "LE Coded";
58 	default: return "Unknown";
59 	}
60 }
61 
data_cb(struct bt_data * data,void * user_data)62 static bool data_cb(struct bt_data *data, void *user_data)
63 {
64 	char *name = user_data;
65 	uint8_t len;
66 
67 	switch (data->type) {
68 	case BT_DATA_NAME_SHORTENED:
69 	case BT_DATA_NAME_COMPLETE:
70 		len = MIN(data->data_len, DEVICE_NAME_LEN - 1);
71 		memcpy(name, data->data, len);
72 		name[len] = '\0';
73 		return false;
74 	default:
75 		return true;
76 	}
77 }
78 
scan_recv(const struct bt_le_scan_recv_info * info,struct net_buf_simple * buf)79 static void scan_recv(const struct bt_le_scan_recv_info *info,
80 		      struct net_buf_simple *buf)
81 {
82 	char le_addr[BT_ADDR_LE_STR_LEN];
83 	char name[DEVICE_NAME_LEN];
84 
85 	if (broadcaster_found) {
86 		return;
87 	}
88 
89 	if (info->interval == 0U) {
90 		/* Not broadcast periodic advertising - Ignore */
91 		return;
92 	}
93 
94 	(void)memset(name, 0, sizeof(name));
95 
96 	bt_data_parse(buf, data_cb, name);
97 
98 	if (strncmp(DEVICE_NAME, name, strlen(DEVICE_NAME))) {
99 		return;
100 	}
101 
102 	bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
103 
104 	LOG_INF("Found broadcaster with address %s (RSSI %i)",
105 		le_addr, info->rssi);
106 
107 	broadcaster_found = true;
108 
109 	per_sid = info->sid;
110 	per_interval_us = BT_CONN_INTERVAL_TO_US(info->interval);
111 	bt_addr_le_copy(&per_addr, info->addr);
112 
113 	k_sem_give(&sem_per_adv);
114 }
115 
116 static struct bt_le_scan_cb scan_callbacks = {
117 	.recv = scan_recv,
118 };
119 
sync_cb(struct bt_le_per_adv_sync * sync,struct bt_le_per_adv_sync_synced_info * info)120 static void sync_cb(struct bt_le_per_adv_sync *sync,
121 		    struct bt_le_per_adv_sync_synced_info *info)
122 {
123 	LOG_INF("Periodic advertisement synced");
124 
125 	k_sem_give(&sem_per_sync);
126 }
127 
term_cb(struct bt_le_per_adv_sync * sync,const struct bt_le_per_adv_sync_term_info * info)128 static void term_cb(struct bt_le_per_adv_sync *sync,
129 		    const struct bt_le_per_adv_sync_term_info *info)
130 {
131 	LOG_INF("Periodic advertisement sync terminated");
132 
133 	per_adv_lost = true;
134 	k_sem_give(&sem_per_sync_lost);
135 }
136 
biginfo_cb(struct bt_le_per_adv_sync * sync,const struct bt_iso_biginfo * biginfo)137 static void biginfo_cb(struct bt_le_per_adv_sync *sync,
138 		       const struct bt_iso_biginfo *biginfo)
139 {
140 	if (biginfo_received) {
141 		return;
142 	}
143 
144 	LOG_INF("BIGinfo received: num_bis %u, nse %u, interval %u us, "
145 		"bn %u, pto %u, irc %u, max_pdu %u, sdu_interval %u us, "
146 		"max_sdu %u, phy %s, %s framing, %sencrypted",
147 		biginfo->num_bis, biginfo->sub_evt_count,
148 		BT_CONN_INTERVAL_TO_US(biginfo->iso_interval),
149 		biginfo->burst_number, biginfo->offset, biginfo->rep_count,
150 		biginfo->max_pdu, biginfo->sdu_interval, biginfo->max_sdu,
151 		phy2str(biginfo->phy), biginfo->framing ? "with" : "without",
152 		biginfo->encryption ? "" : "not ");
153 
154 	iso_interval_us = BT_CONN_INTERVAL_TO_US(biginfo->iso_interval);
155 	bis_count = MIN(biginfo->num_bis, CONFIG_BT_ISO_MAX_CHAN);
156 	biginfo_received = true;
157 	k_sem_give(&sem_per_big_info);
158 }
159 
160 static struct bt_le_per_adv_sync_cb sync_callbacks = {
161 	.synced = sync_cb,
162 	.term = term_cb,
163 	.biginfo = biginfo_cb,
164 };
165 
print_stats(char * name,struct iso_recv_stats * stats)166 static void print_stats(char *name, struct iso_recv_stats *stats)
167 {
168 	uint32_t total_packets;
169 
170 	total_packets = stats->iso_recv_count + stats->iso_lost_count;
171 
172 	LOG_INF("%s: Received %u/%u (%.2f%%) - Total packets lost %u",
173 		name, stats->iso_recv_count, total_packets,
174 		(float)stats->iso_recv_count * 100 / total_packets,
175 		stats->iso_lost_count);
176 
177 }
178 
iso_recv(struct bt_iso_chan * chan,const struct bt_iso_recv_info * info,struct net_buf * buf)179 static void iso_recv(struct bt_iso_chan *chan,
180 		     const struct bt_iso_recv_info *info,
181 		     struct net_buf *buf)
182 {
183 	uint32_t total_packets;
184 	static bool stats_latest_arr[1000];
185 	static size_t stats_latest_arr_pos;
186 
187 	/* NOTE: The packets received may be on different BISes */
188 
189 	if (info->flags & BT_ISO_FLAGS_VALID) {
190 		stats_current_sync.iso_recv_count++;
191 		stats_overall.iso_recv_count++;
192 		stats_latest_arr[stats_latest_arr_pos++] = true;
193 	} else {
194 		stats_current_sync.iso_lost_count++;
195 		stats_overall.iso_lost_count++;
196 		stats_latest_arr[stats_latest_arr_pos++] = false;
197 	}
198 
199 	if (stats_latest_arr_pos == sizeof(stats_latest_arr)) {
200 		stats_latest_arr_pos = 0;
201 	}
202 
203 	total_packets = stats_overall.iso_recv_count + stats_overall.iso_lost_count;
204 
205 	if ((total_packets % 100) == 0) {
206 		struct iso_recv_stats stats_latest = { 0 };
207 
208 		for (int i = 0; i < ARRAY_SIZE(stats_latest_arr); i++) {
209 			/* If we have not yet received 1000 packets, break
210 			 * early
211 			 */
212 			if (i == total_packets) {
213 				break;
214 			}
215 
216 			if (stats_latest_arr[i]) {
217 				stats_latest.iso_recv_count++;
218 			} else {
219 				stats_latest.iso_lost_count++;
220 			}
221 		}
222 
223 		print_stats("Overall     ", &stats_overall);
224 		print_stats("Current Sync", &stats_current_sync);
225 		print_stats("Latest 1000 ", &stats_latest);
226 		LOG_INF(""); /* Empty line to separate the stats */
227 	}
228 }
229 
iso_connected(struct bt_iso_chan * chan)230 static void iso_connected(struct bt_iso_chan *chan)
231 {
232 	LOG_INF("ISO Channel %p connected", chan);
233 
234 	big_sync_start_time = k_uptime_get();
235 
236 	k_sem_give(&sem_big_sync);
237 }
238 
iso_disconnected(struct bt_iso_chan * chan,uint8_t reason)239 static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
240 {
241 	/* Calculate cumulative moving average - Be aware that this may
242 	 * cause overflow at sufficiently large counts or durations
243 	 */
244 	static int64_t average_duration;
245 	uint64_t big_sync_duration = k_uptime_get() - big_sync_start_time;
246 	uint64_t total_duration = big_sync_duration + (big_sync_count - 1) * average_duration;
247 
248 	average_duration = total_duration / big_sync_count;
249 
250 	LOG_INF("ISO Channel %p disconnected with reason 0x%02x after "
251 		"%llu milliseconds (average duration %llu)",
252 		chan, reason, big_sync_duration, average_duration);
253 
254 	big_sync_lost = true;
255 
256 	k_sem_give(&sem_big_sync_lost);
257 }
258 
259 static struct bt_iso_chan_ops iso_ops = {
260 	.recv		= iso_recv,
261 	.connected	= iso_connected,
262 	.disconnected	= iso_disconnected,
263 };
264 
265 static struct bt_iso_chan_io_qos iso_rx_qos;
266 
267 static struct bt_iso_chan_qos bis_iso_qos = {
268 	.rx = &iso_rx_qos,
269 };
270 
271 
start_scan(void)272 static int start_scan(void)
273 {
274 	int err;
275 
276 	err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
277 	if (err != 0) {
278 		LOG_ERR("Scan start failed (err %d)", err);
279 		return err;
280 	}
281 
282 	LOG_INF("Scan started");
283 
284 	return 0;
285 }
286 
stop_scan(void)287 static int stop_scan(void)
288 {
289 	int err;
290 
291 	err = bt_le_scan_stop();
292 	if (err != 0) {
293 		LOG_ERR("Scan stop failed (err %d)", err);
294 		return err;
295 	}
296 	LOG_INF("Scan stopped");
297 
298 	return 0;
299 }
300 
create_pa_sync(struct bt_le_per_adv_sync ** sync)301 static int create_pa_sync(struct bt_le_per_adv_sync **sync)
302 {
303 	struct bt_le_per_adv_sync_param sync_create_param;
304 	int err;
305 	uint32_t sem_timeout_us;
306 
307 	LOG_INF("Creating Periodic Advertising Sync");
308 	bt_addr_le_copy(&sync_create_param.addr, &per_addr);
309 	sync_create_param.options = 0;
310 	sync_create_param.sid = per_sid;
311 	sync_create_param.skip = 0;
312 	/* Multiple PA interval with retry count and convert to unit of 10 ms */
313 	sync_create_param.timeout = (per_interval_us * PA_RETRY_COUNT) / (10 * USEC_PER_MSEC);
314 	sem_timeout_us = per_interval_us * PA_RETRY_COUNT;
315 	err = bt_le_per_adv_sync_create(&sync_create_param, sync);
316 	if (err != 0) {
317 		LOG_ERR("Periodic advertisement sync create failed (err %d)",
318 		       err);
319 		return err;
320 	}
321 
322 	LOG_INF("Waiting for periodic sync");
323 	err = k_sem_take(&sem_per_sync, K_USEC(sem_timeout_us));
324 	if (err != 0) {
325 		LOG_INF("failed to take sem_per_sync (err %d)", err);
326 
327 		LOG_INF("Deleting Periodic Advertising Sync");
328 		err = bt_le_per_adv_sync_delete(*sync);
329 		if (err != 0) {
330 			LOG_ERR("Failed to delete Periodic advertisement sync (err %d)",
331 			       err);
332 
333 			return err;
334 		}
335 	}
336 	LOG_INF("Periodic sync established");
337 
338 	return 0;
339 }
340 
create_big_sync(struct bt_iso_big ** big,struct bt_le_per_adv_sync * sync)341 static int create_big_sync(struct bt_iso_big **big, struct bt_le_per_adv_sync *sync)
342 {
343 	int err;
344 	uint32_t sem_timeout_us = per_interval_us * PA_RETRY_COUNT;
345 	uint32_t sync_timeout_us;
346 	static struct bt_iso_chan bis_iso_chan[CONFIG_BT_ISO_MAX_CHAN];
347 	struct bt_iso_chan *bis[CONFIG_BT_ISO_MAX_CHAN];
348 	struct bt_iso_big_sync_param big_sync_param = {
349 		.bis_channels = bis,
350 		.num_bis = 0,
351 		.bis_bitfield = 0,
352 		.mse = 0,
353 		.sync_timeout = 0
354 	};
355 
356 	for (int i = 0; i < ARRAY_SIZE(bis_iso_chan); i++) {
357 		bis_iso_chan[i].ops = &iso_ops;
358 		bis_iso_chan[i].qos = &bis_iso_qos;
359 		bis[i] = &bis_iso_chan[i];
360 	}
361 
362 	LOG_INF("Waiting for BIG info");
363 	err = k_sem_take(&sem_per_big_info, K_USEC(sem_timeout_us));
364 	if (err != 0) {
365 		LOG_ERR("failed to take sem_per_big_info (err %d)", err);
366 		return err;
367 	}
368 
369 	sync_timeout_us = iso_interval_us * ISO_RETRY_COUNT;
370 	/* timeout is in 10 ms units */
371 	big_sync_param.sync_timeout = CLAMP(sync_timeout_us / (10 * USEC_PER_MSEC),
372 					    BT_ISO_SYNC_TIMEOUT_MIN,
373 					    BT_ISO_SYNC_TIMEOUT_MAX);
374 	big_sync_param.num_bis = bis_count;
375 	/* BIS indexes start from 0x01, so add one to `i` */
376 	for (int i = 1; i <= big_sync_param.num_bis; i++) {
377 		big_sync_param.bis_bitfield |= BIT(i);
378 	}
379 
380 	LOG_INF("Syncing to BIG");
381 	err = bt_iso_big_sync(sync, &big_sync_param, big);
382 	if (err != 0) {
383 		LOG_ERR("BIG sync failed (err %d)", err);
384 		return err;
385 	}
386 
387 	LOG_INF("Waiting for BIG sync");
388 	err = k_sem_take(&sem_big_sync, K_USEC(sem_timeout_us));
389 	if (err != 0) {
390 		LOG_ERR("failed to take sem_big_sync (err %d)", err);
391 		return err;
392 	}
393 	LOG_INF("BIG sync established");
394 
395 	big_sync_count++;
396 
397 	return 0;
398 }
399 
cleanup(struct bt_le_per_adv_sync * sync,struct bt_iso_big * big)400 static int cleanup(struct bt_le_per_adv_sync *sync, struct bt_iso_big *big)
401 {
402 	int pa_err = 0;
403 	int big_err = 0;
404 
405 	if (!per_adv_lost && sync) {
406 		LOG_INF("Deleting Periodic advertisement Sync");
407 		pa_err = bt_le_per_adv_sync_delete(sync);
408 		if (pa_err != 0) {
409 			LOG_ERR("Failed to delete Periodic advertisement sync (err %d)",
410 				pa_err);
411 		}
412 	}
413 
414 	if (!big_sync_lost && big) {
415 		LOG_INF("Terminating BIG Sync");
416 		big_err = bt_iso_big_terminate(big);
417 		if (big_err != 0) {
418 			LOG_ERR("BIG terminate failed (err %d)", big_err);
419 		}
420 	}
421 
422 	if (pa_err != 0 || big_err != 0) {
423 		LOG_ERR("Cleanup failed (%d), recommend restart application to "
424 		       "avoid any potential leftovers",
425 		       pa_err != 0 ? pa_err : big_err);
426 	}
427 
428 	return pa_err != 0 ? pa_err : big_err;
429 }
430 
reset_sems(void)431 static void reset_sems(void)
432 {
433 	(void)k_sem_reset(&sem_per_adv);
434 	(void)k_sem_reset(&sem_per_sync);
435 	(void)k_sem_reset(&sem_per_sync_lost);
436 	(void)k_sem_reset(&sem_per_big_info);
437 	(void)k_sem_reset(&sem_big_sync);
438 	(void)k_sem_reset(&sem_big_sync_lost);
439 }
440 
test_run_receiver(void)441 int test_run_receiver(void)
442 {
443 	struct bt_le_per_adv_sync *sync = NULL;
444 	struct bt_iso_big *big = NULL;
445 	int err;
446 	static bool callbacks_registered;
447 
448 	broadcaster_found = false;
449 	per_adv_lost = false;
450 	big_sync_lost = false;
451 	biginfo_received = false;
452 	reset_sems();
453 
454 	if (!callbacks_registered) {
455 		bt_le_per_adv_sync_cb_register(&sync_callbacks);
456 		bt_le_scan_cb_register(&scan_callbacks);
457 		callbacks_registered = true;
458 	}
459 
460 	err = start_scan();
461 	if (err != 0) {
462 		return err;
463 	}
464 
465 	LOG_INF("Waiting for periodic advertiser");
466 	err = k_sem_take(&sem_per_adv, K_FOREVER);
467 	if (err != 0) {
468 		LOG_ERR("failed to take sem_per_adv (err %d)", err);
469 		return err;
470 	}
471 	LOG_INF("Periodic advertiser found");
472 
473 	err = stop_scan();
474 	if (err != 0) {
475 		return err;
476 	}
477 
478 	err = create_pa_sync(&sync);
479 	if (err != 0) {
480 		return err;
481 	}
482 
483 	last_received_counter = 0;
484 	memset(&stats_current_sync, 0, sizeof(stats_current_sync));
485 	big_sync_start_time = 0;
486 
487 	err = create_big_sync(&big, sync);
488 	if (err != 0) {
489 		(void)cleanup(sync, big);
490 		return err;
491 	}
492 
493 	err = k_sem_take(&sem_big_sync_lost, K_FOREVER);
494 	if (err != 0) {
495 		LOG_ERR("failed to take sem_big_sync_lost (err %d)", err);
496 		return err;
497 	}
498 	LOG_INF("BIG sync lost, returning");
499 	/* TODO: Add support to cancel the test early */
500 
501 	return cleanup(sync, big);
502 }
503