1 /*  Bluetooth VOCS - Volume offset Control Service
2  *
3  * Copyright (c) 2021 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <errno.h>
9 #include <stdbool.h>
10 #include <stddef.h>
11 #include <stdint.h>
12 #include <string.h>
13 #include <sys/types.h>
14 
15 #include <zephyr/autoconf.h>
16 #include <zephyr/bluetooth/att.h>
17 #include <zephyr/bluetooth/audio/audio.h>
18 #include <zephyr/bluetooth/audio/vocs.h>
19 #include <zephyr/bluetooth/bluetooth.h>
20 #include <zephyr/bluetooth/conn.h>
21 #include <zephyr/bluetooth/gatt.h>
22 #include <zephyr/bluetooth/uuid.h>
23 #include <zephyr/device.h>
24 #include <zephyr/init.h>
25 #include <zephyr/kernel.h>
26 #include <zephyr/logging/log.h>
27 #include <zephyr/sys/atomic.h>
28 #include <zephyr/sys/byteorder.h>
29 #include <zephyr/sys/check.h>
30 #include <zephyr/sys/util.h>
31 #include <zephyr/sys/util_macro.h>
32 #include <zephyr/sys_clock.h>
33 
34 #include "audio_internal.h"
35 #include "vocs_internal.h"
36 
37 #define LOG_LEVEL CONFIG_BT_VOCS_LOG_LEVEL
38 LOG_MODULE_REGISTER(bt_vocs);
39 
40 #define VALID_VOCS_OPCODE(opcode)	((opcode) == BT_VOCS_OPCODE_SET_OFFSET)
41 
42 #define BT_AUDIO_LOCATION_RFU (~BT_AUDIO_LOCATION_ANY)
43 
44 #if defined(CONFIG_BT_VOCS)
offset_state_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)45 static void offset_state_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
46 {
47 	LOG_DBG("value 0x%04x", value);
48 }
49 
read_offset_state(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)50 static ssize_t read_offset_state(struct bt_conn *conn, const struct bt_gatt_attr *attr,
51 				 void *buf, uint16_t len, uint16_t offset)
52 {
53 	struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr);
54 
55 	LOG_DBG("offset %d, counter %u", inst->state.offset, inst->state.change_counter);
56 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->state,
57 				 sizeof(inst->state));
58 }
59 
location_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)60 static void location_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
61 {
62 	LOG_DBG("value 0x%04x", value);
63 }
64 
vocs_notify_str(enum bt_vocs_notify notify)65 static const char *vocs_notify_str(enum bt_vocs_notify notify)
66 {
67 	switch (notify) {
68 	case NOTIFY_STATE:
69 		return "state";
70 	case NOTIFY_LOCATION:
71 		return "location";
72 	case NOTIFY_OUTPUT_DESC:
73 		return "output desc";
74 	default:
75 		return "unknown";
76 	}
77 }
78 
notify_work_reschedule(struct bt_vocs_server * inst,enum bt_vocs_notify notify,k_timeout_t delay)79 static void notify_work_reschedule(struct bt_vocs_server *inst, enum bt_vocs_notify notify,
80 				   k_timeout_t delay)
81 {
82 	int err;
83 
84 	atomic_set_bit(inst->notify, notify);
85 
86 	err = k_work_reschedule(&inst->notify_work, K_NO_WAIT);
87 	if (err < 0) {
88 		LOG_ERR("Failed to reschedule %s notification err %d",
89 			vocs_notify_str(notify), err);
90 	}
91 }
92 
notify(struct bt_vocs_server * inst,enum bt_vocs_notify notify,const struct bt_uuid * uuid,const void * data,uint16_t len)93 static void notify(struct bt_vocs_server *inst, enum bt_vocs_notify notify,
94 		   const struct bt_uuid *uuid, const void *data, uint16_t len)
95 {
96 	int err;
97 
98 	err = bt_gatt_notify_uuid(NULL, uuid, inst->service_p->attrs, data, len);
99 	if (err == -ENOMEM) {
100 		notify_work_reschedule(inst, notify, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
101 	} else if (err < 0 && err != -ENOTCONN) {
102 		LOG_ERR("Notify %s err %d", vocs_notify_str(notify), err);
103 	}
104 }
105 
notify_work_handler(struct k_work * work)106 static void notify_work_handler(struct k_work *work)
107 {
108 	struct k_work_delayable *d_work = k_work_delayable_from_work(work);
109 	struct bt_vocs_server *inst = CONTAINER_OF(d_work, struct bt_vocs_server, notify_work);
110 
111 	if (atomic_test_and_clear_bit(inst->notify, NOTIFY_STATE)) {
112 		notify(inst, NOTIFY_STATE, BT_UUID_VOCS_STATE, &inst->state, sizeof(inst->state));
113 	}
114 
115 	if (atomic_test_and_clear_bit(inst->notify, NOTIFY_LOCATION)) {
116 		notify(inst, NOTIFY_LOCATION, BT_UUID_VOCS_LOCATION, &inst->location,
117 		       sizeof(inst->location));
118 	}
119 
120 	if (atomic_test_and_clear_bit(inst->notify, NOTIFY_OUTPUT_DESC)) {
121 		notify(inst, NOTIFY_OUTPUT_DESC, BT_UUID_VOCS_DESCRIPTION, &inst->output_desc,
122 		       strlen(inst->output_desc));
123 	}
124 }
125 
value_changed(struct bt_vocs_server * inst,enum bt_vocs_notify notify)126 static void value_changed(struct bt_vocs_server *inst, enum bt_vocs_notify notify)
127 {
128 	notify_work_reschedule(inst, notify, K_NO_WAIT);
129 }
130 #else
131 #define value_changed(...)
132 #endif /* CONFIG_BT_VOCS */
133 
write_location(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)134 static ssize_t write_location(struct bt_conn *conn, const struct bt_gatt_attr *attr,
135 			      const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
136 {
137 	struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr);
138 	enum bt_audio_location new_location;
139 
140 	if (offset) {
141 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
142 	}
143 
144 	if (len != sizeof(inst->location)) {
145 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
146 	}
147 
148 	new_location = sys_get_le32(buf);
149 	if ((new_location & BT_AUDIO_LOCATION_RFU) > 0) {
150 		LOG_DBG("Invalid location %u", new_location);
151 
152 		return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
153 	}
154 
155 	if (new_location != inst->location) {
156 		inst->location = new_location;
157 
158 		value_changed(inst, NOTIFY_LOCATION);
159 
160 		if (inst->cb && inst->cb->location) {
161 			inst->cb->location(&inst->vocs, 0, inst->location);
162 		}
163 	}
164 
165 	return len;
166 }
167 
168 #if defined(CONFIG_BT_VOCS)
read_location(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)169 static ssize_t read_location(struct bt_conn *conn, const struct bt_gatt_attr *attr,
170 			     void *buf, uint16_t len, uint16_t offset)
171 {
172 	struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr);
173 
174 	LOG_DBG("0x%08x", inst->location);
175 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->location,
176 				 sizeof(inst->location));
177 }
178 #endif /* CONFIG_BT_VOCS */
179 
write_vocs_control(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)180 static ssize_t write_vocs_control(struct bt_conn *conn, const struct bt_gatt_attr *attr,
181 				  const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
182 {
183 	struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr);
184 	const struct bt_vocs_control *cp = buf;
185 	bool notify = false;
186 
187 	if (offset) {
188 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
189 	}
190 
191 	if (!len || !buf) {
192 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
193 	}
194 
195 	/* Check opcode before length */
196 	if (!VALID_VOCS_OPCODE(cp->opcode)) {
197 		LOG_DBG("Invalid opcode %u", cp->opcode);
198 		return BT_GATT_ERR(BT_VOCS_ERR_OP_NOT_SUPPORTED);
199 	}
200 
201 	if (len != sizeof(struct bt_vocs_control)) {
202 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
203 	}
204 
205 	LOG_DBG("Opcode %u, counter %u", cp->opcode, cp->counter);
206 
207 
208 	if (cp->counter != inst->state.change_counter) {
209 		return BT_GATT_ERR(BT_VOCS_ERR_INVALID_COUNTER);
210 	}
211 
212 	switch (cp->opcode) {
213 	case BT_VOCS_OPCODE_SET_OFFSET:
214 		LOG_DBG("Set offset %d", cp->offset);
215 		if (cp->offset > BT_VOCS_MAX_OFFSET || cp->offset < BT_VOCS_MIN_OFFSET) {
216 			return BT_GATT_ERR(BT_VOCS_ERR_OUT_OF_RANGE);
217 		}
218 
219 		if (inst->state.offset != sys_le16_to_cpu(cp->offset)) {
220 			inst->state.offset = sys_le16_to_cpu(cp->offset);
221 			notify = true;
222 		}
223 		break;
224 	default:
225 		return BT_GATT_ERR(BT_VOCS_ERR_OP_NOT_SUPPORTED);
226 	}
227 
228 	if (notify) {
229 		inst->state.change_counter++;
230 		LOG_DBG("New state: offset %d, counter %u", inst->state.offset,
231 			inst->state.change_counter);
232 
233 		value_changed(inst, NOTIFY_STATE);
234 
235 		if (inst->cb && inst->cb->state) {
236 			inst->cb->state(&inst->vocs, 0, inst->state.offset);
237 		}
238 
239 	}
240 
241 	return len;
242 }
243 
244 #if defined(CONFIG_BT_VOCS)
output_desc_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)245 static void output_desc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
246 {
247 	LOG_DBG("value 0x%04x", value);
248 }
249 #endif /* CONFIG_BT_VOCS */
250 
write_output_desc(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)251 static ssize_t write_output_desc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
252 				 const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
253 {
254 	struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr);
255 
256 	if (offset) {
257 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
258 	}
259 
260 	if (len >= sizeof(inst->output_desc)) {
261 		LOG_DBG("Output desc was clipped from length %u to %zu", len,
262 			sizeof(inst->output_desc) - 1);
263 		/* We just clip the string value if it's too long */
264 		len = (uint16_t)sizeof(inst->output_desc) - 1;
265 	}
266 
267 	if (len != strlen(inst->output_desc) || memcmp(buf, inst->output_desc, len)) {
268 		memcpy(inst->output_desc, buf, len);
269 		inst->output_desc[len] = '\0';
270 
271 		value_changed(inst, NOTIFY_OUTPUT_DESC);
272 
273 		if (inst->cb && inst->cb->description) {
274 			inst->cb->description(&inst->vocs, 0, inst->output_desc);
275 		}
276 	}
277 
278 	LOG_DBG("%s", inst->output_desc);
279 
280 	return len;
281 }
282 
vocs_write(struct bt_vocs_server * inst,ssize_t (* write)(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags),const void * buf,uint16_t len)283 static int vocs_write(struct bt_vocs_server *inst,
284 		      ssize_t (*write)(struct bt_conn *conn,
285 				       const struct bt_gatt_attr *attr,
286 				       const void *buf, uint16_t len,
287 				       uint16_t offset, uint8_t flags),
288 		      const void *buf, uint16_t len)
289 {
290 	struct bt_audio_attr_user_data user_data = {
291 		.user_data = inst,
292 	};
293 	struct bt_gatt_attr attr = {
294 		.user_data = &user_data,
295 	};
296 	int err;
297 
298 	err = write(NULL, &attr, buf, len, 0, 0);
299 	if (err < 0) {
300 		return err;
301 	}
302 
303 	return 0;
304 }
305 
306 #if defined(CONFIG_BT_VOCS)
read_output_desc(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)307 static ssize_t read_output_desc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
308 				void *buf, uint16_t len, uint16_t offset)
309 {
310 	struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr);
311 
312 	LOG_DBG("%s", inst->output_desc);
313 	return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->output_desc,
314 				 strlen(inst->output_desc));
315 }
316 
317 #define BT_VOCS_SERVICE_DEFINITION(_vocs) { \
318 	BT_GATT_SECONDARY_SERVICE(BT_UUID_VOCS), \
319 	BT_AUDIO_CHRC(BT_UUID_VOCS_STATE, \
320 		      BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
321 		      BT_GATT_PERM_READ_ENCRYPT, \
322 		      read_offset_state, NULL, &_vocs), \
323 	BT_AUDIO_CCC(offset_state_cfg_changed), \
324 	BT_AUDIO_CHRC(BT_UUID_VOCS_LOCATION, \
325 		      BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
326 		      BT_GATT_PERM_READ_ENCRYPT, \
327 		      read_location, write_location, &_vocs), \
328 	BT_AUDIO_CCC(location_cfg_changed), \
329 	BT_AUDIO_CHRC(BT_UUID_VOCS_CONTROL, \
330 		      BT_GATT_CHRC_WRITE, \
331 		      BT_GATT_PERM_WRITE_ENCRYPT, \
332 		      NULL, write_vocs_control, &_vocs), \
333 	BT_AUDIO_CHRC(BT_UUID_VOCS_DESCRIPTION, \
334 		      BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
335 		      BT_GATT_PERM_READ_ENCRYPT, \
336 		      read_output_desc, write_output_desc, &_vocs), \
337 	BT_AUDIO_CCC(output_desc_cfg_changed) \
338 	}
339 
340 static struct bt_vocs_server vocs_insts[CONFIG_BT_VOCS_MAX_INSTANCE_COUNT];
341 BT_GATT_SERVICE_INSTANCE_DEFINE(vocs_service_list, vocs_insts, CONFIG_BT_VOCS_MAX_INSTANCE_COUNT,
342 				BT_VOCS_SERVICE_DEFINITION);
343 
bt_vocs_free_instance_get(void)344 struct bt_vocs *bt_vocs_free_instance_get(void)
345 {
346 	static uint32_t instance_cnt;
347 
348 	if (instance_cnt >= CONFIG_BT_VOCS_MAX_INSTANCE_COUNT) {
349 		return NULL;
350 	}
351 
352 	return &vocs_insts[instance_cnt++].vocs;
353 }
354 
bt_vocs_svc_decl_get(struct bt_vocs * vocs)355 void *bt_vocs_svc_decl_get(struct bt_vocs *vocs)
356 {
357 	struct bt_vocs_server *inst;
358 
359 	CHECKIF(!vocs) {
360 		LOG_DBG("Null VOCS pointer");
361 		return NULL;
362 	}
363 
364 	CHECKIF(vocs->client_instance) {
365 		LOG_DBG("vocs pointer shall be server instance");
366 		return NULL;
367 	}
368 
369 	inst = CONTAINER_OF(vocs, struct bt_vocs_server, vocs);
370 
371 	return inst->service_p->attrs;
372 }
373 
prepare_vocs_instances(void)374 static void prepare_vocs_instances(void)
375 {
376 	for (int i = 0; i < ARRAY_SIZE(vocs_insts); i++) {
377 		vocs_insts[i].service_p = &vocs_service_list[i];
378 	}
379 }
380 
bt_vocs_register(struct bt_vocs * vocs,const struct bt_vocs_register_param * param)381 int bt_vocs_register(struct bt_vocs *vocs,
382 		     const struct bt_vocs_register_param *param)
383 {
384 	struct bt_vocs_server *inst;
385 	int err;
386 	struct bt_gatt_attr *attr;
387 	struct bt_gatt_chrc *chrc;
388 	static bool instances_prepared;
389 
390 	CHECKIF(!vocs) {
391 		LOG_DBG("Null VOCS pointer");
392 		return -EINVAL;
393 	}
394 
395 	CHECKIF(vocs->client_instance) {
396 		LOG_DBG("vocs pointer shall be server instance");
397 		return -EINVAL;
398 	}
399 
400 	inst = CONTAINER_OF(vocs, struct bt_vocs_server, vocs);
401 
402 	CHECKIF(!param) {
403 		LOG_DBG("NULL params pointer");
404 		return -EINVAL;
405 	}
406 
407 	if (!instances_prepared) {
408 		prepare_vocs_instances();
409 		instances_prepared = true;
410 	}
411 
412 	CHECKIF(inst->initialized) {
413 		LOG_DBG("Already initialized VOCS instance");
414 		return -EALREADY;
415 	}
416 
417 	CHECKIF(param->offset > BT_VOCS_MAX_OFFSET || param->offset < BT_VOCS_MIN_OFFSET) {
418 		LOG_DBG("Invalid offset %d", param->offset);
419 		return -EINVAL;
420 	}
421 
422 	inst->location = param->location;
423 	inst->state.offset = param->offset;
424 	inst->cb = param->cb;
425 
426 	if (param->output_desc) {
427 		(void)utf8_lcpy(inst->output_desc, param->output_desc,
428 				sizeof(inst->output_desc));
429 		if (IS_ENABLED(CONFIG_BT_VOCS_LOG_LEVEL_DBG) &&
430 		    strcmp(inst->output_desc, param->output_desc)) {
431 			LOG_DBG("Output desc clipped to %s", inst->output_desc);
432 		}
433 	}
434 
435 	/* Iterate over the attributes in VOCS (starting from i = 1 to skip the service declaration)
436 	 * to find the BT_UUID_VOCS_DESCRIPTION or BT_UUID_VOCS_LOCATION and update the
437 	 * characteristic value (at [i]), update with the write permission and callback, and
438 	 * also update the characteristic declaration (always found at [i - 1]) with the
439 	 * BT_GATT_CHRC_WRITE_WITHOUT_RESP property.
440 	 */
441 	for (int i = 1; i < inst->service_p->attr_count; i++) {
442 		attr = &inst->service_p->attrs[i];
443 
444 		if (param->location_writable && !bt_uuid_cmp(attr->uuid, BT_UUID_VOCS_LOCATION)) {
445 			/* Update attr and chrc to be writable */
446 			chrc = inst->service_p->attrs[i - 1].user_data;
447 			attr->perm |= BT_GATT_PERM_WRITE_ENCRYPT;
448 			chrc->properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP;
449 		} else if (param->desc_writable &&
450 			   !bt_uuid_cmp(attr->uuid, BT_UUID_VOCS_DESCRIPTION)) {
451 			/* Update attr and chrc to be writable */
452 			chrc = inst->service_p->attrs[i - 1].user_data;
453 			attr->perm |= BT_GATT_PERM_WRITE_ENCRYPT;
454 			chrc->properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP;
455 		}
456 	}
457 
458 	err = bt_gatt_service_register(inst->service_p);
459 	if (err) {
460 		LOG_DBG("Could not register VOCS service");
461 		return err;
462 	}
463 
464 	atomic_clear(inst->notify);
465 	k_work_init_delayable(&inst->notify_work, notify_work_handler);
466 
467 	inst->initialized = true;
468 	return 0;
469 }
470 #endif /* CONFIG_BT_VOCS */
471 
bt_vocs_state_get(struct bt_vocs * inst)472 int bt_vocs_state_get(struct bt_vocs *inst)
473 {
474 	CHECKIF(!inst) {
475 		LOG_DBG("Null VOCS pointer");
476 		return -EINVAL;
477 	}
478 
479 	if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) {
480 		struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, vocs);
481 
482 		return bt_vocs_client_state_get(cli);
483 	} else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) {
484 		struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, vocs);
485 
486 		if (srv->cb && srv->cb->state) {
487 			srv->cb->state(inst, 0, srv->state.offset);
488 		}
489 		return 0;
490 	}
491 
492 	return -ENOTSUP;
493 }
494 
bt_vocs_location_get(struct bt_vocs * inst)495 int bt_vocs_location_get(struct bt_vocs *inst)
496 {
497 	CHECKIF(!inst) {
498 		LOG_DBG("Null VOCS pointer");
499 		return -EINVAL;
500 	}
501 
502 	if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) {
503 		struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, vocs);
504 
505 		return bt_vocs_client_location_get(cli);
506 	} else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) {
507 		struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, vocs);
508 
509 		if (srv->cb && srv->cb->location) {
510 			srv->cb->location(inst, 0, srv->location);
511 		}
512 		return 0;
513 	}
514 
515 	return -ENOTSUP;
516 }
517 
bt_vocs_location_set(struct bt_vocs * inst,uint32_t location)518 int bt_vocs_location_set(struct bt_vocs *inst, uint32_t location)
519 {
520 	CHECKIF(!inst) {
521 		LOG_DBG("Null VOCS pointer");
522 		return -EINVAL;
523 	}
524 
525 	if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) {
526 		struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, vocs);
527 
528 		return bt_vocs_client_location_set(cli, location);
529 	} else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) {
530 		struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, vocs);
531 
532 		return vocs_write(srv, write_location, &location, sizeof(location));
533 	}
534 
535 	return -ENOTSUP;
536 }
537 
bt_vocs_state_set(struct bt_vocs * inst,int16_t offset)538 int bt_vocs_state_set(struct bt_vocs *inst, int16_t offset)
539 {
540 	CHECKIF(!inst) {
541 		LOG_DBG("Null VOCS pointer");
542 		return -EINVAL;
543 	}
544 
545 	if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) {
546 		struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, vocs);
547 
548 		return bt_vocs_client_state_set(cli, offset);
549 	} else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) {
550 		struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, vocs);
551 		struct bt_vocs_control cp;
552 
553 		cp.opcode = BT_VOCS_OPCODE_SET_OFFSET;
554 		cp.counter = srv->state.change_counter;
555 		cp.offset = sys_cpu_to_le16(offset);
556 
557 		return vocs_write(srv, write_vocs_control, &cp, sizeof(cp));
558 	}
559 
560 	return -ENOTSUP;
561 }
562 
bt_vocs_description_get(struct bt_vocs * inst)563 int bt_vocs_description_get(struct bt_vocs *inst)
564 {
565 	CHECKIF(!inst) {
566 		LOG_DBG("Null VOCS pointer");
567 		return -EINVAL;
568 	}
569 
570 	if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) {
571 		struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, vocs);
572 
573 		return bt_vocs_client_description_get(cli);
574 	} else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) {
575 		struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, vocs);
576 
577 		if (srv->cb && srv->cb->description) {
578 			srv->cb->description(inst, 0, srv->output_desc);
579 		}
580 		return 0;
581 	}
582 
583 	return -ENOTSUP;
584 }
585 
bt_vocs_description_set(struct bt_vocs * inst,const char * description)586 int bt_vocs_description_set(struct bt_vocs *inst, const char *description)
587 {
588 	CHECKIF(!inst) {
589 		LOG_DBG("Null VOCS pointer");
590 		return -EINVAL;
591 	}
592 
593 	CHECKIF(!description) {
594 		LOG_DBG("Null description pointer");
595 		return -EINVAL;
596 	}
597 
598 	if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) {
599 		struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, vocs);
600 
601 		return bt_vocs_client_description_set(cli, description);
602 	} else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) {
603 		struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, vocs);
604 
605 		return vocs_write(srv, write_output_desc, description, strlen(description));
606 	}
607 
608 	return -ENOTSUP;
609 }
610