1 /** @file
2 * @brief Bluetooth Object Transfer Client
3 *
4 * Copyright (c) 2020-2022 Nordic Semiconductor ASA
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #include <zephyr/bluetooth/uuid.h>
10 #include <zephyr/kernel.h>
11 #include <zephyr/types.h>
12
13 #include <zephyr/device.h>
14 #include <zephyr/init.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/sys/check.h>
17
18 #include <zephyr/bluetooth/bluetooth.h>
19 #include <zephyr/bluetooth/conn.h>
20 #include <zephyr/bluetooth/gatt.h>
21 #include <zephyr/bluetooth/l2cap.h>
22
23 #include <zephyr/bluetooth/services/ots.h>
24 #include "ots_internal.h"
25 #include "ots_client_internal.h"
26 #include "ots_l2cap_internal.h"
27 #include "ots_dir_list_internal.h"
28 #include "ots_oacp_internal.h"
29 #include "ots_olcp_internal.h"
30
31 #define LOG_LEVEL CONFIG_BT_OTS_CLIENT_LOG_LEVEL
32 #include <zephyr/logging/log.h>
33 LOG_MODULE_REGISTER(bt_otc);
34
35 /* TODO: KConfig options */
36 #define OTS_CLIENT_INST_COUNT 1
37
38 #define OTS_CLIENT_MAX_WRITE_SIZE 23
39 /* 64-bit value, outside of 48-bit Object ID range */
40 #define OTS_CLIENT_UNKNOWN_ID 0x0001000000000000
41
42 struct dirlisting_record_t {
43 uint16_t len;
44 uint8_t flags;
45 uint8_t name_len;
46 struct bt_ots_obj_metadata metadata;
47 };
48
49 /**@brief String literals for the OACP result codes. Used for logging output.*/
50 static const char * const lit_request[] = {
51 "RFU",
52 "Create",
53 "Delete",
54 "Calculate Checksum",
55 "Execute",
56 "Read",
57 "Write",
58 "Abort",
59 };
60
61 /**@brief String literals for the OACP result codes. Used for logging output.*/
62 static const char * const lit_result[] = {
63 "RFU",
64 "Success",
65 "Op Code Not Supported",
66 "Invalid Parameter",
67 "Insufficient Resources",
68 "Invalid Object",
69 "Channel Unavailable",
70 "Unsupported Type",
71 "Procedure Not Permitted",
72 "Object Locked",
73 "Operation Failed"
74 };
75
76 /**@brief String literals for the OLCP request codes. Used for logging output.*/
77 static const char * const lit_olcp_request[] = {
78 "RFU",
79 "FIRST",
80 "LAST",
81 "PREV",
82 "NEXT",
83 "GOTO",
84 "ORDER",
85 "REQ_NUM_OBJS",
86 "CLEAR_MARKING",
87 };
88
89 /**@brief String literals for the OLCP result codes. Used for logging output.*/
90 static const char * const lit_olcp_result[] = {
91 "RFU",
92 "Success",
93 "Op Code Not Supported",
94 "Invalid Parameter",
95 "Operation Failed",
96 "Out of Bonds",
97 "Too Many Objects",
98 "No Object",
99 "Object ID not found",
100 };
101
102 struct bt_otc_internal_instance_t {
103 struct bt_ots_client *otc_inst;
104 struct bt_gatt_ots_l2cap l2cap_ctx;
105 bool busy;
106 /** Bitfield that is used to determine how much metadata to read */
107 uint8_t metadata_to_read;
108 /** Bitfield of how much metadata has been attempted to read */
109 uint8_t metadata_read_attempted;
110 /** Bitfield of how much metadata has been read */
111 uint8_t metadata_read;
112 int metadata_err;
113 uint32_t rcvd_size;
114 uint32_t sent_size;
115 };
116
117 /* The profile clients that uses the OTS are responsible for discovery and
118 * will simply register any OTS instances as pointers, which is stored here.
119 */
120 static struct bt_otc_internal_instance_t otc_insts[OTS_CLIENT_INST_COUNT];
121 NET_BUF_SIMPLE_DEFINE_STATIC(otc_tx_buf, OTS_CLIENT_MAX_WRITE_SIZE);
122 static struct bt_otc_internal_instance_t *cur_inst;
123
124 static int oacp_read(struct bt_conn *conn,
125 struct bt_otc_internal_instance_t *inst);
126 static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst,
127 const void *buf, uint32_t len, uint32_t offset,
128 enum bt_ots_oacp_write_op_mode mode);
129 static int oacp_checksum(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst,
130 uint32_t offset, uint32_t len);
131 static void read_next_metadata(struct bt_conn *conn,
132 struct bt_otc_internal_instance_t *inst);
133 static int read_attr(struct bt_conn *conn,
134 struct bt_otc_internal_instance_t *inst,
135 uint16_t handle, bt_gatt_read_func_t cb);
136
137 /* L2CAP callbacks */
tx_done(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn)138 static void tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
139 struct bt_conn *conn)
140 {
141 /* Not doing any writes yet */
142 LOG_ERR("Unexpected call, context: %p, conn: %p", l2cap_ctx, (void *)conn);
143 }
144
write_obj_tx_done(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn)145 static void write_obj_tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
146 struct bt_conn *conn)
147 {
148 int err;
149 size_t written;
150
151 if (cur_inst == NULL) {
152 LOG_ERR("OTS instance invalid\n");
153 return;
154 }
155
156 written = cur_inst->sent_size;
157 LOG_DBG("ctx: %p, conn: %p, written: %d", l2cap_ctx, (void *)conn, written);
158
159 err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
160 if (err < 0) {
161 LOG_WRN("Disconnecting L2CAP returned error %d", err);
162 }
163
164 if ((cur_inst->otc_inst != NULL) && (cur_inst->otc_inst->cb != NULL)) {
165 if (cur_inst->otc_inst->cb->obj_data_written) {
166 cur_inst->otc_inst->cb->obj_data_written(0, conn, written);
167 }
168 }
169
170 cur_inst = NULL;
171 }
172
rx_done(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn,struct net_buf * buf)173 static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
174 struct bt_conn *conn, struct net_buf *buf)
175 {
176 const uint32_t offset = cur_inst->rcvd_size;
177 bool is_complete = false;
178 const struct bt_ots_obj_metadata *cur_object =
179 &cur_inst->otc_inst->cur_object;
180 int cb_ret;
181
182 LOG_DBG("Incoming L2CAP data, context: %p, conn: %p, len: %u, offset: %u", l2cap_ctx,
183 (void *)conn, buf->len, offset);
184
185 cur_inst->rcvd_size += buf->len;
186
187 if (cur_inst->rcvd_size >= cur_object->size.cur) {
188 is_complete = true;
189 }
190
191 if (cur_inst->rcvd_size > cur_object->size.cur) {
192 LOG_WRN("Received %u but expected maximum %u", cur_inst->rcvd_size,
193 cur_object->size.cur);
194 }
195
196 cb_ret = cur_inst->otc_inst->cb->obj_data_read(0, conn, offset,
197 buf->len, buf->data,
198 is_complete);
199
200 if (is_complete) {
201 const uint32_t rcv_size = cur_object->size.cur;
202 int err;
203
204 LOG_DBG("Received the whole object (%u bytes). "
205 "Disconnecting L2CAP CoC", rcv_size);
206 err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
207 if (err < 0) {
208 LOG_WRN("Disconnecting L2CAP returned error %d", err);
209 }
210
211 cur_inst = NULL;
212 } else if (cb_ret == BT_OTS_STOP) {
213 const uint32_t rcv_size = cur_object->size.cur;
214 int err;
215
216 LOG_DBG("Stopped receiving after%u bytes. "
217 "Disconnecting L2CAP CoC", rcv_size);
218 err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
219
220 if (err < 0) {
221 LOG_WRN("Disconnecting L2CAP returned error %d", err);
222 }
223
224 cur_inst = NULL;
225 }
226
227 return 0;
228 }
229
chan_closed(struct bt_gatt_ots_l2cap * l2cap_ctx,struct bt_conn * conn)230 static void chan_closed(struct bt_gatt_ots_l2cap *l2cap_ctx,
231 struct bt_conn *conn)
232 {
233 LOG_DBG("L2CAP closed, context: %p, conn: %p", l2cap_ctx, (void *)conn);
234 if (cur_inst) {
235 cur_inst = NULL;
236 }
237 }
238 /* End L2CAP callbacks */
239
print_oacp_response(enum bt_gatt_ots_oacp_proc_type req_opcode,enum bt_gatt_ots_oacp_res_code result_code)240 static void print_oacp_response(enum bt_gatt_ots_oacp_proc_type req_opcode,
241 enum bt_gatt_ots_oacp_res_code result_code)
242 {
243 LOG_DBG("Request OP Code: %s", lit_request[req_opcode]);
244 LOG_DBG("Result Code : %s", lit_result[result_code]);
245 }
246
print_olcp_response(enum bt_gatt_ots_olcp_proc_type req_opcode,enum bt_gatt_ots_olcp_res_code result_code)247 static void print_olcp_response(enum bt_gatt_ots_olcp_proc_type req_opcode,
248 enum bt_gatt_ots_olcp_res_code result_code)
249 {
250 LOG_DBG("Request OP Code: %s", lit_olcp_request[req_opcode]);
251 LOG_DBG("Result Code : %s", lit_olcp_result[result_code]);
252 }
253
date_time_decode(struct net_buf_simple * buf,struct bt_ots_date_time * p_date_time)254 static void date_time_decode(struct net_buf_simple *buf,
255 struct bt_ots_date_time *p_date_time)
256 {
257 p_date_time->year = net_buf_simple_pull_le16(buf);
258 p_date_time->month = net_buf_simple_pull_u8(buf);
259 p_date_time->day = net_buf_simple_pull_u8(buf);
260 p_date_time->hours = net_buf_simple_pull_u8(buf);
261 p_date_time->minutes = net_buf_simple_pull_u8(buf);
262 p_date_time->seconds = net_buf_simple_pull_u8(buf);
263 }
264
lookup_inst_by_handle(uint16_t handle)265 static struct bt_otc_internal_instance_t *lookup_inst_by_handle(uint16_t handle)
266 {
267 for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
268 if (otc_insts[i].otc_inst &&
269 otc_insts[i].otc_inst->start_handle <= handle &&
270 otc_insts[i].otc_inst->end_handle >= handle) {
271 return &otc_insts[i];
272 }
273 }
274
275 LOG_DBG("Could not find OTS instance with handle 0x%04x", handle);
276
277 return NULL;
278 }
279
on_object_selected(struct bt_conn * conn,enum bt_gatt_ots_olcp_res_code res,struct bt_ots_client * otc_inst)280 static void on_object_selected(struct bt_conn *conn,
281 enum bt_gatt_ots_olcp_res_code res,
282 struct bt_ots_client *otc_inst)
283 {
284 memset(&otc_inst->cur_object, 0, sizeof(otc_inst->cur_object));
285 otc_inst->cur_object.id = OTS_CLIENT_UNKNOWN_ID;
286
287 if (otc_inst->cb->obj_selected) {
288 otc_inst->cb->obj_selected(otc_inst, conn, res);
289 }
290
291 LOG_DBG("Object selected");
292 }
293
olcp_ind_handler(struct bt_conn * conn,struct bt_ots_client * otc_inst,const void * data,uint16_t length)294 static void olcp_ind_handler(struct bt_conn *conn,
295 struct bt_ots_client *otc_inst,
296 const void *data, uint16_t length)
297 {
298 enum bt_gatt_ots_olcp_proc_type op_code;
299 struct net_buf_simple net_buf;
300
301 if (length < sizeof(op_code)) {
302 LOG_DBG("Invalid indication length: %u", length);
303 return;
304 }
305
306 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
307
308 op_code = net_buf_simple_pull_u8(&net_buf);
309
310 LOG_DBG("OLCP indication");
311
312 if (op_code == BT_GATT_OTS_OLCP_PROC_RESP) {
313 if (net_buf.len < (sizeof(uint8_t) + sizeof(uint8_t))) {
314 LOG_DBG("Invalid indication length for op_code %u: %u", op_code,
315 net_buf.len);
316 return;
317 }
318
319 enum bt_gatt_ots_olcp_proc_type req_opcode =
320 net_buf_simple_pull_u8(&net_buf);
321 enum bt_gatt_ots_olcp_res_code result_code =
322 net_buf_simple_pull_u8(&net_buf);
323
324 print_olcp_response(req_opcode, result_code);
325
326 switch (req_opcode) {
327 case BT_GATT_OTS_OLCP_PROC_FIRST:
328 LOG_DBG("First");
329 on_object_selected(conn, result_code, otc_inst);
330 break;
331 case BT_GATT_OTS_OLCP_PROC_LAST:
332 LOG_DBG("Last");
333 on_object_selected(conn, result_code, otc_inst);
334 break;
335 case BT_GATT_OTS_OLCP_PROC_PREV:
336 LOG_DBG("Previous");
337 on_object_selected(conn, result_code, otc_inst);
338 break;
339 case BT_GATT_OTS_OLCP_PROC_NEXT:
340 LOG_DBG("Next");
341 on_object_selected(conn, result_code, otc_inst);
342 break;
343 case BT_GATT_OTS_OLCP_PROC_GOTO:
344 LOG_DBG("Goto");
345 on_object_selected(conn, result_code, otc_inst);
346 break;
347 case BT_GATT_OTS_OLCP_PROC_ORDER:
348 LOG_DBG("Order");
349 on_object_selected(conn, result_code, otc_inst);
350 break;
351 case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS:
352 LOG_DBG("Request number of objects");
353 if (net_buf.len == sizeof(uint32_t)) {
354 uint32_t obj_cnt =
355 net_buf_simple_pull_le32(&net_buf);
356 LOG_DBG("Number of objects %u", obj_cnt);
357 }
358 break;
359 case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING:
360 LOG_DBG("Clear marking");
361 break;
362 default:
363 LOG_DBG("Invalid indication req opcode %u", req_opcode);
364 break;
365 }
366 } else {
367 LOG_DBG("Invalid indication opcode %u", op_code);
368 }
369 }
370
oacp_ind_handler(struct bt_conn * conn,struct bt_ots_client * otc_inst,const void * data,uint16_t length)371 static void oacp_ind_handler(struct bt_conn *conn,
372 struct bt_ots_client *otc_inst,
373 const void *data, uint16_t length)
374 {
375 enum bt_gatt_ots_oacp_proc_type op_code;
376 enum bt_gatt_ots_oacp_proc_type req_opcode;
377 enum bt_gatt_ots_oacp_res_code result_code;
378 uint32_t checksum;
379 struct net_buf_simple net_buf;
380
381 if (length < sizeof(op_code)) {
382 LOG_DBG("Invalid indication length: %u", length);
383 return;
384 }
385
386 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
387
388 op_code = net_buf_simple_pull_u8(&net_buf);
389
390 LOG_DBG("OACP indication");
391
392 if (op_code == BT_GATT_OTS_OACP_PROC_RESP) {
393 if (net_buf.len >= (sizeof(req_opcode) + sizeof(result_code))) {
394 req_opcode = net_buf_simple_pull_u8(&net_buf);
395 result_code = net_buf_simple_pull_u8(&net_buf);
396 } else {
397 LOG_ERR("Invalid indication data len %u", net_buf.len);
398 return;
399 }
400
401 if (req_opcode == BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC) {
402 if (net_buf.len == sizeof(checksum)) {
403 checksum = net_buf_simple_pull_le32(&net_buf);
404 LOG_DBG("Object checksum 0x%08x\n", checksum);
405 if (otc_inst->cb->obj_checksum_calculated) {
406 otc_inst->cb->obj_checksum_calculated(
407 otc_inst, conn, result_code, checksum);
408 }
409 } else {
410 LOG_ERR("Invalid indication data len %u after opcode and result "
411 "pulled", net_buf.len);
412 return;
413 }
414 }
415
416 print_oacp_response(req_opcode, result_code);
417 } else {
418 LOG_DBG("Invalid indication opcode %u", op_code);
419 }
420 }
421
bt_ots_client_indicate_handler(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)422 uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn,
423 struct bt_gatt_subscribe_params *params,
424 const void *data, uint16_t length)
425 {
426 uint16_t handle = params->value_handle;
427 struct bt_otc_internal_instance_t *inst;
428
429 if (conn == NULL) {
430 return BT_GATT_ITER_CONTINUE;
431 }
432
433 inst = lookup_inst_by_handle(handle);
434
435 /* TODO: Can we somehow avoid exposing this
436 * callback via the public API?
437 */
438
439 if (!inst) {
440 LOG_ERR("Instance not found");
441 return BT_GATT_ITER_STOP;
442 }
443
444 inst->busy = false;
445
446 if (data) {
447 if (handle == inst->otc_inst->olcp_handle) {
448 olcp_ind_handler(conn, inst->otc_inst, data, length);
449 } else if (handle == inst->otc_inst->oacp_handle) {
450 oacp_ind_handler(conn, inst->otc_inst, data, length);
451 }
452 }
453 return BT_GATT_ITER_CONTINUE;
454 }
455
read_feature_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)456 static uint8_t read_feature_cb(struct bt_conn *conn, uint8_t err,
457 struct bt_gatt_read_params *params,
458 const void *data, uint16_t length)
459 {
460 uint8_t cb_err = err;
461 struct bt_otc_internal_instance_t *inst =
462 lookup_inst_by_handle(params->single.handle);
463 struct net_buf_simple net_buf;
464
465 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
466
467 if (!inst) {
468 LOG_ERR("Instance not found");
469 return BT_GATT_ITER_STOP;
470 }
471
472 inst->busy = false;
473
474 if (err) {
475 LOG_DBG("err: 0x%02X", err);
476 } else if (data) {
477 if (length == OTS_FEATURE_LEN) {
478 inst->otc_inst->features.oacp =
479 net_buf_simple_pull_le32(&net_buf);
480
481 inst->otc_inst->features.olcp =
482 net_buf_simple_pull_le32(&net_buf);
483
484 LOG_DBG("features : oacp 0x%x, olcp 0x%x", inst->otc_inst->features.oacp,
485 inst->otc_inst->features.olcp);
486 } else {
487 LOG_DBG("Invalid length %u (expected %u)", length, OTS_FEATURE_LEN);
488 cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
489 }
490 }
491
492 return BT_GATT_ITER_STOP;
493 }
494
bt_ots_client_register(struct bt_ots_client * otc_inst)495 int bt_ots_client_register(struct bt_ots_client *otc_inst)
496 {
497 for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
498 int err;
499
500 if (otc_insts[i].otc_inst) {
501 continue;
502 }
503
504 LOG_DBG("%u", i);
505 err = bt_gatt_ots_l2cap_register(&otc_insts[i].l2cap_ctx);
506 if (err) {
507 LOG_WRN("Could not register L2CAP context %d", err);
508 return err;
509 }
510
511 otc_insts[i].otc_inst = otc_inst;
512 return 0;
513 }
514
515 return -ENOMEM;
516 }
517
bt_ots_client_unregister(uint8_t index)518 int bt_ots_client_unregister(uint8_t index)
519 {
520 if (index < ARRAY_SIZE(otc_insts)) {
521 memset(&otc_insts[index], 0, sizeof(otc_insts[index]));
522 } else {
523 return -EINVAL;
524 }
525
526 return 0;
527 }
528
bt_ots_client_read_feature(struct bt_ots_client * otc_inst,struct bt_conn * conn)529 int bt_ots_client_read_feature(struct bt_ots_client *otc_inst,
530 struct bt_conn *conn)
531 {
532 if (OTS_CLIENT_INST_COUNT > 0) {
533 struct bt_otc_internal_instance_t *inst;
534 int err;
535
536 if (!conn) {
537 LOG_WRN("Invalid Connection");
538 return -ENOTCONN;
539 } else if (!otc_inst) {
540 LOG_ERR("Invalid OTC instance");
541 return -EINVAL;
542 } else if (!otc_inst->feature_handle) {
543 LOG_DBG("Handle not set");
544 return -EINVAL;
545 }
546
547 inst = lookup_inst_by_handle(otc_inst->start_handle);
548
549 if (!inst) {
550 LOG_ERR("Invalid OTC instance");
551 return -EINVAL;
552 } else if (inst->busy) {
553 return -EBUSY;
554 }
555
556 otc_inst->read_proc.func = read_feature_cb;
557 otc_inst->read_proc.handle_count = 1;
558 otc_inst->read_proc.single.handle = otc_inst->feature_handle;
559 otc_inst->read_proc.single.offset = 0U;
560
561 err = bt_gatt_read(conn, &otc_inst->read_proc);
562 if (!err) {
563 inst->busy = true;
564 }
565 return err;
566 }
567
568 LOG_DBG("Not supported");
569 return -EOPNOTSUPP;
570 }
571
write_olcp_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_write_params * params)572 static void write_olcp_cb(struct bt_conn *conn, uint8_t err,
573 struct bt_gatt_write_params *params)
574 {
575 struct bt_otc_internal_instance_t *inst =
576 lookup_inst_by_handle(params->handle);
577
578 LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err);
579
580 if (!inst) {
581 LOG_ERR("Instance not found");
582 return;
583 }
584
585 inst->busy = false;
586 }
587
write_olcp(struct bt_otc_internal_instance_t * inst,struct bt_conn * conn,enum bt_gatt_ots_olcp_proc_type opcode,uint8_t * params,uint8_t param_len)588 static int write_olcp(struct bt_otc_internal_instance_t *inst,
589 struct bt_conn *conn, enum bt_gatt_ots_olcp_proc_type opcode,
590 uint8_t *params, uint8_t param_len)
591 {
592 int err;
593
594 net_buf_simple_reset(&otc_tx_buf);
595
596 net_buf_simple_add_u8(&otc_tx_buf, opcode);
597
598 if (param_len && params) {
599 net_buf_simple_add_mem(&otc_tx_buf, params, param_len);
600 }
601
602 inst->otc_inst->write_params.offset = 0;
603 inst->otc_inst->write_params.data = otc_tx_buf.data;
604 inst->otc_inst->write_params.length = otc_tx_buf.len;
605 inst->otc_inst->write_params.handle = inst->otc_inst->olcp_handle;
606 inst->otc_inst->write_params.func = write_olcp_cb;
607
608 err = bt_gatt_write(conn, &inst->otc_inst->write_params);
609
610 if (!err) {
611 inst->busy = true;
612 }
613 return err;
614 }
615
bt_ots_client_select_id(struct bt_ots_client * otc_inst,struct bt_conn * conn,uint64_t obj_id)616 int bt_ots_client_select_id(struct bt_ots_client *otc_inst,
617 struct bt_conn *conn,
618 uint64_t obj_id)
619 {
620 CHECKIF(!BT_OTS_VALID_OBJ_ID(obj_id)) {
621 LOG_DBG("Invalid object ID 0x%016llx", obj_id);
622
623 return -EINVAL;
624 }
625
626 if (OTS_CLIENT_INST_COUNT > 0) {
627 struct bt_otc_internal_instance_t *inst;
628 uint8_t param[BT_OTS_OBJ_ID_SIZE];
629
630 if (!conn) {
631 LOG_WRN("Invalid Connection");
632 return -ENOTCONN;
633 } else if (!otc_inst) {
634 LOG_ERR("Invalid OTC instance");
635 return -EINVAL;
636 } else if (!otc_inst->olcp_handle) {
637 LOG_DBG("Handle not set");
638 return -EINVAL;
639 }
640
641 inst = lookup_inst_by_handle(otc_inst->start_handle);
642
643 if (!inst) {
644 LOG_ERR("Invalid OTC instance");
645 return -EINVAL;
646 } else if (inst->busy) {
647 return -EBUSY;
648 }
649
650 /* TODO: Should not update this before ID is read */
651 otc_inst->cur_object.id = obj_id;
652 sys_put_le48(obj_id, param);
653
654 return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_GOTO,
655 param, BT_OTS_OBJ_ID_SIZE);
656 }
657
658 LOG_DBG("Not supported");
659 return -EOPNOTSUPP;
660 }
661
bt_ots_client_select_first(struct bt_ots_client * otc_inst,struct bt_conn * conn)662 int bt_ots_client_select_first(struct bt_ots_client *otc_inst,
663 struct bt_conn *conn)
664 {
665 if (OTS_CLIENT_INST_COUNT > 0) {
666 struct bt_otc_internal_instance_t *inst;
667
668 if (!conn) {
669 LOG_WRN("Invalid Connection");
670 return -ENOTCONN;
671 } else if (!otc_inst) {
672 LOG_ERR("Invalid OTC instance");
673 return -EINVAL;
674 } else if (!otc_inst->olcp_handle) {
675 LOG_DBG("Handle not set");
676 return -EINVAL;
677 }
678
679 inst = lookup_inst_by_handle(otc_inst->start_handle);
680
681 if (!inst) {
682 LOG_ERR("Invalid OTC instance");
683 return -EINVAL;
684 } else if (inst->busy) {
685 return -EBUSY;
686 }
687
688 return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_FIRST,
689 NULL, 0);
690 }
691
692 LOG_DBG("Not supported");
693 return -EOPNOTSUPP;
694 }
695
bt_ots_client_select_last(struct bt_ots_client * otc_inst,struct bt_conn * conn)696 int bt_ots_client_select_last(struct bt_ots_client *otc_inst,
697 struct bt_conn *conn)
698 {
699 if (OTS_CLIENT_INST_COUNT > 0) {
700 struct bt_otc_internal_instance_t *inst;
701
702 if (!conn) {
703 LOG_WRN("Invalid Connection");
704 return -ENOTCONN;
705 } else if (!otc_inst) {
706 LOG_ERR("Invalid OTC instance");
707 return -EINVAL;
708 } else if (!otc_inst->olcp_handle) {
709 LOG_DBG("Handle not set");
710 return -EINVAL;
711 }
712
713 inst = lookup_inst_by_handle(otc_inst->start_handle);
714
715 if (!inst) {
716 LOG_ERR("Invalid OTC instance");
717 return -EINVAL;
718 } else if (inst->busy) {
719 return -EBUSY;
720 }
721
722 return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_LAST,
723 NULL, 0);
724
725 }
726
727 LOG_DBG("Not supported");
728 return -EOPNOTSUPP;
729 }
730
bt_ots_client_select_next(struct bt_ots_client * otc_inst,struct bt_conn * conn)731 int bt_ots_client_select_next(struct bt_ots_client *otc_inst,
732 struct bt_conn *conn)
733 {
734 if (OTS_CLIENT_INST_COUNT > 0) {
735 struct bt_otc_internal_instance_t *inst;
736
737 if (!conn) {
738 LOG_WRN("Invalid Connection");
739 return -ENOTCONN;
740 } else if (!otc_inst) {
741 LOG_ERR("Invalid OTC instance");
742 return -EINVAL;
743 } else if (!otc_inst->olcp_handle) {
744 LOG_DBG("Handle not set");
745 return -EINVAL;
746 }
747
748 inst = lookup_inst_by_handle(otc_inst->start_handle);
749
750 if (!inst) {
751 LOG_ERR("Invalid OTC instance");
752 return -EINVAL;
753 } else if (inst->busy) {
754 return -EBUSY;
755 }
756
757 return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_NEXT,
758 NULL, 0);
759 }
760
761 LOG_DBG("Not supported");
762 return -EOPNOTSUPP;
763 }
764
bt_ots_client_select_prev(struct bt_ots_client * otc_inst,struct bt_conn * conn)765 int bt_ots_client_select_prev(struct bt_ots_client *otc_inst,
766 struct bt_conn *conn)
767 {
768 if (OTS_CLIENT_INST_COUNT > 0) {
769 struct bt_otc_internal_instance_t *inst;
770
771 if (!conn) {
772 LOG_WRN("Invalid Connection");
773 return -ENOTCONN;
774 } else if (!otc_inst) {
775 LOG_ERR("Invalid OTC instance");
776 return -EINVAL;
777 } else if (!otc_inst->olcp_handle) {
778 LOG_DBG("Handle not set");
779 return -EINVAL;
780 }
781
782 inst = lookup_inst_by_handle(otc_inst->start_handle);
783
784 if (!inst) {
785 LOG_ERR("Invalid OTC instance");
786 return -EINVAL;
787 } else if (inst->busy) {
788 return -EBUSY;
789 }
790
791 return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_PREV,
792 NULL, 0);
793 }
794
795 LOG_DBG("Not supported");
796 return -EOPNOTSUPP;
797 }
798
read_object_size_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)799 static uint8_t read_object_size_cb(struct bt_conn *conn, uint8_t err,
800 struct bt_gatt_read_params *params,
801 const void *data, uint16_t length)
802 {
803 struct bt_otc_internal_instance_t *inst =
804 lookup_inst_by_handle(params->single.handle);
805 struct net_buf_simple net_buf;
806
807 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
808
809 LOG_DBG("handle %d, length %u", params->single.handle, length);
810
811 if (!inst) {
812 LOG_ERR("Instance not found");
813 return BT_GATT_ITER_STOP;
814 }
815
816 if (err) {
817 LOG_DBG("err: 0x%02X", err);
818 } else if (data) {
819 if (length != OTS_SIZE_LEN) {
820 LOG_DBG("Invalid length %u (expected %u)", length, OTS_SIZE_LEN);
821 err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
822 } else {
823 struct bt_ots_obj_metadata *cur_object =
824 &inst->otc_inst->cur_object;
825 cur_object->size.cur =
826 net_buf_simple_pull_le32(&net_buf);
827 cur_object->size.alloc =
828 net_buf_simple_pull_le32(&net_buf);
829
830 LOG_DBG("Object Size : current size %u, "
831 "allocated size %u",
832 cur_object->size.cur,
833 cur_object->size.alloc);
834
835 if (cur_object->size.cur == 0) {
836 LOG_WRN("Obj size read returned a current "
837 "size of 0");
838 } else if (cur_object->size.cur >
839 cur_object->size.alloc &&
840 cur_object->size.alloc != 0) {
841 LOG_WRN("Allocated size %u is smaller than "
842 "current size %u",
843 cur_object->size.alloc,
844 cur_object->size.cur);
845 }
846
847 BT_OTS_SET_METADATA_REQ_SIZE(inst->metadata_read);
848 }
849 }
850
851 if (err) {
852 LOG_WRN("err: 0x%02X", err);
853 if (!inst->metadata_err) {
854 inst->metadata_err = err;
855 }
856 }
857
858 read_next_metadata(conn, inst);
859
860 return BT_GATT_ITER_STOP;
861 }
862
read_obj_id_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)863 static uint8_t read_obj_id_cb(struct bt_conn *conn, uint8_t err,
864 struct bt_gatt_read_params *params,
865 const void *data, uint16_t length)
866 {
867 struct bt_otc_internal_instance_t *inst =
868 lookup_inst_by_handle(params->single.handle);
869 struct net_buf_simple net_buf;
870
871 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
872
873 LOG_DBG("handle %d, length %u", params->single.handle, length);
874
875 if (!inst) {
876 LOG_ERR("Instance not found");
877 return BT_GATT_ITER_STOP;
878 }
879
880 if (err) {
881 LOG_DBG("err: 0x%02X", err);
882 } else if (data) {
883 if (length == BT_OTS_OBJ_ID_SIZE) {
884 uint64_t obj_id = net_buf_simple_pull_le48(&net_buf);
885 char t[BT_OTS_OBJ_ID_STR_LEN];
886 struct bt_ots_obj_metadata *cur_object =
887 &inst->otc_inst->cur_object;
888
889 (void)bt_ots_obj_id_to_str(obj_id, t, sizeof(t));
890 LOG_DBG("Object Id : %s", t);
891
892 if (cur_object->id != OTS_CLIENT_UNKNOWN_ID &&
893 cur_object->id != obj_id) {
894 char str[BT_OTS_OBJ_ID_STR_LEN];
895
896 (void)bt_ots_obj_id_to_str(cur_object->id, str,
897 sizeof(str));
898 LOG_INF("Read Obj Id %s not selected obj Id %s", t, str);
899 } else {
900 LOG_INF("Read Obj Id confirmed correct Obj Id");
901 cur_object->id = obj_id;
902
903 BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read);
904 }
905 } else {
906 LOG_DBG("Invalid length %u (expected %u)", length, BT_OTS_OBJ_ID_SIZE);
907 err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
908 }
909 }
910
911 if (err) {
912 LOG_WRN("err: 0x%02X", err);
913 if (!inst->metadata_err) {
914 inst->metadata_err = err;
915 }
916 }
917
918 read_next_metadata(conn, inst);
919
920 return BT_GATT_ITER_STOP;
921 }
922
read_obj_name_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)923 static uint8_t read_obj_name_cb(struct bt_conn *conn, uint8_t err,
924 struct bt_gatt_read_params *params,
925 const void *data, uint16_t length)
926 {
927 struct bt_otc_internal_instance_t *inst =
928 lookup_inst_by_handle(params->single.handle);
929
930 LOG_DBG("handle %d, length %u", params->single.handle, length);
931
932 if (!inst) {
933 LOG_ERR("Instance not found");
934 return BT_GATT_ITER_STOP;
935 }
936
937 if (data) {
938 if (length <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
939 memcpy(inst->otc_inst->cur_object.name_c, data, length);
940 inst->otc_inst->cur_object.name_c[length] = '\0';
941 } else {
942 LOG_WRN("Invalid length %u (expected max %u)", length,
943 CONFIG_BT_OTS_OBJ_MAX_NAME_LEN);
944 err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
945 }
946 }
947
948 if (err) {
949 LOG_WRN("err: 0x%02X", err);
950 if (!inst->metadata_err) {
951 inst->metadata_err = err;
952 }
953 }
954
955 read_next_metadata(conn, inst);
956
957 return BT_GATT_ITER_STOP;
958 }
959
read_obj_type_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)960 static uint8_t read_obj_type_cb(struct bt_conn *conn, uint8_t err,
961 struct bt_gatt_read_params *params,
962 const void *data, uint16_t length)
963 {
964 struct bt_otc_internal_instance_t *inst =
965 lookup_inst_by_handle(params->single.handle);
966
967 LOG_DBG("handle %d, length %u", params->single.handle, length);
968
969 if (!inst) {
970 LOG_ERR("Instance not found");
971 return BT_GATT_ITER_STOP;
972 }
973
974 if (data) {
975 if (length == BT_UUID_SIZE_128 || length == BT_UUID_SIZE_16) {
976 char uuid_str[BT_UUID_STR_LEN];
977 struct bt_uuid *uuid =
978 &inst->otc_inst->cur_object.type.uuid;
979
980 if (!bt_uuid_create(uuid, data, length)) {
981 return BT_GATT_ITER_STOP;
982 }
983
984 bt_uuid_to_str(uuid, uuid_str, sizeof(uuid_str));
985 LOG_DBG("UUID type read: %s", uuid_str);
986
987 BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read);
988 } else {
989 LOG_WRN("Invalid length %u (expected max %u)", length, OTS_TYPE_MAX_LEN);
990 err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
991 }
992 }
993
994 if (err) {
995 LOG_WRN("err: 0x%02X", err);
996 if (!inst->metadata_err) {
997 inst->metadata_err = err;
998 }
999 }
1000
1001 read_next_metadata(conn, inst);
1002
1003 return BT_GATT_ITER_STOP;
1004 }
1005
1006
read_obj_created_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)1007 static uint8_t read_obj_created_cb(struct bt_conn *conn, uint8_t err,
1008 struct bt_gatt_read_params *params,
1009 const void *data, uint16_t length)
1010 {
1011 struct bt_otc_internal_instance_t *inst =
1012 lookup_inst_by_handle(params->single.handle);
1013 struct net_buf_simple net_buf;
1014
1015 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
1016
1017 LOG_DBG("handle %d, length %u", params->single.handle, length);
1018
1019 if (!inst) {
1020 LOG_ERR("Instance not found");
1021 return BT_GATT_ITER_STOP;
1022 }
1023
1024 if (data) {
1025 if (length <= BT_OTS_DATE_TIME_FIELD_SIZE) {
1026 date_time_decode(
1027 &net_buf,
1028 &inst->otc_inst->cur_object.first_created);
1029 } else {
1030 LOG_WRN("Invalid length %u (expected max %u)", length,
1031 BT_OTS_DATE_TIME_FIELD_SIZE);
1032 err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
1033 }
1034 }
1035
1036 if (err) {
1037 LOG_WRN("err: 0x%02X", err);
1038 if (!inst->metadata_err) {
1039 inst->metadata_err = err;
1040 }
1041 }
1042
1043 read_next_metadata(conn, inst);
1044
1045 return BT_GATT_ITER_STOP;
1046 }
1047
read_obj_modified_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)1048 static uint8_t read_obj_modified_cb(struct bt_conn *conn, uint8_t err,
1049 struct bt_gatt_read_params *params,
1050 const void *data, uint16_t length)
1051 {
1052 struct bt_otc_internal_instance_t *inst =
1053 lookup_inst_by_handle(params->single.handle);
1054 struct net_buf_simple net_buf;
1055
1056 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
1057
1058 LOG_DBG("handle %d, length %u", params->single.handle, length);
1059
1060 if (!inst) {
1061 LOG_ERR("Instance not found");
1062 return BT_GATT_ITER_STOP;
1063 }
1064
1065 if (data) {
1066 if (length <= BT_OTS_DATE_TIME_FIELD_SIZE) {
1067 date_time_decode(&net_buf,
1068 &inst->otc_inst->cur_object.modified);
1069 } else {
1070 LOG_WRN("Invalid length %u (expected max %u)", length,
1071 BT_OTS_DATE_TIME_FIELD_SIZE);
1072 err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
1073 }
1074 }
1075
1076 if (err) {
1077 LOG_WRN("err: 0x%02X", err);
1078 if (!inst->metadata_err) {
1079 inst->metadata_err = err;
1080 }
1081 }
1082
1083 read_next_metadata(conn, inst);
1084
1085 return BT_GATT_ITER_STOP;
1086 }
1087
read_attr(struct bt_conn * conn,struct bt_otc_internal_instance_t * inst,uint16_t handle,bt_gatt_read_func_t cb)1088 static int read_attr(struct bt_conn *conn,
1089 struct bt_otc_internal_instance_t *inst,
1090 uint16_t handle, bt_gatt_read_func_t cb)
1091 {
1092 if (!handle) {
1093 LOG_DBG("Handle not set");
1094 return -EINVAL;
1095 } else if (cb == NULL) {
1096 LOG_ERR("No callback set");
1097 return -EINVAL;
1098 }
1099
1100 /* TODO: With EATT we can request multiple metadata to be read at once*/
1101 inst->otc_inst->read_proc.func = cb;
1102 inst->otc_inst->read_proc.handle_count = 1;
1103 inst->otc_inst->read_proc.single.handle = handle;
1104 inst->otc_inst->read_proc.single.offset = 0;
1105
1106 return bt_gatt_read(conn, &inst->otc_inst->read_proc);
1107 }
1108
read_obj_properties_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)1109 static uint8_t read_obj_properties_cb(struct bt_conn *conn, uint8_t err,
1110 struct bt_gatt_read_params *params,
1111 const void *data, uint16_t length)
1112 {
1113 uint8_t cb_err = err;
1114 struct bt_otc_internal_instance_t *inst =
1115 lookup_inst_by_handle(params->single.handle);
1116 struct net_buf_simple net_buf;
1117
1118 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
1119
1120 LOG_INF("handle %d, length %u", params->single.handle, length);
1121
1122 if (!inst) {
1123 LOG_ERR("Instance not found");
1124 return BT_GATT_ITER_STOP;
1125 }
1126
1127 if (err) {
1128 LOG_WRN("err: 0x%02X", err);
1129 } else if (data && length <= OTS_PROPERTIES_LEN) {
1130 struct bt_ots_obj_metadata *cur_object =
1131 &inst->otc_inst->cur_object;
1132
1133 cur_object->props = net_buf_simple_pull_le32(&net_buf);
1134
1135 LOG_INF("Object properties (raw) : 0x%x", cur_object->props);
1136
1137 if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) {
1138 LOG_WRN("Obj properties: Obj read not supported");
1139 }
1140
1141 BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read);
1142 } else {
1143 LOG_WRN("Invalid length %u (expected %u)", length, OTS_PROPERTIES_LEN);
1144 cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
1145 }
1146
1147 if (err) {
1148 LOG_WRN("err: 0x%02X", err);
1149 if (!inst->metadata_err) {
1150 inst->metadata_err = err;
1151 }
1152 }
1153
1154 read_next_metadata(conn, inst);
1155
1156 return BT_GATT_ITER_STOP;
1157 }
1158
write_oacp_cp_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_write_params * params)1159 static void write_oacp_cp_cb(struct bt_conn *conn, uint8_t err,
1160 struct bt_gatt_write_params *params)
1161 {
1162 struct bt_otc_internal_instance_t *inst =
1163 lookup_inst_by_handle(params->handle);
1164
1165 LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err);
1166
1167 if (!inst) {
1168 LOG_ERR("Instance not found");
1169 return;
1170 }
1171
1172 inst->busy = false;
1173 }
1174
write_oacp_cp_write_req_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_write_params * params)1175 static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
1176 struct bt_gatt_write_params *params)
1177 {
1178 struct bt_otc_internal_instance_t *inst =
1179 lookup_inst_by_handle(params->handle);
1180 uint32_t len;
1181
1182 LOG_DBG("Write Object request %s (0x%02X)", err ? "failed" : "successful", err);
1183 if (!inst) {
1184 LOG_ERR("Instance not found");
1185 return;
1186 }
1187
1188 len = inst->l2cap_ctx.tx.len;
1189 inst->l2cap_ctx.tx.len = 0;
1190 err = bt_gatt_ots_l2cap_send(&inst->l2cap_ctx, inst->l2cap_ctx.tx.data, len);
1191 if (err) {
1192 LOG_WRN("L2CAP CoC error: %d while trying to execute OACP "
1193 "Read procedure", err);
1194 }
1195
1196 inst->busy = false;
1197 }
1198
oacp_read(struct bt_conn * conn,struct bt_otc_internal_instance_t * inst)1199 static int oacp_read(struct bt_conn *conn,
1200 struct bt_otc_internal_instance_t *inst)
1201 {
1202 int err;
1203 uint32_t offset = 0;
1204 uint32_t length;
1205 struct bt_gatt_ots_l2cap *l2cap;
1206
1207 if (!inst->otc_inst->oacp_handle) {
1208 LOG_DBG("Handle not set");
1209 return -EINVAL;
1210 } else if (inst->busy) {
1211 return -EBUSY;
1212 } else if (cur_inst) {
1213 return -EBUSY;
1214 }
1215
1216 /* TODO: How do we ensure that the L2CAP is connected in time for the
1217 * transfer?
1218 */
1219
1220 err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
1221 if (err) {
1222 LOG_DBG("Could not connect l2cap: %d", err);
1223 return err;
1224 }
1225
1226 l2cap->tx_done = tx_done;
1227 l2cap->rx_done = rx_done;
1228 l2cap->closed = chan_closed;
1229
1230 net_buf_simple_reset(&otc_tx_buf);
1231
1232 /* OP Code */
1233 net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_READ);
1234
1235 /* Offset */
1236 net_buf_simple_add_le32(&otc_tx_buf, offset);
1237
1238 /* Len */
1239 length = inst->otc_inst->cur_object.size.cur - offset;
1240 net_buf_simple_add_le32(&otc_tx_buf, length);
1241
1242 inst->otc_inst->write_params.offset = 0;
1243 inst->otc_inst->write_params.data = otc_tx_buf.data;
1244 inst->otc_inst->write_params.length = otc_tx_buf.len;
1245 inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle;
1246 inst->otc_inst->write_params.func = write_oacp_cp_cb;
1247
1248 err = bt_gatt_write(conn, &inst->otc_inst->write_params);
1249
1250 if (!err) {
1251 inst->busy = true;
1252 }
1253
1254 cur_inst = inst;
1255 inst->rcvd_size = 0;
1256
1257 return err;
1258 }
1259
oacp_write(struct bt_conn * conn,struct bt_otc_internal_instance_t * inst,const void * buf,uint32_t len,uint32_t offset,enum bt_ots_oacp_write_op_mode mode)1260 static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst,
1261 const void *buf, uint32_t len, uint32_t offset,
1262 enum bt_ots_oacp_write_op_mode mode)
1263 {
1264 int err;
1265 struct bt_gatt_ots_l2cap *l2cap;
1266
1267 if (!inst->otc_inst->oacp_handle) {
1268 LOG_DBG("Handle not set");
1269 return -EINVAL;
1270 } else if (inst->busy) {
1271 return -EBUSY;
1272 } else if (cur_inst) {
1273 return -EBUSY;
1274 }
1275
1276 err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
1277 if (err) {
1278 LOG_DBG("Could not connect l2cap: %d", err);
1279 return err;
1280 }
1281
1282 l2cap->tx_done = write_obj_tx_done;
1283 l2cap->rx_done = rx_done;
1284 l2cap->closed = chan_closed;
1285 l2cap->tx.data = (uint8_t *)buf;
1286 l2cap->tx.len = len;
1287 net_buf_simple_reset(&otc_tx_buf);
1288
1289 /* OP Code */
1290 net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_WRITE);
1291
1292 /* Offset */
1293 net_buf_simple_add_le32(&otc_tx_buf, offset);
1294
1295 /* Len */
1296 net_buf_simple_add_le32(&otc_tx_buf, len);
1297
1298 /* Mode, truncate or not */
1299 net_buf_simple_add_u8(&otc_tx_buf, mode);
1300
1301 inst->otc_inst->write_params.offset = 0;
1302 inst->otc_inst->write_params.data = otc_tx_buf.data;
1303 inst->otc_inst->write_params.length = otc_tx_buf.len;
1304 inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle;
1305 inst->otc_inst->write_params.func = write_oacp_cp_write_req_cb;
1306 inst->sent_size = len;
1307 err = bt_gatt_write(conn, &inst->otc_inst->write_params);
1308
1309 if (!err) {
1310 inst->busy = true;
1311 cur_inst = inst;
1312 }
1313
1314 inst->rcvd_size = 0;
1315
1316 return err;
1317 }
1318
oacp_checksum(struct bt_conn * conn,struct bt_otc_internal_instance_t * inst,uint32_t offset,uint32_t len)1319 static int oacp_checksum(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst,
1320 uint32_t offset, uint32_t len)
1321 {
1322 int err;
1323
1324 if (!inst->otc_inst->oacp_handle) {
1325 LOG_DBG("Handle not set");
1326 return -EINVAL;
1327 } else if (inst->busy) {
1328 LOG_DBG("Client is busy");
1329 return -EBUSY;
1330 } else if (cur_inst) {
1331 LOG_DBG("Previous operation is not finished");
1332 return -EBUSY;
1333 }
1334
1335 net_buf_simple_reset(&otc_tx_buf);
1336
1337 /* OP Code */
1338 net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC);
1339
1340 /* Offset */
1341 net_buf_simple_add_le32(&otc_tx_buf, offset);
1342
1343 /* Len */
1344 net_buf_simple_add_le32(&otc_tx_buf, len);
1345
1346 inst->otc_inst->write_params.offset = 0;
1347 inst->otc_inst->write_params.data = otc_tx_buf.data;
1348 inst->otc_inst->write_params.length = otc_tx_buf.len;
1349 inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle;
1350 inst->otc_inst->write_params.func = write_oacp_cp_cb;
1351
1352 err = bt_gatt_write(conn, &inst->otc_inst->write_params);
1353 if (err != 0) {
1354 inst->busy = true;
1355 cur_inst = inst;
1356 }
1357
1358 return err;
1359 }
1360
bt_ots_client_read_object_data(struct bt_ots_client * otc_inst,struct bt_conn * conn)1361 int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst,
1362 struct bt_conn *conn)
1363 {
1364 struct bt_otc_internal_instance_t *inst;
1365
1366 if (!conn) {
1367 LOG_WRN("Invalid Connection");
1368 return -ENOTCONN;
1369 } else if (!otc_inst) {
1370 LOG_ERR("Invalid OTC instance");
1371 return -EINVAL;
1372 }
1373
1374 inst = lookup_inst_by_handle(otc_inst->start_handle);
1375
1376 if (!inst) {
1377 LOG_ERR("Invalid OTC instance");
1378 return -EINVAL;
1379 }
1380
1381 if (otc_inst->cur_object.size.cur == 0) {
1382 LOG_WRN("Unknown object size");
1383 return -EINVAL;
1384 }
1385
1386 return oacp_read(conn, inst);
1387 }
1388
bt_ots_client_write_object_data(struct bt_ots_client * otc_inst,struct bt_conn * conn,const void * buf,size_t len,off_t offset,enum bt_ots_oacp_write_op_mode mode)1389 int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst,
1390 struct bt_conn *conn, const void *buf, size_t len,
1391 off_t offset, enum bt_ots_oacp_write_op_mode mode)
1392 {
1393 struct bt_otc_internal_instance_t *inst;
1394
1395 CHECKIF(!conn) {
1396 LOG_WRN("Invalid Connection");
1397 return -ENOTCONN;
1398 }
1399
1400 CHECKIF(!otc_inst) {
1401 LOG_ERR("Invalid OTC instance");
1402 return -EINVAL;
1403 }
1404
1405 CHECKIF((mode != BT_OTS_OACP_WRITE_OP_MODE_NONE) &&
1406 (mode != BT_OTS_OACP_WRITE_OP_MODE_TRUNCATE)) {
1407 LOG_ERR("Invalid write object mode parameter %d", mode);
1408 return -EINVAL;
1409 }
1410
1411 /* OTS_v10.pdf Table 3.9: Object Action Control Point Procedure Requirements
1412 * Offset and Length field are UINT32 Length
1413 */
1414 CHECKIF(len > UINT32_MAX) {
1415 LOG_ERR("length %zu exceeds UINT32", len);
1416 return -EINVAL;
1417 }
1418
1419 CHECKIF(len == 0) {
1420 LOG_ERR("length equals zero");
1421 return -EINVAL;
1422 }
1423
1424 CHECKIF((sizeof(offset) > sizeof(uint32_t) && (offset > UINT32_MAX)) || (offset < 0)) {
1425 LOG_ERR("offset %ld exceeds UINT32 and must be >= 0", offset);
1426 return -EINVAL;
1427 }
1428
1429 CHECKIF(offset > otc_inst->cur_object.size.cur) {
1430 LOG_ERR("offset %ld exceeds cur size %zu", offset, otc_inst->cur_object.size.cur);
1431 return -EINVAL;
1432 }
1433
1434 CHECKIF((offset < otc_inst->cur_object.size.cur) &&
1435 !BT_OTS_OBJ_GET_PROP_PATCH(otc_inst->cur_object.props)) {
1436 LOG_ERR("Patch is not supported");
1437 return -EACCES;
1438 }
1439
1440 CHECKIF(((len + offset) > otc_inst->cur_object.size.alloc) &&
1441 !BT_OTS_OBJ_GET_PROP_APPEND(otc_inst->cur_object.props)) {
1442 LOG_ERR("APPEND is not supported. Invalid new end of object %lu alloc %zu."
1443 , (len + offset), otc_inst->cur_object.size.alloc);
1444 return -EINVAL;
1445 }
1446
1447 inst = lookup_inst_by_handle(otc_inst->start_handle);
1448
1449 if (!inst) {
1450 LOG_ERR("Invalid OTC instance");
1451 return -EINVAL;
1452 }
1453
1454 return oacp_write(conn, inst, buf, (uint32_t)len, (uint32_t)offset, mode);
1455 }
1456
bt_ots_client_get_object_checksum(struct bt_ots_client * otc_inst,struct bt_conn * conn,off_t offset,size_t len)1457 int bt_ots_client_get_object_checksum(struct bt_ots_client *otc_inst, struct bt_conn *conn,
1458 off_t offset, size_t len)
1459 {
1460 struct bt_otc_internal_instance_t *inst;
1461
1462 CHECKIF(!conn) {
1463 LOG_DBG("Invalid Connection");
1464 return -ENOTCONN;
1465 }
1466
1467 CHECKIF(!otc_inst) {
1468 LOG_DBG("Invalid OTC instance");
1469 return -EINVAL;
1470 }
1471
1472 /* OTS_v10.pdf Table 3.9: Object Action Control Point Procedure Requirements
1473 * Offset and Length field are UINT32 Length
1474 */
1475 CHECKIF(len > UINT32_MAX) {
1476 LOG_DBG("length %zu exceeds UINT32", len);
1477 return -EINVAL;
1478 }
1479
1480 CHECKIF(len == 0) {
1481 LOG_DBG("length equals zero");
1482 return -EINVAL;
1483 }
1484
1485 CHECKIF((sizeof(offset) > sizeof(uint32_t) && (offset > UINT32_MAX)) || (offset < 0)) {
1486 LOG_DBG("offset exceeds %ld UINT32 and must be >= 0", offset);
1487 return -EINVAL;
1488 }
1489
1490 CHECKIF((len + offset) > otc_inst->cur_object.size.cur) {
1491 LOG_DBG("The sum of offset (%ld) and length (%zu) exceed the Current Size %lu "
1492 "alloc %zu.", offset, len, (len + offset), otc_inst->cur_object.size.cur);
1493 return -EINVAL;
1494 }
1495
1496 inst = lookup_inst_by_handle(otc_inst->start_handle);
1497 if (!inst) {
1498 LOG_DBG("Invalid OTC instance");
1499 return -EINVAL;
1500 }
1501
1502 return oacp_checksum(conn, inst, (uint32_t)offset, (uint32_t)len);
1503 }
1504
read_next_metadata(struct bt_conn * conn,struct bt_otc_internal_instance_t * inst)1505 static void read_next_metadata(struct bt_conn *conn,
1506 struct bt_otc_internal_instance_t *inst)
1507 {
1508 uint8_t metadata_remaining =
1509 inst->metadata_to_read ^ inst->metadata_read_attempted;
1510 int err = 0;
1511
1512 LOG_DBG("Attempting to read metadata 0x%02X", metadata_remaining);
1513
1514 if (BT_OTS_GET_METADATA_REQ_NAME(metadata_remaining)) {
1515 BT_OTS_SET_METADATA_REQ_NAME(inst->metadata_read_attempted);
1516 err = read_attr(conn, inst, inst->otc_inst->obj_name_handle,
1517 read_obj_name_cb);
1518 } else if (BT_OTS_GET_METADATA_REQ_TYPE(metadata_remaining)) {
1519 BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read_attempted);
1520 err = read_attr(conn, inst, inst->otc_inst->obj_type_handle,
1521 read_obj_type_cb);
1522 } else if (BT_OTS_GET_METADATA_REQ_SIZE(metadata_remaining)) {
1523 BT_OTS_SET_METADATA_REQ_SIZE(inst->metadata_read_attempted);
1524 err = read_attr(conn, inst, inst->otc_inst->obj_size_handle,
1525 read_object_size_cb);
1526 } else if (BT_OTS_GET_METADATA_REQ_CREATED(metadata_remaining)) {
1527 BT_OTS_SET_METADATA_REQ_CREATED(inst->metadata_read_attempted);
1528 err = read_attr(conn, inst, inst->otc_inst->obj_created_handle,
1529 read_obj_created_cb);
1530 } else if (BT_OTS_GET_METADATA_REQ_MODIFIED(metadata_remaining)) {
1531 BT_OTS_SET_METADATA_REQ_MODIFIED(inst->metadata_read_attempted);
1532 err = read_attr(conn, inst, inst->otc_inst->obj_modified_handle,
1533 read_obj_modified_cb);
1534 } else if (BT_OTS_GET_METADATA_REQ_ID(metadata_remaining)) {
1535 BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read_attempted);
1536 err = read_attr(conn, inst, inst->otc_inst->obj_id_handle,
1537 read_obj_id_cb);
1538 } else if (BT_OTS_GET_METADATA_REQ_PROPS(metadata_remaining)) {
1539 BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read_attempted);
1540 err = read_attr(conn, inst,
1541 inst->otc_inst->obj_properties_handle,
1542 read_obj_properties_cb);
1543 } else {
1544 inst->busy = false;
1545 if (inst->otc_inst->cb->obj_metadata_read) {
1546 inst->otc_inst->cb->obj_metadata_read(
1547 inst->otc_inst, conn, inst->metadata_err,
1548 inst->metadata_read);
1549 }
1550 return;
1551 }
1552
1553 if (err) {
1554 LOG_DBG("Metadata read failed (%d), trying next", err);
1555 read_next_metadata(conn, inst);
1556 }
1557 }
1558
bt_ots_client_read_object_metadata(struct bt_ots_client * otc_inst,struct bt_conn * conn,uint8_t metadata)1559 int bt_ots_client_read_object_metadata(struct bt_ots_client *otc_inst,
1560 struct bt_conn *conn,
1561 uint8_t metadata)
1562 {
1563 struct bt_otc_internal_instance_t *inst;
1564
1565 if (!conn) {
1566 LOG_WRN("Invalid Connection");
1567 return -ENOTCONN;
1568 } else if (!otc_inst) {
1569 LOG_ERR("Invalid OTC instance");
1570 return -EINVAL;
1571 } else if (!metadata) {
1572 LOG_WRN("No metadata to read");
1573 return -ENOEXEC;
1574 }
1575
1576 inst = lookup_inst_by_handle(otc_inst->start_handle);
1577
1578 if (!inst) {
1579 LOG_ERR("Invalid OTC instance");
1580 return -EINVAL;
1581 } else if (inst->busy) {
1582 return -EBUSY;
1583 }
1584
1585 inst->metadata_read = 0;
1586 inst->metadata_to_read = metadata & BT_OTS_METADATA_REQ_ALL;
1587 inst->metadata_read_attempted = 0;
1588
1589 inst->busy = true;
1590 read_next_metadata(conn, inst);
1591
1592 return 0;
1593 }
1594
decode_record(struct net_buf_simple * buf,struct dirlisting_record_t * rec)1595 static int decode_record(struct net_buf_simple *buf,
1596 struct dirlisting_record_t *rec)
1597 {
1598 uint16_t start_len = buf->len;
1599
1600 rec->len = net_buf_simple_pull_le16(buf);
1601
1602 if (rec->len > buf->len) {
1603 LOG_WRN("incorrect DirListing record length %u, "
1604 "longer than remaining size %u",
1605 rec->len, buf->len);
1606 return -EINVAL;
1607 }
1608
1609 if ((start_len - buf->len) + BT_OTS_OBJ_ID_SIZE > rec->len) {
1610 LOG_WRN("incorrect DirListing record, reclen %u too short, "
1611 "includes only record length",
1612 rec->len);
1613 return -EINVAL;
1614 }
1615
1616 rec->metadata.id = net_buf_simple_pull_le48(buf);
1617
1618 if (IS_ENABLED(CONFIG_BT_OTS_CLIENT_LOG_LEVEL_DBG)) {
1619 char t[BT_OTS_OBJ_ID_STR_LEN];
1620
1621 (void)bt_ots_obj_id_to_str(rec->metadata.id, t, sizeof(t));
1622 LOG_DBG("Object ID 0x%s", t);
1623 }
1624
1625 if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) {
1626 LOG_WRN("incorrect DirListing record, reclen %u too short, "
1627 "includes only record length + ObjId",
1628 rec->len);
1629 return -EINVAL;
1630 }
1631
1632 rec->name_len = net_buf_simple_pull_u8(buf);
1633
1634 if (rec->name_len > 0) {
1635 uint8_t *name;
1636
1637 if ((start_len - buf->len) + rec->name_len > rec->len) {
1638 LOG_WRN("incorrect DirListing record, remaining length "
1639 "%u shorter than name length %u",
1640 rec->len - (start_len - buf->len),
1641 rec->name_len);
1642 return -EINVAL;
1643 }
1644
1645 if (rec->name_len >= sizeof(rec->metadata.name_c)) {
1646 LOG_WRN("Name length %u too long, invalid record", rec->name_len);
1647 return -EINVAL;
1648 }
1649
1650 name = net_buf_simple_pull_mem(buf, rec->name_len);
1651 memcpy(rec->metadata.name_c, name, rec->name_len);
1652 }
1653
1654 rec->metadata.name_c[rec->name_len] = '\0';
1655 rec->flags = 0;
1656
1657 if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) {
1658 LOG_WRN("incorrect DirListing record, reclen %u too short, "
1659 "does not include flags", rec->len);
1660 return -EINVAL;
1661 }
1662
1663 rec->flags = net_buf_simple_pull_u8(buf);
1664 LOG_DBG("flags 0x%x", rec->flags);
1665
1666 if (BT_OTS_DIR_LIST_GET_FLAG_TYPE_128(rec->flags)) {
1667 uint8_t *uuid;
1668
1669 if ((start_len - buf->len) + BT_UUID_SIZE_128 > rec->len) {
1670 LOG_WRN("incorrect DirListing record, reclen %u "
1671 "flags indicates uuid128, too short",
1672 rec->len);
1673 LOG_INF("flags 0x%x", rec->flags);
1674 return -EINVAL;
1675 }
1676
1677 uuid = net_buf_simple_pull_mem(buf, BT_UUID_SIZE_128);
1678 if (!bt_uuid_create(&rec->metadata.type.uuid, uuid, BT_UUID_SIZE_128)) {
1679 LOG_DBG("Failed to create UUID");
1680 return -EINVAL;
1681 }
1682 } else {
1683 if ((start_len - buf->len) + BT_UUID_SIZE_16 > rec->len) {
1684 LOG_WRN("incorrect DirListing record, reclen %u "
1685 "flags indicates uuid16, too short",
1686 rec->len);
1687 LOG_INF("flags 0x%x", rec->flags);
1688 return -EINVAL;
1689 }
1690
1691 rec->metadata.type.uuid_16.val =
1692 net_buf_simple_pull_le16(buf);
1693 }
1694
1695 if (BT_OTS_DIR_LIST_GET_FLAG_CUR_SIZE(rec->flags)) {
1696 if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) {
1697 LOG_WRN("incorrect DirListing record, reclen %u "
1698 "flags indicates cur_size, too short",
1699 rec->len);
1700 LOG_INF("flags 0x%x", rec->flags);
1701 return -EINVAL;
1702 }
1703
1704 rec->metadata.size.cur = net_buf_simple_pull_le32(buf);
1705 }
1706
1707 if (BT_OTS_DIR_LIST_GET_FLAG_ALLOC_SIZE(rec->flags)) {
1708 if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) {
1709 LOG_WRN("incorrect DirListing record, reclen %u "
1710 "flags indicates allocated size, too short",
1711 rec->len);
1712 LOG_INF("flags 0x%x", rec->flags);
1713 return -EINVAL;
1714 }
1715
1716 rec->metadata.size.alloc = net_buf_simple_pull_le32(buf);
1717 }
1718
1719 if (BT_OTS_DIR_LIST_GET_FLAG_FIRST_CREATED(rec->flags)) {
1720 if ((start_len - buf->len) + BT_OTS_DATE_TIME_FIELD_SIZE > rec->len) {
1721 LOG_WRN("incorrect DirListing record, reclen %u "
1722 "too short flags indicates first_created",
1723 rec->len);
1724 LOG_INF("flags 0x%x", rec->flags);
1725 return -EINVAL;
1726 }
1727
1728 date_time_decode(buf, &rec->metadata.first_created);
1729 }
1730
1731 if (BT_OTS_DIR_LIST_GET_FLAG_LAST_MODIFIED(rec->flags)) {
1732 if ((start_len - buf->len) + BT_OTS_DATE_TIME_FIELD_SIZE > rec->len) {
1733 LOG_WRN("incorrect DirListing record, reclen %u "
1734 "flags indicates las_mod, too short",
1735 rec->len);
1736 LOG_INF("flags 0x%x", rec->flags);
1737 return -EINVAL;
1738 }
1739
1740 date_time_decode(buf, &rec->metadata.modified);
1741 }
1742
1743 if (BT_OTS_DIR_LIST_GET_FLAG_PROPERTIES(rec->flags)) {
1744 if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) {
1745 LOG_WRN("incorrect DirListing record, reclen %u "
1746 "flags indicates properties, too short",
1747 rec->len);
1748 LOG_INF("flags 0x%x", rec->flags);
1749 return -EINVAL;
1750 }
1751
1752 rec->metadata.props = net_buf_simple_pull_le32(buf);
1753 }
1754
1755 return rec->len;
1756 }
1757
bt_ots_client_decode_dirlisting(uint8_t * data,uint16_t length,bt_ots_client_dirlisting_cb cb)1758 int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length,
1759 bt_ots_client_dirlisting_cb cb)
1760 {
1761 struct net_buf_simple net_buf;
1762 uint8_t count = 0;
1763 struct dirlisting_record_t record;
1764
1765 net_buf_simple_init_with_data(&net_buf, (void *)data, length);
1766
1767 if (!data || length == 0) {
1768 return -EINVAL;
1769 }
1770
1771 while (net_buf.len) {
1772 int ret;
1773
1774 count++;
1775
1776 if (net_buf.len < sizeof(uint16_t)) {
1777 LOG_WRN("incorrect DirListing record, len %u too short", net_buf.len);
1778 return -EINVAL;
1779 }
1780
1781 LOG_DBG("Decoding record %u", count);
1782 ret = decode_record(&net_buf, &record);
1783
1784 if (ret < 0) {
1785 LOG_WRN("DirListing, record %u invalid", count);
1786 return ret;
1787 }
1788
1789 ret = cb(&record.metadata);
1790
1791 if (ret == BT_OTS_STOP) {
1792 break;
1793 }
1794 }
1795
1796 return count;
1797 }
1798
bt_ots_metadata_display(struct bt_ots_obj_metadata * metadata,uint16_t count)1799 void bt_ots_metadata_display(struct bt_ots_obj_metadata *metadata,
1800 uint16_t count)
1801 {
1802 LOG_INF("--- Displaying %u metadata records ---", count);
1803
1804 for (int i = 0; i < count; i++) {
1805 char t[BT_OTS_OBJ_ID_STR_LEN];
1806
1807 (void)bt_ots_obj_id_to_str(metadata->id, t, sizeof(t));
1808 LOG_INF("Object ID: 0x%s", t);
1809 LOG_INF("Object name: %s", metadata->name_c);
1810 LOG_INF("Object Current Size: %u", metadata->size.cur);
1811 LOG_INF("Object Allocate Size: %u", metadata->size.alloc);
1812
1813 if (!bt_uuid_cmp(&metadata->type.uuid,
1814 BT_UUID_OTS_TYPE_MPL_ICON)) {
1815 LOG_INF("Type: Icon Obj Type");
1816 } else if (!bt_uuid_cmp(&metadata->type.uuid,
1817 BT_UUID_OTS_TYPE_TRACK_SEGMENT)) {
1818 LOG_INF("Type: Track Segment Obj Type");
1819 } else if (!bt_uuid_cmp(&metadata->type.uuid,
1820 BT_UUID_OTS_TYPE_TRACK)) {
1821 LOG_INF("Type: Track Obj Type");
1822 } else if (!bt_uuid_cmp(&metadata->type.uuid,
1823 BT_UUID_OTS_TYPE_GROUP)) {
1824 LOG_INF("Type: Group Obj Type");
1825 } else if (!bt_uuid_cmp(&metadata->type.uuid,
1826 BT_UUID_OTS_DIRECTORY_LISTING)) {
1827 LOG_INF("Type: Directory Listing");
1828 }
1829
1830
1831 LOG_INF("Properties:0x%x", metadata->props);
1832
1833 if (BT_OTS_OBJ_GET_PROP_APPEND(metadata->props)) {
1834 LOG_INF(" - append permitted");
1835 }
1836
1837 if (BT_OTS_OBJ_GET_PROP_DELETE(metadata->props)) {
1838 LOG_INF(" - delete permitted");
1839 }
1840
1841 if (BT_OTS_OBJ_GET_PROP_EXECUTE(metadata->props)) {
1842 LOG_INF(" - execute permitted");
1843 }
1844
1845 if (BT_OTS_OBJ_GET_PROP_MARKED(metadata->props)) {
1846 LOG_INF(" - marked");
1847 }
1848
1849 if (BT_OTS_OBJ_GET_PROP_PATCH(metadata->props)) {
1850 LOG_INF(" - patch permitted");
1851 }
1852
1853 if (BT_OTS_OBJ_GET_PROP_READ(metadata->props)) {
1854 LOG_INF(" - read permitted");
1855 }
1856
1857 if (BT_OTS_OBJ_GET_PROP_TRUNCATE(metadata->props)) {
1858 LOG_INF(" - truncate permitted");
1859 }
1860
1861 if (BT_OTS_OBJ_GET_PROP_WRITE(metadata->props)) {
1862 LOG_INF(" - write permitted");
1863 }
1864 }
1865 }
1866