1 /*
2 * Copyright (c) 2021 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 <zephyr/sys/dlist.h>
12 #include <zephyr/sys/byteorder.h>
13
14 #include <zephyr/bluetooth/services/ots.h>
15 #include "ots_internal.h"
16 #include "ots_obj_manager_internal.h"
17 #include "ots_dir_list_internal.h"
18
19 #include <zephyr/logging/log.h>
20
21 LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
22
23 static struct bt_ots_dir_list dir_lists[CONFIG_BT_OTS_MAX_INST_CNT];
24
dir_list_object_record_size(const struct bt_gatt_ots_object * obj)25 static size_t dir_list_object_record_size(const struct bt_gatt_ots_object *obj)
26 {
27 uint16_t len;
28 size_t obj_name_len;
29
30 /* Record Length */
31 len = sizeof(len);
32
33 /* ID */
34 len += BT_OTS_OBJ_ID_SIZE;
35
36 /* Name length (single octet is used for the name length) */
37 len += sizeof(uint8_t);
38
39 /* Name */
40 obj_name_len = strlen(obj->metadata.name);
41 __ASSERT(obj_name_len > 0 && obj_name_len <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
42 "Dir list object len is incorrect %zu", obj_name_len);
43 len += obj_name_len;
44
45 /* Flags */
46 len += sizeof(uint8_t);
47
48 /* Object type */
49 if (obj->metadata.type.uuid.type == BT_UUID_TYPE_16) {
50 len += BT_UUID_SIZE_16;
51 } else {
52 len += BT_UUID_SIZE_128;
53 }
54
55 /* Object Current size */
56 len += sizeof(obj->metadata.size.cur);
57
58 /* Object properties */
59 len += sizeof(obj->metadata.props);
60
61 __ASSERT(len >= DIR_LIST_OBJ_RECORD_MIN_SIZE,
62 "Dir list object len is too small %u", len);
63 __ASSERT(len <= DIR_LIST_OBJ_RECORD_MAX_SIZE,
64 "Dir list object len is too large %u", len);
65
66 return len;
67 }
68
dir_list_object_encode(const struct bt_gatt_ots_object * obj,struct net_buf_simple * net_buf)69 static void dir_list_object_encode(const struct bt_gatt_ots_object *obj,
70 struct net_buf_simple *net_buf)
71 {
72 uint8_t flags = 0;
73 uint8_t *start;
74 uint16_t len;
75 size_t obj_name_len;
76
77 BT_OTS_DIR_LIST_SET_FLAG_PROPERTIES(flags);
78 BT_OTS_DIR_LIST_SET_FLAG_CUR_SIZE(flags);
79 if (obj->metadata.type.uuid.type == BT_UUID_TYPE_128) {
80 BT_OTS_DIR_LIST_SET_FLAG_TYPE_128(flags);
81 }
82
83 /* skip 16bits at the beginning of the record for the record's length */
84 start = net_buf_simple_add(net_buf, sizeof(len));
85
86 /* ID */
87 net_buf_simple_add_le48(net_buf, obj->id);
88
89 /* Name length */
90 obj_name_len = strlen(obj->metadata.name);
91 __ASSERT(obj_name_len > 0 && obj_name_len <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
92 "Dir list object len is incorrect %zu", obj_name_len);
93 net_buf_simple_add_u8(net_buf, obj_name_len);
94
95 /* Name */
96 net_buf_simple_add_mem(net_buf, obj->metadata.name, obj_name_len);
97
98 /* Flags */
99 net_buf_simple_add_u8(net_buf, flags);
100
101 /* encode Object type */
102 if (obj->metadata.type.uuid.type == BT_UUID_TYPE_16) {
103 net_buf_simple_add_le16(net_buf, obj->metadata.type.uuid_16.val);
104 } else {
105 net_buf_simple_add_mem(net_buf, obj->metadata.type.uuid_128.val,
106 BT_UUID_SIZE_128);
107 }
108
109 /* encode Object Current size */
110 net_buf_simple_add_le32(net_buf, obj->metadata.size.cur);
111
112 /* Object properties */
113 net_buf_simple_add_le32(net_buf, obj->metadata.props);
114
115 len = net_buf_simple_tail(net_buf) - start;
116
117 __ASSERT(len >= DIR_LIST_OBJ_RECORD_MIN_SIZE,
118 "Dir list object len is too small %u", len);
119 __ASSERT(len <= DIR_LIST_OBJ_RECORD_MAX_SIZE,
120 "Dir list object len is too large %u", len);
121
122 /* Update the record length at the beginning */
123 sys_put_le16(len, start);
124 }
125
bt_ots_dir_list_reset_anchor(struct bt_ots_dir_list * dir_list,void * obj_manager)126 static void bt_ots_dir_list_reset_anchor(struct bt_ots_dir_list *dir_list, void *obj_manager)
127 {
128 dir_list->anchor_offset = 0;
129
130 /* Reset the dir_list - Ignore any error as we can't do anything about it anyways */
131 (void)bt_gatt_ots_obj_manager_first_obj_get(obj_manager, &dir_list->anchor_object);
132 }
133
bt_ots_dir_list_search_forward(struct bt_ots_dir_list * dir_list,void * obj_manager,off_t offset)134 static int bt_ots_dir_list_search_forward(struct bt_ots_dir_list *dir_list, void *obj_manager,
135 off_t offset)
136 {
137 int err;
138 char id_str[BT_OTS_OBJ_ID_STR_LEN];
139 struct bt_gatt_ots_object *obj = dir_list->anchor_object;
140 size_t rec_len = dir_list_object_record_size(obj);
141
142 bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
143 LOG_DBG("Searching forward for offset %ld starting at %ld with object ID %s",
144 (long)offset, (long)dir_list->anchor_offset, id_str);
145
146 while (dir_list->anchor_offset + rec_len <= offset) {
147
148 err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
149 if (err) {
150 return err;
151 }
152
153 dir_list->anchor_offset += rec_len;
154 dir_list->anchor_object = obj;
155
156 rec_len = dir_list_object_record_size(obj);
157 }
158
159 return 0;
160 }
161
bt_ots_dir_list_search_backward(struct bt_ots_dir_list * dir_list,void * obj_manager,off_t offset)162 static int bt_ots_dir_list_search_backward(struct bt_ots_dir_list *dir_list, void *obj_manager,
163 off_t offset)
164 {
165 int err;
166 char id_str[BT_OTS_OBJ_ID_STR_LEN];
167 struct bt_gatt_ots_object *obj = dir_list->anchor_object;
168
169 bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
170 LOG_DBG("Searching backward for offset %ld starting at %ld with object ID %s",
171 (long)offset, (long)dir_list->anchor_offset, id_str);
172
173 while (dir_list->anchor_offset > offset) {
174
175 err = bt_gatt_ots_obj_manager_prev_obj_get(obj_manager, obj, &obj);
176 if (err) {
177 return err;
178 }
179
180 dir_list->anchor_offset -= dir_list_object_record_size(obj);
181 dir_list->anchor_object = obj;
182 }
183
184 return 0;
185 }
186
bt_ots_dir_list_search(struct bt_ots_dir_list * dir_list,void * obj_manager,off_t offset)187 static int bt_ots_dir_list_search(struct bt_ots_dir_list *dir_list, void *obj_manager, off_t offset)
188 {
189 int err = 0;
190 char id_str[BT_OTS_OBJ_ID_STR_LEN];
191
192 /* decide start location and direction of movement based on offset, we can only choose
193 * current anchor point, beginning, or end as those are the only places where we know
194 * the associated object that builds up the record.
195 */
196 if (offset >= dir_list->anchor_offset) {
197 const size_t last = dir_list->dir_list_obj->metadata.size.cur;
198 const size_t mid = dir_list->anchor_offset + (last - dir_list->anchor_offset) / 2;
199
200 if (offset < mid) {
201 err = bt_ots_dir_list_search_forward(dir_list, obj_manager, offset);
202 } else {
203 size_t rec_len;
204
205 LOG_DBG("Offset %ld is closer to %zu than %ld, start from end",
206 (long)offset, last, (long)dir_list->anchor_offset);
207 bt_gatt_ots_obj_manager_last_obj_get(obj_manager, &dir_list->anchor_object);
208 rec_len = dir_list_object_record_size(dir_list->anchor_object);
209 dir_list->anchor_offset = last - rec_len;
210 err = bt_ots_dir_list_search_backward(dir_list, obj_manager, offset);
211 }
212 } else {
213 const size_t mid = dir_list->anchor_offset / 2;
214
215 if (offset < mid) {
216 LOG_DBG("Offset %ld is closer to 0 than %ld, start from beginning",
217 (long)offset, (long)dir_list->anchor_offset);
218 bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
219 err = bt_ots_dir_list_search_forward(dir_list, obj_manager, offset);
220 } else {
221 err = bt_ots_dir_list_search_backward(dir_list, obj_manager, offset);
222 }
223 }
224
225 if (err) {
226 return err;
227 }
228
229 bt_ots_obj_id_to_str(dir_list->anchor_object->id, id_str, sizeof(id_str));
230 LOG_DBG("Found offset %ld starting at %ld in object with ID %s",
231 (long)offset, (long)dir_list->anchor_offset, id_str);
232
233 return 0;
234 }
235
dir_list_update_size(struct bt_ots_dir_list * dir_list,void * obj_manager)236 static void dir_list_update_size(struct bt_ots_dir_list *dir_list, void *obj_manager)
237 {
238 struct bt_gatt_ots_object *obj;
239 int err;
240 size_t len = 0;
241
242 err = bt_gatt_ots_obj_manager_first_obj_get(obj_manager, &obj);
243
244 __ASSERT(err == 0 && obj == dir_list->dir_list_obj,
245 "Expecting first object to be the Directory Listing Object");
246
247 do {
248 len += dir_list_object_record_size(obj);
249
250 err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
251 } while (!err);
252
253 LOG_DBG("Update directory listing current size to 0x%zx", len);
254 dir_list->dir_list_obj->metadata.size.cur = len;
255 }
256
bt_ots_dir_list_selected(struct bt_ots_dir_list * dir_list,void * obj_manager,struct bt_gatt_ots_object * cur_obj)257 void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager,
258 struct bt_gatt_ots_object *cur_obj)
259 {
260 if (dir_list->dir_list_obj != cur_obj) {
261 /* We only need to update the object directory listing if it is currently selected,
262 * as we otherwise only create it when it is selected.
263 */
264 return;
265 }
266
267 bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
268 dir_list_update_size(dir_list, obj_manager);
269 }
270
bt_ots_dir_list_init(struct bt_ots_dir_list ** dir_list,void * obj_manager)271 void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager)
272 {
273 struct bt_gatt_ots_object *dir_list_obj;
274 int err;
275 static char *dir_list_obj_name = CONFIG_BT_OTS_DIR_LIST_OBJ_NAME;
276
277 __ASSERT(*dir_list == NULL, "Already initialized");
278
279 for (size_t i = 0; i < ARRAY_SIZE(dir_lists); i++) {
280 if (!dir_lists[i].dir_list_obj) {
281 *dir_list = &dir_lists[i];
282 }
283 }
284
285 __ASSERT(*dir_list, "Could not assign Directory Listing Object");
286
287 __ASSERT(strlen(dir_list_obj_name) <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
288 "BT_OTS_DIR_LIST_OBJ_NAME shall be less than or equal to %u octets",
289 CONFIG_BT_OTS_OBJ_MAX_NAME_LEN);
290
291 err = bt_gatt_ots_obj_manager_obj_add(obj_manager, &dir_list_obj);
292
293 __ASSERT(!err, "Could not add Directory Listing Object for object manager %p", obj_manager);
294
295 memset(&dir_list_obj->metadata, 0, sizeof(dir_list_obj->metadata));
296 dir_list_obj->metadata.name = dir_list_obj_name;
297 dir_list_obj->metadata.size.alloc = DIR_LIST_MAX_SIZE;
298 dir_list_obj->metadata.type.uuid.type = BT_UUID_TYPE_16;
299 dir_list_obj->metadata.type.uuid_16.val = BT_UUID_OTS_DIRECTORY_LISTING_VAL;
300 BT_OTS_OBJ_SET_PROP_READ(dir_list_obj->metadata.props);
301
302 (*dir_list)->dir_list_obj = dir_list_obj;
303
304 bt_ots_dir_list_reset_anchor(*dir_list, obj_manager);
305 dir_list_update_size(*dir_list, obj_manager);
306 }
307
bt_ots_dir_list_content_get(struct bt_ots_dir_list * dir_list,void * obj_manager,void ** data,size_t len,off_t offset)308 ssize_t bt_ots_dir_list_content_get(struct bt_ots_dir_list *dir_list, void *obj_manager,
309 void **data, size_t len, off_t offset)
310 {
311 int err;
312 size_t last_rec_len;
313 size_t rec_len;
314 off_t rec_offset;
315 struct bt_gatt_ots_object *obj;
316
317 err = bt_ots_dir_list_search(dir_list, obj_manager, offset);
318 if (err) {
319 return err;
320 }
321
322 net_buf_simple_init_with_data(&dir_list->net_buf, dir_list->_content,
323 sizeof(dir_list->_content));
324 net_buf_simple_reset(&dir_list->net_buf);
325
326 obj = dir_list->anchor_object;
327 rec_offset = dir_list->anchor_offset;
328
329 last_rec_len = 0;
330 rec_len = dir_list_object_record_size(obj);
331 while (net_buf_simple_tailroom(&dir_list->net_buf) >= rec_len) {
332
333 dir_list_object_encode(obj, &dir_list->net_buf);
334
335 dir_list->anchor_object = obj;
336 dir_list->anchor_offset += last_rec_len;
337
338 if (dir_list->net_buf.len - (offset - rec_offset) >= len) {
339 /* we have encoded as much data as the client has asked */
340 break;
341 }
342
343 err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
344 if (err) {
345 /* there are no more objects to encode */
346 break;
347 }
348
349 last_rec_len = rec_len;
350 rec_len = dir_list_object_record_size(obj);
351 }
352
353 *data = dir_list->net_buf.data + (offset - rec_offset);
354
355 return MIN(len, dir_list->net_buf.len - (offset - rec_offset));
356 }
357
bt_ots_dir_list_is_idle(const struct bt_ots_dir_list * dir_list)358 bool bt_ots_dir_list_is_idle(const struct bt_ots_dir_list *dir_list)
359 {
360 return dir_list->dir_list_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE;
361 }
362