1 /*
2  * Copyright (c) 2023 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <errno.h>
7 #include <stdbool.h>
8 #include <stddef.h>
9 #include <stdint.h>
10 #include <string.h>
11 #include <sys/types.h>
12 
13 #include <zephyr/autoconf.h>
14 #include <zephyr/bluetooth/audio/gmap.h>
15 #include <zephyr/bluetooth/conn.h>
16 #include <zephyr/bluetooth/gatt.h>
17 #include <zephyr/bluetooth/uuid.h>
18 #include <zephyr/logging/log.h>
19 #include <zephyr/sys/check.h>
20 #include <zephyr/sys/util.h>
21 #include <zephyr/sys/util_macro.h>
22 
23 #include "audio_internal.h"
24 
25 LOG_MODULE_REGISTER(bt_gmap_server, CONFIG_BT_GMAP_LOG_LEVEL);
26 
27 #define BT_GMAP_ROLE_MASK                                                                          \
28 	(BT_GMAP_ROLE_UGG | BT_GMAP_ROLE_UGT | BT_GMAP_ROLE_BGS | BT_GMAP_ROLE_BGR)
29 
30 static uint8_t gmap_role;
31 static struct bt_gmap_feat gmap_features;
32 
read_gmap_role(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)33 static ssize_t read_gmap_role(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
34 			      uint16_t len, uint16_t offset)
35 {
36 	LOG_DBG("role 0x%02X", gmap_role);
37 
38 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &gmap_role, sizeof(gmap_role));
39 }
40 
41 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
read_gmap_ugg_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)42 static ssize_t read_gmap_ugg_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
43 				  uint16_t len, uint16_t offset)
44 {
45 	const uint8_t feat = (uint8_t)gmap_features.ugg_feat;
46 
47 	LOG_DBG("feat 0x%02X", feat);
48 
49 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
50 }
51 
52 static const struct bt_gatt_attr ugg_feat_chrc[] = {
53 	BT_AUDIO_CHRC(BT_UUID_GMAP_UGG_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
54 		      read_gmap_ugg_feat, NULL, NULL),
55 };
56 #endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
57 
58 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
read_gmap_ugt_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)59 static ssize_t read_gmap_ugt_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
60 				  uint16_t len, uint16_t offset)
61 {
62 	const uint8_t feat = (uint8_t)gmap_features.ugt_feat;
63 
64 	LOG_DBG("feat 0x%02X", feat);
65 
66 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
67 }
68 
69 static const struct bt_gatt_attr ugt_feat_chrc[] = {
70 	BT_AUDIO_CHRC(BT_UUID_GMAP_UGT_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
71 		      read_gmap_ugt_feat, NULL, NULL),
72 };
73 
74 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
75 
76 #if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
read_gmap_bgs_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)77 static ssize_t read_gmap_bgs_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
78 				  uint16_t len, uint16_t offset)
79 {
80 	const uint8_t feat = (uint8_t)gmap_features.bgs_feat;
81 
82 	LOG_DBG("feat 0x%02X", feat);
83 
84 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
85 }
86 
87 static const struct bt_gatt_attr bgs_feat_chrc[] = {
88 	BT_AUDIO_CHRC(BT_UUID_GMAP_BGS_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
89 		      read_gmap_bgs_feat, NULL, NULL),
90 };
91 #endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
92 
93 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
read_gmap_bgr_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)94 static ssize_t read_gmap_bgr_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
95 				  uint16_t len, uint16_t offset)
96 {
97 	const uint8_t feat = (uint8_t)gmap_features.bgr_feat;
98 
99 	LOG_DBG("feat 0x%02X", feat);
100 
101 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
102 }
103 
104 static const struct bt_gatt_attr bgr_feat_chrc[] = {
105 	BT_AUDIO_CHRC(BT_UUID_GMAP_BGR_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
106 		      read_gmap_bgr_feat, NULL, NULL),
107 };
108 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
109 
110 /* There are 4 optional characteristics - Use a dummy definition to allocate memory and then add the
111  * characteristics at will when registering or modifying the role(s)
112  */
113 #define GMAS_DUMMY_CHRC BT_AUDIO_CHRC(0, 0, 0, NULL, NULL, NULL)
114 
115 /* Gaming Audio Service attributes */
116 static struct bt_gatt_attr svc_attrs[] = {
117 	BT_GATT_PRIMARY_SERVICE(BT_UUID_GMAS),
118 	BT_AUDIO_CHRC(BT_UUID_GMAP_ROLE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
119 		      read_gmap_role, NULL, NULL),
120 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
121 	GMAS_DUMMY_CHRC,
122 #endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
123 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
124 	GMAS_DUMMY_CHRC,
125 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
126 #if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
127 	GMAS_DUMMY_CHRC,
128 #endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
129 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
130 	GMAS_DUMMY_CHRC,
131 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
132 };
133 static struct bt_gatt_service gmas;
134 
valid_gmap_role(enum bt_gmap_role role)135 static bool valid_gmap_role(enum bt_gmap_role role)
136 {
137 	if (role == 0 || (role & BT_GMAP_ROLE_MASK) != role) {
138 		LOG_DBG("Invalid role %d", role);
139 	}
140 
141 	if ((role & BT_GMAP_ROLE_UGG) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_UGG_SUPPORTED)) {
142 		LOG_DBG("Device does not support the UGG role");
143 
144 		return false;
145 	}
146 
147 	if ((role & BT_GMAP_ROLE_UGT) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_UGT_SUPPORTED)) {
148 		LOG_DBG("Device does not support the UGT role");
149 
150 		return false;
151 	}
152 
153 	if ((role & BT_GMAP_ROLE_BGS) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGS_SUPPORTED)) {
154 		LOG_DBG("Device does not support the BGS role");
155 
156 		return false;
157 	}
158 
159 	if ((role & BT_GMAP_ROLE_BGR) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED)) {
160 		LOG_DBG("Device does not support the BGR role");
161 
162 		return false;
163 	}
164 
165 	return true;
166 }
167 
valid_gmap_features(enum bt_gmap_role role,struct bt_gmap_feat features)168 static bool valid_gmap_features(enum bt_gmap_role role, struct bt_gmap_feat features)
169 {
170 	/* Guard with BT_GMAP_UGG_SUPPORTED as the Kconfigs may not be available without it*/
171 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
172 	if ((role & BT_GMAP_ROLE_UGG) != 0) {
173 		enum bt_gmap_ugg_feat ugg_feat = features.ugg_feat;
174 
175 		if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTIPLEX) != 0 &&
176 		    CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
177 			LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTIPLEX with "
178 				"CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0");
179 
180 			return false;
181 		}
182 
183 		if ((ugg_feat & BT_GMAP_UGG_FEAT_96KBPS_SOURCE) != 0 &&
184 		    CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
185 			LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_96KBPS_SOURCE with "
186 				"CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0");
187 
188 			return false;
189 		}
190 
191 		if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTISINK) != 0 &&
192 		    (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT < 2 ||
193 		     CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2)) {
194 			LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTISINK with "
195 				"CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT (%d) or "
196 				"CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d) < 2",
197 				CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT,
198 				CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT);
199 
200 			return false;
201 		}
202 	}
203 #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
204 
205 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
206 	if ((role & BT_GMAP_ROLE_UGT) != 0) {
207 		enum bt_gmap_ugt_feat ugt_feat = features.ugt_feat;
208 		enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat;
209 
210 		if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 &&
211 		    (ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0) {
212 			LOG_DBG("Device shall support either BT_GMAP_UGT_FEAT_SOURCE or "
213 				"BT_GMAP_UGT_FEAT_SINK");
214 
215 			return false;
216 		}
217 
218 		if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 &&
219 		    ((ugt_feat & BT_GMAP_UGT_FEAT_80KBPS_SOURCE) != 0 ||
220 		     (ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0)) {
221 			LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SOURCE if "
222 				"BT_GMAP_UGT_FEAT_80KBPS_SOURCE or BT_GMAP_UGT_FEAT_MULTISOURCE is "
223 				"supported");
224 
225 			return false;
226 		}
227 
228 		if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) != 0 &&
229 		    CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0) {
230 			LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SOURCE with "
231 				"CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0");
232 
233 			return false;
234 		}
235 
236 		if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0 &&
237 		    (CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT < 2 || CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) {
238 			LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISOURCE with "
239 				"CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT (%d) or "
240 				"CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2",
241 				CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT, CONFIG_BT_ASCS_MAX_ACTIVE_ASES);
242 
243 			return false;
244 		}
245 
246 		if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) != 0
247 		     && CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT == 0) {
248 			LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SINK with "
249 				"CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT == 0");
250 
251 			return false;
252 		}
253 
254 		if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0 &&
255 		    ((ugt_feat & BT_GMAP_UGT_FEAT_64KBPS_SINK) != 0 ||
256 		     (ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 ||
257 		     (ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) != 0)) {
258 			LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SINK if "
259 				"BT_GMAP_UGT_FEAT_64KBPS_SINK, BT_GMAP_UGT_FEAT_MULTISINK or "
260 				"BT_GMAP_UGT_FEAT_MULTIPLEX is supported");
261 
262 			return false;
263 		}
264 
265 		if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 &&
266 		    (CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT < 2 || CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) {
267 			LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISINK with "
268 				"CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT (%d) or "
269 				"CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2",
270 				CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT, CONFIG_BT_ASCS_MAX_ACTIVE_ASES);
271 
272 			return false;
273 		}
274 
275 		/* If the device supports both the UGT and BGT roles, then it needs have the same
276 		 * support for multiplexing for both roles
277 		 */
278 		if ((role & BT_GMAP_ROLE_BGR) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED) &&
279 		    (ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) !=
280 			    (bgr_feat & BT_GMAP_BGR_FEAT_MULTIPLEX)) {
281 			LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_MULTIPLEX if "
282 				"BT_GMAP_BGR_FEAT_MULTIPLEX is supported, and vice versa");
283 		}
284 	}
285 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
286 
287 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
288 	if ((role & BT_GMAP_ROLE_BGR) != 0) {
289 		enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat;
290 
291 		if ((bgr_feat & BT_GMAP_BGR_FEAT_MULTISINK) != 0 &&
292 		    CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT < 2) {
293 			LOG_DBG("Cannot support BT_GMAP_BGR_FEAT_MULTISINK with "
294 				"CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT (%d) < 2",
295 				CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT);
296 
297 			return false;
298 		}
299 	}
300 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
301 
302 	/* If the roles are not supported, then the feature characteristics are not instantiated and
303 	 * the feature values do not need to be checked, as they will never be read (thus ignore by
304 	 * the stack)
305 	 */
306 
307 	return true;
308 }
309 
update_service(enum bt_gmap_role role)310 static void update_service(enum bt_gmap_role role)
311 {
312 	gmas.attrs = svc_attrs;
313 	gmas.attr_count = 3; /* service + 2 attributes for BT_UUID_GMAP_ROLE */
314 
315 	/* Add characteristics based on the role selected and what is supported */
316 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
317 	if (role & BT_GMAP_ROLE_UGG) {
318 		memcpy(&gmas.attrs[gmas.attr_count], ugg_feat_chrc, sizeof(ugg_feat_chrc));
319 		gmas.attr_count += ARRAY_SIZE(ugg_feat_chrc);
320 	}
321 #endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
322 
323 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
324 	if (role & BT_GMAP_ROLE_UGT) {
325 		memcpy(&gmas.attrs[gmas.attr_count], ugt_feat_chrc, sizeof(ugt_feat_chrc));
326 		gmas.attr_count += ARRAY_SIZE(ugt_feat_chrc);
327 	}
328 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
329 
330 #if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
331 	if (role & BT_GMAP_ROLE_BGS) {
332 		memcpy(&gmas.attrs[gmas.attr_count], bgs_feat_chrc, sizeof(bgs_feat_chrc));
333 		gmas.attr_count += ARRAY_SIZE(bgs_feat_chrc);
334 	}
335 #endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
336 
337 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
338 	if (role & BT_GMAP_ROLE_BGR) {
339 		memcpy(&gmas.attrs[gmas.attr_count], bgr_feat_chrc, sizeof(bgr_feat_chrc));
340 		gmas.attr_count += ARRAY_SIZE(bgr_feat_chrc);
341 	}
342 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
343 }
344 
bt_gmap_register(enum bt_gmap_role role,struct bt_gmap_feat features)345 int bt_gmap_register(enum bt_gmap_role role, struct bt_gmap_feat features)
346 {
347 	int err;
348 
349 	CHECKIF(!valid_gmap_role(role)) {
350 		LOG_DBG("Invalid role: %d", role);
351 
352 		return -EINVAL;
353 	}
354 
355 	CHECKIF(!valid_gmap_features(role, features)) {
356 		LOG_DBG("Invalid features");
357 
358 		return -EINVAL;
359 	}
360 
361 	update_service(role);
362 
363 	err = bt_gatt_service_register(&gmas);
364 	if (err) {
365 		LOG_DBG("Could not register the GMAS service");
366 
367 		return -ENOEXEC;
368 	}
369 
370 	gmap_role = role;
371 	gmap_features = features;
372 
373 	return 0;
374 }
375 
bt_gmap_set_role(enum bt_gmap_role role,struct bt_gmap_feat features)376 int bt_gmap_set_role(enum bt_gmap_role role, struct bt_gmap_feat features)
377 {
378 	int err;
379 
380 	if (gmap_role == 0) { /* not registered if this is 0 */
381 		LOG_DBG("GMAP not registered");
382 
383 		return -ENOEXEC;
384 	}
385 
386 	CHECKIF(!valid_gmap_role(role)) {
387 		LOG_DBG("Invalid role: %d", role);
388 
389 		return -EINVAL;
390 	}
391 
392 	CHECKIF(!valid_gmap_features(role, features)) {
393 		LOG_DBG("Invalid features");
394 
395 		return -EINVAL;
396 	}
397 
398 	if (gmap_role == role) {
399 		LOG_DBG("No role change");
400 
401 		if (memcmp(&gmap_features, &features, sizeof(gmap_features)) == 0) {
402 			LOG_DBG("No feature change");
403 
404 			return -EALREADY;
405 		}
406 
407 		gmap_features = features;
408 
409 		return 0;
410 	}
411 
412 	/* Re-register the service to trigger a db_changed() if the roles changed */
413 	err = bt_gatt_service_unregister(&gmas);
414 	if (err != 0) {
415 		LOG_DBG("Failed to unregister service: %d", err);
416 
417 		return -ENOENT;
418 	}
419 
420 	err = bt_gmap_register(role, gmap_features);
421 	if (err != 0) {
422 		LOG_DBG("Failed to update GMAS: %d", err);
423 
424 		return -ECANCELED;
425 	}
426 
427 	return 0;
428 }
429