1 /*
2  * Copyright (c) 2020 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/types.h>
8 #include <stddef.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <sys/printk.h>
12 #include <sys/byteorder.h>
13 #include <zephyr.h>
14 
15 #include <bluetooth/gatt.h>
16 #include <bluetooth/services/ots.h>
17 #include "ots_internal.h"
18 #include "ots_dir_list_internal.h"
19 
20 #include <logging/log.h>
21 
22 LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
23 
24 #define OACP_PROC_TYPE_SIZE	1
25 #define OACP_RES_MAX_SIZE	3
26 
27 #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
28 static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
29 			struct bt_conn *conn, struct net_buf *buf);
30 
oacp_l2cap_closed(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn)31 static void oacp_l2cap_closed(struct bt_gatt_ots_l2cap *l2cap_ctx,
32 			struct bt_conn *conn)
33 {
34 	struct bt_ots *ots;
35 
36 	ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap);
37 
38 	if (!ots->cur_obj) {
39 		return;
40 	}
41 
42 	ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
43 	l2cap_ctx->rx_done = NULL;
44 	l2cap_ctx->tx_done = NULL;
45 }
46 #endif
47 
oacp_read_proc_validate(struct bt_conn * conn,struct bt_ots * ots,struct bt_gatt_ots_oacp_proc * proc)48 static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
49 	struct bt_conn *conn,
50 	struct bt_ots *ots,
51 	struct bt_gatt_ots_oacp_proc *proc)
52 {
53 	struct bt_gatt_ots_oacp_read_params *params = &proc->read_params;
54 
55 	LOG_DBG("Validating Read procedure with offset: 0x%08X and "
56 		"length: 0x%08X", params->offset, params->len);
57 
58 	if (!ots->cur_obj) {
59 		return BT_GATT_OTS_OACP_RES_INV_OBJ;
60 	}
61 
62 	if (!BT_OTS_OBJ_GET_PROP_READ(ots->cur_obj->metadata.props)) {
63 		return BT_GATT_OTS_OACP_RES_NOT_PERMITTED;
64 	}
65 
66 	if (!bt_gatt_ots_l2cap_is_open(&ots->l2cap, conn)) {
67 		return BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL;
68 	}
69 
70 	if ((params->offset + (uint64_t) params->len) >
71 		ots->cur_obj->metadata.size.cur) {
72 		return BT_GATT_OTS_OACP_RES_INV_PARAM;
73 	}
74 
75 	if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
76 		return BT_GATT_OTS_OACP_RES_OBJ_LOCKED;
77 	}
78 
79 	ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_READ_OP_STATE;
80 	ots->cur_obj->state.read_op.sent_len = 0;
81 	memcpy(&ots->cur_obj->state.read_op.oacp_params, &proc->read_params,
82 		sizeof(ots->cur_obj->state.read_op.oacp_params));
83 
84 	LOG_DBG("Read procedure is accepted");
85 
86 	return BT_GATT_OTS_OACP_RES_SUCCESS;
87 }
88 
89 #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
oacp_write_proc_validate(struct bt_conn * conn,struct bt_ots * ots,struct bt_gatt_ots_oacp_proc * proc)90 static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate(
91 	struct bt_conn *conn,
92 	struct bt_ots *ots,
93 	struct bt_gatt_ots_oacp_proc *proc)
94 {
95 	struct bt_gatt_ots_oacp_write_params *params = &proc->write_params;
96 
97 	LOG_DBG("Validating Write procedure with offset: 0x%08X and "
98 		"length: 0x%08X", params->offset, params->len);
99 
100 	if (!ots->cur_obj) {
101 		return BT_GATT_OTS_OACP_RES_INV_OBJ;
102 	}
103 
104 	if (!BT_OTS_OBJ_GET_PROP_WRITE(ots->cur_obj->metadata.props)) {
105 		return BT_GATT_OTS_OACP_RES_NOT_PERMITTED;
106 	}
107 
108 	/* patching is attempted */
109 	if (params->offset < ots->cur_obj->metadata.size.cur) {
110 		if (!BT_OTS_OACP_GET_FEAT_PATCH(ots->features.oacp)) {
111 			return BT_GATT_OTS_OACP_RES_NOT_PERMITTED;
112 		}
113 		if (!BT_OTS_OBJ_GET_PROP_PATCH(ots->cur_obj->metadata.props)) {
114 			return BT_GATT_OTS_OACP_RES_NOT_PERMITTED;
115 		}
116 	}
117 
118 	/* truncation is not supported */
119 	if (BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_TRUNC(params->mode)) {
120 		return BT_GATT_OTS_OACP_RES_NOT_PERMITTED;
121 	}
122 
123 	if (!bt_gatt_ots_l2cap_is_open(&ots->l2cap, conn)) {
124 		return BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL;
125 	}
126 
127 	if (BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_RFU(params->mode)) {
128 		return BT_GATT_OTS_OACP_RES_INV_PARAM;
129 	}
130 
131 	if (params->offset > ots->cur_obj->metadata.size.cur) {
132 		return BT_GATT_OTS_OACP_RES_INV_PARAM;
133 	}
134 
135 	/* append is not supported */
136 	if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.alloc) {
137 		return BT_GATT_OTS_OACP_RES_INV_PARAM;
138 	}
139 
140 	if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
141 		return BT_GATT_OTS_OACP_RES_OBJ_LOCKED;
142 	}
143 
144 	ots->l2cap.rx_done = oacp_write_proc_cb;
145 	ots->l2cap.closed = oacp_l2cap_closed;
146 	ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_WRITE_OP_STATE;
147 	ots->cur_obj->state.write_op.recv_len = 0;
148 	memcpy(&ots->cur_obj->state.write_op.oacp_params, params,
149 		sizeof(ots->cur_obj->state.write_op.oacp_params));
150 
151 	LOG_DBG("Write procedure is accepted");
152 
153 	return BT_GATT_OTS_OACP_RES_SUCCESS;
154 }
155 #endif
156 
oacp_proc_validate(struct bt_conn * conn,struct bt_ots * ots,struct bt_gatt_ots_oacp_proc * proc)157 static enum bt_gatt_ots_oacp_res_code oacp_proc_validate(
158 	struct bt_conn *conn,
159 	struct bt_ots *ots,
160 	struct bt_gatt_ots_oacp_proc *proc)
161 {
162 	switch (proc->type) {
163 	case BT_GATT_OTS_OACP_PROC_READ:
164 		return oacp_read_proc_validate(conn, ots, proc);
165 	case BT_GATT_OTS_OACP_PROC_WRITE:
166 #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
167 		return oacp_write_proc_validate(conn, ots, proc);
168 #endif
169 	case BT_GATT_OTS_OACP_PROC_CREATE:
170 	case BT_GATT_OTS_OACP_PROC_DELETE:
171 	case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
172 	case BT_GATT_OTS_OACP_PROC_EXECUTE:
173 	case BT_GATT_OTS_OACP_PROC_ABORT:
174 	default:
175 		return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
176 	}
177 };
178 
oacp_command_decode(const uint8_t * buf,uint16_t len,struct bt_gatt_ots_oacp_proc * proc)179 static enum bt_gatt_ots_oacp_res_code oacp_command_decode(
180 	const uint8_t *buf, uint16_t len,
181 	struct bt_gatt_ots_oacp_proc *proc)
182 {
183 	struct net_buf_simple net_buf;
184 
185 	net_buf_simple_init_with_data(&net_buf, (void *) buf, len);
186 
187 	proc->type = net_buf_simple_pull_u8(&net_buf);
188 	switch (proc->type) {
189 	case BT_GATT_OTS_OACP_PROC_CREATE:
190 		proc->create_params.size = net_buf_simple_pull_le32(&net_buf);
191 		bt_uuid_create(&proc->create_params.type.uuid, net_buf.data,
192 			       net_buf.len);
193 		net_buf_simple_pull_mem(&net_buf, net_buf.len);
194 		break;
195 	case BT_GATT_OTS_OACP_PROC_DELETE:
196 		break;
197 	case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
198 		proc->cs_calc_params.offset =
199 			net_buf_simple_pull_le32(&net_buf);
200 		proc->cs_calc_params.len =
201 			net_buf_simple_pull_le32(&net_buf);
202 		break;
203 	case BT_GATT_OTS_OACP_PROC_EXECUTE:
204 		break;
205 	case BT_GATT_OTS_OACP_PROC_READ:
206 		proc->read_params.offset =
207 			net_buf_simple_pull_le32(&net_buf);
208 		proc->read_params.len =
209 			net_buf_simple_pull_le32(&net_buf);
210 		return BT_GATT_OTS_OACP_RES_SUCCESS;
211 	case BT_GATT_OTS_OACP_PROC_WRITE:
212 #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
213 		proc->write_params.offset =
214 			net_buf_simple_pull_le32(&net_buf);
215 		proc->write_params.len =
216 			net_buf_simple_pull_le32(&net_buf);
217 		proc->write_params.mode =
218 			net_buf_simple_pull_u8(&net_buf);
219 		return BT_GATT_OTS_OACP_RES_SUCCESS;
220 #else
221 		break;
222 #endif
223 	case BT_GATT_OTS_OACP_PROC_ABORT:
224 	default:
225 		break;
226 	}
227 
228 	LOG_WRN("OACP unsupported procedure type");
229 	return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
230 }
231 
oacp_command_len_verify(struct bt_gatt_ots_oacp_proc * proc,uint16_t len)232 static bool oacp_command_len_verify(struct bt_gatt_ots_oacp_proc *proc,
233 				    uint16_t len)
234 {
235 	uint16_t ref_len = OACP_PROC_TYPE_SIZE;
236 
237 	switch (proc->type) {
238 	case BT_GATT_OTS_OACP_PROC_CREATE:
239 	{
240 		struct bt_ots_obj_type *type = &proc->create_params.type;
241 
242 		switch (type->uuid.type) {
243 		case BT_UUID_TYPE_16:
244 			ref_len += BT_GATT_OTS_OACP_CREATE_UUID16_PARAMS_SIZE;
245 			break;
246 		case BT_UUID_TYPE_32:
247 			ref_len += BT_GATT_OTS_OACP_CREATE_UUID32_PARAMS_SIZE;
248 			break;
249 		case BT_UUID_TYPE_128:
250 			ref_len += BT_GATT_OTS_OACP_CREATE_UUID128_PARAMS_SIZE;
251 			break;
252 		default:
253 			return false;
254 		}
255 	} break;
256 	case BT_GATT_OTS_OACP_PROC_DELETE:
257 		break;
258 	case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
259 		ref_len += BT_GATT_OTS_OACP_CS_CALC_PARAMS_SIZE;
260 		break;
261 	case BT_GATT_OTS_OACP_PROC_EXECUTE:
262 		break;
263 	case BT_GATT_OTS_OACP_PROC_READ:
264 		ref_len += BT_GATT_OTS_OACP_READ_PARAMS_SIZE;
265 		break;
266 	case BT_GATT_OTS_OACP_PROC_WRITE:
267 		ref_len += BT_GATT_OTS_OACP_WRITE_PARAMS_SIZE;
268 		break;
269 	case BT_GATT_OTS_OACP_PROC_ABORT:
270 		break;
271 	default:
272 		return true;
273 	}
274 
275 	return (len == ref_len);
276 }
277 
oacp_read_proc_cb(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn)278 static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
279 			      struct bt_conn *conn)
280 {
281 	int err;
282 	void *obj_chunk;
283 	off_t offset;
284 	ssize_t len;
285 	struct bt_ots *ots;
286 	struct bt_gatt_ots_object_read_op *read_op;
287 
288 	ots     = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap);
289 	read_op = &ots->cur_obj->state.read_op;
290 	offset  = read_op->oacp_params.offset + read_op->sent_len;
291 
292 	if (read_op->sent_len >= read_op->oacp_params.len) {
293 		LOG_DBG("OACP Read Op over L2CAP is completed");
294 
295 		if (read_op->sent_len > read_op->oacp_params.len) {
296 			LOG_WRN("More bytes sent that the client requested");
297 		}
298 
299 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
300 
301 		if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
302 		    ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
303 			return;
304 		}
305 
306 		ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0,
307 				  offset);
308 		return;
309 	}
310 
311 	len = read_op->oacp_params.len - read_op->sent_len;
312 	if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
313 	    ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
314 		len = bt_ots_dir_list_content_get(ots->dir_list, &obj_chunk, len, offset);
315 	} else {
316 		len = ots->cb->obj_read(ots, conn, ots->cur_obj->id, &obj_chunk,
317 					len, offset);
318 	}
319 
320 	if (len < 0) {
321 		LOG_ERR("OCAP Read Op failed with error: %zd", len);
322 
323 		bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
324 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
325 
326 		return;
327 	}
328 
329 	ots->l2cap.tx_done = oacp_read_proc_cb;
330 	err = bt_gatt_ots_l2cap_send(&ots->l2cap, obj_chunk, len);
331 	if (err) {
332 		LOG_ERR("L2CAP CoC error: %d while trying to execute OACP "
333 			"Read procedure", err);
334 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
335 	} else {
336 		read_op->sent_len += len;
337 	}
338 }
339 
oacp_read_proc_execute(struct bt_ots * ots,struct bt_conn * conn)340 static void oacp_read_proc_execute(struct bt_ots *ots,
341 				   struct bt_conn *conn)
342 {
343 	struct bt_gatt_ots_oacp_read_params *params =
344 		&ots->cur_obj->state.read_op.oacp_params;
345 
346 	if (!ots->cur_obj) {
347 		LOG_ERR("Invalid Current Object on OACP Read procedure");
348 		return;
349 	}
350 
351 	LOG_DBG("Executing Read procedure with offset: 0x%08X and "
352 		"length: 0x%08X", params->offset, params->len);
353 
354 	if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
355 	    ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
356 		oacp_read_proc_cb(&ots->l2cap, conn);
357 	} else if (ots->cb->obj_read) {
358 		oacp_read_proc_cb(&ots->l2cap, conn);
359 	} else {
360 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
361 		LOG_ERR("OTS Read operation failed: "
362 			"there is no OTS Read callback");
363 	}
364 }
365 
366 #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
oacp_write_proc_cb(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn,struct net_buf * buf)367 static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
368 			struct bt_conn *conn, struct net_buf *buf)
369 {
370 	struct bt_gatt_ots_object_write_op *write_op;
371 	struct bt_ots *ots;
372 	off_t offset;
373 	size_t rem;
374 	size_t len;
375 	ssize_t rc;
376 
377 	ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap);
378 
379 	if (!ots->cur_obj) {
380 		LOG_ERR("Invalid Current Object on OACP Write procedure");
381 		return -ENODEV;
382 	}
383 
384 	if (!ots->cb->obj_write) {
385 		LOG_ERR("OTS Write operation failed: "
386 			"there is no OTS Write callback");
387 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
388 		return -ENODEV;
389 	}
390 
391 	write_op = &ots->cur_obj->state.write_op;
392 	offset = write_op->oacp_params.offset + write_op->recv_len;
393 	len = buf->len;
394 	if (write_op->recv_len + len > write_op->oacp_params.len) {
395 		LOG_WRN("More bytes received than the client indicated");
396 		len = write_op->oacp_params.len - write_op->recv_len;
397 	}
398 	rem = write_op->oacp_params.len - (write_op->recv_len + len);
399 
400 	rc = ots->cb->obj_write(ots, conn, ots->cur_obj->id, buf->data, len,
401 				  offset, rem);
402 
403 	if (rc < 0) {
404 		len = 0;
405 
406 		/*
407 		 * Returning an EINPROGRESS return code results in the write buffer not being
408 		 * released by the l2cap layer. This is an unsupported use case at the moment.
409 		 */
410 		if (rc == -EINPROGRESS) {
411 			LOG_ERR("Unsupported error code %zd returned by object write callback", rc);
412 		}
413 
414 		LOG_ERR("OTS Write operation failed with error: %zd", rc);
415 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
416 	} else {
417 		/* Return -EIO as an error if all of data was not written */
418 		if (rc != len) {
419 			len = rc;
420 			rc = -EIO;
421 		}
422 	}
423 
424 	write_op->recv_len += len;
425 	if (write_op->recv_len == write_op->oacp_params.len) {
426 		LOG_DBG("OACP Write Op over L2CAP is completed");
427 		ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
428 	}
429 
430 	if (offset + len > ots->cur_obj->metadata.size.cur) {
431 		ots->cur_obj->metadata.size.cur = offset + len;
432 	}
433 
434 	return rc;
435 }
436 #endif
437 
oacp_ind_cb(struct bt_conn * conn,struct bt_gatt_indicate_params * params,uint8_t err)438 static void oacp_ind_cb(struct bt_conn *conn,
439 			struct bt_gatt_indicate_params *params,
440 			uint8_t err)
441 {
442 	struct bt_ots *ots = (struct bt_ots *) params->attr->user_data;
443 
444 	LOG_DBG("Received OACP Indication ACK with status: 0x%04X", err);
445 
446 	switch (ots->cur_obj->state.type) {
447 	case BT_GATT_OTS_OBJECT_READ_OP_STATE:
448 		oacp_read_proc_execute(ots, conn);
449 		break;
450 	case BT_GATT_OTS_OBJECT_WRITE_OP_STATE:
451 		/* procedure execution is driven by L2CAP socket receive */
452 		break;
453 	default:
454 		LOG_ERR("Unsupported OTS state: %d", ots->cur_obj->state.type);
455 		break;
456 	}
457 }
458 
oacp_ind_send(const struct bt_gatt_attr * oacp_attr,enum bt_gatt_ots_oacp_proc_type req_op_code,enum bt_gatt_ots_oacp_res_code oacp_status)459 static int oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
460 			 enum bt_gatt_ots_oacp_proc_type req_op_code,
461 			 enum bt_gatt_ots_oacp_res_code oacp_status)
462 {
463 	uint8_t oacp_res[OACP_RES_MAX_SIZE];
464 	uint16_t oacp_res_len = 0;
465 	struct bt_ots *ots = (struct bt_ots *) oacp_attr->user_data;
466 
467 	/* Encode OACP Response */
468 	oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP;
469 	oacp_res[oacp_res_len++] = req_op_code;
470 	oacp_res[oacp_res_len++] = oacp_status;
471 
472 	/* Prepare indication parameters */
473 	memset(&ots->oacp_ind.params, 0, sizeof(ots->oacp_ind.params));
474 	memcpy(&ots->oacp_ind.attr, oacp_attr, sizeof(ots->oacp_ind.attr));
475 	ots->oacp_ind.params.attr = &ots->oacp_ind.attr;
476 	ots->oacp_ind.params.func = oacp_ind_cb;
477 	ots->oacp_ind.params.data = oacp_res;
478 	ots->oacp_ind.params.len  = oacp_res_len;
479 
480 	LOG_DBG("Sending OACP indication");
481 
482 	return bt_gatt_indicate(NULL, &ots->oacp_ind.params);
483 }
484 
bt_gatt_ots_oacp_write(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)485 ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
486 				   const struct bt_gatt_attr *attr,
487 				   const void *buf, uint16_t len,
488 				   uint16_t offset, uint8_t flags)
489 {
490 	enum bt_gatt_ots_oacp_res_code oacp_status;
491 	struct bt_gatt_ots_oacp_proc oacp_proc;
492 	struct bt_ots *ots = (struct bt_ots *) attr->user_data;
493 
494 	LOG_DBG("Object Action Control Point GATT Write Operation");
495 
496 	if (!ots->oacp_ind.is_enabled) {
497 		LOG_WRN("OACP indications not enabled");
498 		return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF);
499 	}
500 
501 	if (offset != 0) {
502 		LOG_ERR("Invalid offset of OACP Write Request");
503 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
504 	}
505 
506 	oacp_status = oacp_command_decode(buf, len, &oacp_proc);
507 
508 	if (!oacp_command_len_verify(&oacp_proc, len)) {
509 		LOG_ERR("Invalid length of OACP Write Request for 0x%02X "
510 			"Op Code", oacp_proc.type);
511 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
512 	}
513 
514 	if (oacp_status == BT_GATT_OTS_OACP_RES_SUCCESS) {
515 		oacp_status = oacp_proc_validate(conn, ots, &oacp_proc);
516 	}
517 
518 	if (oacp_status != BT_GATT_OTS_OACP_RES_SUCCESS) {
519 		LOG_WRN("OACP Write error status: 0x%02X", oacp_status);
520 	}
521 
522 	oacp_ind_send(attr, oacp_proc.type, oacp_status);
523 	return len;
524 }
525 
bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)526 void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr,
527 				      uint16_t value)
528 {
529 	struct bt_gatt_ots_indicate *oacp_ind =
530 	    CONTAINER_OF((struct _bt_gatt_ccc *) attr->user_data,
531 			 struct bt_gatt_ots_indicate, ccc);
532 
533 	LOG_DBG("Object Action Control Point CCCD value: 0x%04X", value);
534 
535 	oacp_ind->is_enabled = false;
536 	if (value == BT_GATT_CCC_INDICATE) {
537 		oacp_ind->is_enabled = true;
538 	}
539 }
540