Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog
@ 2026-09-28 17:32 Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 01/21] shared/gatt-client: Fix calling destroy after unregistering notify Luiz Augusto von Dentz
                   ` (20 more replies)
  0 siblings, 21 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds functional tests for HID over GATT (HoG), with bluetoothctl
registering a HID Service acting as a keyboard, with and without
Shorter Connection Interval (SCI) support, using the new
client/scripts/hog-device*.bt scripts, and the HID host:

 - checking HID Information, HID SCI Mode and HID SCI Information over
   GATT with gatt.select-attribute/gatt.read
 - receiving a few Input Reports notified by the HID device
 - with SCI support, requesting SCI Fast mode with the HID Control
   Point, as specified by HOGP.TS 4.6.1, followed by the connection
   rate with mgmt.conn-subrate, and receiving the notification of HID
   SCI Mode from the HID device confirming the mode has been changed

The tests are documented in doc/functional-hog.rst.

The input plugin is also moved away from GAttrib: the new src/shared/hog
implements HID over GATT on top of bt_gatt_client and gatt_db, as a
drop-in replacement of profiles/input/hog-lib, which is removed along
with the GAttrib based Battery, Device Information and Scan Parameters
implementations only used by it, these services being handled by their
own plugins. src/shared/hog supports HID SCI, requesting a mode with
the HID Control Point and enabling the notifications of HID SCI Mode.

With that GAttrib has no users left in bluetoothd, which now creates
the bt_att of the connection directly, so GAttrib is removed along with
the deprecated gatttool, its only other user, and then the attrib
directory, moving what bluetoothd still uses to src/shared/att and
src/device.

unit/test-hog is ported to src/shared/hog, with the test cases renamed
after HOGP.TS p13 and the missing ones for the Report Host added, except
HID ISO which is not supported, including the HID SCI test cases
HGWF/BV-08-C to BV-11-C.

To support this:

 - bluetoothctl can now set descriptor values from scripts, prints the
   MGMT Connection Subrate event, and no longer crashes when the auto
   agent is canceled
 - btvirt defaults to the latest BR/EDR+LE version (6.2), so Shorter
   Connection Intervals are supported by the emulated controllers,
   with the new -C/--core option to emulate older versions
 - the tester can expect a PDU with no response, e.g. Write Command
 - unit/test-uhid tests the replies to Get Report, including through
   hidraw when run as root, which requires CONFIG_HIDRAW now added to
   the tester kernel config

Also, with -n auto, the number of functional test workers is now
limited by the memory available instead of one per CPU, since running
out of memory with so many VM instances made tests fail at random, and
check-functional uses -n auto by default (override with
CHECK_FUNCTIONAL_JOBS).

v5:
 - Add "attrib: Remove directory", moving att_ecode2str to src/shared/att
   as bt_att_ecode2str, and struct gatt_primary and gatt_parse_record to
   src/device

v4:
 - Fix a use-after-free in bluetoothd introduced by "shared/gatt-client:
   Fix calling destroy after unregistering notify", when enabling
   notifications with StartNotify fails, and hold a reference to the
   client while calling destroy
 - Drop "attrib: Fix unregistering notifications registered with
   bt_gatt_client", as GAttrib is now removed
 - client/gatt: fix use-after-free on invalid values set from scripts,
   uninitialized bytes when parsing values with consecutive separators,
   and reject negative values
 - Add "client/agent: Fix crash on Cancel with no pending request",
   reported by TestFunctional with v3
 - Add src/shared/hog, replacing hog-lib in the input plugin, and port
   unit/test-hog to it with HOGP.TS p13 test cases, including HID SCI
 - Change the HID SCI mode with the HID Control Point in the functional
   test, HID SCI Mode being Read and Notify only as specified
 - Fix the size of the reply to Get Report with a Report ID in
   shared/uhid, and add unit/test-uhid Get Report tests along with
   CONFIG_HIDRAW in the tester kernel config
 - Add "device: Use bt_att instead of GAttrib" and "attrib: Remove
   GAttrib and gatttool"
 - Honour PYTEST_XDIST_AUTO_NUM_WORKERS and the CPU affinity when
   limiting the functional test workers
 - Add Assisted-by tags

v3:
 - Add "shared/gatt-client: Fix calling destroy after unregistering
   notify", fixing the heap-use-after-free in report_notify_destroy
   still reported by TestFunctional on the HoG tests with v2: once
   unregistered, the destroy callback of the notification was still
   called later if the write of the CCC disabling it was pending, after
   HoG had freed its reports.

v2:
 - Add "attrib: Fix unregistering notifications registered with
   bt_gatt_client", fixing the heap-use-after-free in
   report_notify_destroy reported by TestFunctional on the HoG tests:
   g_attrib_unregister did not unregister the notifications registered
   with bt_gatt_client, so their destroy callback was called after
   HoG had freed its reports.

Luiz Augusto von Dentz (21):
  shared/gatt-client: Fix calling destroy after unregistering notify
  client/gatt: Fix setting descriptor value from scripts
  client/mgmt: Print Connection Subrate event
  emulator: Default to the latest BR/EDR+LE version
  client/scripts: Add HoG device scripts
  doc: Add functional-hog documentation
  test: functional: add HoG tests
  test: functional: limit the workers by the memory available
  client/agent: Fix crash on Cancel with no pending request
  shared/uhid: Fix size of Get Report reply with a Report ID
  shared/uhid: Keep reading when an event is not available
  shared/tester: Allow expecting a PDU with no response
  shared/hog: Add initial implementation
  unit/test-hog: Use shared/hog
  test: functional: change the HoG SCI mode with the HID Control Point
  input/hog: Use shared/hog
  doc: Add CONFIG_HIDRAW to the tester kernel config
  unit/test-uhid: Add Get Report tests
  device: Use bt_att instead of GAttrib
  attrib: Remove GAttrib and gatttool
  attrib: Remove directory

 .gitignore                       |    2 -
 Makefile.am                      |   33 +-
 Makefile.plugins                 |    4 -
 Makefile.tools                   |   12 -
 attrib/att-database.h            |   30 -
 attrib/att.c                     | 1238 -------------------
 attrib/att.h                     |  186 ---
 attrib/gatt.c                    | 1249 -------------------
 attrib/gatt.h                    |  109 --
 attrib/gattrib.c                 |  473 -------
 attrib/gattrib.h                 |   65 -
 attrib/gatttool.c                |  612 ----------
 attrib/gatttool.h                |   17 -
 attrib/interactive.c             | 1020 ----------------
 attrib/utils.c                   |  110 --
 client/agent.c                   |   10 +-
 client/gatt.c                    |   39 +-
 client/mgmt.c                    |   31 +
 client/scripts/hog-device-sci.bt |   49 +
 client/scripts/hog-device.bt     |   38 +
 doc/functional-hog.rst           |  204 ++++
 doc/functional-testing.rst       |    1 +
 doc/test-functional.rst          |   29 +-
 doc/test-runner.rst              |    5 +
 doc/tester.config                |    1 +
 emulator/main.c                  |   55 +-
 emulator/server.c                |   15 +-
 emulator/server.h                |    2 +
 profiles/battery/bas.c           |  327 -----
 profiles/battery/bas.h           |   19 -
 profiles/battery/battery.c       |    3 +-
 profiles/deviceinfo/deviceinfo.c |    5 +-
 profiles/deviceinfo/dis.c        |  340 ------
 profiles/deviceinfo/dis.h        |   27 -
 profiles/input/hog-lib.c         | 1966 ------------------------------
 profiles/input/hog-lib.h         |   28 -
 profiles/input/hog.c             |   31 +-
 profiles/midi/midi.c             |    3 +-
 profiles/ranging/rap.c           |    2 -
 profiles/scanparam/scan.c        |    3 +-
 profiles/scanparam/scpp.c        |  342 ------
 profiles/scanparam/scpp.h        |   22 -
 src/adapter.c                    |    4 +-
 src/device.c                     |  122 +-
 src/device.h                     |   14 +-
 src/gatt-client.c                |    7 +-
 src/shared/att.c                 |   48 +
 src/shared/att.h                 |    2 +
 src/shared/gatt-client.c         |   21 +
 src/shared/hog.c                 | 1659 +++++++++++++++++++++++++
 src/shared/hog.h                 |   67 +
 src/shared/tester.c              |   19 +
 src/shared/uhid.c                |    7 +-
 test/functional/conftest.py      |   58 +
 test/functional/test_hog.py      |  269 ++++
 unit/test-gattrib.c              |  552 ---------
 unit/test-hog.c                  | 1431 ++++++++++++++++------
 unit/test-uhid.c                 |  272 +++++
 58 files changed, 4106 insertions(+), 9203 deletions(-)
 delete mode 100644 attrib/att-database.h
 delete mode 100644 attrib/att.c
 delete mode 100644 attrib/att.h
 delete mode 100644 attrib/gatt.c
 delete mode 100644 attrib/gatt.h
 delete mode 100644 attrib/gattrib.c
 delete mode 100644 attrib/gattrib.h
 delete mode 100644 attrib/gatttool.c
 delete mode 100644 attrib/gatttool.h
 delete mode 100644 attrib/interactive.c
 delete mode 100644 attrib/utils.c
 create mode 100644 client/scripts/hog-device-sci.bt
 create mode 100644 client/scripts/hog-device.bt
 create mode 100644 doc/functional-hog.rst
 delete mode 100644 profiles/battery/bas.c
 delete mode 100644 profiles/battery/bas.h
 delete mode 100644 profiles/deviceinfo/dis.c
 delete mode 100644 profiles/deviceinfo/dis.h
 delete mode 100644 profiles/input/hog-lib.c
 delete mode 100644 profiles/input/hog-lib.h
 delete mode 100644 profiles/scanparam/scpp.c
 delete mode 100644 profiles/scanparam/scpp.h
 create mode 100644 src/shared/hog.c
 create mode 100644 src/shared/hog.h
 create mode 100644 test/functional/test_hog.py
 delete mode 100644 unit/test-gattrib.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 01/21] shared/gatt-client: Fix calling destroy after unregistering notify
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 02/21] client/gatt: Fix setting descriptor value from scripts Luiz Augusto von Dentz
                   ` (19 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

bt_gatt_client_unregister_notify resets the callbacks of the
notification but not its destroy callback, which is called once the
notify_data is freed. If a procedure is still pending at that point,
e.g. the write of the CCC to disable the notifications, it holds a
reference to notify_data so destroy is called later, once the user data
may have been freed, e.g. the reports of HoG:

 ERROR: AddressSanitizer: heap-use-after-free
 #11 report_notify_destroy profiles/input/hog-lib.c:359
 #12 attrib_callbacks_destroy attrib/gattrib.c:130
 #13 notify_data_unref src/shared/gatt-client.c:256
 #15 destroy_write_op src/shared/gatt-client.c:3189
 #16 request_unref src/shared/gatt-client.c:201
 #17 destroy_att_send_op src/shared/att.c:215
 #18 bt_att_cancel src/shared/att.c:1925
 #19 cancel_request src/shared/gatt-client.c:2783
 ...
 #21 bt_gatt_client_cancel_all src/shared/gatt-client.c:2811
 #22 bt_gatt_client_free src/shared/gatt-client.c:2290

Call destroy when unregistering instead, once done with notify_data and
holding a reference to the client in case destroy drops the last one.

As destroy is now called when unregistering, reply to StartNotify before
freeing the notify client when enabling the notifications fails, since
it frees the operation the reply is for.

Assisted-by: OpenCode:claude-opus-5.5
---
 src/gatt-client.c        |  7 +++++--
 src/shared/gatt-client.c | 21 +++++++++++++++++++++
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/src/gatt-client.c b/src/gatt-client.c
index 3baf95c4f79c..d94dc9d7fbf6 100644
--- a/src/gatt-client.c
+++ b/src/gatt-client.c
@@ -1488,12 +1488,15 @@ static void register_notify_cb(uint16_t att_ecode, void *user_data)
 	struct characteristic *chrc = client->chrc;
 
 	if (att_ecode) {
+		/* Reply first, as freeing the client unregisters the
+		 * notification, which frees op with its destroy callback.
+		 */
+		create_notify_reply(op, false, att_ecode);
+
 		queue_remove(chrc->notify_clients, client);
 		queue_remove(chrc->service->client->all_notify_clients, client);
 		notify_client_free(client);
 
-		create_notify_reply(op, false, att_ecode);
-
 		return;
 	}
 
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 92ad7c39c115..b3bc62220e16 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -3842,6 +3842,8 @@ bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
 							unsigned int id)
 {
 	struct notify_data *notify_data;
+	bt_gatt_client_destroy_func_t destroy;
+	void *user_data;
 
 	if (!client || !id)
 		return false;
@@ -3858,7 +3860,26 @@ bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
 	notify_data->callback = NULL;
 	notify_data->notify = NULL;
 
+	/* Call destroy once unregistered, as the user data may be freed then,
+	 * while notify_data may still be referenced by a pending procedure,
+	 * e.g. the write of the CCC, which would otherwise call it later.
+	 */
+	destroy = notify_data->destroy;
+	user_data = notify_data->user_data;
+	notify_data->destroy = NULL;
+
+	/* The client may be freed by destroy, e.g. if the user data holds
+	 * its last reference.
+	 */
+	bt_gatt_client_ref(client);
+
 	complete_unregister_notify(notify_data);
+
+	if (destroy)
+		destroy(user_data);
+
+	bt_gatt_client_unref(client);
+
 	return true;
 }
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 02/21] client/gatt: Fix setting descriptor value from scripts
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 01/21] shared/gatt-client: Fix calling destroy after unregistering notify Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 03/21] client/mgmt: Print Connection Subrate event Luiz Augusto von Dentz
                   ` (18 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

gatt.register-descriptor completed the command right after prompting
for the value, so when run from a script the line with the value was
executed as a command instead of being passed to the prompt, causing
the descriptor to be unregistered.

Complete the command once the value is set, as done for
characteristics, and parse a copy of the value so the input line is
not truncated by strsep while still in use by the shell.

As invalid values can now come from scripts, fix handling them: the
attribute is no longer used once unregistered, which frees it, nor kept
in the list of its parent, and the command fails. Also stop counting
the empty entries between the values, which left bytes uninitialized,
and reject negative values.

Assisted-by: OpenCode:claude-opus-5.5
---
 client/gatt.c | 39 ++++++++++++++++++++++++++++-----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/client/gatt.c b/client/gatt.c
index 6dc80e2a31cd..a85f6003d9b8 100644
--- a/client/gatt.c
+++ b/client/gatt.c
@@ -700,13 +700,21 @@ void gatt_read_local_attribute(char *data, int argc, char *argv[])
 	return bt_shell_noninteractive_quit(EXIT_FAILURE);
 }
 
-static uint8_t *str2bytearray(char *arg, size_t *val_len)
+static uint8_t *str2bytearray(const char *arg, size_t *val_len)
 {
 	uint8_t value[MAX_ATTR_VAL_LEN];
-	char *entry;
+	char *str, *next, *entry;
 	unsigned int i;
 
-	for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) {
+	/* Parse a copy as strsep modifies the string, which may still be
+	 * in use by the caller, e.g. the shell printing the input line.
+	 */
+	str = next = strdup(arg);
+	if (!str)
+		return NULL;
+
+	/* Only count the values, not the empty entries in between */
+	for (i = 0; (entry = strsep(&next, " \t")) != NULL;) {
 		long val;
 		char *endptr = NULL;
 
@@ -715,18 +723,22 @@ static uint8_t *str2bytearray(char *arg, size_t *val_len)
 
 		if (i >= G_N_ELEMENTS(value)) {
 			bt_shell_printf("Too much data\n");
+			free(str);
 			return NULL;
 		}
 
 		val = strtol(entry, &endptr, 0);
-		if (!endptr || *endptr != '\0' || val > UINT8_MAX) {
+		if (!endptr || *endptr != '\0' || val < 0 || val > UINT8_MAX) {
 			bt_shell_printf("Invalid value at index %d\n", i);
+			free(str);
 			return NULL;
 		}
 
-		value[i] = val;
+		value[i++] = val;
 	}
 
+	free(str);
+
 	*val_len = i;
 
 	return util_memdup(value, i);
@@ -2788,11 +2800,14 @@ static void chrc_set_value(const char *input, void *user_data)
 
 	g_free(chrc->value);
 
-	chrc->value = str2bytearray((char *) input, &chrc->value_len);
+	chrc->value = str2bytearray(input, &chrc->value_len);
 
 	if (!chrc->value) {
-		print_chrc(chrc, COLORED_DEL);
+		/* Unregistering frees chrc, so it is removed first */
+		chrc->service->chrcs = g_list_remove(chrc->service->chrcs,
+									chrc);
 		chrc_unregister(chrc);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
 	}
 
 	chrc->max_val_len = chrc->value_len;
@@ -3078,14 +3093,18 @@ static void desc_set_value(const char *input, void *user_data)
 
 	g_free(desc->value);
 
-	desc->value = str2bytearray((char *) input, &desc->value_len);
+	desc->value = str2bytearray(input, &desc->value_len);
 
 	if (!desc->value) {
-		print_desc(desc, COLORED_DEL);
+		/* Unregistering frees desc, so it is removed first */
+		desc->chrc->descs = g_list_remove(desc->chrc->descs, desc);
 		desc_unregister(desc);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
 	}
 
 	desc->max_val_len = desc->value_len;
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 }
 
 void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy,
@@ -3134,8 +3153,6 @@ void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy,
 	print_desc(desc, COLORED_NEW);
 
 	bt_shell_prompt_input(desc->path, "Enter value:", desc_set_value, desc);
-
-	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 }
 
 static struct desc *desc_find(const char *pattern)
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 03/21] client/mgmt: Print Connection Subrate event
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 01/21] shared/gatt-client: Fix calling destroy after unregistering notify Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 02/21] client/gatt: Fix setting descriptor value from scripts Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 04/21] emulator: Default to the latest BR/EDR+LE version Luiz Augusto von Dentz
                   ` (17 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Print the MGMT Connection Subrate event, generated as a result of a
LE Connection Rate Request, e.g. after mgmt.conn-subrate, or a change
initiated by the remote device, so the new connection rate can be
confirmed.

Assisted-by: OpenCode:claude-opus-5.5
---
 client/mgmt.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/client/mgmt.c b/client/mgmt.c
index cd2ef80221ad..a7069e5bbb37 100644
--- a/client/mgmt.c
+++ b/client/mgmt.c
@@ -499,6 +499,35 @@ static void disconnected(uint16_t index, uint16_t len, const void *param,
 			index, addr, typestr(ev->addr.type), reason);
 }
 
+static void conn_subrate(uint16_t index, uint16_t len, const void *param,
+							void *user_data)
+{
+	const struct mgmt_ev_conn_subrate *ev = param;
+	char addr[18];
+
+	if (len < sizeof(*ev)) {
+		error("Invalid connection subrate event length (%u bytes)",
+									len);
+		return;
+	}
+
+	ba2str(&ev->addr.bdaddr, addr);
+
+	if (ev->status) {
+		print("hci%u %s type %s connection subrate failed status "
+			"0x%02x (%s)", index, addr, typestr(ev->addr.type),
+			ev->status, mgmt_errstr(ev->status));
+		return;
+	}
+
+	print("hci%u %s type %s connection subrate interval 0x%04x "
+		"subrate 0x%04x latency 0x%04x cont_num 0x%04x timeout 0x%04x",
+		index, addr, typestr(ev->addr.type),
+		le16_to_cpu(ev->interval), le16_to_cpu(ev->subrate),
+		le16_to_cpu(ev->latency), le16_to_cpu(ev->cont_num),
+		le16_to_cpu(ev->supv_timeout));
+}
+
 static void conn_failed(uint16_t index, uint16_t len, const void *param,
 							void *user_data)
 {
@@ -6006,6 +6035,8 @@ static void register_mgmt_callbacks(struct mgmt *mgmt, uint16_t index)
 								NULL, NULL);
 	mgmt_register(mgmt, MGMT_EV_CONNECT_FAILED, index, conn_failed,
 								NULL, NULL);
+	mgmt_register(mgmt, MGMT_EV_CONN_SUBRATE, index, conn_subrate,
+								NULL, NULL);
 	mgmt_register(mgmt, MGMT_EV_AUTH_FAILED, index, auth_failed,
 								NULL, NULL);
 	mgmt_register(mgmt, MGMT_EV_CLASS_OF_DEV_CHANGED, index,
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 04/21] emulator: Default to the latest BR/EDR+LE version
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (2 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 03/21] client/mgmt: Print Connection Subrate event Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 05/21] client/scripts: Add HoG device scripts Luiz Augusto von Dentz
                   ` (16 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Emulate BR/EDR+LE 6.2 controllers by default, for both local (-l) and
server (--server, --tcp) controllers, so the latest features such as
Shorter Connection Intervals (LE Connection Rate Request) are
supported, e.g. by test-functional which uses --server.

Add the -C/--core=<version> option to emulate older versions instead:
5.0, 5.2, 6.0 or 6.2.

Assisted-by: OpenCode:claude-opus-5.5
---
 doc/test-functional.rst |  4 ++-
 emulator/main.c         | 55 ++++++++++++++++++++++++++++++++++++++---
 emulator/server.c       | 15 ++++++++++-
 emulator/server.h       |  2 ++
 4 files changed, 71 insertions(+), 5 deletions(-)

diff --git a/doc/test-functional.rst b/doc/test-functional.rst
index 3ffcbf6dec5c..826210b73ce7 100644
--- a/doc/test-functional.rst
+++ b/doc/test-functional.rst
@@ -227,7 +227,9 @@ Controllers
 
 By default a single ``btvirt`` process runs on the developer machine and
 provides an emulated BR/EDR/LE controller to every VM host over a UNIX
-socket, also bridging the air interface between them::
+socket, also bridging the air interface between them. The controllers
+emulate the latest Core Specification version supported by ``btvirt``,
+6.2, older ones can be emulated with its ``-C/--core`` option::
 
     VM host #0                 developer machine              VM host #1
     ┌────────────┐            ┌─────────────────┐            ┌────────────┐
diff --git a/emulator/main.c b/emulator/main.c
index c21640adc359..b0e295b17609 100644
--- a/emulator/main.c
+++ b/emulator/main.c
@@ -24,8 +24,8 @@
 #include "src/shared/util.h"
 
 #include "serial.h"
-#include "server.h"
 #include "btdev.h"
+#include "server.h"
 #include "vhci.h"
 #include "le.h"
 
@@ -54,6 +54,10 @@ static void usage(void)
 		"\t-U[num]               Number of test LE controllers\n"
 		"\t-B                    Create BR/EDR only controller\n"
 		"\t-A                    Create AMP controller\n"
+		"\t-C, --core=<version>  Core Specification version of the\n"
+		"\t                      BR/EDR/LE controllers created with\n"
+		"\t                      -l, -s and -t:\n"
+		"\t                      5.0, 5.2, 6.0 or 6.2 (default)\n"
 		"\t-T[num]               Number of test AMP controllers\n"
 		"\t-h, --help            Show help options\n");
 }
@@ -67,12 +71,37 @@ static const struct option main_options[] = {
 	{ "le",      no_argument,       NULL, 'L' },
 	{ "bredr",   no_argument,       NULL, 'B' },
 	{ "amp",     no_argument,       NULL, 'A' },
+	{ "core",    required_argument, NULL, 'C' },
 	{ "letest",  optional_argument, NULL, 'U' },
 	{ "version", no_argument,	NULL, 'v' },
 	{ "help",    no_argument,	NULL, 'h' },
 	{ }
 };
 
+static const struct {
+	const char *version;
+	enum btdev_type type;
+} core_versions[] = {
+	{ "5.0", BTDEV_TYPE_BREDRLE50 },
+	{ "5.2", BTDEV_TYPE_BREDRLE52 },
+	{ "6.0", BTDEV_TYPE_BREDRLE60 },
+	{ "6.2", BTDEV_TYPE_BREDRLE62 },
+};
+
+static bool parse_core_version(const char *version, enum btdev_type *type)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(core_versions); i++) {
+		if (!strcmp(core_versions[i].version, version)) {
+			*type = core_versions[i].type;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 static void vhci_debug(const char *str, void *user_data)
 {
 	int i = PTR_TO_UINT(user_data);
@@ -101,7 +130,10 @@ int main(int argc, char *argv[])
 	bool serial_enabled = false;
 	int letest_count = 0;
 	int vhci_count = 0;
-	enum btdev_type type = BTDEV_TYPE_BREDRLE60;
+	/* Default to the latest version supported by the emulator */
+	enum btdev_type bredrle_type = BTDEV_TYPE_BREDRLE62;
+	enum btdev_type type;
+	bool type_set = false;
 	int i;
 
 	mainloop_init();
@@ -109,7 +141,7 @@ int main(int argc, char *argv[])
 	for (;;) {
 		int opt;
 
-		opt = getopt_long(argc, argv, "dSs::t::l::LBAU::T::vh",
+		opt = getopt_long(argc, argv, "dSs::t::l::LBAC:U::T::vh",
 						main_options, NULL);
 		if (opt < 0)
 			break;
@@ -140,12 +172,22 @@ int main(int argc, char *argv[])
 			break;
 		case 'L':
 			type = BTDEV_TYPE_LE;
+			type_set = true;
 			break;
 		case 'B':
 			type = BTDEV_TYPE_BREDR;
+			type_set = true;
 			break;
 		case 'A':
 			type = BTDEV_TYPE_AMP;
+			type_set = true;
+			break;
+		case 'C':
+			if (!parse_core_version(optarg, &bredrle_type)) {
+				fprintf(stderr, "Unsupported version: %s\n",
+								optarg);
+				return EXIT_FAILURE;
+			}
 			break;
 		case 'U':
 			if (optarg)
@@ -164,6 +206,9 @@ int main(int argc, char *argv[])
 		}
 	}
 
+	if (!type_set)
+		type = bredrle_type;
+
 	if (letest_count < 1 && vhci_count < 1 && !server_enabled &&
 						!tcp_port && !serial_enabled) {
 		fprintf(stderr, "No emulator specified\n");
@@ -214,6 +259,8 @@ int main(int argc, char *argv[])
 		server1 = server_open_unix(SERVER_TYPE_BREDRLE, path);
 		if (!server1)
 			fprintf(stderr, "Failed to open BR/EDR/LE server\n");
+		else
+			server_set_bredrle_type(server1, bredrle_type);
 
 		snprintf(path, sizeof(path), "%s/%s", server_path,
 							"bt-server-bredr");
@@ -255,6 +302,8 @@ int main(int argc, char *argv[])
 		tcp_server = server_open_tcp(SERVER_TYPE_BREDRLE, tcp_port);
 		if (!tcp_server)
 			fprintf(stderr, "Failed to open TCP port\n");
+		else
+			server_set_bredrle_type(tcp_server, bredrle_type);
 		fprintf(stderr, "Listening TCP on 127.0.0.1:%d\n", tcp_port);
 	}
 
diff --git a/emulator/server.c b/emulator/server.c
index e3eda8458ae0..5a827b24531e 100644
--- a/emulator/server.c
+++ b/emulator/server.c
@@ -38,6 +38,7 @@
 
 struct server {
 	enum server_type type;
+	enum btdev_type bredrle_type;
 	uint16_t id;
 	int fd;
 	struct queue *clients;
@@ -281,7 +282,7 @@ static void server_accept_callback(int fd, uint32_t events, void *user_data)
 
 	switch (server->type) {
 	case SERVER_TYPE_BREDRLE:
-		type = BTDEV_TYPE_BREDRLE52;
+		type = server->bredrle_type;
 		break;
 	case SERVER_TYPE_BREDR:
 		type = BTDEV_TYPE_BREDR;
@@ -361,6 +362,7 @@ struct server *server_open_unix(enum server_type type, const char *path)
 
 	memset(server, 0, sizeof(*server));
 	server->type = type;
+	server->bredrle_type = BTDEV_TYPE_BREDRLE62;
 	server->id = 0x42;
 
 	server->fd = open_unix(path);
@@ -429,6 +431,7 @@ struct server *server_open_tcp(enum server_type type, uint16_t port)
 
 	memset(server, 0, sizeof(*server));
 	server->type = type;
+	server->bredrle_type = BTDEV_TYPE_BREDRLE62;
 	server->id = 0x43;
 
 	server->fd = open_tcp(port);
@@ -455,6 +458,16 @@ void server_close(struct server *server)
 	mainloop_remove_fd(server->fd);
 }
 
+bool server_set_bredrle_type(struct server *server, enum btdev_type type)
+{
+	if (!server || server->type != SERVER_TYPE_BREDRLE)
+		return false;
+
+	server->bredrle_type = type;
+
+	return true;
+}
+
 bool server_set_debug(struct server *server, server_debug_func_t callback,
 			void *user_data, server_destroy_func_t destroy)
 {
diff --git a/emulator/server.h b/emulator/server.h
index 1844a9871af1..5fc3a284f53f 100644
--- a/emulator/server.h
+++ b/emulator/server.h
@@ -25,6 +25,8 @@ struct server *server_open_unix(enum server_type type, const char *path);
 struct server *server_open_tcp(enum server_type type, uint16_t port);
 void server_close(struct server *server);
 
+bool server_set_bredrle_type(struct server *server, enum btdev_type type);
+
 typedef void (*server_debug_func_t)(const char *str, void *user_data);
 typedef void (*server_destroy_func_t)(void *user_data);
 bool server_set_debug(struct server *server, server_debug_func_t callback,
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 05/21] client/scripts: Add HoG device scripts
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (3 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 04/21] emulator: Default to the latest BR/EDR+LE version Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 06/21] doc: Add functional-hog documentation Luiz Augusto von Dentz
                   ` (15 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Add scripts registering a HID Service (HIDS) acting as a HID over GATT
keyboard, with and without Shorter Connection Interval (SCI) support.

HID SCI Mode can be notified so the device can confirm a mode change.

Assisted-by: OpenCode:claude-opus-5.5
---
 client/scripts/hog-device-sci.bt | 49 ++++++++++++++++++++++++++++++++
 client/scripts/hog-device.bt     | 38 +++++++++++++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 client/scripts/hog-device-sci.bt
 create mode 100644 client/scripts/hog-device.bt

diff --git a/client/scripts/hog-device-sci.bt b/client/scripts/hog-device-sci.bt
new file mode 100644
index 000000000000..1bf3a0a90db7
--- /dev/null
+++ b/client/scripts/hog-device-sci.bt
@@ -0,0 +1,49 @@
+#
+# Register a HID Service (HIDS) acting as a HID over GATT (HoG) keyboard
+# with Shorter Connection Interval (SCI) support.
+#
+gatt.register-service 0x1812
+# Primary
+yes
+#
+# HID Information: bcdHID 1.11, bCountryCode 0x00,
+# Flags: NormallyConnectable and SCI Supported
+gatt.register-characteristic 0x2a4a read
+0x11 0x01 0x00 0x06
+#
+# Report Map: keyboard with Report ID 1
+gatt.register-characteristic 0x2a4b read
+0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x08 0x81 0x02 0x95 0x01 0x75 0x08 0x81 0x01 0x95 0x06 0x75 0x08 0x15 0x00 0x25 0x65 0x05 0x07 0x19 0x00 0x29 0x65 0x81 0x00 0xc0
+#
+# Report: Input Report ID 1
+gatt.register-characteristic 0x2a4d read,notify
+0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+# Report Reference: Report ID 1, Input
+gatt.register-descriptor 0x2908 read
+0x01 0x01
+#
+# Protocol Mode: Report Protocol Mode
+gatt.register-characteristic 0x2a4e read,write-without-response
+0x01
+#
+# HID Control Point
+gatt.register-characteristic 0x2a4c write-without-response
+0x00
+#
+# HID SCI Mode: None (0x00), notified when changed
+gatt.register-characteristic 0x2c39 read,write,notify
+0x00
+#
+# HID SCI Information (intervals in units of 0.125 ms):
+# Minimum Supported Connection Interval: 0x08 (1 ms)
+# Number of Supported Subgroups: 1
+# Subgroup[0]: Min 0x0008 (1 ms) Max 0x0050 (10 ms) Stride 0x0008 (1 ms)
+gatt.register-characteristic 0x2c3a read
+0x08 0x01 0x08 0x00 0x50 0x00 0x08 0x00
+#
+gatt.register-application
+#
+# Advertise as a keyboard with HIDS
+advertise.uuids 0x1812
+advertise.appearance 0x03c1
+power on
diff --git a/client/scripts/hog-device.bt b/client/scripts/hog-device.bt
new file mode 100644
index 000000000000..42b324eda020
--- /dev/null
+++ b/client/scripts/hog-device.bt
@@ -0,0 +1,38 @@
+#
+# Register a HID Service (HIDS) acting as a HID over GATT (HoG) keyboard
+# without Shorter Connection Interval (SCI) support.
+#
+gatt.register-service 0x1812
+# Primary
+yes
+#
+# HID Information: bcdHID 1.11, bCountryCode 0x00,
+# Flags: NormallyConnectable
+gatt.register-characteristic 0x2a4a read
+0x11 0x01 0x00 0x02
+#
+# Report Map: keyboard with Report ID 1
+gatt.register-characteristic 0x2a4b read
+0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x08 0x81 0x02 0x95 0x01 0x75 0x08 0x81 0x01 0x95 0x06 0x75 0x08 0x15 0x00 0x25 0x65 0x05 0x07 0x19 0x00 0x29 0x65 0x81 0x00 0xc0
+#
+# Report: Input Report ID 1
+gatt.register-characteristic 0x2a4d read,notify
+0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+# Report Reference: Report ID 1, Input
+gatt.register-descriptor 0x2908 read
+0x01 0x01
+#
+# Protocol Mode: Report Protocol Mode
+gatt.register-characteristic 0x2a4e read,write-without-response
+0x01
+#
+# HID Control Point
+gatt.register-characteristic 0x2a4c write-without-response
+0x00
+#
+gatt.register-application
+#
+# Advertise as a keyboard with HIDS
+advertise.uuids 0x1812
+advertise.appearance 0x03c1
+power on
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 06/21] doc: Add functional-hog documentation
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (4 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 05/21] client/scripts: Add HoG device scripts Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 07/21] test: functional: add HoG tests Luiz Augusto von Dentz
                   ` (14 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Document the HoG functional tests, where bluetoothctl registers a HID
Service with and without Shorter Connection Interval (SCI) support
using client/scripts/hog-device*.bt, and the HID host:

- checks HID Information, HID SCI Mode and HID SCI Information with
  gatt.select-attribute/gatt.read
- receives a few Input Reports notified by the HID device
- changes the SCI mode, followed by the connection rate with
  mgmt.conn-subrate, and receives the notification from the HID device
  confirming the mode has been changed

Assisted-by: OpenCode:claude-opus-5.5
---
 Makefile.am                |   1 +
 doc/functional-hog.rst     | 188 +++++++++++++++++++++++++++++++++++++
 doc/functional-testing.rst |   1 +
 3 files changed, 190 insertions(+)
 create mode 100644 doc/functional-hog.rst

diff --git a/Makefile.am b/Makefile.am
index 1d46b9b938cf..17348788a30b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -501,6 +501,7 @@ EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
 				doc/functional-a2dp.rst \
 				doc/functional-avrcp.rst \
 				doc/functional-bap.rst \
+				doc/functional-hog.rst \
 				doc/functional-mpris-proxy.rst \
 				doc/functional-obex.rst \
 				doc/settings-storage.txt
diff --git a/doc/functional-hog.rst b/doc/functional-hog.rst
new file mode 100644
index 000000000000..d748f241a378
--- /dev/null
+++ b/doc/functional-hog.rst
@@ -0,0 +1,188 @@
+==============
+functional-hog
+==============
+
+DESCRIPTION
+===========
+
+HID over GATT (HoG) functional tests, `test/functional/test_hog.py`,
+driven through **bluetoothctl(1)**. See **functional-testing(7)** for
+the conventions used here, and **test-functional(1)** for how to run
+the suite.
+
+SETUP
+=====
+
+Two hosts, connected over LE, both running **bluetoothd(8)** with
+``ControllerMode = le`` and ``ExportClaimedServices = read-write``, as
+the HID Service is claimed by the input plugin of the HID host and
+bluetoothctl has to write HID SCI Mode:
+
+.. code-block::
+
+	+------------------------+                 +------------------------+
+	| host0                  |       LE        | host1                  |
+	| central                | --------------> | peripheral             |
+	| bluetoothctl           |                 | bluetoothctl           |
+	| HID host               |   GATT (HIDS)   | hog-device[-sci].bt    |
+	|                        | <============== | HID Service            |
+	+------------------------+                 +------------------------+
+
+	--> connection is initiated by      ==> reports flow towards
+
+host1 starts `bluetoothctl` with a script registering a HID Service
+(HIDS, ``00001812-0000-1000-8000-00805f9b34fb``) acting as a keyboard,
+through the ``gatt.register-service``, ``gatt.register-characteristic``
+and ``gatt.register-descriptor`` commands:
+
+``client/scripts/hog-device.bt``
+	HIDS without Shorter Connection Interval (SCI) support:
+
+	- HID Information (0x2A4A): ``11 01 00 02``, i.e. bcdHID 1.11,
+	  bCountryCode 0x00 and Flags NormallyConnectable.
+	- Report Map (0x2A4B): keyboard with Report ID 1.
+	- Report (0x2A4D), with a Report Reference descriptor (0x2908)
+	  ``01 01``, i.e. Report ID 1, Input Report.
+	- Protocol Mode (0x2A4E): ``01``, i.e. Report Protocol Mode.
+	- HID Control Point (0x2A4C).
+
+``client/scripts/hog-device-sci.bt``
+	Same as above, with SCI support:
+
+	- HID Information (0x2A4A): ``11 01 00 06``, i.e. the SCI
+	  Supported flag (0x04) is set as well.
+	- HID SCI Mode (0x2C39): ``00``, i.e. None, with the notify
+	  property so the HID device can confirm a mode change.
+	- HID SCI Information (0x2C3A): ``08 01 08 00 50 00 08 00``, i.e.
+	  Minimum Supported Connection Interval 1 ms, and one subgroup
+	  with Min 1 ms, Max 10 ms and Stride 1 ms (in units of 0.125 ms).
+
+Both scripts set the advertising data to the HIDS UUID and the
+keyboard appearance (0x03C1). The same scripts can be used manually to
+emulate a HoG device:
+
+.. code-block::
+
+	$ bluetoothctl --init-script client/scripts/hog-device-sci.bt
+	[bluetoothctl]> advertise on
+
+host0 runs a plain `bluetoothctl`, and both use
+``-a auto:NoInputNoOutput`` so pairing is Just Works.
+
+TEST CASES
+==========
+
+test_hog[no-sci]
+----------------
+
+:Setup: As above, with ``client/scripts/hog-device.bt`` on host1.
+
+:Steps:
+	1. host1: start `bluetoothctl` with the script.
+	2. host0: ``scan on``; host1: ``advertise on``.
+	3. host0: ``pair <host1 bdaddr>``.
+	4. host0: ``info <host1 bdaddr>``.
+	5. host0: ``gatt.select-attribute 2a4a`` and ``gatt.read``.
+	6. host0: ``gatt.select-attribute 2a4d`` and ``gatt.notify on``.
+	7. host1: ``gatt.select-attribute local
+	   /org/bluez/app/service0/chrc2``, then for each report
+	   ``gatt.write "<report>"``: ``00 00 04 00 00 00 00 00`` (a
+	   pressed), ``02 00 05 00 00 00 00 00`` (Left Shift + b pressed)
+	   and ``00 00 00 00 00 00 00 00`` (released).
+
+:Expected:
+	1. ``Application registered`` on host1.
+	2. ``Advertising object registered`` on host1 and the device found
+	   on host0.
+	3. ``Pairing successful`` and ``ServicesResolved: yes``.
+	4. ``Human Interface Device (00001812-...)`` is listed in the UUIDs.
+	5. HID Information reads ``11 01 00 02``:
+
+	   .. code-block::
+
+		[bluetoothctl]> gatt.select-attribute 2a4a
+		[bluetoothctl]> gatt.read
+		Attempting to read /org/bluez/hci0/dev_XX/service0013/char001e
+		  11 01 00 02                                      ....
+
+	6. ``Notify started``, and the Report subscribed on host1
+	   (``Notify sock acquired``, as the input plugin already
+	   subscribed with AcquireNotify).
+	7. Each report is notified to host0, in order:
+
+	   .. code-block::
+
+		[CHG] Attribute /org/bluez/hci0/dev_XX/service0013/char0018 Value:
+		  00 00 04 00 00 00 00 00                          ........
+
+:Notes: The service is checked at the GATT level only, so the test
+	does not depend on the kernel supporting uhid. The HID Service is
+	claimed by the input plugin on host0, but it is still exported
+	read-only over D-Bus by default (see ``ExportClaimedServices`` in
+	**bluetoothd(8)**), so it can be read with bluetoothctl.
+
+test_hog[sci]
+-------------
+
+:Setup: As above, with ``client/scripts/hog-device-sci.bt`` on host1.
+
+:Steps: As for test_hog[no-sci], then:
+
+	8. host0: ``gatt.select-attribute 2c39`` and ``gatt.read``.
+	9. host0: ``gatt.select-attribute 2c3a`` and ``gatt.read``.
+	10. host0: ``gatt.select-attribute 2c39`` and ``gatt.notify on``.
+	11. host0: ``gatt.write "0x03"``, i.e. SCI Fast Mode.
+	12. host0: ``mgmt.conn-subrate <host1 bdaddr> 0x0008 0x0010 1 1 0 0
+	    0x01f4``, i.e. interval 1 ms to 2 ms, within the range given in
+	    HID SCI Information, no subrating, no latency and 5 s
+	    supervision timeout.
+	13. host1: ``gatt.select-attribute local
+	    /org/bluez/app/service0/chrc5`` and ``gatt.write "0x03"``.
+
+:Expected: As for test_hog[no-sci], except HID Information reads
+	``11 01 00 06``, then:
+
+	8. HID SCI Mode reads ``00``.
+	9. HID SCI Information reads ``08 01 08 00 50 00 08 00``.
+	10. ``Notify started`` and HID SCI Mode subscribed on host1.
+	11. host1 receives the write:
+
+	    .. code-block::
+
+		[/org/bluez/app/service0/chrc5 (HID SCI Mode)] WriteValue: XX offset 0 link LE
+		  03                                               .
+
+	12. ``Connection Subrate loaded successfully``, then the MGMT
+	    Connection Subrate event with the new interval on both hosts:
+
+	    .. code-block::
+
+		hci0 XX type LE Public connection subrate interval 0x0008 subrate 0x0001 latency 0x0000 cont_num 0x0000 timeout 0x01f4
+
+	13. The new mode is notified to host0, confirming it has been
+	    changed:
+
+	    .. code-block::
+
+		[CHG] Attribute /org/bluez/hci0/dev_XX/service0015/char0018 Value:
+		  03                                               .
+
+	.. code-block::
+
+		[bluetoothctl]> gatt.select-attribute 2c39
+		[bluetoothctl]> gatt.read
+		  00                                               .
+
+		[bluetoothctl]> gatt.select-attribute 2c3a
+		[bluetoothctl]> gatt.read
+		  08 01 08 00 50 00 08 00                          ....P...
+
+:Notes: As the SCI Supported flag is set, the input plugin on host0
+	reads HID SCI Mode and HID SCI Information as well, which can be
+	seen in the **bluetoothd(8)** debug output (``SCI Mode:`` and
+	``SCI Info:``).
+
+	The kernel only issues the LE Connection Rate Request as central,
+	so the connection rate is changed by the HID host. This requires
+	the controllers to support Shorter Connection Intervals, which
+	btvirt emulates as a BR/EDR/LE 6.2 controller.
diff --git a/doc/functional-testing.rst b/doc/functional-testing.rst
index ca7bfa672a76..7b3cad1c66b1 100644
--- a/doc/functional-testing.rst
+++ b/doc/functional-testing.rst
@@ -15,6 +15,7 @@ are documented separately:
 - **functional-a2dp(7)**: `test/functional/test_a2dp.py`
 - **functional-avrcp(7)**: `test/functional/test_avrcp.py`
 - **functional-bap(7)**: `test/functional/test_bap.py`
+- **functional-hog(7)**: `test/functional/test_hog.py`
 - **functional-mpris-proxy(7)**: `test/functional/test_mpris_proxy.py`
 - **functional-obex(7)**: `test/functional/test_obex.py`
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 07/21] test: functional: add HoG tests
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (5 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 06/21] doc: Add functional-hog documentation Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 08/21] test: functional: limit the workers by the memory available Luiz Augusto von Dentz
                   ` (13 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Add tests where bluetoothctl registers a HID Service, with and without
SCI support, using client/scripts/hog-device*.bt, and the HID host
checks the service, receives Input Reports and, with SCI support,
changes the SCI mode and the connection rate.

See doc/functional-hog.rst for details.

Assisted-by: OpenCode:claude-opus-5.5
---
 test/functional/test_hog.py | 252 ++++++++++++++++++++++++++++++++++++
 1 file changed, 252 insertions(+)
 create mode 100644 test/functional/test_hog.py

diff --git a/test/functional/test_hog.py b/test/functional/test_hog.py
new file mode 100644
index 000000000000..ea54882d059d
--- /dev/null
+++ b/test/functional/test_hog.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for HID over GATT (HoG) using bluetoothctl in VM instances
+
+The HID device (host1) registers a HID Service (HIDS) with bluetoothctl,
+using client/scripts/hog-device.bt or client/scripts/hog-device-sci.bt,
+and the HID host (host0) pairs with it and checks the service over GATT.
+"""
+
+import warnings
+
+import pytest
+
+from pytest_bluezenv import Bluetoothd, Pexpect, find_exe, host_config
+from pytest_bluezenv.utils import bluez_src_dir
+
+pytestmark = [pytest.mark.vm]
+
+# The HID Service is claimed by the input plugin of the HID host, so it
+# has to be exported read-write for bluetoothctl to write HID SCI Mode
+HOG_CONF = """[General]
+ControllerMode = le
+
+[GATT]
+ExportClaimedServices = read-write
+"""
+
+HIDS_UUID = "00001812-0000-1000-8000-00805f9b34fb"
+
+# Local attributes registered by client/scripts/hog-device*.bt
+LOCAL_REPORT = "/org/bluez/app/service0/chrc2"
+LOCAL_SCI_MODE = "/org/bluez/app/service0/chrc5"
+
+# Keyboard Input Reports: Modifiers, Reserved, then 6 Key Codes
+REPORTS = [
+    "00 00 04 00 00 00 00 00",  # a pressed
+    "02 00 05 00 00 00 00 00",  # Left Shift + b pressed
+    "00 00 00 00 00 00 00 00",  # released
+]
+
+# HID SCI Mode: Fast Mode
+SCI_FAST_MODE = "03"
+
+# LE Connection Rate parameters requested with mgmt.conn-subrate once in
+# SCI Fast Mode: interval 1 ms to 2 ms (units of 0.125 ms), within the
+# range given in HID SCI Information, no subrating, no latency and 5 s
+# supervision timeout (units of 10 ms)
+SCI_RATE = ["0x0008", "0x0010", "1", "1", "0", "0", "0x01f4"]
+
+# Reported when an operation cannot complete, so a test does not have to
+# wait for its timeout to know it is not going to
+FAILURES = [
+    r"(Failed to \w+[^\r\n]*)",
+    r"(Device \S+ not available)",
+    r"(No device connected)",
+    r"(No attribute selected)",
+]
+
+# What a command reports is printed as it runs, unlike what the peers
+# report over the air, so waiting the default timeout for it only makes
+# a failure slower
+REPLY_TIMEOUT = 5
+
+
+def script(name):
+    src = bluez_src_dir()
+    if src is None:
+        pytest.skip("BlueZ source directory not known")
+
+    path = src / "client" / "scripts" / name
+    if not path.exists():
+        pytest.skip(f"{path} not found")
+
+    return str(path)
+
+
+def spawn_bluetoothctl(host, init_script=None):
+    exe = find_exe("client", "bluetoothctl")
+    # Accept pairing and authorize services without prompting, with a
+    # capability pairing Just Works, as there is no one to answer the
+    # entry of a passkey
+    args = [exe, "-a", "auto:NoInputNoOutput"]
+    if init_script:
+        args += ["--init-script", script(init_script)]
+    return host.pexpect.spawn(args)
+
+
+def expect(ctl, patterns, **kwargs):
+    """
+    Expect one of the patterns, failing as soon as one of the failures
+    shows up. Return the index of the pattern matched and its groups.
+    """
+    if isinstance(patterns, str):
+        patterns = [patterns]
+
+    idx, m = ctl.expect(FAILURES + list(patterns), **kwargs)
+    if idx < len(FAILURES):
+        raise AssertionError(m[0].decode("utf-8") if m else "failed")
+
+    return idx - len(FAILURES), m
+
+
+def expect_all(ctl, patterns, **kwargs):
+    """Expect all the given patterns, in any order."""
+    pending = list(patterns)
+
+    while pending:
+        idx, _ = expect(ctl, pending, **kwargs)
+        pending.pop(idx)
+
+
+def pair_le(host0, ctl0, host1, ctl1):
+    ctl0.send("scan on\n")
+    expect(ctl0, f"Controller {host0.bdaddr.upper()} Discovering: yes")
+
+    ctl1.send("advertise on\n")
+    expect(ctl1, "Advertising object registered")
+
+    expect(ctl0, f"Device {host1.bdaddr.upper()}")
+    ctl0.send(f"pair {host1.bdaddr.upper()}\n")
+
+    # See test_bluetoothctl_pair_le: passkey confirmation is handled by
+    # the auto agent, but legacy passkey entry still needs an answer
+    legacy = r"\[agent\].*Passkey:.*m(\d+)"
+    pending = [
+        r"Pairing successful",
+        f"Device {host1.bdaddr.upper()} ServicesResolved: yes",
+    ]
+
+    while pending:
+        idx, m = expect(ctl0, [legacy] + pending)
+        if idx == 0:
+            warnings.warn(
+                "BUG: we got passkey authentication, bluetoothd/kernel "
+                "should be fixed"
+            )
+            ctl1.expect(r"\[agent\] Enter passkey \(number in 0-999999\):")
+            ctl1.send(f"{m[0].decode('utf-8')}\n")
+            continue
+        pending.pop(idx - 1)
+
+    ctl0.send("scan off\n")
+
+
+def read_attribute(ctl, uuid):
+    """Read the given remote attribute, returning its value as hex string."""
+    ctl.send(f"gatt.select-attribute {uuid}\n")
+    ctl.send("gatt.read\n")
+    expect(ctl, r"Attempting to read \S+", timeout=REPLY_TIMEOUT)
+    return expect_hexdump(ctl)
+
+
+def hexbytes(value):
+    """Turn a hex string into the format taken by gatt.write."""
+    return " ".join(f"0x{byte}" for byte in value.split())
+
+
+def expect_hexdump(ctl, **kwargs):
+    """Expect a value printed by bluetoothctl, returning it as hex string."""
+    _, m = expect(ctl, r"((?: [0-9a-f]{2})+)  ", **kwargs)
+    return m[0].decode("utf-8").strip()
+
+
+def expect_notification(ctl):
+    """Expect a notification of the remote attribute, returning its value."""
+    expect(ctl, rf"CHG.*? Attribute /\S+ Value:")
+    return expect_hexdump(ctl)
+
+
+def enable_notifications(ctl, device, uuid, local):
+    """Enable notifications of the given attribute, on the HID host."""
+    ctl.send(f"gatt.select-attribute {uuid}\n")
+    ctl.send("gatt.notify on\n")
+    expect(ctl, r"Notify started", timeout=REPLY_TIMEOUT)
+    # Either subscribed with StartNotify, or with AcquireNotify as done by
+    # the input plugin of the HID host for the Input Reports
+    expect(
+        device,
+        rf"Attribute {local} (\S+ )?(notifications enabled|Notify sock acquired)",
+    )
+
+
+def notify(device, local, value):
+    """Notify the given value of a local attribute, on the HID device."""
+    device.send(f"gatt.select-attribute local {local}\n")
+    device.send(f'gatt.write "{hexbytes(value)}"\n')
+    expect(device, rf"Attribute {local} .*written", timeout=REPLY_TIMEOUT)
+
+
+@host_config(
+    [Bluetoothd(conf=HOG_CONF), Pexpect()],
+    [Bluetoothd(conf=HOG_CONF), Pexpect()],
+)
+@pytest.mark.parametrize(
+    "init_script, flags, sci",
+    [
+        ("hog-device.bt", "02", None),
+        ("hog-device-sci.bt", "06", ("00", "08 01 08 00 50 00 08 00")),
+    ],
+    ids=["no-sci", "sci"],
+)
+def test_hog(hosts, init_script, flags, sci):
+    host0, host1 = hosts
+
+    device = spawn_bluetoothctl(host1, init_script)
+    expect(device, "Application registered")
+
+    ctl = spawn_bluetoothctl(host0)
+    pair_le(host0, ctl, host1, device)
+
+    ctl.send(f"info {host1.bdaddr.upper()}\n")
+    expect(ctl, rf"Human Interface Device\s+\({HIDS_UUID}\)", timeout=REPLY_TIMEOUT)
+
+    # HID Information: bcdHID 1.11, bCountryCode 0x00 and Flags
+    assert read_attribute(ctl, "2a4a") == f"11 01 00 {flags}"
+
+    # Input Reports: the HID device notifies a few key presses
+    enable_notifications(ctl, device, "2a4d", LOCAL_REPORT)
+
+    for report in REPORTS:
+        notify(device, LOCAL_REPORT, report)
+        assert expect_notification(ctl) == report
+
+    if sci is None:
+        return
+
+    mode, info = sci
+    assert read_attribute(ctl, "2c39") == mode
+    assert read_attribute(ctl, "2c3a") == info
+
+    # SCI mode change: the HID host writes the new mode to the HID device
+    enable_notifications(ctl, device, "2c39", LOCAL_SCI_MODE)
+
+    ctl.send(f'gatt.write "{hexbytes(SCI_FAST_MODE)}"\n')
+    expect(device, rf"\[{LOCAL_SCI_MODE} .*\] WriteValue:")
+    assert expect_hexdump(device) == SCI_FAST_MODE
+
+    # The HID host, as central, changes the connection rate accordingly
+    ctl.send(f"mgmt.conn-subrate {host1.bdaddr} {' '.join(SCI_RATE)}\n")
+    # The connection rate may change before the command completes, so the
+    # event may be printed before the reply
+    rate = rf"{{}} type .* connection subrate interval {SCI_RATE[0]}"
+    expect_all(
+        ctl,
+        [r"Connection Subrate loaded successfully", rate.format(host1.bdaddr.upper())],
+    )
+    expect(device, rate.format(host0.bdaddr.upper()))
+
+    # Then the HID device confirms the mode has been changed
+    notify(device, LOCAL_SCI_MODE, SCI_FAST_MODE)
+    assert expect_notification(ctl) == SCI_FAST_MODE
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 08/21] test: functional: limit the workers by the memory available
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (6 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 07/21] test: functional: add HoG tests Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 09/21] client/agent: Fix crash on Cancel with no pending request Luiz Augusto von Dentz
                   ` (12 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Each worker of pytest-xdist runs VM instances, so using one worker per
CPU could run out of memory, with the OOM killer terminating some of
them and tests failing at random.

With -n auto, limit the number of workers by the memory available,
estimating each worker needs memory for 3 VM instances of 256M of guest
memory plus the overhead of qemu, and use -n auto for check-functional
by default, which can be overridden with CHECK_FUNCTIONAL_JOBS.

Assisted-by: OpenCode:claude-opus-5.5
---
 Makefile.am                 |  7 ++++-
 doc/test-functional.rst     | 25 ++++++++++++++++
 test/functional/conftest.py | 58 +++++++++++++++++++++++++++++++++++++
 3 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index 17348788a30b..e5587c55d2dd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -885,9 +885,14 @@ check-TESTS recheck: AM_MAKEFLAGS += -j$(CHECK_JOBS)
 
 # The functional tests are parallelized by pytest-xdist, loadgroup is required
 # since pytest-bluezenv groups the tests sharing a host/VM setup together.
+# Each worker runs VM instances, so by default the number of workers is limited
+# by the memory available (see test/functional/conftest.py), override with e.g.
+# CHECK_FUNCTIONAL_JOBS=4.
+CHECK_FUNCTIONAL_JOBS ?= auto
+
 check-functional: all
 	python3 -m pytest "$(srcdir)/test/functional" -v \
-		-n $(CHECK_JOBS) --dist loadgroup \
+		-n $(CHECK_FUNCTIONAL_JOBS) --dist loadgroup \
 		-m "not tester" \
 		--kernel="$(FUNCTIONAL_TESTING_KERNEL)" \
 		--bluez-build-dir="$(top_builddir)" \
diff --git a/doc/test-functional.rst b/doc/test-functional.rst
index 826210b73ce7..e2fcf34138b3 100644
--- a/doc/test-functional.rst
+++ b/doc/test-functional.rst
@@ -474,6 +474,31 @@ pytest-xdist is required for parallel execution. To run:
 
 	$ test/test-functional -n auto --dist loadgroup
 
+With ``-n auto`` the number of workers is limited by the memory
+available, rather than using one worker per CPU, as each worker runs
+VM instances and running out of memory makes the OOM killer terminate
+some of them, failing tests at random. Each worker is estimated to need
+memory for 3 VM instances (the maximum used by a test) of 256M of guest
+memory plus the overhead of qemu, see `test/functional/conftest.py`.
+The estimate is printed when starting:
+
+.. code-block::
+
+	Using 9 workers: 22 CPUs, 12159 MiB available, 1218 MiB per worker (3 VMs of 406 MiB)
+
+To use a given number of workers instead:
+
+.. code-block::
+
+	$ test/test-functional -n 4 --dist loadgroup
+
+``make check-functional`` uses ``-n auto`` as well, which can be
+overridden with ``CHECK_FUNCTIONAL_JOBS``:
+
+.. code-block::
+
+	$ make check-functional CHECK_FUNCTIONAL_JOBS=4
+
 Logging in to a test VM instance
 --------------------------------
 
diff --git a/test/functional/conftest.py b/test/functional/conftest.py
index 4e0bda882de3..5ee3e15e43ea 100644
--- a/test/functional/conftest.py
+++ b/test/functional/conftest.py
@@ -208,6 +208,64 @@ def _setup_progress(config):
     )
 
 
+# Estimate of the memory used by a VM instance: 256M of guest memory, the
+# default of test-runner as the tests do not set it, plus the overhead of
+# qemu itself
+VM_MEM = (256 + 150) * 1024 * 1024
+
+# Maximum number of VM instances used by a test, i.e. by an xdist worker
+# as it runs one test at a time
+VM_MAX_HOSTS = 3
+
+
+def _mem_available():
+    try:
+        with open("/proc/meminfo") as f:
+            for line in f:
+                if line.startswith("MemAvailable:"):
+                    return int(line.split()[1]) * 1024
+    except (OSError, ValueError, IndexError):
+        pass
+
+    return None
+
+
+@pytest.hookimpl(optionalhook=True)
+def pytest_xdist_auto_num_workers(config):
+    """
+    Number of workers used with -n auto: limited by the memory available,
+    so running a VM instance per worker does not end up with the OOM
+    killer terminating some of them, instead of one worker per CPU.
+    """
+    # Honour the override of pytest-xdist, as its own hook is not used
+    env = os.environ.get("PYTEST_XDIST_AUTO_NUM_WORKERS")
+    if env:
+        try:
+            return max(1, int(env))
+        except ValueError:
+            pass
+
+    try:
+        cpus = len(os.sched_getaffinity(0))
+    except (AttributeError, OSError):
+        cpus = os.cpu_count() or 1
+
+    mem = _mem_available()
+    if mem is None:
+        return cpus
+
+    per_worker = VM_MAX_HOSTS * VM_MEM
+    workers = max(1, min(cpus, mem // per_worker))
+
+    sys.stderr.write(
+        f"Using {workers} workers: {cpus} CPUs, {mem >> 20} MiB available,"
+        f" {per_worker >> 20} MiB per worker ({VM_MAX_HOSTS} VMs of"
+        f" {VM_MEM >> 20} MiB)\n"
+    )
+
+    return workers
+
+
 def pytest_configure(config):
     _setup_progress(config)
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 09/21] client/agent: Fix crash on Cancel with no pending request
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (7 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 08/21] test: functional: limit the workers by the memory available Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 10/21] shared/uhid: Fix size of Get Report reply with a Report ID Luiz Augusto von Dentz
                   ` (11 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

The auto agent replies to the requests right away, so there is no
pending request when bluetoothd cancels one, e.g. when pairing fails,
and dbus_message_unref is then called with NULL, which libdbus aborts
on:

 #5  ?? () from /usr/lib/x86_64-linux-gnu/libdbus-1.so.3
 #6  _dbus_warn_check_failed () from /usr/lib/x86_64-linux-gnu/libdbus-1.so.3
 #7  cancel_request (...) at client/agent.c:258
 #8  process_message (...) at gdbus/object.c:293

Only release the pending request if there is one, as done when the
agent is released.

Assisted-by: OpenCode:claude-opus-5.5
---
 client/agent.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/client/agent.c b/client/agent.c
index a678d5f785a9..2dd0113dc624 100644
--- a/client/agent.c
+++ b/client/agent.c
@@ -255,8 +255,14 @@ static DBusMessage *cancel_request(DBusConnection *conn,
 	bt_shell_printf("Request canceled\n");
 
 	agent_release_prompt();
-	dbus_message_unref(pending_message);
-	pending_message = NULL;
+
+	/* There is no pending request with the auto agent, which replies
+	 * right away, or if it has already been replied.
+	 */
+	if (pending_message) {
+		dbus_message_unref(pending_message);
+		pending_message = NULL;
+	}
 
 	return dbus_message_new_method_return(msg);
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 10/21] shared/uhid: Fix size of Get Report reply with a Report ID
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (8 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 09/21] client/agent: Fix crash on Cancel with no pending request Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 11/21] shared/uhid: Keep reading when an event is not available Luiz Augusto von Dentz
                   ` (10 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

The size of the reply did not account for the Report ID prepended to
the data of numbered reports, dropping the last byte of the report,
unlike Input Reports which already account for it.

Assisted-by: OpenCode:claude-opus-5.5
---
 src/shared/uhid.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/shared/uhid.c b/src/shared/uhid.c
index 919618a71b00..36a16c331e83 100644
--- a/src/shared/uhid.c
+++ b/src/shared/uhid.c
@@ -530,7 +530,7 @@ int bt_uhid_get_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t number,
 
 	if (number) {
 		rsp->data[len++] = number;
-		rsp->size += MIN(size, sizeof(rsp->data) - 1);
+		rsp->size = 1 + MIN(size, sizeof(rsp->data) - 1);
 	} else
 		rsp->size = MIN(size, sizeof(ev.u.input.data));
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 11/21] shared/uhid: Keep reading when an event is not available
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (9 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 10/21] shared/uhid: Fix size of Get Report reply with a Report ID Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 12/21] shared/tester: Allow expecting a PDU with no response Luiz Augusto von Dentz
                   ` (9 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

When the fd is non-blocking an event may have been consumed by another
reader of the same fd, e.g. multiple instances of HID over GATT sharing
the fd of the uHID device in unit tests, so keep reading on EAGAIN
instead of stopping.

Assisted-by: OpenCode:claude-opus-5.5
---
 src/shared/uhid.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/shared/uhid.c b/src/shared/uhid.c
index 36a16c331e83..20c55f63b82a 100644
--- a/src/shared/uhid.c
+++ b/src/shared/uhid.c
@@ -169,7 +169,10 @@ static bool uhid_read_handler(struct io *io, void *user_data)
 
 	len = read(fd, &ev, sizeof(ev));
 	if (len < 0)
-		return false;
+		/* Keep reading if the event was consumed by another reader of
+		 * a non-blocking fd.
+		 */
+		return errno == EAGAIN || errno == EINTR;
 
 	if ((size_t) len < sizeof(ev.type))
 		return false;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 12/21] shared/tester: Allow expecting a PDU with no response
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (10 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 11/21] shared/uhid: Keep reading when an event is not available Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 13/21] shared/hog: Add initial implementation Luiz Augusto von Dentz
                   ` (8 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

IOV_NULL following an expected PDU used to send an empty PDU, unless
followed by another IOV_NULL chaining the PDUs to send, so it was not
possible to expect a PDU with no response, e.g. a Write Command,
followed by another expected PDU.

Wait for the next PDU instead of sending an empty one in that case.

Assisted-by: OpenCode:claude-opus-5.5
---
 src/shared/tester.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/src/shared/tester.c b/src/shared/tester.c
index e99cc452cd07..eb2e923fd790 100644
--- a/src/shared/tester.c
+++ b/src/shared/tester.c
@@ -948,6 +948,25 @@ static bool test_io_send(struct io *io, void *user_data)
 	if (!iov)
 		return false;
 
+	/* IOV_NULL following an expected PDU means there is no response to
+	 * it, e.g. a Write Command: wait for the next PDU instead, unless
+	 * followed by another IOV_NULL chaining the PDUs to send.
+	 */
+	if (!iov->iov_base) {
+		/* Nothing left to send or to expect */
+		if (!test->iovcnt) {
+			if (test->io_complete_func)
+				test->io_complete_func(test->test_data);
+			return false;
+		}
+
+		if (test->iov->iov_base)
+			return false;
+
+		test_get_iov(test);
+		return test_io_send(io, user_data);
+	}
+
 	len = io_send(io, iov, 1);
 
 	tester_monitor('<', 0x0004, 0x0000, iov->iov_base, len);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 13/21] shared/hog: Add initial implementation
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (11 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 12/21] shared/tester: Allow expecting a PDU with no response Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 14/21] unit/test-hog: Use shared/hog Luiz Augusto von Dentz
                   ` (7 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Add a HID over GATT implementation based on bt_gatt_client and
gatt_db, meant as a drop-in replacement of profiles/input/hog-lib
which is based on GAttrib:

- bt_hog_attach takes a bt_gatt_client
- the services and characteristics are only looked up in the gatt_db,
  the one given to bt_hog_new or otherwise the one of the client
- notifications of the Input Reports are enabled with
  bt_gatt_client_register_notify, once their CCC has been read
- the Report Map is read with a long read once every other read is
  complete, so the report IDs are known before creating the uHID device
- the addresses of the uHID device are taken from the ATT socket
- pending requests are cancelled when detaching, and notifications are
  unregistered so they are disabled if still connected

Unlike hog-lib, the Scan Parameters, Device Information and Battery
services are not handled, as they are by the scanparam, deviceinfo and
battery plugins, so bt_hog_set_ids is added to set the vendor, product
and version once known, e.g. from the PnP ID. A debug callback can be
set with bt_hog_set_debug.

HID SCI is supported as specified by the HIDS/HOGP SCI FIPD:
bt_hog_set_sci_mode requests the HID Device to enable a HID SCI mode by
writing it to the HID Control Point, and the notifications of HID SCI
Mode, with which the HID Device confirms the mode once the connection
rate has been changed, are enabled and reported with the callback set
with bt_hog_set_sci_mode_callback.

Assisted-by: OpenCode:claude-opus-5.5
---
 Makefile.am      |    1 +
 src/shared/hog.c | 1659 ++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/hog.h |   67 ++
 3 files changed, 1727 insertions(+)
 create mode 100644 src/shared/hog.c
 create mode 100644 src/shared/hog.h

diff --git a/Makefile.am b/Makefile.am
index e5587c55d2dd..5419b31e21d5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -239,6 +239,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
 			src/shared/mcs.h src/shared/mcp.h src/shared/mcp.c \
 			src/shared/vcp.c src/shared/vcp.h \
 			src/shared/micp.c src/shared/micp.h \
+			src/shared/hog.c src/shared/hog.h \
 			src/shared/csip.c src/shared/csip.h \
 			src/shared/bass.h src/shared/bass.c \
 			src/shared/ccp.h src/shared/ccp.c \
diff --git a/src/shared/hog.c b/src/shared/hog.c
new file mode 100644
index 000000000000..f5dd631e805a
--- /dev/null
+++ b/src/shared/hog.c
@@ -0,0 +1,1659 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2014  Intel Corporation.
+ *  Copyright (C) 2012  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2012  Nordic Semiconductor Inc.
+ *  Copyright (C) 2012  Instituto Nokia de Tecnologia - INdT
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/l2cap.h"
+#include "bluetooth/uuid.h"
+
+#include "src/shared/util.h"
+#include "src/shared/uhid.h"
+#include "src/shared/queue.h"
+#include "src/shared/att.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/hog.h"
+
+#define DBG(_hog, fmt, arg...) \
+	hog_debug(_hog, "%s:%s() " fmt, __FILE__, __func__, ##arg)
+
+#define HOG_UUID16		0x1812
+
+#define HOG_INFO_UUID		0x2A4A
+#define HOG_REPORT_MAP_UUID	0x2A4B
+#define HOG_REPORT_UUID		0x2A4D
+#define HOG_PROTO_MODE_UUID	0x2A4E
+#define HOG_CP_UUID		0x2A4C
+#define HOG_SCI_MODE_UUID	0x2C39
+#define HOG_SCI_INFO_UUID	0x2C3A
+
+#define HOG_REPORT_TYPE_INPUT	1
+#define HOG_REPORT_TYPE_OUTPUT	2
+#define HOG_REPORT_TYPE_FEATURE	3
+
+#define HOG_PROTO_MODE_BOOT    0
+#define HOG_PROTO_MODE_REPORT  1
+
+#define HOG_INFO_FLAG_SCI_SUPPORTED	0x04
+#define HOG_INFO_FLAG_SCI_LOW_POWER	0x08
+
+#define HID_INFO_SIZE			4
+
+struct bt_hog {
+	int			ref_count;
+	char			*name;
+	uint16_t		vendor;
+	uint16_t		product;
+	uint16_t		version;
+	uint8_t			type;
+	struct gatt_db		*db;
+	struct gatt_db_attribute *attr;
+	struct bt_gatt_client	*client;
+	struct queue		*reports;
+	struct bt_uhid		*uhid;
+	int			uhid_fd;
+	uint64_t		uhid_flags;
+	uint16_t		bcdhid;
+	uint8_t			bcountrycode;
+	uint16_t		proto_mode_handle;
+	uint16_t		cp_handle;
+	uint8_t			flags;
+	unsigned int		getrep_att;
+	uint32_t		getrep_id;
+	unsigned int		setrep_att;
+	uint32_t		setrep_id;
+	unsigned int		report_map_id;
+	struct gatt_db_attribute *report_map_attr;
+	uint16_t		sci_mode_handle;
+	uint8_t			sci_mode_props;
+	unsigned int		sci_mode_id;
+	uint8_t			sci_mode;
+	uint16_t		sci_info_handle;
+	bt_hog_sci_mode_func_t	sci_mode_func;
+	void			*sci_mode_data;
+	/* Requests to cancel when detaching */
+	struct queue		*reqs;
+	/* Reads pending before reading the Report Map */
+	unsigned int		pending;
+	struct queue		*instances;
+	bt_hog_debug_func_t	debug_func;
+	bt_hog_destroy_func_t	debug_destroy;
+	void			*debug_data;
+};
+
+struct report {
+	struct bt_hog		*hog;
+	bool			numbered;
+	uint8_t			id;
+	uint8_t			type;
+	uint16_t		handle;
+	uint16_t		value_handle;
+	uint8_t			properties;
+	uint16_t		ccc_handle;
+	unsigned int		notify_id;
+	uint16_t		len;
+	uint8_t			*value;
+};
+
+/* Request tracked so it is cancelled when detaching, freed once complete or
+ * cancelled.
+ */
+struct hog_req {
+	struct bt_hog		*hog;
+	void			*data;
+	unsigned int		id;
+};
+
+static void hog_debug(struct bt_hog *hog, const char *format, ...)
+{
+	va_list ap;
+
+	if (!hog || !format || !hog->debug_func)
+		return;
+
+	va_start(ap, format);
+	util_debug_va(hog->debug_func, hog->debug_data, format, ap);
+	va_end(ap);
+}
+
+static void read_report_map(struct bt_hog *hog);
+
+static void req_free(void *data)
+{
+	struct hog_req *req = data;
+
+	if (req->hog)
+		queue_remove(req->hog->reqs, req);
+
+	free(req);
+}
+
+static struct hog_req *req_new(struct bt_hog *hog, void *data)
+{
+	struct hog_req *req;
+
+	req = new0(struct hog_req, 1);
+	req->hog = hog;
+	req->data = data;
+
+	return req;
+}
+
+static bool req_track(struct hog_req *req, unsigned int id)
+{
+	if (!id) {
+		free(req);
+		return false;
+	}
+
+	req->id = id;
+	queue_push_tail(req->hog->reqs, req);
+
+	return true;
+}
+
+static void req_cancel(void *data)
+{
+	struct hog_req *req = data;
+	struct bt_gatt_client *client = req->hog->client;
+
+	/* Already removed from the queue, req is freed by req_free */
+	req->hog = NULL;
+	bt_gatt_client_cancel(client, req->id);
+}
+
+static unsigned int read_char(struct bt_hog *hog, uint16_t handle,
+				bt_gatt_client_read_callback_t func,
+				void *data)
+{
+	struct hog_req *req = req_new(hog, data);
+
+	if (!req_track(req, bt_gatt_client_read_value(hog->client, handle,
+						func, req, req_free))) {
+		DBG(hog, "Could not read handle 0x%04x", handle);
+		return 0;
+	}
+
+	hog->pending++;
+
+	return req->id;
+}
+
+static void req_done(struct hog_req *req, bool success)
+{
+	struct bt_hog *hog = req->hog;
+
+	if (hog->pending)
+		hog->pending--;
+
+	/* Report Map must be read last since that can result in uhid being
+	 * created and the driver may start to use UHID_SET_REPORT which
+	 * requires the report->id to be known what attribute to send to.
+	 *
+	 * Read it even if a request failed, e.g. reading an optional
+	 * attribute, otherwise the uHID device would never be created.
+	 */
+	if (!hog->pending)
+		read_report_map(hog);
+}
+
+static const char *type_to_string(uint8_t type)
+{
+	switch (type) {
+	case HOG_REPORT_TYPE_INPUT:
+		return "input";
+	case HOG_REPORT_TYPE_OUTPUT:
+		return "output";
+	case HOG_REPORT_TYPE_FEATURE:
+		return "feature";
+	}
+
+	return NULL;
+}
+
+static void report_value_cb(uint16_t value_handle, const uint8_t *value,
+					uint16_t length, void *user_data)
+{
+	struct report *report = user_data;
+	struct bt_hog *hog = report->hog;
+	bool numbered = report->numbered;
+	int err;
+
+	/* Drop the reports until the uHID device is created, as they would be
+	 * queued with no limit until then, e.g. if the HID device never lets
+	 * the Report Map be read.
+	 */
+	if (!bt_uhid_created(hog->uhid))
+		return;
+
+	/* Until UHID_START tells which reports are numbered, rely on the
+	 * Report ID since it is 0 when the Report Map does not use them.
+	 */
+	if (!bt_uhid_started(hog->uhid))
+		numbered = report->id;
+
+	err = bt_uhid_input(hog->uhid, numbered ? report->id : 0, value,
+								length);
+	if (err < 0)
+		DBG(hog, "bt_uhid_input: %s (%d)", strerror(-err), -err);
+}
+
+static void report_notify_registered(uint16_t att_ecode, void *user_data)
+{
+	struct report *report = user_data;
+	struct bt_hog *hog = report->hog;
+
+	if (att_ecode) {
+		DBG(hog, "Unable to enable notifications: handle 0x%04x "
+					"error 0x%02x", report->value_handle,
+					att_ecode);
+		return;
+	}
+
+	DBG(hog, "Report 0x%04x: notifications enabled",
+						report->value_handle);
+}
+
+static void report_enable_notify(struct report *report)
+{
+	struct bt_hog *hog = report->hog;
+
+	if (report->notify_id || !hog->client ||
+			report->type != HOG_REPORT_TYPE_INPUT ||
+			!(report->properties & BT_GATT_CHRC_PROP_NOTIFY))
+		return;
+
+	report->notify_id = bt_gatt_client_register_notify(hog->client,
+					report->value_handle,
+					report_notify_registered,
+					report_value_cb, report, NULL);
+	if (!report->notify_id)
+		DBG(hog, "Unable to register report notification: "
+				"handle 0x%04x", report->value_handle);
+}
+
+static void ccc_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct report *report = req->data;
+
+	if (!success)
+		DBG(req->hog, "Error reading CCC value: 0x%02x", att_ecode);
+	else if (length == 2)
+		DBG(req->hog, "Report 0x%04x: CCC 0x%04x",
+				report->value_handle, get_le16(value));
+
+	/* Enable notifications regardless, as the HID Device may not
+	 * persist them.
+	 */
+	report_enable_notify(report);
+
+	req_done(req, success);
+}
+
+static void report_reference_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct report *report = req->data;
+	struct bt_hog *hog = req->hog;
+
+	if (!success) {
+		DBG(hog, "Read Report Reference descriptor failed: 0x%02x",
+								att_ecode);
+		goto done;
+	}
+
+	if (length != 2) {
+		DBG(hog, "Malformed Report Reference: length %u", length);
+		goto done;
+	}
+
+	report->id = value[0];
+	report->type = value[1];
+
+	DBG(hog, "Report 0x%04x: id 0x%02x type %s", report->value_handle,
+				report->id, type_to_string(report->type));
+
+	/* Enable notifications only for Input Reports, reading the CCC
+	 * first to know its current value.
+	 */
+	if (report->type == HOG_REPORT_TYPE_INPUT && report->ccc_handle)
+		read_char(hog, report->ccc_handle, ccc_read_cb, report);
+	else
+		report_enable_notify(report);
+
+done:
+	req_done(req, success);
+}
+
+static void report_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct report *report = req->data;
+
+	if (!success) {
+		DBG(req->hog, "Error reading Report value: 0x%02x", att_ecode);
+		goto done;
+	}
+
+	free(report->value);
+	report->value = util_memdup(value, length);
+	report->len = length;
+
+done:
+	req_done(req, success);
+}
+
+static void foreach_hog_report(struct gatt_db_attribute *attr, void *user_data)
+{
+	struct report *report = user_data;
+	struct bt_hog *hog = report->hog;
+	const bt_uuid_t *uuid;
+	bt_uuid_t ref_uuid, ccc_uuid;
+	uint16_t handle;
+
+	handle = gatt_db_attribute_get_handle(attr);
+	uuid = gatt_db_attribute_get_type(attr);
+
+	bt_uuid16_create(&ref_uuid, GATT_REPORT_REFERENCE);
+	if (!bt_uuid_cmp(&ref_uuid, uuid)) {
+		read_char(hog, handle, report_reference_cb, report);
+		return;
+	}
+
+	bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+	if (!bt_uuid_cmp(&ccc_uuid, uuid))
+		report->ccc_handle = handle;
+}
+
+static bool match_report_handle(const void *data, const void *match_data)
+{
+	const struct report *report = data;
+	uint16_t handle = PTR_TO_UINT(match_data);
+
+	return report->handle == handle;
+}
+
+static struct report *report_add(struct bt_hog *hog,
+					struct gatt_db_attribute *attr)
+{
+	struct report *report;
+	uint16_t handle = gatt_db_attribute_get_handle(attr);
+
+	/* Skip if report already exists, e.g. when reconnecting, only
+	 * enabling its notifications again.
+	 */
+	report = queue_find(hog->reports, match_report_handle,
+						UINT_TO_PTR(handle));
+	if (report) {
+		report_enable_notify(report);
+		return report;
+	}
+
+	report = new0(struct report, 1);
+	report->hog = hog;
+
+	gatt_db_attribute_get_char_data(attr, &report->handle,
+					&report->value_handle,
+					&report->properties,
+					NULL, NULL);
+
+	queue_push_tail(hog->reports, report);
+
+	read_char(hog, report->value_handle, report_read_cb, report);
+
+	gatt_db_service_foreach_desc(attr, foreach_hog_report, report);
+
+	return report;
+}
+
+static void foreach_external_report(struct gatt_db_attribute *attr,
+							void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	bt_uuid_t uuid, report_uuid;
+
+	gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, NULL, &uuid);
+
+	bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
+	if (!bt_uuid_cmp(&report_uuid, &uuid))
+		report_add(hog, attr);
+}
+
+static void foreach_external_service(struct gatt_db_attribute *attr,
+							void *user_data)
+{
+	bt_uuid_t uuid, hog_uuid;
+
+	gatt_db_attribute_get_service_uuid(attr, &uuid);
+
+	/* Reports of HID services belong to their own instance */
+	bt_uuid16_create(&hog_uuid, HOG_UUID16);
+	if (!bt_uuid_cmp(&hog_uuid, &uuid))
+		return;
+
+	gatt_db_service_foreach_char(attr, foreach_external_report, user_data);
+}
+
+static void external_report_reference_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct bt_hog *hog = req->hog;
+	uint16_t uuid16;
+
+	if (!success) {
+		DBG(hog, "Read External Report Reference descriptor failed: "
+							"0x%02x", att_ecode);
+		goto done;
+	}
+
+	if (length != 2) {
+		DBG(hog, "Malformed External Report Reference: length %u",
+								length);
+		goto done;
+	}
+
+	uuid16 = get_le16(value);
+	DBG(hog, "External report reference read, external report "
+				"characteristic UUID: 0x%04x", uuid16);
+
+	/* Do not add if is not a Report */
+	if (uuid16 != HOG_REPORT_UUID)
+		goto done;
+
+	gatt_db_foreach_service(hog->db, NULL, foreach_external_service, hog);
+
+done:
+	req_done(req, success);
+}
+
+static void foreach_hog_external(struct gatt_db_attribute *attr,
+							void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	const bt_uuid_t *uuid;
+	bt_uuid_t ext_uuid;
+
+	uuid = gatt_db_attribute_get_type(attr);
+
+	bt_uuid16_create(&ext_uuid, GATT_EXTERNAL_REPORT_REFERENCE);
+	if (!bt_uuid_cmp(&ext_uuid, uuid))
+		read_char(hog, gatt_db_attribute_get_handle(attr),
+					external_report_reference_cb, hog);
+}
+
+static int report_cmp(const struct report *ra, const struct report *rb)
+{
+	/* sort by type first.. */
+	if (ra->type != rb->type)
+		return ra->type - rb->type;
+
+	/* skip id check in case of reports not being numbered  */
+	if (!ra->numbered && !rb->numbered)
+		return 0;
+
+	/* ..then by id */
+	return ra->id - rb->id;
+}
+
+static bool match_report(const void *data, const void *match_data)
+{
+	return !report_cmp(data, match_data);
+}
+
+static struct report *find_report(struct bt_hog *hog, uint8_t type, uint8_t id)
+{
+	struct report cmp;
+
+	memset(&cmp, 0, sizeof(cmp));
+	cmp.type = type;
+	cmp.id = id;
+
+	switch (type) {
+	case HOG_REPORT_TYPE_FEATURE:
+		if (hog->uhid_flags & UHID_DEV_NUMBERED_FEATURE_REPORTS)
+			cmp.numbered = true;
+		break;
+	case HOG_REPORT_TYPE_OUTPUT:
+		if (hog->uhid_flags & UHID_DEV_NUMBERED_OUTPUT_REPORTS)
+			cmp.numbered = true;
+		break;
+	case HOG_REPORT_TYPE_INPUT:
+		if (hog->uhid_flags & UHID_DEV_NUMBERED_INPUT_REPORTS)
+			cmp.numbered = true;
+		break;
+	}
+
+	return queue_find(hog->reports, match_report, &cmp);
+}
+
+static struct report *find_report_by_rtype(struct bt_hog *hog, uint8_t rtype,
+								uint8_t id)
+{
+	uint8_t type;
+
+	switch (rtype) {
+	case UHID_FEATURE_REPORT:
+		type = HOG_REPORT_TYPE_FEATURE;
+		break;
+	case UHID_OUTPUT_REPORT:
+		type = HOG_REPORT_TYPE_OUTPUT;
+		break;
+	case UHID_INPUT_REPORT:
+		type = HOG_REPORT_TYPE_INPUT;
+		break;
+	default:
+		return NULL;
+	}
+
+	return find_report(hog, type, id);
+}
+
+static void output_written_cb(bool success, uint8_t att_ecode,
+							void *user_data)
+{
+	struct hog_req *req = user_data;
+
+	if (!success)
+		DBG(req->hog, "Write output report failed: 0x%02x", att_ecode);
+}
+
+static void write_output(struct bt_hog *hog, struct report *report,
+					const uint8_t *data, size_t size)
+{
+	struct hog_req *req = req_new(hog, report);
+
+	if (!req_track(req, bt_gatt_client_write_value(hog->client,
+					report->value_handle, data, size,
+					output_written_cb, req, req_free)))
+		DBG(hog, "Could not write report 0x%04x",
+						report->value_handle);
+}
+
+static void write_report(struct bt_hog *hog, struct report *report,
+					const uint8_t *data, size_t size)
+{
+	if (report->properties & BT_GATT_CHRC_PROP_WRITE)
+		write_output(hog, report, data, size);
+	else if (report->properties & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)
+		bt_gatt_client_write_without_response(hog->client,
+						report->value_handle, false,
+						data, size);
+}
+
+static void forward_report(struct uhid_event *ev, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	struct report *report;
+	uint8_t *data;
+	int size;
+
+	report = find_report_by_rtype(hog, ev->u.output.rtype,
+							ev->u.output.data[0]);
+	if (!report)
+		return;
+
+	data = ev->u.output.data;
+	size = ev->u.output.size;
+
+	if (report->numbered && size > 0) {
+		data++;
+		--size;
+	}
+
+	DBG(hog, "Sending report type %d ID %d to handle 0x%X", report->type,
+				report->id, report->value_handle);
+
+	if (!hog->client)
+		return;
+
+	write_report(hog, report, data, size);
+}
+
+static void set_numbered(void *data, void *user_data)
+{
+	struct report *report = data;
+	struct bt_hog *hog = user_data;
+
+	switch (report->type) {
+	case HOG_REPORT_TYPE_INPUT:
+		if (hog->uhid_flags & UHID_DEV_NUMBERED_INPUT_REPORTS)
+			report->numbered = true;
+		break;
+	case HOG_REPORT_TYPE_OUTPUT:
+		if (hog->uhid_flags & UHID_DEV_NUMBERED_OUTPUT_REPORTS)
+			report->numbered = true;
+		break;
+	case HOG_REPORT_TYPE_FEATURE:
+		if (hog->uhid_flags & UHID_DEV_NUMBERED_FEATURE_REPORTS)
+			report->numbered = true;
+		break;
+	}
+}
+
+static void start_flags(struct uhid_event *ev, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+
+	hog->uhid_flags = ev->u.start.dev_flags;
+
+	DBG(hog, "uHID device flags: 0x%16" PRIx64, hog->uhid_flags);
+
+	if (hog->uhid_flags)
+		queue_foreach(hog->reports, set_numbered, hog);
+}
+
+static void uhid_destroy(struct bt_hog *hog, bool force)
+{
+	int err;
+
+	if (!hog->uhid)
+		return;
+
+	bt_uhid_unregister_all(hog->uhid);
+
+	err = bt_uhid_destroy(hog->uhid, force);
+	if (err < 0)
+		DBG(hog, "bt_uhid_destroy: %s", strerror(-err));
+}
+
+static void set_report_reply(struct bt_hog *hog, uint8_t status)
+{
+	int err;
+
+	hog->setrep_att = 0;
+
+	err = bt_uhid_set_report_reply(hog->uhid, hog->setrep_id, status);
+	if (err < 0)
+		DBG(hog, "bt_uhid_set_report_reply: %s", strerror(-err));
+}
+
+static void set_report_cb(bool success, uint8_t att_ecode, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+
+	if (!success)
+		DBG(hog, "Error setting Report value: 0x%02x", att_ecode);
+
+	set_report_reply(hog, success ? 0 : att_ecode);
+}
+
+static void set_report(struct uhid_event *ev, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	struct report *report;
+	uint8_t *data;
+	int size;
+
+	/* Destroy input device if there is an attempt to communicate with it
+	 * while disconnected.
+	 */
+	if (!hog->client) {
+		uhid_destroy(hog, true);
+		return;
+	}
+
+	/* uhid never sends reqs in parallel; if there's a req, it timed out */
+	if (hog->setrep_att) {
+		bt_gatt_client_cancel(hog->client, hog->setrep_att);
+		hog->setrep_att = 0;
+	}
+
+	hog->setrep_id = ev->u.set_report.id;
+
+	report = find_report_by_rtype(hog, ev->u.set_report.rtype,
+							ev->u.set_report.rnum);
+	if (!report) {
+		set_report_reply(hog, ENOTSUP);
+		return;
+	}
+
+	data = ev->u.set_report.data;
+	size = ev->u.set_report.size;
+
+	if (report->numbered && size > 0) {
+		data++;
+		--size;
+	}
+
+	DBG(hog, "Sending report type %d ID %d to handle 0x%X", report->type,
+				report->id, report->value_handle);
+
+	hog->setrep_att = bt_gatt_client_write_value(hog->client,
+						report->value_handle,
+						data, size, set_report_cb,
+						hog, NULL);
+	if (!hog->setrep_att)
+		set_report_reply(hog, ENOMEM);
+}
+
+static void report_reply(struct bt_hog *hog, uint8_t status, uint8_t id,
+			uint16_t len, const uint8_t *data)
+{
+	int err;
+
+	hog->getrep_att = 0;
+
+	err = bt_uhid_get_report_reply(hog->uhid, hog->getrep_id, id, status,
+					data, len);
+	if (err < 0)
+		DBG(hog, "bt_uhid_get_report_reply: %s", strerror(-err));
+}
+
+static void get_report_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct report *report = user_data;
+	struct bt_hog *hog = report->hog;
+	uint8_t status = 0;
+
+	if (!success) {
+		DBG(hog, "Error reading Report value: 0x%02x", att_ecode);
+		status = att_ecode;
+	} else if (!length) {
+		DBG(hog, "Error reading Report, length %d", length);
+		status = EIO;
+	}
+
+	report_reply(hog, status, report->numbered ? report->id : 0,
+					status ? 0 : length, value);
+}
+
+static void get_report(struct uhid_event *ev, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	struct report *report;
+
+	/* Destroy input device if there is an attempt to communicate with it
+	 * while disconnected.
+	 */
+	if (!hog->client) {
+		uhid_destroy(hog, true);
+		return;
+	}
+
+	/* uhid never sends reqs in parallel; if there's a req, it timed out */
+	if (hog->getrep_att) {
+		bt_gatt_client_cancel(hog->client, hog->getrep_att);
+		hog->getrep_att = 0;
+	}
+
+	hog->getrep_id = ev->u.get_report.id;
+
+	report = find_report_by_rtype(hog, ev->u.get_report.rtype,
+							ev->u.get_report.rnum);
+	if (!report) {
+		report_reply(hog, ENOTSUP, 0, 0, NULL);
+		return;
+	}
+
+	hog->getrep_att = bt_gatt_client_read_value(hog->client,
+						report->value_handle,
+						get_report_cb, report, NULL);
+	if (!hog->getrep_att)
+		report_reply(hog, ENOMEM, 0, 0, NULL);
+}
+
+static void get_addrs(struct bt_hog *hog, bdaddr_t *src, bdaddr_t *dst)
+{
+	struct sockaddr_l2 addr;
+	socklen_t len;
+	int fd;
+
+	bacpy(src, BDADDR_ANY);
+	bacpy(dst, BDADDR_ANY);
+
+	fd = bt_att_get_fd(bt_gatt_client_get_att(hog->client));
+	if (fd < 0)
+		return;
+
+	memset(&addr, 0, sizeof(addr));
+	len = sizeof(addr);
+	if (!getsockname(fd, (struct sockaddr *) &addr, &len) &&
+					addr.l2_family == AF_BLUETOOTH)
+		bacpy(src, &addr.l2_bdaddr);
+
+	memset(&addr, 0, sizeof(addr));
+	len = sizeof(addr);
+	if (!getpeername(fd, (struct sockaddr *) &addr, &len) &&
+					addr.l2_family == AF_BLUETOOTH)
+		bacpy(dst, &addr.l2_bdaddr);
+}
+
+static void uhid_register(struct bt_hog *hog)
+{
+	/* The handlers are unregistered when detaching, even if the uHID
+	 * device is kept, e.g. keyboards.
+	 */
+	bt_uhid_unregister_all(hog->uhid);
+
+	bt_uhid_register(hog->uhid, UHID_START, start_flags, hog);
+	bt_uhid_register(hog->uhid, UHID_OUTPUT, forward_report, hog);
+	bt_uhid_register(hog->uhid, UHID_GET_REPORT, get_report, hog);
+	bt_uhid_register(hog->uhid, UHID_SET_REPORT, set_report, hog);
+}
+
+static bool uhid_create(struct bt_hog *hog, const uint8_t *report_map,
+							size_t report_map_len)
+{
+	bdaddr_t src, dst;
+	int err;
+
+	get_addrs(hog, &src, &dst);
+
+	err = bt_uhid_create(hog->uhid, hog->name, &src, &dst,
+				hog->vendor, hog->product, hog->version,
+				hog->bcountrycode, hog->type,
+				(void *) report_map, report_map_len);
+	if (err < 0) {
+		DBG(hog, "bt_uhid_create: %s", strerror(-err));
+		return false;
+	}
+
+	uhid_register(hog);
+
+	DBG(hog, "HoG created uHID device");
+
+	return true;
+}
+
+static void db_report_map_write_value_cb(struct gatt_db_attribute *attr,
+						int err, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+
+	if (err)
+		DBG(hog, "Error writing report map value to gatt db");
+}
+
+static void report_map_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct bt_hog *hog = user_data;
+
+	hog->report_map_id = 0;
+
+	if (!success) {
+		DBG(hog, "Report Map read failed: 0x%02x", att_ecode);
+		return;
+	}
+
+	if (!uhid_create(hog, value, length))
+		return;
+
+	/* Cache the report map, once known to be usable */
+	gatt_db_attribute_write(hog->report_map_attr, 0, value, length, 0,
+					NULL, db_report_map_write_value_cb,
+					hog);
+}
+
+static void read_report_map(struct bt_hog *hog)
+{
+	uint16_t handle;
+
+	if (!hog->client || !hog->report_map_attr ||
+			bt_uhid_created(hog->uhid) || hog->report_map_id)
+		return;
+
+	handle = gatt_db_attribute_get_handle(hog->report_map_attr);
+
+	/* The Report Map is usually longer than the MTU, which is handled
+	 * by reading it with Read Blob.
+	 */
+	hog->report_map_id = bt_gatt_client_read_long_value(hog->client,
+						handle, 0, report_map_read_cb,
+						hog, NULL);
+	if (!hog->report_map_id)
+		DBG(hog, "Could not read Report Map");
+}
+
+static void sci_mode_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct bt_hog *hog = req->hog;
+
+	if (!success) {
+		DBG(hog, "HID SCI Mode read failed: 0x%02x", att_ecode);
+		goto done;
+	}
+
+	if (length != 1) {
+		DBG(hog, "Malformed HID SCI Mode: length %u", length);
+		goto done;
+	}
+
+	hog->sci_mode = value[0];
+
+	DBG(hog, "SCI Mode: 0x%02X", value[0]);
+
+done:
+	req_done(req, success);
+}
+
+static void sci_info_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct bt_hog *hog = req->hog;
+	uint8_t min_conn_interval;
+	uint8_t num_grps;
+	uint8_t i;
+
+	if (!success) {
+		DBG(hog, "HID SCI Information read failed: 0x%02x", att_ecode);
+		goto done;
+	}
+
+	if (length < 2) {
+		DBG(hog, "Malformed HID SCI Information: length %u", length);
+		goto done;
+	}
+
+	min_conn_interval = value[0];
+	num_grps = value[1];
+
+	DBG(hog, "SCI Info: Minimum Supported Connection Interval: %.3f ms "
+		"(0x%02x) Number of Supported Subgroups: %d",
+		min_conn_interval * 0.125, min_conn_interval, num_grps);
+
+	for (i = 0; i < num_grps && (2 + i * 6 + 5) < length; i++) {
+		uint16_t min, max, stride;
+		size_t off = 2 + i * 6;
+
+		min = get_le16(&value[off]);
+		max = get_le16(&value[off + 2]);
+		stride = get_le16(&value[off + 4]);
+
+		DBG(hog, "  Subgroup[%u]: Min %.3f ms Max %.3f ms "
+				"Stride %.3f ms", i, min * 0.125, max * 0.125,
+				stride * 0.125);
+	}
+
+done:
+	req_done(req, success);
+}
+
+static void sci_mode_notify_cb(uint16_t value_handle, const uint8_t *value,
+					uint16_t length, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+
+	if (length != 1) {
+		DBG(hog, "Malformed HID SCI Mode notification: length %u",
+								length);
+		return;
+	}
+
+	hog->sci_mode = value[0];
+
+	DBG(hog, "SCI Mode changed: 0x%02X", hog->sci_mode);
+
+	if (hog->sci_mode_func)
+		hog->sci_mode_func(hog->sci_mode, hog->sci_mode_data);
+}
+
+static void sci_mode_registered(uint16_t att_ecode, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+
+	if (att_ecode)
+		DBG(hog, "Unable to enable SCI Mode notifications: 0x%02x",
+								att_ecode);
+}
+
+static void sci_mode_enable_notify(struct bt_hog *hog)
+{
+	if (hog->sci_mode_id || !hog->client || !hog->sci_mode_handle ||
+			!(hog->flags & HOG_INFO_FLAG_SCI_SUPPORTED) ||
+			!(hog->sci_mode_props & BT_GATT_CHRC_PROP_NOTIFY))
+		return;
+
+	hog->sci_mode_id = bt_gatt_client_register_notify(hog->client,
+					hog->sci_mode_handle,
+					sci_mode_registered,
+					sci_mode_notify_cb, hog, NULL);
+	if (!hog->sci_mode_id)
+		DBG(hog, "Unable to register SCI Mode notification");
+}
+
+static void info_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct bt_hog *hog = req->hog;
+
+	if (!success) {
+		DBG(hog, "HID Information read failed: 0x%02x", att_ecode);
+		goto done;
+	}
+
+	if (length != HID_INFO_SIZE) {
+		DBG(hog, "Malformed HID Information: length %u", length);
+		goto done;
+	}
+
+	hog->bcdhid = get_le16(&value[0]);
+	hog->bcountrycode = value[2];
+	hog->flags = value[3];
+
+	DBG(hog, "bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X",
+			hog->bcdhid, hog->bcountrycode, hog->flags);
+
+	/* Read SCI attributes if SCI is supported */
+	if (hog->flags & HOG_INFO_FLAG_SCI_SUPPORTED) {
+		if (hog->sci_mode_handle)
+			read_char(hog, hog->sci_mode_handle, sci_mode_read_cb,
+									hog);
+		if (hog->sci_info_handle)
+			read_char(hog, hog->sci_info_handle, sci_info_read_cb,
+									hog);
+
+		/* The HID Device notifies the mode once changed */
+		sci_mode_enable_notify(hog);
+	}
+
+done:
+	req_done(req, success);
+}
+
+static void proto_mode_read_cb(bool success, uint8_t att_ecode,
+					const uint8_t *value, uint16_t length,
+					void *user_data)
+{
+	struct hog_req *req = user_data;
+	struct bt_hog *hog = req->hog;
+
+	if (!success) {
+		DBG(hog, "Protocol Mode characteristic read failed: 0x%02x",
+								att_ecode);
+		goto done;
+	}
+
+	if (length < 1) {
+		DBG(hog, "Malformed Protocol Mode: length %u", length);
+		goto done;
+	}
+
+	if (value[0] == HOG_PROTO_MODE_BOOT) {
+		uint8_t nval = HOG_PROTO_MODE_REPORT;
+
+		DBG(hog, "HoG is operating in Boot Protocol Mode");
+
+		bt_gatt_client_write_without_response(hog->client,
+						hog->proto_mode_handle, false,
+						&nval, sizeof(nval));
+	} else if (value[0] == HOG_PROTO_MODE_REPORT)
+		DBG(hog, "HoG is operating in Report Protocol Mode");
+
+done:
+	req_done(req, success);
+}
+
+static void db_report_map_read_value_cb(struct gatt_db_attribute *attrib,
+						int err, const uint8_t *value,
+						size_t length, void *user_data)
+{
+	struct iovec *map = user_data;
+
+	if (err || !length)
+		return;
+
+	map->iov_len = length;
+	map->iov_base = (void *) value;
+}
+
+static void foreach_hog_chrc(struct gatt_db_attribute *attr, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	bt_uuid_t uuid, report_uuid, report_map_uuid, info_uuid;
+	bt_uuid_t proto_mode_uuid, cp_uuid, sci_mode_uuid, sci_info_uuid;
+	uint16_t handle, value_handle;
+	uint8_t props;
+	struct iovec map = {};
+
+	gatt_db_attribute_get_char_data(attr, &handle, &value_handle, &props,
+					NULL, &uuid);
+
+	bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
+	if (!bt_uuid_cmp(&report_uuid, &uuid)) {
+		report_add(hog, attr);
+		return;
+	}
+
+	bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
+	if (!bt_uuid_cmp(&report_map_uuid, &uuid)) {
+		/* Try to read the cache of report map if available */
+		hog->report_map_attr = gatt_db_get_attribute(hog->db,
+								value_handle);
+		gatt_db_attribute_read(hog->report_map_attr, 0,
+					BT_ATT_OP_READ_REQ, NULL,
+					db_report_map_read_value_cb, &map);
+
+		if (map.iov_len) {
+			/* Report map found in the cache, straight to creating
+			 * UHID to optimize reconnection.
+			 */
+			uhid_create(hog, map.iov_base, map.iov_len);
+		}
+
+		gatt_db_service_foreach_desc(attr, foreach_hog_external, hog);
+		return;
+	}
+
+	bt_uuid16_create(&info_uuid, HOG_INFO_UUID);
+	if (!bt_uuid_cmp(&info_uuid, &uuid)) {
+		read_char(hog, value_handle, info_read_cb, hog);
+		return;
+	}
+
+	bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID);
+	if (!bt_uuid_cmp(&proto_mode_uuid, &uuid)) {
+		hog->proto_mode_handle = value_handle;
+		read_char(hog, value_handle, proto_mode_read_cb, hog);
+		return;
+	}
+
+	bt_uuid16_create(&cp_uuid, HOG_CP_UUID);
+	if (!bt_uuid_cmp(&cp_uuid, &uuid)) {
+		hog->cp_handle = value_handle;
+		return;
+	}
+
+	bt_uuid16_create(&sci_mode_uuid, HOG_SCI_MODE_UUID);
+	if (!bt_uuid_cmp(&sci_mode_uuid, &uuid)) {
+		hog->sci_mode_handle = value_handle;
+		hog->sci_mode_props = props;
+		return;
+	}
+
+	bt_uuid16_create(&sci_info_uuid, HOG_SCI_INFO_UUID);
+	if (!bt_uuid_cmp(&sci_info_uuid, &uuid))
+		hog->sci_info_handle = value_handle;
+}
+
+static void report_free(void *data)
+{
+	struct report *report = data;
+
+	free(report->value);
+	free(report);
+}
+
+static void hog_free(struct bt_hog *hog)
+{
+	bt_hog_detach(hog, true);
+	uhid_destroy(hog, true);
+
+	queue_destroy(hog->instances, (void *) bt_hog_unref);
+	bt_uhid_unref(hog->uhid);
+	queue_destroy(hog->reports, report_free);
+	queue_destroy(hog->reqs, NULL);
+	free(hog->name);
+	gatt_db_unref(hog->db);
+
+	if (hog->debug_destroy)
+		hog->debug_destroy(hog->debug_data);
+
+	free(hog);
+}
+
+static struct bt_hog *hog_new(int fd, const char *name, uint16_t vendor,
+					uint16_t product, uint16_t version,
+					uint8_t type,
+					struct gatt_db_attribute *attr)
+{
+	struct bt_uhid *uhid;
+	struct bt_hog *hog;
+
+	if (fd < 0)
+		uhid = bt_uhid_new_default();
+	else
+		uhid = bt_uhid_new(fd);
+
+	if (!uhid)
+		return NULL;
+
+	hog = new0(struct bt_hog, 1);
+	hog->reports = queue_new();
+	hog->instances = queue_new();
+	hog->reqs = queue_new();
+	hog->uhid_fd = fd;
+	hog->uhid = uhid;
+	hog->name = strdup(name ? name : "");
+	hog->vendor = vendor;
+	hog->product = product;
+	hog->version = version;
+	hog->type = type;
+	hog->attr = attr;
+
+	return hog;
+}
+
+static void foreach_hog_service(struct gatt_db_attribute *attr, void *user_data)
+{
+	struct bt_hog *hog = user_data;
+	struct bt_hog *instance;
+
+	if (!hog->attr) {
+		hog->attr = attr;
+		return;
+	}
+
+	instance = hog_new(hog->uhid_fd, hog->name, hog->vendor, hog->product,
+				hog->version, hog->type, attr);
+	if (!instance)
+		return;
+
+	instance->db = gatt_db_ref(hog->db);
+
+	/* The debug callback is owned by the parent */
+	instance->debug_func = hog->debug_func;
+	instance->debug_data = hog->debug_data;
+	instance->sci_mode_func = hog->sci_mode_func;
+	instance->sci_mode_data = hog->sci_mode_data;
+
+	queue_push_tail(hog->instances, bt_hog_ref(instance));
+}
+
+static bool hog_set_db(struct bt_hog *hog, struct gatt_db *db)
+{
+	bt_uuid_t uuid;
+
+	hog->db = gatt_db_ref(db);
+
+	/* Handle the HID services */
+	bt_uuid16_create(&uuid, HOG_UUID16);
+	gatt_db_foreach_service(db, &uuid, foreach_hog_service, hog);
+
+	if (!hog->attr) {
+		/* Look up the services again on the next attempt */
+		gatt_db_unref(hog->db);
+		hog->db = NULL;
+		return false;
+	}
+
+	return true;
+}
+
+struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
+					uint16_t product, uint16_t version,
+					uint8_t type, struct gatt_db *db)
+{
+	struct bt_hog *hog;
+
+	hog = hog_new(fd, name, vendor, product, version, type, NULL);
+	if (!hog)
+		return NULL;
+
+	/* Without a db the services are looked up once attached */
+	if (db && !hog_set_db(hog, db)) {
+		hog_free(hog);
+		return NULL;
+	}
+
+	return bt_hog_ref(hog);
+}
+
+struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
+					uint16_t product, uint16_t version,
+					uint8_t type, struct gatt_db *db)
+{
+	return bt_hog_new(-1, name, vendor, product, version, type, db);
+}
+
+struct bt_hog *bt_hog_ref(struct bt_hog *hog)
+{
+	if (!hog)
+		return NULL;
+
+	__sync_fetch_and_add(&hog->ref_count, 1);
+
+	return hog;
+}
+
+void bt_hog_unref(struct bt_hog *hog)
+{
+	if (!hog)
+		return;
+
+	if (__sync_sub_and_fetch(&hog->ref_count, 1))
+		return;
+
+	hog_free(hog);
+}
+
+static void instance_set_debug(void *data, void *user_data)
+{
+	struct bt_hog *instance = data;
+	struct bt_hog *hog = user_data;
+
+	instance->debug_func = hog->debug_func;
+	instance->debug_data = hog->debug_data;
+}
+
+bool bt_hog_set_debug(struct bt_hog *hog, bt_hog_debug_func_t func,
+			void *user_data, bt_hog_destroy_func_t destroy)
+{
+	if (!hog)
+		return false;
+
+	if (hog->debug_destroy)
+		hog->debug_destroy(hog->debug_data);
+
+	hog->debug_func = func;
+	hog->debug_destroy = destroy;
+	hog->debug_data = user_data;
+
+	queue_foreach(hog->instances, instance_set_debug, hog);
+
+	return true;
+}
+
+void bt_hog_set_ids(struct bt_hog *hog, uint16_t vendor, uint16_t product,
+							uint16_t version)
+{
+	const struct queue_entry *entry;
+
+	if (!hog)
+		return;
+
+	hog->vendor = vendor;
+	hog->product = product;
+	hog->version = version;
+
+	for (entry = queue_get_entries(hog->instances); entry;
+							entry = entry->next)
+		bt_hog_set_ids(entry->data, vendor, product, version);
+}
+
+static void report_reattach(void *data, void *user_data)
+{
+	report_enable_notify(data);
+}
+
+bool bt_hog_attach(struct bt_hog *hog, struct bt_gatt_client *client)
+{
+	const struct queue_entry *entry;
+
+	if (!hog || !client || hog->client)
+		return false;
+
+	if (!hog->db && !hog_set_db(hog, bt_gatt_client_get_db(client))) {
+		DBG(hog, "No HID service found");
+		return false;
+	}
+
+	hog->client = bt_gatt_client_ref(client);
+
+	for (entry = queue_get_entries(hog->instances); entry;
+							entry = entry->next)
+		bt_hog_attach(entry->data, client);
+
+	/* The uHID device may have been kept while detached, e.g. keyboards,
+	 * in which case its handlers have to be registered again.
+	 */
+	if (bt_uhid_created(hog->uhid)) {
+		uhid_register(hog);
+	} else {
+		DBG(hog, "HoG discovering characteristics");
+		gatt_db_service_foreach_char(hog->attr, foreach_hog_chrc, hog);
+
+		/* Nothing to wait for before reading the Report Map */
+		if (!hog->pending)
+			read_report_map(hog);
+
+		/* Unless created with the Report Map in the cache, wait for it
+		 * to be read.
+		 */
+		if (!bt_uhid_created(hog->uhid))
+			return true;
+	}
+
+	/* If UHID is already created, set up the report value handlers to
+	 * optimize reconnection, as reports already known from a previous
+	 * connection are not read again when discovering them.
+	 */
+	queue_foreach(hog->reports, report_reattach, NULL);
+	sci_mode_enable_notify(hog);
+
+	/* Replay any pending input reports sent while disconnected, e.g.
+	 * when the device was reconnected by pressing a key.
+	 */
+	bt_uhid_replay(hog->uhid);
+
+	return true;
+}
+
+static void report_detach(void *data, void *user_data)
+{
+	struct report *report = data;
+	struct bt_hog *hog = user_data;
+
+	if (!report->notify_id)
+		return;
+
+	bt_gatt_client_unregister_notify(hog->client, report->notify_id);
+	report->notify_id = 0;
+}
+
+void bt_hog_detach(struct bt_hog *hog, bool force)
+{
+	const struct queue_entry *entry;
+	struct bt_gatt_client *client;
+
+	if (!hog)
+		return;
+
+	if (!hog->client)
+		goto done;
+
+	for (entry = queue_get_entries(hog->instances); entry;
+							entry = entry->next)
+		bt_hog_detach(entry->data, force);
+
+	queue_foreach(hog->reports, report_detach, hog);
+
+	if (hog->sci_mode_id) {
+		bt_gatt_client_unregister_notify(hog->client,
+							hog->sci_mode_id);
+		hog->sci_mode_id = 0;
+	}
+
+	/* Cancel the pending requests, as the client may still be in use,
+	 * e.g. the notifications are disabled when still connected.
+	 */
+	queue_remove_all(hog->reqs, NULL, NULL, req_cancel);
+
+	if (hog->getrep_att) {
+		bt_gatt_client_cancel(hog->client, hog->getrep_att);
+		hog->getrep_att = 0;
+	}
+
+	if (hog->setrep_att) {
+		bt_gatt_client_cancel(hog->client, hog->setrep_att);
+		hog->setrep_att = 0;
+	}
+
+	if (hog->report_map_id) {
+		bt_gatt_client_cancel(hog->client, hog->report_map_id);
+		hog->report_map_id = 0;
+	}
+
+	hog->pending = 0;
+
+	client = hog->client;
+	hog->client = NULL;
+	bt_gatt_client_unref(client);
+
+done:
+	uhid_destroy(hog, force);
+}
+
+int bt_hog_set_control_point(struct bt_hog *hog, bool suspend)
+{
+	uint8_t value = suspend ? 0x00 : 0x01;
+
+	if (!hog)
+		return -EINVAL;
+
+	if (!hog->client)
+		return -ENOTCONN;
+
+	if (!hog->cp_handle)
+		return -ENOTSUP;
+
+	bt_gatt_client_write_without_response(hog->client, hog->cp_handle,
+						false, &value, sizeof(value));
+
+	return 0;
+}
+
+int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type)
+{
+	const struct queue_entry *entry;
+	struct report *report;
+
+	if (!hog)
+		return -EINVAL;
+
+	if (!hog->client)
+		return -ENOTCONN;
+
+	report = find_report(hog, type, 0);
+	if (!report)
+		return -ENOTSUP;
+
+	DBG(hog, "Write report, handle 0x%X", report->value_handle);
+
+	if (report->properties & BT_GATT_CHRC_PROP_WRITE)
+		write_output(hog, report, data, size);
+
+	if (report->properties & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)
+		bt_gatt_client_write_without_response(hog->client,
+						report->value_handle, false,
+						data, size);
+
+	for (entry = queue_get_entries(hog->instances); entry;
+							entry = entry->next)
+		bt_hog_send_report(entry->data, data, size, type);
+
+	return 0;
+}
+
+static struct bt_hog *find_cp(struct bt_hog *hog)
+{
+	const struct queue_entry *entry;
+
+	if (hog->cp_handle)
+		return hog;
+
+	for (entry = queue_get_entries(hog->instances); entry;
+							entry = entry->next) {
+		struct bt_hog *instance = entry->data;
+
+		if (instance->cp_handle)
+			return instance;
+	}
+
+	return NULL;
+}
+
+int bt_hog_set_sci_mode(struct bt_hog *hog, uint8_t mode)
+{
+	struct bt_hog *cp;
+
+	if (!hog)
+		return -EINVAL;
+
+	if (mode < BT_HOG_SCI_MODE_DEFAULT || mode > BT_HOG_SCI_MODE_FULL_RANGE)
+		return -EINVAL;
+
+	if (!hog->client)
+		return -ENOTCONN;
+
+	if (!(hog->flags & HOG_INFO_FLAG_SCI_SUPPORTED))
+		return -ENOTSUP;
+
+	if (mode == BT_HOG_SCI_MODE_LOW_POWER &&
+			!(hog->flags & HOG_INFO_FLAG_SCI_LOW_POWER))
+		return -ENOTSUP;
+
+	/* The command affects the whole HID Device, regardless of the
+	 * instance of HID Service the HID Control Point belongs to.
+	 */
+	cp = find_cp(hog);
+	if (!cp || !cp->client)
+		return -ENOTSUP;
+
+	DBG(hog, "Enable SCI mode 0x%02x", mode);
+
+	if (!bt_gatt_client_write_without_response(cp->client,
+					cp->cp_handle, false,
+					&mode, sizeof(mode)))
+		return -EIO;
+
+	return 0;
+}
+
+uint8_t bt_hog_get_sci_mode(struct bt_hog *hog)
+{
+	if (!hog)
+		return 0;
+
+	return hog->sci_mode;
+}
+
+bool bt_hog_set_sci_mode_callback(struct bt_hog *hog,
+					bt_hog_sci_mode_func_t func,
+					void *user_data)
+{
+	const struct queue_entry *entry;
+
+	if (!hog)
+		return false;
+
+	hog->sci_mode_func = func;
+	hog->sci_mode_data = user_data;
+
+	for (entry = queue_get_entries(hog->instances); entry;
+							entry = entry->next)
+		bt_hog_set_sci_mode_callback(entry->data, func, user_data);
+
+	return true;
+}
diff --git a/src/shared/hog.h b/src/shared/hog.h
new file mode 100644
index 000000000000..910a2261b611
--- /dev/null
+++ b/src/shared/hog.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2014  Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+struct bt_hog;
+struct gatt_db;
+struct bt_gatt_client;
+
+/* HID Control Point values enabling the HID SCI modes */
+#define BT_HOG_SCI_MODE_DEFAULT		0x02
+#define BT_HOG_SCI_MODE_FAST		0x03
+#define BT_HOG_SCI_MODE_LOW_POWER	0x04
+#define BT_HOG_SCI_MODE_FULL_RANGE	0x05
+
+typedef void (*bt_hog_debug_func_t)(const char *str, void *user_data);
+typedef void (*bt_hog_destroy_func_t)(void *user_data);
+typedef void (*bt_hog_sci_mode_func_t)(uint8_t mode, void *user_data);
+
+struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
+					uint16_t product, uint16_t version,
+					uint8_t type, struct gatt_db *db);
+
+struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
+					uint16_t product, uint16_t version,
+					uint8_t type, struct gatt_db *db);
+
+struct bt_hog *bt_hog_ref(struct bt_hog *hog);
+void bt_hog_unref(struct bt_hog *hog);
+
+bool bt_hog_set_debug(struct bt_hog *hog, bt_hog_debug_func_t func,
+			void *user_data, bt_hog_destroy_func_t destroy);
+
+/* Set the vendor, product and version used when creating the uHID device,
+ * e.g. once known from the PnP ID of the Device Information Service.
+ */
+void bt_hog_set_ids(struct bt_hog *hog, uint16_t vendor, uint16_t product,
+							uint16_t version);
+
+bool bt_hog_attach(struct bt_hog *hog, struct bt_gatt_client *client);
+void bt_hog_detach(struct bt_hog *hog, bool force);
+
+int bt_hog_set_control_point(struct bt_hog *hog, bool suspend);
+
+/* Request the HID Device to enable the given HID SCI mode, by writing it to
+ * the HID Control Point. There is no response, the HID Device updates the
+ * connection rate and then notifies the new mode with HID SCI Mode.
+ */
+int bt_hog_set_sci_mode(struct bt_hog *hog, uint8_t mode);
+
+/* Current HID SCI mode, as read or notified with HID SCI Mode */
+uint8_t bt_hog_get_sci_mode(struct bt_hog *hog);
+
+/* Called when the HID Device notifies a new HID SCI mode */
+bool bt_hog_set_sci_mode_callback(struct bt_hog *hog,
+					bt_hog_sci_mode_func_t func,
+					void *user_data);
+int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 14/21] unit/test-hog: Use shared/hog
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (12 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 13/21] shared/hog: Add initial implementation Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 15/21] test: functional: change the HoG SCI mode with the HID Control Point Luiz Augusto von Dentz
                   ` (6 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Port the tests to src/shared/hog, with the discovery done by
bt_gatt_client, and the uHID device emulated with a socket pair so the
events are checked.

The test cases are renamed after HOGP.TS p13, and the missing ones for
the Report Host are added, except for HID ISO which is not supported:

- Discovery: HGDC/BV-03-C (two instances of the HID Service),
  HGDC/BV-04-C and HGDC/BV-05-C
- Read: HGRF/BV-02-C to BV-08-C, BV-18-C and BV-19-C, with the Report
  values also read with UHID_GET_REPORT
- Write: HGWF/BV-01-C, BV-02-C and BV-04-C with UHID_SET_REPORT and
  UHID_OUTPUT, BV-05-C and BV-06-C (Suspend and Exit Suspend), BV-07-C
  (Set Report Protocol Mode)
- HID SCI: HGWF/BV-08-C to BV-11-C, writing each mode to the HID
  Control Point with two instances of the HID Service, then changing
  to another mode once notified with HID SCI Mode
- Configuration: HGCF/BV-01-C and BV-02-C (notifications disabled when
  detaching)
- Notifications: HGNF/BV-01-C, forwarded as UHID_INPUT2, and BI-01-C
  and BI-02-C, ignoring the Boot Keyboard and Boot Mouse Input Reports,
  and HGNF/BV-01-C once reconnected, with the reports already known and
  the Report Map cached
- Characteristic GGIT: CHA/BV-01-C to BV-04-C, BV-09-C, BV-10-C and
  DES/BV-01-C

The Battery Level and PnP ID GGIT test cases are left to the battery
and deviceinfo plugins which handle these services.

Assisted-by: OpenCode:claude-opus-5.5
---
 Makefile.am     |   11 +-
 unit/test-hog.c | 1431 +++++++++++++++++++++++++++++++++++------------
 2 files changed, 1079 insertions(+), 363 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 5419b31e21d5..621892055c05 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -768,16 +768,7 @@ unit_test_gatt_LDADD = src/libshared-glib.la \
 
 unit_tests += unit/test-hog
 
-unit_test_hog_SOURCES = unit/test-hog.c \
-			$(btio_sources) \
-			profiles/input/hog-lib.h profiles/input/hog-lib.c \
-			profiles/scanparam/scpp.h profiles/scanparam/scpp.c \
-			profiles/battery/bas.h profiles/battery/bas.c \
-			profiles/deviceinfo/dis.h profiles/deviceinfo/dis.c \
-			src/log.h src/log.c \
-			attrib/att.h attrib/att.c \
-			attrib/gatt.h attrib/gatt.c \
-			attrib/gattrib.h attrib/gattrib.c
+unit_test_hog_SOURCES = unit/test-hog.c
 unit_test_hog_LDADD = src/libshared-glib.la \
 				lib/libbluetooth-internal.la $(GLIB_LIBS)
 
diff --git a/unit/test-hog.c b/unit/test-hog.c
index 45ffe718e7de..b808ea30d9f6 100644
--- a/unit/test-hog.c
+++ b/unit/test-hog.c
@@ -12,11 +12,12 @@
 #include <config.h>
 #endif
 
-#define _GNU_SOURCE
-#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
 #include <string.h>
+#include <unistd.h>
+#include <errno.h>
 #include <sys/socket.h>
-#include <fcntl.h>
 
 #include <glib.h>
 
@@ -24,404 +25,1128 @@
 #include "bluetooth/uuid.h"
 
 #include "src/shared/util.h"
+#include "src/shared/io.h"
 #include "src/shared/tester.h"
 #include "src/shared/queue.h"
 #include "src/shared/att.h"
 #include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/uhid.h"
+#include "src/shared/hog.h"
 
-#include "attrib/gattrib.h"
+/*
+ * HOGP Report Host tests, see HOGP.TS. The HID Device (Lower Tester) is
+ * emulated with the PDUs below, and the uHID device with a socket pair.
+ *
+ * The HID Service of the HID Device is laid out as follows, the SCI
+ * variant adding the HID SCI Mode and HID SCI Information characteristics
+ * and the multiple instances variant having a second HID Service at
+ * 0x0021 with the same layout as the SCI variant:
+ *
+ * 0x0001 HID Service
+ * 0x0003   Protocol Mode (Read, Write Without Response)
+ * 0x0005   Report Map (Read)
+ * 0x0006     External Report Reference: Battery Level
+ * 0x0008   HID Information (Read)
+ * 0x000a   HID Control Point (Write Without Response)
+ * 0x000c   Report (Read, Notify): Input Report 1
+ * 0x000d     Client Characteristic Configuration
+ * 0x000e     Report Reference
+ * 0x0010   Report (Read, Write, Write Without Response): Output Report 1
+ * 0x0011     Report Reference
+ * 0x0013   Report (Read, Write): Feature Report 1
+ * 0x0014     Report Reference
+ * 0x0016   Boot Keyboard Input Report (Read, Notify)
+ * 0x0017     Client Characteristic Configuration
+ * 0x0019   Boot Mouse Input Report (Read, Notify)
+ * 0x001a     Client Characteristic Configuration
+ * 0x001c   HID SCI Mode (Read, Notify) - SCI variant
+ * 0x001d     Client Characteristic Configuration
+ * 0x001f   HID SCI Information (Read) - SCI variant
+ *
+ * followed by a Battery Service with the Battery Level characteristic.
+ *
+ * The Client Characteristic Configuration descriptors following a
+ * characteristic with no other descriptor are not discovered with Find
+ * Information, as bt_gatt_client assumes them in that case.
+ */
 
-#include "profiles/input/hog-lib.h"
+#define HOG_GATT_CLIENT_MTU	64
 
-struct test_pdu {
-	bool valid;
-	const uint8_t *data;
-	size_t size;
+#define REPORT_MAP_LEN		73
+
+enum action {
+	ACT_NONE,
+	ACT_NOTIFY,
+	ACT_GET_INPUT,
+	ACT_GET_OUTPUT,
+	ACT_GET_FEATURE,
+	ACT_SET_INPUT,
+	ACT_SET_OUTPUT,
+	ACT_SET_FEATURE,
+	ACT_OUTPUT,
+	ACT_SUSPEND,
+	ACT_RESUME,
+	ACT_DETACH,
+	ACT_REATTACH,
+	ACT_SCI,
+};
+
+struct test_config {
+	enum action action;
+	/* Number of uHID devices created, once the Report Map is read */
+	unsigned int creates;
+	/* HID SCI modes to enable, the second once the first is notified */
+	uint8_t mode[2];
+	/* Number of HID SCI Mode notifications per mode */
+	unsigned int notifications;
+	/* uHID event expected after the action */
+	uint32_t uhid_type;
+	const uint8_t *uhid_data;
+	size_t uhid_len;
 };
 
 struct test_data {
-	char *test_name;
-	struct test_pdu *pdu_list;
-};
-
-struct context {
-	GAttrib *attrib;
+	const struct iovec *setup;
+	size_t setup_cnt;
+	struct iovec *iov;
+	size_t iovcnt;
+	const struct test_config *cfg;
+	struct bt_gatt_client *client;
 	struct bt_hog *hog;
-	guint source;
-	guint process;
-	int fd;
-	unsigned int pdu_offset;
-	const struct test_data *data;
+	int uhid_fd;
+	struct io *uhid_io;
+	unsigned int created;
+	unsigned int notified;
+	unsigned int mode_idx;
+	bool io_done;
+	bool uhid_done;
+	bool sci_done;
 };
 
-#define data(args...) ((const unsigned char[]) { args })
-
-#define raw_pdu(args...)					\
-{								\
-	.valid = true,						\
-	.data = util_memdup(data(args), sizeof(data(args))),	\
-	.size = sizeof(data(args)),				\
-}
-
-#define false_pdu()						\
-{								\
-		.valid = false,					\
-}
-
-#define define_test(name, function, args...)			\
-	do {							\
-		const struct test_pdu pdus[] = {		\
-			args, { }				\
-		};						\
-		static struct test_data data;			\
-		data.test_name = g_strdup(name);		\
-		data.pdu_list = util_memdup(pdus, sizeof(pdus));\
-		tester_add(name, &data, NULL, function, NULL);	\
+#define define_test(name, _setup, _cfg, args...)			\
+	do {								\
+		const struct iovec iov[] = { args };			\
+		static struct test_data data;				\
+		data.setup = _setup;					\
+		data.setup_cnt = ARRAY_SIZE(_setup);			\
+		data.cfg = _cfg;					\
+		data.iovcnt = ARRAY_SIZE(iov);				\
+		data.iov = util_iov_dup(iov, ARRAY_SIZE(iov));		\
+		tester_add_full(name, &data, NULL, test_setup,		\
+				test_hog, test_teardown, NULL, 2,	\
+				&data, test_free);			\
 	} while (0)
 
-static gboolean context_quit(gpointer user_data)
+static void test_free(void *user_data)
 {
-	struct context *context = user_data;
+	struct test_data *data = user_data;
 
-	if (context->process > 0)
-		g_source_remove(context->process);
-
-	if (context->source > 0)
-		g_source_remove(context->source);
-
-	bt_hog_unref(context->hog);
-
-	g_attrib_unref(context->attrib);
-
-	g_free(context);
-
-	tester_test_passed();
-
-	return FALSE;
+	util_iov_free(data->iov, data->iovcnt);
 }
 
-static gboolean send_pdu(gpointer user_data)
+static void print_debug(const char *str, void *user_data)
 {
-	struct context *context = user_data;
-	const struct test_pdu *pdu;
-	ssize_t len;
+	const char *prefix = user_data;
 
-	pdu = &context->data->pdu_list[context->pdu_offset++];
-
-	len = write(context->fd, pdu->data, pdu->size);
-
-	tester_monitor('<', 0x0004, 0x0000, pdu->data, len);
-
-	g_assert_cmpint(len, ==, pdu->size);
-
-	context->process = 0;
-
-	if (!context->data->pdu_list[context->pdu_offset].valid)
-		context_quit(context);
-
-	return FALSE;
+	if (tester_use_debug())
+		tester_debug("%s%s", prefix, str);
 }
 
-static gboolean test_handler(GIOChannel *channel, GIOCondition cond,
-							gpointer user_data)
+static void test_done(struct test_data *data)
 {
-	struct context *context = user_data;
-	unsigned char buf[512];
-	const struct test_pdu *pdu;
+	if (data->io_done && data->uhid_done && data->sci_done)
+		tester_test_passed();
+}
+
+static void test_complete_cb(const void *user_data)
+{
+	struct test_data *data = (void *) user_data;
+
+	data->io_done = true;
+	test_done(data);
+}
+
+static void uhid_send(struct test_data *data, uint32_t type, uint8_t rtype)
+{
+	struct uhid_event ev;
 	ssize_t len;
-	int fd;
 
-	pdu = &context->data->pdu_list[context->pdu_offset++];
+	memset(&ev, 0, sizeof(ev));
+	ev.type = type;
 
-	if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
-		context->source = 0;
-		g_print("%s: cond %x\n", __func__, cond);
-		return FALSE;
+	switch (type) {
+	case UHID_START:
+		ev.u.start.dev_flags = UHID_DEV_NUMBERED_FEATURE_REPORTS |
+					UHID_DEV_NUMBERED_OUTPUT_REPORTS |
+					UHID_DEV_NUMBERED_INPUT_REPORTS;
+		break;
+	case UHID_GET_REPORT:
+		ev.u.get_report.id = 1;
+		ev.u.get_report.rnum = 1;
+		ev.u.get_report.rtype = rtype;
+		break;
+	case UHID_SET_REPORT:
+		ev.u.set_report.id = 2;
+		ev.u.set_report.rnum = 1;
+		ev.u.set_report.rtype = rtype;
+		ev.u.set_report.size = 2;
+		ev.u.set_report.data[0] = 0x01;
+		ev.u.set_report.data[1] = 0x42;
+		break;
+	case UHID_OUTPUT:
+		ev.u.output.rtype = rtype;
+		ev.u.output.size = 2;
+		ev.u.output.data[0] = 0x01;
+		ev.u.output.data[1] = 0x07;
+		break;
 	}
 
-	fd = g_io_channel_unix_get_fd(channel);
-
-	len = read(fd, buf, sizeof(buf));
-
-	g_assert(len > 0);
-
-	tester_monitor('>', 0x0004, 0x0000, buf, len);
-
-	g_assert_cmpint(len, ==, pdu->size);
-
-	g_assert(memcmp(buf, pdu->data, pdu->size) == 0);
-
-	context->process = g_idle_add(send_pdu, context);
-
-	return TRUE;
+	len = write(io_get_fd(data->uhid_io), &ev, sizeof(ev));
+	g_assert_cmpint(len, ==, sizeof(ev));
 }
 
-static struct context *create_context(gconstpointer data)
+static void do_action(struct test_data *data)
 {
-	struct context *context;
-	GIOChannel *channel, *att_io;
-	int err, sv[2], fd;
-	char name[] = "bluez-hog";
-	uint16_t vendor = 0x0002;
-	uint16_t product = 0x0001;
-	uint16_t version = 0x0001;
+	const struct test_config *cfg = data->cfg;
 
-	context = g_new0(struct context, 1);
-	err = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sv);
-	g_assert(err == 0);
-
-	att_io = g_io_channel_unix_new(sv[0]);
-
-	g_io_channel_set_close_on_unref(att_io, TRUE);
-
-	context->attrib = g_attrib_new(att_io, 23, false);
-	g_assert(context->attrib);
-
-	g_io_channel_unref(att_io);
-
-	fd = open("/dev/null", O_WRONLY | O_CLOEXEC);
-	g_assert(fd > 0);
-
-	context->hog = bt_hog_new(fd, name, vendor, product, version, 0, NULL);
-	g_assert(context->hog);
-
-	channel = g_io_channel_unix_new(sv[1]);
-
-	g_io_channel_set_close_on_unref(channel, TRUE);
-	g_io_channel_set_encoding(channel, NULL, NULL);
-	g_io_channel_set_buffered(channel, FALSE);
-
-	context->source = g_io_add_watch(channel,
-				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-				test_handler, context);
-	g_assert(context->source > 0);
-
-	g_io_channel_unref(channel);
-
-	context->fd = sv[1];
-	context->data = data;
-
-	return context;
+	switch (cfg->action) {
+	case ACT_NONE:
+		break;
+	case ACT_NOTIFY:
+		/* Have the HID Device send the notifications */
+		tester_io_send();
+		break;
+	case ACT_GET_INPUT:
+		uhid_send(data, UHID_GET_REPORT, UHID_INPUT_REPORT);
+		break;
+	case ACT_GET_OUTPUT:
+		uhid_send(data, UHID_GET_REPORT, UHID_OUTPUT_REPORT);
+		break;
+	case ACT_GET_FEATURE:
+		uhid_send(data, UHID_GET_REPORT, UHID_FEATURE_REPORT);
+		break;
+	case ACT_SET_INPUT:
+		uhid_send(data, UHID_SET_REPORT, UHID_INPUT_REPORT);
+		break;
+	case ACT_SET_OUTPUT:
+		uhid_send(data, UHID_SET_REPORT, UHID_OUTPUT_REPORT);
+		break;
+	case ACT_SET_FEATURE:
+		uhid_send(data, UHID_SET_REPORT, UHID_FEATURE_REPORT);
+		break;
+	case ACT_OUTPUT:
+		uhid_send(data, UHID_OUTPUT, UHID_OUTPUT_REPORT);
+		break;
+	case ACT_SUSPEND:
+		g_assert_cmpint(bt_hog_set_control_point(data->hog, true), ==,
+									0);
+		break;
+	case ACT_RESUME:
+		g_assert_cmpint(bt_hog_set_control_point(data->hog, false), ==,
+									0);
+		break;
+	case ACT_DETACH:
+		bt_hog_detach(data->hog, true);
+		break;
+	case ACT_REATTACH:
+		/* Reconnect, the uHID device being destroyed meanwhile */
+		bt_hog_detach(data->hog, true);
+		g_assert(bt_hog_attach(data->hog, data->client));
+		break;
+	case ACT_SCI:
+		g_assert_cmpint(bt_hog_set_sci_mode(data->hog, cfg->mode[0]),
+								==, 0);
+		break;
+	}
 }
 
-static void test_hog(gconstpointer data)
+static void uhid_check(struct test_data *data, const struct uhid_event *ev)
 {
-	struct context *context = create_context(data);
+	const struct test_config *cfg = data->cfg;
+	const uint8_t *buf = NULL;
+	size_t len = 0;
 
-	g_assert(bt_hog_attach(context->hog, context->attrib));
+	if (ev->type != cfg->uhid_type)
+		return;
+
+	switch (ev->type) {
+	case UHID_INPUT2:
+		buf = ev->u.input2.data;
+		len = ev->u.input2.size;
+		break;
+	case UHID_GET_REPORT_REPLY:
+		g_assert_cmpint(ev->u.get_report_reply.id, ==, 1);
+		g_assert_cmpint(ev->u.get_report_reply.err, ==, 0);
+		buf = ev->u.get_report_reply.data;
+		len = ev->u.get_report_reply.size;
+		break;
+	case UHID_SET_REPORT_REPLY:
+		g_assert_cmpint(ev->u.set_report_reply.id, ==, 2);
+		g_assert_cmpint(ev->u.set_report_reply.err, ==, 0);
+		break;
+	}
+
+	if (cfg->uhid_data) {
+		g_assert_cmpint(len, ==, cfg->uhid_len);
+		g_assert(!memcmp(buf, cfg->uhid_data, len));
+	}
+
+	data->uhid_done = true;
+	test_done(data);
 }
 
+static bool uhid_read(struct io *io, void *user_data)
+{
+	struct test_data *data = user_data;
+	struct uhid_event ev;
+	ssize_t len;
+
+	len = read(io_get_fd(io), &ev, sizeof(ev));
+	if (len < 0)
+		return errno == EAGAIN;
+
+	g_assert_cmpint(len, ==, sizeof(ev));
+
+	if (ev.type == UHID_CREATE2) {
+		g_assert_cmpstr((char *) ev.u.create2.name, ==, "bluez-hog");
+		g_assert_cmpint(ev.u.create2.vendor, ==, 0x0002);
+		g_assert_cmpint(ev.u.create2.product, ==, 0x0001);
+		g_assert_cmpint(ev.u.create2.version, ==, 0x0001);
+		g_assert_cmpint(ev.u.create2.bus, ==, BUS_BLUETOOTH);
+		g_assert_cmpint(ev.u.create2.rd_size, ==, REPORT_MAP_LEN);
+
+		uhid_send(data, UHID_START, 0);
+
+		/* Once all instances are created, perform the action of the
+		 * test.
+		 */
+		if (++data->created == data->cfg->creates)
+			do_action(data);
+
+		return true;
+	}
+
+	uhid_check(data, &ev);
+
+	return true;
+}
+
+static void sci_mode_cb(uint8_t mode, void *user_data)
+{
+	struct test_data *data = user_data;
+	const struct test_config *cfg = data->cfg;
+
+	g_assert_cmpint(mode, ==, cfg->mode[data->mode_idx]);
+
+	/* Wait for the notification of every HID SCI Mode characteristic */
+	if (++data->notified < cfg->notifications)
+		return;
+
+	data->notified = 0;
+
+	/* Change to another mode, see HOGP.TS 4.6.1 step 6 */
+	if (!data->mode_idx && cfg->mode[1]) {
+		data->mode_idx++;
+		g_assert_cmpint(bt_hog_set_sci_mode(data->hog, cfg->mode[1]),
+								==, 0);
+		return;
+	}
+
+	data->sci_done = true;
+	test_done(data);
+}
+
+static void test_hog(const void *user_data)
+{
+	struct test_data *data = (void *) user_data;
+	struct io *io;
+	int fds[2];
+
+	io = tester_setup_io(data->iov, data->iovcnt);
+	g_assert(io);
+
+	tester_io_set_complete_func(test_complete_cb);
+
+	data->uhid_done = !data->cfg->uhid_type;
+	data->sci_done = !data->cfg->notifications;
+
+	g_assert(!socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK |
+						SOCK_CLOEXEC, 0, fds));
+
+	data->uhid_io = io_new(fds[1]);
+	g_assert(data->uhid_io);
+	io_set_close_on_destroy(data->uhid_io, true);
+	io_set_read_handler(data->uhid_io, uhid_read, data, NULL);
+
+	/* Not closed by bt_hog, see test_teardown */
+	data->uhid_fd = fds[0];
+
+	data->hog = bt_hog_new(fds[0], "bluez-hog", 0x0002, 0x0001, 0x0001,
+								0, NULL);
+	g_assert(data->hog);
+
+	bt_hog_set_debug(data->hog, print_debug, "bt_hog: ", NULL);
+	bt_hog_set_sci_mode_callback(data->hog, sci_mode_cb, data);
+
+	g_assert(bt_hog_attach(data->hog, data->client));
+}
+
+static void client_ready_cb(bool success, uint8_t att_ecode, void *user_data)
+{
+	if (!success) {
+		tester_setup_failed();
+		return;
+	}
+
+	tester_setup_complete();
+}
+
+static void test_setup(const void *user_data)
+{
+	struct test_data *data = (void *) user_data;
+	struct bt_att *att;
+	struct gatt_db *db;
+	struct io *io;
+
+	io = tester_setup_io(data->setup, data->setup_cnt);
+	g_assert(io);
+
+	att = bt_att_new(io_get_fd(io), false);
+	g_assert(att);
+
+	bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att: ", NULL);
+
+	db = gatt_db_new();
+	g_assert(db);
+
+	data->client = bt_gatt_client_new(db, att, HOG_GATT_CLIENT_MTU, 0);
+	g_assert(data->client);
+
+	bt_gatt_client_set_debug(data->client, print_debug,
+						"bt_gatt_client: ", NULL);
+
+	bt_gatt_client_ready_register(data->client, client_ready_cb, data,
+									NULL);
+
+	bt_att_unref(att);
+	gatt_db_unref(db);
+}
+
+static void test_teardown(const void *user_data)
+{
+	struct test_data *data = (void *) user_data;
+
+	bt_hog_unref(data->hog);
+	data->hog = NULL;
+	close(data->uhid_fd);
+	io_destroy(data->uhid_io);
+	data->uhid_io = NULL;
+	bt_gatt_client_unref(data->client);
+	data->client = NULL;
+
+	data->created = 0;
+	data->notified = 0;
+	data->mode_idx = 0;
+	data->io_done = false;
+
+	tester_teardown_complete();
+}
+
+/* ATT: Read Request (0x0a) / Read Response (0x0b) */
+#define READ(hnd, value...) \
+	IOV_DATA(0x0a, hnd, 0x00), \
+	IOV_DATA(0x0b, ##value)
+
+/* ATT: Write Request (0x12) / Write Response (0x13) */
+#define WRITE(hnd, value...) \
+	IOV_DATA(0x12, hnd, 0x00, value), \
+	IOV_DATA(0x13)
+
+/* ATT: Write Command (0x52), which has no response */
+#define WRITE_CMD(hnd, value...) \
+	IOV_DATA(0x52, hnd, 0x00, value), \
+	IOV_NULL
+
+/* ATT: Handle Value Notification (0x1b), following another PDU sent */
+#define NOTIFY(hnd, value...) \
+	IOV_NULL, \
+	IOV_DATA(0x1b, hnd, 0x00, value)
+
+#define REPORT_MAP \
+	0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, \
+	0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, \
+	0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, \
+	0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, \
+	0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, 0x95, 0x06, \
+	0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, \
+	0x29, 0x65, 0x81, 0x00, 0x09, 0x01, 0x95, 0x01, 0x75, 0x08, \
+	0xb1, 0x02, 0xc0
+
+#define REPORT_MAP_1 \
+	0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, \
+	0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, \
+	0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, \
+	0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, \
+	0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, 0x95, 0x06, \
+	0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, \
+	0x29, 0x65, 0x81
+
+#define REPORT_MAP_2 \
+	0x00, 0x09, 0x01, 0x95, 0x01, 0x75, 0x08, 0xb1, 0x02, 0xc0
+
+#define INPUT_REPORT	0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00
+
+/* Protocol Mode: Report Protocol Mode */
+#define READ_PROTO_MODE		READ(0x03, 0x01)
+/* External Report Reference: Battery Level, which is not a Report */
+#define READ_EXT_REPORT_REF	READ(0x06, 0x19, 0x2a)
+/* HID Information: bcdHID 1.11, NormallyConnectable */
+#define READ_INFO		READ(0x08, 0x11, 0x01, 0x00, 0x02)
+/* HID Information: bcdHID 1.11, NormallyConnectable, SCI and SCI Low
+ * Power mode supported.
+ */
+#define READ_INFO_SCI		READ(0x08, 0x11, 0x01, 0x00, 0x0e)
+#define READ_INPUT		READ(0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, \
+					0x00, 0x00, 0x00)
+#define READ_INPUT_REF		READ(0x0e, 0x01, 0x01)
+#define READ_OUTPUT		READ(0x10, 0x00)
+#define READ_OUTPUT_REF		READ(0x11, 0x01, 0x02)
+#define READ_FEATURE		READ(0x13, 0x00)
+#define READ_FEATURE_REF	READ(0x14, 0x01, 0x03)
+#define READ_INPUT_CCC		READ(0x0d, 0x00, 0x00)
+#define ENABLE_INPUT_CCC	WRITE(0x0d, 0x01, 0x00)
+#define DISABLE_INPUT_CCC	WRITE(0x0d, 0x00, 0x00)
+#define READ_SCI_MODE		READ(0x1c, 0x00)
+#define READ_SCI_INFO		READ(0x1f, 0x08, 0x01, 0x08, 0x00, 0x50, \
+					0x00, 0x08, 0x00)
+#define ENABLE_SCI_MODE_CCC	WRITE(0x1d, 0x01, 0x00)
+#define DISABLE_SCI_MODE_CCC	WRITE(0x1d, 0x00, 0x00)
+
+/* The Report Map is longer than the MTU so it is read with Read Blob */
+#define READ_REPORT_MAP \
+	IOV_DATA(0x0a, 0x05, 0x00), \
+	IOV_DATA(0x0b, REPORT_MAP_1), \
+	IOV_DATA(0x0c, 0x05, 0x00, 0x3f, 0x00), \
+	IOV_DATA(0x0d, REPORT_MAP_2)
+
+/* The Report Map is read last, once every report is known */
+#define ATTACH \
+	READ_PROTO_MODE, \
+	READ_EXT_REPORT_REF, \
+	READ_INFO, \
+	READ_INPUT, \
+	READ_INPUT_REF, \
+	READ_OUTPUT, \
+	READ_OUTPUT_REF, \
+	READ_FEATURE, \
+	READ_FEATURE_REF, \
+	READ_INPUT_CCC, \
+	ENABLE_INPUT_CCC, \
+	READ_REPORT_MAP
+
+/* HID SCI Mode notifications are enabled once SCI support is known */
+#define SCI_ATTACH \
+	READ_PROTO_MODE, \
+	READ_EXT_REPORT_REF, \
+	READ_INFO_SCI, \
+	READ_INPUT, \
+	READ_INPUT_REF, \
+	READ_OUTPUT, \
+	READ_OUTPUT_REF, \
+	READ_FEATURE, \
+	READ_FEATURE_REF, \
+	READ_SCI_MODE, \
+	READ_SCI_INFO, \
+	ENABLE_SCI_MODE_CCC, \
+	READ_INPUT_CCC, \
+	ENABLE_INPUT_CCC, \
+	READ_REPORT_MAP
+
+/* Protocol Mode in Boot Protocol Mode, set to Report Protocol Mode */
+#define BOOT_ATTACH \
+	READ(0x03, 0x00), \
+	WRITE_CMD(0x03, 0x01), \
+	READ_EXT_REPORT_REF, \
+	READ_INFO, \
+	READ_INPUT, \
+	READ_INPUT_REF, \
+	READ_OUTPUT, \
+	READ_OUTPUT_REF, \
+	READ_FEATURE, \
+	READ_FEATURE_REF, \
+	READ_INPUT_CCC, \
+	ENABLE_INPUT_CCC, \
+	READ_REPORT_MAP
+
+/* Enable a HID SCI mode, with no response other than the notification
+ * of the HID SCI Mode characteristics once the connection rate has been
+ * changed.
+ */
+#define MULTI_SCI_MODE(mode) \
+	WRITE_CMD(0x0a, mode), \
+	NOTIFY(0x1c, mode), \
+	NOTIFY(0x3c, mode)
+
+/* Discovery by bt_gatt_client */
+#define DISC_MTU \
+	IOV_DATA(0x02, 0x40, 0x00), \
+	IOV_DATA(0x03, 0x40, 0x00)
+
+#define DISC_SR_FEATURES \
+	IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b), \
+	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+#define DISC_SECONDARY \
+	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), \
+	IOV_DATA(0x01, 0x10, 0x01, 0x00, 0x0a)
+
+#define DISC_HID_CHRC_1 \
+	IOV_DATA(0x09, 0x07, 0x02, 0x00, 0x06, 0x03, 0x00, 0x4e, 0x2a, \
+			0x04, 0x00, 0x02, 0x05, 0x00, 0x4b, 0x2a, 0x07, \
+			0x00, 0x02, 0x08, 0x00, 0x4a, 0x2a, 0x09, 0x00, \
+			0x04, 0x0a, 0x00, 0x4c, 0x2a, 0x0b, 0x00, 0x12, \
+			0x0c, 0x00, 0x4d, 0x2a, 0x0f, 0x00, 0x0e, 0x10, \
+			0x00, 0x4d, 0x2a, 0x12, 0x00, 0x0a, 0x13, 0x00, \
+			0x4d, 0x2a, 0x15, 0x00, 0x12, 0x16, 0x00, 0x22, \
+			0x2a)
+
+#define DISC_HID_DESC \
+	IOV_DATA(0x04, 0x06, 0x00, 0x06, 0x00), \
+	IOV_DATA(0x05, 0x01, 0x06, 0x00, 0x07, 0x29), \
+	IOV_DATA(0x04, 0x0d, 0x00, 0x0e, 0x00), \
+	IOV_DATA(0x05, 0x01, 0x0d, 0x00, 0x02, 0x29, 0x0e, 0x00, 0x08, \
+			0x29), \
+	IOV_DATA(0x04, 0x11, 0x00, 0x11, 0x00), \
+	IOV_DATA(0x05, 0x01, 0x11, 0x00, 0x08, 0x29), \
+	IOV_DATA(0x04, 0x14, 0x00, 0x14, 0x00), \
+	IOV_DATA(0x05, 0x01, 0x14, 0x00, 0x08, 0x29)
+
+static const struct iovec setup_basic[] = {
+	DISC_MTU,
+	DISC_SR_FEATURES,
+	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
+	IOV_DATA(0x11, 0x06, 0x01, 0x00, 0x1a, 0x00, 0x12, 0x18, 0x1b,
+			0x00, 0x1e, 0x00, 0x0f, 0x18),
+	IOV_DATA(0x10, 0x1f, 0x00, 0xff, 0xff, 0x00, 0x28),
+	IOV_DATA(0x01, 0x10, 0x1f, 0x00, 0x0a),
+	DISC_SECONDARY,
+	IOV_DATA(0x08, 0x01, 0x00, 0x1e, 0x00, 0x02, 0x28),
+	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a),
+	IOV_DATA(0x08, 0x01, 0x00, 0x1e, 0x00, 0x03, 0x28),
+	DISC_HID_CHRC_1,
+	IOV_DATA(0x08, 0x16, 0x00, 0x1e, 0x00, 0x03, 0x28),
+	IOV_DATA(0x09, 0x07, 0x18, 0x00, 0x12, 0x19, 0x00, 0x33, 0x2a,
+			0x1c, 0x00, 0x12, 0x1d, 0x00, 0x19, 0x2a),
+	IOV_DATA(0x08, 0x1d, 0x00, 0x1e, 0x00, 0x03, 0x28),
+	IOV_DATA(0x01, 0x08, 0x1d, 0x00, 0x0a),
+	DISC_HID_DESC,
+};
+
+static const struct iovec setup_sci[] = {
+	DISC_MTU,
+	DISC_SR_FEATURES,
+	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
+	IOV_DATA(0x11, 0x06, 0x01, 0x00, 0x20, 0x00, 0x12, 0x18, 0x21,
+			0x00, 0x24, 0x00, 0x0f, 0x18),
+	IOV_DATA(0x10, 0x25, 0x00, 0xff, 0xff, 0x00, 0x28),
+	IOV_DATA(0x01, 0x10, 0x25, 0x00, 0x0a),
+	DISC_SECONDARY,
+	IOV_DATA(0x08, 0x01, 0x00, 0x24, 0x00, 0x02, 0x28),
+	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a),
+	IOV_DATA(0x08, 0x01, 0x00, 0x24, 0x00, 0x03, 0x28),
+	DISC_HID_CHRC_1,
+	IOV_DATA(0x08, 0x16, 0x00, 0x24, 0x00, 0x03, 0x28),
+	IOV_DATA(0x09, 0x07, 0x18, 0x00, 0x12, 0x19, 0x00, 0x33, 0x2a,
+			0x1b, 0x00, 0x12, 0x1c, 0x00, 0x39, 0x2c, 0x1e,
+			0x00, 0x02, 0x1f, 0x00, 0x3a, 0x2c, 0x22, 0x00,
+			0x12, 0x23, 0x00, 0x19, 0x2a),
+	IOV_DATA(0x08, 0x23, 0x00, 0x24, 0x00, 0x03, 0x28),
+	IOV_DATA(0x01, 0x08, 0x23, 0x00, 0x0a),
+	DISC_HID_DESC,
+	IOV_DATA(0x04, 0x20, 0x00, 0x20, 0x00),
+	IOV_DATA(0x01, 0x04, 0x20, 0x00, 0x0a),
+};
+
+/* Two instances of the HID Service, with SCI support */
+static const struct iovec setup_multi[] = {
+	IOV_DATA(0x02, 0x40, 0x00),
+	IOV_DATA(0x03, 0x40, 0x00),
+	IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b),
+	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a),
+	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
+	IOV_DATA(0x11, 0x06, 0x01, 0x00, 0x20, 0x00, 0x12, 0x18, 0x21,
+			0x00, 0x40, 0x00, 0x12, 0x18, 0x41, 0x00, 0x44,
+			0x00, 0x0f, 0x18),
+	IOV_DATA(0x10, 0x45, 0x00, 0xff, 0xff, 0x00, 0x28),
+	IOV_DATA(0x01, 0x10, 0x45, 0x00, 0x0a),
+	IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28),
+	IOV_DATA(0x01, 0x10, 0x01, 0x00, 0x0a),
+	IOV_DATA(0x08, 0x01, 0x00, 0x44, 0x00, 0x02, 0x28),
+	IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a),
+	IOV_DATA(0x08, 0x01, 0x00, 0x44, 0x00, 0x03, 0x28),
+	IOV_DATA(0x09, 0x07, 0x02, 0x00, 0x06, 0x03, 0x00, 0x4e, 0x2a,
+			0x04, 0x00, 0x02, 0x05, 0x00, 0x4b, 0x2a, 0x07,
+			0x00, 0x02, 0x08, 0x00, 0x4a, 0x2a, 0x09, 0x00,
+			0x04, 0x0a, 0x00, 0x4c, 0x2a, 0x0b, 0x00, 0x12,
+			0x0c, 0x00, 0x4d, 0x2a, 0x0f, 0x00, 0x0e, 0x10,
+			0x00, 0x4d, 0x2a, 0x12, 0x00, 0x0a, 0x13, 0x00,
+			0x4d, 0x2a, 0x15, 0x00, 0x12, 0x16, 0x00, 0x22,
+			0x2a),
+	IOV_DATA(0x08, 0x16, 0x00, 0x44, 0x00, 0x03, 0x28),
+	IOV_DATA(0x09, 0x07, 0x18, 0x00, 0x12, 0x19, 0x00, 0x33, 0x2a,
+			0x1b, 0x00, 0x12, 0x1c, 0x00, 0x39, 0x2c, 0x1e,
+			0x00, 0x02, 0x1f, 0x00, 0x3a, 0x2c, 0x22, 0x00,
+			0x06, 0x23, 0x00, 0x4e, 0x2a, 0x24, 0x00, 0x02,
+			0x25, 0x00, 0x4b, 0x2a, 0x27, 0x00, 0x02, 0x28,
+			0x00, 0x4a, 0x2a, 0x29, 0x00, 0x04, 0x2a, 0x00,
+			0x4c, 0x2a, 0x2b, 0x00, 0x12, 0x2c, 0x00, 0x4d,
+			0x2a),
+	IOV_DATA(0x08, 0x2c, 0x00, 0x44, 0x00, 0x03, 0x28),
+	IOV_DATA(0x09, 0x07, 0x2f, 0x00, 0x0e, 0x30, 0x00, 0x4d, 0x2a,
+			0x32, 0x00, 0x0a, 0x33, 0x00, 0x4d, 0x2a, 0x35,
+			0x00, 0x12, 0x36, 0x00, 0x22, 0x2a, 0x38, 0x00,
+			0x12, 0x39, 0x00, 0x33, 0x2a, 0x3b, 0x00, 0x12,
+			0x3c, 0x00, 0x39, 0x2c, 0x3e, 0x00, 0x02, 0x3f,
+			0x00, 0x3a, 0x2c, 0x42, 0x00, 0x12, 0x43, 0x00,
+			0x19, 0x2a),
+	IOV_DATA(0x08, 0x43, 0x00, 0x44, 0x00, 0x03, 0x28),
+	IOV_DATA(0x01, 0x08, 0x43, 0x00, 0x0a),
+	IOV_DATA(0x04, 0x06, 0x00, 0x06, 0x00),
+	IOV_DATA(0x05, 0x01, 0x06, 0x00, 0x07, 0x29),
+	IOV_DATA(0x04, 0x0d, 0x00, 0x0e, 0x00),
+	IOV_DATA(0x05, 0x01, 0x0d, 0x00, 0x02, 0x29, 0x0e, 0x00, 0x08,
+			0x29),
+	IOV_DATA(0x04, 0x11, 0x00, 0x11, 0x00),
+	IOV_DATA(0x05, 0x01, 0x11, 0x00, 0x08, 0x29),
+	IOV_DATA(0x04, 0x14, 0x00, 0x14, 0x00),
+	IOV_DATA(0x05, 0x01, 0x14, 0x00, 0x08, 0x29),
+	IOV_DATA(0x04, 0x20, 0x00, 0x20, 0x00),
+	IOV_DATA(0x01, 0x04, 0x20, 0x00, 0x0a),
+	IOV_DATA(0x04, 0x26, 0x00, 0x26, 0x00),
+	IOV_DATA(0x05, 0x01, 0x26, 0x00, 0x07, 0x29),
+	IOV_DATA(0x04, 0x2d, 0x00, 0x2e, 0x00),
+	IOV_DATA(0x05, 0x01, 0x2d, 0x00, 0x02, 0x29, 0x2e, 0x00, 0x08,
+			0x29),
+	IOV_DATA(0x04, 0x31, 0x00, 0x31, 0x00),
+	IOV_DATA(0x05, 0x01, 0x31, 0x00, 0x08, 0x29),
+	IOV_DATA(0x04, 0x34, 0x00, 0x34, 0x00),
+	IOV_DATA(0x05, 0x01, 0x34, 0x00, 0x08, 0x29),
+	IOV_DATA(0x04, 0x40, 0x00, 0x40, 0x00),
+	IOV_DATA(0x01, 0x04, 0x40, 0x00, 0x0a),
+};
+
+#define MULTI_ATTACH \
+	IOV_DATA(0x0a, 0x23, 0x00), \
+	IOV_DATA(0x0b, 0x01), \
+	IOV_DATA(0x0a, 0x26, 0x00), \
+	IOV_DATA(0x0b, 0x19, 0x2a), \
+	IOV_DATA(0x0a, 0x28, 0x00), \
+	IOV_DATA(0x0b, 0x11, 0x01, 0x00, 0x0e), \
+	IOV_DATA(0x0a, 0x2c, 0x00), \
+	IOV_DATA(0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), \
+	IOV_DATA(0x0a, 0x2e, 0x00), \
+	IOV_DATA(0x0b, 0x01, 0x01), \
+	IOV_DATA(0x0a, 0x30, 0x00), \
+	IOV_DATA(0x0b, 0x00), \
+	IOV_DATA(0x0a, 0x31, 0x00), \
+	IOV_DATA(0x0b, 0x01, 0x02), \
+	IOV_DATA(0x0a, 0x33, 0x00), \
+	IOV_DATA(0x0b, 0x00), \
+	IOV_DATA(0x0a, 0x34, 0x00), \
+	IOV_DATA(0x0b, 0x01, 0x03), \
+	IOV_DATA(0x0a, 0x03, 0x00), \
+	IOV_DATA(0x0b, 0x01), \
+	IOV_DATA(0x0a, 0x06, 0x00), \
+	IOV_DATA(0x0b, 0x19, 0x2a), \
+	IOV_DATA(0x0a, 0x08, 0x00), \
+	IOV_DATA(0x0b, 0x11, 0x01, 0x00, 0x0e), \
+	IOV_DATA(0x0a, 0x0c, 0x00), \
+	IOV_DATA(0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), \
+	IOV_DATA(0x0a, 0x0e, 0x00), \
+	IOV_DATA(0x0b, 0x01, 0x01), \
+	IOV_DATA(0x0a, 0x10, 0x00), \
+	IOV_DATA(0x0b, 0x00), \
+	IOV_DATA(0x0a, 0x11, 0x00), \
+	IOV_DATA(0x0b, 0x01, 0x02), \
+	IOV_DATA(0x0a, 0x13, 0x00), \
+	IOV_DATA(0x0b, 0x00), \
+	IOV_DATA(0x0a, 0x14, 0x00), \
+	IOV_DATA(0x0b, 0x01, 0x03), \
+	IOV_DATA(0x0a, 0x3c, 0x00), \
+	IOV_DATA(0x0b, 0x00), \
+	IOV_DATA(0x0a, 0x3f, 0x00), \
+	IOV_DATA(0x0b, 0x08, 0x01, 0x08, 0x00, 0x50, 0x00, 0x08, 0x00), \
+	IOV_DATA(0x12, 0x3d, 0x00, 0x01, 0x00), \
+	IOV_DATA(0x13), \
+	IOV_DATA(0x0a, 0x2d, 0x00), \
+	IOV_DATA(0x0b, 0x00, 0x00), \
+	IOV_DATA(0x0a, 0x1c, 0x00), \
+	IOV_DATA(0x0b, 0x00), \
+	IOV_DATA(0x0a, 0x1f, 0x00), \
+	IOV_DATA(0x0b, 0x08, 0x01, 0x08, 0x00, 0x50, 0x00, 0x08, 0x00), \
+	IOV_DATA(0x12, 0x1d, 0x00, 0x01, 0x00), \
+	IOV_DATA(0x13), \
+	IOV_DATA(0x0a, 0x0d, 0x00), \
+	IOV_DATA(0x0b, 0x00, 0x00), \
+	IOV_DATA(0x12, 0x2d, 0x00, 0x01, 0x00), \
+	IOV_DATA(0x13), \
+	IOV_DATA(0x0a, 0x25, 0x00), \
+	IOV_DATA(0x0b, 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, \
+			0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, \
+			0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, \
+			0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05, \
+			0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, \
+			0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, \
+			0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, \
+			0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81), \
+	IOV_DATA(0x0c, 0x25, 0x00, 0x3f, 0x00), \
+	IOV_DATA(0x0d, 0x00, 0x09, 0x01, 0x95, 0x01, 0x75, 0x08, 0xb1, \
+			0x02, 0xc0), \
+	IOV_DATA(0x12, 0x0d, 0x00, 0x01, 0x00), \
+	IOV_DATA(0x13), \
+	IOV_DATA(0x0a, 0x05, 0x00), \
+	IOV_DATA(0x0b, 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, \
+			0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, \
+			0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, \
+			0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05, \
+			0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, \
+			0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01, \
+			0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, \
+			0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81), \
+	IOV_DATA(0x0c, 0x05, 0x00, 0x3f, 0x00), \
+	IOV_DATA(0x0d, 0x00, 0x09, 0x01, 0x95, 0x01, 0x75, 0x08, 0xb1, \
+			0x02, 0xc0)
+
+static const uint8_t input_report[] = { 0x01, INPUT_REPORT };
+static const uint8_t input_value[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+					0x00, 0x00, 0x00 };
+static const uint8_t output_report[] = { 0x01, 0x00 };
+static const uint8_t feature_report[] = { 0x01, 0x00 };
+
+static const struct test_config cfg_none = {
+	.action = ACT_NONE,
+};
+
+static const struct test_config cfg_attach = {
+	.action = ACT_NONE,
+	.creates = 1,
+};
+
+static const struct test_config cfg_multi_attach = {
+	.action = ACT_NONE,
+	.creates = 2,
+};
+
+static const struct test_config cfg_notify = {
+	.action = ACT_NOTIFY,
+	.creates = 1,
+	.uhid_type = UHID_INPUT2,
+	.uhid_data = input_report,
+	.uhid_len = sizeof(input_report),
+};
+
+static const struct test_config cfg_get_input = {
+	.action = ACT_GET_INPUT,
+	.creates = 1,
+	.uhid_type = UHID_GET_REPORT_REPLY,
+	.uhid_data = input_value,
+	.uhid_len = sizeof(input_value),
+};
+
+static const struct test_config cfg_get_output = {
+	.action = ACT_GET_OUTPUT,
+	.creates = 1,
+	.uhid_type = UHID_GET_REPORT_REPLY,
+	.uhid_data = output_report,
+	.uhid_len = sizeof(output_report),
+};
+
+static const struct test_config cfg_get_feature = {
+	.action = ACT_GET_FEATURE,
+	.creates = 1,
+	.uhid_type = UHID_GET_REPORT_REPLY,
+	.uhid_data = feature_report,
+	.uhid_len = sizeof(feature_report),
+};
+
+static const struct test_config cfg_set_input = {
+	.action = ACT_SET_INPUT,
+	.creates = 1,
+	.uhid_type = UHID_SET_REPORT_REPLY,
+};
+
+static const struct test_config cfg_set_output = {
+	.action = ACT_SET_OUTPUT,
+	.creates = 1,
+	.uhid_type = UHID_SET_REPORT_REPLY,
+};
+
+static const struct test_config cfg_set_feature = {
+	.action = ACT_SET_FEATURE,
+	.creates = 1,
+	.uhid_type = UHID_SET_REPORT_REPLY,
+};
+
+static const struct test_config cfg_output = {
+	.action = ACT_OUTPUT,
+	.creates = 1,
+};
+
+static const struct test_config cfg_suspend = {
+	.action = ACT_SUSPEND,
+	.creates = 1,
+};
+
+static const struct test_config cfg_resume = {
+	.action = ACT_RESUME,
+	.creates = 1,
+};
+
+static const struct test_config cfg_detach = {
+	.action = ACT_DETACH,
+	.creates = 1,
+};
+
+static const struct test_config cfg_reattach = {
+	.action = ACT_REATTACH,
+	.creates = 1,
+	.uhid_type = UHID_INPUT2,
+	.uhid_data = input_report,
+	.uhid_len = sizeof(input_report),
+};
+
+#define define_cfg_sci(_name, _mode, _mode2, _notifications) \
+static const struct test_config _name = { \
+	.action = ACT_SCI, \
+	.creates = 2, \
+	.mode = { _mode, _mode2 }, \
+	.notifications = _notifications, \
+}
+
+/* HOGP.TS 4.6.1: change from the tested mode to Full Range if Default,
+ * otherwise to Default.
+ */
+define_cfg_sci(cfg_sci_default, BT_HOG_SCI_MODE_DEFAULT,
+				BT_HOG_SCI_MODE_FULL_RANGE, 2);
+define_cfg_sci(cfg_sci_fast, BT_HOG_SCI_MODE_FAST,
+				BT_HOG_SCI_MODE_DEFAULT, 2);
+define_cfg_sci(cfg_sci_low_power, BT_HOG_SCI_MODE_LOW_POWER,
+				BT_HOG_SCI_MODE_DEFAULT, 2);
+define_cfg_sci(cfg_sci_full_range, BT_HOG_SCI_MODE_FULL_RANGE,
+				BT_HOG_SCI_MODE_DEFAULT, 2);
+
+static const struct test_config cfg_sci_attach = {
+	.action = ACT_NONE,
+	.creates = 1,
+};
+
 int main(int argc, char *argv[])
 {
 	tester_init(&argc, &argv);
 
-	define_test("/TP/HGRF/RH/BV-01-I", test_hog,
-		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x04, 0x00, 0x12,
-			0x18, 0x05, 0x00, 0x08, 0x00, 0x12, 0x18),
-		raw_pdu(0x10, 0x09, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x01, 0x10, 0x09, 0x00, 0x0a),
-		raw_pdu(0x08, 0x01, 0x00, 0x04, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x03, 0x00, 0x02, 0x04, 0x00,
-			0x4b, 0x2a),
-		raw_pdu(0x08, 0x01, 0x00, 0x04, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a),
-		raw_pdu(0x08, 0x05, 0x00, 0x08, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x05, 0x00, 0x0a),
-		raw_pdu(0x08, 0x05, 0x00, 0x08, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x07, 0x00, 0x02, 0x08, 0x00,
-			0x4b, 0x2a),
-		raw_pdu(0x0a, 0x04, 0x00),
-		raw_pdu(0x0b, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-			0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
-			0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
-			0x15, 0x16),
-		raw_pdu(0x0c, 0x04, 0x00, 0x16, 0x00),
-		raw_pdu(0x0d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-			0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
-			0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
-			0x15, 0x16),
-		raw_pdu(0x0c, 0x04, 0x00, 0x2c, 0x00),
-		raw_pdu(0x0d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-			0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
-			0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13),
-		raw_pdu(0x0a, 0x08, 0x00),
-		raw_pdu(0x0b, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-			0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
-			0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
-			0x15, 0x16),
-		raw_pdu(0x0c, 0x08, 0x00, 0x16, 0x00),
-		raw_pdu(0x0d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-			0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
-			0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
-			0x15, 0x16),
-		raw_pdu(0x0c, 0x08, 0x00, 0x2c, 0x00),
-		raw_pdu(0x0d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-			0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
-			0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13));
+	/* Discovery */
+	define_test("HOGP/RH/HGDC/BV-03-C [Discover Report Characteristics]",
+			setup_multi, &cfg_multi_attach,
+			MULTI_ATTACH);
+	define_test("HOGP/RH/HGDC/BV-04-C [Discover Report Characteristic "
+			"Client Characteristic Configuration Descriptors]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF, READ_FEATURE, READ_FEATURE_REF,
+			READ_INPUT_CCC);
+	define_test("HOGP/RH/HGDC/BV-05-C [Discover Report Characteristic "
+			"Report Reference Characteristic Descriptors]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF, READ_FEATURE, READ_FEATURE_REF);
 
-	define_test("/TP/HGRF/RH/BV-08-I", test_hog,
-		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x05, 0x00, 0x12,
-			0x18, 0x06, 0x00, 0x0a, 0x00, 0x12, 0x18),
-		raw_pdu(0x10, 0x0b, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x01, 0x10, 0x0b, 0x00, 0x0a),
-		raw_pdu(0x08, 0x01, 0x00, 0x05, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x03, 0x00, 0x0a, 0x04, 0x00,
-			0x4d, 0x2a),
-		raw_pdu(0x08, 0x01, 0x00, 0x05, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a),
-		raw_pdu(0x08, 0x06, 0x00, 0x0a, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x06, 0x00, 0x0a),
-		raw_pdu(0x08, 0x06, 0x00, 0x0a, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x08, 0x00, 0x0a, 0x09, 0x00,
-			0x4d, 0x2a),
-		raw_pdu(0x08, 0x04, 0x00, 0x05, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x04, 0x00, 0x0a),
-		raw_pdu(0x08, 0x09, 0x00, 0x0a, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x09, 0x00, 0x0a),
-		raw_pdu(0x0a, 0x04, 0x00),
-		raw_pdu(0x0b, 0xee, 0xee, 0xff, 0xff),
-		raw_pdu(0x04, 0x05, 0x00, 0x05, 0x00),
-		raw_pdu(0x05, 0x01, 0x05, 0x00, 0x08, 0x29),
-		raw_pdu(0x0a, 0x09, 0x00),
-		raw_pdu(0x0b, 0xff, 0xff, 0xee, 0xee),
-		raw_pdu(0x04, 0x0a, 0x00, 0x0a, 0x00),
-		raw_pdu(0x05, 0x01, 0x0a, 0x00, 0x08, 0x29),
-		raw_pdu(0x0a, 0x05, 0x00),
-		raw_pdu(0x0b, 0x01, 0x03),
-		raw_pdu(0x0a, 0x0a, 0x00),
-		raw_pdu(0x0b, 0x02, 0x03));
+	/* Read */
+	define_test("HOGP/RH/HGRF/BV-02-C [Read External Report Reference "
+			"Characteristic Descriptors for Report Map]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF);
+	define_test("HOGP/RH/HGRF/BV-03-C [Read Report Characteristics - "
+			"Input Report]",
+			setup_basic, &cfg_get_input,
+			ATTACH, READ_INPUT);
+	define_test("HOGP/RH/HGRF/BV-04-C [Read Report Reference "
+			"Characteristic Descriptors for Report Characteristics "
+			"- Input Report]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF);
+	define_test("HOGP/RH/HGRF/BV-05-C [Read Client Characteristic "
+			"Configuration Descriptors for Report Characteristics "
+			"- Input Report]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF, READ_FEATURE, READ_FEATURE_REF,
+			READ_INPUT_CCC);
+	define_test("HOGP/RH/HGRF/BV-19-C [Read Report Characteristics - "
+			"Output Report]",
+			setup_basic, &cfg_get_output,
+			ATTACH, READ_OUTPUT);
+	define_test("HOGP/RH/HGRF/BV-06-C [Read Report Reference "
+			"Characteristic Descriptors for Report Characteristics "
+			"- Output Report]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF);
+	define_test("HOGP/RH/HGRF/BV-07-C [Read Report Characteristics - "
+			"Feature Report]",
+			setup_basic, &cfg_get_feature,
+			ATTACH, READ_FEATURE);
+	define_test("HOGP/RH/HGRF/BV-08-C [Read Report Reference "
+			"Characteristic Descriptors for Report Characteristics "
+			"- Feature Report]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF, READ_FEATURE, READ_FEATURE_REF);
+	define_test("HOGP/RH/HGRF/BV-18-C [Read Protocol Mode Characteristics "
+			"(Get Boot Protocol Mode Command) - RH]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE);
 
-	define_test("/TP/HGRF/RH/BV-09-I", test_hog,
-		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x04, 0x00, 0x12,
-			0x18, 0x05, 0x00, 0x08, 0x00, 0x12, 0x18),
-		raw_pdu(0x10, 0x09, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x01, 0x10, 0x09, 0x00, 0x0a),
-		raw_pdu(0x08, 0x01, 0x00, 0x04, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x03, 0x00, 0x02, 0x04, 0x00,
-			0x4a, 0x2a),
-		raw_pdu(0x08, 0x01, 0x00, 0x04, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a),
-		raw_pdu(0x08, 0x05, 0x00, 0x08, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x05, 0x00, 0x0a),
-		raw_pdu(0x08, 0x05, 0x00, 0x08, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x07, 0x00, 0x02, 0x08, 0x00,
-			0x4a, 0x2a),
-		raw_pdu(0x0a, 0x04, 0x00),
-		raw_pdu(0x0b, 0x01, 0x11, 0x00, 0x01),
-		raw_pdu(0x0a, 0x08, 0x00),
-		raw_pdu(0x0b, 0x01, 0x11, 0x00, 0x01));
+	/* Write */
+	define_test("HOGP/RH/HGWF/BV-01-C [Write Report Characteristics - "
+			"Input Report]",
+			setup_basic, &cfg_set_input,
+			ATTACH, WRITE(0x0c, 0x42));
+	define_test("HOGP/RH/HGWF/BV-02-C [Write Report Characteristics - "
+			"Output Report]",
+			setup_basic, &cfg_output,
+			ATTACH, WRITE(0x10, 0x07));
+	define_test("HOGP/RH/HGWF/BV-02-C [Write Report Characteristics - "
+			"Output Report] - Set Report",
+			setup_basic, &cfg_set_output,
+			ATTACH, WRITE(0x10, 0x42));
+	define_test("HOGP/RH/HGWF/BV-04-C [Write Report Characteristics - "
+			"Feature Report]",
+			setup_basic, &cfg_set_feature,
+			ATTACH, WRITE(0x13, 0x42));
+	define_test("HOGP/RH/HGWF/BV-05-C [Write HID Control Point "
+			"Characteristics - Suspend]",
+			setup_basic, &cfg_suspend,
+			ATTACH, IOV_DATA(0x52, 0x0a, 0x00, 0x00));
+	define_test("HOGP/RH/HGWF/BV-06-C [Write HID Control Point "
+			"Characteristics - Exit Suspend]",
+			setup_basic, &cfg_resume,
+			ATTACH, IOV_DATA(0x52, 0x0a, 0x00, 0x01));
+	define_test("HOGP/RH/HGWF/BV-07-C [Write Protocol Mode "
+			"Characteristics - Set Protocol Command (Protocol Mode "
+			"= Report Protocol Mode)]",
+			setup_basic, &cfg_attach,
+			BOOT_ATTACH);
 
-	define_test("/TP/HGRF/RH/BV-06-I", test_hog,
-		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x05, 0x00, 0x12,
-			0x18, 0x06, 0x00, 0x0a, 0x00, 0x12, 0x18),
-		raw_pdu(0x10, 0x0b, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x01, 0x10, 0x0b, 0x00, 0x0a),
-		raw_pdu(0x08, 0x01, 0x00, 0x05, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x03, 0x00, 0x0a, 0x04, 0x00,
-			0x4d, 0x2a),
-		raw_pdu(0x08, 0x01, 0x00, 0x05, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a),
-		raw_pdu(0x08, 0x06, 0x00, 0x0a, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x06, 0x00, 0x0a),
-		raw_pdu(0x08, 0x06, 0x00, 0x0a, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x08, 0x00, 0x0a, 0x09, 0x00,
-			0x4d, 0x2a),
-		raw_pdu(0x08, 0x04, 0x00, 0x05, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x05, 0x00, 0x0a),
-		raw_pdu(0x08, 0x09, 0x00, 0x0a, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x09, 0x00, 0x0a),
-		raw_pdu(0x0a, 0x04, 0x00),
-		raw_pdu(0x0b, 0xee, 0xee, 0xff, 0xff),
-		raw_pdu(0x04, 0x05, 0x00, 0x05, 0x00),
-		raw_pdu(0x05, 0x01, 0x05, 0x00, 0x08, 0x29),
-		raw_pdu(0x0a, 0x09, 0x00),
-		raw_pdu(0x0b, 0xff, 0xff, 0xee, 0xee),
-		raw_pdu(0x04, 0x0a, 0x00, 0x0a, 0x00),
-		raw_pdu(0x05, 0x01, 0x0a, 0x00, 0x08, 0x29),
-		raw_pdu(0x0a, 0x05, 0x00),
-		raw_pdu(0x0b, 0x01, 0x02),
-		raw_pdu(0x0a, 0x0a, 0x00),
-		raw_pdu(0x0b, 0x02, 0x02));
+	/* HID SCI, see HOGP.TS 4.6.1 */
+	define_test("HOGP/RH/HGWF/BV-08-C [Write HID Control Point "
+			"Characteristic, Enable SCI Default mode]",
+			setup_multi, &cfg_sci_default,
+			MULTI_ATTACH,
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_DEFAULT),
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_FULL_RANGE));
+	define_test("HOGP/RH/HGWF/BV-09-C [Write HID Control Point "
+			"Characteristic, Enable SCI Fast mode]",
+			setup_multi, &cfg_sci_fast,
+			MULTI_ATTACH,
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_FAST),
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_DEFAULT));
+	define_test("HOGP/RH/HGWF/BV-10-C [Write HID Control Point "
+			"Characteristic, Enable SCI Low Power mode]",
+			setup_multi, &cfg_sci_low_power,
+			MULTI_ATTACH,
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_LOW_POWER),
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_DEFAULT));
+	define_test("HOGP/RH/HGWF/BV-11-C [Write HID Control Point "
+			"Characteristic, Enable SCI Full Range mode]",
+			setup_multi, &cfg_sci_full_range,
+			MULTI_ATTACH,
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_FULL_RANGE),
+			MULTI_SCI_MODE(BT_HOG_SCI_MODE_DEFAULT));
 
-	define_test("/TP/HGCF/RH/BV-01-I", test_hog,
-		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x06, 0x00, 0x12,
-			0x18, 0x07, 0x00, 0x0c, 0x00, 0x12, 0x18),
-		raw_pdu(0x10, 0x0d, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x01, 0x10, 0x0d, 0x00, 0x0a),
-		raw_pdu(0x08, 0x01, 0x00, 0x06, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x03, 0x00, 0x1a, 0x04, 0x00,
-			0x4d, 0x2a),
-		raw_pdu(0x08, 0x01, 0x00, 0x06, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a),
-		raw_pdu(0x08, 0x07, 0x00, 0x0c, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x07, 0x00, 0x0a),
-		raw_pdu(0x08, 0x07, 0x00, 0x0c, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x09, 0x00, 0x1a, 0x0a, 0x00,
-			0x4d, 0x2a),
-		raw_pdu(0x08, 0x04, 0x00, 0x06, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x04, 0x00, 0x0a),
-		raw_pdu(0x08, 0x0a, 0x00, 0x0c, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x0a, 0x00, 0x0a),
-		raw_pdu(0x0a, 0x04, 0x00),
-		raw_pdu(0x0b, 0xed, 0x00),
-		raw_pdu(0x04, 0x05, 0x00, 0x06, 0x00),
-		raw_pdu(0x05, 0x01, 0x05, 0x00, 0x02, 0x29,
-			0x06, 0x00, 0x08, 0x29),
-		raw_pdu(0x0a, 0x0a, 0x00),
-		raw_pdu(0x0b, 0xed, 0x00),
-		raw_pdu(0x04, 0x0b, 0x00, 0x0c, 0x00),
-		raw_pdu(0x05, 0x01, 0x0b, 0x00, 0x02, 0x29,
-			0x0c, 0x00, 0x08, 0x29),
-		raw_pdu(0x0a, 0x06, 0x00),
-		raw_pdu(0x0b, 0x01, 0x01),
-		raw_pdu(0x0a, 0x0c, 0x00),
-		raw_pdu(0x0b, 0x02, 0x01),
-		raw_pdu(0x0a, 0x05, 0x00),
-		raw_pdu(0x0b, 0x00, 0x00),
-		raw_pdu(0x0a, 0x0b, 0x00),
-		raw_pdu(0x0b, 0x00, 0x00),
-		raw_pdu(0x12, 0x05, 0x00, 0x01, 0x00),
-		raw_pdu(0x13),
-		raw_pdu(0x12, 0x0b, 0x00, 0x01, 0x00),
-		raw_pdu(0x13));
+	/* Configuration */
+	define_test("HOGP/RH/HGCF/BV-01-C [Report Characteristic - Input "
+			"Reports - enable notifications (write with 0x0001)]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF, READ_FEATURE, READ_FEATURE_REF,
+			READ_INPUT_CCC, ENABLE_INPUT_CCC);
+	define_test("HOGP/RH/HGCF/BV-02-C [Report Characteristic - Input "
+			"Reports - disable notifications (write with 0x0000)]",
+			setup_basic, &cfg_detach,
+			ATTACH, DISABLE_INPUT_CCC);
 
-	define_test("/TP/HGRF/RH/BV-02-I", test_hog,
-		raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x11, 0x06, 0x01, 0x00, 0x05, 0x00, 0x12,
-			0x18, 0x06, 0x00, 0x0a, 0x00, 0x12, 0x18),
-		raw_pdu(0x10, 0x0b, 0x00, 0xff, 0xff, 0x00, 0x28),
-		raw_pdu(0x01, 0x10, 0x0b, 0x00, 0x0a),
-		raw_pdu(0x08, 0x01, 0x00, 0x05, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x03, 0x00, 0x02, 0x04, 0x00,
-			0x4b, 0x2a),
-		raw_pdu(0x08, 0x01, 0x00, 0x05, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a),
-		raw_pdu(0x08, 0x06, 0x00, 0x0a, 0x00, 0x02, 0x28),
-		raw_pdu(0x01, 0x08, 0x06, 0x00, 0x0a),
-		raw_pdu(0x08, 0x06, 0x00, 0x0a, 0x00, 0x03, 0x28),
-		raw_pdu(0x09, 0x07, 0x08, 0x00, 0x02, 0x09, 0x00,
-			0x4b, 0x2a),
-		raw_pdu(0x08, 0x04, 0x00, 0x05, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x04, 0x00, 0x0a),
-		raw_pdu(0x08, 0x09, 0x00, 0x0a, 0x00, 0x03, 0x28),
-		raw_pdu(0x01, 0x08, 0x09, 0x00, 0x0a),
-		raw_pdu(0x0a, 0x04, 0x00),
-		raw_pdu(0x0b, 0x01, 0x02, 0x03),
-		raw_pdu(0x04, 0x05, 0x00, 0x05, 0x00),
-		raw_pdu(0x05, 0x01, 0x05, 0x00, 0x07, 0x29),
-		raw_pdu(0x0a, 0x09, 0x00),
-		raw_pdu(0x0b, 0x01, 0x02, 0x03),
-		raw_pdu(0x04, 0x0a, 0x00, 0x0a, 0x00),
-		raw_pdu(0x05, 0x01, 0x0a, 0x00, 0x07, 0x29),
-		raw_pdu(0x0a, 0x05, 0x00),
-		raw_pdu(0x0b, 0x19, 0x2a),
-		raw_pdu(0x0a, 0x0a, 0x00),
-		raw_pdu(0x0b, 0x19, 0x2a));
+	/* Notifications */
+	define_test("HOGP/RH/HGNF/BV-01-C [Report Characteristic "
+			"Configuration, receive notifications]",
+			setup_basic, &cfg_notify,
+			ATTACH, IOV_DATA(0x1b, 0x0c, 0x00, INPUT_REPORT));
+	/* Reconnection: the reports are known and the Report Map cached, so
+	 * the uHID device is created right away and only the notifications
+	 * need to be enabled again.
+	 */
+	define_test("HOGP/RH/HGNF/BV-01-C [Report Characteristic "
+			"Configuration, receive notifications] - Reconnect",
+			setup_basic, &cfg_reattach,
+			ATTACH, DISABLE_INPUT_CCC,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO,
+			ENABLE_INPUT_CCC,
+			NOTIFY(0x0c, INPUT_REPORT));
+	define_test("HOGP/RH/HGNF/BI-01-C [Boot Keyboard Input Report "
+			"Characteristic Configuration, ignore notifications, "
+			"Report Host]",
+			setup_basic, &cfg_notify,
+			ATTACH,
+			IOV_DATA(0x1b, 0x16, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
+					0x00, 0x00, 0x00),
+			NOTIFY(0x0c, INPUT_REPORT));
+	define_test("HOGP/RH/HGNF/BI-02-C [Boot Mouse Input Report "
+			"Characteristic Configuration, ignore notifications, "
+			"Report Host]",
+			setup_basic, &cfg_notify,
+			ATTACH,
+			IOV_DATA(0x1b, 0x19, 0x00, 0x01, 0x02, 0x03),
+			NOTIFY(0x0c, INPUT_REPORT));
+
+	/* Characteristic GGIT, the discovery being done by bt_gatt_client */
+	define_test("HOGP/RH/CGGIT/CHA/BV-01-C [Characteristic GGIT - Report "
+			"Map - RH]",
+			setup_basic, &cfg_attach,
+			ATTACH);
+	define_test("HOGP/RH/CGGIT/CHA/BV-02-C [Characteristic GGIT - HID "
+			"Information - RH]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO);
+	define_test("HOGP/RH/CGGIT/CHA/BV-03-C [Characteristic GGIT - HID "
+			"Control Point]",
+			setup_basic, &cfg_resume,
+			ATTACH, IOV_DATA(0x52, 0x0a, 0x00, 0x01));
+	define_test("HOGP/RH/CGGIT/CHA/BV-04-C [Characteristic GGIT - "
+			"Protocol Mode - RH]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE);
+	define_test("HOGP/RH/CGGIT/DES/BV-01-C [Descriptor GGIT - External "
+			"Report Reference for Report Map]",
+			setup_basic, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF);
+	define_test("HOGP/RH/CGGIT/CHA/BV-09-C [Characteristic GGIT - HID SCI "
+			"Information]",
+			setup_sci, &cfg_sci_attach,
+			SCI_ATTACH);
+	define_test("HOGP/RH/CGGIT/CHA/BV-10-C [Characteristic GGIT - HID SCI "
+			"Mode]",
+			setup_sci, &cfg_none,
+			READ_PROTO_MODE, READ_EXT_REPORT_REF, READ_INFO_SCI,
+			READ_INPUT, READ_INPUT_REF, READ_OUTPUT,
+			READ_OUTPUT_REF, READ_FEATURE, READ_FEATURE_REF,
+			READ_SCI_MODE, READ_SCI_INFO, ENABLE_SCI_MODE_CCC);
 
 	return tester_run();
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 15/21] test: functional: change the HoG SCI mode with the HID Control Point
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (13 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 14/21] unit/test-hog: Use shared/hog Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 16/21] input/hog: Use shared/hog Luiz Augusto von Dentz
                   ` (5 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

As specified by HOGP.TS 4.6.1 and the HIDS/HOGP SCI FIPD, the HID host
requests a HID SCI mode by writing it to the HID Control Point, the HID
device then confirming it with a notification of HID SCI Mode, which is
Read and Notify only.

Write the mode to the HID Control Point instead of HID SCI Mode, which
is no longer writable, and don't expect HID SCI Mode notifications to
be subscribed by bluetoothctl as the input plugin of the HID host may
already have done it.

The connection rate is still changed by the HID host, as the kernel
only issues the LE Connection Rate Request as central.

Assisted-by: OpenCode:claude-opus-5.5
---
 client/scripts/hog-device-sci.bt |  6 ++--
 doc/functional-hog.rst           | 52 +++++++++++++++++++++-----------
 test/functional/test_hog.py      | 29 ++++++++++++++----
 3 files changed, 60 insertions(+), 27 deletions(-)

diff --git a/client/scripts/hog-device-sci.bt b/client/scripts/hog-device-sci.bt
index 1bf3a0a90db7..7500eeb105bf 100644
--- a/client/scripts/hog-device-sci.bt
+++ b/client/scripts/hog-device-sci.bt
@@ -26,12 +26,12 @@ gatt.register-descriptor 0x2908 read
 gatt.register-characteristic 0x2a4e read,write-without-response
 0x01
 #
-# HID Control Point
+# HID Control Point: Suspend, Exit Suspend and Enable SCI modes
 gatt.register-characteristic 0x2a4c write-without-response
 0x00
 #
-# HID SCI Mode: None (0x00), notified when changed
-gatt.register-characteristic 0x2c39 read,write,notify
+# HID SCI Mode: None (0x00), notified once changed with the HID Control Point
+gatt.register-characteristic 0x2c39 read,notify
 0x00
 #
 # HID SCI Information (intervals in units of 0.125 ms):
diff --git a/doc/functional-hog.rst b/doc/functional-hog.rst
index d748f241a378..795a066a6f1a 100644
--- a/doc/functional-hog.rst
+++ b/doc/functional-hog.rst
@@ -16,7 +16,7 @@ SETUP
 Two hosts, connected over LE, both running **bluetoothd(8)** with
 ``ControllerMode = le`` and ``ExportClaimedServices = read-write``, as
 the HID Service is claimed by the input plugin of the HID host and
-bluetoothctl has to write HID SCI Mode:
+bluetoothctl has to write the HID Control Point:
 
 .. code-block::
 
@@ -102,24 +102,26 @@ test_hog[no-sci]
 
 		[bluetoothctl]> gatt.select-attribute 2a4a
 		[bluetoothctl]> gatt.read
-		Attempting to read /org/bluez/hci0/dev_XX/service0013/char001e
+		Attempting to read /org/bluez/hci0/dev_XX/serviceXX/charXX
 		  11 01 00 02                                      ....
 
 	6. ``Notify started``, and the Report subscribed on host1
-	   (``Notify sock acquired``, as the input plugin already
-	   subscribed with AcquireNotify).
+	   (``Notify sock acquired``, as bluetoothd on host1 forwards the
+	   subscription with AcquireNotify, the input plugin of host0
+	   having already enabled the notifications).
 	7. Each report is notified to host0, in order:
 
 	   .. code-block::
 
-		[CHG] Attribute /org/bluez/hci0/dev_XX/service0013/char0018 Value:
+		[CHG] Attribute /org/bluez/hci0/dev_XX/serviceXX/charXX Value:
 		  00 00 04 00 00 00 00 00                          ........
 
 :Notes: The service is checked at the GATT level only, so the test
 	does not depend on the kernel supporting uhid. The HID Service is
-	claimed by the input plugin on host0, but it is still exported
-	read-only over D-Bus by default (see ``ExportClaimedServices`` in
-	**bluetoothd(8)**), so it can be read with bluetoothctl.
+	claimed by the input plugin on host0, but it is still exported over
+	D-Bus, read-write as configured in SETUP (see
+	``ExportClaimedServices`` in **bluetoothd(8)**), so it can be read
+	with bluetoothctl.
 
 test_hog[sci]
 -------------
@@ -131,25 +133,30 @@ test_hog[sci]
 	8. host0: ``gatt.select-attribute 2c39`` and ``gatt.read``.
 	9. host0: ``gatt.select-attribute 2c3a`` and ``gatt.read``.
 	10. host0: ``gatt.select-attribute 2c39`` and ``gatt.notify on``.
-	11. host0: ``gatt.write "0x03"``, i.e. SCI Fast Mode.
+	11. host0: ``gatt.select-attribute 2a4c`` and ``gatt.write "0x03"``,
+	    i.e. Enable SCI Fast mode written to the HID Control Point.
 	12. host0: ``mgmt.conn-subrate <host1 bdaddr> 0x0008 0x0010 1 1 0 0
 	    0x01f4``, i.e. interval 1 ms to 2 ms, within the range given in
 	    HID SCI Information, no subrating, no latency and 5 s
 	    supervision timeout.
 	13. host1: ``gatt.select-attribute local
-	    /org/bluez/app/service0/chrc5`` and ``gatt.write "0x03"``.
+	    /org/bluez/app/service0/chrc5`` and ``gatt.write "0x03"``,
+	    notifying the new mode with HID SCI Mode.
 
 :Expected: As for test_hog[no-sci], except HID Information reads
 	``11 01 00 06``, then:
 
 	8. HID SCI Mode reads ``00``.
 	9. HID SCI Information reads ``08 01 08 00 50 00 08 00``.
-	10. ``Notify started`` and HID SCI Mode subscribed on host1.
-	11. host1 receives the write:
+	10. ``Notify started``. HID SCI Mode is already subscribed on host1,
+	    by the input plugin of host0 once HID Information tells SCI is
+	    supported.
+	11. host1 receives the write, over the socket acquired with
+	    AcquireWrite as it is a Write Without Response:
 
 	    .. code-block::
 
-		[/org/bluez/app/service0/chrc5 (HID SCI Mode)] WriteValue: XX offset 0 link LE
+		[CHG] Attribute /org/bluez/app/service0/chrc4 (HID Control Point) written:
 		  03                                               .
 
 	12. ``Connection Subrate loaded successfully``, then the MGMT
@@ -160,11 +167,12 @@ test_hog[sci]
 		hci0 XX type LE Public connection subrate interval 0x0008 subrate 0x0001 latency 0x0000 cont_num 0x0000 timeout 0x01f4
 
 	13. The new mode is notified to host0, confirming it has been
-	    changed:
+	    changed, which the input plugin reports in the
+	    **bluetoothd(8)** debug output (``SCI Mode changed: 0x03``):
 
 	    .. code-block::
 
-		[CHG] Attribute /org/bluez/hci0/dev_XX/service0015/char0018 Value:
+		[CHG] Attribute /org/bluez/hci0/dev_XX/serviceXX/charXX Value:
 		  03                                               .
 
 	.. code-block::
@@ -180,9 +188,17 @@ test_hog[sci]
 :Notes: As the SCI Supported flag is set, the input plugin on host0
 	reads HID SCI Mode and HID SCI Information as well, which can be
 	seen in the **bluetoothd(8)** debug output (``SCI Mode:`` and
-	``SCI Info:``).
+	``SCI Info:``), and enables the notifications of HID SCI Mode.
 
-	The kernel only issues the LE Connection Rate Request as central,
-	so the connection rate is changed by the HID host. This requires
+	As specified by HOGP.TS 4.6.1, the HID host requests a HID SCI
+	mode by writing it to the HID Control Point (0x02 Default, 0x03
+	Fast, 0x04 Low Power, 0x05 Full Range), HID SCI Mode being Read and
+	Notify only. The Control Point is written with bluetoothctl, as the
+	input plugin has no D-Bus API to request a mode.
+
+	The HID device is meant to change the connection rate once the
+	mode is written, but the kernel only issues the LE Connection Rate
+	Request as central, so the connection rate is changed by the HID
+	host. This requires
 	the controllers to support Shorter Connection Intervals, which
 	btvirt emulates as a BR/EDR/LE 6.2 controller.
diff --git a/test/functional/test_hog.py b/test/functional/test_hog.py
index ea54882d059d..7ff0965275d1 100644
--- a/test/functional/test_hog.py
+++ b/test/functional/test_hog.py
@@ -18,7 +18,8 @@ from pytest_bluezenv.utils import bluez_src_dir
 pytestmark = [pytest.mark.vm]
 
 # The HID Service is claimed by the input plugin of the HID host, so it
-# has to be exported read-write for bluetoothctl to write HID SCI Mode
+# has to be exported read-write for bluetoothctl to write the HID Control
+# Point
 HOG_CONF = """[General]
 ControllerMode = le
 
@@ -30,6 +31,7 @@ HIDS_UUID = "00001812-0000-1000-8000-00805f9b34fb"
 
 # Local attributes registered by client/scripts/hog-device*.bt
 LOCAL_REPORT = "/org/bluez/app/service0/chrc2"
+LOCAL_CP = "/org/bluez/app/service0/chrc4"
 LOCAL_SCI_MODE = "/org/bluez/app/service0/chrc5"
 
 # Keyboard Input Reports: Modifiers, Reserved, then 6 Key Codes
@@ -39,7 +41,8 @@ REPORTS = [
     "00 00 00 00 00 00 00 00",  # released
 ]
 
-# HID SCI Mode: Fast Mode
+# HID Control Point: Enable SCI Fast mode, the value then notified with
+# HID SCI Mode
 SCI_FAST_MODE = "03"
 
 # LE Connection Rate parameters requested with mgmt.conn-subrate once in
@@ -229,14 +232,27 @@ def test_hog(hosts, init_script, flags, sci):
     assert read_attribute(ctl, "2c39") == mode
     assert read_attribute(ctl, "2c3a") == info
 
-    # SCI mode change: the HID host writes the new mode to the HID device
-    enable_notifications(ctl, device, "2c39", LOCAL_SCI_MODE)
+    # The input plugin of the HID host enables the notifications of HID
+    # SCI Mode on its own, so they only have to be started on bluetoothctl
+    # to be printed
+    ctl.send("gatt.select-attribute 2c39\n")
+    ctl.send("gatt.notify on\n")
+    expect(ctl, r"Notify started", timeout=REPLY_TIMEOUT)
 
+    # SCI mode change, see HOGP.TS 4.6.1: the HID host writes the mode to
+    # enable to the HID Control Point, with Write Without Response
+    ctl.send("gatt.select-attribute 2a4c\n")
     ctl.send(f'gatt.write "{hexbytes(SCI_FAST_MODE)}"\n')
-    expect(device, rf"\[{LOCAL_SCI_MODE} .*\] WriteValue:")
+    # Received with WriteValue, or over the socket acquired with
+    # AcquireWrite
+    expect(
+        device,
+        rf"\[{LOCAL_CP} .*\] WriteValue:|Attribute {LOCAL_CP} .*written:",
+    )
     assert expect_hexdump(device) == SCI_FAST_MODE
 
-    # The HID host, as central, changes the connection rate accordingly
+    # The HID device is meant to change the connection rate, but the kernel
+    # only requests it as central, so the HID host does it instead
     ctl.send(f"mgmt.conn-subrate {host1.bdaddr} {' '.join(SCI_RATE)}\n")
     # The connection rate may change before the command completes, so the
     # event may be printed before the reply
@@ -248,5 +264,6 @@ def test_hog(hosts, init_script, flags, sci):
     expect(device, rate.format(host0.bdaddr.upper()))
 
     # Then the HID device confirms the mode has been changed
+    ctl.send("gatt.select-attribute 2c39\n")
     notify(device, LOCAL_SCI_MODE, SCI_FAST_MODE)
     assert expect_notification(ctl) == SCI_FAST_MODE
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 16/21] input/hog: Use shared/hog
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (14 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 15/21] test: functional: change the HoG SCI mode with the HID Control Point Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 17/21] doc: Add CONFIG_HIDRAW to the tester kernel config Luiz Augusto von Dentz
                   ` (4 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Use src/shared/hog, based on bt_gatt_client, instead of hog-lib which
is based on GAttrib, and remove it along with the GAttrib based Battery,
Device Information and Scan Parameters implementations only used by it.

These services are handled by the battery, deviceinfo and scanparam
plugins, so the vendor, product and version of the uHID device are
set from the device, which gets them from the PnP ID, when accepting
the connection.

With shared/hog the notifications of HID SCI Mode are enabled when the
HID device supports SCI, so the mode is known once changed with the HID
Control Point.

Assisted-by: OpenCode:claude-opus-5.5
---
 Makefile.plugins          |    4 -
 profiles/battery/bas.c    |  327 ------
 profiles/battery/bas.h    |   19 -
 profiles/deviceinfo/dis.c |  340 -------
 profiles/deviceinfo/dis.h |   27 -
 profiles/input/hog-lib.c  | 1966 -------------------------------------
 profiles/input/hog-lib.h  |   28 -
 profiles/input/hog.c      |   31 +-
 profiles/scanparam/scpp.c |  342 -------
 profiles/scanparam/scpp.h |   22 -
 10 files changed, 21 insertions(+), 3085 deletions(-)
 delete mode 100644 profiles/battery/bas.c
 delete mode 100644 profiles/battery/bas.h
 delete mode 100644 profiles/deviceinfo/dis.c
 delete mode 100644 profiles/deviceinfo/dis.h
 delete mode 100644 profiles/input/hog-lib.c
 delete mode 100644 profiles/input/hog-lib.h
 delete mode 100644 profiles/scanparam/scpp.c
 delete mode 100644 profiles/scanparam/scpp.h

diff --git a/Makefile.plugins b/Makefile.plugins
index edc93e8e9705..a492784418b7 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -62,10 +62,6 @@ endif
 if HOG
 builtin_modules += hog
 builtin_sources += profiles/input/hog.c \
-			profiles/input/hog-lib.c profiles/input/hog-lib.h \
-			profiles/deviceinfo/dis.c profiles/deviceinfo/dis.h \
-			profiles/battery/bas.c profiles/battery/bas.h \
-			profiles/scanparam/scpp.c profiles/scanparam/scpp.h \
 			profiles/input/suspend.h profiles/input/suspend-none.c
 endif
 
diff --git a/profiles/battery/bas.c b/profiles/battery/bas.c
deleted file mode 100644
index ed38c3db1506..000000000000
--- a/profiles/battery/bas.c
+++ /dev/null
@@ -1,327 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Intel Corporation. All rights reserved.
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdbool.h>
-#include <errno.h>
-
-#include <glib.h>
-
-#include "src/log.h"
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/util.h"
-#include "src/shared/queue.h"
-
-#include "attrib/gattrib.h"
-#include "attrib/att.h"
-#include "attrib/gatt.h"
-
-#include "profiles/battery/bas.h"
-
-#define ATT_NOTIFICATION_HEADER_SIZE 3
-#define ATT_READ_RESPONSE_HEADER_SIZE 1
-
-struct bt_bas {
-	int ref_count;
-	GAttrib *attrib;
-	struct gatt_primary *primary;
-	uint16_t handle;
-	uint16_t ccc_handle;
-	guint id;
-	struct queue *gatt_op;
-};
-
-struct gatt_request {
-	unsigned int id;
-	struct bt_bas *bas;
-	void *user_data;
-};
-
-static void destroy_gatt_req(struct gatt_request *req)
-{
-	queue_remove(req->bas->gatt_op, req);
-	bt_bas_unref(req->bas);
-	free(req);
-}
-
-static void bas_free(struct bt_bas *bas)
-{
-	bt_bas_detach(bas);
-
-	free(bas->primary);
-	queue_destroy(bas->gatt_op, (void *) destroy_gatt_req);
-	free(bas);
-}
-
-struct bt_bas *bt_bas_new(void *primary)
-{
-	struct bt_bas *bas;
-
-	bas = new0(struct bt_bas, 1);
-	bas->gatt_op = queue_new();
-
-	if (primary)
-		bas->primary = util_memdup(primary, sizeof(*bas->primary));
-
-	return bt_bas_ref(bas);
-}
-
-struct bt_bas *bt_bas_ref(struct bt_bas *bas)
-{
-	if (!bas)
-		return NULL;
-
-	__sync_fetch_and_add(&bas->ref_count, 1);
-
-	return bas;
-}
-
-void bt_bas_unref(struct bt_bas *bas)
-{
-	if (!bas)
-		return;
-
-	if (__sync_sub_and_fetch(&bas->ref_count, 1))
-		return;
-
-	bas_free(bas);
-}
-
-static struct gatt_request *create_request(struct bt_bas *bas,
-							void *user_data)
-{
-	struct gatt_request *req;
-
-	req = new0(struct gatt_request, 1);
-	req->user_data = user_data;
-	req->bas = bt_bas_ref(bas);
-
-	return req;
-}
-
-static void set_and_store_gatt_req(struct bt_bas *bas,
-						struct gatt_request *req,
-						unsigned int id)
-{
-	req->id = id;
-	queue_push_head(bas->gatt_op, req);
-}
-
-static void write_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle,
-					const uint8_t *value, size_t vlen,
-					GAttribResultFunc func,
-					gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(bas, user_data);
-
-	id = gatt_write_char(attrib, handle, value, vlen, func, req);
-
-	set_and_store_gatt_req(bas, req, id);
-}
-
-static void read_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle,
-				GAttribResultFunc func, gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(bas, user_data);
-
-	id = gatt_read_char(attrib, handle, func, req);
-
-	set_and_store_gatt_req(bas, req, id);
-}
-
-static void discover_char(struct bt_bas *bas, GAttrib *attrib,
-						uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(bas, user_data);
-
-	id = gatt_discover_char(attrib, start, end, uuid, func, req);
-
-	set_and_store_gatt_req(bas, req, id);
-}
-
-static void discover_desc(struct bt_bas *bas, GAttrib *attrib,
-				uint16_t start, uint16_t end, bt_uuid_t *uuid,
-				gatt_cb_t func, gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(bas, user_data);
-
-	id = gatt_discover_desc(attrib, start, end, uuid, func, req);
-	set_and_store_gatt_req(bas, req, id);
-}
-
-static void notification_cb(const guint8 *pdu, guint16 len, gpointer user_data)
-{
-	DBG("Battery Level at %u", pdu[ATT_NOTIFICATION_HEADER_SIZE]);
-}
-
-static void read_value_cb(guint8 status, const guint8 *pdu, guint16 len,
-					gpointer user_data)
-{
-	DBG("Battery Level at %u", pdu[ATT_READ_RESPONSE_HEADER_SIZE]);
-}
-
-static void ccc_written_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_bas *bas = req->user_data;
-
-	destroy_gatt_req(req);
-
-	if (status != 0) {
-		error("Write Scan Refresh CCC failed: %s",
-						att_ecode2str(status));
-		return;
-	}
-
-	DBG("Battery Level: notification enabled");
-
-	bas->id = g_attrib_register(bas->attrib, ATT_OP_HANDLE_NOTIFY,
-					bas->handle, notification_cb, bas,
-					NULL);
-}
-
-static void write_ccc(struct bt_bas *bas, GAttrib *attrib, uint16_t handle,
-							void *user_data)
-{
-	uint8_t value[2];
-
-	put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
-
-	write_char(bas, attrib, handle, value, sizeof(value), ccc_written_cb,
-								user_data);
-}
-
-static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_bas *bas = req->user_data;
-
-	destroy_gatt_req(req);
-
-	if (status != 0) {
-		error("Error reading CCC value: %s", att_ecode2str(status));
-		return;
-	}
-
-	write_ccc(bas, bas->attrib, bas->ccc_handle, bas);
-}
-
-static void discover_descriptor_cb(uint8_t status, GSList *descs,
-								void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_bas *bas = req->user_data;
-	struct gatt_desc *desc;
-
-	destroy_gatt_req(req);
-
-	if (status != 0) {
-		error("Discover descriptors failed: %s", att_ecode2str(status));
-		return;
-	}
-
-	/* There will be only one descriptor on list and it will be CCC */
-	desc = descs->data;
-	bas->ccc_handle = desc->handle;
-
-	read_char(bas, bas->attrib, desc->handle, ccc_read_cb, bas);
-}
-
-static void bas_discovered_cb(uint8_t status, GSList *chars, void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_bas *bas = req->user_data;
-	struct gatt_char *chr;
-	uint16_t start, end;
-	bt_uuid_t uuid;
-
-	destroy_gatt_req(req);
-
-	if (status) {
-		error("Battery: %s", att_ecode2str(status));
-		return;
-	}
-
-	chr = chars->data;
-	bas->handle = chr->value_handle;
-
-	DBG("Battery handle: 0x%04x", bas->handle);
-
-	read_char(bas, bas->attrib, bas->handle, read_value_cb, bas);
-
-	start = chr->value_handle + 1;
-	end = bas->primary->range.end;
-
-	bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
-
-	discover_desc(bas, bas->attrib, start, end, &uuid,
-						discover_descriptor_cb, bas);
-}
-
-bool bt_bas_attach(struct bt_bas *bas, void *attrib)
-{
-	if (!bas || bas->attrib || !bas->primary)
-		return false;
-
-	bas->attrib = g_attrib_ref(attrib);
-
-	if (bas->handle > 0)
-		return true;
-
-	discover_char(bas, bas->attrib, bas->primary->range.start,
-					bas->primary->range.end, NULL,
-					bas_discovered_cb, bas);
-
-	return true;
-}
-
-static void cancel_gatt_req(struct gatt_request *req)
-{
-	if (g_attrib_cancel(req->bas->attrib, req->id))
-		destroy_gatt_req(req);
-}
-
-void bt_bas_detach(struct bt_bas *bas)
-{
-	if (!bas || !bas->attrib)
-		return;
-
-	if (bas->id > 0) {
-		g_attrib_unregister(bas->attrib, bas->id);
-		bas->id = 0;
-	}
-
-	queue_foreach(bas->gatt_op, (void *) cancel_gatt_req, NULL);
-	g_attrib_unref(bas->attrib);
-	bas->attrib = NULL;
-}
diff --git a/profiles/battery/bas.h b/profiles/battery/bas.h
deleted file mode 100644
index cd503a498673..000000000000
--- a/profiles/battery/bas.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Intel Corporation. All rights reserved.
- *
- *
- */
-
-struct bt_bas;
-
-struct bt_bas *bt_bas_new(void *primary);
-
-struct bt_bas *bt_bas_ref(struct bt_bas *bas);
-void bt_bas_unref(struct bt_bas *bas);
-
-bool bt_bas_attach(struct bt_bas *bas, void *gatt);
-void bt_bas_detach(struct bt_bas *bas);
diff --git a/profiles/deviceinfo/dis.c b/profiles/deviceinfo/dis.c
deleted file mode 100644
index 7cc722c526e1..000000000000
--- a/profiles/deviceinfo/dis.c
+++ /dev/null
@@ -1,340 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2012 Texas Instruments, Inc.
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdbool.h>
-#include <errno.h>
-
-#include <glib.h>
-
-#include "src/log.h"
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/util.h"
-#include "src/shared/queue.h"
-#include "src/shared/att.h"
-#include "src/shared/gatt-db.h"
-
-#include "attrib/gattrib.h"
-#include "attrib/att.h"
-#include "attrib/gatt.h"
-
-#include "profiles/deviceinfo/dis.h"
-
-#define DIS_UUID16	0x180a
-#define PNP_ID_SIZE	7
-
-struct bt_dis {
-	int			ref_count;
-	uint16_t		handle;
-	uint8_t			source;
-	uint16_t		vendor;
-	uint16_t		product;
-	uint16_t		version;
-	GAttrib			*attrib;	/* GATT connection */
-	struct gatt_primary	*primary;	/* Primary details */
-	bt_dis_notify		notify;
-	void			*notify_data;
-	struct queue		*gatt_op;
-};
-
-struct characteristic {
-	struct gatt_char	attr;	/* Characteristic */
-	struct bt_dis		*d;	/* deviceinfo where the char belongs */
-};
-
-struct gatt_request {
-	unsigned int id;
-	struct bt_dis *dis;
-	void *user_data;
-};
-
-static void destroy_gatt_req(struct gatt_request *req)
-{
-	queue_remove(req->dis->gatt_op, req);
-	bt_dis_unref(req->dis);
-	free(req);
-}
-
-static void dis_free(struct bt_dis *dis)
-{
-	bt_dis_detach(dis);
-
-	free(dis->primary);
-	queue_destroy(dis->gatt_op, (void *) destroy_gatt_req);
-	g_free(dis);
-}
-
-static void foreach_dis_char(struct gatt_db_attribute *attr, void *user_data)
-{
-	struct bt_dis *dis = user_data;
-	bt_uuid_t pnpid_uuid, uuid;
-	uint16_t value_handle;
-
-	/* Ignore if there are multiple instances */
-	if (dis->handle)
-		return;
-
-	if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid))
-		return;
-
-	/* Find PNPID characteristic's value handle */
-	bt_string_to_uuid(&pnpid_uuid, PNPID_UUID);
-	if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0)
-		dis->handle = value_handle;
-}
-
-static void foreach_dis_service(struct gatt_db_attribute *attr, void *user_data)
-{
-	struct bt_dis *dis = user_data;
-
-	/* Ignore if there are multiple instances */
-	if (dis->handle)
-		return;
-
-	gatt_db_service_foreach_char(attr, foreach_dis_char, dis);
-}
-
-struct bt_dis *bt_dis_new(struct gatt_db *db)
-{
-	struct bt_dis *dis;
-
-	dis = g_try_new0(struct bt_dis, 1);
-	if (!dis)
-		return NULL;
-
-	dis->gatt_op = queue_new();
-
-	if (db) {
-		bt_uuid_t uuid;
-
-		/* Handle the DIS service */
-		bt_uuid16_create(&uuid, DIS_UUID16);
-		gatt_db_foreach_service(db, &uuid, foreach_dis_service, dis);
-		if (!dis->handle) {
-			dis_free(dis);
-			return NULL;
-		}
-	}
-
-	return bt_dis_ref(dis);
-}
-
-struct bt_dis *bt_dis_new_primary(void *primary)
-{
-	struct bt_dis *dis;
-
-	dis = g_try_new0(struct bt_dis, 1);
-	if (!dis)
-		return NULL;
-
-	dis->gatt_op = queue_new();
-
-	if (primary)
-		dis->primary = util_memdup(primary, sizeof(*dis->primary));
-
-	return bt_dis_ref(dis);
-}
-
-struct bt_dis *bt_dis_ref(struct bt_dis *dis)
-{
-	if (!dis)
-		return NULL;
-
-	__sync_fetch_and_add(&dis->ref_count, 1);
-
-	return dis;
-}
-
-void bt_dis_unref(struct bt_dis *dis)
-{
-	if (!dis)
-		return;
-
-	if (__sync_sub_and_fetch(&dis->ref_count, 1))
-		return;
-
-	dis_free(dis);
-}
-
-static struct gatt_request *create_request(struct bt_dis *dis,
-							void *user_data)
-{
-	struct gatt_request *req;
-
-	req = new0(struct gatt_request, 1);
-	req->user_data = user_data;
-	req->dis = bt_dis_ref(dis);
-
-	return req;
-}
-
-static bool set_and_store_gatt_req(struct bt_dis *dis,
-						struct gatt_request *req,
-						unsigned int id)
-{
-	req->id = id;
-	return queue_push_head(dis->gatt_op, req);
-}
-
-static void read_pnpid_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_dis *dis = req->user_data;
-	uint8_t value[PNP_ID_SIZE];
-	ssize_t vlen;
-
-	destroy_gatt_req(req);
-
-	if (status != 0) {
-		error("Error reading PNP_ID value: %s", att_ecode2str(status));
-		return;
-	}
-
-	vlen = dec_read_resp(pdu, len, value, sizeof(value));
-	if (vlen < 0) {
-		error("Error reading PNP_ID: Protocol error");
-		return;
-	}
-
-	if (vlen < 7) {
-		error("Error reading PNP_ID: Invalid pdu length received");
-		return;
-	}
-
-	dis->source = value[0];
-	dis->vendor = get_le16(&value[1]);
-	dis->product = get_le16(&value[3]);
-	dis->version = get_le16(&value[5]);
-
-	DBG("source: 0x%02X vendor: 0x%04X product: 0x%04X version: 0x%04X",
-			dis->source, dis->vendor, dis->product, dis->version);
-
-	if (dis->notify)
-		dis->notify(dis->source, dis->vendor, dis->product,
-						dis->version, dis->notify_data);
-}
-
-static void read_char(struct bt_dis *dis, GAttrib *attrib, uint16_t handle,
-				GAttribResultFunc func, gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(dis, user_data);
-
-	id = gatt_read_char(attrib, handle, func, req);
-
-	if (set_and_store_gatt_req(dis, req, id))
-		return;
-
-	error("dis: Could not read characteristic");
-	g_attrib_cancel(attrib, id);
-	free(req);
-}
-
-static void discover_char(struct bt_dis *dis, GAttrib *attrib,
-						uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(dis, user_data);
-
-	id = gatt_discover_char(attrib, start, end, uuid, func, req);
-
-	if (set_and_store_gatt_req(dis, req, id))
-		return;
-
-	error("dis: Could not send discover characteristic");
-	g_attrib_cancel(attrib, id);
-	free(req);
-}
-
-static void configure_deviceinfo_cb(uint8_t status, GSList *characteristics,
-								void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_dis *d = req->user_data;
-	GSList *l;
-
-	destroy_gatt_req(req);
-
-	if (status != 0) {
-		error("Discover deviceinfo characteristics: %s",
-							att_ecode2str(status));
-		return;
-	}
-
-	for (l = characteristics; l; l = l->next) {
-		struct gatt_char *c = l->data;
-
-		if (strcmp(c->uuid, PNPID_UUID) == 0) {
-			d->handle = c->value_handle;
-			read_char(d, d->attrib, d->handle, read_pnpid_cb, d);
-			break;
-		}
-	}
-}
-
-bool bt_dis_attach(struct bt_dis *dis, void *attrib)
-{
-	struct gatt_primary *primary = dis->primary;
-
-	if (dis->attrib)
-		return false;
-
-	dis->attrib = g_attrib_ref(attrib);
-
-	if (!dis->handle)
-		discover_char(dis, dis->attrib, primary->range.start,
-						primary->range.end, NULL,
-						configure_deviceinfo_cb, dis);
-	else
-		read_char(dis, attrib, dis->handle, read_pnpid_cb, dis);
-
-	return true;
-}
-
-static void cancel_gatt_req(struct gatt_request *req)
-{
-	if (g_attrib_cancel(req->dis->attrib, req->id))
-		destroy_gatt_req(req);
-}
-
-void bt_dis_detach(struct bt_dis *dis)
-{
-	if (!dis->attrib)
-		return;
-
-	queue_foreach(dis->gatt_op, (void *) cancel_gatt_req, NULL);
-	g_attrib_unref(dis->attrib);
-	dis->attrib = NULL;
-}
-
-bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func,
-							void *user_data)
-{
-	if (!dis)
-		return false;
-
-	dis->notify = func;
-	dis->notify_data = user_data;
-
-	return true;
-}
diff --git a/profiles/deviceinfo/dis.h b/profiles/deviceinfo/dis.h
deleted file mode 100644
index 7b6f4f123c76..000000000000
--- a/profiles/deviceinfo/dis.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Intel Corporation. All rights reserved.
- *
- *
- */
-
-struct bt_dis;
-
-struct bt_dis *bt_dis_new(struct gatt_db *db);
-struct bt_dis *bt_dis_new_primary(void *primary);
-
-struct bt_dis *bt_dis_ref(struct bt_dis *dis);
-void bt_dis_unref(struct bt_dis *dis);
-
-bool bt_dis_attach(struct bt_dis *dis, void *gatt);
-void bt_dis_detach(struct bt_dis *dis);
-
-typedef void (*bt_dis_notify) (uint8_t source, uint16_t vendor,
-					uint16_t product, uint16_t version,
-					void *user_data);
-
-bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func,
-							void *user_data);
diff --git a/profiles/input/hog-lib.c b/profiles/input/hog-lib.c
deleted file mode 100644
index 6c75c607f3e1..000000000000
--- a/profiles/input/hog-lib.c
+++ /dev/null
@@ -1,1966 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Intel Corporation.
- *  Copyright (C) 2012  Marcel Holtmann <marcel@holtmann.org>
- *  Copyright (C) 2012  Nordic Semiconductor Inc.
- *  Copyright (C) 2012  Instituto Nokia de Tecnologia - INdT
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <errno.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <inttypes.h>
-
-#include <glib.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/util.h"
-#include "src/shared/uhid.h"
-#include "src/shared/queue.h"
-#include "src/shared/att.h"
-#include "src/shared/gatt-db.h"
-#include "src/log.h"
-
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "attrib/gatt.h"
-
-#include "btio/btio.h"
-
-#include "profiles/scanparam/scpp.h"
-#include "profiles/deviceinfo/dis.h"
-#include "profiles/battery/bas.h"
-#include "profiles/input/hog-lib.h"
-
-#define HOG_UUID16		0x1812
-
-#define HOG_INFO_UUID		0x2A4A
-#define HOG_REPORT_MAP_UUID	0x2A4B
-#define HOG_REPORT_UUID		0x2A4D
-#define HOG_PROTO_MODE_UUID	0x2A4E
-#define HOG_CONTROL_POINT_UUID	0x2A4C
-#define HOG_SCI_MODE_UUID	0x2C39
-#define HOG_SCI_INFO_UUID	0x2C3A
-
-#define HOG_REPORT_TYPE_INPUT	1
-#define HOG_REPORT_TYPE_OUTPUT	2
-#define HOG_REPORT_TYPE_FEATURE	3
-
-#define HOG_PROTO_MODE_BOOT    0
-#define HOG_PROTO_MODE_REPORT  1
-
-#define HOG_INFO_FLAG_SCI_SUPPORTED	0x04
-
-#define HID_INFO_SIZE			4
-#define ATT_NOTIFICATION_HEADER_SIZE	3
-
-struct bt_hog {
-	int			ref_count;
-	char			*name;
-	uint16_t		vendor;
-	uint16_t		product;
-	uint16_t		version;
-	uint8_t			type;
-	struct gatt_db_attribute *attr;
-	struct gatt_primary	*primary;
-	GAttrib			*attrib;
-	GSList			*reports;
-	struct bt_uhid		*uhid;
-	int			uhid_fd;
-	uint64_t		uhid_flags;
-	uint16_t		bcdhid;
-	uint8_t			bcountrycode;
-	uint16_t		proto_mode_handle;
-	uint16_t		ctrlpt_handle;
-	uint8_t			flags;
-	unsigned int		getrep_att;
-	uint32_t		getrep_id;
-	unsigned int		setrep_att;
-	uint32_t		setrep_id;
-	unsigned int		report_map_id;
-	struct bt_scpp		*scpp;
-	struct bt_dis		*dis;
-	struct queue		*bas;
-	GSList			*instances;
-	struct queue		*gatt_op;
-	struct gatt_db		*gatt_db;
-	struct gatt_db_attribute	*report_map_attr;
-	uint16_t		sci_mode_handle;
-	uint16_t		sci_info_handle;
-};
-
-struct report {
-	struct bt_hog		*hog;
-	bool			numbered;
-	uint8_t			id;
-	uint8_t			type;
-	uint16_t		handle;
-	uint16_t		value_handle;
-	uint8_t			properties;
-	uint16_t		ccc_handle;
-	guint			notifyid;
-	uint16_t		len;
-	uint8_t			*value;
-};
-
-struct gatt_request {
-	unsigned int id;
-	struct bt_hog *hog;
-	void *user_data;
-};
-
-static struct gatt_request *create_request(struct bt_hog *hog,
-							void *user_data)
-{
-	struct gatt_request *req;
-
-	req = new0(struct gatt_request, 1);
-	if (!req)
-		return NULL;
-
-	req->user_data = user_data;
-	req->hog = bt_hog_ref(hog);
-
-	return req;
-}
-
-static bool set_and_store_gatt_req(struct bt_hog *hog,
-						struct gatt_request *req,
-						unsigned int id)
-{
-	req->id = id;
-	return queue_push_head(hog->gatt_op, req);
-}
-
-static void destroy_gatt_req(void *data)
-{
-	struct gatt_request *req = data;
-
-	bt_hog_unref(req->hog);
-	free(req);
-}
-
-static void read_report_map(struct bt_hog *hog);
-static void sci_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data);
-static void sci_info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data);
-
-static void remove_gatt_req(struct gatt_request *req, uint8_t status)
-{
-	struct bt_hog *hog = req->hog;
-
-	queue_remove(hog->gatt_op, req);
-
-	if (!status && queue_isempty(hog->gatt_op)) {
-		/* Report Map must be read last since that can result
-		 * in uhid being created and the driver may start to
-		 * use UHID_SET_REPORT which requires the report->id to
-		 * be known what attribute to send to.
-		 */
-		read_report_map(hog);
-	}
-
-	destroy_gatt_req(req);
-}
-
-static void write_char(struct bt_hog *hog, GAttrib *attrib, uint16_t handle,
-					const uint8_t *value, size_t vlen,
-					GAttribResultFunc func,
-					gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(hog, user_data);
-	if (!req)
-		return;
-
-	id = gatt_write_char(attrib, handle, value, vlen, func, req);
-	if (!id) {
-		error("hog: Could not write char");
-		return;
-	}
-
-	if (!set_and_store_gatt_req(hog, req, id)) {
-		error("hog: Failed to queue write char req");
-		g_attrib_cancel(attrib, id);
-		free(req);
-	}
-}
-
-static unsigned int read_char(struct bt_hog *hog, GAttrib *attrib,
-				uint16_t handle, GAttribResultFunc func,
-				gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(hog, user_data);
-	if (!req)
-		return 0;
-
-	id = gatt_read_char(attrib, handle, func, req);
-	if (!id) {
-		error("hog: Could not read char");
-		return 0;
-	}
-
-	if (!set_and_store_gatt_req(hog, req, id)) {
-		error("hog: Failed to queue read char req");
-		g_attrib_cancel(attrib, id);
-		free(req);
-		return 0;
-	}
-
-	return id;
-}
-
-static void discover_desc(struct bt_hog *hog, GAttrib *attrib,
-				uint16_t start, uint16_t end, gatt_cb_t func,
-				gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(hog, user_data);
-	if (!req)
-		return;
-
-	id = gatt_discover_desc(attrib, start, end, NULL, func, req);
-	if (!id) {
-		error("hog: Could not discover descriptors");
-		return;
-	}
-
-	if (!set_and_store_gatt_req(hog, req, id)) {
-		error("hog: Failed to queue discover descriptors req");
-		g_attrib_cancel(attrib, id);
-		free(req);
-	}
-}
-
-static void discover_char(struct bt_hog *hog, GAttrib *attrib,
-						uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(hog, user_data);
-	if (!req)
-		return;
-
-	id = gatt_discover_char(attrib, start, end, uuid, func, req);
-	if (!id) {
-		error("hog: Could not discover characteristic");
-		return;
-	}
-
-	if (!set_and_store_gatt_req(hog, req, id)) {
-		error("hog: Failed to queue discover characteristic req");
-		g_attrib_cancel(attrib, id);
-		free(req);
-	}
-}
-
-static void discover_primary(struct bt_hog *hog, GAttrib *attrib,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(hog, user_data);
-	if (!req)
-		return;
-
-	id = gatt_discover_primary(attrib, uuid, func, req);
-	if (!id) {
-		error("hog: Could not send discover primary");
-		return;
-	}
-
-	if (!set_and_store_gatt_req(hog, req, id)) {
-		error("hog: Failed to queue discover primary req");
-		g_attrib_cancel(attrib, id);
-		free(req);
-	}
-}
-
-static void find_included(struct bt_hog *hog, GAttrib *attrib,
-					uint16_t start, uint16_t end,
-					gatt_cb_t func, gpointer user_data)
-{
-	struct gatt_request *req;
-	unsigned int id;
-
-	req = create_request(hog, user_data);
-	if (!req)
-		return;
-
-	id = gatt_find_included(attrib, start, end, func, req);
-	if (!id) {
-		error("hog: Could not find included");
-		return;
-	}
-
-	if (!set_and_store_gatt_req(hog, req, id)) {
-		error("hog: Failed to queue find included req");
-		g_attrib_cancel(attrib, id);
-		free(req);
-	}
-}
-
-static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data)
-{
-	struct report *report = user_data;
-	struct bt_hog *hog = report->hog;
-	int err;
-
-	if (len < ATT_NOTIFICATION_HEADER_SIZE) {
-		error("Malformed ATT notification");
-		return;
-	}
-
-	pdu += ATT_NOTIFICATION_HEADER_SIZE;
-	len -= ATT_NOTIFICATION_HEADER_SIZE;
-
-	err = bt_uhid_input(hog->uhid, report->numbered ? report->id : 0, pdu,
-				len);
-	if (err < 0)
-		error("bt_uhid_input: %s (%d)", strerror(-err), -err);
-}
-
-static void report_notify_destroy(void *user_data)
-{
-	struct report *report = user_data;
-
-	DBG("");
-
-	report->notifyid = 0;
-}
-
-static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct report *report = req->user_data;
-	struct bt_hog *hog = report->hog;
-
-	if (status != 0) {
-		error("Write report characteristic descriptor failed: %s",
-							att_ecode2str(status));
-		goto remove;
-	}
-
-	if (report->notifyid)
-		goto remove;
-
-	report->notifyid = g_attrib_register(hog->attrib,
-					ATT_OP_HANDLE_NOTIFY,
-					report->value_handle,
-					report_value_cb, report,
-					report_notify_destroy);
-	if (!report->notifyid) {
-		error("Unable to register report notification: handle 0x%04x",
-					report->value_handle);
-		goto remove;
-	}
-
-	DBG("Report characteristic descriptor written: notifications enabled");
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void write_ccc(struct bt_hog *hog, GAttrib *attrib, uint16_t handle,
-							void *user_data)
-{
-	uint8_t value[2];
-
-	put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
-
-	write_char(hog, attrib, handle, value, sizeof(value),
-					report_ccc_written_cb, user_data);
-}
-
-static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct report *report = req->user_data;
-
-	if (status != 0) {
-		error("Error reading CCC value: %s", att_ecode2str(status));
-		goto remove;
-	}
-
-	write_ccc(report->hog, report->hog->attrib, report->ccc_handle, report);
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static const char *type_to_string(uint8_t type)
-{
-	switch (type) {
-	case HOG_REPORT_TYPE_INPUT:
-		return "input";
-	case HOG_REPORT_TYPE_OUTPUT:
-		return "output";
-	case HOG_REPORT_TYPE_FEATURE:
-		return "feature";
-	}
-
-	return NULL;
-}
-
-static void report_reference_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct report *report = req->user_data;
-
-	if (status != 0) {
-		error("Read Report Reference descriptor failed: %s",
-							att_ecode2str(status));
-		goto remove;
-	}
-
-	if (plen != 3) {
-		error("Malformed ATT read response");
-		goto remove;
-	}
-
-	report->id = pdu[1];
-	report->type = pdu[2];
-
-	DBG("Report 0x%04x: id 0x%02x type %s", report->value_handle,
-				report->id, type_to_string(report->type));
-
-	/* Enable notifications only for Input Reports */
-	if (report->type == HOG_REPORT_TYPE_INPUT)
-		read_char(report->hog, report->hog->attrib, report->ccc_handle,
-							ccc_read_cb, report);
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void external_report_reference_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data);
-
-static void discover_external_cb(uint8_t status, GSList *descs, void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-
-	if (status != 0) {
-		error("Discover external descriptors failed: %s",
-							att_ecode2str(status));
-		goto remove;
-	}
-
-	for ( ; descs; descs = descs->next) {
-		struct gatt_desc *desc = descs->data;
-
-		read_char(hog, hog->attrib, desc->handle,
-						external_report_reference_cb,
-						hog);
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void discover_external(struct bt_hog *hog, GAttrib *attrib,
-						uint16_t start, uint16_t end,
-						gpointer user_data)
-{
-	bt_uuid_t uuid;
-
-	if (start > end)
-		return;
-
-	bt_uuid16_create(&uuid, GATT_EXTERNAL_REPORT_REFERENCE);
-
-	discover_desc(hog, attrib, start, end, discover_external_cb,
-								user_data);
-}
-
-static void discover_report_cb(uint8_t status, GSList *descs, void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct report *report = req->user_data;
-	struct bt_hog *hog = report->hog;
-
-	if (status != 0) {
-		error("Discover report descriptors failed: %s",
-							att_ecode2str(status));
-		goto remove;
-	}
-
-	for ( ; descs; descs = descs->next) {
-		struct gatt_desc *desc = descs->data;
-
-		switch (desc->uuid16) {
-		case GATT_CLIENT_CHARAC_CFG_UUID:
-			report->ccc_handle = desc->handle;
-			break;
-		case GATT_REPORT_REFERENCE:
-			read_char(hog, hog->attrib, desc->handle,
-						report_reference_cb, report);
-			break;
-		}
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void discover_report(struct bt_hog *hog, GAttrib *attrib,
-						uint16_t start, uint16_t end,
-							gpointer user_data)
-{
-	if (start > end)
-		return;
-
-	discover_desc(hog, attrib, start, end, discover_report_cb, user_data);
-}
-
-static void report_read_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct report *report = req->user_data;
-
-	if (status != 0) {
-		error("Error reading Report value: %s", att_ecode2str(status));
-		goto remove;
-	}
-
-	if (report->value)
-		free(report->value);
-
-	report->value = util_memdup(pdu, len);
-	report->len = len;
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static int report_chrc_cmp(const void *data, const void *user_data)
-{
-	const struct report *report = data;
-	const struct gatt_char *decl = user_data;
-
-	return report->handle - decl->handle;
-}
-
-static struct report *report_new(struct bt_hog *hog, struct gatt_char *chr)
-{
-	struct report *report;
-	GSList *l;
-
-	if (!chr)
-		return NULL;
-
-	/* Skip if report already exists */
-	l = g_slist_find_custom(hog->reports, chr, report_chrc_cmp);
-	if (l)
-		return l->data;
-
-	report = g_new0(struct report, 1);
-	report->hog = hog;
-	report->handle = chr->handle;
-	report->value_handle = chr->value_handle;
-	report->properties = chr->properties;
-	hog->reports = g_slist_append(hog->reports, report);
-
-	read_char(hog, hog->attrib, chr->value_handle, report_read_cb, report);
-
-	return report;
-}
-
-static void external_service_char_cb(uint8_t status, GSList *chars,
-								void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	struct gatt_primary *primary = hog->primary;
-	struct report *report;
-	GSList *l;
-
-	if (status != 0) {
-		const char *str = att_ecode2str(status);
-
-		DBG("Discover external service characteristic failed: %s", str);
-		goto remove;
-	}
-
-	for (l = chars; l; l = g_slist_next(l)) {
-		struct gatt_char *chr, *next;
-		uint16_t start, end;
-
-		chr = l->data;
-		next = l->next ? l->next->data : NULL;
-
-		if (!chr)
-			continue;
-
-		DBG("0x%04x UUID: %s properties: %02x",
-				chr->handle, chr->uuid, chr->properties);
-
-		report = report_new(hog, chr);
-		start = chr->value_handle + 1;
-		end = (next ? next->handle - 1 : primary->range.end);
-		discover_report(hog, hog->attrib, start, end, report);
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void external_report_reference_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	uint16_t uuid16;
-	bt_uuid_t uuid;
-
-	if (status != 0) {
-		error("Read External Report Reference descriptor failed: %s",
-							att_ecode2str(status));
-		goto remove;
-	}
-
-	if (plen != 3) {
-		error("Malformed ATT read response");
-		goto remove;
-	}
-
-	uuid16 = get_le16(&pdu[1]);
-	DBG("External report reference read, external report characteristic "
-						"UUID: 0x%04x", uuid16);
-
-	/* Do not discover if is not a Report */
-	if (uuid16 != HOG_REPORT_UUID)
-		goto remove;
-
-	bt_uuid16_create(&uuid, uuid16);
-	discover_char(hog, hog->attrib, 0x0001, 0xffff, &uuid,
-					external_service_char_cb, hog);
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static int report_cmp(gconstpointer a, gconstpointer b)
-{
-	const struct report *ra = a, *rb = b;
-
-	/* sort by type first.. */
-	if (ra->type != rb->type)
-		return ra->type - rb->type;
-
-	/* skip id check in case of reports not being numbered  */
-	if (!ra->numbered && !rb->numbered)
-		return 0;
-
-	/* ..then by id */
-	return ra->id - rb->id;
-}
-
-static struct report *find_report(struct bt_hog *hog, uint8_t type, uint8_t id)
-{
-	struct report cmp;
-	GSList *l;
-
-	memset(&cmp, 0, sizeof(cmp));
-	cmp.type = type;
-	cmp.id = id;
-
-	switch (type) {
-	case HOG_REPORT_TYPE_FEATURE:
-		if (hog->flags & UHID_DEV_NUMBERED_FEATURE_REPORTS)
-			cmp.numbered = true;
-		break;
-	case HOG_REPORT_TYPE_OUTPUT:
-		if (hog->flags & UHID_DEV_NUMBERED_OUTPUT_REPORTS)
-			cmp.numbered = true;
-		break;
-	case HOG_REPORT_TYPE_INPUT:
-		if (hog->flags & UHID_DEV_NUMBERED_INPUT_REPORTS)
-			cmp.numbered = true;
-		break;
-	}
-
-	l = g_slist_find_custom(hog->reports, &cmp, report_cmp);
-
-	return l ? l->data : NULL;
-}
-
-static struct report *find_report_by_rtype(struct bt_hog *hog, uint8_t rtype,
-								uint8_t id)
-{
-	uint8_t type;
-
-	switch (rtype) {
-	case UHID_FEATURE_REPORT:
-		type = HOG_REPORT_TYPE_FEATURE;
-		break;
-	case UHID_OUTPUT_REPORT:
-		type = HOG_REPORT_TYPE_OUTPUT;
-		break;
-	case UHID_INPUT_REPORT:
-		type = HOG_REPORT_TYPE_INPUT;
-		break;
-	default:
-		return NULL;
-	}
-
-	return find_report(hog, type, id);
-}
-
-static void output_written_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-
-	if (status != 0)
-		error("Write output report failed: %s", att_ecode2str(status));
-
-	remove_gatt_req(req, status);
-}
-
-static void forward_report(struct uhid_event *ev, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-	struct report *report;
-	void *data;
-	int size;
-
-	report = find_report_by_rtype(hog, ev->u.output.rtype,
-							ev->u.output.data[0]);
-	if (!report)
-		return;
-
-	data = ev->u.output.data;
-	size = ev->u.output.size;
-
-	if (report->numbered && size > 0) {
-		data++;
-		--size;
-	}
-
-	DBG("Sending report type %d ID %d to handle 0x%X", report->type,
-				report->id, report->value_handle);
-
-	if (hog->attrib == NULL)
-		return;
-
-	if (report->properties & GATT_CHR_PROP_WRITE)
-		write_char(hog, hog->attrib, report->value_handle,
-				data, size, output_written_cb, hog);
-	else if (report->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
-		gatt_write_cmd(hog->attrib, report->value_handle,
-						data, size, NULL, NULL);
-}
-
-static void set_numbered(void *data, void *user_data)
-{
-	struct report *report = data;
-	struct bt_hog *hog = user_data;
-
-	switch (report->type) {
-	case HOG_REPORT_TYPE_INPUT:
-		if (hog->uhid_flags & UHID_DEV_NUMBERED_INPUT_REPORTS)
-			report->numbered = true;
-		break;
-	case HOG_REPORT_TYPE_OUTPUT:
-		if (hog->uhid_flags & UHID_DEV_NUMBERED_OUTPUT_REPORTS)
-			report->numbered = true;
-		break;
-	case HOG_REPORT_TYPE_FEATURE:
-		if (hog->uhid_flags & UHID_DEV_NUMBERED_FEATURE_REPORTS)
-			report->numbered = true;
-		break;
-	}
-}
-
-static void start_flags(struct uhid_event *ev, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-
-	hog->uhid_flags = ev->u.start.dev_flags;
-
-	DBG("uHID device flags: 0x%16" PRIx64, hog->uhid_flags);
-
-	if (hog->uhid_flags)
-		g_slist_foreach(hog->reports, set_numbered, hog);
-}
-
-static void set_report_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct bt_hog *hog = user_data;
-	int err;
-
-	hog->setrep_att = 0;
-
-	if (status != 0)
-		error("Error setting Report value: %s", att_ecode2str(status));
-
-	err = bt_uhid_set_report_reply(hog->uhid, hog->setrep_id, status);
-	if (err < 0)
-		error("bt_uhid_set_report_reply: %s", strerror(-err));
-}
-
-static void uhid_destroy(struct bt_hog *hog, bool force)
-{
-	int err;
-
-	if (!hog->uhid)
-		return;
-
-	bt_uhid_unregister_all(hog->uhid);
-
-	err = bt_uhid_destroy(hog->uhid, force);
-	if (err < 0) {
-		error("bt_uhid_destroy: %s", strerror(-err));
-		return;
-	}
-}
-
-static void set_report(struct uhid_event *ev, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-	struct report *report;
-	void *data;
-	int size;
-	int err;
-
-	/* Destroy input device if there is an attempt to communicate with it
-	 * while disconnected.
-	 */
-	if (hog->attrib == NULL) {
-		uhid_destroy(hog, true);
-		return;
-	}
-
-	/* uhid never sends reqs in parallel; if there's a req, it timed out */
-	if (hog->setrep_att) {
-		g_attrib_cancel(hog->attrib, hog->setrep_att);
-		hog->setrep_att = 0;
-	}
-
-	hog->setrep_id = ev->u.set_report.id;
-
-	report = find_report_by_rtype(hog, ev->u.set_report.rtype,
-							ev->u.set_report.rnum);
-	if (!report) {
-		err = ENOTSUP;
-		goto fail;
-	}
-
-	data = ev->u.set_report.data;
-	size = ev->u.set_report.size;
-
-	if (report->numbered && size > 0) {
-		data++;
-		--size;
-	}
-
-	DBG("Sending report type %d ID %d to handle 0x%X", report->type,
-				report->id, report->value_handle);
-
-	hog->setrep_att = gatt_write_char(hog->attrib,
-						report->value_handle,
-						data, size, set_report_cb,
-						hog);
-	if (!hog->setrep_att) {
-		err = ENOMEM;
-		goto fail;
-	}
-
-	return;
-fail:
-	/* cancel the request on failure */
-	set_report_cb(err, NULL, 0, hog);
-}
-
-static void report_reply(struct bt_hog *hog, uint8_t status, uint8_t id,
-			uint16_t len, const uint8_t *data)
-{
-	int err;
-
-	hog->getrep_att = 0;
-
-	err = bt_uhid_get_report_reply(hog->uhid, hog->getrep_id, id, status,
-					data, len);
-	if (err < 0)
-		error("bt_uhid_get_report_reply: %s", strerror(-err));
-}
-
-static void get_report_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
-{
-	struct report *report = user_data;
-	struct bt_hog *hog = report->hog;
-
-	if (status != 0) {
-		error("Error reading Report value: %s", att_ecode2str(status));
-		goto exit;
-	}
-
-	if (len == 0) {
-		error("Error reading Report, length %d", len);
-		status = EIO;
-		goto exit;
-	}
-
-	if (pdu[0] != 0x0b) {
-		error("Error reading Report, invalid response: %02x", pdu[0]);
-		status = EPROTO;
-		goto exit;
-	}
-
-	--len;
-	++pdu;
-
-exit:
-	report_reply(hog, status, report->numbered ? report->id : 0, len, pdu);
-}
-
-static void get_report(struct uhid_event *ev, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-	struct report *report;
-	guint8 err;
-
-	/* Destroy input device if there is an attempt to communicate with it
-	 * while disconnected.
-	 */
-	if (hog->attrib == NULL) {
-		uhid_destroy(hog, true);
-		return;
-	}
-
-	/* uhid never sends reqs in parallel; if there's a req, it timed out */
-	if (hog->getrep_att) {
-		g_attrib_cancel(hog->attrib, hog->getrep_att);
-		hog->getrep_att = 0;
-	}
-
-	hog->getrep_id = ev->u.get_report.id;
-
-	report = find_report_by_rtype(hog, ev->u.get_report.rtype,
-							ev->u.get_report.rnum);
-	if (!report) {
-		err = ENOTSUP;
-		goto fail;
-	}
-
-	hog->getrep_att = gatt_read_char(hog->attrib,
-						report->value_handle,
-						get_report_cb, report);
-	if (!hog->getrep_att) {
-		err = ENOMEM;
-		goto fail;
-	}
-
-	return;
-
-fail:
-	/* reply with an error on failure */
-	report_reply(hog, err, 0, 0, NULL);
-}
-
-static void uhid_create(struct bt_hog *hog, uint8_t *report_map,
-							size_t report_map_len)
-{
-	uint8_t *value = report_map;
-	size_t vlen = report_map_len;
-	int err;
-	GError *gerr = NULL;
-	bdaddr_t src, dst;
-
-	bt_io_get(g_attrib_get_channel(hog->attrib), &gerr,
-			BT_IO_OPT_SOURCE_BDADDR, &src,
-			BT_IO_OPT_DEST_BDADDR, &dst,
-			BT_IO_OPT_INVALID);
-	if (gerr) {
-		error("Failed to connection details: %s", gerr->message);
-		g_error_free(gerr);
-		return;
-	}
-
-	err = bt_uhid_create(hog->uhid, hog->name, &src, &dst,
-				hog->vendor, hog->product, hog->version,
-				hog->bcountrycode, hog->type, value, vlen);
-	if (err < 0) {
-		error("bt_uhid_create: %s", strerror(-err));
-		return;
-	}
-
-	bt_uhid_register(hog->uhid, UHID_START, start_flags, hog);
-	bt_uhid_register(hog->uhid, UHID_OUTPUT, forward_report, hog);
-	bt_uhid_register(hog->uhid, UHID_GET_REPORT, get_report, hog);
-	bt_uhid_register(hog->uhid, UHID_SET_REPORT, set_report, hog);
-
-	DBG("HoG created uHID device");
-}
-
-static void db_report_map_write_value_cb(struct gatt_db_attribute *attr,
-						int err, void *user_data)
-{
-	if (err)
-		error("Error writing report map value to gatt db");
-}
-
-static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	uint8_t *value;
-	ssize_t vlen;
-
-	remove_gatt_req(req, status);
-
-	if (status != 0) {
-		error("Report Map read failed: %s", att_ecode2str(status));
-		return;
-	}
-
-	value = new0(uint8_t, plen);
-
-	vlen = dec_read_resp(pdu, plen, value, plen);
-	if (vlen < 0) {
-		error("ATT protocol error");
-		goto done;
-	}
-
-	uhid_create(hog, value, vlen);
-
-	/* Cache the report map if gatt_db is available  */
-	if (hog->report_map_attr) {
-		gatt_db_attribute_write(hog->report_map_attr, 0, value, vlen, 0,
-					NULL, db_report_map_write_value_cb,
-					NULL);
-	}
-
-done:
-	free(value);
-}
-
-static void read_report_map(struct bt_hog *hog)
-{
-	uint16_t handle;
-
-	if (!hog->report_map_attr || bt_uhid_created(hog->uhid) ||
-			hog->report_map_id)
-		return;
-
-	handle = gatt_db_attribute_get_handle(hog->report_map_attr);
-
-	hog->report_map_id = read_char(hog, hog->attrib, handle,
-						report_map_read_cb, hog);
-}
-
-static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	uint8_t value[HID_INFO_SIZE];
-	ssize_t vlen;
-
-	if (status != 0) {
-		error("HID Information read failed: %s",
-						att_ecode2str(status));
-		goto remove;
-	}
-
-	vlen = dec_read_resp(pdu, plen, value, sizeof(value));
-	if (vlen != 4) {
-		error("ATT protocol error");
-		goto remove;
-	}
-
-	hog->bcdhid = get_le16(&value[0]);
-	hog->bcountrycode = value[2];
-	hog->flags = value[3];
-
-	DBG("bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X",
-			hog->bcdhid, hog->bcountrycode, hog->flags);
-
-	/* Read SCI attributes if SCI is supported */
-	if (hog->flags & HOG_INFO_FLAG_SCI_SUPPORTED) {
-		if (hog->sci_mode_handle)
-			read_char(hog, hog->attrib, hog->sci_mode_handle,
-						sci_mode_read_cb, hog);
-		if (hog->sci_info_handle)
-			read_char(hog, hog->attrib, hog->sci_info_handle,
-						sci_info_read_cb, hog);
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void sci_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	uint8_t value;
-	ssize_t vlen;
-
-	if (status != 0) {
-		error("HID SCI Mode read failed: %s", att_ecode2str(status));
-		goto remove;
-	}
-
-	vlen = dec_read_resp(pdu, plen, &value, sizeof(value));
-	if (vlen != 1) {
-		error("ATT protocol error");
-		goto remove;
-	}
-
-	DBG("SCI Mode: 0x%02X", value);
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void sci_info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	uint8_t value[14];
-	ssize_t vlen;
-	uint8_t min_conn_interval;
-	uint8_t num_grps;
-	uint8_t i;
-
-	if (status != 0) {
-		error("HID SCI Information read failed: %s",
-						att_ecode2str(status));
-		goto remove;
-	}
-
-	vlen = dec_read_resp(pdu, plen, value, sizeof(value));
-	if (vlen < 2) {
-		error("ATT protocol error");
-		goto remove;
-	}
-
-	min_conn_interval = value[0];
-	num_grps = value[1];
-
-	DBG("SCI Info: Minimum Supported Connection Interval: %.3f ms "
-		"(0x%02x) Number of Supported Subgroups: %d",
-		min_conn_interval * 0.125, min_conn_interval, num_grps);
-
-	for (i = 0; i < num_grps && (2 + i * 6 + 5) < vlen; i++) {
-		uint16_t min, max, stride;
-		size_t off = 2 + i * 6;
-
-		min = get_le16(&value[off]);
-		max = get_le16(&value[off + 2]);
-		stride = get_le16(&value[off + 4]);
-
-		DBG("  Subgroup[%u]: Min %.3f ms Max %.3f ms Stride %.3f ms",
-			i, min * 0.125, max * 0.125, stride * 0.125);
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void proto_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	uint8_t value;
-	ssize_t vlen;
-
-	if (status != 0) {
-		error("Protocol Mode characteristic read failed: %s",
-							att_ecode2str(status));
-		goto remove;
-	}
-
-	vlen = dec_read_resp(pdu, plen, &value, sizeof(value));
-	if (vlen < 0) {
-		error("ATT protocol error");
-		goto remove;
-	}
-
-	if (value == HOG_PROTO_MODE_BOOT) {
-		uint8_t nval = HOG_PROTO_MODE_REPORT;
-
-		DBG("HoG is operating in Boot Protocol Mode");
-
-		gatt_write_cmd(hog->attrib, hog->proto_mode_handle, &nval,
-						sizeof(nval), NULL, NULL);
-	} else if (value == HOG_PROTO_MODE_REPORT)
-		DBG("HoG is operating in Report Protocol Mode");
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	struct gatt_primary *primary = hog->primary;
-	bt_uuid_t report_uuid, report_map_uuid, info_uuid;
-	bt_uuid_t proto_mode_uuid, ctrlpt_uuid;
-	bt_uuid_t sci_mode_uuid, sci_info_uuid;
-	struct report *report;
-	GSList *l;
-	uint16_t info_handle = 0, proto_mode_handle = 0;
-
-	DBG("HoG inspecting characteristics");
-
-	if (status != 0) {
-		DBG("Discover all characteristics failed: %s",
-					att_ecode2str(status));
-		goto remove;
-	}
-
-	bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
-	bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
-	bt_uuid16_create(&info_uuid, HOG_INFO_UUID);
-	bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID);
-	bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID);
-	bt_uuid16_create(&sci_mode_uuid, HOG_SCI_MODE_UUID);
-	bt_uuid16_create(&sci_info_uuid, HOG_SCI_INFO_UUID);
-
-	for (l = chars; l; l = g_slist_next(l)) {
-		struct gatt_char *chr, *next;
-		bt_uuid_t uuid;
-		uint16_t start, end;
-
-		chr = l->data;
-		next = l->next ? l->next->data : NULL;
-
-		if (!chr)
-			continue;
-
-		DBG("0x%04x UUID: %s properties: %02x",
-				chr->handle, chr->uuid, chr->properties);
-
-		bt_string_to_uuid(&uuid, chr->uuid);
-
-		start = chr->value_handle + 1;
-		end = (next ? next->handle - 1 : primary->range.end);
-
-		if (bt_uuid_cmp(&uuid, &report_uuid) == 0) {
-			report = report_new(hog, chr);
-			discover_report(hog, hog->attrib, start, end, report);
-		} else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0) {
-			DBG("HoG discovering report map");
-			read_char(hog, hog->attrib, chr->value_handle,
-						report_map_read_cb, hog);
-			discover_external(hog, hog->attrib, start, end, hog);
-		} else if (bt_uuid_cmp(&uuid, &info_uuid) == 0)
-			info_handle = chr->value_handle;
-		else if (bt_uuid_cmp(&uuid, &proto_mode_uuid) == 0)
-			proto_mode_handle = chr->value_handle;
-		else if (bt_uuid_cmp(&uuid, &ctrlpt_uuid) == 0)
-			hog->ctrlpt_handle = chr->value_handle;
-		else if (bt_uuid_cmp(&uuid, &sci_mode_uuid) == 0)
-			hog->sci_mode_handle = chr->value_handle;
-		else if (bt_uuid_cmp(&uuid, &sci_info_uuid) == 0)
-			hog->sci_info_handle = chr->value_handle;
-	}
-
-	if (proto_mode_handle) {
-		hog->proto_mode_handle = proto_mode_handle;
-		read_char(hog, hog->attrib, proto_mode_handle,
-						proto_mode_read_cb, hog);
-	}
-
-	if (info_handle)
-		read_char(hog, hog->attrib, info_handle, info_read_cb, hog);
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void report_free(void *data)
-{
-	struct report *report = data;
-
-	free(report->value);
-	g_free(report);
-}
-
-static bool cancel_gatt_req(const void *data, const void *user_data)
-{
-	struct gatt_request *req = (void *) data;
-	const struct bt_hog *hog = user_data;
-
-	return g_attrib_cancel(hog->attrib, req->id);
-}
-
-static void hog_free(void *data)
-{
-	struct bt_hog *hog = data;
-
-	bt_hog_detach(hog, true);
-	uhid_destroy(hog, true);
-
-	queue_destroy(hog->bas, (void *) bt_bas_unref);
-	g_slist_free_full(hog->instances, hog_free);
-
-	bt_scpp_unref(hog->scpp);
-	bt_dis_unref(hog->dis);
-	bt_uhid_unref(hog->uhid);
-	g_slist_free_full(hog->reports, report_free);
-	g_free(hog->name);
-	free(hog->primary);
-	queue_destroy(hog->gatt_op, (void *) destroy_gatt_req);
-	if (hog->gatt_db)
-		gatt_db_unref(hog->gatt_db);
-	g_free(hog);
-}
-
-struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
-					uint16_t product, uint16_t version,
-					uint8_t type, struct gatt_db *db)
-{
-	return bt_hog_new(-1, name, vendor, product, version, type, db);
-}
-
-static void foreach_hog_report(struct gatt_db_attribute *attr, void *user_data)
-{
-	struct report *report = user_data;
-	struct bt_hog *hog = report->hog;
-	const bt_uuid_t *uuid;
-	bt_uuid_t ref_uuid, ccc_uuid;
-	uint16_t handle;
-
-	handle = gatt_db_attribute_get_handle(attr);
-	uuid = gatt_db_attribute_get_type(attr);
-
-	bt_uuid16_create(&ref_uuid, GATT_REPORT_REFERENCE);
-	if (!bt_uuid_cmp(&ref_uuid, uuid)) {
-		read_char(hog, hog->attrib, handle, report_reference_cb,
-								report);
-		return;
-	}
-
-	bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID);
-	if (!bt_uuid_cmp(&ccc_uuid, uuid))
-		report->ccc_handle = handle;
-}
-
-static int report_attr_cmp(const void *data, const void *user_data)
-{
-	const struct report *report = data;
-	const struct gatt_db_attribute *attr = user_data;
-
-	return report->handle - gatt_db_attribute_get_handle(attr);
-}
-
-static struct report *report_add(struct bt_hog *hog,
-					struct gatt_db_attribute *attr)
-{
-	struct report *report;
-	GSList *l;
-
-	/* Skip if report already exists */
-	l = g_slist_find_custom(hog->reports, attr, report_attr_cmp);
-	if (l)
-		return l->data;
-
-	report = g_new0(struct report, 1);
-	report->hog = hog;
-
-	gatt_db_attribute_get_char_data(attr, &report->handle,
-					&report->value_handle,
-					&report->properties,
-					NULL, NULL);
-
-	hog->reports = g_slist_append(hog->reports, report);
-
-	read_char(hog, hog->attrib, report->value_handle, report_read_cb,
-								report);
-
-	return report;
-}
-
-static void foreach_hog_external(struct gatt_db_attribute *attr,
-							void *user_data)
-{
-	struct bt_hog *hog = user_data;
-	const bt_uuid_t *uuid;
-	bt_uuid_t ext_uuid;
-	uint16_t handle;
-
-	handle = gatt_db_attribute_get_handle(attr);
-	uuid = gatt_db_attribute_get_type(attr);
-
-	bt_uuid16_create(&ext_uuid, GATT_EXTERNAL_REPORT_REFERENCE);
-	if (!bt_uuid_cmp(&ext_uuid, uuid))
-		read_char(hog, hog->attrib, handle,
-					external_report_reference_cb, hog);
-}
-
-static void db_report_map_read_value_cb(struct gatt_db_attribute *attrib,
-						int err, const uint8_t *value,
-						size_t length, void *user_data)
-{
-	struct iovec *map = user_data;
-
-	if (err) {
-		error("Error reading report map from gatt db %s",
-								strerror(-err));
-		return;
-	}
-
-	if (!length)
-		return;
-
-
-	map->iov_len = length;
-	map->iov_base = (void *) value;
-}
-
-static void foreach_hog_chrc(struct gatt_db_attribute *attr, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-	bt_uuid_t uuid, report_uuid, report_map_uuid, info_uuid;
-	bt_uuid_t proto_mode_uuid, ctrlpt_uuid, sci_mode_uuid, sci_info_uuid;
-	uint16_t handle, value_handle;
-	struct iovec map = {};
-
-	gatt_db_attribute_get_char_data(attr, &handle, &value_handle, NULL,
-					NULL, &uuid);
-
-	bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
-	if (!bt_uuid_cmp(&report_uuid, &uuid)) {
-		struct report *report = report_add(hog, attr);
-		gatt_db_service_foreach_desc(attr, foreach_hog_report, report);
-		return;
-	}
-
-	bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
-	if (!bt_uuid_cmp(&report_map_uuid, &uuid)) {
-
-		if (hog->gatt_db) {
-			/* Try to read the cache of report map if available */
-			hog->report_map_attr = gatt_db_get_attribute(
-								hog->gatt_db,
-								value_handle);
-			gatt_db_attribute_read(hog->report_map_attr, 0,
-						BT_ATT_OP_READ_REQ, NULL,
-						db_report_map_read_value_cb,
-						&map);
-		}
-
-		if (map.iov_len) {
-			/* Report map found in the cache, straight to creating
-			 * UHID to optimize reconnection.
-			 */
-			uhid_create(hog, map.iov_base, map.iov_len);
-		}
-
-		gatt_db_service_foreach_desc(attr, foreach_hog_external, hog);
-	}
-
-	bt_uuid16_create(&info_uuid, HOG_INFO_UUID);
-	if (!bt_uuid_cmp(&info_uuid, &uuid)) {
-		read_char(hog, hog->attrib, value_handle, info_read_cb, hog);
-		return;
-	}
-
-	bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID);
-	if (!bt_uuid_cmp(&proto_mode_uuid, &uuid)) {
-		hog->proto_mode_handle = value_handle;
-		read_char(hog, hog->attrib, value_handle, proto_mode_read_cb,
-									hog);
-	}
-
-	bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID);
-	if (!bt_uuid_cmp(&ctrlpt_uuid, &uuid))
-		hog->ctrlpt_handle = value_handle;
-
-	bt_uuid16_create(&sci_mode_uuid, HOG_SCI_MODE_UUID);
-	if (!bt_uuid_cmp(&sci_mode_uuid, &uuid))
-		hog->sci_mode_handle = value_handle;
-
-	bt_uuid16_create(&sci_info_uuid, HOG_SCI_INFO_UUID);
-	if (!bt_uuid_cmp(&sci_info_uuid, &uuid))
-		hog->sci_info_handle = value_handle;
-}
-
-static struct bt_hog *hog_new(int fd, const char *name, uint16_t vendor,
-					uint16_t product, uint16_t version,
-					uint8_t type,
-					struct gatt_db_attribute *attr)
-{
-	struct bt_uhid *uhid;
-	struct bt_hog *hog;
-
-	if (fd < 0)
-		uhid = bt_uhid_new_default();
-	else
-		uhid = bt_uhid_new(fd);
-
-	if (!uhid) {
-		DBG("Unable to create UHID");
-		return NULL;
-	}
-
-	hog = g_try_new0(struct bt_hog, 1);
-	if (!hog)
-		return NULL;
-
-	hog->gatt_op = queue_new();
-	hog->bas = queue_new();
-	hog->uhid_fd = fd;
-	hog->uhid = uhid;
-
-	if (!hog->gatt_op || !hog->bas) {
-		hog_free(hog);
-		return NULL;
-	}
-
-	hog->name = g_strdup(name);
-	hog->vendor = vendor;
-	hog->product = product;
-	hog->version = version;
-	hog->type = type;
-	hog->attr = attr;
-
-	return hog;
-}
-
-static void hog_attach_instance(struct bt_hog *hog,
-				struct gatt_db_attribute *attr)
-{
-	struct bt_hog *instance;
-
-	if (!hog->attr) {
-		hog->attr = attr;
-		return;
-	}
-
-	instance = hog_new(hog->uhid_fd, hog->name, hog->vendor, hog->product,
-				hog->version, hog->type, attr);
-	if (!instance)
-		return;
-
-	instance->gatt_db = gatt_db_ref(hog->gatt_db);
-	hog->instances = g_slist_append(hog->instances, bt_hog_ref(instance));
-}
-
-static void foreach_hog_service(struct gatt_db_attribute *attr, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-
-	hog_attach_instance(hog, attr);
-}
-
-static void dis_notify(uint8_t source, uint16_t vendor, uint16_t product,
-					uint16_t version, void *user_data)
-{
-	struct bt_hog *hog = user_data;
-	GSList *l;
-
-	hog->vendor = vendor;
-	hog->product = product;
-	hog->version = version;
-
-	for (l = hog->instances; l; l = l->next) {
-		struct bt_hog *instance = l->data;
-
-		instance->vendor = vendor;
-		instance->product = product;
-		instance->version = version;
-	}
-}
-
-struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
-					uint16_t product, uint16_t version,
-					uint8_t type, struct gatt_db *db)
-{
-	struct bt_hog *hog;
-
-	hog = hog_new(fd, name, vendor, product, version, type, NULL);
-	if (!hog)
-		return NULL;
-
-	hog->gatt_db = gatt_db_ref(db);
-
-	if (db) {
-		bt_uuid_t uuid;
-
-		/* Handle the HID services */
-		bt_uuid16_create(&uuid, HOG_UUID16);
-		gatt_db_foreach_service(db, &uuid, foreach_hog_service, hog);
-		if (!hog->attr) {
-			hog_free(hog);
-			return NULL;
-		}
-
-		/* Try creating a DIS instance in case pid/vid are not set */
-		if (!vendor && !product) {
-			hog->dis = bt_dis_new(db);
-			bt_dis_set_notification(hog->dis, dis_notify, hog);
-		}
-	}
-
-	return bt_hog_ref(hog);
-}
-
-struct bt_hog *bt_hog_ref(struct bt_hog *hog)
-{
-	if (!hog)
-		return NULL;
-
-	__sync_fetch_and_add(&hog->ref_count, 1);
-
-	return hog;
-}
-
-void bt_hog_unref(struct bt_hog *hog)
-{
-	if (!hog)
-		return;
-
-	if (__sync_sub_and_fetch(&hog->ref_count, 1))
-		return;
-
-	hog_free(hog);
-}
-
-static void find_included_cb(uint8_t status, GSList *services, void *user_data)
-{
-	struct gatt_request *req = user_data;
-	GSList *l;
-
-	DBG("");
-
-	if (status) {
-		DBG("Find included failed: %s", att_ecode2str(status));
-		goto remove;
-	}
-
-	for (l = services; l; l = l->next) {
-		struct gatt_included *include = l->data;
-
-		DBG("included: handle %x, uuid %s",
-			include->handle, include->uuid);
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-static void hog_attach_scpp(struct bt_hog *hog, struct gatt_primary *primary)
-{
-	if (hog->scpp) {
-		bt_scpp_attach(hog->scpp, hog->attrib);
-		return;
-	}
-
-	hog->scpp = bt_scpp_new(primary);
-	if (hog->scpp)
-		bt_scpp_attach(hog->scpp, hog->attrib);
-}
-
-static void hog_attach_dis(struct bt_hog *hog, struct gatt_primary *primary)
-{
-	if (hog->dis) {
-		bt_dis_attach(hog->dis, hog->attrib);
-		return;
-	}
-
-	hog->dis = bt_dis_new_primary(primary);
-	if (hog->dis) {
-		bt_dis_set_notification(hog->dis, dis_notify, hog);
-		bt_dis_attach(hog->dis, hog->attrib);
-	}
-}
-
-static void hog_attach_bas(struct bt_hog *hog, struct gatt_primary *primary)
-{
-	struct bt_bas *instance;
-
-	instance = bt_bas_new(primary);
-
-	bt_bas_attach(instance, hog->attrib);
-	queue_push_head(hog->bas, instance);
-}
-
-static void hog_attach_hog(struct bt_hog *hog, struct gatt_primary *primary)
-{
-	struct bt_hog *instance;
-
-	if (!hog->primary) {
-		hog->primary = util_memdup(primary, sizeof(*primary));
-		discover_char(hog, hog->attrib, primary->range.start,
-						primary->range.end, NULL,
-						char_discovered_cb, hog);
-		find_included(hog, hog->attrib, primary->range.start,
-				primary->range.end, find_included_cb, hog);
-		return;
-	}
-
-	instance = bt_hog_new(hog->uhid_fd, hog->name, hog->vendor,
-					hog->product, hog->version,
-					hog->type, hog->gatt_db);
-	if (!instance)
-		return;
-
-	instance->primary = util_memdup(primary, sizeof(*primary));
-	find_included(instance, hog->attrib, primary->range.start,
-			primary->range.end, find_included_cb, instance);
-
-	bt_hog_attach(instance, hog->attrib);
-	hog->instances = g_slist_append(hog->instances, instance);
-}
-
-static void primary_cb(uint8_t status, GSList *services, void *user_data)
-{
-	struct gatt_request *req = user_data;
-	struct bt_hog *hog = req->user_data;
-	struct gatt_primary *primary;
-	GSList *l;
-
-	DBG("");
-
-	if (status) {
-		DBG("Discover primary failed: %s", att_ecode2str(status));
-		goto remove;
-	}
-
-	if (!services) {
-		DBG("No primary service found");
-		goto remove;
-	}
-
-	for (l = services; l; l = l->next) {
-		primary = l->data;
-
-		if (strcmp(primary->uuid, SCAN_PARAMETERS_UUID) == 0) {
-			hog_attach_scpp(hog, primary);
-			continue;
-		}
-
-		if (strcmp(primary->uuid, DEVICE_INFORMATION_UUID) == 0) {
-			hog_attach_dis(hog, primary);
-			continue;
-		}
-
-		if (strcmp(primary->uuid, BATTERY_UUID) == 0) {
-			hog_attach_bas(hog, primary);
-			continue;
-		}
-
-		if (strcmp(primary->uuid, HOG_UUID) == 0)
-			hog_attach_hog(hog, primary);
-	}
-
-remove:
-	remove_gatt_req(req, status);
-}
-
-bool bt_hog_attach(struct bt_hog *hog, void *gatt)
-{
-	GSList *l;
-
-	if (hog->attrib)
-		return false;
-
-	hog->attrib = g_attrib_ref(gatt);
-
-	if (!hog->attr && !hog->primary) {
-		discover_primary(hog, hog->attrib, NULL, primary_cb, hog);
-		return true;
-	}
-
-	if (hog->scpp)
-		bt_scpp_attach(hog->scpp, gatt);
-
-	if (hog->dis)
-		bt_dis_attach(hog->dis, gatt);
-
-	queue_foreach(hog->bas, (void *) bt_bas_attach, gatt);
-
-	for (l = hog->instances; l; l = l->next) {
-		struct bt_hog *instance = l->data;
-
-		bt_hog_attach(instance, gatt);
-	}
-
-	if (!bt_uhid_created(hog->uhid)) {
-		DBG("HoG discovering characteristics");
-		if (hog->attr)
-			gatt_db_service_foreach_char(hog->attr,
-							foreach_hog_chrc, hog);
-		else
-			discover_char(hog, hog->attrib,
-					hog->primary->range.start,
-					hog->primary->range.end, NULL,
-					char_discovered_cb, hog);
-	}
-
-	if (!bt_uhid_created(hog->uhid))
-		return true;
-
-	/* If UHID is already created, set up the report value handlers to
-	 * optimize reconnection.
-	 */
-	for (l = hog->reports; l; l = l->next) {
-		struct report *r = l->data;
-
-		if (r->notifyid)
-			continue;
-
-		r->notifyid = g_attrib_register(hog->attrib,
-					ATT_OP_HANDLE_NOTIFY,
-					r->value_handle,
-					report_value_cb, r,
-					report_notify_destroy);
-		if (!r->notifyid)
-			error("Unable to register report notification: "
-				"handle 0x%04x", r->value_handle);
-	}
-
-	/* Attempt to replay get/set report messages since the driver might not
-	 * be aware the device has been disconnected in the meantime.
-	 */
-	bt_uhid_replay(hog->uhid);
-
-	return true;
-}
-
-void bt_hog_detach(struct bt_hog *hog, bool force)
-{
-	GSList *l;
-
-	if (!hog)
-		return;
-
-	if (!hog->attrib)
-		goto done;
-
-	queue_foreach(hog->bas, (void *) bt_bas_detach, NULL);
-
-	for (l = hog->instances; l; l = l->next) {
-		struct bt_hog *instance = l->data;
-
-		bt_hog_detach(instance, force);
-	}
-
-	for (l = hog->reports; l; l = l->next) {
-		struct report *r = l->data;
-
-		if (r->notifyid > 0) {
-			g_attrib_unregister(hog->attrib, r->notifyid);
-			r->notifyid = 0;
-		}
-	}
-
-	if (hog->scpp)
-		bt_scpp_detach(hog->scpp);
-
-	if (hog->dis)
-		bt_dis_detach(hog->dis);
-
-	/* Report requests are tracked separately from gatt_op. */
-	if (hog->getrep_att) {
-		g_attrib_cancel(hog->attrib, hog->getrep_att);
-		hog->getrep_att = 0;
-	}
-
-	if (hog->setrep_att) {
-		g_attrib_cancel(hog->attrib, hog->setrep_att);
-		hog->setrep_att = 0;
-	}
-
-	queue_remove_all(hog->gatt_op, cancel_gatt_req, hog, destroy_gatt_req);
-	g_attrib_unref(hog->attrib);
-	hog->attrib = NULL;
-
-done:
-	uhid_destroy(hog, force);
-}
-
-int bt_hog_set_control_point(struct bt_hog *hog, bool suspend)
-{
-	uint8_t value = suspend ? 0x00 : 0x01;
-
-	if (hog->attrib == NULL)
-		return -ENOTCONN;
-
-	if (hog->ctrlpt_handle == 0)
-		return -ENOTSUP;
-
-	gatt_write_cmd(hog->attrib, hog->ctrlpt_handle, &value,
-					sizeof(value), NULL, NULL);
-
-	return 0;
-}
-
-int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type)
-{
-	struct report *report;
-	GSList *l;
-
-	if (!hog)
-		return -EINVAL;
-
-	if (!hog->attrib)
-		return -ENOTCONN;
-
-	report = find_report(hog, type, 0);
-	if (!report)
-		return -ENOTSUP;
-
-	DBG("hog: Write report, handle 0x%X", report->value_handle);
-
-	if (report->properties & GATT_CHR_PROP_WRITE)
-		write_char(hog, hog->attrib, report->value_handle,
-				data, size, output_written_cb, hog);
-
-	if (report->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
-		gatt_write_cmd(hog->attrib, report->value_handle,
-						data, size, NULL, NULL);
-
-	for (l = hog->instances; l; l = l->next) {
-		struct bt_hog *instance = l->data;
-
-		bt_hog_send_report(instance, data, size, type);
-	}
-
-	return 0;
-}
diff --git a/profiles/input/hog-lib.h b/profiles/input/hog-lib.h
deleted file mode 100644
index 41e454642705..000000000000
--- a/profiles/input/hog-lib.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Intel Corporation. All rights reserved.
- *
- *
- */
-
-struct bt_hog;
-
-struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
-					uint16_t product, uint16_t version,
-					uint8_t type, struct gatt_db *db);
-
-struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
-					uint16_t product, uint16_t version,
-					uint8_t type, struct gatt_db *db);
-
-struct bt_hog *bt_hog_ref(struct bt_hog *hog);
-void bt_hog_unref(struct bt_hog *hog);
-
-bool bt_hog_attach(struct bt_hog *hog, void *gatt);
-void bt_hog_detach(struct bt_hog *hog, bool force);
-
-int bt_hog_set_control_point(struct bt_hog *hog, bool suspend);
-int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type);
diff --git a/profiles/input/hog.c b/profiles/input/hog.c
index f50a0f217f7f..0f6224990e07 100644
--- a/profiles/input/hog.c
+++ b/profiles/input/hog.c
@@ -38,13 +38,10 @@
 #include "src/shared/queue.h"
 #include "src/shared/att.h"
 #include "src/shared/gatt-client.h"
+#include "src/shared/hog.h"
 #include "src/plugin.h"
 
 #include "suspend.h"
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "attrib/gatt.h"
-#include "hog-lib.h"
 
 struct hog_device {
 	struct btd_device	*device;
@@ -57,6 +54,11 @@ static bool auto_sec = true;
 static bool uhid_state_persist = false;
 static struct queue *devices = NULL;
 
+static void hog_debug(const char *str, void *user_data)
+{
+	DBG_IDX(0xffff, "%s", str);
+}
+
 static void hog_device_accept(struct hog_device *dev, struct gatt_db *db)
 {
 	char name[248];
@@ -79,6 +81,8 @@ static void hog_device_accept(struct hog_device *dev, struct gatt_db *db)
 							product, version);
 
 	dev->hog = bt_hog_new_default(name, vendor, product, version, type, db);
+	if (dev->hog)
+		bt_hog_set_debug(dev->hog, hog_debug, NULL, NULL);
 }
 
 static struct hog_device *hog_device_new(struct btd_device *device)
@@ -170,7 +174,10 @@ static int hog_accept(struct btd_service *service)
 	struct hog_device *dev = btd_service_get_user_data(service);
 	struct btd_device *device = btd_service_get_device(service);
 	struct gatt_db *db = btd_device_get_gatt_db(device);
-	GAttrib *attrib = btd_device_get_attrib(device);
+	struct bt_gatt_client *client = btd_device_get_gatt_client(device);
+
+	if (!client)
+		return -ENOTCONN;
 
 	if (!dev->hog) {
 		hog_device_accept(dev, db);
@@ -180,19 +187,23 @@ static int hog_accept(struct btd_service *service)
 
 	/* HOGP 1.0 Section 6.1 requires bonding */
 	if (!device_is_bonded(device, btd_device_get_bdaddr_type(device))) {
-		struct bt_gatt_client *client;
-
 		if (!auto_sec)
 			return -ECONNREFUSED;
 
-		client = btd_device_get_gatt_client(device);
 		if (!bt_gatt_client_set_security(client,
 						BT_ATT_SECURITY_MEDIUM))
 			return -ECONNREFUSED;
 	}
 
-	/* TODO: Replace GAttrib with bt_gatt_client */
-	bt_hog_attach(dev->hog, attrib);
+	/* The PnP ID may have been read by the deviceinfo plugin since the
+	 * HoG instance was created, the uHID device is only created once
+	 * the Report Map is read.
+	 */
+	bt_hog_set_ids(dev->hog, btd_device_get_vendor(device),
+					btd_device_get_product(device),
+					btd_device_get_version(device));
+
+	bt_hog_attach(dev->hog, client);
 
 	btd_service_connecting_complete(service, 0);
 
diff --git a/profiles/scanparam/scpp.c b/profiles/scanparam/scpp.c
deleted file mode 100644
index e70d161c4dba..000000000000
--- a/profiles/scanparam/scpp.c
+++ /dev/null
@@ -1,342 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2012  Nordic Semiconductor Inc.
- *  Copyright (C) 2012  Instituto Nokia de Tecnologia - INdT
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdbool.h>
-#include <errno.h>
-
-#include <glib.h>
-
-#include "src/log.h"
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/util.h"
-#include "src/shared/queue.h"
-
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "attrib/gatt.h"
-
-#include "profiles/scanparam/scpp.h"
-
-#define SCAN_INTERVAL_WIN_UUID		0x2A4F
-#define SCAN_REFRESH_UUID		0x2A31
-
-#define SCAN_INTERVAL		0x0060
-#define SCAN_WINDOW		0x0030
-#define SERVER_REQUIRES_REFRESH	0x00
-
-struct bt_scpp {
-	int ref_count;
-	GAttrib *attrib;
-	struct gatt_primary *primary;
-	uint16_t interval;
-	uint16_t window;
-	uint16_t iwhandle;
-	uint16_t refresh_handle;
-	guint refresh_cb_id;
-	struct queue *gatt_op;
-};
-
-static void discover_char(struct bt_scpp *scpp, GAttrib *attrib,
-						uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	unsigned int id;
-
-	id = gatt_discover_char(attrib, start, end, uuid, func, user_data);
-
-	queue_push_head(scpp->gatt_op, UINT_TO_PTR(id));
-}
-
-static void discover_desc(struct bt_scpp *scpp, GAttrib *attrib,
-				uint16_t start, uint16_t end, bt_uuid_t *uuid,
-				gatt_cb_t func, gpointer user_data)
-{
-	unsigned int id;
-
-	id = gatt_discover_desc(attrib, start, end, uuid, func, user_data);
-
-	queue_push_head(scpp->gatt_op, UINT_TO_PTR(id));
-}
-
-static void write_char(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle,
-					const uint8_t *value, size_t vlen,
-					GAttribResultFunc func,
-					gpointer user_data)
-{
-	unsigned int id;
-
-	id = gatt_write_char(attrib, handle, value, vlen, func, user_data);
-
-	queue_push_head(scan->gatt_op, UINT_TO_PTR(id));
-}
-
-static void scpp_free(struct bt_scpp *scan)
-{
-	bt_scpp_detach(scan);
-
-	free(scan->primary);
-	queue_destroy(scan->gatt_op, NULL); /* cleared in bt_scpp_detach */
-	g_free(scan);
-}
-
-struct bt_scpp *bt_scpp_new(void *primary)
-{
-	struct bt_scpp *scan;
-
-	scan = g_try_new0(struct bt_scpp, 1);
-	if (!scan)
-		return NULL;
-
-	scan->interval = SCAN_INTERVAL;
-	scan->window = SCAN_WINDOW;
-
-	scan->gatt_op = queue_new();
-
-	if (primary)
-		scan->primary = util_memdup(primary, sizeof(*scan->primary));
-
-	return bt_scpp_ref(scan);
-}
-
-struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan)
-{
-	if (!scan)
-		return NULL;
-
-	__sync_fetch_and_add(&scan->ref_count, 1);
-
-	return scan;
-}
-
-void bt_scpp_unref(struct bt_scpp *scan)
-{
-	if (!scan)
-		return;
-
-	if (__sync_sub_and_fetch(&scan->ref_count, 1))
-		return;
-
-	scpp_free(scan);
-}
-
-static void write_scan_params(GAttrib *attrib, uint16_t handle,
-					uint16_t interval, uint16_t window)
-{
-	uint8_t value[4];
-
-	put_le16(interval, &value[0]);
-	put_le16(window, &value[2]);
-
-	gatt_write_cmd(attrib, handle, value, sizeof(value), NULL, NULL);
-}
-
-static void refresh_value_cb(const uint8_t *pdu, uint16_t len,
-						gpointer user_data)
-{
-	struct bt_scpp *scan = user_data;
-
-	DBG("Server requires refresh: %d", pdu[3]);
-
-	if (pdu[3] == SERVER_REQUIRES_REFRESH)
-		write_scan_params(scan->attrib, scan->iwhandle, scan->interval,
-								scan->window);
-}
-
-static void ccc_written_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct bt_scpp *scan = user_data;
-
-	if (status != 0) {
-		error("Write Scan Refresh CCC failed: %s",
-						att_ecode2str(status));
-		return;
-	}
-
-	DBG("Scan Refresh: notification enabled");
-
-	scan->refresh_cb_id = g_attrib_register(scan->attrib,
-				ATT_OP_HANDLE_NOTIFY, scan->refresh_handle,
-				refresh_value_cb, scan, NULL);
-}
-
-static void write_ccc(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle,
-								void *user_data)
-{
-	uint8_t value[2];
-
-	put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
-
-	write_char(scan, attrib, handle, value, sizeof(value), ccc_written_cb,
-								user_data);
-}
-
-static void discover_descriptor_cb(uint8_t status, GSList *descs,
-								void *user_data)
-{
-	struct bt_scpp *scan = user_data;
-	struct gatt_desc *desc;
-
-	if (status != 0) {
-		error("Discover descriptors failed: %s", att_ecode2str(status));
-		return;
-	}
-
-	/* There will be only one descriptor on list and it will be CCC */
-	desc = descs->data;
-
-	write_ccc(scan, scan->attrib, desc->handle, scan);
-}
-
-static void refresh_discovered_cb(uint8_t status, GSList *chars,
-								void *user_data)
-{
-	struct bt_scpp *scan = user_data;
-	struct gatt_char *chr;
-	uint16_t start, end;
-	bt_uuid_t uuid;
-
-	if (status) {
-		error("Scan Refresh %s", att_ecode2str(status));
-		return;
-	}
-
-	if (!chars) {
-		DBG("Scan Refresh not supported");
-		return;
-	}
-
-	chr = chars->data;
-
-	DBG("Scan Refresh handle: 0x%04x", chr->value_handle);
-
-	start = chr->value_handle + 1;
-	end = scan->primary->range.end;
-
-	if (start > end)
-		return;
-
-	scan->refresh_handle = chr->value_handle;
-
-	bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
-
-	discover_desc(scan, scan->attrib, start, end, &uuid,
-					discover_descriptor_cb, user_data);
-}
-
-static void iwin_discovered_cb(uint8_t status, GSList *chars, void *user_data)
-{
-	struct bt_scpp *scan = user_data;
-	struct gatt_char *chr;
-
-	if (status) {
-		error("Discover Scan Interval Window: %s",
-						att_ecode2str(status));
-		return;
-	}
-
-	chr = chars->data;
-	scan->iwhandle = chr->value_handle;
-
-	DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle);
-
-	write_scan_params(scan->attrib, scan->iwhandle, scan->interval,
-								scan->window);
-}
-
-bool bt_scpp_attach(struct bt_scpp *scan, void *attrib)
-{
-	bt_uuid_t iwin_uuid, refresh_uuid;
-
-	if (!scan || scan->attrib || !scan->primary)
-		return false;
-
-	scan->attrib = g_attrib_ref(attrib);
-
-	if (scan->iwhandle)
-		write_scan_params(scan->attrib, scan->iwhandle, scan->interval,
-								scan->window);
-	else {
-		bt_uuid16_create(&iwin_uuid, SCAN_INTERVAL_WIN_UUID);
-		discover_char(scan, scan->attrib, scan->primary->range.start,
-					scan->primary->range.end, &iwin_uuid,
-					iwin_discovered_cb, scan);
-	}
-
-	if (scan->refresh_handle)
-		scan->refresh_cb_id = g_attrib_register(scan->attrib,
-				ATT_OP_HANDLE_NOTIFY, scan->refresh_handle,
-				refresh_value_cb, scan, NULL);
-	else {
-		bt_uuid16_create(&refresh_uuid, SCAN_REFRESH_UUID);
-		discover_char(scan, scan->attrib, scan->primary->range.start,
-					scan->primary->range.end, &refresh_uuid,
-					refresh_discovered_cb, scan);
-	}
-
-	return true;
-}
-
-static void cancel_gatt_req(void *data, void *user_data)
-{
-	unsigned int id = PTR_TO_UINT(data);
-	struct bt_scpp *scan = user_data;
-
-	g_attrib_cancel(scan->attrib, id);
-}
-
-void bt_scpp_detach(struct bt_scpp *scan)
-{
-	if (!scan || !scan->attrib)
-		return;
-
-	if (scan->refresh_cb_id > 0) {
-		g_attrib_unregister(scan->attrib, scan->refresh_cb_id);
-		scan->refresh_cb_id = 0;
-	}
-
-	queue_foreach(scan->gatt_op, cancel_gatt_req, scan);
-	g_attrib_unref(scan->attrib);
-	scan->attrib = NULL;
-}
-
-bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value)
-{
-	if (!scan)
-		return false;
-
-	/* TODO: Check valid range */
-
-	scan->interval = value;
-
-	return true;
-}
-
-bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value)
-{
-	if (!scan)
-		return false;
-
-	/* TODO: Check valid range */
-
-	scan->window = value;
-
-	return true;
-}
diff --git a/profiles/scanparam/scpp.h b/profiles/scanparam/scpp.h
deleted file mode 100644
index c3cc5f156d9e..000000000000
--- a/profiles/scanparam/scpp.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Intel Corporation. All rights reserved.
- *
- *
- */
-
-struct bt_scpp;
-
-struct bt_scpp *bt_scpp_new(void *primary);
-
-struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan);
-void bt_scpp_unref(struct bt_scpp *scan);
-
-bool bt_scpp_attach(struct bt_scpp *scan, void *gatt);
-void bt_scpp_detach(struct bt_scpp *scan);
-
-bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value);
-bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 17/21] doc: Add CONFIG_HIDRAW to the tester kernel config
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (15 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 16/21] input/hog: Use shared/hog Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 18/21] unit/test-uhid: Add Get Report tests Luiz Augusto von Dentz
                   ` (3 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Enable hidraw so unit/test-uhid, when run as root e.g. with test-runner,
can request reports through a hidraw device, as HIDIOCGFEATURE does, and
check the replies to the requests the kernel sends to the uHID device.

Assisted-by: OpenCode:claude-opus-5.5
---
 doc/test-runner.rst | 5 +++++
 doc/tester.config   | 1 +
 2 files changed, 6 insertions(+)

diff --git a/doc/test-runner.rst b/doc/test-runner.rst
index dc1e51305c51..52178244fce8 100644
--- a/doc/test-runner.rst
+++ b/doc/test-runner.rst
@@ -205,6 +205,11 @@ Bluetooth
 	CONFIG_UNIX=y
 
 	CONFIG_UHID=y
+	CONFIG_HIDRAW=y
+
+CONFIG_HIDRAW is used by unit/test-uhid, when run as root e.g. with
+test-runner, to check the replies to the reports requested by the kernel
+through a hidraw device, as HIDIOCGFEATURE does.
 
 For 6lowpan-tester, the following are required:
 
diff --git a/doc/tester.config b/doc/tester.config
index e68740dc0f44..34f1530e174c 100644
--- a/doc/tester.config
+++ b/doc/tester.config
@@ -54,6 +54,7 @@ CONFIG_CRYPTO_USER_API_SKCIPHER=y
 CONFIG_UNIX=y
 
 CONFIG_UHID=y
+CONFIG_HIDRAW=y
 
 CONFIG_DEBUG_KERNEL=y
 CONFIG_LOCKDEP_SUPPORT=y
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 18/21] unit/test-uhid: Add Get Report tests
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (16 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 17/21] doc: Add CONFIG_HIDRAW to the tester kernel config Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 19/21] device: Use bt_att instead of GAttrib Luiz Augusto von Dentz
                   ` (2 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Add tests replying to UHID_GET_REPORT for a numbered Feature Report:

- /uhid/command/get_report_reply checks the reply contains the Report
  ID followed by the report data, with its size accounting for both
- /uhid/device/get_report creates a uHID device, requests the report
  through hidraw with HIDIOCGFEATURE and checks it returns the Report ID
  followed by the report data, as documented in
  Documentation/hid/hidraw.rst, since uhid copies the reply to the buffer
  of hid_hw_raw_request. It requires root and CONFIG_HIDRAW, otherwise
  it is not run.

Assisted-by: OpenCode:claude-opus-5.5
---
 unit/test-uhid.c | 272 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 272 insertions(+)

diff --git a/unit/test-uhid.c b/unit/test-uhid.c
index c5848bef97f9..7ccfe8f375ff 100644
--- a/unit/test-uhid.c
+++ b/unit/test-uhid.c
@@ -18,7 +18,12 @@
 #include <inttypes.h>
 #include <string.h>
 #include <fcntl.h>
+#include <limits.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <linux/hidraw.h>
 
 #include <glib.h>
 
@@ -56,6 +61,14 @@ struct context {
 	int fd;
 	unsigned int pdu_offset;
 	const struct test_data *data;
+	/* Get Report through hidraw, see test_get_report */
+	guint poll;
+	unsigned int poll_count;
+	GThread *thread;
+	guint done;
+	uint8_t report[64];
+	int report_len;
+	int report_err;
 };
 
 #define event(args...)						\
@@ -100,6 +113,20 @@ static void destroy_context(struct context *context)
 	if (context->source > 0)
 		g_source_remove(context->source);
 
+	if (context->poll > 0)
+		g_source_remove(context->poll);
+
+	if (context->thread) {
+		/* Destroying the device fails the pending request, if any, so
+		 * the thread returns.
+		 */
+		bt_uhid_destroy(context->uhid, true);
+		g_thread_join(context->thread);
+	}
+
+	if (context->done > 0)
+		g_source_remove(context->done);
+
 	bt_uhid_unregister_all(context->uhid);
 	bt_uhid_unref(context->uhid);
 
@@ -331,6 +358,245 @@ static void test_server(gconstpointer data)
 }
 
 
+/* Feature Report 1: 8 bytes, which is numbered since the Report Map uses a
+ * Report ID.
+ */
+#define FEATURE_REPORT_ID	0x01
+#define FEATURE_REPORT		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
+
+static const uint8_t feature_report[] = { FEATURE_REPORT };
+
+static const struct uhid_event ev_get_report = {
+	.type = UHID_GET_REPORT,
+	.u.get_report = {
+		.id = 0x42,
+		.rnum = FEATURE_REPORT_ID,
+		.rtype = UHID_FEATURE_REPORT,
+	},
+};
+
+/* The reply contains the Report ID, followed by the report data, as the
+ * buffer returned by hid_hw_raw_request which uhid copies the reply to.
+ */
+static const struct uhid_event ev_get_report_reply = {
+	.type = UHID_GET_REPORT_REPLY,
+	.u.get_report_reply = {
+		.id = 0x42,
+		.err = 0,
+		.size = 1 + sizeof(feature_report),
+		.data = { FEATURE_REPORT_ID, FEATURE_REPORT },
+	},
+};
+
+static void handle_get_report(struct uhid_event *ev, void *user_data)
+{
+	struct context *context = user_data;
+	int err;
+
+	g_assert_cmpint(ev->type, ==, UHID_GET_REPORT);
+	g_assert_cmpint(ev->u.get_report.rnum, ==, FEATURE_REPORT_ID);
+	g_assert_cmpint(ev->u.get_report.rtype, ==, UHID_FEATURE_REPORT);
+
+	err = bt_uhid_get_report_reply(context->uhid, ev->u.get_report.id,
+					FEATURE_REPORT_ID, 0, feature_report,
+					sizeof(feature_report));
+	g_assert_cmpint(err, ==, 0);
+}
+
+static void test_get_report_reply(gconstpointer data)
+{
+	struct context *context = create_context(data);
+
+	bt_uhid_register(context->uhid, UHID_GET_REPORT, handle_get_report,
+								context);
+
+	g_idle_add(send_pdu, context);
+}
+
+static struct test_device get_report_device = {
+	.name = "BlueZ uHID Get Report",
+	.type = BT_UHID_NONE,
+	/* Vendor defined collection with Feature Report 1 of 8 bytes */
+	.map = UTIL_IOV_INIT(0x06, 0x00, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x85,
+				FEATURE_REPORT_ID, 0x09, 0x01, 0x15, 0x00,
+				0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x08,
+				0xb1, 0x02, 0xc0),
+};
+
+static gboolean get_report_done(gpointer user_data)
+{
+	struct context *context = user_data;
+	uint8_t expected[] = { FEATURE_REPORT_ID, FEATURE_REPORT };
+
+	g_thread_join(context->thread);
+	context->thread = NULL;
+	context->done = 0;
+
+	if (context->report_len < 0) {
+		tester_warn("HIDIOCGFEATURE: %s",
+					strerror(context->report_err));
+		tester_test_failed();
+		return FALSE;
+	}
+
+	if (tester_use_debug())
+		util_hexdump('>', context->report, context->report_len,
+						test_debug, "hidraw: ");
+
+	/* hidraw returns the Report ID in the first byte for numbered
+	 * reports, see Documentation/hid/hidraw.rst, followed by the data
+	 * of the reply.
+	 */
+	g_assert_cmpint(context->report_len, ==, sizeof(expected));
+	g_assert(!memcmp(context->report, expected, sizeof(expected)));
+
+	bt_uhid_destroy(context->uhid, true);
+	context_quit(context);
+
+	return FALSE;
+}
+
+static bool find_hidraw(const char *name, char *path, size_t len)
+{
+	DIR *dir;
+	struct dirent *d;
+	bool found = false;
+
+	dir = opendir("/sys/class/hidraw");
+	if (!dir)
+		return false;
+
+	while (!found && (d = readdir(dir))) {
+		char uevent[PATH_MAX], buf[1024];
+		ssize_t n;
+		int fd;
+
+		if (d->d_name[0] == '.')
+			continue;
+
+		snprintf(uevent, sizeof(uevent),
+				"/sys/class/hidraw/%s/device/uevent",
+				d->d_name);
+
+		fd = open(uevent, O_RDONLY);
+		if (fd < 0)
+			continue;
+
+		/* Start with a new line so every line can be matched in full */
+		buf[0] = '\n';
+
+		n = read(fd, buf + 1, sizeof(buf) - 2);
+		close(fd);
+		if (n <= 0)
+			continue;
+
+		buf[n + 1] = '\0';
+
+		if (strstr(buf, name)) {
+			snprintf(path, len, "/dev/%s", d->d_name);
+			found = true;
+		}
+	}
+
+	closedir(dir);
+
+	return found;
+}
+
+static char hidraw_path[PATH_MAX];
+
+static gpointer hidraw_get_feature(gpointer user_data)
+{
+	struct context *context = user_data;
+	int fd;
+
+	fd = open(hidraw_path, O_RDWR);
+	if (fd < 0) {
+		context->report_len = -1;
+		context->report_err = errno;
+		goto done;
+	}
+
+	/* The first byte is the Report ID of the requested report */
+	context->report[0] = FEATURE_REPORT_ID;
+
+	/* Blocks until the reply to UHID_GET_REPORT, which is handled by the
+	 * main loop.
+	 */
+	context->report_len = ioctl(fd, HIDIOCGFEATURE(sizeof(context->report)),
+							context->report);
+	context->report_err = errno;
+
+	close(fd);
+
+done:
+	/* Read by the main thread once joined */
+	context->done = g_idle_add(get_report_done, context);
+
+	return NULL;
+}
+
+static gboolean poll_hidraw(gpointer user_data)
+{
+	struct context *context = user_data;
+	char name[128];
+
+	/* Match the whole line, not another device named with a prefix */
+	snprintf(name, sizeof(name), "\nHID_NAME=%s\n",
+						get_report_device.name);
+
+	/* The hidraw device is created once the HID device is started */
+	if (!find_hidraw(name, hidraw_path, sizeof(hidraw_path))) {
+		if (++context->poll_count < 40)
+			return TRUE;
+
+		tester_warn("hidraw device not found, is CONFIG_HIDRAW set?");
+		context->poll = 0;
+		tester_test_failed();
+		return FALSE;
+	}
+
+	context->poll = 0;
+	context->thread = g_thread_new("hidraw", hidraw_get_feature, context);
+
+	return FALSE;
+}
+
+static void test_get_report(gconstpointer data)
+{
+	struct context *context;
+	struct test_device *device = ((struct test_data *) data)->test_device;
+	int err;
+
+	/* Requires the permissions to create uHID devices, and the kernel
+	 * to support hidraw.
+	 */
+	if (getuid() || access("/sys/class/hidraw", F_OK)) {
+		tester_test_abort();
+		return;
+	}
+
+	context = create_context(data);
+	if (!context)
+		return;
+
+	bt_uhid_register(context->uhid, UHID_GET_REPORT, handle_get_report,
+								context);
+
+	err = bt_uhid_create(context->uhid, device->name, BDADDR_ANY,
+				BDADDR_ANY, device->vendor, device->product,
+				device->version, device->country, device->type,
+				device->map.iov_base, device->map.iov_len);
+	if (err < 0) {
+		tester_warn("create failed: %s", strerror(-err));
+		destroy_context(context);
+		tester_test_failed();
+		return;
+	}
+
+	context->poll = g_timeout_add(50, poll_hidraw, context);
+}
+
 static struct test_device mx_anywhere_3 = {
 	.name = "MX Anywhere 3",
 	.vendor = 0x46D,
@@ -366,8 +632,14 @@ int main(int argc, char *argv[])
 	define_test("/uhid/event/output", test_server, event(&ev_output));
 	define_test("/uhid/event/feature", test_server, event(&ev_feature));
 
+	define_test("/uhid/command/get_report_reply", test_get_report_reply,
+					event(&ev_get_report),
+					event(&ev_get_report_reply));
+
 	define_test_device("/uhid/device/mx_anywhere_3", test_client,
 					&mx_anywhere_3, event(&ev_create));
+	define_test_device("/uhid/device/get_report", test_get_report,
+					&get_report_device, event(&ev_create));
 
 	return tester_run();
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 19/21] device: Use bt_att instead of GAttrib
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (17 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 18/21] unit/test-uhid: Add Get Report tests Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 20/21] attrib: Remove GAttrib and gatttool Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 21/21] attrib: Remove directory Luiz Augusto von Dentz
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

GAttrib is no longer used other than to create the bt_att of the
connection, so create it directly, and remove btd_device_get_attrib as
there are no users left.

Assisted-by: OpenCode:claude-opus-5.5
---
 profiles/deviceinfo/deviceinfo.c |  2 +-
 profiles/ranging/rap.c           |  1 -
 src/adapter.c                    |  1 -
 src/device.c                     | 46 ++++++++++++--------------------
 src/device.h                     |  1 -
 5 files changed, 18 insertions(+), 33 deletions(-)

diff --git a/profiles/deviceinfo/deviceinfo.c b/profiles/deviceinfo/deviceinfo.c
index e77bb50b457a..b1f6fb3ff749 100644
--- a/profiles/deviceinfo/deviceinfo.c
+++ b/profiles/deviceinfo/deviceinfo.c
@@ -26,9 +26,9 @@
 #include "src/device.h"
 #include "src/profile.h"
 #include "src/service.h"
-#include "attrib/gattrib.h"
 #include "src/shared/util.h"
 #include "src/shared/queue.h"
+#include "src/shared/att.h"
 #include "src/shared/gatt-db.h"
 #include "src/shared/gatt-client.h"
 #include "attrib/att.h"
diff --git a/profiles/ranging/rap.c b/profiles/ranging/rap.c
index b494d188c9c0..f3f677c4b4bb 100644
--- a/profiles/ranging/rap.c
+++ b/profiles/ranging/rap.c
@@ -27,7 +27,6 @@
 #include "src/profile.h"
 #include "src/service.h"
 #include "src/gatt-database.h"
-#include "attrib/gattrib.h"
 #include "src/shared/util.h"
 #include "src/shared/queue.h"
 #include "src/shared/att.h"
diff --git a/src/adapter.c b/src/adapter.c
index 33e1ff07ab24..4131c4431dc6 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -59,7 +59,6 @@
 #include "uuid-helper.h"
 #include "agent.h"
 #include "storage.h"
-#include "attrib/gattrib.h"
 #include "attrib/att.h"
 #include "attrib/gatt.h"
 #include "gatt-database.h"
diff --git a/src/device.c b/src/device.c
index c5d8e4c6ebdd..7ed32a63cf19 100644
--- a/src/device.c
+++ b/src/device.c
@@ -47,10 +47,10 @@
 #include "btio/btio.h"
 #include "bluetooth/mgmt.h"
 #include "attrib/att.h"
+#include "attrib/gatt.h"
 #include "btd.h"
 #include "adapter.h"
 #include "gatt-database.h"
-#include "attrib/gattrib.h"
 #include "device.h"
 #include "gatt-client.h"
 #include "profile.h"
@@ -59,7 +59,6 @@
 #include "error.h"
 #include "uuid-helper.h"
 #include "sdp-client.h"
-#include "attrib/gatt.h"
 #include "agent.h"
 #include "textfile.h"
 #include "storage.h"
@@ -257,7 +256,6 @@ struct btd_device {
 	GSList		*disconnects;		/* disconnects message */
 	DBusMessage	*connect;		/* connect message */
 	DBusMessage	*disconnect;		/* disconnect message */
-	GAttrib		*attrib;
 
 	struct bt_att *att;			/* The new ATT transport */
 	uint16_t att_mtu;			/* The ATT MTU */
@@ -865,14 +863,6 @@ static void attio_cleanup(struct btd_device *device)
 		bt_att_unref(device->att);
 		device->att = NULL;
 	}
-
-	if (device->attrib) {
-		GAttrib *attrib = device->attrib;
-
-		device->attrib = NULL;
-		g_attrib_cancel_all(attrib);
-		g_attrib_unref(attrib);
-	}
 }
 
 static void browse_request_cancel(struct browse_req *req)
@@ -2342,7 +2332,7 @@ static void device_set_auto_connect(struct btd_device *device, gboolean enable)
 	/* Enabling auto connect */
 	adapter_auto_connect_add(device->adapter, device);
 
-	if (device->attrib) {
+	if (device->att) {
 		DBG("Already connected");
 		return;
 	}
@@ -6391,7 +6381,6 @@ static void gatt_client_init(struct btd_device *device)
 	}
 
 	bt_gatt_client_set_debug(device->client, gatt_debug, NULL, NULL);
-	g_attrib_attach_client(device->attrib, device->client);
 
 	/*
 	 * If we have cache, notify existing service about the new connection
@@ -6491,7 +6480,7 @@ static bool remote_counter(uint32_t *sign_cnt, void *user_data)
 bool device_attach_att(struct btd_device *dev, GIOChannel *io)
 {
 	GError *gerr = NULL;
-	GAttrib *attrib;
+	struct bt_att *att;
 	BtIOSecLevel sec_level;
 	uint16_t mtu;
 	uint16_t cid;
@@ -6541,17 +6530,24 @@ bool device_attach_att(struct btd_device *dev, GIOChannel *io)
 	}
 
 	dev->att_mtu = MIN(mtu, btd_opts.gatt_mtu);
-	attrib = g_attrib_new(io, cid == BT_ATT_CID ? BT_ATT_DEFAULT_LE_MTU :
-					dev->att_mtu, false);
-	if (!attrib) {
-		error("Unable to create new GAttrib instance");
+
+	att = bt_att_new(g_io_channel_unix_get_fd(io), false);
+	if (!att) {
+		error("Unable to create new ATT instance");
 		return false;
 	}
 
-	dev->attrib = attrib;
-	dev->att = g_attrib_get_att(attrib);
+	if (!bt_att_set_mtu(att, cid == BT_ATT_CID ? BT_ATT_DEFAULT_LE_MTU :
+							dev->att_mtu)) {
+		error("Unable to set ATT MTU");
+		bt_att_unref(att);
+		return false;
+	}
 
-	bt_att_ref(dev->att);
+	/* The fd is closed once the ATT instance is freed */
+	g_io_channel_set_close_on_unref(io, FALSE);
+
+	dev->att = att;
 
 	bt_att_set_debug(dev->att, BT_ATT_DEBUG, gatt_debug, NULL, NULL);
 
@@ -7984,14 +7980,6 @@ struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device)
 	return device->client;
 }
 
-void *btd_device_get_attrib(struct btd_device *device)
-{
-	if (!device)
-		return NULL;
-
-	return device->attrib;
-}
-
 struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device)
 {
 	if (!device)
diff --git a/src/device.h b/src/device.h
index 2ed369856d3d..f18fa5b2119f 100644
--- a/src/device.h
+++ b/src/device.h
@@ -75,7 +75,6 @@ bool btd_device_set_gatt_db(struct btd_device *device, struct gatt_db *db);
 struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device);
 struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device);
 bool btd_device_is_initiator(struct btd_device *device);
-void *btd_device_get_attrib(struct btd_device *device);
 void btd_device_gatt_set_service_changed(struct btd_device *device,
 						uint16_t start, uint16_t end);
 bool device_attach_att(struct btd_device *dev, GIOChannel *io);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 20/21] attrib: Remove GAttrib and gatttool
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (18 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 19/21] device: Use bt_att instead of GAttrib Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  2026-09-28 17:32 ` [PATCH BlueZ v5 21/21] attrib: Remove directory Luiz Augusto von Dentz
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

GAttrib has no users left besides the deprecated gatttool, which is
replaced by the gatt submenu of bluetoothctl and btgatt-client, so
remove both along with unit/test-gattrib and what is left unused of
attrib/gatt.c, only keeping struct gatt_primary and gatt_parse_record
which are still used by bluetoothd.

Assisted-by: OpenCode:claude-opus-5.5
---
 .gitignore           |    2 -
 Makefile.am          |   11 +-
 Makefile.tools       |   12 -
 attrib/gatt.c        | 1150 +-----------------------------------------
 attrib/gatt.h        |   89 ----
 attrib/gattrib.c     |  473 -----------------
 attrib/gattrib.h     |   65 ---
 attrib/gatttool.c    |  612 ----------------------
 attrib/gatttool.h    |   17 -
 attrib/interactive.c | 1020 -------------------------------------
 attrib/utils.c       |  110 ----
 unit/test-gattrib.c  |  552 --------------------
 12 files changed, 3 insertions(+), 4110 deletions(-)
 delete mode 100644 attrib/gattrib.c
 delete mode 100644 attrib/gattrib.h
 delete mode 100644 attrib/gatttool.c
 delete mode 100644 attrib/gatttool.h
 delete mode 100644 attrib/interactive.c
 delete mode 100644 attrib/utils.c
 delete mode 100644 unit/test-gattrib.c

diff --git a/.gitignore b/.gitignore
index b1d63e1d4a33..769354a072b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,7 +45,6 @@ tools/97-hid2hci.rules
 profiles/cups/bluetooth
 profiles/iap/iapd
 
-attrib/gatttool
 tools/avinfo
 tools/bccmd
 tools/hwdb
@@ -174,7 +173,6 @@ unit/test-avctp
 unit/test-avrcp
 unit/test-gatt
 unit/test-midi
-unit/test-gattrib
 unit/test-mesh-crypto
 unit/test-micp
 unit/test-vcp
diff --git a/Makefile.am b/Makefile.am
index 621892055c05..33a2f79fbff6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -287,8 +287,7 @@ src_libshared_ell_la_CFLAGS = $(AM_CFLAGS)
 endif
 
 attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \
-		attrib/gatt.h attrib/gatt.c \
-		attrib/gattrib.h attrib/gattrib.c
+		attrib/gatt.h attrib/gatt.c
 
 btio_sources = btio/btio.h btio/btio.c
 
@@ -772,14 +771,6 @@ unit_test_hog_SOURCES = unit/test-hog.c
 unit_test_hog_LDADD = src/libshared-glib.la \
 				lib/libbluetooth-internal.la $(GLIB_LIBS)
 
-unit_tests += unit/test-gattrib
-
-unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c \
-					$(btio_sources) src/log.h src/log.c
-unit_test_gattrib_LDADD = src/libshared-glib.la \
-				lib/libbluetooth-internal.la \
-				$(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt
-
 unit_tests += unit/test-bap
 
 unit_test_bap_SOURCES = unit/test-bap.c
diff --git a/Makefile.tools b/Makefile.tools
index 671b40fb0e42..f0ed20969e7e 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -514,18 +514,6 @@ tools_btmgmt_SOURCES = tools/btmgmt.c src/uuid-helper.c client/display.c \
 			client/mgmt.c
 tools_btmgmt_LDADD = lib/libbluetooth-internal.la src/libshared-glib.la \
 				$(GLIB_LIBS) -lreadline
-if DEPRECATED
-noinst_PROGRAMS += attrib/gatttool
-
-attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \
-				attrib/gattrib.c btio/btio.c \
-				attrib/gatttool.h attrib/interactive.c \
-				attrib/utils.c src/log.c client/display.c \
-				client/display.h
-attrib_gatttool_LDADD = lib/libbluetooth-internal.la \
-			src/libshared-glib.la $(GLIB_LIBS) -lreadline
-
-endif
 endif
 
 if CUPS
diff --git a/attrib/gatt.c b/attrib/gatt.c
index 71367c083fc4..335094f37b09 100644
--- a/attrib/gatt.c
+++ b/attrib/gatt.c
@@ -15,1164 +15,18 @@
 
 #include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <glib.h>
 
+#include "bluetooth/bluetooth.h"
 #include "bluetooth/sdp.h"
 #include "bluetooth/sdp_lib.h"
 #include "bluetooth/uuid.h"
 
-#include "src/shared/util.h"
 #include "att.h"
-#include "gattrib.h"
 #include "gatt.h"
 
-struct discover_primary {
-	int ref;
-	GAttrib *attrib;
-	unsigned int id;
-	bt_uuid_t uuid;
-	uint16_t start;
-	GSList *primaries;
-	gatt_cb_t cb;
-	void *user_data;
-};
-
-/* Used for the Included Services Discovery (ISD) procedure */
-struct included_discovery {
-	GAttrib		*attrib;
-	unsigned int	id;
-	int		refs;
-	int		err;
-	uint16_t	start_handle;
-	uint16_t	end_handle;
-	GSList		*includes;
-	gatt_cb_t	cb;
-	void		*user_data;
-};
-
-struct included_uuid_query {
-	struct included_discovery	*isd;
-	struct gatt_included		*included;
-};
-
-struct discover_char {
-	int ref;
-	GAttrib *attrib;
-	unsigned int id;
-	bt_uuid_t *uuid;
-	uint16_t end;
-	uint16_t start;
-	GSList *characteristics;
-	gatt_cb_t cb;
-	void *user_data;
-};
-
-struct discover_desc {
-	int ref;
-	GAttrib *attrib;
-	unsigned int id;
-	bt_uuid_t *uuid;
-	uint16_t start;
-	uint16_t end;
-	GSList *descriptors;
-	gatt_cb_t cb;
-	void *user_data;
-};
-
-static void discover_primary_unref(void *data)
-{
-	struct discover_primary *dp = data;
-
-	dp->ref--;
-
-	if (dp->ref > 0)
-		return;
-
-	g_slist_free_full(dp->primaries, g_free);
-	g_attrib_unref(dp->attrib);
-	g_free(dp);
-}
-
-static struct discover_primary *discover_primary_ref(
-						struct discover_primary *dp)
-{
-	dp->ref++;
-
-	return dp;
-}
-
-static struct included_discovery *isd_ref(struct included_discovery *isd)
-{
-	__sync_fetch_and_add(&isd->refs, 1);
-
-	return isd;
-}
-
-static void isd_unref(struct included_discovery *isd)
-{
-	if (__sync_sub_and_fetch(&isd->refs, 1) > 0)
-		return;
-
-	if (isd->err)
-		isd->cb(isd->err, NULL, isd->user_data);
-	else
-		isd->cb(isd->err, isd->includes, isd->user_data);
-
-	g_slist_free_full(isd->includes, g_free);
-	g_attrib_unref(isd->attrib);
-	g_free(isd);
-}
-
-static void discover_char_unref(void *data)
-{
-	struct discover_char *dc = data;
-
-	dc->ref--;
-
-	if (dc->ref > 0)
-		return;
-
-	g_slist_free_full(dc->characteristics, g_free);
-	g_attrib_unref(dc->attrib);
-	free(dc->uuid);
-	g_free(dc);
-}
-
-static struct discover_char *discover_char_ref(struct discover_char *dc)
-{
-	dc->ref++;
-
-	return dc;
-}
-
-static void discover_desc_unref(void *data)
-{
-	struct discover_desc *dd = data;
-
-	dd->ref--;
-
-	if (dd->ref > 0)
-		return;
-
-	g_slist_free_full(dd->descriptors, g_free);
-	g_attrib_unref(dd->attrib);
-	free(dd->uuid);
-	g_free(dd);
-}
-
-static struct discover_desc *discover_desc_ref(struct discover_desc *dd)
-{
-	dd->ref++;
-
-	return dd;
-}
-
-static void put_uuid_le(const bt_uuid_t *uuid, void *dst)
-{
-	if (uuid->type == BT_UUID16)
-		put_le16(uuid->value.u16, dst);
-	else
-		/* Convert from 128-bit BE to LE */
-		bswap_128(&uuid->value.u128, dst);
-}
-
-static void get_uuid128(uint8_t type, const void *val, bt_uuid_t *uuid)
-{
-	if (type == BT_UUID16) {
-		bt_uuid_t uuid16;
-
-		bt_uuid16_create(&uuid16, get_le16(val));
-		bt_uuid_to_uuid128(&uuid16, uuid);
-	} else {
-		uint128_t u128;
-
-		/* Convert from 128-bit LE to BE */
-		bswap_128(val, &u128);
-		bt_uuid128_create(uuid, u128);
-	}
-}
-
-static guint16 encode_discover_primary(uint16_t start, uint16_t end,
-				bt_uuid_t *uuid, uint8_t *pdu, size_t len)
-{
-	bt_uuid_t prim;
-	guint16 plen;
-
-	bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID);
-
-	if (uuid == NULL) {
-		/* Discover all primary services */
-		plen = enc_read_by_grp_req(start, end, &prim, pdu, len);
-	} else {
-		uint8_t value[16];
-		size_t vlen;
-
-		/* Discover primary service by service UUID */
-		put_uuid_le(uuid, value);
-		vlen = bt_uuid_len(uuid);
-
-		plen = enc_find_by_type_req(start, end, &prim, value, vlen,
-								pdu, len);
-	}
-
-	return plen;
-}
-
-static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu,
-					guint16 iplen, gpointer user_data)
-
-{
-	struct discover_primary *dp = user_data;
-	GSList *ranges, *last;
-	struct att_range *range;
-	uint8_t *buf;
-	guint16 oplen;
-	int err = 0;
-	size_t buflen;
-
-	if (status) {
-		err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
-		goto done;
-	}
-
-	ranges = dec_find_by_type_resp(ipdu, iplen);
-	if (ranges == NULL)
-		goto done;
-
-	dp->primaries = g_slist_concat(dp->primaries, ranges);
-
-	last = g_slist_last(ranges);
-	range = last->data;
-
-	if (range->end == 0xffff)
-		goto done;
-
-	/*
-	 * If last handle is lower from previous start handle then it is smth
-	 * wrong. Let's stop search, otherwise we might enter infinite loop.
-	 */
-	if (range->end < dp->start) {
-		err = ATT_ECODE_UNLIKELY;
-		goto done;
-	}
-
-	dp->start = range->end + 1;
-
-	buf = g_attrib_get_buffer(dp->attrib, &buflen);
-	oplen = encode_discover_primary(dp->start, 0xffff, &dp->uuid,
-								buf, buflen);
-
-	if (oplen == 0)
-		goto done;
-
-	g_attrib_send(dp->attrib, dp->id, buf, oplen, primary_by_uuid_cb,
-			discover_primary_ref(dp), discover_primary_unref);
-	return;
-
-done:
-	dp->cb(err, dp->primaries, dp->user_data);
-}
-
-static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
-							gpointer user_data)
-{
-	struct discover_primary *dp = user_data;
-	struct att_data_list *list;
-	unsigned int i, err;
-	uint16_t start, end;
-	uint8_t type;
-
-	if (status) {
-		err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
-		goto done;
-	}
-
-	list = dec_read_by_grp_resp(ipdu, iplen);
-	if (list == NULL) {
-		err = ATT_ECODE_IO;
-		goto done;
-	}
-
-	if (list->len == 6)
-		type = BT_UUID16;
-	else if (list->len == 20)
-		type = BT_UUID128;
-	else {
-		att_data_list_free(list);
-		err = ATT_ECODE_INVALID_PDU;
-		goto done;
-	}
-
-	for (i = 0, end = 0; i < list->num; i++) {
-		const uint8_t *data = list->data[i];
-		struct gatt_primary *primary;
-		bt_uuid_t uuid128;
-
-		start = get_le16(&data[0]);
-		end = get_le16(&data[2]);
-
-		get_uuid128(type, &data[4], &uuid128);
-
-		primary = g_try_new0(struct gatt_primary, 1);
-		if (!primary) {
-			att_data_list_free(list);
-			err = ATT_ECODE_INSUFF_RESOURCES;
-			goto done;
-		}
-		primary->range.start = start;
-		primary->range.end = end;
-		bt_uuid_to_string(&uuid128, primary->uuid, sizeof(primary->uuid));
-		dp->primaries = g_slist_append(dp->primaries, primary);
-	}
-
-	att_data_list_free(list);
-	err = 0;
-
-	/*
-	 * If last handle is lower from previous start handle then it is smth
-	 * wrong. Let's stop search, otherwise we might enter infinite loop.
-	 */
-	if (end < dp->start) {
-		err = ATT_ECODE_UNLIKELY;
-		goto done;
-	}
-
-	dp->start = end + 1;
-
-	if (end != 0xffff) {
-		size_t buflen;
-		uint8_t *buf = g_attrib_get_buffer(dp->attrib, &buflen);
-		guint16 oplen = encode_discover_primary(dp->start, 0xffff, NULL,
-								buf, buflen);
-
-
-		g_attrib_send(dp->attrib, dp->id, buf, oplen, primary_all_cb,
-						discover_primary_ref(dp),
-						discover_primary_unref);
-
-		return;
-	}
-
-done:
-	dp->cb(err, dp->primaries, dp->user_data);
-}
-
-guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func,
-							gpointer user_data)
-{
-	struct discover_primary *dp;
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
-	GAttribResultFunc cb;
-	guint16 plen;
-
-	plen = encode_discover_primary(0x0001, 0xffff, uuid, buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	dp = g_try_new0(struct discover_primary, 1);
-	if (dp == NULL)
-		return 0;
-
-	dp->attrib = g_attrib_ref(attrib);
-	dp->cb = func;
-	dp->user_data = user_data;
-	dp->start = 0x0001;
-
-	if (uuid) {
-		dp->uuid = *uuid;
-		cb = primary_by_uuid_cb;
-	} else
-		cb = primary_all_cb;
-
-	dp->id = g_attrib_send(attrib, 0, buf, plen, cb,
-					discover_primary_ref(dp),
-					discover_primary_unref);
-
-	return dp->id;
-}
-
-static void resolve_included_uuid_cb(uint8_t status, const uint8_t *pdu,
-					uint16_t len, gpointer user_data)
-{
-	struct included_uuid_query *query = user_data;
-	struct included_discovery *isd = query->isd;
-	struct gatt_included *incl = query->included;
-	unsigned int err = status;
-	bt_uuid_t uuid128;
-	size_t buflen;
-	uint8_t *buf;
-
-	if (err)
-		goto done;
-
-	buf = g_attrib_get_buffer(isd->attrib, &buflen);
-	if (dec_read_resp(pdu, len, buf, buflen) != 16) {
-		err = ATT_ECODE_IO;
-		goto done;
-	}
-
-	get_uuid128(BT_UUID128, buf, &uuid128);
-
-	bt_uuid_to_string(&uuid128, incl->uuid, sizeof(incl->uuid));
-	isd->includes = g_slist_append(isd->includes, incl);
-	query->included = NULL;
-
-done:
-	if (isd->err == 0)
-		isd->err = err;
-}
-
-static void inc_query_free(void *data)
-{
-	struct included_uuid_query *query = data;
-
-	isd_unref(query->isd);
-	g_free(query->included);
-	g_free(query);
-}
-
-static guint resolve_included_uuid(struct included_discovery *isd,
-					struct gatt_included *incl)
-{
-	struct included_uuid_query *query;
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(isd->attrib, &buflen);
-	guint16 oplen = enc_read_req(incl->range.start, buf, buflen);
-
-	query = g_new0(struct included_uuid_query, 1);
-	query->isd = isd_ref(isd);
-	query->included = incl;
-
-	return g_attrib_send(isd->attrib, query->isd->id, buf, oplen,
-				resolve_included_uuid_cb, query,
-				inc_query_free);
-}
-
-static struct gatt_included *included_from_buf(const uint8_t *buf, gsize len)
-{
-	struct gatt_included *incl = g_new0(struct gatt_included, 1);
-
-	incl->handle = get_le16(&buf[0]);
-	incl->range.start = get_le16(&buf[2]);
-	incl->range.end = get_le16(&buf[4]);
-
-	if (len == 8) {
-		bt_uuid_t uuid128;
-
-		get_uuid128(BT_UUID16, &buf[6], &uuid128);
-		bt_uuid_to_string(&uuid128, incl->uuid, sizeof(incl->uuid));
-	}
-
-	return incl;
-}
-
-static void find_included_cb(uint8_t status, const uint8_t *pdu, uint16_t len,
-							gpointer user_data);
-
-static guint find_included(struct included_discovery *isd, uint16_t start)
-{
-	bt_uuid_t uuid;
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(isd->attrib, &buflen);
-	guint16 oplen;
-
-	bt_uuid16_create(&uuid, GATT_INCLUDE_UUID);
-	oplen = enc_read_by_type_req(start, isd->end_handle, &uuid,
-							buf, buflen);
-
-	/* If id != 0 it means we are in the middle of include search */
-	if (isd->id)
-		return g_attrib_send(isd->attrib, isd->id, buf, oplen,
-				find_included_cb, isd_ref(isd),
-				(GDestroyNotify) isd_unref);
-
-	/* This is first call from the gattrib user */
-	isd->id = g_attrib_send(isd->attrib, 0, buf, oplen, find_included_cb,
-				isd_ref(isd), (GDestroyNotify) isd_unref);
-
-	return isd->id;
-}
-
-static void find_included_cb(uint8_t status, const uint8_t *pdu, uint16_t len,
-							gpointer user_data)
-{
-	struct included_discovery *isd = user_data;
-	uint16_t last_handle = isd->end_handle;
-	unsigned int err = status;
-	struct att_data_list *list;
-	int i;
-
-	if (err == ATT_ECODE_ATTR_NOT_FOUND)
-		err = 0;
-
-	if (status)
-		goto done;
-
-	list = dec_read_by_type_resp(pdu, len);
-	if (list == NULL) {
-		err = ATT_ECODE_IO;
-		goto done;
-	}
-
-	if (list->len != 6 && list->len != 8) {
-		err = ATT_ECODE_IO;
-		att_data_list_free(list);
-		goto done;
-	}
-
-	for (i = 0; i < list->num; i++) {
-		struct gatt_included *incl;
-
-		incl = included_from_buf(list->data[i], list->len);
-		last_handle = incl->handle;
-
-		/* 128 bit UUID, needs resolving */
-		if (list->len == 6) {
-			resolve_included_uuid(isd, incl);
-			continue;
-		}
-
-		isd->includes = g_slist_append(isd->includes, incl);
-	}
-
-	att_data_list_free(list);
-
-	/*
-	 * If last handle is lower from previous start handle then it is smth
-	 * wrong. Let's stop search, otherwise we might enter infinite loop.
-	 */
-	if (last_handle < isd->start_handle) {
-		isd->err = ATT_ECODE_UNLIKELY;
-		goto done;
-	}
-
-	isd->start_handle = last_handle + 1;
-
-	if (last_handle < isd->end_handle)
-		find_included(isd, isd->start_handle);
-
-done:
-	if (isd->err == 0)
-		isd->err = err;
-}
-
-unsigned int gatt_find_included(GAttrib *attrib, uint16_t start, uint16_t end,
-					gatt_cb_t func, gpointer user_data)
-{
-	struct included_discovery *isd;
-
-	isd = g_new0(struct included_discovery, 1);
-	isd->attrib = g_attrib_ref(attrib);
-	isd->start_handle = start;
-	isd->end_handle = end;
-	isd->cb = func;
-	isd->user_data = user_data;
-
-	return find_included(isd, start);
-}
-
-static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
-							gpointer user_data)
-{
-	struct discover_char *dc = user_data;
-	struct att_data_list *list;
-	unsigned int i, err = 0;
-	uint16_t last = 0;
-	uint8_t type;
-
-	/* We have all the characteristic now, lets send it up */
-	if (status == ATT_ECODE_ATTR_NOT_FOUND) {
-		err = dc->characteristics ? 0 : status;
-		goto done;
-	}
-
-	if (status) {
-		err = status;
-		goto done;
-	}
-
-	list = dec_read_by_type_resp(ipdu, iplen);
-	if (list == NULL) {
-		err = ATT_ECODE_IO;
-		goto done;
-	}
-
-	if (list->len == 7)
-		type = BT_UUID16;
-	else
-		type = BT_UUID128;
-
-	for (i = 0; i < list->num; i++) {
-		uint8_t *value = list->data[i];
-		struct gatt_char *chars;
-		bt_uuid_t uuid128;
-
-		last = get_le16(value);
-
-		get_uuid128(type, &value[5], &uuid128);
-
-		if (dc->uuid && bt_uuid_cmp(dc->uuid, &uuid128))
-			continue;
-
-		chars = g_try_new0(struct gatt_char, 1);
-		if (!chars) {
-			att_data_list_free(list);
-			err = ATT_ECODE_INSUFF_RESOURCES;
-			goto done;
-		}
-
-		chars->handle = last;
-		chars->properties = value[2];
-		chars->value_handle = get_le16(&value[3]);
-		bt_uuid_to_string(&uuid128, chars->uuid, sizeof(chars->uuid));
-		dc->characteristics = g_slist_append(dc->characteristics,
-									chars);
-	}
-
-	att_data_list_free(list);
-
-	/*
-	 * If last handle is lower from previous start handle then it is smth
-	 * wrong. Let's stop search, otherwise we might enter infinite loop.
-	 */
-	if (last < dc->start) {
-		err = ATT_ECODE_UNLIKELY;
-		goto done;
-	}
-
-	dc->start = last + 1;
-
-	if (last != 0 && (dc->start < dc->end)) {
-		bt_uuid_t uuid;
-		guint16 oplen;
-		size_t buflen;
-		uint8_t *buf;
-
-		buf = g_attrib_get_buffer(dc->attrib, &buflen);
-
-		bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
-
-		oplen = enc_read_by_type_req(dc->start, dc->end, &uuid, buf,
-									buflen);
-
-		if (oplen == 0)
-			return;
-
-		g_attrib_send(dc->attrib, dc->id, buf, oplen,
-				char_discovered_cb, discover_char_ref(dc),
-				discover_char_unref);
-
-		return;
-	}
-
-done:
-	dc->cb(err, dc->characteristics, dc->user_data);
-}
-
-guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
-	struct discover_char *dc;
-	bt_uuid_t type_uuid;
-	guint16 plen;
-
-	bt_uuid16_create(&type_uuid, GATT_CHARAC_UUID);
-
-	plen = enc_read_by_type_req(start, end, &type_uuid, buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	dc = g_try_new0(struct discover_char, 1);
-	if (dc == NULL)
-		return 0;
-
-	dc->attrib = g_attrib_ref(attrib);
-	dc->cb = func;
-	dc->user_data = user_data;
-	dc->end = end;
-	dc->start = start;
-	dc->uuid = util_memdup(uuid, sizeof(bt_uuid_t));
-
-	dc->id = g_attrib_send(attrib, 0, buf, plen, char_discovered_cb,
-				discover_char_ref(dc), discover_char_unref);
-
-	return dc->id;
-}
-
-guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
-					bt_uuid_t *uuid, GAttribResultFunc func,
-					gpointer user_data)
-{
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
-	guint16 plen;
-
-	plen = enc_read_by_type_req(start, end, uuid, buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL);
-}
-
-struct read_long_data {
-	GAttrib *attrib;
-	GAttribResultFunc func;
-	gpointer user_data;
-	guint8 *buffer;
-	guint16 size;
-	guint16 handle;
-	guint id;
-	int ref;
-};
-
-static void read_long_destroy(gpointer user_data)
-{
-	struct read_long_data *long_read = user_data;
-
-	if (__sync_sub_and_fetch(&long_read->ref, 1) > 0)
-		return;
-
-	g_attrib_unref(long_read->attrib);
-
-	if (long_read->buffer != NULL)
-		g_free(long_read->buffer);
-
-	g_free(long_read);
-}
-
-static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen,
-							gpointer user_data)
-{
-	struct read_long_data *long_read = user_data;
-	uint8_t *buf;
-	size_t buflen;
-	guint8 *tmp;
-	guint16 plen;
-	guint id;
-
-	if (status != 0 || rlen == 1) {
-		status = 0;
-		goto done;
-	}
-
-	tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1);
-
-	if (tmp == NULL) {
-		status = ATT_ECODE_INSUFF_RESOURCES;
-		goto done;
-	}
-
-	memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1);
-	long_read->buffer = tmp;
-	long_read->size += rlen - 1;
-
-	buf = g_attrib_get_buffer(long_read->attrib, &buflen);
-	if (rlen < buflen)
-		goto done;
-
-	plen = enc_read_blob_req(long_read->handle, long_read->size - 1,
-								buf, buflen);
-	id = g_attrib_send(long_read->attrib, long_read->id, buf, plen,
-				read_blob_helper, long_read, read_long_destroy);
-
-	if (id != 0) {
-		__sync_fetch_and_add(&long_read->ref, 1);
-		return;
-	}
-
-	status = ATT_ECODE_IO;
-
-done:
-	long_read->func(status, long_read->buffer, long_read->size,
-							long_read->user_data);
-}
-
-static void read_char_helper(guint8 status, const guint8 *rpdu,
-					guint16 rlen, gpointer user_data)
-{
-	struct read_long_data *long_read = user_data;
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(long_read->attrib, &buflen);
-	guint16 plen;
-	guint id;
-
-	if (status != 0 || rlen < buflen)
-		goto done;
-
-	long_read->buffer = g_malloc(rlen);
-	if (long_read->buffer == NULL) {
-		status = ATT_ECODE_INSUFF_RESOURCES;
-		goto done;
-	}
-
-	memcpy(long_read->buffer, rpdu, rlen);
-	long_read->size = rlen;
-
-	plen = enc_read_blob_req(long_read->handle, rlen - 1, buf, buflen);
-
-	id = g_attrib_send(long_read->attrib, long_read->id, buf, plen,
-				read_blob_helper, long_read, read_long_destroy);
-	if (id != 0) {
-		__sync_fetch_and_add(&long_read->ref, 1);
-		return;
-	}
-
-	status = ATT_ECODE_IO;
-
-done:
-	long_read->func(status, rpdu, rlen, long_read->user_data);
-}
-
-guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func,
-							gpointer user_data)
-{
-	uint8_t *buf;
-	size_t buflen;
-	guint16 plen;
-	guint id;
-	struct read_long_data *long_read;
-
-	long_read = g_try_new0(struct read_long_data, 1);
-
-	if (long_read == NULL)
-		return 0;
-
-	long_read->attrib = g_attrib_ref(attrib);
-	long_read->func = func;
-	long_read->user_data = user_data;
-	long_read->handle = handle;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-	plen = enc_read_req(handle, buf, buflen);
-	id = g_attrib_send(attrib, 0, buf, plen, read_char_helper,
-						long_read, read_long_destroy);
-	if (id == 0) {
-		g_attrib_unref(long_read->attrib);
-		g_free(long_read);
-	} else {
-		__sync_fetch_and_add(&long_read->ref, 1);
-		long_read->id = id;
-	}
-
-	return id;
-}
-
-struct write_long_data {
-	GAttrib *attrib;
-	GAttribResultFunc func;
-	gpointer user_data;
-	guint16 handle;
-	uint16_t offset;
-	uint8_t *value;
-	size_t vlen;
-};
-
-static guint execute_write(GAttrib *attrib, uint8_t flags,
-				GAttribResultFunc func, gpointer user_data)
-{
-	uint8_t *buf;
-	size_t buflen;
-	guint16 plen;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-	plen = enc_exec_write_req(flags, buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL);
-}
-
-static guint prepare_write(struct write_long_data *long_write);
-
-static void prepare_write_cb(guint8 status, const guint8 *rpdu, guint16 rlen,
-							gpointer user_data)
-{
-	struct write_long_data *long_write = user_data;
-
-	if (status != 0) {
-		long_write->func(status, rpdu, rlen, long_write->user_data);
-		return;
-	}
-
-	/* Skip Prepare Write Response PDU header (5 bytes) */
-	long_write->offset += rlen - 5;
-
-	if (long_write->offset == long_write->vlen) {
-		execute_write(long_write->attrib, ATT_WRITE_ALL_PREP_WRITES,
-				long_write->func, long_write->user_data);
-		free(long_write->value);
-		g_free(long_write);
-
-		return;
-	}
-
-	prepare_write(long_write);
-}
-
-static guint prepare_write(struct write_long_data *long_write)
-{
-	GAttrib *attrib = long_write->attrib;
-	uint16_t handle = long_write->handle;
-	uint16_t offset = long_write->offset;
-	uint8_t *buf, *value = long_write->value + offset;
-	size_t buflen, vlen = long_write->vlen - offset;
-	guint16 plen;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-
-	plen = enc_prep_write_req(handle, offset, value, vlen, buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	return g_attrib_send(attrib, 0, buf, plen, prepare_write_cb, long_write,
-									NULL);
-}
-
-guint gatt_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value,
-			size_t vlen, GAttribResultFunc func, gpointer user_data)
-{
-	uint8_t *buf;
-	size_t buflen;
-	struct write_long_data *long_write;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-
-	/* Use Write Request if payload fits on a single transfer, including 3
-	 * bytes for the header. */
-	if (vlen <= buflen - 3) {
-		uint16_t plen;
-
-		plen = enc_write_req(handle, value, vlen, buf, buflen);
-		if (plen == 0)
-			return 0;
-
-		return g_attrib_send(attrib, 0, buf, plen, func, user_data,
-									NULL);
-	}
-
-	/* Write Long Characteristic Values */
-	long_write = g_try_new0(struct write_long_data, 1);
-	if (long_write == NULL)
-		return 0;
-
-	long_write->attrib = attrib;
-	long_write->func = func;
-	long_write->user_data = user_data;
-	long_write->handle = handle;
-	long_write->value = util_memdup(value, vlen);
-	long_write->vlen = vlen;
-
-	return prepare_write(long_write);
-}
-
-guint gatt_execute_write(GAttrib *attrib, uint8_t flags,
-				GAttribResultFunc func, gpointer user_data)
-{
-	return execute_write(attrib, flags, func, user_data);
-}
-
-guint gatt_reliable_write_char(GAttrib *attrib, uint16_t handle,
-					const uint8_t *value, size_t vlen,
-					GAttribResultFunc func,
-					gpointer user_data)
-{
-	uint8_t *buf;
-	guint16 plen;
-	size_t buflen;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-
-	plen = enc_prep_write_req(handle, 0, value, vlen, buf, buflen);
-	if (!plen)
-		return 0;
-
-	return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL);
-}
-
-guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func,
-							gpointer user_data)
-{
-	uint8_t *buf;
-	size_t buflen;
-	guint16 plen;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-	plen = enc_mtu_req(mtu, buf, buflen);
-	return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL);
-}
-
-static void desc_discovered_cb(guint8 status, const guint8 *ipdu,
-					guint16 iplen, gpointer user_data)
-{
-	struct discover_desc *dd = user_data;
-	struct att_data_list *list;
-	unsigned int i, err = 0;
-	guint8 format;
-	uint16_t last = 0xffff;
-	uint8_t type;
-	gboolean uuid_found = FALSE;
-
-	if (status == ATT_ECODE_ATTR_NOT_FOUND) {
-		err = dd->descriptors ? 0 : status;
-		goto done;
-	}
-
-	if (status) {
-		err = status;
-		goto done;
-	}
-
-	list = dec_find_info_resp(ipdu, iplen, &format);
-	if (!list) {
-		err = ATT_ECODE_IO;
-		goto done;
-	}
-
-	if (format == ATT_FIND_INFO_RESP_FMT_16BIT)
-		type = BT_UUID16;
-	else
-		type = BT_UUID128;
-
-	for (i = 0; i < list->num; i++) {
-		uint8_t *value = list->data[i];
-		struct gatt_desc *desc;
-		bt_uuid_t uuid128;
-
-		last = get_le16(value);
-
-		get_uuid128(type, &value[2], &uuid128);
-
-		if (dd->uuid) {
-			if (bt_uuid_cmp(dd->uuid, &uuid128))
-				continue;
-			else
-				uuid_found = TRUE;
-		}
-
-		desc = g_try_new0(struct gatt_desc, 1);
-		if (!desc) {
-			att_data_list_free(list);
-			err = ATT_ECODE_INSUFF_RESOURCES;
-			goto done;
-		}
-
-		bt_uuid_to_string(&uuid128, desc->uuid, sizeof(desc->uuid));
-		desc->handle = last;
-
-		if (type == BT_UUID16)
-			desc->uuid16 = get_le16(&value[2]);
-
-		dd->descriptors = g_slist_append(dd->descriptors, desc);
-
-		if (uuid_found)
-			break;
-	}
-
-	att_data_list_free(list);
-
-	/*
-	 * If last handle is lower from previous start handle or if iterating
-	 * to the next handle from the last possible offset would overflow, then
-	 * something is wrong. Let's stop search, otherwise we might enter
-	 * infinite loop.
-	 */
-	if (last < dd->start || last == G_MAXUINT16) {
-		err = ATT_ECODE_UNLIKELY;
-		goto done;
-	}
-
-	dd->start = last + 1;
-
-	if (last < dd->end && !uuid_found) {
-		guint16 oplen;
-		size_t buflen;
-		uint8_t *buf;
-
-		buf = g_attrib_get_buffer(dd->attrib, &buflen);
-
-		oplen = enc_find_info_req(dd->start, dd->end, buf, buflen);
-		if (oplen == 0)
-			return;
-
-		g_attrib_send(dd->attrib, dd->id, buf, oplen,
-				desc_discovered_cb, discover_desc_ref(dd),
-				discover_desc_unref);
-
-		return;
-	}
-
-done:
-	dd->cb(err, dd->descriptors, dd->user_data);
-}
-
-guint gatt_discover_desc(GAttrib *attrib, uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data)
-{
-	size_t buflen;
-	uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
-	struct discover_desc *dd;
-	guint16 plen;
-
-	plen = enc_find_info_req(start, end, buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	dd = g_try_new0(struct discover_desc, 1);
-	if (dd == NULL)
-		return 0;
-
-	dd->attrib = g_attrib_ref(attrib);
-	dd->cb = func;
-	dd->user_data = user_data;
-	dd->start = start;
-	dd->end = end;
-	dd->uuid = util_memdup(uuid, sizeof(bt_uuid_t));
-
-	dd->id = g_attrib_send(attrib, 0, buf, plen, desc_discovered_cb,
-				discover_desc_ref(dd), discover_desc_unref);
-
-	return dd->id;
-}
-
-guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value,
-			int vlen, GDestroyNotify notify, gpointer user_data)
-{
-	uint8_t *buf;
-	size_t buflen;
-	guint16 plen;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-	plen = enc_write_cmd(handle, value, vlen, buf, buflen);
-	return g_attrib_send(attrib, 0, buf, plen, NULL, user_data, notify);
-}
-
-guint gatt_signed_write_cmd(GAttrib *attrib, uint16_t handle,
-						const uint8_t *value, int vlen,
-						struct bt_crypto *crypto,
-						const uint8_t csrk[16],
-						uint32_t sign_cnt,
-						GDestroyNotify notify,
-						gpointer user_data)
-{
-	uint8_t *buf;
-	size_t buflen;
-	guint16 plen;
-
-	buf = g_attrib_get_buffer(attrib, &buflen);
-	plen = enc_signed_write_cmd(handle, value, vlen, crypto, csrk, sign_cnt,
-								buf, buflen);
-	if (plen == 0)
-		return 0;
-
-	return g_attrib_send(attrib, 0, buf, plen, NULL, user_data, notify);
-}
-
 static sdp_data_t *proto_seq_find(sdp_list_t *proto_list)
 {
 	sdp_list_t *list;
diff --git a/attrib/gatt.h b/attrib/gatt.h
index e2c8c90a8add..0fe81db06fe6 100644
--- a/attrib/gatt.h
+++ b/attrib/gatt.h
@@ -9,101 +9,12 @@
  *
  */
 
-/*
- * GATT Characteristic Property bit field
- * Reference: Core SPEC 4.1 page 2183 (Table 3.5: Characteristic Properties
- * bit field) defines how the Characteristic Value can be used, or how the
- * characteristic descriptors (see Section 3.3.3 - page 2184) can be accessed.
- * In the core spec, regular properties are included in the characteristic
- * declaration, and the extended properties are defined as descriptor.
- */
-
-#define GATT_CHR_PROP_BROADCAST				0x01
-#define GATT_CHR_PROP_READ				0x02
-#define GATT_CHR_PROP_WRITE_WITHOUT_RESP		0x04
-#define GATT_CHR_PROP_WRITE				0x08
-#define GATT_CHR_PROP_NOTIFY				0x10
-#define GATT_CHR_PROP_INDICATE				0x20
-#define GATT_CHR_PROP_AUTH				0x40
-#define GATT_CHR_PROP_EXT_PROP				0x80
-
-/* Client Characteristic Configuration bit field */
-#define GATT_CLIENT_CHARAC_CFG_NOTIF_BIT	0x0001
-#define GATT_CLIENT_CHARAC_CFG_IND_BIT		0x0002
-
-typedef void (*gatt_cb_t) (uint8_t status, GSList *l, void *user_data);
-
 struct gatt_primary {
 	char uuid[MAX_LEN_UUID_STR + 1];
 	gboolean changed;
 	struct att_range range;
 };
 
-struct gatt_included {
-	char uuid[MAX_LEN_UUID_STR + 1];
-	uint16_t handle;
-	struct att_range range;
-};
-
-struct gatt_char {
-	char uuid[MAX_LEN_UUID_STR + 1];
-	uint16_t handle;
-	uint8_t properties;
-	uint16_t value_handle;
-};
-
-struct gatt_desc {
-	char uuid[MAX_LEN_UUID_STR + 1];
-	uint16_t handle;
-	uint16_t uuid16;
-};
-
-guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func,
-							gpointer user_data);
-
-unsigned int gatt_find_included(GAttrib *attrib, uint16_t start, uint16_t end,
-					gatt_cb_t func, gpointer user_data);
-
-guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
-					bt_uuid_t *uuid, gatt_cb_t func,
-					gpointer user_data);
-
-guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func,
-							gpointer user_data);
-
-guint gatt_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value,
-					size_t vlen, GAttribResultFunc func,
-					gpointer user_data);
-
-guint gatt_discover_desc(GAttrib *attrib, uint16_t start, uint16_t end,
-						bt_uuid_t *uuid, gatt_cb_t func,
-						gpointer user_data);
-
-guint gatt_reliable_write_char(GAttrib *attrib, uint16_t handle,
-					const uint8_t *value, size_t vlen,
-					GAttribResultFunc func,
-					gpointer user_data);
-
-guint gatt_execute_write(GAttrib *attrib, uint8_t flags,
-				GAttribResultFunc func, gpointer user_data);
-
-guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value,
-			int vlen, GDestroyNotify notify, gpointer user_data);
-
-guint gatt_signed_write_cmd(GAttrib *attrib, uint16_t handle,
-						const uint8_t *value, int vlen,
-						struct bt_crypto *crypto,
-						const uint8_t csrk[16],
-						uint32_t sign_cnt,
-						GDestroyNotify notify,
-						gpointer user_data);
-guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
-				bt_uuid_t *uuid, GAttribResultFunc func,
-				gpointer user_data);
-
-guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func,
-							gpointer user_data);
-
 gboolean gatt_parse_record(const sdp_record_t *rec,
 					uuid_t *prim_uuid, uint16_t *psm,
 					uint16_t *start, uint16_t *end);
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
deleted file mode 100644
index 3a988e131e94..000000000000
--- a/attrib/gattrib.c
+++ /dev/null
@@ -1,473 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <string.h>
-
-#include <glib.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/uuid.h"
-
-#include "btio/btio.h"
-#include "src/log.h"
-#include "src/shared/util.h"
-#include "src/shared/att.h"
-#include "src/shared/gatt-helpers.h"
-#include "src/shared/queue.h"
-#include "src/shared/gatt-db.h"
-#include "src/shared/gatt-client.h"
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-
-struct _GAttrib {
-	int ref_count;
-	struct bt_att *att;
-	struct bt_gatt_client *client;
-	GIOChannel *io;
-	GDestroyNotify destroy;
-	gpointer destroy_user_data;
-	struct queue *callbacks;
-	uint8_t *buf;
-	int buflen;
-	struct queue *track_ids;
-};
-
-struct attrib_callbacks {
-	unsigned int id;
-	GAttribResultFunc result_func;
-	GAttribNotifyFunc notify_func;
-	GDestroyNotify destroy_func;
-	gpointer user_data;
-	GAttrib *parent;
-	uint16_t notify_handle;
-};
-
-GAttrib *g_attrib_new(GIOChannel *io, guint16 mtu, bool ext_signed)
-{
-	gint fd;
-	GAttrib *attr;
-
-	if (!io)
-		return NULL;
-
-	fd = g_io_channel_unix_get_fd(io);
-	attr = new0(GAttrib, 1);
-	if (!attr)
-		return NULL;
-
-	g_io_channel_ref(io);
-	attr->io = io;
-
-	attr->att = bt_att_new(fd, ext_signed);
-	if (!attr->att)
-		goto fail;
-
-	bt_att_set_close_on_unref(attr->att, true);
-	g_io_channel_set_close_on_unref(io, FALSE);
-
-	if (!bt_att_set_mtu(attr->att, mtu))
-		goto fail;
-
-	attr->buf = malloc0(mtu);
-	attr->buflen = mtu;
-	if (!attr->buf)
-		goto fail;
-
-	attr->callbacks = queue_new();
-	if (!attr->callbacks)
-		goto fail;
-
-	attr->track_ids = queue_new();
-	if (!attr->track_ids)
-		goto fail;
-
-	return g_attrib_ref(attr);
-
-fail:
-	free(attr->buf);
-	bt_att_unref(attr->att);
-	g_io_channel_unref(io);
-	free(attr);
-	return NULL;
-}
-
-GAttrib *g_attrib_ref(GAttrib *attrib)
-{
-	if (!attrib)
-		return NULL;
-
-	__sync_fetch_and_add(&attrib->ref_count, 1);
-
-	DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count);
-
-	return attrib;
-}
-
-static void attrib_callbacks_destroy(void *data)
-{
-	struct attrib_callbacks *cb = data;
-
-	if (cb->destroy_func)
-		cb->destroy_func(cb->user_data);
-
-	free(data);
-}
-
-static void attrib_callbacks_remove(void *data)
-{
-	struct attrib_callbacks *cb = data;
-
-	if (!data || !queue_remove(cb->parent->callbacks, data))
-		return;
-
-	attrib_callbacks_destroy(data);
-}
-
-void g_attrib_unref(GAttrib *attrib)
-{
-	if (!attrib)
-		return;
-
-	DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1);
-
-	if (__sync_sub_and_fetch(&attrib->ref_count, 1))
-		return;
-
-	if (attrib->destroy)
-		attrib->destroy(attrib->destroy_user_data);
-
-	bt_gatt_client_unref(attrib->client);
-	bt_att_unref(attrib->att);
-
-	queue_destroy(attrib->callbacks, attrib_callbacks_destroy);
-	queue_destroy(attrib->track_ids, NULL);
-
-	free(attrib->buf);
-
-	g_io_channel_unref(attrib->io);
-
-	free(attrib);
-}
-
-GIOChannel *g_attrib_get_channel(GAttrib *attrib)
-{
-	if (!attrib)
-		return NULL;
-
-	return attrib->io;
-}
-
-struct bt_att *g_attrib_get_att(GAttrib *attrib)
-{
-	if (!attrib)
-		return NULL;
-
-	return attrib->att;
-}
-
-gboolean g_attrib_set_destroy_function(GAttrib *attrib, GDestroyNotify destroy,
-							gpointer user_data)
-{
-	if (!attrib)
-		return FALSE;
-
-	attrib->destroy = destroy;
-	attrib->destroy_user_data = user_data;
-
-	return TRUE;
-}
-
-
-static uint8_t *construct_full_pdu(uint8_t opcode, const void *pdu,
-								uint16_t length)
-{
-	uint8_t *buf = malloc0(length + 1);
-
-	if (!buf)
-		return NULL;
-
-	buf[0] = opcode;
-
-	if (pdu && length)
-		memcpy(buf + 1, pdu, length);
-
-	return buf;
-}
-
-static void attrib_callback_result(uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
-{
-	uint8_t *buf;
-	struct attrib_callbacks *cb = user_data;
-	guint8 status = 0;
-
-	if (!cb)
-		return;
-
-	buf = construct_full_pdu(opcode, pdu, length);
-	if (!buf)
-		return;
-
-	if (opcode == BT_ATT_OP_ERROR_RSP) {
-		/* Error code is the third byte of the PDU data */
-		if (length < 4)
-			status = BT_ATT_ERROR_UNLIKELY;
-		else
-			status = ((guint8 *)pdu)[3];
-	}
-
-	if (cb->result_func)
-		cb->result_func(status, buf, length + 1, cb->user_data);
-
-	free(buf);
-}
-
-static void attrib_callback_notify(struct bt_att_chan *chan, uint16_t mtu,
-					uint8_t opcode, const void *pdu,
-					uint16_t length, void *user_data)
-{
-	uint8_t *buf;
-	struct attrib_callbacks *cb = user_data;
-
-	if (!cb || !cb->notify_func)
-		return;
-
-	if (cb->notify_handle != GATTRIB_ALL_HANDLES && length < 2)
-		return;
-
-	if (cb->notify_handle != GATTRIB_ALL_HANDLES &&
-					cb->notify_handle != get_le16(pdu))
-		return;
-
-	buf = construct_full_pdu(opcode, pdu, length);
-	if (!buf)
-		return;
-
-	cb->notify_func(buf, length + 1, cb->user_data);
-
-	free(buf);
-}
-
-guint g_attrib_send(GAttrib *attrib, guint id, const guint8 *pdu, guint16 len,
-				GAttribResultFunc func, gpointer user_data,
-				GDestroyNotify notify)
-{
-	struct attrib_callbacks *cb = NULL;
-	bt_att_response_func_t response_cb = NULL;
-	bt_att_destroy_func_t destroy_cb = NULL;
-
-	if (!attrib)
-		return 0;
-
-	if (!pdu || !len)
-		return 0;
-
-	if (func || notify) {
-		cb = new0(struct attrib_callbacks, 1);
-		if (!cb)
-			return 0;
-		cb->result_func = func;
-		cb->user_data = user_data;
-		cb->destroy_func = notify;
-		cb->parent = attrib;
-		queue_push_head(attrib->callbacks, cb);
-		response_cb = attrib_callback_result;
-		destroy_cb = attrib_callbacks_remove;
-
-	}
-
-	if (id == 0)
-		id = bt_att_send(attrib->att, pdu[0], (void *) pdu + 1,
-					len - 1, response_cb, cb, destroy_cb);
-	else {
-		int err;
-
-		err = bt_att_resend(attrib->att, id, pdu[0], (void *) pdu + 1,
-					len - 1, response_cb, cb, destroy_cb);
-		if (err)
-			return 0;
-	}
-
-	if (!id)
-		return id;
-
-	/*
-	 * If user what us to use given id, lets keep track on that so we give
-	 * user a possibility to cancel ongoing request.
-	 */
-	if (cb) {
-		cb->id = id;
-		queue_push_tail(attrib->track_ids, UINT_TO_PTR(id));
-	}
-
-	return id;
-}
-
-gboolean g_attrib_cancel(GAttrib *attrib, guint id)
-{
-	if (!attrib)
-		return FALSE;
-
-	return bt_att_cancel(attrib->att, id);
-}
-
-static void cancel_request(void *data, void *user_data)
-{
-	unsigned int id = PTR_TO_UINT(data);
-	GAttrib *attrib = user_data;
-
-	bt_att_cancel(attrib->att, id);
-}
-
-gboolean g_attrib_cancel_all(GAttrib *attrib)
-{
-	if (!attrib)
-		return FALSE;
-
-	queue_foreach(attrib->track_ids, cancel_request, attrib);
-	queue_remove_all(attrib->track_ids, NULL, NULL, NULL);
-
-	return TRUE;
-}
-
-static void client_notify_cb(uint16_t value_handle, const uint8_t *value,
-				uint16_t length, void *user_data)
-{
-	uint8_t *buf = newa(uint8_t, length + 2);
-
-	put_le16(value_handle, buf);
-
-	if (length)
-		memcpy(buf + 2, value, length);
-
-	attrib_callback_notify(NULL, 0, ATT_OP_HANDLE_NOTIFY, buf, length + 2,
-							user_data);
-}
-
-guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle,
-				GAttribNotifyFunc func, gpointer user_data,
-				GDestroyNotify notify)
-{
-	struct attrib_callbacks *cb = NULL;
-
-	if (!attrib)
-		return 0;
-
-	if (func || notify) {
-		cb = new0(struct attrib_callbacks, 1);
-		if (!cb)
-			return 0;
-		cb->notify_func = func;
-		cb->notify_handle = handle;
-		cb->user_data = user_data;
-		cb->destroy_func = notify;
-		cb->parent = attrib;
-		queue_push_head(attrib->callbacks, cb);
-	}
-
-	if (opcode == ATT_OP_HANDLE_NOTIFY && attrib->client) {
-		unsigned int id;
-
-		id = bt_gatt_client_register_notify(attrib->client, handle,
-						NULL, client_notify_cb, cb,
-						attrib_callbacks_remove);
-		if (id)
-			return id;
-	}
-
-	if (opcode == GATTRIB_ALL_REQS)
-		opcode = BT_ATT_ALL_REQUESTS;
-
-	return bt_att_register(attrib->att, opcode, attrib_callback_notify,
-						cb, attrib_callbacks_remove);
-}
-
-uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len)
-{
-	uint16_t mtu;
-
-	if (!attrib || !len)
-		return NULL;
-
-	mtu = bt_att_get_mtu(attrib->att);
-
-	/*
-	 * Clients of this expect a buffer to use.
-	 *
-	 * Pdu encoding in shared/att verifies if whole buffer fits the mtu,
-	 * thus we should set the buflen also when mtu is reduced. But we
-	 * need to reallocate the buffer only if mtu is larger.
-	 */
-	if (mtu > attrib->buflen)
-		attrib->buf = g_realloc(attrib->buf, mtu);
-
-	attrib->buflen = mtu;
-	*len = attrib->buflen;
-	return attrib->buf;
-}
-
-gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu)
-{
-	if (!attrib)
-		return FALSE;
-
-	/*
-	 * Clients of this expect a buffer to use.
-	 *
-	 * Pdu encoding in sharred/att verifies if whole buffer fits the mtu,
-	 * thus we should set the buflen also when mtu is reduced. But we
-	 * need to reallocate the buffer only if mtu is larger.
-	 */
-	if (mtu > attrib->buflen)
-		attrib->buf = g_realloc(attrib->buf, mtu);
-
-	attrib->buflen = mtu;
-
-	return bt_att_set_mtu(attrib->att, mtu);
-}
-
-gboolean g_attrib_attach_client(GAttrib *attrib, struct bt_gatt_client *client)
-{
-	if (!attrib || !client)
-		return FALSE;
-
-	if (attrib->client)
-		bt_gatt_client_unref(attrib->client);
-
-	attrib->client = bt_gatt_client_clone(client);
-	if (!attrib->client)
-		return FALSE;
-
-	return TRUE;
-}
-
-gboolean g_attrib_unregister(GAttrib *attrib, guint id)
-{
-	if (!attrib)
-		return FALSE;
-
-	return bt_att_unregister(attrib->att, id);
-}
-
-gboolean g_attrib_unregister_all(GAttrib *attrib)
-{
-	if (!attrib)
-		return false;
-
-	return bt_att_unregister_all(attrib->att);
-}
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
deleted file mode 100644
index 0111bfc3f2fa..000000000000
--- a/attrib/gattrib.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-#ifndef __GATTRIB_H
-#define __GATTRIB_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define GATTRIB_ALL_REQS 0xFE
-#define GATTRIB_ALL_HANDLES 0x0000
-
-struct bt_att;  /* Forward declaration for compatibility */
-struct bt_gatt_client;  /* Forward declaration for compatibility */
-struct _GAttrib;
-typedef struct _GAttrib GAttrib;
-
-typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu,
-					guint16 len, gpointer user_data);
-typedef void (*GAttribDisconnectFunc)(gpointer user_data);
-typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data);
-typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len,
-							gpointer user_data);
-
-GAttrib *g_attrib_new(GIOChannel *io, guint16 mtu, bool ext_signed);
-GAttrib *g_attrib_ref(GAttrib *attrib);
-void g_attrib_unref(GAttrib *attrib);
-
-GIOChannel *g_attrib_get_channel(GAttrib *attrib);
-
-struct bt_att *g_attrib_get_att(GAttrib *attrib);
-
-gboolean g_attrib_set_destroy_function(GAttrib *attrib,
-		GDestroyNotify destroy, gpointer user_data);
-
-guint g_attrib_send(GAttrib *attrib, guint id, const guint8 *pdu, guint16 len,
-			GAttribResultFunc func, gpointer user_data,
-			GDestroyNotify notify);
-
-gboolean g_attrib_cancel(GAttrib *attrib, guint id);
-gboolean g_attrib_cancel_all(GAttrib *attrib);
-
-guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle,
-				GAttribNotifyFunc func, gpointer user_data,
-				GDestroyNotify notify);
-
-uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len);
-gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu);
-gboolean g_attrib_attach_client(GAttrib *attrib, struct bt_gatt_client *client);
-
-gboolean g_attrib_unregister(GAttrib *attrib, guint id);
-gboolean g_attrib_unregister_all(GAttrib *attrib);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/attrib/gatttool.c b/attrib/gatttool.c
deleted file mode 100644
index c984bef9dd39..000000000000
--- a/attrib/gatttool.c
+++ /dev/null
@@ -1,612 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <glib.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/hci.h"
-#include "bluetooth/hci_lib.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/att-types.h"
-#include "src/shared/util.h"
-#include "att.h"
-#include "btio/btio.h"
-#include "gattrib.h"
-#include "gatt.h"
-#include "gatttool.h"
-
-static char *opt_src = NULL;
-static char *opt_dst = NULL;
-static char *opt_dst_type = NULL;
-static char *opt_value = NULL;
-static char *opt_sec_level = NULL;
-static bt_uuid_t *opt_uuid = NULL;
-static int opt_start = 0x0001;
-static int opt_end = 0xffff;
-static int opt_handle = -1;
-static int opt_mtu = 0;
-static int opt_psm = 0;
-static gboolean opt_primary = FALSE;
-static gboolean opt_characteristics = FALSE;
-static gboolean opt_char_read = FALSE;
-static gboolean opt_listen = FALSE;
-static gboolean opt_char_desc = FALSE;
-static gboolean opt_char_write = FALSE;
-static gboolean opt_char_write_req = FALSE;
-static gboolean opt_interactive = FALSE;
-static GMainLoop *event_loop;
-static gboolean got_error = FALSE;
-static GSourceFunc operation;
-
-struct characteristic_data {
-	GAttrib *attrib;
-	uint16_t start;
-	uint16_t end;
-};
-
-static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-	uint8_t *opdu;
-	uint16_t handle, i, olen = 0;
-	size_t plen;
-
-	handle = get_le16(&pdu[1]);
-
-	switch (pdu[0]) {
-	case ATT_OP_HANDLE_NOTIFY:
-		g_print("Notification handle = 0x%04x value: ", handle);
-		break;
-	case ATT_OP_HANDLE_IND:
-		g_print("Indication   handle = 0x%04x value: ", handle);
-		break;
-	default:
-		g_print("Invalid opcode\n");
-		return;
-	}
-
-	for (i = 3; i < len; i++)
-		g_print("%02x ", pdu[i]);
-
-	g_print("\n");
-
-	if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
-		return;
-
-	opdu = g_attrib_get_buffer(attrib, &plen);
-	olen = enc_confirmation(opdu, plen);
-
-	if (olen > 0)
-		g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL);
-}
-
-static gboolean listen_start(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-
-	g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES,
-						events_handler, attrib, NULL);
-	g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES,
-						events_handler, attrib, NULL);
-
-	return FALSE;
-}
-
-static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
-{
-	GAttrib *attrib;
-	uint16_t mtu;
-	uint16_t cid;
-	GError *gerr = NULL;
-
-	if (err) {
-		g_printerr("%s\n", err->message);
-		got_error = TRUE;
-		g_main_loop_quit(event_loop);
-	}
-
-	bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &mtu,
-				BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID);
-
-	if (gerr) {
-		g_printerr("Can't detect MTU, using default: %s",
-								gerr->message);
-		g_error_free(gerr);
-		mtu = ATT_DEFAULT_LE_MTU;
-	}
-
-	if (cid == BT_ATT_CID)
-		mtu = ATT_DEFAULT_LE_MTU;
-
-	attrib = g_attrib_new(io, mtu, false);
-
-	if (opt_listen)
-		g_idle_add(listen_start, attrib);
-
-	operation(attrib);
-}
-
-static void primary_all_cb(uint8_t status, GSList *services, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		g_printerr("Discover all primary services failed: %s\n",
-							att_ecode2str(status));
-		goto done;
-	}
-
-	for (l = services; l; l = l->next) {
-		struct gatt_primary *prim = l->data;
-		g_print("attr handle = 0x%04x, end grp handle = 0x%04x "
-			"uuid: %s\n", prim->range.start, prim->range.end, prim->uuid);
-	}
-
-done:
-	g_main_loop_quit(event_loop);
-}
-
-static void primary_by_uuid_cb(uint8_t status, GSList *ranges, void *user_data)
-{
-	GSList *l;
-
-	if (status != 0) {
-		g_printerr("Discover primary services by UUID failed: %s\n",
-							att_ecode2str(status));
-		goto done;
-	}
-
-	for (l = ranges; l; l = l->next) {
-		struct att_range *range = l->data;
-		g_print("Starting handle: %04x Ending handle: %04x\n",
-						range->start, range->end);
-	}
-
-done:
-	g_main_loop_quit(event_loop);
-}
-
-static gboolean primary(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-
-	if (opt_uuid)
-		gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb,
-									NULL);
-	else
-		gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
-
-	return FALSE;
-}
-
-static void char_discovered_cb(uint8_t status, GSList *characteristics,
-								void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		g_printerr("Discover all characteristics failed: %s\n",
-							att_ecode2str(status));
-		goto done;
-	}
-
-	for (l = characteristics; l; l = l->next) {
-		struct gatt_char *chars = l->data;
-
-		g_print("handle = 0x%04x, char properties = 0x%02x, char value "
-			"handle = 0x%04x, uuid = %s\n", chars->handle,
-			chars->properties, chars->value_handle, chars->uuid);
-	}
-
-done:
-	g_main_loop_quit(event_loop);
-}
-
-static gboolean characteristics(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-
-	gatt_discover_char(attrib, opt_start, opt_end, opt_uuid,
-						char_discovered_cb, NULL);
-
-	return FALSE;
-}
-
-static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	uint8_t value[plen];
-	ssize_t vlen;
-	int i;
-
-	if (status != 0) {
-		g_printerr("Characteristic value/descriptor read failed: %s\n",
-							att_ecode2str(status));
-		goto done;
-	}
-
-	vlen = dec_read_resp(pdu, plen, value, sizeof(value));
-	if (vlen < 0) {
-		g_printerr("Protocol error\n");
-		goto done;
-	}
-	g_print("Characteristic value/descriptor: ");
-	for (i = 0; i < vlen; i++)
-		g_print("%02x ", value[i]);
-	g_print("\n");
-
-done:
-	if (!opt_listen)
-		g_main_loop_quit(event_loop);
-}
-
-static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct att_data_list *list;
-	int i;
-
-	if (status != 0) {
-		g_printerr("Read characteristics by UUID failed: %s\n",
-							att_ecode2str(status));
-		goto done;
-	}
-
-	list = dec_read_by_type_resp(pdu, plen);
-	if (list == NULL)
-		goto done;
-
-	for (i = 0; i < list->num; i++) {
-		uint8_t *value = list->data[i];
-		int j;
-
-		g_print("handle: 0x%04x \t value: ", get_le16(value));
-		value += 2;
-		for (j = 0; j < list->len - 2; j++, value++)
-			g_print("%02x ", *value);
-		g_print("\n");
-	}
-
-	att_data_list_free(list);
-
-done:
-	g_main_loop_quit(event_loop);
-}
-
-static gboolean characteristics_read(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-
-	if (opt_uuid != NULL) {
-
-		gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid,
-						char_read_by_uuid_cb, NULL);
-
-		return FALSE;
-	}
-
-	if (opt_handle <= 0) {
-		g_printerr("A valid handle is required\n");
-		g_main_loop_quit(event_loop);
-		return FALSE;
-	}
-
-	gatt_read_char(attrib, opt_handle, char_read_cb, attrib);
-
-	return FALSE;
-}
-
-static void mainloop_quit(gpointer user_data)
-{
-	uint8_t *value = user_data;
-
-	g_free(value);
-	g_main_loop_quit(event_loop);
-}
-
-static gboolean characteristics_write(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-	uint8_t *value;
-	size_t len;
-
-	if (opt_handle <= 0) {
-		g_printerr("A valid handle is required\n");
-		goto error;
-	}
-
-	if (opt_value == NULL || opt_value[0] == '\0') {
-		g_printerr("A value is required\n");
-		goto error;
-	}
-
-	len = gatt_attr_data_from_string(opt_value, &value);
-	if (len == 0) {
-		g_printerr("Invalid value\n");
-		goto error;
-	}
-
-	gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value);
-
-	g_free(value);
-	return FALSE;
-
-error:
-	g_main_loop_quit(event_loop);
-	return FALSE;
-}
-
-static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	if (status != 0) {
-		g_printerr("Characteristic Write Request failed: "
-						"%s\n", att_ecode2str(status));
-		goto done;
-	}
-
-	if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) {
-		g_printerr("Protocol error\n");
-		goto done;
-	}
-
-	g_print("Characteristic value was written successfully\n");
-
-done:
-	if (!opt_listen)
-		g_main_loop_quit(event_loop);
-}
-
-static gboolean characteristics_write_req(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-	uint8_t *value;
-	size_t len;
-
-	if (opt_handle <= 0) {
-		g_printerr("A valid handle is required\n");
-		goto error;
-	}
-
-	if (opt_value == NULL || opt_value[0] == '\0') {
-		g_printerr("A value is required\n");
-		goto error;
-	}
-
-	len = gatt_attr_data_from_string(opt_value, &value);
-	if (len == 0) {
-		g_printerr("Invalid value\n");
-		goto error;
-	}
-
-	gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb,
-									NULL);
-
-	g_free(value);
-	return FALSE;
-
-error:
-	g_main_loop_quit(event_loop);
-	return FALSE;
-}
-
-static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		g_printerr("Discover descriptors failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	for (l = descriptors; l; l = l->next) {
-		struct gatt_desc *desc = l->data;
-
-		g_print("handle = 0x%04x, uuid = %s\n", desc->handle,
-								desc->uuid);
-	}
-
-	if (!opt_listen)
-		g_main_loop_quit(event_loop);
-}
-
-static gboolean characteristics_desc(gpointer user_data)
-{
-	GAttrib *attrib = user_data;
-
-	gatt_discover_desc(attrib, opt_start, opt_end, NULL, char_desc_cb,
-									NULL);
-
-	return FALSE;
-}
-
-static gboolean parse_uuid(const char *key, const char *value,
-				gpointer user_data, GError **error)
-{
-	if (!value)
-		return FALSE;
-
-	opt_uuid = g_try_malloc(sizeof(bt_uuid_t));
-	if (opt_uuid == NULL)
-		return FALSE;
-
-	if (bt_string_to_uuid(opt_uuid, value) < 0)
-		return FALSE;
-
-	return TRUE;
-}
-
-static const GOptionEntry primary_char_options[] = {
-	{ "start", 's' , 0, G_OPTION_ARG_INT, &opt_start,
-		"Starting handle (optional)", "0x0001" },
-	{ "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end,
-		"Ending handle (optional)", "0xffff" },
-	{ "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
-		parse_uuid, "UUID16 or UUID128 (optional)", "0x1801"},
-	{ NULL },
-};
-
-static const GOptionEntry char_rw_options[] = {
-	{ "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle,
-		"Read/Write characteristic by handle (required)", "0x0001" },
-	{ "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value,
-		"Write characteristic value (required for write operation)",
-		"0x0001" },
-	{NULL},
-};
-
-static const GOptionEntry gatt_options[] = {
-	{ "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary,
-		"Primary Service Discovery", NULL },
-	{ "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics,
-		"Characteristics Discovery", NULL },
-	{ "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read,
-		"Characteristics Value/Descriptor Read", NULL },
-	{ "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write,
-		"Characteristics Value Write Without Response (Write Command)",
-		NULL },
-	{ "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req,
-		"Characteristics Value Write (Write Request)", NULL },
-	{ "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc,
-		"Characteristics Descriptor Discovery", NULL },
-	{ "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen,
-		"Listen for notifications and indications", NULL },
-	{ "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
-		&opt_interactive, "Use interactive mode", NULL },
-	{ NULL },
-};
-
-static const GOptionEntry options[] = {
-	{ "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src,
-		"Specify local adapter interface", "hciX" },
-	{ "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst,
-		"Specify remote Bluetooth address", "MAC" },
-	{ "addr-type", 't', 0, G_OPTION_ARG_STRING, &opt_dst_type,
-		"Set LE address type. Default: public", "[public | random]"},
-	{ "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu,
-		"Specify the MTU size", "MTU" },
-	{ "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm,
-		"Specify the PSM for GATT/ATT over BR/EDR", "PSM" },
-	{ "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level,
-		"Set security level. Default: low", "[low | medium | high]"},
-	{ NULL },
-};
-
-int main(int argc, char *argv[])
-{
-	GOptionContext *context;
-	GOptionGroup *gatt_group, *params_group, *char_rw_group;
-	GError *gerr = NULL;
-	GIOChannel *chan;
-
-	opt_dst_type = g_strdup("public");
-	opt_sec_level = g_strdup("low");
-
-	context = g_option_context_new(NULL);
-	g_option_context_add_main_entries(context, options, NULL);
-
-	/* GATT commands */
-	gatt_group = g_option_group_new("gatt", "GATT commands",
-					"Show all GATT commands", NULL, NULL);
-	g_option_context_add_group(context, gatt_group);
-	g_option_group_add_entries(gatt_group, gatt_options);
-
-	/* Primary Services and Characteristics arguments */
-	params_group = g_option_group_new("params",
-			"Primary Services/Characteristics arguments",
-			"Show all Primary Services/Characteristics arguments",
-			NULL, NULL);
-	g_option_context_add_group(context, params_group);
-	g_option_group_add_entries(params_group, primary_char_options);
-
-	/* Characteristics value/descriptor read/write arguments */
-	char_rw_group = g_option_group_new("char-read-write",
-		"Characteristics Value/Descriptor Read/Write arguments",
-		"Show all Characteristics Value/Descriptor Read/Write "
-		"arguments",
-		NULL, NULL);
-	g_option_context_add_group(context, char_rw_group);
-	g_option_group_add_entries(char_rw_group, char_rw_options);
-
-	if (!g_option_context_parse(context, &argc, &argv, &gerr)) {
-		g_printerr("%s\n", gerr->message);
-		g_clear_error(&gerr);
-	}
-
-	if (opt_interactive) {
-		interactive(opt_src, opt_dst, opt_dst_type, opt_psm);
-		goto done;
-	}
-
-	if (opt_primary)
-		operation = primary;
-	else if (opt_characteristics)
-		operation = characteristics;
-	else if (opt_char_read)
-		operation = characteristics_read;
-	else if (opt_char_write)
-		operation = characteristics_write;
-	else if (opt_char_write_req)
-		operation = characteristics_write_req;
-	else if (opt_char_desc)
-		operation = characteristics_desc;
-	else {
-		char *help = g_option_context_get_help(context, TRUE, NULL);
-		g_print("%s\n", help);
-		g_free(help);
-		got_error = TRUE;
-		goto done;
-	}
-
-	if (opt_dst == NULL) {
-		g_print("Remote Bluetooth address required\n");
-		got_error = TRUE;
-		goto done;
-	}
-
-	chan = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level,
-					opt_psm, opt_mtu, connect_cb, &gerr);
-	if (chan == NULL) {
-		g_printerr("%s\n", gerr->message);
-		g_clear_error(&gerr);
-		got_error = TRUE;
-		goto done;
-	}
-
-	event_loop = g_main_loop_new(NULL, FALSE);
-
-	g_main_loop_run(event_loop);
-
-	g_main_loop_unref(event_loop);
-
-done:
-	g_option_context_free(context);
-	g_free(opt_src);
-	g_free(opt_dst);
-	g_free(opt_uuid);
-	g_free(opt_sec_level);
-
-	if (got_error)
-		exit(EXIT_FAILURE);
-	else
-		exit(EXIT_SUCCESS);
-}
diff --git a/attrib/gatttool.h b/attrib/gatttool.h
deleted file mode 100644
index 20cb42417e34..000000000000
--- a/attrib/gatttool.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2011  Nokia Corporation
- *
- *
- */
-
-int interactive(const char *src, const char *dst, const char *dst_type,
-								int psm);
-GIOChannel *gatt_connect(const char *src, const char *dst,
-			const char *dst_type, const char *sec_level,
-			int psm, int mtu, BtIOConnect connect_cb,
-			GError **gerr);
-size_t gatt_attr_data_from_string(const char *str, uint8_t **data);
diff --git a/attrib/interactive.c b/attrib/interactive.c
deleted file mode 100644
index 0291fa876533..000000000000
--- a/attrib/interactive.c
+++ /dev/null
@@ -1,1020 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2011  Nokia Corporation
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#define _GNU_SOURCE
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <signal.h>
-#include <sys/signalfd.h>
-#include <glib.h>
-
-#include <readline/readline.h>
-#include <readline/history.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/att-types.h"
-#include "src/shared/util.h"
-#include "btio/btio.h"
-#include "att.h"
-#include "gattrib.h"
-#include "gatt.h"
-#include "gatttool.h"
-#include "client/display.h"
-
-static GIOChannel *iochannel = NULL;
-static GAttrib *attrib = NULL;
-static GMainLoop *event_loop;
-static GString *prompt;
-
-static char *opt_src = NULL;
-static char *opt_dst = NULL;
-static char *opt_dst_type = NULL;
-static char *opt_sec_level = NULL;
-static int opt_psm = 0;
-static int opt_mtu = 0;
-static int start;
-static int end;
-
-static void cmd_help(int argcp, char **argvp);
-
-static enum state {
-	STATE_DISCONNECTED,
-	STATE_CONNECTING,
-	STATE_CONNECTED
-} conn_state;
-
-#define error(fmt, arg...) \
-	rl_printf(COLOR_RED "Error: " COLOR_OFF fmt, ## arg)
-
-#define failed(fmt, arg...) \
-	rl_printf(COLOR_RED "Command Failed: " COLOR_OFF fmt, ## arg)
-
-static char *get_prompt(void)
-{
-	if (conn_state == STATE_CONNECTED)
-		g_string_assign(prompt, COLOR_BLUE);
-	else
-		g_string_assign(prompt, "");
-
-	if (opt_dst)
-		g_string_append_printf(prompt, "[%17s]", opt_dst);
-	else
-		g_string_append_printf(prompt, "[%17s]", "");
-
-	if (conn_state == STATE_CONNECTED)
-		g_string_append(prompt, COLOR_OFF);
-
-	if (opt_psm)
-		g_string_append(prompt, "[BR]");
-	else
-		g_string_append(prompt, "[LE]");
-
-	g_string_append(prompt, "> ");
-
-	return prompt->str;
-}
-
-
-static void set_state(enum state st)
-{
-	conn_state = st;
-	rl_set_prompt(get_prompt());
-}
-
-static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
-{
-	uint8_t *opdu;
-	uint16_t handle, i, olen;
-	size_t plen;
-	GString *s;
-
-	handle = get_le16(&pdu[1]);
-
-	switch (pdu[0]) {
-	case ATT_OP_HANDLE_NOTIFY:
-		s = g_string_new(NULL);
-		g_string_printf(s, "Notification handle = 0x%04x value: ",
-									handle);
-		break;
-	case ATT_OP_HANDLE_IND:
-		s = g_string_new(NULL);
-		g_string_printf(s, "Indication   handle = 0x%04x value: ",
-									handle);
-		break;
-	default:
-		error("Invalid opcode\n");
-		return;
-	}
-
-	for (i = 3; i < len; i++)
-		g_string_append_printf(s, "%02x ", pdu[i]);
-
-	rl_printf("%s\n", s->str);
-	g_string_free(s, TRUE);
-
-	if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
-		return;
-
-	opdu = g_attrib_get_buffer(attrib, &plen);
-	olen = enc_confirmation(opdu, plen);
-
-	if (olen > 0)
-		g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL);
-}
-
-static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
-{
-	uint16_t mtu;
-	uint16_t cid;
-
-	if (err) {
-		set_state(STATE_DISCONNECTED);
-		error("%s\n", err->message);
-		return;
-	}
-
-	bt_io_get(io, &err, BT_IO_OPT_IMTU, &mtu,
-				BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID);
-
-	if (err) {
-		g_printerr("Can't detect MTU, using default: %s", err->message);
-		g_error_free(err);
-		mtu = ATT_DEFAULT_LE_MTU;
-	}
-
-	if (cid == BT_ATT_CID)
-		mtu = ATT_DEFAULT_LE_MTU;
-
-	attrib = g_attrib_new(iochannel, mtu, false);
-	g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES,
-						events_handler, attrib, NULL);
-	g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES,
-						events_handler, attrib, NULL);
-	set_state(STATE_CONNECTED);
-	rl_printf("Connection successful\n");
-}
-
-static void disconnect_io(void)
-{
-	if (conn_state == STATE_DISCONNECTED)
-		return;
-
-	g_attrib_unref(attrib);
-	attrib = NULL;
-	opt_mtu = 0;
-
-	g_io_channel_shutdown(iochannel, FALSE, NULL);
-	g_io_channel_unref(iochannel);
-	iochannel = NULL;
-
-	set_state(STATE_DISCONNECTED);
-}
-
-static void primary_all_cb(uint8_t status, GSList *services, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		error("Discover all primary services failed: %s\n",
-						att_ecode2str(status));
-		return;
-	}
-
-	if (services == NULL) {
-		error("No primary service found\n");
-		return;
-	}
-
-	for (l = services; l; l = l->next) {
-		struct gatt_primary *prim = l->data;
-		rl_printf("attr handle: 0x%04x, end grp handle: 0x%04x uuid: %s\n",
-				prim->range.start, prim->range.end, prim->uuid);
-	}
-}
-
-static void primary_by_uuid_cb(uint8_t status, GSList *ranges, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		error("Discover primary services by UUID failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	if (ranges == NULL) {
-		error("No service UUID found\n");
-		return;
-	}
-
-	for (l = ranges; l; l = l->next) {
-		struct att_range *range = l->data;
-		rl_printf("Starting handle: 0x%04x Ending handle: 0x%04x\n",
-						range->start, range->end);
-	}
-}
-
-static void included_cb(uint8_t status, GSList *includes, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		error("Find included services failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	if (includes == NULL) {
-		rl_printf("No included services found for this range\n");
-		return;
-	}
-
-	for (l = includes; l; l = l->next) {
-		struct gatt_included *incl = l->data;
-		rl_printf("handle: 0x%04x, start handle: 0x%04x, "
-					"end handle: 0x%04x uuid: %s\n",
-					incl->handle, incl->range.start,
-					incl->range.end, incl->uuid);
-	}
-}
-
-static void char_cb(uint8_t status, GSList *characteristics, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		error("Discover all characteristics failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	for (l = characteristics; l; l = l->next) {
-		struct gatt_char *chars = l->data;
-
-		rl_printf("handle: 0x%04x, char properties: 0x%02x, char value "
-				"handle: 0x%04x, uuid: %s\n", chars->handle,
-				chars->properties, chars->value_handle,
-				chars->uuid);
-	}
-}
-
-static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data)
-{
-	GSList *l;
-
-	if (status) {
-		error("Discover descriptors failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	for (l = descriptors; l; l = l->next) {
-		struct gatt_desc *desc = l->data;
-
-		rl_printf("handle: 0x%04x, uuid: %s\n", desc->handle,
-								desc->uuid);
-	}
-}
-
-static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	uint8_t value[plen];
-	ssize_t vlen;
-	int i;
-	GString *s;
-
-	if (status != 0) {
-		error("Characteristic value/descriptor read failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	vlen = dec_read_resp(pdu, plen, value, sizeof(value));
-	if (vlen < 0) {
-		error("Protocol error\n");
-		return;
-	}
-
-	s = g_string_new("Characteristic value/descriptor: ");
-	for (i = 0; i < vlen; i++)
-		g_string_append_printf(s, "%02x ", value[i]);
-
-	rl_printf("%s\n", s->str);
-	g_string_free(s, TRUE);
-}
-
-static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
-					guint16 plen, gpointer user_data)
-{
-	struct att_data_list *list;
-	int i;
-	GString *s;
-
-	if (status != 0) {
-		error("Read characteristics by UUID failed: %s\n",
-							att_ecode2str(status));
-		return;
-	}
-
-	list = dec_read_by_type_resp(pdu, plen);
-	if (list == NULL)
-		return;
-
-	s = g_string_new(NULL);
-	for (i = 0; i < list->num; i++) {
-		uint8_t *value = list->data[i];
-		int j;
-
-		g_string_printf(s, "handle: 0x%04x \t value: ",
-							get_le16(value));
-		value += 2;
-		for (j = 0; j < list->len - 2; j++, value++)
-			g_string_append_printf(s, "%02x ", *value);
-
-		rl_printf("%s\n", s->str);
-	}
-
-	att_data_list_free(list);
-	g_string_free(s, TRUE);
-}
-
-static void cmd_exit(int argcp, char **argvp)
-{
-	rl_callback_handler_remove();
-	g_main_loop_quit(event_loop);
-}
-
-static gboolean channel_watcher(GIOChannel *chan, GIOCondition cond,
-				gpointer user_data)
-{
-	disconnect_io();
-
-	return FALSE;
-}
-
-static void cmd_connect(int argcp, char **argvp)
-{
-	GError *gerr = NULL;
-
-	if (conn_state != STATE_DISCONNECTED)
-		return;
-
-	if (argcp > 1) {
-		g_free(opt_dst);
-		opt_dst = g_strdup(argvp[1]);
-
-		g_free(opt_dst_type);
-		if (argcp > 2)
-			opt_dst_type = g_strdup(argvp[2]);
-		else
-			opt_dst_type = g_strdup("public");
-	}
-
-	if (opt_dst == NULL) {
-		error("Remote Bluetooth address required\n");
-		return;
-	}
-
-	rl_printf("Attempting to connect to %s\n", opt_dst);
-	set_state(STATE_CONNECTING);
-	iochannel = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level,
-					opt_psm, opt_mtu, connect_cb, &gerr);
-	if (iochannel == NULL) {
-		set_state(STATE_DISCONNECTED);
-		error("%s\n", gerr->message);
-		g_error_free(gerr);
-	} else
-		g_io_add_watch(iochannel, G_IO_HUP, channel_watcher, NULL);
-}
-
-static void cmd_disconnect(int argcp, char **argvp)
-{
-	disconnect_io();
-}
-
-static void cmd_primary(int argcp, char **argvp)
-{
-	bt_uuid_t uuid;
-
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp == 1) {
-		gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
-		return;
-	}
-
-	if (bt_string_to_uuid(&uuid, argvp[1]) < 0) {
-		error("Invalid UUID\n");
-		return;
-	}
-
-	gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL);
-}
-
-static int strtohandle(const char *src)
-{
-	char *e;
-	int dst;
-
-	errno = 0;
-	dst = strtoll(src, &e, 16);
-	if (errno != 0 || *e != '\0')
-		return -EINVAL;
-
-	return dst;
-}
-
-static void cmd_included(int argcp, char **argvp)
-{
-	int start = 0x0001;
-	int end = 0xffff;
-
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp > 1) {
-		start = strtohandle(argvp[1]);
-		if (start < 0) {
-			error("Invalid start handle: %s\n", argvp[1]);
-			return;
-		}
-		end = start;
-	}
-
-	if (argcp > 2) {
-		end = strtohandle(argvp[2]);
-		if (end < 0) {
-			error("Invalid end handle: %s\n", argvp[2]);
-			return;
-		}
-	}
-
-	gatt_find_included(attrib, start, end, included_cb, NULL);
-}
-
-static void cmd_char(int argcp, char **argvp)
-{
-	int start = 0x0001;
-	int end = 0xffff;
-
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp > 1) {
-		start = strtohandle(argvp[1]);
-		if (start < 0) {
-			error("Invalid start handle: %s\n", argvp[1]);
-			return;
-		}
-	}
-
-	if (argcp > 2) {
-		end = strtohandle(argvp[2]);
-		if (end < 0) {
-			error("Invalid end handle: %s\n", argvp[2]);
-			return;
-		}
-	}
-
-	if (argcp > 3) {
-		bt_uuid_t uuid;
-
-		if (bt_string_to_uuid(&uuid, argvp[3]) < 0) {
-			error("Invalid UUID\n");
-			return;
-		}
-
-		gatt_discover_char(attrib, start, end, &uuid, char_cb, NULL);
-		return;
-	}
-
-	gatt_discover_char(attrib, start, end, NULL, char_cb, NULL);
-}
-
-static void cmd_char_desc(int argcp, char **argvp)
-{
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp > 1) {
-		start = strtohandle(argvp[1]);
-		if (start < 0) {
-			error("Invalid start handle: %s\n", argvp[1]);
-			return;
-		}
-	} else
-		start = 0x0001;
-
-	if (argcp > 2) {
-		end = strtohandle(argvp[2]);
-		if (end < 0) {
-			error("Invalid end handle: %s\n", argvp[2]);
-			return;
-		}
-	} else
-		end = 0xffff;
-
-	gatt_discover_desc(attrib, start, end, NULL, char_desc_cb, NULL);
-}
-
-static void cmd_read_hnd(int argcp, char **argvp)
-{
-	int handle;
-
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp < 2) {
-		error("Missing argument: handle\n");
-		return;
-	}
-
-	handle = strtohandle(argvp[1]);
-	if (handle < 0) {
-		error("Invalid handle: %s\n", argvp[1]);
-		return;
-	}
-
-	gatt_read_char(attrib, handle, char_read_cb, attrib);
-}
-
-static void cmd_read_uuid(int argcp, char **argvp)
-{
-	int start = 0x0001;
-	int end = 0xffff;
-	bt_uuid_t uuid;
-
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp < 2) {
-		error("Missing argument: UUID\n");
-		return;
-	}
-
-	if (bt_string_to_uuid(&uuid, argvp[1]) < 0) {
-		error("Invalid UUID\n");
-		return;
-	}
-
-	if (argcp > 2) {
-		start = strtohandle(argvp[2]);
-		if (start < 0) {
-			error("Invalid start handle: %s\n", argvp[1]);
-			return;
-		}
-	}
-
-	if (argcp > 3) {
-		end = strtohandle(argvp[3]);
-		if (end < 0) {
-			error("Invalid end handle: %s\n", argvp[2]);
-			return;
-		}
-	}
-
-	gatt_read_char_by_uuid(attrib, start, end, &uuid, char_read_by_uuid_cb,
-									NULL);
-}
-
-static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	if (status != 0) {
-		error("Characteristic Write Request failed: "
-						"%s\n", att_ecode2str(status));
-		return;
-	}
-
-	if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) {
-		error("Protocol error\n");
-		return;
-	}
-
-	rl_printf("Characteristic value was written successfully\n");
-}
-
-static void cmd_char_write(int argcp, char **argvp)
-{
-	uint8_t *value;
-	size_t plen;
-	int handle;
-
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (argcp < 3) {
-		rl_printf("Usage: %s <handle> <new value>\n", argvp[0]);
-		return;
-	}
-
-	handle = strtohandle(argvp[1]);
-	if (handle <= 0) {
-		error("A valid handle is required\n");
-		return;
-	}
-
-	plen = gatt_attr_data_from_string(argvp[2], &value);
-	if (plen == 0) {
-		error("Invalid value\n");
-		return;
-	}
-
-	if (g_strcmp0("char-write-req", argvp[0]) == 0)
-		gatt_write_char(attrib, handle, value, plen,
-					char_write_req_cb, NULL);
-	else
-		gatt_write_cmd(attrib, handle, value, plen, NULL, NULL);
-
-	g_free(value);
-}
-
-static void cmd_sec_level(int argcp, char **argvp)
-{
-	GError *gerr = NULL;
-	BtIOSecLevel sec_level;
-
-	if (argcp < 2) {
-		rl_printf("sec-level: %s\n", opt_sec_level);
-		return;
-	}
-
-	if (strcasecmp(argvp[1], "medium") == 0)
-		sec_level = BT_IO_SEC_MEDIUM;
-	else if (strcasecmp(argvp[1], "high") == 0)
-		sec_level = BT_IO_SEC_HIGH;
-	else if (strcasecmp(argvp[1], "low") == 0)
-		sec_level = BT_IO_SEC_LOW;
-	else {
-		rl_printf("Allowed values: low | medium | high\n");
-		return;
-	}
-
-	g_free(opt_sec_level);
-	opt_sec_level = g_strdup(argvp[1]);
-
-	if (conn_state != STATE_CONNECTED)
-		return;
-
-	if (opt_psm) {
-		rl_printf("Change will take effect on reconnection\n");
-		return;
-	}
-
-	bt_io_set(iochannel, &gerr,
-			BT_IO_OPT_SEC_LEVEL, sec_level,
-			BT_IO_OPT_INVALID);
-	if (gerr) {
-		error("%s\n", gerr->message);
-		g_error_free(gerr);
-	}
-}
-
-static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen,
-							gpointer user_data)
-{
-	uint16_t mtu;
-
-	if (status != 0) {
-		error("Exchange MTU Request failed: %s\n",
-						att_ecode2str(status));
-		return;
-	}
-
-	if (!dec_mtu_resp(pdu, plen, &mtu)) {
-		error("Protocol error\n");
-		return;
-	}
-
-	mtu = MIN(mtu, opt_mtu);
-	/* Set new value for MTU in client */
-	if (g_attrib_set_mtu(attrib, mtu))
-		rl_printf("MTU was exchanged successfully: %d\n", mtu);
-	else
-		error("Error exchanging MTU\n");
-}
-
-static void cmd_mtu(int argcp, char **argvp)
-{
-	if (conn_state != STATE_CONNECTED) {
-		failed("Disconnected\n");
-		return;
-	}
-
-	if (opt_psm) {
-		failed("Operation is only available for LE transport.\n");
-		return;
-	}
-
-	if (argcp < 2) {
-		rl_printf("Usage: mtu <value>\n");
-		return;
-	}
-
-	if (opt_mtu) {
-		failed("MTU exchange can only occur once per connection.\n");
-		return;
-	}
-
-	errno = 0;
-	opt_mtu = strtoll(argvp[1], NULL, 0);
-	if (errno != 0 || opt_mtu < ATT_DEFAULT_LE_MTU) {
-		error("Invalid value. Minimum MTU size is %d\n",
-							ATT_DEFAULT_LE_MTU);
-		return;
-	}
-
-	gatt_exchange_mtu(attrib, opt_mtu, exchange_mtu_cb, NULL);
-}
-
-static const struct {
-	const char *cmd;
-	void (*func)(int argcp, char **argvp);
-	const char *params;
-	const char *desc;
-} commands[] = {
-	{ "help",		cmd_help,	"",
-		"Show this help"},
-	{ "exit",		cmd_exit,	"",
-		"Exit interactive mode" },
-	{ "quit",		cmd_exit,	"",
-		"Exit interactive mode" },
-	{ "connect",		cmd_connect,	"[address [address type]]",
-		"Connect to a remote device" },
-	{ "disconnect",		cmd_disconnect,	"",
-		"Disconnect from a remote device" },
-	{ "primary",		cmd_primary,	"[UUID]",
-		"Primary Service Discovery" },
-	{ "included",		cmd_included,	"[start hnd [end hnd]]",
-		"Find Included Services" },
-	{ "characteristics",	cmd_char,	"[start hnd [end hnd [UUID]]]",
-		"Characteristics Discovery" },
-	{ "char-desc",		cmd_char_desc,	"[start hnd] [end hnd]",
-		"Characteristics Descriptor Discovery" },
-	{ "char-read-hnd",	cmd_read_hnd,	"<handle>",
-		"Characteristics Value/Descriptor Read by handle" },
-	{ "char-read-uuid",	cmd_read_uuid,	"<UUID> [start hnd] [end hnd]",
-		"Characteristics Value/Descriptor Read by UUID" },
-	{ "char-write-req",	cmd_char_write,	"<handle> <new value>",
-		"Characteristic Value Write (Write Request)" },
-	{ "char-write-cmd",	cmd_char_write,	"<handle> <new value>",
-		"Characteristic Value Write (No response)" },
-	{ "sec-level",		cmd_sec_level,	"[low | medium | high]",
-		"Set security level. Default: low" },
-	{ "mtu",		cmd_mtu,	"<value>",
-		"Exchange MTU for GATT/ATT" },
-	{ NULL, NULL, NULL}
-};
-
-static void cmd_help(int argcp, char **argvp)
-{
-	int i;
-
-	for (i = 0; commands[i].cmd; i++)
-		rl_printf("%-15s %-30s %s\n", commands[i].cmd,
-				commands[i].params, commands[i].desc);
-}
-
-static void parse_line(char *line_read)
-{
-	char **argvp;
-	int argcp;
-	int i;
-
-	if (line_read == NULL) {
-		rl_printf("\n");
-		cmd_exit(0, NULL);
-		return;
-	}
-
-	line_read = g_strstrip(line_read);
-
-	if (*line_read == '\0')
-		goto done;
-
-	add_history(line_read);
-
-	if (g_shell_parse_argv(line_read, &argcp, &argvp, NULL) == FALSE)
-		goto done;
-
-	for (i = 0; commands[i].cmd; i++)
-		if (strcasecmp(commands[i].cmd, argvp[0]) == 0)
-			break;
-
-	if (commands[i].cmd)
-		commands[i].func(argcp, argvp);
-	else
-		error("%s: command not found\n", argvp[0]);
-
-	g_strfreev(argvp);
-
-done:
-	free(line_read);
-}
-
-static gboolean prompt_read(GIOChannel *chan, GIOCondition cond,
-							gpointer user_data)
-{
-	if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
-		g_io_channel_unref(chan);
-		return FALSE;
-	}
-
-	rl_callback_read_char();
-
-	return TRUE;
-}
-
-static char *completion_generator(const char *text, int state)
-{
-	static int index = 0, len = 0;
-	const char *cmd = NULL;
-
-	if (state == 0) {
-		index = 0;
-		len = strlen(text);
-	}
-
-	while ((cmd = commands[index].cmd) != NULL) {
-		index++;
-		if (strncmp(cmd, text, len) == 0)
-			return strdup(cmd);
-	}
-
-	return NULL;
-}
-
-static char **commands_completion(const char *text, int start, int end)
-{
-	if (start == 0)
-		return rl_completion_matches(text, &completion_generator);
-	else
-		return NULL;
-}
-
-static guint setup_standard_input(void)
-{
-	GIOChannel *channel;
-	guint source;
-
-	channel = g_io_channel_unix_new(fileno(stdin));
-
-	source = g_io_add_watch(channel,
-				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-				prompt_read, NULL);
-
-	g_io_channel_unref(channel);
-
-	return source;
-}
-
-static gboolean signal_handler(GIOChannel *channel, GIOCondition condition,
-							gpointer user_data)
-{
-	static unsigned int __terminated = 0;
-	struct signalfd_siginfo si;
-	ssize_t result;
-	int fd;
-
-	if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
-		g_main_loop_quit(event_loop);
-		return FALSE;
-	}
-
-	fd = g_io_channel_unix_get_fd(channel);
-
-	result = read(fd, &si, sizeof(si));
-	if (result != sizeof(si))
-		return FALSE;
-
-	switch (si.ssi_signo) {
-	case SIGINT:
-		rl_replace_line("", 0);
-		rl_crlf();
-		rl_on_new_line();
-		rl_redisplay();
-		break;
-	case SIGTERM:
-		if (__terminated == 0) {
-			rl_replace_line("", 0);
-			rl_crlf();
-			g_main_loop_quit(event_loop);
-		}
-
-		__terminated = 1;
-		break;
-	}
-
-	return TRUE;
-}
-
-static guint setup_signalfd(void)
-{
-	GIOChannel *channel;
-	guint source;
-	sigset_t mask;
-	int fd;
-
-	sigemptyset(&mask);
-	sigaddset(&mask, SIGINT);
-	sigaddset(&mask, SIGTERM);
-
-	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
-		perror("Failed to set signal mask");
-		return 0;
-	}
-
-	fd = signalfd(-1, &mask, 0);
-	if (fd < 0) {
-		perror("Failed to create signal descriptor");
-		return 0;
-	}
-
-	channel = g_io_channel_unix_new(fd);
-
-	g_io_channel_set_close_on_unref(channel, TRUE);
-	g_io_channel_set_encoding(channel, NULL, NULL);
-	g_io_channel_set_buffered(channel, FALSE);
-
-	source = g_io_add_watch(channel,
-				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-				signal_handler, NULL);
-
-	g_io_channel_unref(channel);
-
-	return source;
-}
-
-int interactive(const char *src, const char *dst,
-		const char *dst_type, int psm)
-{
-	guint input;
-	guint signal;
-
-	opt_sec_level = g_strdup("low");
-
-	opt_src = g_strdup(src);
-	opt_dst = g_strdup(dst);
-	opt_dst_type = g_strdup(dst_type);
-	opt_psm = psm;
-
-	prompt = g_string_new(NULL);
-
-	event_loop = g_main_loop_new(NULL, FALSE);
-
-	input = setup_standard_input();
-	signal = setup_signalfd();
-
-	rl_attempted_completion_function = commands_completion;
-	rl_erase_empty_line = 1;
-	rl_callback_handler_install(get_prompt(), parse_line);
-
-	g_main_loop_run(event_loop);
-
-	rl_callback_handler_remove();
-	cmd_disconnect(0, NULL);
-	g_source_remove(input);
-	g_source_remove(signal);
-	g_main_loop_unref(event_loop);
-	g_string_free(prompt, TRUE);
-
-	g_free(opt_src);
-	g_free(opt_dst);
-	g_free(opt_sec_level);
-
-	return 0;
-}
diff --git a/attrib/utils.c b/attrib/utils.c
deleted file mode 100644
index ca05baaa1e8a..000000000000
--- a/attrib/utils.c
+++ /dev/null
@@ -1,110 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2011  Nokia Corporation
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-
-#include <glib.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/hci.h"
-#include "bluetooth/hci_lib.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/att-types.h"
-#include "btio/btio.h"
-#include "att.h"
-#include "gattrib.h"
-#include "gatt.h"
-#include "gatttool.h"
-
-GIOChannel *gatt_connect(const char *src, const char *dst,
-				const char *dst_type, const char *sec_level,
-				int psm, int mtu, BtIOConnect connect_cb,
-				GError **gerr)
-{
-	GIOChannel *chan;
-	bdaddr_t sba, dba;
-	uint8_t dest_type;
-	GError *tmp_err = NULL;
-	BtIOSecLevel sec;
-
-	str2ba(dst, &dba);
-
-	/* Local adapter */
-	if (src != NULL) {
-		if (!strncmp(src, "hci", 3))
-			hci_devba(atoi(src + 3), &sba);
-		else
-			str2ba(src, &sba);
-	} else
-		bacpy(&sba, BDADDR_ANY);
-
-	/* Not used for BR/EDR */
-	if (strcmp(dst_type, "random") == 0)
-		dest_type = BDADDR_LE_RANDOM;
-	else
-		dest_type = BDADDR_LE_PUBLIC;
-
-	if (strcmp(sec_level, "medium") == 0)
-		sec = BT_IO_SEC_MEDIUM;
-	else if (strcmp(sec_level, "high") == 0)
-		sec = BT_IO_SEC_HIGH;
-	else
-		sec = BT_IO_SEC_LOW;
-
-	if (psm == 0)
-		chan = bt_io_connect(connect_cb, NULL, NULL, &tmp_err,
-				BT_IO_OPT_SOURCE_BDADDR, &sba,
-				BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC,
-				BT_IO_OPT_DEST_BDADDR, &dba,
-				BT_IO_OPT_DEST_TYPE, dest_type,
-				BT_IO_OPT_CID, BT_ATT_CID,
-				BT_IO_OPT_SEC_LEVEL, sec,
-				BT_IO_OPT_INVALID);
-	else
-		chan = bt_io_connect(connect_cb, NULL, NULL, &tmp_err,
-				BT_IO_OPT_SOURCE_BDADDR, &sba,
-				BT_IO_OPT_DEST_BDADDR, &dba,
-				BT_IO_OPT_PSM, psm,
-				BT_IO_OPT_IMTU, mtu,
-				BT_IO_OPT_SEC_LEVEL, sec,
-				BT_IO_OPT_INVALID);
-
-	if (tmp_err) {
-		g_propagate_error(gerr, tmp_err);
-		return NULL;
-	}
-
-	return chan;
-}
-
-size_t gatt_attr_data_from_string(const char *str, uint8_t **data)
-{
-	char tmp[3];
-	size_t size, i;
-
-	size = strlen(str) / 2;
-	*data = g_try_malloc0(size);
-	if (*data == NULL)
-		return 0;
-
-	tmp[2] = '\0';
-	for (i = 0; i < size; i++) {
-		memcpy(tmp, str + (i * 2), 2);
-		(*data)[i] = (uint8_t) strtol(tmp, NULL, 16);
-	}
-
-	return size;
-}
diff --git a/unit/test-gattrib.c b/unit/test-gattrib.c
deleted file mode 100644
index 81573dfde142..000000000000
--- a/unit/test-gattrib.c
+++ /dev/null
@@ -1,552 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2014  Google, Inc.
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <inttypes.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/socket.h>
-
-#include <glib.h>
-
-#include "src/shared/util.h"
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/uuid.h"
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "src/log.h"
-
-#define DEFAULT_MTU 23
-
-#define data(args...) ((const unsigned char[]) { args })
-
-struct test_pdu {
-	bool valid;
-	bool sent;
-	bool received;
-	const uint8_t *data;
-	size_t size;
-};
-
-#define pdu(args...)				\
-	{					\
-		.valid = true,			\
-		.sent = false,			\
-		.received = false,		\
-		.data = data(args),		\
-		.size = sizeof(data(args)),	\
-	}
-
-struct context {
-	GMainLoop *main_loop;
-	GIOChannel *att_io;
-	GIOChannel *server_io;
-	GAttrib *att;
-};
-
-static void setup_context(struct context *cxt, gconstpointer data)
-{
-	int err, sv[2];
-
-	cxt->main_loop = g_main_loop_new(NULL, FALSE);
-	g_assert(cxt->main_loop != NULL);
-
-	err = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sv);
-	g_assert(err == 0);
-
-	cxt->att_io = g_io_channel_unix_new(sv[0]);
-	g_assert(cxt->att_io != NULL);
-
-	g_io_channel_set_close_on_unref(cxt->att_io, TRUE);
-
-	cxt->server_io = g_io_channel_unix_new(sv[1]);
-	g_assert(cxt->server_io != NULL);
-
-	g_io_channel_set_close_on_unref(cxt->server_io, TRUE);
-	g_io_channel_set_encoding(cxt->server_io, NULL, NULL);
-	g_io_channel_set_buffered(cxt->server_io, FALSE);
-
-	cxt->att = g_attrib_new(cxt->att_io, DEFAULT_MTU, false);
-	g_assert(cxt->att != NULL);
-}
-
-static void teardown_context(struct context *cxt, gconstpointer data)
-{
-	if (cxt->att)
-		g_attrib_unref(cxt->att);
-
-	g_io_channel_unref(cxt->server_io);
-
-	g_io_channel_unref(cxt->att_io);
-
-	g_main_loop_unref(cxt->main_loop);
-}
-
-
-static void test_debug(const char *str, void *user_data)
-{
-	const char *prefix = user_data;
-
-	g_print("%s%s\n", prefix, str);
-}
-
-static void destroy_canary_increment(gpointer data)
-{
-	int *canary = data;
-	(*canary)++;
-}
-
-static void test_refcount(struct context *cxt, gconstpointer unused)
-{
-	GAttrib *extra_ref;
-	int destroy_canary = 0;
-
-	g_attrib_set_destroy_function(cxt->att, destroy_canary_increment,
-							       &destroy_canary);
-
-	extra_ref = g_attrib_ref(cxt->att);
-
-	g_assert(extra_ref == cxt->att);
-
-	g_assert(destroy_canary == 0);
-
-	g_attrib_unref(extra_ref);
-
-	g_assert(destroy_canary == 0);
-
-	g_attrib_unref(cxt->att);
-
-	g_assert(destroy_canary == 1);
-
-	/* Avoid a double-free from the teardown function */
-	cxt->att = NULL;
-}
-
-static void test_get_channel(struct context *cxt, gconstpointer unused)
-{
-	GIOChannel *chan;
-
-	chan = g_attrib_get_channel(cxt->att);
-
-	g_assert(chan == cxt->att_io);
-}
-
-struct expect_response {
-	struct test_pdu expect;
-	struct test_pdu respond;
-	GSourceFunc receive_cb;
-	gpointer user_data;
-};
-
-static gboolean test_client(GIOChannel *channel, GIOCondition cond,
-								  gpointer data)
-{
-	struct expect_response *cr = data;
-	int fd;
-	uint8_t buf[256];
-	ssize_t len;
-	int cmp;
-
-	if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
-		return FALSE;
-
-	fd = g_io_channel_unix_get_fd(channel);
-
-	len = read(fd, buf, sizeof(buf));
-
-	g_assert(len > 0);
-	g_assert_cmpint(len, ==, cr->expect.size);
-
-	if (g_test_verbose())
-		util_hexdump('?', cr->expect.data,  cr->expect.size,
-						   test_debug, "test_client: ");
-
-	cmp = memcmp(cr->expect.data, buf, len);
-
-	g_assert(cmp == 0);
-
-	cr->expect.received = true;
-
-	if (cr->receive_cb != NULL)
-		cr->receive_cb(cr->user_data);
-
-	if (cr->respond.valid) {
-		if (g_test_verbose())
-			util_hexdump('<', cr->respond.data, cr->respond.size,
-						   test_debug, "test_client: ");
-		len = write(fd, cr->respond.data, cr->respond.size);
-
-		g_assert_cmpint(len, ==, cr->respond.size);
-
-		cr->respond.sent = true;
-	}
-
-	return TRUE;
-}
-
-struct result_data {
-	guint8 status;
-	guint8 *pdu;
-	guint16 len;
-	GSourceFunc complete_cb;
-	gpointer user_data;
-};
-
-static void result_canary(guint8 status, const guint8 *pdu, guint16 len,
-								gpointer data)
-{
-	struct result_data *result = data;
-
-	result->status = status;
-	result->pdu = g_malloc0(len);
-	memcpy(result->pdu, pdu, len);
-	result->len = len;
-
-	if (g_test_verbose())
-		util_hexdump('<', pdu, len, test_debug, "result_canary: ");
-
-	if (result->complete_cb != NULL)
-		result->complete_cb(result->user_data);
-}
-
-static gboolean context_stop_main_loop(gpointer user_data)
-{
-	struct context *cxt = user_data;
-
-	g_main_loop_quit(cxt->main_loop);
-	return FALSE;
-}
-
-static void test_send(struct context *cxt, gconstpointer unused)
-{
-	int cmp;
-	struct result_data results;
-	struct expect_response data = {
-		.expect = pdu(0x02, 0x00, 0x02),
-		.respond = pdu(0x03, 0x02, 0x03, 0x04),
-		.receive_cb = NULL,
-		.user_data = NULL,
-	};
-
-	g_io_add_watch(cxt->server_io, G_IO_IN | G_IO_HUP | G_IO_ERR |
-						G_IO_NVAL, test_client, &data);
-
-	results.complete_cb = context_stop_main_loop;
-	results.user_data = cxt;
-
-	g_attrib_send(cxt->att, 0, data.expect.data, data.expect.size,
-				      result_canary, (gpointer) &results, NULL);
-
-	g_main_loop_run(cxt->main_loop);
-
-	g_assert(results.pdu != NULL);
-
-	g_assert_cmpint(results.len, ==, data.respond.size);
-
-	cmp = memcmp(results.pdu, data.respond.data, results.len);
-
-	g_assert(cmp == 0);
-
-	g_free(results.pdu);
-}
-
-struct event_info {
-	struct context *context;
-	int event_id;
-};
-
-static gboolean cancel_existing_attrib_event(gpointer user_data)
-{
-	struct event_info *info = user_data;
-	gboolean canceled;
-
-	canceled = g_attrib_cancel(info->context->att, info->event_id);
-
-	g_assert(canceled);
-
-	g_idle_add(context_stop_main_loop, info->context);
-
-	return FALSE;
-}
-
-static void test_cancel(struct context *cxt, gconstpointer unused)
-{
-	gboolean canceled;
-	struct result_data results;
-	struct event_info info;
-	struct expect_response data = {
-		.expect = pdu(0x02, 0x00, 0x02),
-		.respond = pdu(0x03, 0x02, 0x03, 0x04),
-	};
-
-	g_io_add_watch(cxt->server_io,
-				      G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-							    test_client, &data);
-
-	results.pdu = NULL;
-
-	info.context = cxt;
-	info.event_id = g_attrib_send(cxt->att, 0, data.expect.data,
-						data.expect.size, result_canary,
-								&results, NULL);
-
-	data.receive_cb = cancel_existing_attrib_event;
-	data.user_data = &info;
-
-	g_main_loop_run(cxt->main_loop);
-
-	g_assert(results.pdu == NULL);
-
-	results.pdu = NULL;
-	data.expect.received = false;
-	data.respond.sent = false;
-
-	info.event_id = g_attrib_send(cxt->att, 0, data.expect.data,
-						data.expect.size, result_canary,
-								&results, NULL);
-
-	canceled = g_attrib_cancel(cxt->att, info.event_id);
-	g_assert(canceled);
-
-	g_idle_add(context_stop_main_loop, info.context);
-
-	g_main_loop_run(cxt->main_loop);
-
-	g_assert(!data.expect.received);
-	g_assert(!data.respond.sent);
-	g_assert(results.pdu == NULL);
-
-	/* Invalid ID */
-	canceled = g_attrib_cancel(cxt->att, 42);
-	g_assert(!canceled);
-}
-
-static void send_test_pdus(gpointer context, struct test_pdu *pdus)
-{
-	struct context *cxt = context;
-	size_t len;
-	int fd;
-	struct test_pdu *cur_pdu;
-
-	fd = g_io_channel_unix_get_fd(cxt->server_io);
-
-	for (cur_pdu = pdus; cur_pdu->valid; cur_pdu++)
-		cur_pdu->sent = false;
-
-	for (cur_pdu = pdus; cur_pdu->valid; cur_pdu++) {
-		if (g_test_verbose())
-			util_hexdump('>', cur_pdu->data, cur_pdu->size,
-						test_debug, "send_test_pdus: ");
-		len = write(fd, cur_pdu->data, cur_pdu->size);
-		g_assert_cmpint(len, ==, cur_pdu->size);
-		cur_pdu->sent = true;
-	}
-
-	g_idle_add(context_stop_main_loop, cxt);
-	g_main_loop_run(cxt->main_loop);
-}
-
-#define PDU_MTU_RESP pdu(ATT_OP_MTU_RESP, 0x17)
-#define PDU_FIND_INFO_REQ pdu(ATT_OP_FIND_INFO_REQ, 0x01, 0x00, 0xFF, 0xFF)
-#define PDU_NO_ATT_ERR pdu(ATT_OP_ERROR, ATT_OP_FIND_INFO_REQ, 0x00, 0x00, 0x0A)
-#define PDU_IND_NODATA pdu(ATT_OP_HANDLE_IND, 0x01, 0x00)
-#define PDU_INVALID_IND pdu(ATT_OP_HANDLE_IND, 0x14)
-#define PDU_IND_DATA pdu(ATT_OP_HANDLE_IND, 0x14, 0x00, 0x01)
-
-struct expect_test_data {
-	struct test_pdu *expected;
-	GAttrib *att;
-};
-
-static void notify_canary_expect(const guint8 *pdu, guint16 len, gpointer data)
-{
-	struct expect_test_data *expect = data;
-	struct test_pdu *expected = expect->expected;
-	int cmp;
-
-	if (g_test_verbose())
-		util_hexdump('<', pdu, len, test_debug,
-						      "notify_canary_expect: ");
-
-	while (expected->valid && expected->received)
-		expected++;
-
-	g_assert(expected->valid);
-
-	if (g_test_verbose())
-		util_hexdump('?', expected->data, expected->size, test_debug,
-						      "notify_canary_expect: ");
-
-	g_assert_cmpint(expected->size, ==, len);
-
-	cmp = memcmp(pdu, expected->data, expected->size);
-
-	g_assert(cmp == 0);
-
-	expected->received = true;
-
-	if (pdu[0] == ATT_OP_FIND_INFO_REQ) {
-		struct test_pdu no_attributes = PDU_NO_ATT_ERR;
-		int reqid;
-
-		reqid = g_attrib_send(expect->att, 0, no_attributes.data,
-					  no_attributes.size, NULL, NULL, NULL);
-		g_assert(reqid != 0);
-	}
-}
-
-static void test_register(struct context *cxt, gconstpointer user_data)
-{
-	guint reg_id;
-	gboolean canceled;
-	struct test_pdu pdus[] = {
-		/*
-		 * Unmatched PDU opcode
-		 * Unmatched handle (GATTRIB_ALL_REQS) */
-		PDU_FIND_INFO_REQ,
-		/*
-		 * Matched PDU opcode
-		 * Unmatched handle (GATTRIB_ALL_HANDLES) */
-		PDU_IND_NODATA,
-		/*
-		 * Matched PDU opcode
-		 * Invalid length? */
-		PDU_INVALID_IND,
-		/*
-		 * Matched PDU opcode
-		 * Matched handle */
-		PDU_IND_DATA,
-		{ },
-	};
-	struct test_pdu req_pdus[] = { PDU_FIND_INFO_REQ, { } };
-	struct test_pdu all_ind_pdus[] = {
-		PDU_IND_NODATA,
-		PDU_INVALID_IND,
-		PDU_IND_DATA,
-		{ },
-	};
-	struct test_pdu followed_ind_pdus[] = { PDU_IND_DATA, { } };
-	struct test_pdu *current_pdu;
-	struct expect_test_data expect;
-
-	expect.att = cxt->att;
-
-	/*
-	 * Without registering anything, should be able to ignore everything but
-	 * an unexpected response. */
-	send_test_pdus(cxt, pdus);
-
-	if (g_test_verbose())
-		g_print("ALL_REQS, ALL_HANDLES\r\n");
-
-	expect.expected = req_pdus;
-	reg_id = g_attrib_register(cxt->att, GATTRIB_ALL_REQS,
-				      GATTRIB_ALL_HANDLES, notify_canary_expect,
-								 &expect, NULL);
-
-	send_test_pdus(cxt, pdus);
-
-	canceled = g_attrib_unregister(cxt->att, reg_id);
-
-	g_assert(canceled);
-
-	for (current_pdu = req_pdus; current_pdu->valid; current_pdu++)
-		g_assert(current_pdu->received);
-
-	if (g_test_verbose())
-		g_print("IND, ALL_HANDLES\r\n");
-
-	expect.expected = all_ind_pdus;
-	reg_id = g_attrib_register(cxt->att, ATT_OP_HANDLE_IND,
-				      GATTRIB_ALL_HANDLES, notify_canary_expect,
-								 &expect, NULL);
-
-	send_test_pdus(cxt, pdus);
-
-	canceled = g_attrib_unregister(cxt->att, reg_id);
-
-	g_assert(canceled);
-
-	for (current_pdu = all_ind_pdus; current_pdu->valid; current_pdu++)
-		g_assert(current_pdu->received);
-
-	if (g_test_verbose())
-		g_print("IND, 0x0014\r\n");
-
-	expect.expected = followed_ind_pdus;
-	reg_id = g_attrib_register(cxt->att, ATT_OP_HANDLE_IND, 0x0014,
-					notify_canary_expect, &expect, NULL);
-
-	send_test_pdus(cxt, pdus);
-
-	canceled = g_attrib_unregister(cxt->att, reg_id);
-
-	g_assert(canceled);
-
-	for (current_pdu = followed_ind_pdus; current_pdu->valid; current_pdu++)
-		g_assert(current_pdu->received);
-
-	canceled = g_attrib_unregister(cxt->att, reg_id);
-
-	g_assert(!canceled);
-}
-
-static void test_buffers(struct context *cxt, gconstpointer unused)
-{
-	size_t buflen;
-	uint8_t *buf;
-	gboolean success;
-
-	buf = g_attrib_get_buffer(cxt->att, &buflen);
-	g_assert(buf != 0);
-	g_assert_cmpint(buflen, ==, DEFAULT_MTU);
-
-	success = g_attrib_set_mtu(cxt->att, 5);
-	g_assert(!success);
-
-	success = g_attrib_set_mtu(cxt->att, 255);
-	g_assert(success);
-
-	buf = g_attrib_get_buffer(cxt->att, &buflen);
-	g_assert(buf != 0);
-	g_assert_cmpint(buflen, ==, 255);
-}
-
-int main(int argc, char *argv[])
-{
-	g_test_init(&argc, &argv, NULL);
-
-	if (g_test_verbose())
-		__btd_log_init("*", 0);
-
-	/*
-	 * Test the GAttrib API behavior
-	 */
-	g_test_add("/gattrib/refcount", struct context, NULL, setup_context,
-					      test_refcount, teardown_context);
-	g_test_add("/gattrib/get_channel", struct context, NULL, setup_context,
-					    test_get_channel, teardown_context);
-	g_test_add("/gattrib/send", struct context, NULL, setup_context,
-						   test_send, teardown_context);
-	g_test_add("/gattrib/cancel", struct context, NULL, setup_context,
-						 test_cancel, teardown_context);
-	g_test_add("/gattrib/register", struct context, NULL, setup_context,
-					       test_register, teardown_context);
-	g_test_add("/gattrib/buffers", struct context, NULL, setup_context,
-						test_buffers, teardown_context);
-
-	return g_test_run();
-}
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH BlueZ v5 21/21] attrib: Remove directory
  2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
                   ` (19 preceding siblings ...)
  2026-09-28 17:32 ` [PATCH BlueZ v5 20/21] attrib: Remove GAttrib and gatttool Luiz Augusto von Dentz
@ 2026-09-28 17:32 ` Luiz Augusto von Dentz
  20 siblings, 0 replies; 22+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-28 17:32 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

What is left of attrib is only used by bluetoothd: att_ecode2str, which
is moved to src/shared/att as bt_att_ecode2str, and struct gatt_primary
along with gatt_parse_record, which are moved to src/device, so remove
the attrib directory.

Assisted-by: OpenCode:claude-opus-5.5
---
 Makefile.am                      |    4 +-
 attrib/att-database.h            |   30 -
 attrib/att.c                     | 1238 ------------------------------
 attrib/att.h                     |  186 -----
 attrib/gatt.c                    |  103 ---
 attrib/gatt.h                    |   20 -
 profiles/battery/battery.c       |    3 +-
 profiles/deviceinfo/deviceinfo.c |    3 +-
 profiles/midi/midi.c             |    3 +-
 profiles/ranging/rap.c           |    1 -
 profiles/scanparam/scan.c        |    3 +-
 src/adapter.c                    |    3 +-
 src/device.c                     |   78 +-
 src/device.h                     |   13 +
 src/shared/att.c                 |   48 ++
 src/shared/att.h                 |    2 +
 16 files changed, 145 insertions(+), 1593 deletions(-)
 delete mode 100644 attrib/att-database.h
 delete mode 100644 attrib/att.c
 delete mode 100644 attrib/att.h
 delete mode 100644 attrib/gatt.c
 delete mode 100644 attrib/gatt.h

diff --git a/Makefile.am b/Makefile.am
index 33a2f79fbff6..1d2459b2de8f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -286,8 +286,6 @@ src_libshared_ell_la_LDFLAGS = $(AM_LDFLAGS)
 src_libshared_ell_la_CFLAGS = $(AM_CFLAGS)
 endif
 
-attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \
-		attrib/gatt.h attrib/gatt.c
 
 btio_sources = btio/btio.h btio/btio.c
 
@@ -308,7 +306,7 @@ include Makefile.plugins
 pkglibexec_PROGRAMS += src/bluetoothd
 
 bluetoothd_internal_sources = \
-			$(attrib_sources) $(btio_sources) \
+			$(btio_sources) \
 			src/log.h src/log.c \
 			src/backtrace.h src/backtrace.c \
 			src/rfkill.c src/btd.h src/sdpd.h \
diff --git a/attrib/att-database.h b/attrib/att-database.h
deleted file mode 100644
index bb30933eaf04..000000000000
--- a/attrib/att-database.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2012 Texas Instruments Corporation
- *
- */
-
-/* Requirements for read/write operations */
-enum {
-	ATT_NONE,		/* No restrictions */
-	ATT_AUTHENTICATION,	/* Authentication required */
-	ATT_AUTHORIZATION,	/* Authorization required */
-	ATT_NOT_PERMITTED,	/* Operation not permitted */
-};
-
-struct attribute {
-	uint16_t handle;
-	bt_uuid_t uuid;
-	int read_req;		/* Read requirement */
-	int write_req;		/* Write requirement */
-	uint8_t (*read_cb)(struct attribute *a, struct btd_device *device,
-							gpointer user_data);
-	uint8_t (*write_cb)(struct attribute *a, struct btd_device *device,
-							gpointer user_data);
-	gpointer cb_user_data;
-	size_t len;
-	uint8_t *data;
-};
diff --git a/attrib/att.c b/attrib/att.c
deleted file mode 100644
index 686674a9ac74..000000000000
--- a/attrib/att.c
+++ /dev/null
@@ -1,1238 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include <glib.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/uuid.h"
-
-#include "src/shared/util.h"
-#include "att.h"
-
-static inline void put_uuid_le(const bt_uuid_t *src, void *dst)
-{
-	if (src->type == BT_UUID16)
-		put_le16(src->value.u16, dst);
-	else
-		/* Convert from 128-bit BE to LE */
-		bswap_128(&src->value.u128, dst);
-}
-
-const char *att_ecode2str(uint8_t status)
-{
-	switch (status)  {
-	case ATT_ECODE_INVALID_HANDLE:
-		return "Invalid handle";
-	case ATT_ECODE_READ_NOT_PERM:
-		return "Attribute can't be read";
-	case ATT_ECODE_WRITE_NOT_PERM:
-		return "Attribute can't be written";
-	case ATT_ECODE_INVALID_PDU:
-		return "Attribute PDU was invalid";
-	case ATT_ECODE_AUTHENTICATION:
-		return "Attribute requires authentication before read/write";
-	case ATT_ECODE_REQ_NOT_SUPP:
-		return "Server doesn't support the request received";
-	case ATT_ECODE_INVALID_OFFSET:
-		return "Offset past the end of the attribute";
-	case ATT_ECODE_AUTHORIZATION:
-		return "Attribute requires authorization before read/write";
-	case ATT_ECODE_PREP_QUEUE_FULL:
-		return "Too many prepare writes have been queued";
-	case ATT_ECODE_ATTR_NOT_FOUND:
-		return "No attribute found within the given range";
-	case ATT_ECODE_ATTR_NOT_LONG:
-		return "Attribute can't be read/written using Read Blob Req";
-	case ATT_ECODE_INSUFF_ENCR_KEY_SIZE:
-		return "Encryption Key Size is insufficient";
-	case ATT_ECODE_INVAL_ATTR_VALUE_LEN:
-		return "Attribute value length is invalid";
-	case ATT_ECODE_UNLIKELY:
-		return "Request attribute has encountered an unlikely error";
-	case ATT_ECODE_INSUFF_ENC:
-		return "Encryption required before read/write";
-	case ATT_ECODE_UNSUPP_GRP_TYPE:
-		return "Attribute type is not a supported grouping attribute";
-	case ATT_ECODE_INSUFF_RESOURCES:
-		return "Insufficient Resources to complete the request";
-	case ATT_ECODE_IO:
-		return "Internal application error: I/O";
-	case ATT_ECODE_TIMEOUT:
-		return "A timeout occurred";
-	case ATT_ECODE_ABORTED:
-		return "The operation was aborted";
-	default:
-		return "Unexpected error code";
-	}
-}
-
-void att_data_list_free(struct att_data_list *list)
-{
-	if (list == NULL)
-		return;
-
-	if (list->data) {
-		int i;
-		for (i = 0; i < list->num; i++)
-			g_free(list->data[i]);
-	}
-
-	g_free(list->data);
-	g_free(list);
-}
-
-struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len)
-{
-	struct att_data_list *list;
-	int i;
-
-	if (len > UINT8_MAX)
-		return NULL;
-
-	list = g_new0(struct att_data_list, 1);
-	list->len = len;
-	list->num = num;
-
-	list->data = g_malloc0(sizeof(uint8_t *) * num);
-
-	for (i = 0; i < num; i++)
-		list->data[i] = g_malloc0(sizeof(uint8_t) * len);
-
-	return list;
-}
-
-static void get_uuid(uint8_t type, const void *val, bt_uuid_t *uuid)
-{
-	if (type == BT_UUID16)
-		bt_uuid16_create(uuid, get_le16(val));
-	else {
-		uint128_t u128;
-
-		/* Convert from 128-bit LE to BE */
-		bswap_128(val, &u128);
-		bt_uuid128_create(uuid, u128);
-	}
-}
-
-uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
-						uint8_t *pdu, size_t len)
-{
-	uint16_t uuid_len;
-
-	if (!uuid)
-		return 0;
-
-	if (uuid->type == BT_UUID16)
-		uuid_len = 2;
-	else if (uuid->type == BT_UUID128)
-		uuid_len = 16;
-	else
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_READ_BY_GROUP_REQ;
-	/* Starting Handle (2 octets) */
-	put_le16(start, &pdu[1]);
-	/* Ending Handle (2 octets) */
-	put_le16(end, &pdu[3]);
-	/* Attribute Group Type (2 or 16 octet UUID) */
-	put_uuid_le(uuid, &pdu[5]);
-
-	return 5 + uuid_len;
-}
-
-uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start,
-						uint16_t *end, bt_uuid_t *uuid)
-{
-	const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
-	uint8_t type;
-
-	if (pdu == NULL)
-		return 0;
-
-	if (start == NULL || end == NULL || uuid == NULL)
-		return 0;
-
-	if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ)
-		return 0;
-
-	if (len == (min_len + 2))
-		type = BT_UUID16;
-	else if (len == (min_len + 16))
-		type = BT_UUID128;
-	else
-		return 0;
-
-	*start = get_le16(&pdu[1]);
-	*end = get_le16(&pdu[3]);
-
-	get_uuid(type, &pdu[5], uuid);
-
-	return len;
-}
-
-uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu,
-								size_t len)
-{
-	int i;
-	uint16_t w;
-	uint8_t *ptr;
-
-	if (list == NULL)
-		return 0;
-
-	if (len < list->len + sizeof(uint8_t) * 2)
-		return 0;
-
-	pdu[0] = ATT_OP_READ_BY_GROUP_RESP;
-	pdu[1] = list->len;
-
-	ptr = &pdu[2];
-
-	for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
-		memcpy(ptr, list->data[i], list->len);
-		ptr += list->len;
-		w += list->len;
-	}
-
-	return w;
-}
-
-struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len)
-{
-	struct att_data_list *list;
-	const uint8_t *ptr;
-	uint16_t elen, num;
-	int i;
-
-	if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP)
-		return NULL;
-
-	/* PDU must contain at least:
-	 * - Attribute Opcode (1 octet)
-	 * - Length (1 octet)
-	 * - Attribute Data List (at least one entry):
-	 *   - Attribute Handle (2 octets)
-	 *   - End Group Handle (2 octets)
-	 *   - Attribute Value (at least 1 octet) */
-	if (len < 7)
-		return NULL;
-
-	elen = pdu[1];
-	/* Minimum Attribute Data List size */
-	if (elen < 5)
-		return NULL;
-
-	/* Reject incomplete Attribute Data List */
-	if ((len - 2) % elen)
-		return NULL;
-
-	num = (len - 2) / elen;
-	list = att_data_list_alloc(num, elen);
-	if (list == NULL)
-		return NULL;
-
-	ptr = &pdu[2];
-
-	for (i = 0; i < num; i++) {
-		memcpy(list->data[i], ptr, list->len);
-		ptr += list->len;
-	}
-
-	return list;
-}
-
-uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
-					const uint8_t *value, size_t vlen,
-					uint8_t *pdu, size_t len)
-{
-	uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) +
-							sizeof(uint16_t);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (!uuid)
-		return 0;
-
-	if (uuid->type != BT_UUID16)
-		return 0;
-
-	if (vlen > len - min_len)
-		vlen = len - min_len;
-
-	pdu[0] = ATT_OP_FIND_BY_TYPE_REQ;
-	put_le16(start, &pdu[1]);
-	put_le16(end, &pdu[3]);
-	put_le16(uuid->value.u16, &pdu[5]);
-
-	if (vlen > 0) {
-		memcpy(&pdu[7], value, vlen);
-		return min_len + vlen;
-	}
-
-	return min_len;
-}
-
-uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start,
-						uint16_t *end, bt_uuid_t *uuid,
-						uint8_t *value, size_t *vlen)
-{
-	if (pdu == NULL)
-		return 0;
-
-	if (len < 7)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ)
-		return 0;
-
-	/* First requested handle number (2 octets) */
-	*start = get_le16(&pdu[1]);
-	/* Last requested handle number (2 octets) */
-	*end = get_le16(&pdu[3]);
-	/* 16-bit UUID to find (2 octets) */
-	bt_uuid16_create(uuid, get_le16(&pdu[5]));
-
-	/* Attribute value to find */
-	*vlen = len - 7;
-	if (*vlen > 0)
-		memcpy(value, pdu + 7, *vlen);
-
-	return len;
-}
-
-uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, size_t len)
-{
-	GSList *l;
-	uint16_t offset;
-
-	if (!pdu)
-		return 0;
-
-	pdu[0] = ATT_OP_FIND_BY_TYPE_RESP;
-
-	for (l = matches, offset = 1;
-				l && len >= (offset + sizeof(uint16_t) * 2);
-				l = l->next, offset += sizeof(uint16_t) * 2) {
-		struct att_range *range = l->data;
-
-		put_le16(range->start, &pdu[offset]);
-		put_le16(range->end, &pdu[offset + 2]);
-	}
-
-	return offset;
-}
-
-GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len)
-{
-	struct att_range *range;
-	GSList *matches;
-	off_t offset;
-
-	/* PDU should contain at least:
-	 * - Attribute Opcode (1 octet)
-	 * - Handles Information List (at least one entry):
-	 *   - Found Attribute Handle (2 octets)
-	 *   - Group End Handle (2 octets) */
-	if (pdu == NULL || len < 5)
-		return NULL;
-
-	if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP)
-		return NULL;
-
-	/* Reject incomplete Handles Information List */
-	if ((len - 1) % 4)
-		return NULL;
-
-	for (offset = 1, matches = NULL;
-				len >= (offset + sizeof(uint16_t) * 2);
-				offset += sizeof(uint16_t) * 2) {
-		range = g_new0(struct att_range, 1);
-		range->start = get_le16(&pdu[offset]);
-		range->end = get_le16(&pdu[offset + 2]);
-
-		matches = g_slist_append(matches, range);
-	}
-
-	return matches;
-}
-
-uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
-						uint8_t *pdu, size_t len)
-{
-	uint16_t uuid_len;
-
-	if (!uuid)
-		return 0;
-
-	if (uuid->type == BT_UUID16)
-		uuid_len = 2;
-	else if (uuid->type == BT_UUID128)
-		uuid_len = 16;
-	else
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_READ_BY_TYPE_REQ;
-	/* Starting Handle (2 octets) */
-	put_le16(start, &pdu[1]);
-	/* Ending Handle (2 octets) */
-	put_le16(end, &pdu[3]);
-	/* Attribute Type (2 or 16 octet UUID) */
-	put_uuid_le(uuid, &pdu[5]);
-
-	return 5 + uuid_len;
-}
-
-uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start,
-						uint16_t *end, bt_uuid_t *uuid)
-{
-	const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
-	uint8_t type;
-
-	if (pdu == NULL)
-		return 0;
-
-	if (start == NULL || end == NULL || uuid == NULL)
-		return 0;
-
-	if (len == (min_len + 2))
-		type = BT_UUID16;
-	else if (len == (min_len + 16))
-		type = BT_UUID128;
-	else
-		return 0;
-
-	if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ)
-		return 0;
-
-	*start = get_le16(&pdu[1]);
-	*end = get_le16(&pdu[3]);
-
-	get_uuid(type, &pdu[5], uuid);
-
-	return len;
-}
-
-uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu,
-								size_t len)
-{
-	uint8_t *ptr;
-	size_t i, w, l;
-
-	if (list == NULL)
-		return 0;
-
-	if (pdu == NULL)
-		return 0;
-
-	l = MIN(len - 2, list->len);
-
-	pdu[0] = ATT_OP_READ_BY_TYPE_RESP;
-	pdu[1] = l;
-	ptr = &pdu[2];
-
-	for (i = 0, w = 2; i < list->num && w + l <= len; i++) {
-		memcpy(ptr, list->data[i], l);
-		ptr += l;
-		w += l;
-	}
-
-	return w;
-}
-
-struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len)
-{
-	struct att_data_list *list;
-	const uint8_t *ptr;
-	uint16_t elen, num;
-	int i;
-
-	if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP)
-		return NULL;
-
-	/* PDU must contain at least:
-	 * - Attribute Opcode (1 octet)
-	 * - Length (1 octet)
-	 * - Attribute Data List (at least one entry):
-	 *   - Attribute Handle (2 octets)
-	 *   - Attribute Value (at least 1 octet) */
-	if (len < 5)
-		return NULL;
-
-	elen = pdu[1];
-	/* Minimum Attribute Data List size */
-	if (elen < 3)
-		return NULL;
-
-	/* Reject incomplete Attribute Data List */
-	if ((len - 2) % elen)
-		return NULL;
-
-	num = (len - 2) / elen;
-	list = att_data_list_alloc(num, elen);
-	if (list == NULL)
-		return NULL;
-
-	ptr = &pdu[2];
-
-	for (i = 0; i < num; i++) {
-		memcpy(list->data[i], ptr, list->len);
-		ptr += list->len;
-	}
-
-	return list;
-}
-
-uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (vlen > len - min_len)
-		vlen = len - min_len;
-
-	pdu[0] = ATT_OP_WRITE_CMD;
-	put_le16(handle, &pdu[1]);
-
-	if (vlen > 0) {
-		memcpy(&pdu[3], value, vlen);
-		return min_len + vlen;
-	}
-
-	return min_len;
-}
-
-uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint8_t *value, size_t *vlen)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (value == NULL || vlen == NULL || handle == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_WRITE_CMD)
-		return 0;
-
-	*handle = get_le16(&pdu[1]);
-	memcpy(value, pdu + min_len, len - min_len);
-	*vlen = len - min_len;
-
-	return len;
-}
-
-uint16_t enc_signed_write_cmd(uint16_t handle, const uint8_t *value,
-					size_t vlen, struct bt_crypto *crypto,
-					const uint8_t csrk[16],
-					uint32_t sign_cnt,
-					uint8_t *pdu, size_t len)
-{
-	const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(handle);
-	const uint16_t min_len =  hdr_len + ATT_SIGNATURE_LEN;
-
-	if (pdu == NULL)
-		return 0;
-
-	if (vlen > len - min_len)
-		vlen = len - min_len;
-
-	pdu[0] = ATT_OP_SIGNED_WRITE_CMD;
-	put_le16(handle, &pdu[1]);
-
-	if (vlen > 0)
-		memcpy(&pdu[hdr_len], value, vlen);
-
-	if (!bt_crypto_sign_att(crypto, csrk, pdu, hdr_len + vlen, sign_cnt,
-							&pdu[hdr_len + vlen]))
-		return 0;
-
-	return min_len + vlen;
-}
-
-uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len,
-						uint16_t *handle,
-						uint8_t *value, size_t *vlen,
-						uint8_t signature[12])
-{
-	const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(*handle);
-	const uint16_t min_len =  hdr_len + ATT_SIGNATURE_LEN;
-
-
-	if (pdu == NULL)
-		return 0;
-
-	if (value == NULL || vlen == NULL || handle == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_SIGNED_WRITE_CMD)
-		return 0;
-
-	*vlen = len - min_len;
-	*handle = get_le16(&pdu[1]);
-	memcpy(value, pdu + hdr_len, *vlen);
-
-	memcpy(signature, pdu + hdr_len + *vlen, ATT_SIGNATURE_LEN);
-
-	return len;
-}
-
-uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (vlen > len - min_len)
-		vlen = len - min_len;
-
-	pdu[0] = ATT_OP_WRITE_REQ;
-	put_le16(handle, &pdu[1]);
-
-	if (vlen > 0) {
-		memcpy(&pdu[3], value, vlen);
-		return min_len + vlen;
-	}
-
-	return min_len;
-}
-
-uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint8_t *value, size_t *vlen)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (value == NULL || vlen == NULL || handle == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_WRITE_REQ)
-		return 0;
-
-	*handle = get_le16(&pdu[1]);
-	*vlen = len - min_len;
-	if (*vlen > 0)
-		memcpy(value, pdu + min_len, *vlen);
-
-	return len;
-}
-
-uint16_t enc_write_resp(uint8_t *pdu)
-{
-	if (pdu == NULL)
-		return 0;
-
-	pdu[0] = ATT_OP_WRITE_RESP;
-
-	return sizeof(pdu[0]);
-}
-
-uint16_t dec_write_resp(const uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	if (pdu[0] != ATT_OP_WRITE_RESP)
-		return 0;
-
-	return len;
-}
-
-uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_READ_REQ;
-	/* Attribute Handle (2 octets) */
-	put_le16(handle, &pdu[1]);
-
-	return 3;
-}
-
-uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
-								size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_READ_BLOB_REQ;
-	/* Attribute Handle (2 octets) */
-	put_le16(handle, &pdu[1]);
-	/* Value Offset (2 octets) */
-	put_le16(offset, &pdu[3]);
-
-	return 5;
-}
-
-uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (handle == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_READ_REQ)
-		return 0;
-
-	*handle = get_le16(&pdu[1]);
-
-	return min_len;
-}
-
-uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle,
-							uint16_t *offset)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) +
-							sizeof(*offset);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (handle == NULL)
-		return 0;
-
-	if (offset == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_READ_BLOB_REQ)
-		return 0;
-
-	*handle = get_le16(&pdu[1]);
-	*offset = get_le16(&pdu[3]);
-
-	return min_len;
-}
-
-uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* If the attribute value length is longer than the allowed PDU size,
-	 * send only the octets that fit on the PDU. The remaining octets can
-	 * be requested using the Read Blob Request. */
-	if (vlen > len - 1)
-		vlen = len - 1;
-
-	pdu[0] = ATT_OP_READ_RESP;
-
-	memcpy(pdu + 1, value, vlen);
-
-	return vlen + 1;
-}
-
-uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset,
-						uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	vlen -= offset;
-	if (vlen > len - 1)
-		vlen = len - 1;
-
-	pdu[0] = ATT_OP_READ_BLOB_RESP;
-
-	memcpy(pdu + 1, &value[offset], vlen);
-
-	return vlen + 1;
-}
-
-ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value,
-								size_t vlen)
-{
-	if (pdu == NULL)
-		return -EINVAL;
-
-	if (pdu[0] != ATT_OP_READ_RESP)
-		return -EINVAL;
-
-	if (value == NULL)
-		return len - 1;
-
-	if (vlen < (len - 1))
-		return -ENOBUFS;
-
-	memcpy(value, pdu + 1, len - 1);
-
-	return len - 1;
-}
-
-uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
-						uint8_t *pdu, size_t len)
-{
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_ERROR;
-	/* Request Opcode In Error (1 octet) */
-	pdu[1] = opcode;
-	/* Attribute Handle In Error (2 octets) */
-	put_le16(handle, &pdu[2]);
-	/* Error Code (1 octet) */
-	pdu[4] = status;
-
-	return 5;
-}
-
-uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu,
-								size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_FIND_INFO_REQ;
-	/* Starting Handle (2 octets) */
-	put_le16(start, &pdu[1]);
-	/* Ending Handle (2 octets) */
-	put_le16(end, &pdu[3]);
-
-	return 5;
-}
-
-uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start,
-								uint16_t *end)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (start == NULL || end == NULL)
-		return 0;
-
-	if (pdu[0] != ATT_OP_FIND_INFO_REQ)
-		return 0;
-
-	*start = get_le16(&pdu[1]);
-	*end = get_le16(&pdu[3]);
-
-	return min_len;
-}
-
-uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
-						uint8_t *pdu, size_t len)
-{
-	uint8_t *ptr;
-	size_t i, w;
-
-	if (pdu == NULL)
-		return 0;
-
-	if (list == NULL)
-		return 0;
-
-	if (len < list->len + sizeof(uint8_t) * 2)
-		return 0;
-
-	pdu[0] = ATT_OP_FIND_INFO_RESP;
-	pdu[1] = format;
-	ptr = (void *) &pdu[2];
-
-	for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
-		memcpy(ptr, list->data[i], list->len);
-		ptr += list->len;
-		w += list->len;
-	}
-
-	return w;
-}
-
-struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len,
-							uint8_t *format)
-{
-	struct att_data_list *list;
-	uint8_t *ptr;
-	uint16_t elen, num;
-	int i;
-
-	if (pdu == NULL)
-		return 0;
-
-	if (format == NULL)
-		return 0;
-
-	if (pdu[0] != ATT_OP_FIND_INFO_RESP)
-		return 0;
-
-	*format = pdu[1];
-	elen = sizeof(pdu[0]) + sizeof(*format);
-	if (*format == 0x01)
-		elen += 2;
-	else if (*format == 0x02)
-		elen += 16;
-
-	num = (len - 2) / elen;
-
-	ptr = (void *) &pdu[2];
-
-	list = att_data_list_alloc(num, elen);
-	if (list == NULL)
-		return NULL;
-
-	for (i = 0; i < num; i++) {
-		memcpy(list->data[i], ptr, list->len);
-		ptr += list->len;
-	}
-
-	return list;
-}
-
-uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (len < (vlen + min_len))
-		return 0;
-
-	pdu[0] = ATT_OP_HANDLE_NOTIFY;
-	put_le16(handle, &pdu[1]);
-	memcpy(&pdu[3], value, vlen);
-
-	return vlen + min_len;
-}
-
-uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (len < (vlen + min_len))
-		return 0;
-
-	pdu[0] = ATT_OP_HANDLE_IND;
-	put_le16(handle, &pdu[1]);
-	memcpy(&pdu[3], value, vlen);
-
-	return vlen + min_len;
-}
-
-uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint8_t *value, size_t vlen)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
-	uint16_t dlen;
-
-	if (pdu == NULL)
-		return 0;
-
-	if (pdu[0] != ATT_OP_HANDLE_IND)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	dlen = MIN(len - min_len, vlen);
-
-	if (handle)
-		*handle = get_le16(&pdu[1]);
-
-	memcpy(value, &pdu[3], dlen);
-
-	return dlen;
-}
-
-uint16_t enc_confirmation(uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_HANDLE_CNF;
-
-	return 1;
-}
-
-uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_MTU_REQ;
-	/* Client Rx MTU (2 octets) */
-	put_le16(mtu, &pdu[1]);
-
-	return 3;
-}
-
-uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (mtu == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_MTU_REQ)
-		return 0;
-
-	*mtu = get_le16(&pdu[1]);
-
-	return min_len;
-}
-
-uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_MTU_RESP;
-	/* Server Rx MTU (2 octets) */
-	put_le16(mtu, &pdu[1]);
-
-	return 3;
-}
-
-uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (mtu == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_MTU_RESP)
-		return 0;
-
-	*mtu = get_le16(&pdu[1]);
-
-	return min_len;
-}
-
-uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset,
-					const uint8_t *value, size_t vlen,
-					uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) +
-								sizeof(offset);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (vlen > len - min_len)
-		vlen = len - min_len;
-
-	pdu[0] = ATT_OP_PREP_WRITE_REQ;
-	put_le16(handle, &pdu[1]);
-	put_le16(offset, &pdu[3]);
-
-	if (vlen > 0) {
-		memcpy(&pdu[5], value, vlen);
-		return min_len + vlen;
-	}
-
-	return min_len;
-}
-
-uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle,
-				uint16_t *offset, uint8_t *value, size_t *vlen)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) +
-							sizeof(*offset);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (handle == NULL || offset == NULL || value == NULL || vlen == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_PREP_WRITE_REQ)
-		return 0;
-
-	*handle = get_le16(&pdu[1]);
-	*offset = get_le16(&pdu[3]);
-
-	*vlen = len - min_len;
-	if (*vlen > 0)
-		memcpy(value, pdu + min_len, *vlen);
-
-	return len;
-}
-
-uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset,
-					const uint8_t *value, size_t vlen,
-					uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) +
-								sizeof(offset);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (vlen > len - min_len)
-		vlen = len - min_len;
-
-	pdu[0] = ATT_OP_PREP_WRITE_RESP;
-	put_le16(handle, &pdu[1]);
-	put_le16(offset, &pdu[3]);
-
-	if (vlen > 0) {
-		memcpy(&pdu[5], value, vlen);
-		return min_len + vlen;
-	}
-
-	return min_len;
-}
-
-uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle,
-				uint16_t *offset, uint8_t *value, size_t *vlen)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) +
-								sizeof(*offset);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (handle == NULL || offset == NULL || value == NULL || vlen == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_PREP_WRITE_REQ)
-		return 0;
-
-	*handle = get_le16(&pdu[1]);
-	*offset = get_le16(&pdu[3]);
-	*vlen = len - min_len;
-	if (*vlen > 0)
-		memcpy(value, pdu + min_len, *vlen);
-
-	return len;
-}
-
-uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len)
-{
-	if (pdu == NULL)
-		return 0;
-
-	if (flags > 1)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_EXEC_WRITE_REQ;
-	/* Flags (1 octet) */
-	pdu[1] = flags;
-
-	return 2;
-}
-
-uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags)
-{
-	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*flags);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (flags == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_EXEC_WRITE_REQ)
-		return 0;
-
-	*flags = pdu[1];
-
-	return min_len;
-}
-
-uint16_t enc_exec_write_resp(uint8_t *pdu)
-{
-	if (pdu == NULL)
-		return 0;
-
-	/* Attribute Opcode (1 octet) */
-	pdu[0] = ATT_OP_EXEC_WRITE_RESP;
-
-	return 1;
-}
-
-uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len)
-{
-	const uint16_t min_len = sizeof(pdu[0]);
-
-	if (pdu == NULL)
-		return 0;
-
-	if (len < min_len)
-		return 0;
-
-	if (pdu[0] != ATT_OP_EXEC_WRITE_RESP)
-		return 0;
-
-	return len;
-}
diff --git a/attrib/att.h b/attrib/att.h
deleted file mode 100644
index 8d6ca5930859..000000000000
--- a/attrib/att.h
+++ /dev/null
@@ -1,186 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-
-#include "src/shared/crypto.h"
-
-/* Len of signature in write signed packet */
-#define ATT_SIGNATURE_LEN		12
-
-/* Attribute Protocol Opcodes */
-#define ATT_OP_ERROR			0x01
-#define ATT_OP_MTU_REQ			0x02
-#define ATT_OP_MTU_RESP			0x03
-#define ATT_OP_FIND_INFO_REQ		0x04
-#define ATT_OP_FIND_INFO_RESP		0x05
-#define ATT_OP_FIND_BY_TYPE_REQ		0x06
-#define ATT_OP_FIND_BY_TYPE_RESP	0x07
-#define ATT_OP_READ_BY_TYPE_REQ		0x08
-#define ATT_OP_READ_BY_TYPE_RESP	0x09
-#define ATT_OP_READ_REQ			0x0A
-#define ATT_OP_READ_RESP		0x0B
-#define ATT_OP_READ_BLOB_REQ		0x0C
-#define ATT_OP_READ_BLOB_RESP		0x0D
-#define ATT_OP_READ_MULTI_REQ		0x0E
-#define ATT_OP_READ_MULTI_RESP		0x0F
-#define ATT_OP_READ_BY_GROUP_REQ	0x10
-#define ATT_OP_READ_BY_GROUP_RESP	0x11
-#define ATT_OP_WRITE_REQ		0x12
-#define ATT_OP_WRITE_RESP		0x13
-#define ATT_OP_WRITE_CMD		0x52
-#define ATT_OP_PREP_WRITE_REQ		0x16
-#define ATT_OP_PREP_WRITE_RESP		0x17
-#define ATT_OP_EXEC_WRITE_REQ		0x18
-#define ATT_OP_EXEC_WRITE_RESP		0x19
-#define ATT_OP_HANDLE_NOTIFY		0x1B
-#define ATT_OP_HANDLE_IND		0x1D
-#define ATT_OP_HANDLE_CNF		0x1E
-#define ATT_OP_SIGNED_WRITE_CMD		0xD2
-
-/* Error codes for Error response PDU */
-#define ATT_ECODE_INVALID_HANDLE		0x01
-#define ATT_ECODE_READ_NOT_PERM			0x02
-#define ATT_ECODE_WRITE_NOT_PERM		0x03
-#define ATT_ECODE_INVALID_PDU			0x04
-#define ATT_ECODE_AUTHENTICATION		0x05
-#define ATT_ECODE_REQ_NOT_SUPP			0x06
-#define ATT_ECODE_INVALID_OFFSET		0x07
-#define ATT_ECODE_AUTHORIZATION			0x08
-#define ATT_ECODE_PREP_QUEUE_FULL		0x09
-#define ATT_ECODE_ATTR_NOT_FOUND		0x0A
-#define ATT_ECODE_ATTR_NOT_LONG			0x0B
-#define ATT_ECODE_INSUFF_ENCR_KEY_SIZE		0x0C
-#define ATT_ECODE_INVAL_ATTR_VALUE_LEN		0x0D
-#define ATT_ECODE_UNLIKELY			0x0E
-#define ATT_ECODE_INSUFF_ENC			0x0F
-#define ATT_ECODE_UNSUPP_GRP_TYPE		0x10
-#define ATT_ECODE_INSUFF_RESOURCES		0x11
-/* Application error */
-#define ATT_ECODE_IO				0x80
-#define ATT_ECODE_TIMEOUT			0x81
-#define ATT_ECODE_ABORTED			0x82
-
-#define ATT_MAX_VALUE_LEN			512
-#define ATT_DEFAULT_L2CAP_MTU			48
-#define ATT_DEFAULT_LE_MTU			23
-
-/* Flags for Execute Write Request Operation */
-#define ATT_CANCEL_ALL_PREP_WRITES		0x00
-#define ATT_WRITE_ALL_PREP_WRITES		0x01
-
-/* Find Information Response Formats */
-#define ATT_FIND_INFO_RESP_FMT_16BIT		0x01
-#define ATT_FIND_INFO_RESP_FMT_128BIT		0x02
-
-struct att_data_list {
-	uint16_t num;
-	uint16_t len;
-	uint8_t **data;
-};
-
-struct att_range {
-	uint16_t start;
-	uint16_t end;
-};
-
-struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len);
-void att_data_list_free(struct att_data_list *list);
-
-const char *att_ecode2str(uint8_t status);
-uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
-						uint8_t *pdu, size_t len);
-uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start,
-					uint16_t *end, bt_uuid_t *uuid);
-uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu,
-								size_t len);
-uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
-				const uint8_t *value, size_t vlen, uint8_t *pdu,
-				size_t len);
-uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start,
-		uint16_t *end, bt_uuid_t *uuid, uint8_t *value, size_t *vlen);
-uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, size_t len);
-GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len);
-struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len);
-uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
-						uint8_t *pdu, size_t len);
-uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start,
-					uint16_t *end, bt_uuid_t *uuid);
-uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu,
-								size_t len);
-uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len);
-uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint8_t *value, size_t *vlen);
-uint16_t enc_signed_write_cmd(uint16_t handle,
-					const uint8_t *value, size_t vlen,
-					struct bt_crypto *crypto,
-					const uint8_t csrk[16],
-					uint32_t sign_cnt,
-					uint8_t *pdu, size_t len);
-uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len,
-						uint16_t *handle,
-						uint8_t *value, size_t *vlen,
-						uint8_t signature[12]);
-struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len);
-uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len);
-uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint8_t *value, size_t *vlen);
-uint16_t enc_write_resp(uint8_t *pdu);
-uint16_t dec_write_resp(const uint8_t *pdu, size_t len);
-uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len);
-uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
-								size_t len);
-uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle);
-uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle,
-							uint16_t *offset);
-uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len);
-uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset,
-						uint8_t *pdu, size_t len);
-ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value,
-								size_t vlen);
-uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
-						uint8_t *pdu, size_t len);
-uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu,
-								size_t len);
-uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start,
-								uint16_t *end);
-uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
-						uint8_t *pdu, size_t len);
-struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len,
-							uint8_t *format);
-uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len);
-uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen,
-						uint8_t *pdu, size_t len);
-uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint8_t *value, size_t vlen);
-uint16_t enc_confirmation(uint8_t *pdu, size_t len);
-
-uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len);
-uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu);
-uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len);
-uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu);
-
-uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset,
-					const uint8_t *value, size_t vlen,
-					uint8_t *pdu, size_t len);
-uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle,
-				uint16_t *offset, uint8_t *value, size_t *vlen);
-uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset,
-					const uint8_t *value, size_t vlen,
-					uint8_t *pdu, size_t len);
-uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle,
-						uint16_t *offset, uint8_t *value,
-						size_t *vlen);
-uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len);
-uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags);
-uint16_t enc_exec_write_resp(uint8_t *pdu);
-uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len);
diff --git a/attrib/gatt.c b/attrib/gatt.c
deleted file mode 100644
index 335094f37b09..000000000000
--- a/attrib/gatt.c
+++ /dev/null
@@ -1,103 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <glib.h>
-
-#include "bluetooth/bluetooth.h"
-#include "bluetooth/sdp.h"
-#include "bluetooth/sdp_lib.h"
-#include "bluetooth/uuid.h"
-
-#include "att.h"
-#include "gatt.h"
-
-static sdp_data_t *proto_seq_find(sdp_list_t *proto_list)
-{
-	sdp_list_t *list;
-	uuid_t proto;
-
-	sdp_uuid16_create(&proto, ATT_UUID);
-
-	for (list = proto_list; list; list = list->next) {
-		sdp_list_t *p;
-		for (p = list->data; p; p = p->next) {
-			sdp_data_t *seq = p->data;
-			if (seq && seq->dtd == SDP_UUID16 &&
-				sdp_uuid16_cmp(&proto, &seq->val.uuid) == 0)
-				return seq->next;
-		}
-	}
-
-	return NULL;
-}
-
-static gboolean parse_proto_params(sdp_list_t *proto_list, uint16_t *psm,
-						uint16_t *start, uint16_t *end)
-{
-	sdp_data_t *seq1, *seq2;
-
-	if (psm)
-		*psm = sdp_get_proto_port(proto_list, L2CAP_UUID);
-
-	/* Getting start and end handle */
-	seq1 = proto_seq_find(proto_list);
-	if (!seq1 || seq1->dtd != SDP_UINT16)
-		return FALSE;
-
-	seq2 = seq1->next;
-	if (!seq2 || seq2->dtd != SDP_UINT16)
-		return FALSE;
-
-	if (start)
-		*start = seq1->val.uint16;
-
-	if (end)
-		*end = seq2->val.uint16;
-
-	return TRUE;
-}
-
-gboolean gatt_parse_record(const sdp_record_t *rec,
-					uuid_t *prim_uuid, uint16_t *psm,
-					uint16_t *start, uint16_t *end)
-{
-	sdp_list_t *list;
-	uuid_t uuid;
-	gboolean ret;
-
-	if (sdp_get_service_classes(rec, &list) < 0)
-		return FALSE;
-
-	memcpy(&uuid, list->data, sizeof(uuid));
-	sdp_list_free(list, free);
-
-	if (sdp_get_access_protos(rec, &list) < 0)
-		return FALSE;
-
-	ret = parse_proto_params(list, psm, start, end);
-
-	sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL);
-	sdp_list_free(list, NULL);
-
-	/* FIXME: replace by bt_uuid_t after uuid_t/sdp code cleanup */
-	if (ret && prim_uuid)
-		memcpy(prim_uuid, &uuid, sizeof(uuid_t));
-
-	return ret;
-}
diff --git a/attrib/gatt.h b/attrib/gatt.h
deleted file mode 100644
index 0fe81db06fe6..000000000000
--- a/attrib/gatt.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- *
- *  BlueZ - Bluetooth protocol stack for Linux
- *
- *  Copyright (C) 2010  Nokia Corporation
- *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- */
-
-struct gatt_primary {
-	char uuid[MAX_LEN_UUID_STR + 1];
-	gboolean changed;
-	struct att_range range;
-};
-
-gboolean gatt_parse_record(const sdp_record_t *rec,
-					uuid_t *prim_uuid, uint16_t *psm,
-					uint16_t *start, uint16_t *end);
diff --git a/profiles/battery/battery.c b/profiles/battery/battery.c
index 2fe1f4aca2cf..ce4564356c33 100644
--- a/profiles/battery/battery.c
+++ b/profiles/battery/battery.c
@@ -40,7 +40,6 @@
 #include "src/service.h"
 #include "src/log.h"
 #include "src/battery.h"
-#include "attrib/att.h"
 
 #define BATTERY_INTERFACE "org.bluez.Battery1"
 
@@ -128,7 +127,7 @@ static void batt_io_ccc_written_cb(uint16_t att_ecode, void *user_data)
 
 	if (att_ecode != 0) {
 		error("Battery Level: notifications not enabled %s",
-		      att_ecode2str(att_ecode));
+		      bt_att_ecode2str(att_ecode));
 		return;
 	}
 
diff --git a/profiles/deviceinfo/deviceinfo.c b/profiles/deviceinfo/deviceinfo.c
index b1f6fb3ff749..2aaec84a8a4b 100644
--- a/profiles/deviceinfo/deviceinfo.c
+++ b/profiles/deviceinfo/deviceinfo.c
@@ -31,7 +31,6 @@
 #include "src/shared/att.h"
 #include "src/shared/gatt-db.h"
 #include "src/shared/gatt-client.h"
-#include "attrib/att.h"
 #include "src/log.h"
 
 #define PNP_ID_SIZE	7
@@ -43,7 +42,7 @@ static void read_pnpid_cb(bool success, uint8_t att_ecode, const uint8_t *value,
 
 	if (!success) {
 		error("Error reading PNP_ID value: %s",
-						att_ecode2str(att_ecode));
+						bt_att_ecode2str(att_ecode));
 		return;
 	}
 
diff --git a/profiles/midi/midi.c b/profiles/midi/midi.c
index ea4719b95b23..f432ccde92f8 100644
--- a/profiles/midi/midi.c
+++ b/profiles/midi/midi.c
@@ -31,7 +31,6 @@
 #include "src/shared/gatt-client.h"
 #include "src/shared/io.h"
 #include "src/log.h"
-#include "attrib/att.h"
 
 #include "libmidi.h"
 
@@ -134,7 +133,7 @@ static void midi_io_ccc_written_cb(uint16_t att_ecode, void *user_data)
 {
 	if (att_ecode != 0) {
 		error("MIDI I/O: notifications not enabled %s",
-		      att_ecode2str(att_ecode));
+		      bt_att_ecode2str(att_ecode));
 		return;
 	}
 
diff --git a/profiles/ranging/rap.c b/profiles/ranging/rap.c
index f3f677c4b4bb..0fffd3e032b7 100644
--- a/profiles/ranging/rap.c
+++ b/profiles/ranging/rap.c
@@ -33,7 +33,6 @@
 #include "src/shared/gatt-db.h"
 #include "src/shared/gatt-client.h"
 #include "src/shared/rap.h"
-#include "attrib/att.h"
 #include "src/log.h"
 #include "src/shared/cs-types.h"
 #include "src/btd.h"
diff --git a/profiles/scanparam/scan.c b/profiles/scanparam/scan.c
index a66f80eabf3e..d3c0809042be 100644
--- a/profiles/scanparam/scan.c
+++ b/profiles/scanparam/scan.c
@@ -31,7 +31,6 @@
 #include "src/shared/queue.h"
 #include "src/shared/gatt-db.h"
 #include "src/shared/gatt-client.h"
-#include "attrib/att.h"
 #include "src/btd.h"
 
 #define SCAN_INTERVAL_WIN_UUID		0x2A4F
@@ -91,7 +90,7 @@ static void refresh_ccc_written_cb(uint16_t att_ecode, void *user_data)
 {
 	if (att_ecode != 0) {
 		error("Scan Refresh: notifications not enabled %s",
-						att_ecode2str(att_ecode));
+						bt_att_ecode2str(att_ecode));
 		return;
 	}
 
diff --git a/src/adapter.c b/src/adapter.c
index 4131c4431dc6..b33192641047 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -45,6 +45,7 @@
 #include "src/shared/util.h"
 #include "src/shared/queue.h"
 #include "src/shared/att.h"
+#include "src/shared/crypto.h"
 #include "src/shared/gatt-db.h"
 #include "src/shared/timeout.h"
 
@@ -59,8 +60,6 @@
 #include "uuid-helper.h"
 #include "agent.h"
 #include "storage.h"
-#include "attrib/att.h"
-#include "attrib/gatt.h"
 #include "gatt-database.h"
 #include "advertising.h"
 #include "adv_monitor.h"
diff --git a/src/device.c b/src/device.c
index 7ed32a63cf19..a8bd29b7018c 100644
--- a/src/device.c
+++ b/src/device.c
@@ -38,6 +38,7 @@
 #include "log.h"
 #include "src/shared/util.h"
 #include "src/shared/att.h"
+#include "src/shared/crypto.h"
 #include "src/shared/queue.h"
 #include "src/shared/gatt-db.h"
 #include "src/shared/gatt-client.h"
@@ -46,8 +47,6 @@
 #include "src/shared/timeout.h"
 #include "btio/btio.h"
 #include "bluetooth/mgmt.h"
-#include "attrib/att.h"
-#include "attrib/gatt.h"
 #include "btd.h"
 #include "adapter.h"
 #include "gatt-database.h"
@@ -6477,6 +6476,81 @@ static bool remote_counter(uint32_t *sign_cnt, void *user_data)
 	return true;
 }
 
+static sdp_data_t *proto_seq_find(sdp_list_t *proto_list)
+{
+	sdp_list_t *list;
+	uuid_t proto;
+
+	sdp_uuid16_create(&proto, ATT_UUID);
+
+	for (list = proto_list; list; list = list->next) {
+		sdp_list_t *p;
+		for (p = list->data; p; p = p->next) {
+			sdp_data_t *seq = p->data;
+			if (seq && seq->dtd == SDP_UUID16 &&
+				sdp_uuid16_cmp(&proto, &seq->val.uuid) == 0)
+				return seq->next;
+		}
+	}
+
+	return NULL;
+}
+
+static gboolean parse_proto_params(sdp_list_t *proto_list, uint16_t *psm,
+						uint16_t *start, uint16_t *end)
+{
+	sdp_data_t *seq1, *seq2;
+
+	if (psm)
+		*psm = sdp_get_proto_port(proto_list, L2CAP_UUID);
+
+	/* Getting start and end handle */
+	seq1 = proto_seq_find(proto_list);
+	if (!seq1 || seq1->dtd != SDP_UINT16)
+		return FALSE;
+
+	seq2 = seq1->next;
+	if (!seq2 || seq2->dtd != SDP_UINT16)
+		return FALSE;
+
+	if (start)
+		*start = seq1->val.uint16;
+
+	if (end)
+		*end = seq2->val.uint16;
+
+	return TRUE;
+}
+
+gboolean gatt_parse_record(const sdp_record_t *rec,
+					uuid_t *prim_uuid, uint16_t *psm,
+					uint16_t *start, uint16_t *end)
+{
+	sdp_list_t *list;
+	uuid_t uuid;
+	gboolean ret;
+
+	if (sdp_get_service_classes(rec, &list) < 0)
+		return FALSE;
+
+	memcpy(&uuid, list->data, sizeof(uuid));
+	sdp_list_free(list, free);
+
+	if (sdp_get_access_protos(rec, &list) < 0)
+		return FALSE;
+
+	ret = parse_proto_params(list, psm, start, end);
+
+	sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(list, NULL);
+
+	/* FIXME: replace by bt_uuid_t after uuid_t/sdp code cleanup */
+	if (ret && prim_uuid)
+		memcpy(prim_uuid, &uuid, sizeof(uuid_t));
+
+	return ret;
+}
+
 bool device_attach_att(struct btd_device *dev, GIOChannel *io)
 {
 	GError *gerr = NULL;
diff --git a/src/device.h b/src/device.h
index f18fa5b2119f..fdc036ec4dae 100644
--- a/src/device.h
+++ b/src/device.h
@@ -9,6 +9,7 @@
  *
  */
 
+#include "bluetooth/uuid.h"
 #include "src/shared/queue.h"
 
 #define DEVICE_INTERFACE	"org.bluez.Device1"
@@ -67,6 +68,18 @@ void btd_device_set_record(struct btd_device *device, const char *uuid,
 							const char *record);
 const sdp_record_t *btd_device_get_record(struct btd_device *device,
 						const char *uuid);
+struct gatt_primary {
+	char uuid[MAX_LEN_UUID_STR + 1];
+	gboolean changed;
+	struct {
+		uint16_t start;
+		uint16_t end;
+	} range;
+};
+
+gboolean gatt_parse_record(const sdp_record_t *rec, uuid_t *prim_uuid,
+				uint16_t *psm, uint16_t *start, uint16_t *end);
+
 struct gatt_primary *btd_device_get_primary(struct btd_device *device,
 							const char *uuid);
 GSList *btd_device_get_primaries(struct btd_device *device);
diff --git a/src/shared/att.c b/src/shared/att.c
index 3d3c8cfa262a..e49743b5a764 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -2175,3 +2175,51 @@ done:
 
 	return true;
 }
+
+const char *bt_att_ecode2str(uint8_t ecode)
+{
+	switch (ecode) {
+	case BT_ATT_ERROR_INVALID_HANDLE:
+		return "Invalid handle";
+	case BT_ATT_ERROR_READ_NOT_PERMITTED:
+		return "Attribute can't be read";
+	case BT_ATT_ERROR_WRITE_NOT_PERMITTED:
+		return "Attribute can't be written";
+	case BT_ATT_ERROR_INVALID_PDU:
+		return "Attribute PDU was invalid";
+	case BT_ATT_ERROR_AUTHENTICATION:
+		return "Attribute requires authentication before read/write";
+	case BT_ATT_ERROR_REQUEST_NOT_SUPPORTED:
+		return "Server doesn't support the request received";
+	case BT_ATT_ERROR_INVALID_OFFSET:
+		return "Offset past the end of the attribute";
+	case BT_ATT_ERROR_AUTHORIZATION:
+		return "Attribute requires authorization before read/write";
+	case BT_ATT_ERROR_PREPARE_QUEUE_FULL:
+		return "Too many prepare writes have been queued";
+	case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND:
+		return "No attribute found within the given range";
+	case BT_ATT_ERROR_ATTRIBUTE_NOT_LONG:
+		return "Attribute can't be read/written using Read Blob Req";
+	case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE:
+		return "Encryption Key Size is insufficient";
+	case BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN:
+		return "Attribute value length is invalid";
+	case BT_ATT_ERROR_UNLIKELY:
+		return "Request attribute has encountered an unlikely error";
+	case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION:
+		return "Encryption required before read/write";
+	case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE:
+		return "Attribute type is not a supported grouping attribute";
+	case BT_ATT_ERROR_INSUFFICIENT_RESOURCES:
+		return "Insufficient Resources to complete the request";
+	case 0x80:
+		return "Internal application error: I/O";
+	case 0x81:
+		return "A timeout occurred";
+	case 0x82:
+		return "The operation was aborted";
+	default:
+		return "Unexpected error code";
+	}
+}
diff --git a/src/shared/att.h b/src/shared/att.h
index ba1f846777dc..5b5cd4fbab2c 100644
--- a/src/shared/att.h
+++ b/src/shared/att.h
@@ -29,6 +29,8 @@ bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close);
 
 int bt_att_get_fd(struct bt_att *att);
 
+const char *bt_att_ecode2str(uint8_t ecode);
+
 int bt_att_attach_fd(struct bt_att *att, int fd);
 
 int bt_att_get_channels(struct bt_att *att);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-09-28 17:33 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-28 17:32 [PATCH BlueZ v5 00/21] Add HoG functional tests and shared/hog Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 01/21] shared/gatt-client: Fix calling destroy after unregistering notify Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 02/21] client/gatt: Fix setting descriptor value from scripts Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 03/21] client/mgmt: Print Connection Subrate event Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 04/21] emulator: Default to the latest BR/EDR+LE version Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 05/21] client/scripts: Add HoG device scripts Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 06/21] doc: Add functional-hog documentation Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 07/21] test: functional: add HoG tests Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 08/21] test: functional: limit the workers by the memory available Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 09/21] client/agent: Fix crash on Cancel with no pending request Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 10/21] shared/uhid: Fix size of Get Report reply with a Report ID Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 11/21] shared/uhid: Keep reading when an event is not available Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 12/21] shared/tester: Allow expecting a PDU with no response Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 13/21] shared/hog: Add initial implementation Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 14/21] unit/test-hog: Use shared/hog Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 15/21] test: functional: change the HoG SCI mode with the HID Control Point Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 16/21] input/hog: Use shared/hog Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 17/21] doc: Add CONFIG_HIDRAW to the tester kernel config Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 18/21] unit/test-uhid: Add Get Report tests Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 19/21] device: Use bt_att instead of GAttrib Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 20/21] attrib: Remove GAttrib and gatttool Luiz Augusto von Dentz
2026-09-28 17:32 ` [PATCH BlueZ v5 21/21] attrib: Remove directory Luiz Augusto von Dentz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox