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 	struct bt_le_ext_adv *ext_adv;
162 	const struct bt_pacs_register_param pacs_param = {
163 		.snk_pac = true,
164 		.snk_loc = true,
165 		.src_pac = true,
166 		.src_loc = true,
167 	};
168 
169 	LOG_DBG("Enabling Bluetooth");
170 	err = bt_enable(NULL);
171 	if (err != 0) {
172 		FAIL("Bluetooth enable failed (err %d)", err);
173 		return;
174 	}
175 
176 	err = bt_pacs_register(&pacs_param);
177 	if (err) {
178 		FAIL("Could not register PACS (err %d)\n", err);
179 		return;
180 	}
181 
182 	bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
183 	bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
184 	bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
185 	bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
186 
187 	LOG_DBG("Registereding PACS");
188 	bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &caps_1);
189 	bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &caps_1);
190 
191 	err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
192 	if (err != 0) {
193 		LOG_DBG("Failed to set device sink location");
194 		return;
195 	}
196 
197 	err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, BT_AUDIO_LOCATION_FRONT_RIGHT);
198 	if (err != 0) {
199 		LOG_DBG("Failed to set device source location");
200 		return;
201 	}
202 
203 	LOG_DBG("Start Advertising");
204 	setup_connectable_adv(&ext_adv);
205 
206 	LOG_DBG("Waiting to be connected");
207 	WAIT_FOR_FLAG(flag_connected);
208 	LOG_DBG("Connected");
209 	LOG_DBG("Waiting to be subscribed");
210 
211 	while (!is_peer_subscribed(default_conn)) {
212 		(void)k_sleep(K_MSEC(10));
213 	}
214 	LOG_DBG("Subscribed");
215 
216 	LOG_INF("Trigger changes while device is connected");
217 	trigger_notifications();
218 
219 	/* Now wait for client to disconnect */
220 	LOG_DBG("Wait for client disconnect");
221 	WAIT_FOR_UNSET_FLAG(flag_connected);
222 	LOG_DBG("Client disconnected");
223 
224 	LOG_INF("Trigger changes while device is disconnected");
225 	trigger_notifications();
226 
227 	LOG_DBG("Start Advertising");
228 	err = bt_le_ext_adv_start(ext_adv, BT_LE_EXT_ADV_START_DEFAULT);
229 	if (err != 0) {
230 		FAIL("Failed to start advertising set (err %d)\n", err);
231 
232 		bt_le_ext_adv_delete(ext_adv);
233 
234 		return;
235 	}
236 
237 	WAIT_FOR_FLAG(flag_connected);
238 	WAIT_FOR_UNSET_FLAG(flag_connected);
239 	LOG_DBG("Client disconnected");
240 
241 	LOG_DBG("Start Advertising");
242 	err = bt_le_ext_adv_start(ext_adv, BT_LE_EXT_ADV_START_DEFAULT);
243 	if (err != 0) {
244 		FAIL("Failed to start advertising set (err %d)\n", err);
245 
246 		bt_le_ext_adv_delete(ext_adv);
247 
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 	LOG_DBG("Start Advertising");
273 	err = bt_le_ext_adv_start(ext_adv, BT_LE_EXT_ADV_START_DEFAULT);
274 	if (err != 0) {
275 		FAIL("Failed to start advertising set (err %d)\n", err);
276 
277 		bt_le_ext_adv_delete(ext_adv);
278 
279 		return;
280 	}
281 
282 	WAIT_FOR_FLAG(flag_connected);
283 	LOG_DBG("Connected");
284 
285 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK) == available);
286 	__ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
287 			available);
288 
289 	WAIT_FOR_UNSET_FLAG(flag_connected);
290 
291 	PASS("PACS Notify Server passed\n");
292 }
293 
294 static const struct bst_test_instance test_pacs_notify_server[] = {
295 	{
296 		.test_id = "pacs_notify_server",
297 		.test_pre_init_f = test_init,
298 		.test_tick_f = test_tick,
299 		.test_main_f = test_main,
300 	},
301 	BSTEST_END_MARKER,
302 };
303 
test_pacs_notify_server_install(struct bst_test_list * tests)304 struct bst_test_list *test_pacs_notify_server_install(struct bst_test_list *tests)
305 {
306 	return bst_add_tests(tests, test_pacs_notify_server);
307 }
308