1 /*
2  * Copyright (c) 2023 Codecoup
3  * Copyright (c) 2024 Demant A/S
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <errno.h>
9 #include <stdbool.h>
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/types.h>
14 
15 #include <zephyr/autoconf.h>
16 #include <zephyr/bluetooth/att.h>
17 #include <zephyr/bluetooth/conn.h>
18 #include <zephyr/bluetooth/gatt.h>
19 #include <zephyr/bluetooth/uuid.h>
20 #include <zephyr/fff.h>
21 #include <zephyr/sys/__assert.h>
22 #include <zephyr/sys/iterable_sections.h>
23 #include <zephyr/sys/slist.h>
24 #include <zephyr/sys/util.h>
25 #include <zephyr/types.h>
26 #include <zephyr/ztest_test.h>
27 #include <zephyr/ztest_assert.h>
28 
29 #include "gatt.h"
30 #include "conn.h"
31 #include "common/bt_str.h"
32 
33 #define LOG_LEVEL CONFIG_BT_GATT_LOG_LEVEL
34 #include <zephyr/logging/log.h>
35 LOG_MODULE_REGISTER(bt_gatt);
36 
37 /* List of fakes used by this unit tester */
38 #define FFF_FAKES_LIST(FAKE)                                                                       \
39 	FAKE(mock_bt_gatt_notify_cb)                                                               \
40 	FAKE(mock_bt_gatt_is_subscribed)                                                           \
41 
42 DEFINE_FAKE_VALUE_FUNC(int, mock_bt_gatt_notify_cb, struct bt_conn *,
43 		       struct bt_gatt_notify_params *);
44 DEFINE_FAKE_VALUE_FUNC(bool, mock_bt_gatt_is_subscribed, struct bt_conn *,
45 		       const struct bt_gatt_attr *, uint16_t);
46 
47 static uint16_t last_static_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
48 static sys_slist_t db;
49 
bt_gatt_attr_read_service(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)50 ssize_t bt_gatt_attr_read_service(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
51 				  uint16_t len, uint16_t offset)
52 {
53 	zassert_unreachable("Unexpected call to '%s()' occurred", __func__);
54 	return 0;
55 }
56 
bt_gatt_attr_read_chrc(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)57 ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
58 			       uint16_t len, uint16_t offset)
59 {
60 	zassert_unreachable("Unexpected call to '%s()' occurred", __func__);
61 	return 0;
62 }
63 
bt_gatt_attr_read_ccc(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)64 ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
65 			      uint16_t len, uint16_t offset)
66 {
67 	zassert_unreachable("Unexpected call to '%s()' occurred", __func__);
68 	return 0;
69 }
70 
bt_gatt_attr_write_ccc(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)71 ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
72 			       const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
73 {
74 	zassert_unreachable("Unexpected call to '%s()' occurred", __func__);
75 	return 0;
76 }
77 
mock_bt_gatt_init(void)78 void mock_bt_gatt_init(void)
79 {
80 	FFF_FAKES_LIST(RESET_FAKE);
81 
82 	mock_bt_gatt_is_subscribed_fake.return_val = true;
83 }
84 
notify_params_deep_copy_destroy(struct bt_gatt_notify_params * params)85 static void notify_params_deep_copy_destroy(struct bt_gatt_notify_params *params)
86 {
87 	struct bt_gatt_notify_params *copy;
88 
89 	for (unsigned int i = 0; i < mock_bt_gatt_notify_cb_fake.call_count; i++) {
90 		copy = mock_bt_gatt_notify_cb_fake.arg1_history[i];
91 		if (copy != params) {
92 			continue;
93 		}
94 
95 		/* Free UUID deep copy */
96 		if (copy->uuid) {
97 			free((void *)copy->uuid);
98 		}
99 
100 		free(copy);
101 
102 		mock_bt_gatt_notify_cb_fake.arg1_history[i] = NULL;
103 
104 		break;
105 	}
106 }
107 
notify_params_deep_copy_destroy_all(void)108 static void notify_params_deep_copy_destroy_all(void)
109 {
110 	struct bt_gatt_notify_params *copy;
111 
112 	for (unsigned int i = 0; i < mock_bt_gatt_notify_cb_fake.call_count; i++) {
113 		copy = mock_bt_gatt_notify_cb_fake.arg1_history[i];
114 		if (copy == NULL) {
115 			continue;
116 		}
117 
118 		/* Free UUID deep copy */
119 		if (copy->uuid) {
120 			free((void *)copy->uuid);
121 		}
122 
123 		free(copy);
124 	}
125 }
126 
mock_bt_gatt_cleanup(void)127 void mock_bt_gatt_cleanup(void)
128 {
129 	notify_params_deep_copy_destroy_all();
130 }
131 
uuid_deep_copy(const struct bt_uuid * uuid)132 static struct bt_uuid *uuid_deep_copy(const struct bt_uuid *uuid)
133 {
134 	struct bt_uuid *copy;
135 
136 	switch (uuid->type) {
137 	case BT_UUID_TYPE_16:
138 		copy = malloc(sizeof(struct bt_uuid_16));
139 		zassert_not_null(copy);
140 		memcpy(copy, uuid, sizeof(struct bt_uuid_16));
141 		break;
142 	case BT_UUID_TYPE_32:
143 		copy = malloc(sizeof(struct bt_uuid_32));
144 		zassert_not_null(copy);
145 		memcpy(copy, uuid, sizeof(struct bt_uuid_32));
146 		break;
147 	case BT_UUID_TYPE_128:
148 		copy = malloc(sizeof(struct bt_uuid_128));
149 		zassert_not_null(copy);
150 		memcpy(copy, uuid, sizeof(struct bt_uuid_128));
151 		break;
152 	default:
153 		zassert_unreachable("Unexpected uuid->type 0x%02x", uuid->type);
154 	}
155 
156 	return copy;
157 }
158 
notify_params_deep_copy(struct bt_gatt_notify_params * params)159 static struct bt_gatt_notify_params *notify_params_deep_copy(struct bt_gatt_notify_params *params)
160 {
161 	struct bt_gatt_notify_params *copy;
162 
163 	copy = malloc(sizeof(*params));
164 	zassert_not_null(copy);
165 
166 	memcpy(copy, params, sizeof(*params));
167 
168 	if (params->uuid != NULL) {
169 		copy->uuid = uuid_deep_copy(params->uuid);
170 	}
171 
172 	return copy;
173 }
174 
bt_gatt_notify_cb(struct bt_conn * conn,struct bt_gatt_notify_params * params)175 int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params)
176 {
177 	struct bt_gatt_notify_params *copy;
178 	int err;
179 
180 	zassert_not_null(params, "'%s()' was called with incorrect '%s' value", __func__, "params");
181 
182 	/* Either params->uuid, params->attr, or both has to be provided */
183 	zassert_true(params->uuid != NULL || params->attr != NULL,
184 		     "'%s()' was called with incorrect '%s' value", __func__,
185 		     "params->uuid or params->attr");
186 
187 	copy = notify_params_deep_copy(params);
188 
189 	err = mock_bt_gatt_notify_cb(conn, copy);
190 	if (err != 0) {
191 		notify_params_deep_copy_destroy(copy);
192 	}
193 
194 	return err;
195 }
196 
bt_gatt_notify_cb_reset(void)197 void bt_gatt_notify_cb_reset(void)
198 {
199 	notify_params_deep_copy_destroy_all();
200 
201 	RESET_FAKE(mock_bt_gatt_notify_cb);
202 }
203 
204 /* Exact copy of subsys/bluetooth/host/gatt.c:gatt_foreach_iter() */
gatt_foreach_iter(const struct bt_gatt_attr * attr,uint16_t handle,uint16_t start_handle,uint16_t end_handle,const struct bt_uuid * uuid,const void * attr_data,uint16_t * num_matches,bt_gatt_attr_func_t func,void * user_data)205 static uint8_t gatt_foreach_iter(const struct bt_gatt_attr *attr,
206 				 uint16_t handle, uint16_t start_handle,
207 				 uint16_t end_handle,
208 				 const struct bt_uuid *uuid,
209 				 const void *attr_data, uint16_t *num_matches,
210 				 bt_gatt_attr_func_t func, void *user_data)
211 {
212 	uint8_t result;
213 
214 	/* Stop if over the requested range */
215 	if (handle > end_handle) {
216 		return BT_GATT_ITER_STOP;
217 	}
218 
219 	/* Check if attribute handle is within range */
220 	if (handle < start_handle) {
221 		return BT_GATT_ITER_CONTINUE;
222 	}
223 
224 	/* Match attribute UUID if set */
225 	if (uuid && bt_uuid_cmp(uuid, attr->uuid)) {
226 		return BT_GATT_ITER_CONTINUE;
227 	}
228 
229 	/* Match attribute user_data if set */
230 	if (attr_data && attr_data != attr->user_data) {
231 		return BT_GATT_ITER_CONTINUE;
232 	}
233 
234 	*num_matches -= 1;
235 
236 	result = func(attr, handle, user_data);
237 
238 	if (!*num_matches) {
239 		return BT_GATT_ITER_STOP;
240 	}
241 
242 	return result;
243 }
244 
245 /* Exact copy of subsys/bluetooth/host/gatt.c:foreach_attr_type_dyndb() */
foreach_attr_type_dyndb(uint16_t start_handle,uint16_t end_handle,const struct bt_uuid * uuid,const void * attr_data,uint16_t num_matches,bt_gatt_attr_func_t func,void * user_data)246 static void foreach_attr_type_dyndb(uint16_t start_handle, uint16_t end_handle,
247 				    const struct bt_uuid *uuid, const void *attr_data,
248 				    uint16_t num_matches, bt_gatt_attr_func_t func, void *user_data)
249 {
250 	size_t i;
251 	struct bt_gatt_service *svc;
252 
253 	SYS_SLIST_FOR_EACH_CONTAINER(&db, svc, node) {
254 		struct bt_gatt_service *next;
255 
256 		next = SYS_SLIST_PEEK_NEXT_CONTAINER(svc, node);
257 		if (next) {
258 			/* Skip ahead if start is not within service handles */
259 			if (next->attrs[0].handle <= start_handle) {
260 				continue;
261 			}
262 		}
263 
264 		for (i = 0; i < svc->attr_count; i++) {
265 			struct bt_gatt_attr *attr = &svc->attrs[i];
266 
267 			if (gatt_foreach_iter(attr, attr->handle, start_handle, end_handle, uuid,
268 					      attr_data, &num_matches, func,
269 					      user_data) == BT_GATT_ITER_STOP) {
270 				return;
271 			}
272 		}
273 	}
274 }
275 
276 /* Exact copy of subsys/bluetooth/host/gatt.c:bt_gatt_foreach_attr_type() */
bt_gatt_foreach_attr_type(uint16_t start_handle,uint16_t end_handle,const struct bt_uuid * uuid,const void * attr_data,uint16_t num_matches,bt_gatt_attr_func_t func,void * user_data)277 void bt_gatt_foreach_attr_type(uint16_t start_handle, uint16_t end_handle,
278 			       const struct bt_uuid *uuid,
279 			       const void *attr_data, uint16_t num_matches,
280 			       bt_gatt_attr_func_t func, void *user_data)
281 {
282 	size_t i;
283 
284 	LOG_DBG("bt_gatt_foreach_attr_type");
285 
286 	if (!num_matches) {
287 		num_matches = UINT16_MAX;
288 	}
289 
290 	if (start_handle <= last_static_handle) {
291 		uint16_t handle = 1;
292 
293 		STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) {
294 			/* Skip ahead if start is not within service handles */
295 			if (handle + static_svc->attr_count < start_handle) {
296 				handle += static_svc->attr_count;
297 				continue;
298 			}
299 
300 			for (i = 0; i < static_svc->attr_count; i++, handle++) {
301 				if (gatt_foreach_iter(&static_svc->attrs[i],
302 						      handle, start_handle,
303 						      end_handle, uuid,
304 						      attr_data, &num_matches,
305 						      func, user_data) ==
306 				    BT_GATT_ITER_STOP) {
307 					LOG_DBG("Returning after searching static DB");
308 					return;
309 				}
310 			}
311 		}
312 	}
313 
314 	LOG_DBG("foreach_attr_type_dyndb");
315 	/* Iterate over dynamic db */
316 	foreach_attr_type_dyndb(start_handle, end_handle, uuid, attr_data,
317 				num_matches, func, user_data);
318 }
319 
bt_gatt_service_init(void)320 static void bt_gatt_service_init(void)
321 {
322 	last_static_handle = 0U;
323 
324 	STRUCT_SECTION_FOREACH(bt_gatt_service_static, svc) {
325 		last_static_handle += svc->attr_count;
326 	}
327 }
328 
329 /* Exact copy of subsys/bluetooth/host/gatt.c:found_attr() */
found_attr(const struct bt_gatt_attr * attr,uint16_t handle,void * user_data)330 static uint8_t found_attr(const struct bt_gatt_attr *attr, uint16_t handle, void *user_data)
331 {
332 	const struct bt_gatt_attr **found = user_data;
333 
334 	*found = attr;
335 
336 	return BT_GATT_ITER_STOP;
337 }
338 
339 /* Exact copy of subsys/bluetooth/host/gatt.c:find_attr() */
find_attr(uint16_t handle)340 static const struct bt_gatt_attr *find_attr(uint16_t handle)
341 {
342 	const struct bt_gatt_attr *attr = NULL;
343 
344 	bt_gatt_foreach_attr(handle, handle, found_attr, &attr);
345 
346 	return attr;
347 }
348 
349 /* Exact copy of subsys/bluetooth/host/gatt.c:gatt_insert() */
gatt_insert(struct bt_gatt_service * svc,uint16_t last_handle)350 static void gatt_insert(struct bt_gatt_service *svc, uint16_t last_handle)
351 {
352 	struct bt_gatt_service *tmp, *prev = NULL;
353 
354 	if (last_handle == 0 || svc->attrs[0].handle > last_handle) {
355 		sys_slist_append(&db, &svc->node);
356 		return;
357 	}
358 
359 	/* DB shall always have its service in ascending order */
360 	SYS_SLIST_FOR_EACH_CONTAINER(&db, tmp, node) {
361 		if (tmp->attrs[0].handle > svc->attrs[0].handle) {
362 			if (prev) {
363 				sys_slist_insert(&db, &prev->node, &svc->node);
364 			} else {
365 				sys_slist_prepend(&db, &svc->node);
366 			}
367 			return;
368 		}
369 
370 		prev = tmp;
371 	}
372 }
373 
374 /* Exact copy of subsys/bluetooth/host/gatt.c:gatt_register() */
gatt_register(struct bt_gatt_service * svc)375 static int gatt_register(struct bt_gatt_service *svc)
376 {
377 	struct bt_gatt_service *last;
378 	uint16_t handle, last_handle;
379 	struct bt_gatt_attr *attrs = svc->attrs;
380 	uint16_t count = svc->attr_count;
381 
382 	if (sys_slist_is_empty(&db)) {
383 		handle = last_static_handle;
384 		last_handle = 0;
385 		goto populate;
386 	}
387 
388 	last = SYS_SLIST_PEEK_TAIL_CONTAINER(&db, last, node);
389 	handle = last->attrs[last->attr_count - 1].handle;
390 	last_handle = handle;
391 
392 populate:
393 	/* Populate the handles and append them to the list */
394 	for (; attrs && count; attrs++, count--) {
395 		if (!attrs->handle) {
396 			/* Allocate handle if not set already */
397 			attrs->handle = ++handle;
398 		} else if (attrs->handle > handle) {
399 			/* Use existing handle if valid */
400 			handle = attrs->handle;
401 		} else if (find_attr(attrs->handle)) {
402 			/* Service has conflicting handles */
403 			LOG_ERR("Mock: Unable to register handle 0x%04x", attrs->handle);
404 			return -EINVAL;
405 		}
406 
407 		LOG_DBG("attr %p handle 0x%04x uuid %s perm 0x%02x", attrs, attrs->handle,
408 			bt_uuid_str(attrs->uuid), attrs->perm);
409 	}
410 
411 	gatt_insert(svc, last_handle);
412 
413 	return 0;
414 }
415 
gatt_unregister(struct bt_gatt_service * svc)416 static int gatt_unregister(struct bt_gatt_service *svc)
417 {
418 	if (!sys_slist_find_and_remove(&db, &svc->node)) {
419 		return -ENOENT;
420 	}
421 
422 	return 0;
423 }
424 
bt_gatt_service_register(struct bt_gatt_service * svc)425 int bt_gatt_service_register(struct bt_gatt_service *svc)
426 {
427 	int err;
428 
429 	__ASSERT(svc, "invalid parameters\n");
430 	__ASSERT(svc->attrs, "invalid parameters\n");
431 	__ASSERT(svc->attr_count, "invalid parameters\n");
432 
433 	/* Init GATT core services */
434 	bt_gatt_service_init();
435 
436 	/* Do no allow to register mandatory services twice */
437 	if (!bt_uuid_cmp(svc->attrs[0].uuid, BT_UUID_GAP) ||
438 	    !bt_uuid_cmp(svc->attrs[0].uuid, BT_UUID_GATT)) {
439 		return -EALREADY;
440 	}
441 
442 	err = gatt_register(svc);
443 	if (err < 0) {
444 		return err;
445 	}
446 
447 	return 0;
448 }
449 
bt_gatt_service_unregister(struct bt_gatt_service * svc)450 int bt_gatt_service_unregister(struct bt_gatt_service *svc)
451 {
452 	int err;
453 
454 	__ASSERT(svc, "invalid parameters\n");
455 
456 	err = gatt_unregister(svc);
457 	if (err) {
458 		return err;
459 	}
460 
461 	return 0;
462 }
463 
464 /* Exact copy of subsys/bluetooth/host/gatt.c:bt_gatt_attr_read() */
bt_gatt_attr_read(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t buf_len,uint16_t offset,const void * value,uint16_t value_len)465 ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
466 			  void *buf, uint16_t buf_len, uint16_t offset,
467 			  const void *value, uint16_t value_len)
468 {
469 	uint16_t len;
470 
471 	if (offset > value_len) {
472 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
473 	}
474 
475 	len = MIN(buf_len, value_len - offset);
476 
477 	LOG_DBG("handle 0x%04x offset %u length %u", attr->handle, offset, len);
478 
479 	memcpy(buf, (uint8_t *)value + offset, len);
480 
481 	return len;
482 }
483 
bt_gatt_discover(struct bt_conn * conn,struct bt_gatt_discover_params * params)484 int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *params)
485 {
486 	zassert_not_null(conn, "'%s()' was called with incorrect '%s' value", __func__, "conn");
487 	zassert_not_null(params, "'%s()' was called with incorrect '%s' value", __func__, "params");
488 	zassert_not_null(params->func, "'%s()' was called with incorrect '%s' value", __func__,
489 			 "params->func");
490 	zassert_between_inclusive(
491 		params->start_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE,
492 		"'%s()' was called with incorrect '%s' value", __func__, "params->start_handle");
493 	zassert_between_inclusive(
494 		params->end_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE,
495 		"'%s()' was called with incorrect '%s' value", __func__, "params->end_handle");
496 	zassert_true(params->start_handle <= params->end_handle,
497 		     "'%s()' was called with incorrect '%s' value", __func__, "params->end_handle");
498 
499 	struct bt_gatt_service_val value;
500 	struct bt_uuid_16 uuid;
501 	struct bt_gatt_attr attr;
502 	uint16_t start_handle;
503 	uint16_t end_handle;
504 
505 	if (conn->info.state != BT_CONN_STATE_CONNECTED) {
506 		return -ENOTCONN;
507 	}
508 
509 	switch (params->type) {
510 	case BT_GATT_DISCOVER_PRIMARY:
511 	case BT_GATT_DISCOVER_SECONDARY:
512 	case BT_GATT_DISCOVER_STD_CHAR_DESC:
513 	case BT_GATT_DISCOVER_INCLUDE:
514 	case BT_GATT_DISCOVER_CHARACTERISTIC:
515 	case BT_GATT_DISCOVER_DESCRIPTOR:
516 	case BT_GATT_DISCOVER_ATTRIBUTE:
517 		break;
518 	default:
519 		LOG_ERR("Invalid discovery type: %u", params->type);
520 		return -EINVAL;
521 	}
522 
523 	uuid.uuid.type = BT_UUID_TYPE_16;
524 	uuid.val = params->type;
525 	start_handle = params->start_handle;
526 	end_handle = params->end_handle;
527 	value.end_handle = end_handle;
528 	value.uuid = params->uuid;
529 
530 	attr = (struct bt_gatt_attr){
531 		.uuid = &uuid.uuid,
532 		.user_data = &value,
533 		.handle = start_handle,
534 	};
535 
536 	params->func(conn, &attr, params);
537 
538 	return 0;
539 }
540 
bt_gatt_get_mtu(struct bt_conn * conn)541 uint16_t bt_gatt_get_mtu(struct bt_conn *conn)
542 {
543 	return 64;
544 }
545 
bt_gatt_is_subscribed(struct bt_conn * conn,const struct bt_gatt_attr * attr,uint16_t ccc_type)546 bool bt_gatt_is_subscribed(struct bt_conn *conn,
547 			   const struct bt_gatt_attr *attr, uint16_t ccc_type)
548 {
549 	return mock_bt_gatt_is_subscribed(conn, attr, ccc_type);
550 }
551 
bt_gatt_attr_get_handle(const struct bt_gatt_attr * attr)552 uint16_t bt_gatt_attr_get_handle(const struct bt_gatt_attr *attr)
553 {
554 	uint16_t handle = 1;
555 
556 	if (!attr) {
557 		return 0;
558 	}
559 
560 	if (attr->handle) {
561 		return attr->handle;
562 	}
563 
564 	STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) {
565 		/* Skip ahead if start is not within service attributes array */
566 		if ((attr < &static_svc->attrs[0]) ||
567 		    (attr > &static_svc->attrs[static_svc->attr_count - 1])) {
568 			handle += static_svc->attr_count;
569 			continue;
570 		}
571 
572 		for (size_t i = 0; i < static_svc->attr_count; i++, handle++) {
573 			if (attr == &static_svc->attrs[i]) {
574 				return handle;
575 			}
576 		}
577 	}
578 
579 	return 0;
580 }
581 
find_next(const struct bt_gatt_attr * attr,uint16_t handle,void * user_data)582 static uint8_t find_next(const struct bt_gatt_attr *attr, uint16_t handle, void *user_data)
583 {
584 	struct bt_gatt_attr **next = user_data;
585 
586 	*next = (struct bt_gatt_attr *)attr;
587 
588 	return BT_GATT_ITER_STOP;
589 }
590 
bt_gatt_find_by_uuid(const struct bt_gatt_attr * attr,uint16_t attr_count,const struct bt_uuid * uuid)591 struct bt_gatt_attr *bt_gatt_find_by_uuid(const struct bt_gatt_attr *attr, uint16_t attr_count,
592 					  const struct bt_uuid *uuid)
593 {
594 	struct bt_gatt_attr *found = NULL;
595 	uint16_t start_handle = bt_gatt_attr_get_handle(attr);
596 	uint16_t end_handle = start_handle && attr_count
597 				      ? MIN(start_handle + attr_count, BT_ATT_LAST_ATTRIBUTE_HANDLE)
598 				      : BT_ATT_LAST_ATTRIBUTE_HANDLE;
599 
600 	if (attr != NULL && start_handle == 0U) {
601 		/* If start_handle is 0 then `attr` is not in our database, and should not be used
602 		 * as a starting point for the search
603 		 */
604 		LOG_DBG("Could not find handle of attr %p", attr);
605 		return NULL;
606 	}
607 
608 	bt_gatt_foreach_attr_type(start_handle, end_handle, uuid, NULL, 1, find_next, &found);
609 
610 	return found;
611 }
612