1 /*  Bluetooth VOCS - Volume Offset Control Service - Client */
2 
3 /*
4  * Copyright (c) 2021 Nordic Semiconductor ASA
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #include <zephyr.h>
10 #include <zephyr/types.h>
11 
12 #include <device.h>
13 #include <init.h>
14 #include <sys/check.h>
15 
16 #include <bluetooth/bluetooth.h>
17 #include <bluetooth/l2cap.h>
18 #include <bluetooth/conn.h>
19 #include <bluetooth/gatt.h>
20 #include <bluetooth/audio/vocs.h>
21 
22 #include "vocs_internal.h"
23 
24 #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_VOCS_CLIENT)
25 #define LOG_MODULE_NAME bt_vocs_client
26 #include "common/log.h"
27 
28 static struct bt_vocs vocs_insts[CONFIG_BT_MAX_CONN * CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT];
29 
lookup_vocs_by_handle(struct bt_conn * conn,uint16_t handle)30 static struct bt_vocs *lookup_vocs_by_handle(struct bt_conn *conn, uint16_t handle)
31 {
32 	__ASSERT(handle != 0, "Handle cannot be 0");
33 	__ASSERT(conn, "Conn cannot be NULL");
34 
35 	for (int i = 0; i < ARRAY_SIZE(vocs_insts); i++) {
36 		if (vocs_insts[i].cli.conn == conn &&
37 		    vocs_insts[i].cli.active &&
38 		    vocs_insts[i].cli.start_handle <= handle &&
39 		    vocs_insts[i].cli.end_handle >= handle) {
40 			return &vocs_insts[i];
41 		}
42 	}
43 
44 	BT_DBG("Could not find VOCS instance with handle 0x%04x", handle);
45 	return NULL;
46 }
47 
vocs_client_notify_handler(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)48 uint8_t vocs_client_notify_handler(struct bt_conn *conn, struct bt_gatt_subscribe_params *params,
49 				   const void *data, uint16_t length)
50 {
51 	uint16_t handle = params->value_handle;
52 	struct bt_vocs *inst = lookup_vocs_by_handle(conn, handle);
53 
54 	if (!inst) {
55 		BT_DBG("Instance not found");
56 		return BT_GATT_ITER_STOP;
57 	}
58 
59 	if (!data || !length) {
60 		return BT_GATT_ITER_CONTINUE;
61 	}
62 
63 	if (handle == inst->cli.state_handle) {
64 		if (length == sizeof(inst->cli.state)) {
65 			memcpy(&inst->cli.state, data, length);
66 			BT_DBG("Inst %p: Offset %d, counter %u", inst, inst->cli.state.offset,
67 			       inst->cli.state.change_counter);
68 			if (inst->cli.cb && inst->cli.cb->state) {
69 				inst->cli.cb->state(inst, 0, inst->cli.state.offset);
70 			}
71 		} else {
72 			BT_DBG("Invalid state length %u", length);
73 		}
74 	} else if (handle == inst->cli.desc_handle) {
75 		char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1];
76 
77 		/* Truncate if too large */
78 
79 		if (length > sizeof(desc) - 1) {
80 			BT_DBG("Description truncated from %u to %zu octets",
81 			       length, sizeof(desc) - 1);
82 		}
83 		length = MIN(sizeof(desc) - 1, length);
84 
85 		memcpy(desc, data, length);
86 		desc[length] = '\0';
87 		BT_DBG("Inst %p: Output description: %s", inst, log_strdup(desc));
88 		if (inst->cli.cb && inst->cli.cb->description) {
89 			inst->cli.cb->description(inst, 0, desc);
90 		}
91 	} else if (handle == inst->cli.location_handle) {
92 		if (length == sizeof(inst->cli.location)) {
93 			memcpy(&inst->cli.location, data, length);
94 			BT_DBG("Inst %p: Location %u", inst, inst->cli.location);
95 			if (inst->cli.cb && inst->cli.cb->location) {
96 				inst->cli.cb->location(inst, 0, inst->cli.location);
97 			}
98 		} else {
99 			BT_DBG("Invalid location length %u", length);
100 		}
101 	}
102 
103 	return BT_GATT_ITER_CONTINUE;
104 }
105 
vocs_client_read_offset_state_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)106 static uint8_t vocs_client_read_offset_state_cb(struct bt_conn *conn, uint8_t err,
107 						struct bt_gatt_read_params *params,
108 						const void *data, uint16_t length)
109 {
110 	int cb_err = err;
111 	struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
112 
113 	memset(params, 0, sizeof(*params));
114 
115 	if (!inst) {
116 		BT_DBG("Instance not found");
117 		return BT_GATT_ITER_STOP;
118 	}
119 
120 	BT_DBG("Inst %p: err: 0x%02X", inst, err);
121 	inst->cli.busy = false;
122 
123 	if (cb_err) {
124 		BT_DBG("Offset state read failed: %d", err);
125 	} else if (data) {
126 		if (length == sizeof(inst->cli.state)) {
127 			memcpy(&inst->cli.state, data, length);
128 			BT_DBG("Offset %d, counter %u",
129 			       inst->cli.state.offset, inst->cli.state.change_counter);
130 		} else {
131 			BT_DBG("Invalid length %u (expected %zu)", length, sizeof(inst->cli.state));
132 			cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
133 		}
134 	} else {
135 		BT_DBG("Invalid state");
136 		cb_err = BT_ATT_ERR_UNLIKELY;
137 	}
138 
139 	if (inst->cli.cb && inst->cli.cb->state) {
140 		inst->cli.cb->state(inst, cb_err,
141 				    cb_err ? 0 : inst->cli.state.offset);
142 	}
143 
144 	return BT_GATT_ITER_STOP;
145 }
146 
vocs_client_read_location_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)147 static uint8_t vocs_client_read_location_cb(struct bt_conn *conn, uint8_t err,
148 					    struct bt_gatt_read_params *params,
149 					    const void *data, uint16_t length)
150 {
151 	int cb_err = err;
152 	struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
153 
154 	memset(params, 0, sizeof(*params));
155 
156 	if (!inst) {
157 		BT_DBG("Instance not found");
158 		return BT_GATT_ITER_STOP;
159 	}
160 
161 	BT_DBG("Inst %p: err: 0x%02X", inst, err);
162 	inst->cli.busy = false;
163 
164 	if (cb_err) {
165 		BT_DBG("Offset state read failed: %d", err);
166 	} else if (data) {
167 		if (length == sizeof(inst->cli.location)) {
168 			memcpy(&inst->cli.location, data, length);
169 			BT_DBG("Location %u", inst->cli.location);
170 		} else {
171 			BT_DBG("Invalid length %u (expected %zu)",
172 			       length, sizeof(inst->cli.location));
173 			cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
174 		}
175 	} else {
176 		BT_DBG("Invalid location");
177 		cb_err = BT_ATT_ERR_UNLIKELY;
178 	}
179 
180 	if (inst->cli.cb && inst->cli.cb->location) {
181 		inst->cli.cb->location(inst, cb_err,
182 				       cb_err ? 0 : inst->cli.location);
183 	}
184 
185 	return BT_GATT_ITER_STOP;
186 }
187 
internal_read_volume_offset_state_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)188 static uint8_t internal_read_volume_offset_state_cb(struct bt_conn *conn, uint8_t err,
189 						    struct bt_gatt_read_params *params,
190 						    const void *data, uint16_t length)
191 {
192 	int cb_err = err;
193 	struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
194 
195 	memset(params, 0, sizeof(*params));
196 
197 	if (!inst) {
198 		BT_ERR("Instance not found");
199 		return BT_GATT_ITER_STOP;
200 	}
201 
202 	if (err) {
203 		BT_WARN("Volume offset state read failed: %d", err);
204 		cb_err = BT_ATT_ERR_UNLIKELY;
205 	} else if (data) {
206 		if (length == sizeof(inst->cli.state)) {
207 			int write_err;
208 
209 			memcpy(&inst->cli.state, data, length);
210 			BT_DBG("Offset %d, counter %u",
211 			       inst->cli.state.offset,
212 			       inst->cli.state.change_counter);
213 
214 			/* clear busy flag to reuse function */
215 			inst->cli.busy = false;
216 			write_err = bt_vocs_client_state_set(inst, inst->cli.cp.offset);
217 			if (write_err) {
218 				cb_err = BT_ATT_ERR_UNLIKELY;
219 			}
220 		} else {
221 			BT_DBG("Invalid length %u (expected %zu)", length, sizeof(inst->cli.state));
222 			cb_err = BT_ATT_ERR_UNLIKELY;
223 		}
224 	} else {
225 		BT_DBG("Invalid (empty) offset state read");
226 		cb_err = BT_ATT_ERR_UNLIKELY;
227 	}
228 
229 	if (cb_err) {
230 		inst->cli.busy = false;
231 
232 		if (inst->cli.cb && inst->cli.cb->set_offset) {
233 			inst->cli.cb->set_offset(inst, err);
234 		}
235 	}
236 
237 	return BT_GATT_ITER_STOP;
238 }
239 
vcs_client_write_vocs_cp_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_write_params * params)240 static void vcs_client_write_vocs_cp_cb(struct bt_conn *conn, uint8_t err,
241 					struct bt_gatt_write_params *params)
242 {
243 	int cb_err = err;
244 	struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->handle);
245 
246 	memset(params, 0, sizeof(*params));
247 
248 	if (!inst) {
249 		BT_DBG("Instance not found");
250 		return;
251 	}
252 
253 	BT_DBG("Inst %p: err: 0x%02X", inst, err);
254 
255 	/* If the change counter is out of data when a write was attempted from the application,
256 	 * we automatically initiate a read to get the newest state and try again. Once the
257 	 * change counter has been read, we restart the applications write request. If it fails
258 	 * the second time, we return an error to the application.
259 	 */
260 	if (cb_err == BT_VOCS_ERR_INVALID_COUNTER && inst->cli.cp_retried) {
261 		cb_err = BT_ATT_ERR_UNLIKELY;
262 	} else if (cb_err == BT_VOCS_ERR_INVALID_COUNTER && inst->cli.state_handle) {
263 		BT_DBG("Invalid change counter. Reading volume offset state from server.");
264 
265 		inst->cli.read_params.func = internal_read_volume_offset_state_cb;
266 		inst->cli.read_params.handle_count = 1;
267 		inst->cli.read_params.single.handle = inst->cli.state_handle;
268 
269 		cb_err = bt_gatt_read(conn, &inst->cli.read_params);
270 		if (cb_err) {
271 			BT_WARN("Could not read Volume offset state: %d", cb_err);
272 		} else {
273 			inst->cli.cp_retried = true;
274 			/* Wait for read callback */
275 			return;
276 		}
277 	}
278 
279 	inst->cli.busy = false;
280 	inst->cli.cp_retried = false;
281 
282 	if (inst->cli.cb && inst->cli.cb->set_offset) {
283 		inst->cli.cb->set_offset(inst, cb_err);
284 	}
285 }
286 
vcs_client_read_output_desc_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)287 static uint8_t vcs_client_read_output_desc_cb(struct bt_conn *conn, uint8_t err,
288 					      struct bt_gatt_read_params *params,
289 					      const void *data, uint16_t length)
290 {
291 	int cb_err = err;
292 	struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
293 	char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1];
294 
295 	memset(params, 0, sizeof(*params));
296 
297 	if (!inst) {
298 		BT_DBG("Instance not found");
299 		return BT_GATT_ITER_STOP;
300 	}
301 
302 	BT_DBG("Inst %p: err: 0x%02X", inst, err);
303 	inst->cli.busy = false;
304 
305 	if (cb_err) {
306 		BT_DBG("Description read failed: %d", err);
307 	} else {
308 		if (data) {
309 			BT_HEXDUMP_DBG(data, length, "Output description read");
310 
311 			if (length > sizeof(desc) - 1) {
312 				BT_DBG("Description truncated from %u to %zu octets",
313 				       length, sizeof(desc) - 1);
314 			}
315 			length = MIN(sizeof(desc) - 1, length);
316 
317 			/* TODO: Handle long reads */
318 			memcpy(desc, data, length);
319 		}
320 		desc[length] = '\0';
321 		BT_DBG("Output description: %s", log_strdup(desc));
322 	}
323 
324 	if (inst->cli.cb && inst->cli.cb->description) {
325 		inst->cli.cb->description(inst, cb_err, cb_err ? NULL : desc);
326 	}
327 
328 	return BT_GATT_ITER_STOP;
329 }
330 
valid_inst_discovered(struct bt_vocs * inst)331 static bool valid_inst_discovered(struct bt_vocs *inst)
332 {
333 	return inst->cli.state_handle &&
334 		inst->cli.control_handle &&
335 		inst->cli.location_handle &&
336 		inst->cli.desc_handle;
337 }
338 
vocs_discover_func(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * params)339 static uint8_t vocs_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
340 				  struct bt_gatt_discover_params *params)
341 {
342 	struct bt_vocs_client *client_inst = CONTAINER_OF(params,
343 							  struct bt_vocs_client,
344 							  discover_params);
345 	struct bt_vocs *inst = CONTAINER_OF(client_inst, struct bt_vocs, cli);
346 
347 	if (!attr) {
348 		BT_DBG("Discovery complete for VOCS %p", inst);
349 		inst->cli.busy = false;
350 		(void)memset(params, 0, sizeof(*params));
351 
352 		if (inst->cli.cb && inst->cli.cb->discover) {
353 			int err = valid_inst_discovered(inst) ? 0 : -ENOENT;
354 
355 			inst->cli.cb->discover(inst, err);
356 		}
357 
358 		return BT_GATT_ITER_STOP;
359 	}
360 
361 	BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
362 
363 	if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
364 		struct bt_gatt_subscribe_params *sub_params = NULL;
365 		struct bt_gatt_chrc *chrc;
366 
367 		chrc = (struct bt_gatt_chrc *)attr->user_data;
368 		if (inst->cli.start_handle == 0) {
369 			inst->cli.start_handle = chrc->value_handle;
370 		}
371 		inst->cli.end_handle = chrc->value_handle;
372 
373 		if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_STATE)) {
374 			BT_DBG("Volume offset state");
375 			inst->cli.state_handle = chrc->value_handle;
376 			sub_params = &inst->cli.state_sub_params;
377 		} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_LOCATION)) {
378 			BT_DBG("Location");
379 			inst->cli.location_handle = chrc->value_handle;
380 			if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
381 				sub_params = &inst->cli.location_sub_params;
382 			}
383 			if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
384 				inst->cli.location_writable = true;
385 			}
386 		} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_CONTROL)) {
387 			BT_DBG("Control point");
388 			inst->cli.control_handle = chrc->value_handle;
389 		} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_DESCRIPTION)) {
390 			BT_DBG("Description");
391 			inst->cli.desc_handle = chrc->value_handle;
392 			if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
393 				sub_params = &inst->cli.desc_sub_params;
394 			}
395 			if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
396 				inst->cli.desc_writable = true;
397 			}
398 		}
399 
400 		if (sub_params) {
401 			int err;
402 
403 			sub_params->value = BT_GATT_CCC_NOTIFY;
404 			sub_params->value_handle = chrc->value_handle;
405 			/*
406 			 * TODO: Don't assume that CCC is at handle + 2;
407 			 * do proper discovery;
408 			 */
409 			sub_params->ccc_handle = attr->handle + 2;
410 			sub_params->notify = vocs_client_notify_handler;
411 			err = bt_gatt_subscribe(conn, sub_params);
412 			if (err) {
413 				BT_WARN("Could not subscribe to handle %u",
414 					sub_params->ccc_handle);
415 			}
416 		}
417 	}
418 
419 	return BT_GATT_ITER_CONTINUE;
420 }
421 
bt_vocs_client_state_get(struct bt_vocs * inst)422 int bt_vocs_client_state_get(struct bt_vocs *inst)
423 {
424 	int err;
425 
426 	CHECKIF(!inst) {
427 		BT_DBG("NULL instance");
428 		return -EINVAL;
429 	}
430 
431 	CHECKIF(inst->cli.conn == NULL) {
432 		BT_DBG("NULL conn");
433 		return -EINVAL;
434 	}
435 
436 	if (!inst->cli.state_handle) {
437 		BT_DBG("Handle not set");
438 		return -EINVAL;
439 	}
440 
441 	if (inst->cli.busy) {
442 		BT_DBG("Handle not set");
443 		return -EBUSY;
444 	}
445 
446 	inst->cli.read_params.func = vocs_client_read_offset_state_cb;
447 	inst->cli.read_params.handle_count = 1;
448 	inst->cli.read_params.single.handle = inst->cli.state_handle;
449 	inst->cli.read_params.single.offset = 0U;
450 
451 	err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params);
452 	if (!err) {
453 		inst->cli.busy = true;
454 	}
455 
456 	return err;
457 }
458 
bt_vocs_client_location_set(struct bt_vocs * inst,uint32_t location)459 int bt_vocs_client_location_set(struct bt_vocs *inst, uint32_t location)
460 {
461 
462 	CHECKIF(!inst) {
463 		BT_DBG("NULL instance");
464 		return -EINVAL;
465 	}
466 
467 	CHECKIF(inst->cli.conn == NULL) {
468 		BT_DBG("NULL conn");
469 		return -EINVAL;
470 	}
471 
472 	if (!inst->cli.location_handle) {
473 		BT_DBG("Handle not set");
474 		return -EINVAL;
475 	} else if (inst->cli.busy) {
476 		return -EBUSY;
477 	} else if (!inst->cli.location_writable) {
478 		BT_DBG("Location is not writable on peer service instance");
479 		return -EPERM;
480 	}
481 
482 	return bt_gatt_write_without_response(inst->cli.conn,
483 					      inst->cli.location_handle,
484 					      &location, sizeof(location),
485 					      false);
486 }
487 
bt_vocs_client_location_get(struct bt_vocs * inst)488 int bt_vocs_client_location_get(struct bt_vocs *inst)
489 {
490 	int err;
491 
492 	CHECKIF(!inst) {
493 		BT_DBG("NULL instance");
494 		return -EINVAL;
495 	}
496 
497 	CHECKIF(inst->cli.conn == NULL) {
498 		BT_DBG("NULL conn");
499 		return -EINVAL;
500 	}
501 
502 	if (!inst->cli.location_handle) {
503 		BT_DBG("Handle not set");
504 		return -EINVAL;
505 	} else if (inst->cli.busy) {
506 		return -EBUSY;
507 	}
508 
509 	inst->cli.read_params.func = vocs_client_read_location_cb;
510 	inst->cli.read_params.handle_count = 1;
511 	inst->cli.read_params.single.handle = inst->cli.location_handle;
512 	inst->cli.read_params.single.offset = 0U;
513 
514 	err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params);
515 	if (!err) {
516 		inst->cli.busy = true;
517 	}
518 
519 	return err;
520 }
521 
bt_vocs_client_state_set(struct bt_vocs * inst,int16_t offset)522 int bt_vocs_client_state_set(struct bt_vocs *inst, int16_t offset)
523 {
524 	int err;
525 
526 	CHECKIF(!inst) {
527 		BT_DBG("NULL instance");
528 		return -EINVAL;
529 	}
530 
531 	CHECKIF(inst->cli.conn == NULL) {
532 		BT_DBG("NULL conn");
533 		return -EINVAL;
534 	}
535 
536 	if (!inst->cli.control_handle) {
537 		BT_DBG("Handle not set");
538 		return -EINVAL;
539 	} else if (inst->cli.busy) {
540 		return -EBUSY;
541 	}
542 
543 	inst->cli.cp.opcode = BT_VOCS_OPCODE_SET_OFFSET;
544 	inst->cli.cp.counter = inst->cli.state.change_counter;
545 	inst->cli.cp.offset = offset;
546 
547 	inst->cli.write_params.offset = 0;
548 	inst->cli.write_params.data = &inst->cli.cp;
549 	inst->cli.write_params.length = sizeof(inst->cli.cp);
550 	inst->cli.write_params.handle = inst->cli.control_handle;
551 	inst->cli.write_params.func = vcs_client_write_vocs_cp_cb;
552 
553 	err = bt_gatt_write(inst->cli.conn, &inst->cli.write_params);
554 	if (!err) {
555 		inst->cli.busy = true;
556 	}
557 
558 	return err;
559 }
560 
bt_vocs_client_description_get(struct bt_vocs * inst)561 int bt_vocs_client_description_get(struct bt_vocs *inst)
562 {
563 	int err;
564 
565 	CHECKIF(!inst) {
566 		BT_DBG("NULL instance");
567 		return -EINVAL;
568 	}
569 
570 	CHECKIF(inst->cli.conn == NULL) {
571 		BT_DBG("NULL conn");
572 		return -EINVAL;
573 	}
574 
575 	if (!inst->cli.desc_handle) {
576 		BT_DBG("Handle not set");
577 		return -EINVAL;
578 	} else if (inst->cli.busy) {
579 		return -EBUSY;
580 	}
581 
582 	inst->cli.read_params.func = vcs_client_read_output_desc_cb;
583 	inst->cli.read_params.handle_count = 1;
584 	inst->cli.read_params.single.handle = inst->cli.desc_handle;
585 	inst->cli.read_params.single.offset = 0U;
586 
587 	err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params);
588 	if (!err) {
589 		inst->cli.busy = true;
590 	}
591 
592 	return err;
593 }
594 
bt_vocs_client_description_set(struct bt_vocs * inst,const char * description)595 int bt_vocs_client_description_set(struct bt_vocs *inst,
596 				   const char *description)
597 {
598 	CHECKIF(!inst) {
599 		BT_DBG("NULL instance");
600 		return -EINVAL;
601 	}
602 
603 	CHECKIF(inst->cli.conn == NULL) {
604 		BT_DBG("NULL conn");
605 		return -EINVAL;
606 	}
607 
608 	if (!inst->cli.desc_handle) {
609 		BT_DBG("Handle not set");
610 		return -EINVAL;
611 	} else if (inst->cli.busy) {
612 		return -EBUSY;
613 	} else if (!inst->cli.desc_writable) {
614 		BT_DBG("Description is not writable on peer service instance");
615 		return -EPERM;
616 	}
617 
618 	return bt_gatt_write_without_response(inst->cli.conn,
619 					      inst->cli.desc_handle,
620 					      description,
621 					      strlen(description), false);
622 }
623 
bt_vocs_client_free_instance_get(void)624 struct bt_vocs *bt_vocs_client_free_instance_get(void)
625 {
626 	for (int i = 0; i < ARRAY_SIZE(vocs_insts); i++) {
627 		if (!vocs_insts[i].cli.active) {
628 			vocs_insts[i].client_instance = true;
629 			vocs_insts[i].cli.active = true;
630 			return &vocs_insts[i];
631 		}
632 	}
633 
634 	return NULL;
635 }
636 
bt_vocs_client_conn_get(const struct bt_vocs * vocs,struct bt_conn ** conn)637 int bt_vocs_client_conn_get(const struct bt_vocs *vocs, struct bt_conn **conn)
638 {
639 	CHECKIF(vocs == NULL) {
640 		BT_DBG("NULL vocs pointer");
641 		return -EINVAL;
642 	}
643 
644 	if (!vocs->client_instance) {
645 		BT_DBG("vocs pointer shall be client instance");
646 		return -EINVAL;
647 	}
648 
649 	if (vocs->cli.conn == NULL) {
650 		BT_DBG("vocs pointer not associated with a connection. "
651 		       "Do discovery first");
652 		return -ENOTCONN;
653 	}
654 
655 	*conn = vocs->cli.conn;
656 	return 0;
657 }
658 
vocs_client_reset(struct bt_vocs * inst,struct bt_conn * conn)659 static void vocs_client_reset(struct bt_vocs *inst, struct bt_conn *conn)
660 {
661 	memset(&inst->cli.state, 0, sizeof(inst->cli.state));
662 	inst->cli.location_writable = 0;
663 	inst->cli.location = 0;
664 	inst->cli.desc_writable = 0;
665 	inst->cli.start_handle = 0;
666 	inst->cli.end_handle = 0;
667 	inst->cli.state_handle = 0;
668 	inst->cli.location_handle = 0;
669 	inst->cli.control_handle = 0;
670 	inst->cli.desc_handle = 0;
671 
672 	/* It's okay if these fail */
673 	(void)bt_gatt_unsubscribe(conn, &inst->cli.state_sub_params);
674 	(void)bt_gatt_unsubscribe(conn, &inst->cli.location_sub_params);
675 	(void)bt_gatt_unsubscribe(conn, &inst->cli.desc_sub_params);
676 }
677 
bt_vocs_discover(struct bt_conn * conn,struct bt_vocs * inst,const struct bt_vocs_discover_param * param)678 int bt_vocs_discover(struct bt_conn *conn, struct bt_vocs *inst,
679 		     const struct bt_vocs_discover_param *param)
680 {
681 	int err = 0;
682 
683 	CHECKIF(!inst || !conn || !param) {
684 		BT_DBG("%s cannot be NULL",
685 		       inst == NULL ? "inst" : conn == NULL ? "conn" : "param");
686 		return -EINVAL;
687 	}
688 
689 	CHECKIF(param->end_handle < param->start_handle) {
690 		BT_DBG("start_handle (%u) shall be less than end_handle (%u)",
691 		       param->start_handle, param->end_handle);
692 		return -EINVAL;
693 	}
694 
695 	CHECKIF(!inst->cli.active) {
696 		BT_DBG("Inactive instance");
697 		return -EINVAL;
698 	}
699 
700 	if (inst->cli.busy) {
701 		BT_DBG("Instance is busy");
702 		return -EBUSY;
703 	}
704 
705 	vocs_client_reset(inst, conn);
706 
707 	inst->cli.conn = conn;
708 	inst->cli.discover_params.start_handle = param->start_handle;
709 	inst->cli.discover_params.end_handle = param->end_handle;
710 	inst->cli.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
711 	inst->cli.discover_params.func = vocs_discover_func;
712 
713 	err = bt_gatt_discover(conn, &inst->cli.discover_params);
714 	if (err) {
715 		BT_DBG("Discover failed (err %d)", err);
716 	} else {
717 		inst->cli.busy = true;
718 	}
719 
720 	return err;
721 }
722 
bt_vocs_client_cb_register(struct bt_vocs * inst,struct bt_vocs_cb * cb)723 void bt_vocs_client_cb_register(struct bt_vocs *inst, struct bt_vocs_cb *cb)
724 {
725 	CHECKIF(!inst) {
726 		BT_DBG("inst cannot be NULL");
727 		return;
728 	}
729 
730 	inst->cli.cb = cb;
731 }
732