1 /*
2  * Copyright (c) 2023 Demant A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdbool.h>
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <zephyr/bluetooth/audio/audio.h>
12 #include <zephyr/bluetooth/audio/lc3.h>
13 #include <zephyr/bluetooth/audio/pacs.h>
14 #include <zephyr/bluetooth/bluetooth.h>
15 #include <zephyr/bluetooth/gap.h>
16 #include <zephyr/bluetooth/gatt.h>
17 #include <zephyr/bluetooth/uuid.h>
18 #include <zephyr/kernel.h>
19 #include <zephyr/logging/log.h>
20 #include <zephyr/logging/log_core.h>
21 #include <zephyr/sys/__assert.h>
22 #include <zephyr/sys/util.h>
23 
24 #include "bstests.h"
25 #include "common.h"
26 
27 LOG_MODULE_REGISTER(pacs_notify_server_test, LOG_LEVEL_DBG);
28 
29 extern enum bst_result_t bst_result;
30 
31 static struct bt_audio_codec_cap lc3_codec_1 =
32 	BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_CAP_FREQ_16KHZ | BT_AUDIO_CODEC_CAP_FREQ_24KHZ,
33 			   BT_AUDIO_CODEC_CAP_DURATION_10,
34 			   BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
35 			   BT_AUDIO_CONTEXT_TYPE_ANY);
36 static struct bt_audio_codec_cap lc3_codec_2 =
37 	BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_CAP_FREQ_16KHZ,
38 			   BT_AUDIO_CODEC_CAP_DURATION_10,
39 			   BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
40 			   BT_AUDIO_CONTEXT_TYPE_ANY);
41 static struct bt_pacs_cap                    caps_1 = {
42 	.codec_cap = &lc3_codec_1,
43 };
44 static struct bt_pacs_cap                    caps_2 = {
45 	.codec_cap = &lc3_codec_2,
46 };
47 
is_peer_subscribed(struct bt_conn * conn)48 static bool is_peer_subscribed(struct bt_conn *conn)
49 {
50 	struct bt_gatt_attr *attr;
51 	uint8_t nbr_subscribed = 0;
52 
53 	attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SNK);
54 	if (!attr) {
55 		LOG_DBG("No BT_UUID_PACS_SNK attribute found");
56 	}
57 	if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
58 		nbr_subscribed++;
59 	}
60 
61 	attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SNK_LOC);
62 	if (!attr) {
63 		LOG_DBG("No BT_UUID_PACS_SNK_LOC attribute found");
64 	}
65 	if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
66 		nbr_subscribed++;
67 	}
68 
69 	attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SRC);
70 	if (!attr) {
71 		LOG_DBG("No BT_UUID_PACS_SRC attribute found");
72 	}
73 	if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
74 		nbr_subscribed++;
75 	}
76 
77 	attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SRC_LOC);
78 	if (!attr) {
79 		LOG_DBG("No BT_UUID_PACS_SRC_LOC attribute found");
80 	}
81 	if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
82 		nbr_subscribed++;
83 	}
84 
85 	attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_AVAILABLE_CONTEXT);
86 	if (!attr) {
87 		LOG_DBG("No BT_UUID_PACS_AVAILABLE_CONTEXT attribute found");
88 	}
89 	if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
90 		nbr_subscribed++;
91 	}
92 
93 	attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SUPPORTED_CONTEXT);
94 	if (!attr) {
95 		LOG_DBG("No BT_UUID_PACS_SUPPORTED_CONTEXT attribute found");
96 	}
97 	if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
98 		nbr_subscribed++;
99 	}
100 
101 	if (nbr_subscribed != 6) {
102 		return false;
103 	}
104 
105 	return true;
106 }
107 
trigger_notifications(void)108 static void trigger_notifications(void)
109 {
110 	static enum bt_audio_context available = BT_AUDIO_CONTEXT_TYPE_ANY;
111 	static enum bt_audio_context supported = BT_AUDIO_CONTEXT_TYPE_ANY;
112 	static int i;
113 	int err;
114 	struct bt_pacs_cap *caps;
115 	enum bt_audio_location snk_loc;
116 	enum bt_audio_location src_loc;
117 
118 	LOG_DBG("Triggering Notifications");
119 
120 	if (i) {
121 		caps = &caps_1;
122 		snk_loc = BT_AUDIO_LOCATION_FRONT_LEFT;
123 		src_loc = BT_AUDIO_LOCATION_FRONT_RIGHT;
124 		i = 0;
125 	} else {
126 		caps = &caps_2;
127 		snk_loc = BT_AUDIO_LOCATION_FRONT_RIGHT;
128 		src_loc = BT_AUDIO_LOCATION_FRONT_LEFT;
129 		i++;
130 	}
131 
132 	LOG_DBG("Changing Sink PACs");
133 	bt_pacs_cap_register(BT_AUDIO_DIR_SINK, caps);
134 	bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, caps);
135 
136 	LOG_DBG("Changing Sink Location");
137 	err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, snk_loc);
138 	if (err != 0) {
139 		LOG_DBG("Failed to set device sink location");
140 	}
141 
142 	LOG_DBG("Changing Source Location");
143 	err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, src_loc);
144 	if (err != 0) {
145 		LOG_DBG("Failed to set device source location");
146 	}
147 
148 	LOG_DBG("Changing Supported Contexts Location");
149 	supported = supported ^ BT_AUDIO_CONTEXT_TYPE_MEDIA;
150 	bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, supported);
151 
152 	LOG_DBG("Changing Available Contexts");
153 	available = available ^ BT_AUDIO_CONTEXT_TYPE_MEDIA;
154 	bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, available);
155 }
156 
test_main(void)157 static void test_main(void)
158 {
159 	int err;
160 	enum bt_audio_context available, available_for_conn;
161 	const struct bt_data ad[] = {
162 		BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
163 	};
164 
165 	LOG_DBG("Enabling Bluetooth");
166 	err = bt_enable(NULL);
167 	if (err != 0) {
168 		FAIL("Bluetooth enable failed (err %d)", err);
169 		return;
170 	}
171 
172 	bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
173 	bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
174 	bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
175 	bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
176 
177 	LOG_DBG("Registereding PACS");
178 	bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &caps_1);
179 	bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &caps_1);
180 
181 	err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
182 	if (err != 0) {
183 		LOG_DBG("Failed to set device sink location");
184 		return;
185 	}
186 
187 	err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, BT_AUDIO_LOCATION_FRONT_RIGHT);
188 	if (err != 0) {
189 		LOG_DBG("Failed to set device source location");
190 		return;
191 	}
192 
193 	LOG_DBG("Start Advertising");
194 	err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
195 	if (err != 0) {
196 		FAIL("Advertising failed to start (err %d)", err);
197 		return;
198 	}
199 
200 	LOG_DBG("Waiting to be connected");
201 	WAIT_FOR_FLAG(flag_connected);
202 	LOG_DBG("Connected");
203 	LOG_DBG("Waiting to be subscribed");
204 
205 	while (!is_peer_subscribed(default_conn)) {
206 		(void)k_sleep(K_MSEC(10));
207 	}
208 	LOG_DBG("Subscribed");
209 
210 	LOG_INF("Trigger changes while device is connected");
211 	trigger_notifications();
212 
213 	/* Now wait for client to disconnect, then stop adv so it does not reconnect */
214 	LOG_DBG("Wait for client disconnect");
215 	WAIT_FOR_UNSET_FLAG(flag_connected);
216 	LOG_DBG("Client disconnected");
217 
218 	err = bt_le_adv_stop();
219 	if (err != 0) {
220 		FAIL("Advertising failed to stop (err %d)", err);
221 		return;
222 	}
223 
224 	LOG_INF("Trigger changes while device is disconnected");
225 	trigger_notifications();
226 
227 	LOG_DBG("Start Advertising");
228 	err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
229 	if (err != 0) {
230 		FAIL("Advertising failed to start (err %d)", err);
231 		return;
232 	}
233 
234 	WAIT_FOR_FLAG(flag_connected);
235 	WAIT_FOR_UNSET_FLAG(flag_connected);
236 	LOG_DBG("Client disconnected");
237 
238 	err = bt_le_adv_stop();
239 	if (err != 0) {
240 		FAIL("Advertising failed to stop (err %d)", err);
241 		return;
242 	}
243 
244 	LOG_DBG("Start Advertising");
245 	err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
246 	if (err != 0) {
247 		FAIL("Advertising failed to start (err %d)", err);
248 		return;
249 	}
250 
251 	WAIT_FOR_FLAG(flag_connected);
252 	LOG_DBG("Connected");
253 
254 	available = bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK);
255 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
256 			available);
257 
258 	available_for_conn = BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED;
259 
260 	LOG_INF("Override available contexts");
261 	err = bt_pacs_conn_set_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK,
262 							   &available_for_conn);
263 	__ASSERT_NO_MSG(err == 0);
264 
265 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK) == available);
266 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
267 			available_for_conn);
268 
269 	WAIT_FOR_UNSET_FLAG(flag_connected);
270 	LOG_DBG("Client disconnected");
271 
272 	err = bt_le_adv_stop();
273 	if (err != 0) {
274 		FAIL("Advertising failed to stop (err %d)", err);
275 		return;
276 	}
277 
278 	LOG_DBG("Start Advertising");
279 	err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
280 	if (err != 0) {
281 		FAIL("Advertising failed to start (err %d)", err);
282 		return;
283 	}
284 
285 	WAIT_FOR_FLAG(flag_connected);
286 	LOG_DBG("Connected");
287 
288 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK) == available);
289 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
290 			available);
291 
292 	WAIT_FOR_UNSET_FLAG(flag_connected);
293 
294 	PASS("PACS Notify Server passed\n");
295 }
296 
297 static const struct bst_test_instance test_pacs_notify_server[] = {
298 	{
299 		.test_id = "pacs_notify_server",
300 		.test_pre_init_f = test_init,
301 		.test_tick_f = test_tick,
302 		.test_main_f = test_main,
303 	},
304 	BSTEST_END_MARKER,
305 };
306 
test_pacs_notify_server_install(struct bst_test_list * tests)307 struct bst_test_list *test_pacs_notify_server_install(struct bst_test_list *tests)
308 {
309 	return bst_add_tests(tests, test_pacs_notify_server);
310 }
311