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