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 <init.h>
12 #include <sys/printk.h>
13 #include <sys/byteorder.h>
14 #include <zephyr.h>
15
16 #include <bluetooth/bluetooth.h>
17 #include <bluetooth/l2cap.h>
18 #include <bluetooth/conn.h>
19 #include <bluetooth/uuid.h>
20 #include <bluetooth/gatt.h>
21
22 #include <sys/check.h>
23
24 #include <bluetooth/services/ots.h>
25 #include "ots_internal.h"
26 #include "ots_obj_manager_internal.h"
27 #include "ots_dir_list_internal.h"
28
29 #include <logging/log.h>
30
31 LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
32
33 #if defined(CONFIG_BT_OTS_OACP_READ_SUPPORT)
34 #define OACP_FEAT_BIT_READ BIT(BT_OTS_OACP_FEAT_READ)
35 #else
36 #define OACP_FEAT_BIT_READ 0
37 #endif
38
39 #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
40 #define OACP_FEAT_BIT_WRITE BIT(BT_OTS_OACP_FEAT_WRITE)
41 #else
42 #define OACP_FEAT_BIT_WRITE 0
43 #endif
44
45 #if defined(CONFIG_BT_OTS_OACP_PATCH_SUPPORT)
46 #define OACP_FEAT_BIT_PATCH BIT(BT_OTS_OACP_FEAT_PATCH)
47 #else
48 #define OACP_FEAT_BIT_PATCH 0
49 #endif
50
51 /* OACP features supported by Kconfig */
52 #define OACP_FEAT ( \
53 OACP_FEAT_BIT_READ | \
54 OACP_FEAT_BIT_WRITE | \
55 OACP_FEAT_BIT_PATCH)
56
57 #if defined(CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT)
58 #define OLCP_FEAT_BIT_GOTO BIT(BT_OTS_OLCP_FEAT_GO_TO)
59 #else
60 #define OLCP_FEAT_BIT_GOTO 0
61 #endif
62
63 /* OLCP features supported by Kconfig */
64 #define OLCP_FEAT OLCP_FEAT_BIT_GOTO
65
ots_obj_validate_prop_against_oacp(uint32_t prop,uint32_t oacp)66 static bool ots_obj_validate_prop_against_oacp(uint32_t prop, uint32_t oacp)
67 {
68 if (BT_OTS_OBJ_GET_PROP_DELETE(prop) > 0 && BT_OTS_OACP_GET_FEAT_DELETE(oacp) == 0) {
69 return false;
70 }
71
72 if (BT_OTS_OBJ_GET_PROP_EXECUTE(prop) > 0 && BT_OTS_OACP_GET_FEAT_EXECUTE(oacp) == 0) {
73 return false;
74 }
75
76 if (BT_OTS_OBJ_GET_PROP_READ(prop) > 0 && BT_OTS_OACP_GET_FEAT_READ(oacp) == 0) {
77 return false;
78 }
79
80 if (BT_OTS_OBJ_GET_PROP_WRITE(prop) > 0 && BT_OTS_OACP_GET_FEAT_WRITE(oacp) == 0) {
81 return false;
82 }
83
84 if (BT_OTS_OBJ_GET_PROP_APPEND(prop) > 0 && BT_OTS_OACP_GET_FEAT_APPEND(oacp) == 0) {
85 return false;
86 }
87
88 if (BT_OTS_OBJ_GET_PROP_TRUNCATE(prop) > 0 && BT_OTS_OACP_GET_FEAT_TRUNCATE(oacp) == 0) {
89 return false;
90 }
91
92 if (BT_OTS_OBJ_GET_PROP_PATCH(prop) > 0 && BT_OTS_OACP_GET_FEAT_PATCH(oacp) == 0) {
93 return false;
94 }
95
96 return true;
97 }
98
ots_feature_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)99 static ssize_t ots_feature_read(struct bt_conn *conn,
100 const struct bt_gatt_attr *attr, void *buf,
101 uint16_t len, uint16_t offset)
102 {
103 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
104
105 LOG_DBG("OTS Feature GATT Read Operation");
106
107 return bt_gatt_attr_read(conn, attr, buf, len, offset, &ots->features,
108 sizeof(ots->features));
109 }
110
ots_obj_name_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)111 static ssize_t ots_obj_name_read(struct bt_conn *conn,
112 const struct bt_gatt_attr *attr, void *buf,
113 uint16_t len, uint16_t offset)
114 {
115 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
116
117 LOG_DBG("OTS Object Name GATT Read Operation");
118
119 if (!ots->cur_obj) {
120 LOG_DBG("No Current Object selected in OTS!");
121 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
122 }
123
124 return bt_gatt_attr_read(conn, attr, buf, len, offset,
125 ots->cur_obj->metadata.name,
126 strlen(ots->cur_obj->metadata.name));
127 }
128
129 #if defined(CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT)
ots_obj_name_write(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)130 ssize_t ots_obj_name_write(struct bt_conn *conn,
131 const struct bt_gatt_attr *attr,
132 const void *buf, uint16_t len,
133 uint16_t offset, uint8_t flags)
134 {
135 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
136 struct bt_gatt_ots_object *obj = NULL;
137 int rc = 0;
138 char name[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1];
139
140 LOG_DBG("OTS Object Name GATT Write Operation");
141
142 if (!ots->cur_obj) {
143 LOG_DBG("No Current Object selected in OTS!");
144 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
145 }
146
147 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
148 ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
149 LOG_DBG("Rejecting name write for the directory list object.");
150 return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
151 }
152
153 if (offset > 0) {
154 LOG_DBG("Rejecting a long write, offset must be 0!");
155 return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
156 }
157
158 if (len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
159 LOG_DBG("Object name is too long!");
160 return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
161 }
162
163 /* Construct a temporary name for duplication detection */
164 memcpy(name, buf, len);
165 name[len] = '\0';
166
167 rc = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &obj);
168 while (rc == 0) {
169 if (obj != ots->cur_obj && strcmp(name, obj->metadata.name) == 0) {
170 LOG_DBG("Object name is duplicated!");
171 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NAME_ALREADY_EXISTS);
172 }
173 rc = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, obj, &obj);
174 }
175
176 /* Update real object name after no duplicate detected */
177 strcpy(ots->cur_obj->metadata.name, name);
178
179 if (ots->cb->obj_name_written) {
180 ots->cb->obj_name_written(ots, conn, ots->cur_obj->id,
181 ots->cur_obj->metadata.name);
182 }
183
184 return len;
185 }
186 #endif
187
ots_obj_type_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)188 static ssize_t ots_obj_type_read(struct bt_conn *conn,
189 const struct bt_gatt_attr *attr, void *buf,
190 uint16_t len, uint16_t offset)
191 {
192 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
193 struct bt_ots_obj_metadata *obj_meta;
194
195 LOG_DBG("OTS Object Type GATT Read Operation");
196
197 if (!ots->cur_obj) {
198 LOG_DBG("No Current Object selected in OTS!");
199 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
200 }
201
202 obj_meta = &ots->cur_obj->metadata;
203 if (obj_meta->type.uuid.type == BT_UUID_TYPE_128) {
204 return bt_gatt_attr_read(conn, attr, buf, len, offset,
205 obj_meta->type.uuid_128.val,
206 sizeof(obj_meta->type.uuid_128.val));
207 } else {
208 return bt_gatt_attr_read(conn, attr, buf, len, offset,
209 &obj_meta->type.uuid_16.val,
210 sizeof(obj_meta->type.uuid_16.val));
211 }
212 }
213
ots_obj_size_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)214 static ssize_t ots_obj_size_read(struct bt_conn *conn,
215 const struct bt_gatt_attr *attr, void *buf,
216 uint16_t len, uint16_t offset)
217 {
218 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
219
220 LOG_DBG("OTS Object Size GATT Read Operation");
221
222 if (!ots->cur_obj) {
223 LOG_DBG("No Current Object selected in OTS!");
224 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
225 }
226
227 return bt_gatt_attr_read(conn, attr, buf, len, offset,
228 &ots->cur_obj->metadata.size,
229 sizeof(ots->cur_obj->metadata.size));
230 }
231
ots_obj_id_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)232 static ssize_t ots_obj_id_read(struct bt_conn *conn,
233 const struct bt_gatt_attr *attr, void *buf,
234 uint16_t len, uint16_t offset)
235 {
236 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
237 uint8_t id[BT_OTS_OBJ_ID_SIZE];
238 char id_str[BT_OTS_OBJ_ID_STR_LEN];
239
240 LOG_DBG("OTS Object ID GATT Read Operation");
241
242 if (!ots->cur_obj) {
243 LOG_DBG("No Current Object selected in OTS!");
244 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
245 }
246
247 sys_put_le48(ots->cur_obj->id, id);
248
249 bt_ots_obj_id_to_str(ots->cur_obj->id, id_str,
250 sizeof(id_str));
251 LOG_DBG("Current Object ID: %s", log_strdup(id_str));
252
253 return bt_gatt_attr_read(conn, attr, buf, len, offset, id, sizeof(id));
254 }
255
ots_obj_prop_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)256 static ssize_t ots_obj_prop_read(struct bt_conn *conn,
257 const struct bt_gatt_attr *attr, void *buf,
258 uint16_t len, uint16_t offset)
259 {
260 struct bt_ots *ots = (struct bt_ots *) attr->user_data;
261
262 LOG_DBG("OTS Object Properties GATT Read Operation");
263
264 if (!ots->cur_obj) {
265 LOG_DBG("No Current Object selected in OTS!");
266 return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
267 }
268
269 return bt_gatt_attr_read(conn, attr, buf, len, offset,
270 &ots->cur_obj->metadata.props,
271 sizeof(ots->cur_obj->metadata.props));
272 }
273
bt_ots_obj_add(struct bt_ots * ots,struct bt_ots_obj_metadata * obj_init)274 int bt_ots_obj_add(struct bt_ots *ots,
275 struct bt_ots_obj_metadata *obj_init)
276 {
277 int err;
278 struct bt_gatt_ots_object *obj;
279 size_t name_len;
280
281 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
282 ots->dir_list->dir_list_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
283 LOG_DBG("Directory Listing Object is being read");
284 return -EBUSY;
285 }
286
287 name_len = strlen(obj_init->name);
288
289 CHECKIF(name_len == 0 || name_len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
290 LOG_DBG("Invalid name length %zu", name_len);
291 return -EINVAL;
292 }
293
294 CHECKIF(!ots_obj_validate_prop_against_oacp(obj_init->props, ots->features.oacp)) {
295 LOG_DBG("Object properties (0x%04X) are not a subset of OACP (0x%04X)",
296 obj_init->props, ots->features.oacp);
297 return -ENOTSUP;
298 }
299
300 err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &obj);
301 if (err) {
302 LOG_ERR("No space available in the object manager");
303 return err;
304 }
305
306 /* Initialize object. */
307 memcpy(&obj->metadata, obj_init, sizeof(obj->metadata));
308
309 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
310 bt_ots_dir_list_obj_add(ots->dir_list, ots->obj_manager, ots->cur_obj, obj);
311 }
312
313 /* Request object data. */
314 if (ots->cb->obj_created) {
315 err = ots->cb->obj_created(ots, NULL, obj->id, obj_init);
316 if (err) {
317 bt_gatt_ots_obj_manager_obj_delete(obj);
318
319 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
320 bt_ots_dir_list_obj_remove(ots->dir_list, ots->obj_manager,
321 ots->cur_obj, obj);
322 }
323
324 return err;
325 }
326 }
327
328 return 0;
329 }
330
bt_ots_obj_delete(struct bt_ots * ots,uint64_t id)331 int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
332 {
333 int err;
334 struct bt_gatt_ots_object *obj;
335
336 err = bt_gatt_ots_obj_manager_obj_get(ots->obj_manager, id, &obj);
337 if (err) {
338 return err;
339 }
340
341 if (ots->cur_obj == obj) {
342 if (obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
343 return -EBUSY;
344 }
345 ots->cur_obj = NULL;
346 }
347
348 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
349 ots->dir_list->dir_list_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
350 LOG_DBG("Directory Listing Object is being read");
351 return -EBUSY;
352 }
353
354 err = bt_gatt_ots_obj_manager_obj_delete(obj);
355 if (err) {
356 return err;
357 }
358
359 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
360 bt_ots_dir_list_obj_remove(ots->dir_list, ots->obj_manager, ots->cur_obj, obj);
361 }
362
363 if (ots->cb->obj_deleted) {
364 ots->cb->obj_deleted(ots, NULL, obj->id);
365 }
366
367 return 0;
368 }
369
370 #if defined(CONFIG_BT_OTS_SECONDARY_SVC)
bt_ots_svc_decl_get(struct bt_ots * ots)371 void *bt_ots_svc_decl_get(struct bt_ots *ots)
372 {
373 return ots->service->attrs;
374 }
375 #endif
376
bt_ots_init(struct bt_ots * ots,struct bt_ots_init * ots_init)377 int bt_ots_init(struct bt_ots *ots,
378 struct bt_ots_init *ots_init)
379 {
380 int err;
381
382 if (!ots || !ots_init || !ots_init->cb) {
383 return -EINVAL;
384 }
385
386 __ASSERT(ots_init->cb->obj_created,
387 "Callback for object creation is not set");
388 __ASSERT(ots_init->cb->obj_read ||
389 !BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp),
390 "Callback for object reading is not set");
391 __ASSERT(ots_init->cb->obj_write ||
392 !BT_OTS_OACP_GET_FEAT_WRITE(ots_init->features.oacp),
393 "Callback for object write is not set");
394
395 /* Set callback structure. */
396 ots->cb = ots_init->cb;
397
398 /* Check OACP supported features against Kconfig. */
399 if (ots_init->features.oacp & (~((uint32_t) OACP_FEAT))) {
400 return -ENOTSUP;
401 }
402
403 ots->features.oacp = ots_init->features.oacp;
404 LOG_DBG("OACP features: 0x%04X", ots->features.oacp);
405
406 /* Check OLCP supported features against Kconfig. */
407 if (ots_init->features.olcp & (~((uint32_t) OLCP_FEAT))) {
408 return -ENOTSUP;
409 }
410 ots->features.olcp = ots_init->features.olcp;
411 LOG_DBG("OLCP features: 0x%04X", ots->features.olcp);
412
413 /* Register L2CAP context. */
414 err = bt_gatt_ots_l2cap_register(&ots->l2cap);
415 if (err) {
416 return err;
417 }
418
419 err = bt_gatt_service_register(ots->service);
420 if (err) {
421 bt_gatt_ots_l2cap_unregister(&ots->l2cap);
422
423 return err;
424 }
425
426 if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
427 bt_ots_dir_list_init(&ots->dir_list, ots->obj_manager);
428 }
429
430 LOG_DBG("Initialized OTS");
431
432 return 0;
433 }
434
435 #if defined(CONFIG_BT_OTS_SECONDARY_SVC)
436 #define BT_GATT_OTS_SERVICE BT_GATT_SECONDARY_SERVICE
437 #else
438 #define BT_GATT_OTS_SERVICE BT_GATT_PRIMARY_SERVICE
439 #endif
440
441 #if defined(CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT)
442 #define BT_OTS_OBJ_NAME_GATT_CHRC (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE)
443 #define BT_OTS_OBJ_NAME_GATT_PERM (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)
444 #define BT_OTS_OBJ_NAME_GATT_WRITE (ots_obj_name_write)
445 #else
446 #define BT_OTS_OBJ_NAME_GATT_CHRC (BT_GATT_CHRC_READ)
447 #define BT_OTS_OBJ_NAME_GATT_PERM (BT_GATT_PERM_READ)
448 #define BT_OTS_OBJ_NAME_GATT_WRITE (NULL)
449 #endif
450
451 #define BT_GATT_OTS_ATTRS(_ots) { \
452 BT_GATT_OTS_SERVICE(BT_UUID_OTS), \
453 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_FEATURE, \
454 BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
455 ots_feature_read, NULL, &_ots), \
456 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_NAME, \
457 BT_OTS_OBJ_NAME_GATT_CHRC, BT_OTS_OBJ_NAME_GATT_PERM, \
458 ots_obj_name_read, BT_OTS_OBJ_NAME_GATT_WRITE, &_ots), \
459 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_TYPE, \
460 BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
461 ots_obj_type_read, NULL, &_ots), \
462 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_SIZE, \
463 BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
464 ots_obj_size_read, NULL, &_ots), \
465 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ID, \
466 BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
467 ots_obj_id_read, NULL, &_ots), \
468 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_PROPERTIES, \
469 BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
470 ots_obj_prop_read, NULL, &_ots), \
471 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ACTION_CP, \
472 BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
473 BT_GATT_PERM_WRITE, NULL, \
474 bt_gatt_ots_oacp_write, &_ots), \
475 BT_GATT_CCC_MANAGED(&_ots.oacp_ind.ccc, \
476 BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), \
477 BT_GATT_CHARACTERISTIC(BT_UUID_OTS_LIST_CP, \
478 BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
479 BT_GATT_PERM_WRITE, NULL, \
480 bt_gatt_ots_olcp_write, &_ots), \
481 BT_GATT_CCC_MANAGED(&_ots.olcp_ind.ccc, \
482 BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) \
483 }
484
485 #define BT_GATT_OTS_INSTANCE_LIST_SIZE (ARRAY_SIZE(ots_instances))
486 #define BT_GATT_OTS_INSTANCE_LIST_START ots_instances
487 #define BT_GATT_OTS_INSTANCE_LIST_END \
488 (&ots_instances[BT_GATT_OTS_INSTANCE_LIST_SIZE])
489
490 #define BT_GATT_OTS_SERVICE_LIST_START ots_service_list
491
492 static struct bt_ots ots_instances[CONFIG_BT_OTS_MAX_INST_CNT];
493 static uint32_t instance_cnt;
494 BT_GATT_SERVICE_INSTANCE_DEFINE(ots_service_list, ots_instances,
495 CONFIG_BT_OTS_MAX_INST_CNT,
496 BT_GATT_OTS_ATTRS);
497
bt_ots_free_instance_get(void)498 struct bt_ots *bt_ots_free_instance_get(void)
499 {
500 if (instance_cnt >= BT_GATT_OTS_INSTANCE_LIST_SIZE) {
501 return NULL;
502 }
503
504 return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++];
505 }
506
bt_gatt_ots_instances_prepare(const struct device * dev)507 static int bt_gatt_ots_instances_prepare(const struct device *dev)
508 {
509 uint32_t index;
510 struct bt_ots *instance;
511
512 for (instance = BT_GATT_OTS_INSTANCE_LIST_START, index = 0;
513 instance != BT_GATT_OTS_INSTANCE_LIST_END;
514 instance++, index++) {
515 /* Assign an object pool to the OTS instance. */
516 instance->obj_manager = bt_gatt_ots_obj_manager_assign();
517
518 if (!instance->obj_manager) {
519 LOG_ERR("OTS Object manager instance not available");
520 return -ENOMEM;
521 }
522
523 /* Assign pointer to the service descriptor. */
524 instance->service = &BT_GATT_OTS_SERVICE_LIST_START[index];
525
526 /* Initialize CCC descriptors for characteristics with
527 * indication properties.
528 */
529 instance->oacp_ind.ccc.cfg_changed =
530 bt_gatt_ots_oacp_cfg_changed;
531 instance->olcp_ind.ccc.cfg_changed =
532 bt_gatt_ots_olcp_cfg_changed;
533 }
534
535 return 0;
536 }
537
538 SYS_INIT(bt_gatt_ots_instances_prepare, APPLICATION,
539 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
540