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