1 /*
2 * Copyright (c) 2024 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdint.h>
8 #include <stddef.h>
9 #include <stdio.h>
10 #include <string.h>
11
12 #include <zephyr/autoconf.h>
13 #include <zephyr/bluetooth/addr.h>
14 #include <zephyr/bluetooth/audio/tbs.h>
15 #include <zephyr/bluetooth/audio/ccp.h>
16 #include <zephyr/bluetooth/bluetooth.h>
17 #include <zephyr/bluetooth/conn.h>
18 #include <zephyr/bluetooth/gap.h>
19 #include <zephyr/bluetooth/hci_types.h>
20 #include <zephyr/bluetooth/uuid.h>
21 #include <zephyr/kernel.h>
22 #include <zephyr/logging/log.h>
23 #include <zephyr/net_buf.h>
24 #include <zephyr/sys/byteorder.h>
25 #include <zephyr/sys/util.h>
26 #include <zephyr/sys/util_macro.h>
27
28 LOG_MODULE_REGISTER(ccp_call_control_client, CONFIG_LOG_DEFAULT_LEVEL);
29
30 #define SEM_TIMEOUT K_SECONDS(10)
31
32 static struct bt_conn *peer_conn;
33 /* client is not static as it is used for testing purposes */
34 struct bt_ccp_call_control_client *client;
35 static struct bt_ccp_call_control_client_bearers client_bearers;
36
37 static K_SEM_DEFINE(sem_conn_state_change, 0, 1);
38 static K_SEM_DEFINE(sem_security_updated, 0, 1);
39 static K_SEM_DEFINE(sem_ccp_action_completed, 0, 1);
40
connected_cb(struct bt_conn * conn,uint8_t err)41 static void connected_cb(struct bt_conn *conn, uint8_t err)
42 {
43 char addr[BT_ADDR_LE_STR_LEN];
44
45 (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
46 LOG_INF("Connected: %s", addr);
47
48 k_sem_give(&sem_conn_state_change);
49 }
50
disconnected_cb(struct bt_conn * conn,uint8_t reason)51 static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
52 {
53 char addr[BT_ADDR_LE_STR_LEN];
54
55 if (conn != peer_conn) {
56 return;
57 }
58
59 (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
60 LOG_INF("Disconnected: %s (reason 0x%02x)", addr, reason);
61
62 bt_conn_unref(peer_conn);
63 peer_conn = NULL;
64 client = NULL;
65 memset(&client_bearers, 0, sizeof(client_bearers));
66 k_sem_give(&sem_conn_state_change);
67 }
68
security_changed_cb(struct bt_conn * conn,bt_security_t level,enum bt_security_err err)69 static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
70 {
71 if (err == 0) {
72 k_sem_give(&sem_security_updated);
73 } else {
74 LOG_ERR("Failed to set security level: %s(%u)", bt_security_err_to_str(err), err);
75 }
76 }
77
78 BT_CONN_CB_DEFINE(conn_callbacks) = {
79 .connected = connected_cb,
80 .disconnected = disconnected_cb,
81 .security_changed = security_changed_cb,
82 };
83
check_gtbs_support(struct bt_data * data,void * user_data)84 static bool check_gtbs_support(struct bt_data *data, void *user_data)
85 {
86 struct net_buf_simple svc_data;
87 bool *connect = user_data;
88 const struct bt_uuid *uuid;
89 uint16_t uuid_val;
90
91 if (data->type != BT_DATA_SVC_DATA16) {
92 return true; /* Continue parsing to next AD data type */
93 }
94
95 if (data->data_len < sizeof(uuid_val)) {
96 LOG_WRN("AD invalid size %u", data->data_len);
97 return true; /* Continue parsing to next AD data type */
98 }
99
100 net_buf_simple_init_with_data(&svc_data, (void *)data->data, data->data_len);
101
102 /* Pull the 16-bit service data and compare to what we are searching for */
103 uuid_val = net_buf_simple_pull_le16(&svc_data);
104 uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(uuid_val));
105 if (bt_uuid_cmp(uuid, BT_UUID_GTBS) != 0) {
106 /* We are looking for the GTBS service data */
107 return true; /* Continue parsing to next AD data type */
108 }
109
110 *connect = true;
111
112 return false; /* Stop parsing */
113 }
114
scan_recv_cb(const struct bt_le_scan_recv_info * info,struct net_buf_simple * ad)115 static void scan_recv_cb(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
116 {
117 char addr_str[BT_ADDR_LE_STR_LEN];
118 bool connect = false;
119
120 if (peer_conn != NULL) {
121 /* Already connected */
122 return;
123 }
124
125 /* CCP mandates that connectbale extended advertising is used by the peripherals so we
126 * ignore any scan report this is not that.
127 * We also ignore reports with poor RSSI
128 */
129 if (info->adv_type != BT_GAP_ADV_TYPE_EXT_ADV ||
130 (info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) == 0 ||
131 (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) == 0 || info->rssi < -70) {
132 return;
133 }
134
135 (void)bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str));
136 LOG_INF("Connectable device found: %s (RSSI %d)", addr_str, info->rssi);
137
138 /* Iterate on the advertising data to see if claims GTBS support */
139 bt_data_parse(ad, check_gtbs_support, &connect);
140
141 if (connect) {
142 int err;
143
144 err = bt_le_scan_stop();
145 if (err != 0) {
146 LOG_ERR("Scanning failed to stop (err %d)", err);
147 return;
148 }
149
150 LOG_INF("Connecting to found CCP server");
151 err = bt_conn_le_create(info->addr, BT_CONN_LE_CREATE_CONN,
152 BT_LE_CONN_PARAM_DEFAULT, &peer_conn);
153 if (err != 0) {
154 LOG_ERR("Conn create failed: %d", err);
155 }
156 }
157 }
158
scan_and_connect(void)159 static int scan_and_connect(void)
160 {
161 int err;
162
163 err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
164 if (err != 0) {
165 LOG_ERR("Scanning failed to start (err %d)", err);
166 return err;
167 }
168
169 LOG_INF("Scanning successfully started");
170
171 err = k_sem_take(&sem_conn_state_change, K_FOREVER);
172 if (err != 0) {
173 LOG_ERR("failed to take sem_connected (err %d)", err);
174 return err;
175 }
176
177 err = bt_conn_set_security(peer_conn, BT_SECURITY_L2);
178 if (err != 0) {
179 LOG_ERR("failed to set security (err %d)", err);
180 return err;
181 }
182
183 err = k_sem_take(&sem_security_updated, SEM_TIMEOUT);
184 if (err != 0) {
185 LOG_ERR("failed to take sem_security_updated (err %d)", err);
186 return err;
187 }
188
189 LOG_INF("Security successfully updated");
190
191 return 0;
192 }
193
ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client * client,int err,struct bt_ccp_call_control_client_bearers * bearers)194 static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client *client, int err,
195 struct bt_ccp_call_control_client_bearers *bearers)
196 {
197 if (err != 0) {
198 LOG_ERR("Discovery failed: %d", err);
199 return;
200 }
201
202 LOG_INF("Discovery completed with %s%u TBS bearers",
203 bearers->gtbs_bearer != NULL ? "GTBS and " : "", bearers->tbs_count);
204
205 memcpy(&client_bearers, bearers, sizeof(client_bearers));
206
207 k_sem_give(&sem_ccp_action_completed);
208 }
209
reset_ccp_call_control_client(void)210 static int reset_ccp_call_control_client(void)
211 {
212 int err;
213
214 LOG_INF("Resetting");
215
216 if (peer_conn != NULL) {
217 err = bt_conn_disconnect(peer_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
218 if (err != 0) {
219 return err;
220 }
221
222 err = k_sem_take(&sem_conn_state_change, K_FOREVER);
223 if (err != 0) {
224 LOG_ERR("Failed to take sem_conn_state_change: %d", err);
225 return err;
226 }
227 }
228
229 /* If scanning is already stopped it will still return `0` */
230 err = bt_le_scan_stop();
231 if (err != 0) {
232 LOG_ERR("Scanning failed to stop (err %d)", err);
233 return err;
234 }
235
236 k_sem_reset(&sem_conn_state_change);
237
238 return 0;
239 }
240
discover_services(void)241 static int discover_services(void)
242 {
243 int err;
244
245 LOG_INF("Discovering GTBS and TBS");
246
247 err = bt_ccp_call_control_client_discover(peer_conn, &client);
248 if (err != 0) {
249 LOG_ERR("Failed to discover: %d", err);
250 return err;
251 }
252
253 err = k_sem_take(&sem_ccp_action_completed, SEM_TIMEOUT);
254 if (err != 0) {
255 LOG_ERR("Failed to take sem_ccp_action_completed: %d", err);
256 return err;
257 }
258
259 return 0;
260 }
261
init_ccp_call_control_client(void)262 static int init_ccp_call_control_client(void)
263 {
264 static struct bt_le_scan_cb scan_cbs = {
265 .recv = scan_recv_cb,
266 };
267 static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = {
268 .discover = ccp_call_control_client_discover_cb,
269 };
270 int err;
271
272 err = bt_enable(NULL);
273 if (err != 0) {
274 LOG_ERR("Bluetooth enable failed (err %d)", err);
275
276 return err;
277 }
278
279 LOG_DBG("Bluetooth initialized");
280 err = bt_le_scan_cb_register(&scan_cbs);
281 if (err != 0) {
282 LOG_ERR("Bluetooth enable failed (err %d)", err);
283
284 return err;
285 }
286
287 err = bt_ccp_call_control_client_register_cb(&ccp_call_control_client_cbs);
288 if (err != 0) {
289 LOG_ERR("Bluetooth enable failed (err %d)", err);
290
291 return err;
292 }
293
294 return 0;
295 }
296
main(void)297 int main(void)
298 {
299 int err;
300
301 err = init_ccp_call_control_client();
302 if (err != 0) {
303 return 0;
304 }
305
306 LOG_INF("CCP Call Control Client initialized");
307
308 while (true) {
309 err = reset_ccp_call_control_client();
310 if (err != 0) {
311 break;
312 }
313
314 /* Start scanning for CCP servers and connect to the first we find */
315 err = scan_and_connect();
316 if (err != 0) {
317 continue;
318 }
319
320 /* Discover TBS and GTBS on the remove server */
321 err = discover_services();
322 if (err != 0) {
323 continue;
324 }
325
326 /* Reset if disconnected */
327 err = k_sem_take(&sem_conn_state_change, K_FOREVER);
328 if (err != 0) {
329 LOG_ERR("Failed to take sem_conn_state_change: err %d", err);
330 break;
331 }
332 }
333
334 return 0;
335 }
336