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